首页 > 解决方案 > 如何从二维数组创建和写入图像?

问题描述

我在 C# 中有一个Color[,]数组(二维数组)。Drawing.Color如何在本地将其保存为 PNG?

仅使用构建中提供的 .Net 官方包,无需额外的库或 Nuget 包。

标签: c#.net

解决方案


简单的方法

首先创建一个Bitmap具有所需尺寸的空白实例:

Bitmap bmp = new Bitmap(100, 100);

循环遍历您的颜色数组并绘制像素:

for (int i = 0; i < 100; i++)
{
  for (int j = 0; j < 100; j++)
  {
    bmp.SetPixel(i, j, colors[i,j]);
  }
}

最后,将您的位图保存到文件中:

bmp.Save("myfile.png", ImageFormat.Png);

更快的方法

Bitmap.SetPixel方法很慢。访问位图像素的更快方法是直接写入 32 位值数组(假设您正在拍摄 32 位 PNG),并让Bitmap类使用该数组作为其支持。

做到这一点的一种方法是创建所述数组,并在其上获取一个GCHandle以防止它被垃圾收集。该类Bitmap提供了一个构造函数,允许您从数组指针、像素格式和步幅(构成单行像素数据的字节数)创建一个实例:

public Bitmap (int width, int height, int stride,
  System.Drawing.Imaging.PixelFormat format, IntPtr scan0);

这是您将如何创建一个支持数组、一个句柄和一个Bitmap实例:

Int32[] bits = new Int32[width * height];
GCHandle handle = GCHandle.Alloc(bits, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(width, height, width * 4, 
  PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject());

注意:

  • 后备数组具有 32 位条目,因为我们使用的是 32 位像素格式
  • Bitmap步幅为,即width*4单行像素占用的字节数(每像素 4 个字节)

有了这个,您现在可以将像素值直接写入支持数组,它们将反映在Bitmap. 这比使用快得多Bitmap.SetPixel。这是一个代码示例,它假设您已经将所有内容都包含在一个知道位图的宽度和高度的类中:

public void SetPixelValue(int x, int y, int color)
{
  // Out of bounds?
  if (x < 0 || x >= Width || y < 0 || y >= Height) return;

  int index = x + (y * Width);
  Bits[index] = color;
}

请注意,这color是一个int值,而不是一个Color值。如果您有一个Color值数组,则必须将每个值转换为int第一个,例如

public void SetPixelColor(int x, int y, Color color)
{
  SetPixelValue(x, y, color.ToArgb());
}

这种转换需要时间,所以最好一直使用int值。如果您确定您从不使用越界坐标,您可以通过放弃 x/y 边界检查来加快速度:

public void SetPixelValueUnchecked(int x, int y, int color)
{
  // No out of bounds checking.
  int index = x + (y * Width);
  Bits[index] = color;
}

这里有一个警告。如果Bitmap以这种方式包装,您仍然可以Graphics通过直接访问实例来绘制线条、矩形、圆形等内容Bitmap,但不会获得通过固定数组的速度增益。如果您还希望更快地绘制这些图元,则必须提供自己的线/圆实现。请注意,根据我的经验,您自己的 Bresenham 线路例程几乎不会胜过 GDI 的内置例程,因此可能不值得。

更快的方法

如果您能够一次设置多个像素,事情可能会更快。如果您有具有相同颜色值的水平像素序列,这将适用。我发现在数组中设置序列的最快方法是使用Buffer.BlockCopy. (请参阅此处进行讨论)。这是一个实现:

/// <summary>
/// Set a sequential stretch of integers in the bitmap to a specified value.
/// This is done using a Buffer.BlockCopy that duplicates its size on each
/// pass for speed.
/// </summary>
/// <param name="value">Fill value</param>
/// <param name="startIndex">Fill start index</param>
/// <param name="count">Number of ints to fill</param>
private void FillUsingBlockCopy(Int32 value, int startIndex, int count)
{
  int numBytesInItem = 4;

  int block = 32, index = startIndex;
  int endIndex = startIndex + Math.Min(block, count);

  while (index < endIndex)          // Fill the initial block
    Bits[index++] = value;

  endIndex = startIndex + count;
  for (; index < endIndex; index += block, block *= 2)
  {
    int actualBlockSize = Math.Min(block, endIndex - index);
    Buffer.BlockCopy(Bits, startIndex * numBytesInItem, Bits, index * numBytesInItem, actualBlockSize * numBytesInItem);
  }
}

当您需要快速清除位图、使用水平线填充矩形或三角形(例如在三角形光栅化之后)时,这将特别有用。


推荐阅读