这个问题是关于如何读取/写入,分配和管理位图的像素数据。
这是一个如何为像素数据分配字节数组(托管内存)并使用它创建位图的示例:
Size size = new Size(800, 600); PixelFormat pxFormat = PixelFormat.Format8bppIndexed; //Get the stride, in this case it will have the same length of the width. //Because the image Pixel format is 1 Byte/pixel. //Usually stride = "ByterPerPixel"*Width
//但这并不总是正确的。在bobpowell的更多信息。
int stride = GetStride(size.Width, pxFormat); byte[] data = new byte[stride * size.Height]; GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned); Bitmap bmp = new Bitmap(size.Width, size.Height, stride, pxFormat, handle.AddrOfPinnedObject()); //After doing your stuff, free the Bitmap and unpin the array. bmp.Dispose(); handle.Free(); public static int GetStride(int width, PixelFormat pxFormat) { //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format); int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF; //Number of bits used to store the image data per line (only the valid data) int validBitsPerLine = width * bitsPerPixel; //4 bytes for every int32 (32 bits) int stride = ((validBitsPerLine + 31) / 32) * 4; return stride; }
我以为该位图可以复制数组数据,但实际上它指向相同的数据。您可以看到:
Color c; c = bmp.GetPixel(0, 0); Console.WriteLine("Color before: " + c.ToString()); //Prints: Color before: Color [A=255, R=0, G=0, B=0] data[0] = 255; c = bmp.GetPixel(0, 0); Console.WriteLine("Color after: " + c.ToString()); //Prints: Color after: Color [A=255, R=255, G=255, B=255]
问题:
从byte []数组(托管内存)和free()GCHandle创建位图是否安全?如果不安全,则需要保留固定的阵列,这对GC / Performance有多严重?
更改数据是否安全(例如:data [0] = 255;)?
GC可以更改Scan0的地址吗?我的意思是,我从锁定的位图获取Scan0,然后将其解锁,再过一段时间锁定后,Scan0会有所不同吗?
LockBits方法中ImageLockMode.UserInputBuffer的用途是什么?很难找到有关该信息!MSDN没有清楚地解释它!
编辑1:一些后续
您需要保持固定。会降低GC的速度吗?我在这里问过。这取决于图像的数量及其大小。没有人给我定量的答案。似乎很难确定。您也可以使用元数据分配内存,也可以使用位图分配的非托管内存。
我已经使用两个线程做了很多测试。只要位图被锁定就可以。如果位图是解锁的,那将是不安全的!我与之相关的文章直接关于Scan0的读/写。Boing的回答“我已经在上面解释了为什么您很幸运能够在锁之外使用scan0。因为您使用的是原始bmp PixelFormat,并且在这种情况下对GDI进行了优化,可以为您提供指针而不是副本。此指针有效直到操作系统决定释放它为止。唯一可以保证的时间是在LockBits和UnLockBits之间。
是的,可能会发生,但是GC对大型内存区域的处理方式有所不同,它不大频繁地移动/释放此大型对象。因此,GC可能需要一段时间才能移动该阵列。来自MSDN:“任何大于或等于的分配85,000 bytes都将在large object heap (LOH)… … 上进行:”仅在第二代收集期间收集LOH”。.NET 4.5的LOH有所改进。
85,000 bytes
large object heap (LOH)
@Boing已回答了这个问题。但是我要承认。我没有完全理解它。因此,如果Boing有其他人可以please clarify it,我将很高兴。顺便说一句,为什么我不能不加锁就直接读写Sca0?=>您不应直接写入Scan0,因为Scan0指向由非托管内存(在GDI内部)生成的位图数据的副本。解锁后,可以将该内存重新分配给其他内容,不再确定Scan0将指向实际的位图数据。可以将Scan0锁定,解锁并在解锁的位图中进行一些旋转平移,以重现这一点。一段时间后,Scan0将指向无效区域,并且尝试读取/写入其内存位置时将获得异常。
Boing
please clarify it
从数组创建灰度位图的示例代码:
var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed); ColorPalette ncp = b.Palette; for (int i = 0; i < 256; i++) ncp.Entries[i] = Color.FromArgb(255, i, i, i); b.Palette = ncp; var BoundsRect = new Rectangle(0, 0, Width, Height); BitmapData bmpData = b.LockBits(BoundsRect, ImageLockMode.WriteOnly, b.PixelFormat); IntPtr ptr = bmpData.Scan0; int bytes = bmpData.Stride*b.Height; var rgbValues = new byte[bytes]; // fill in rgbValues, e.g. with a for loop over an input array Marshal.Copy(rgbValues, 0, ptr, bytes); b.UnlockBits(bmpData); return b;