如何从2D阵列创建和写入图像?

时间:2019-12-23 11:16:53

标签: c# .net

我在C#中有一个Color[,]数组(Drawing.Color的2d数组)。如何将其另存为PNG?

仅使用内部版本中提供的.Net官方软件包,而不使用其他库或Nuget软件包。

2 个答案:

答案 0 :(得分:2)

简单方法

首先创建一个具有所需尺寸的空白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。 (有关讨论,请参见here)。这是一个实现:

/// <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);
  }
}

当您需要一种快速的方法来清除位图,使用水平线填充矩形或三角形时(例如在triangle rasterization之后),这将特别有用。

答案 1 :(得分:2)

// Color 2D Array
var imgColors = new Color[128, 128];

// Get Image Width And Height Form Color Array
int imageH = imgColors.GetLength(0);
int imageW = imgColors.GetLength(1);

// Create Image Instance
Bitmap img = new Bitmap(imageW, imageH);

// Fill Colors on Our Image
for (int x = 0; x < img.Width; ++x)
{
    for (int y = 0; y < img.Height; ++y)
    {
        img.SetPixel(x, y, imgColors[x, y]);
    }
}

// Just Save it
img.Save("image.png", ImageFormat.Png);