小编典典

从像素数据的字节数组创建位图

c#

这个问题是关于如何读取/写入,分配和管理位图的像素数据。

这是一个如何为像素数据分配字节数组(托管内存)并使用它创建位图的示例:

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]

问题:

  1. 从byte []数组(托管内存)和free()GCHandle创建位图是否安全?如果不安全,则需要保留固定的阵列,这对GC / Performance有多严重?

  2. 更改数据是否安全(例如:data [0] = 255;)?

  3. GC可以更改Scan0的地址吗?我的意思是,我从锁定的位图获取Scan0,然后将其解锁,再过一段时间锁定后,Scan0会有所不同吗?

  4. LockBits方法中ImageLockMode.UserInputBuffer的用途是什么?很难找到有关该信息!MSDN没有清楚地解释它!

编辑1:一些后续

  1. 您需要保持固定。会降低GC的速度吗?我在这里问过。这取决于图像的数量及其大小。没有人给我定量的答案。似乎很难确定。您也可以使用元数据分配内存,也可以使用位图分配的非托管内存。

  2. 我已经使用两个线程做了很多测试。只要位图被锁定就可以。如果位图是解锁的,那将是不安全的!我与之相关的文章直接关于Scan0的读/写。Boing的回答“我已经在上面解释了为什么您很幸运能够在锁之外使用scan0。因为您使用的是原始bmp PixelFormat,并且在这种情况下对GDI进行了优化,可以为您提供指针而不是副本。此指针有效直到操作系统决定释放它为止。唯一可以保证的时间是在LockBits和UnLockBits之间。

  3. 是的,可能会发生,但是GC对大型内存区域的处理方式有所不同,它不大频繁地移动/释放此大型对象。因此,GC可能需要一段时间才能移动该阵列。来自MSDN:“任何大于或等于的分配85,000 bytes都将在large object heap (LOH)… … 上进行:”仅在第二代收集期间收集LOH”。.NET 4.5的LOH有所改进。

  4. @Boing已回答了这个问题。但是我要承认。我没有完全理解它。因此,如果Boing有其他人可以please clarify it,我将很高兴。顺便说一句,为什么我不能不加锁直接读写Sca0?=>您不应直接写入Scan0,因为Scan0指向由非托管内存(在GDI内部)生成的位图数据的副本。解锁后,可以将该内存重新分配给其他内容,不再确定Scan0将指向实际的位图数据。可以将Scan0锁定,解锁并在解锁的位图中进行一些旋转平移,以重现这一点。一段时间后,Scan0将指向无效区域,并且尝试读取/写入其内存位置时将获得异常。


阅读 339

收藏
2020-05-19

共1个答案

小编典典

  1. 如果封送处理数据而不是设置scan0(直接或通过BitMap()的重载)进行设置,这是安全的。您不想让托管对象固定,这将限制垃圾收集器。
  2. 如果您进行复制,绝对安全。
  3. 输入数组是托管的,可以由GC进行移动,scan0是非托管指针,如果移动数组,该指针将过期。Bitmap对象本身是受管理的,但通过句柄在Windows中设置scan0指针。
  4. ImageLockMode.UserInputBuffer是吗?显然,它可以传递给LockBits,也许它告诉Bitmap()复制输入数组数据。

从数组创建灰度位图的示例代码:

    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;
2020-05-19