c# - 如何从二维数组创建和写入图像?
问题描述
我在 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());
注意:
- 后备数组具有 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);
}
}
当您需要快速清除位图、使用水平线填充矩形或三角形(例如在三角形光栅化之后)时,这将特别有用。
推荐阅读
- php - 循环检查复选框并删除所有勾选的类别的正确方法是尽可能少的查询?
- android - 适用于 Android 的 OpenJML/Jessie
- rebus - 通用错误处理程序生成错误消息重复
- javascript - 如何使用 tus-client 恢复和上传文件上传
- python - Python 脚本通过任务计划程序运行后内存使用量稳步增加
- awk - 根据字符串的出现对列重新编号
- apache-kafka-streams - 使用 1 个分区时何时在 KTable 上使用 GlobalKTable
- sql - 在 isNull 上连接 NULL+字符串
- c++ - C++ 伪随机数生成平台依赖
- r - 在 R 中合并连续的日期范围