Ped*_*o77 38 c# bitmap bitmapdata
这个问题是关于如何读/写,分配和管理位图的像素数据.
下面是一个如何为像素数据分配字节数组(托管内存)并使用它创建位图的示例:
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
Run Code Online (Sandbox Code Playgroud)
//但并非总是如此.更多信息在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;
}
Run Code Online (Sandbox Code Playgroud)
我认为Bitmap会复制数组数据,但它实际上指的是相同的数据.你能看到:
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]
Run Code Online (Sandbox Code Playgroud)
问题:
从byte []数组(托管内存)和free()GCHandle创建位图是否安全?如果它不安全,我需要保持固定阵列,这对GC/Performance有多糟糕?
更改数据是否安全(例如:data [0] = 255;)?
可以通过GC更改Scan0的地址?我的意思是,我从一个锁定的位图获取Scan0,然后将其解锁,经过一段时间再次锁定它,Scan0可能会有所不同?
LockBits方法中ImageLockMode.UserInputBuffer的用途是什么?很难找到相关的信息!MSDN没有明确解释清楚!
编辑1:一些后续行动
你需要保持固定.它会降低GC的速度吗?我在这里问过它.这取决于图像的数量及其大小.没有人给我一个定量答案.它接缝很难确定.您还可以使用Marshal分配内存或使用Bitmap分配的非托管内存.
我用两个线程做了很多测试.只要位图被锁定就可以了.如果位图解锁,则不安全!我的相关帖子关于直接读/写Scan0.Boing的回答"我已经在上面解释了为什么你能够在锁外使用scan0是幸运的.因为你使用原始的bmp PixelFormat并且在这种情况下GDI被优化以给你指针而不是副本.这个指针是有效的直到操作系统决定释放它.唯一一次保证是在LockBits和UnLockBits之间.期间."
是的,它可能会发生,但是大的内存区域被GC处理不同,它不那么频繁地移动/释放这个大对象.因此GC移动此阵列可能需要一段时间.从MSDN:"任何分配大于或等于85,000 bytes
那张large object heap (LOH)
"一代2收集过程中LOH只收集"" ........NET 4.5在LOH方面有所改进.
@Boing回答了这个问题.但我会承认.我没有完全理解它.所以,如果Boing
或其他人可以please clarify it
,我会很高兴.那么,为什么我不能直接读/写Sca0而不锁定?=>您不应直接写入Scan0,因为Scan0指向由非托管内存(GDI内部)生成的位图数据的副本.解锁后,这个内存可以重新分配给其他东西,它不再确定Scan0将指向实际的位图数据.这可以重现,让Scan0处于锁定状态,解锁,并在解锁的位图中进行一些旋转.一段时间后,Scan0将指向一个无效区域,当您尝试读取/写入其内存位置时,您将收到异常.
Pet*_*art 24
从数组创建灰度位图的示例代码:
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;
Run Code Online (Sandbox Code Playgroud)
Boi*_*ing 10
关于你的问题4:在ImageLockMode.UserInputBuffer
能给你可能被引用到的那些记忆大量的分配过程的控制BitmapData
对象.
如果你选择创建自己的BitmapData
对象,你可以避免Marshall.Copy
.然后你必须在另一个组合中使用这个标志ImageLockMode
.
请注意,这是一项复杂的业务,特别是关于Stride和PixelFormat.
下面是一个例子,它可以一次性将24bbp缓冲区的内容放到BitMap上,然后在另一个镜头中将其读回另一个缓冲区,转换为48bbp.
Size size = Image.Size;
Bitmap bitmap = Image;
// myPrewrittenBuff is allocated just like myReadingBuffer below (skipped for space sake)
// But with two differences: the buff would be byte [] (not ushort[]) and the Stride == 3 * size.Width (not 6 * ...) because we build a 24bpp not 48bpp
BitmapData writerBuff= bm.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb, myPrewrittenBuff);
// note here writerBuff and myPrewrittenBuff are the same reference
bitmap.UnlockBits(writerBuff);
// done. bitmap updated , no marshal needed to copy myPrewrittenBuff
// Now lets read back the bitmap into another format...
BitmapData myReadingBuffer = new BitmapData();
ushort[] buff = new ushort[(3 * size.Width) * size.Height]; // ;Marshal.AllocHGlobal() if you want
GCHandle handle= GCHandle.Alloc(buff, GCHandleType.Pinned);
myReadingBuffer.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
myReadingBuffer.Height = size.Height;
myReadingBuffer.Width = size.Width;
myReadingBuffer.PixelFormat = PixelFormat.Format48bppRgb;
myReadingBuffer.Stride = 6 * size.Width;
// now read into that buff
BitmapData result = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb, myReadingBuffer);
if (object.ReferenceEquals(result, myReadingBuffer)) {
// Note: we pass here
// and buff is filled
}
bitmap.UnlockBits(result);
handle.Free();
// use buff at will...
Run Code Online (Sandbox Code Playgroud)
如果您使用ILSpy,您将看到此方法链接到GDI +,这些方法有助于更完整.
您可以使用自己的内存方案来提高性能,但要注意Stride可能需要进行一些对齐才能获得最佳性能.
然后,您将能够疯狂地分配巨大的虚拟内存映射scan0并非常有效地进行blit.请注意,固定大型阵列(尤其是少数阵列)不会对GC造成负担,并且允许您以完全安全的方式操作字节/短路(或者如果您寻求速度则不安全)
我不确定你是否有理由按照自己的方式去做.也许有.看起来你已经走得太远了,所以你可能会尝试做一些比问题标题所暗示的更先进的事情......
但是,从Byte数组创建Bitmap的传统方法是:
using (MemoryStream stream = new MemoryStream(byteArray))
{
Bitmap bmp = new Bitmap(stream);
// use bmp here....
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
74985 次 |
最近记录: |