我在C#中有一个Color[,]
数组(Drawing.Color
的2d数组)。如何将其另存为PNG?
仅使用内部版本中提供的.Net官方软件包,而不使用其他库或Nuget软件包。
答案 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());
请注意:
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);