我在C#中有一个Color[,]
数组(二维数组,其中元素类型为Drawing.Color
),如何将其保存为PNG图像文件?
只使用构建中提供的 .Net 官方包,不使用任何其他库或 Nuget 包。
简单方法
首先创建一个指定尺寸的空白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());
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
,仍然可以直接访问Bitmap
实例来使用Graphics
绘制线条、矩形、圆等东西,但是没有通过固定数组获得速度增益。如果您希望这些原语也能更快地绘制,则必须提供自己的线条/圆形实现。请注意,根据我的经验,自己的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);
}
}
Bitmap
中像素的一些观察。 - Alexander van Oostenrijk// 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);
Bitmap.Save
。Bitmap 对象本身是一个三维数组(X、Y、RGB),因此你完全可以通过使用 Bitmap 而不是 Color 数组来避免使用二维颜色数组。Bitmap 对象包含设置/获取特定坐标处像素颜色的函数。 - Prime