如何将现有的内存缓冲区包装为GDI的DC
我有一个与屏幕分辨率相对应的内存缓冲区(每像素24位时为1280x800),其中包含24bpp的屏幕内容。我想将其转换为8-bpp(即Windows中的半色调调色板)。
我目前正在这样做:
1.使用CreateDIBSection分配新的1280x800 24-bpp缓冲区,并将其作为DC和普通内存缓冲区进行访问
2.使用memcpy从我的原始缓冲区复制到第1步中的此新缓冲区
3.使用BitBlt让GDI执行颜色转换
我想避免步骤2的额外操作。为此,我可以想到两种方法:
一种。将我的原始mem buf包裹在DC中,以直接从中执行BitBlt
b。编写我自己的24-bpp到8-bpp颜色转换。我找不到有关Windows如何实现这种半色调颜色转换的任何信息。此外,即使我发现了问题,也不会使用BitBlt可以访问的GDI的加速功能。
那么我该怎么做(a)或者(b)?
谢谢!
解决方案
好,解决问题的两个部分。
- 以下代码显示了如何获取位图内部的像素,如何对其进行更改并将其放回到位图中。我们总是可以生成正确大小和格式的虚拟位图,将其打开,复制数据,然后获得一个带有数据的位图对象:
private void LockUnlockBitsExample(PaintEventArgs e) { // Create a new bitmap. Bitmap bmp = new Bitmap("c:\fakePhoto.jpg"); // Lock the bitmap's bits. Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height); System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat); // Get the address of the first line. IntPtr ptr = bmpData.Scan0; // Declare an array to hold the bytes of the bitmap. int bytes = bmpData.Stride * bmp.Height; byte[] rgbValues = new byte[bytes]; // Copy the RGB values into the array. System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes); // Set every third value to 255. A 24bpp bitmap will look red. for (int counter = 2; counter < rgbValues.Length; counter += 3) rgbValues[counter] = 255; // Copy the RGB values back to the bitmap System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes); // Unlock the bits. bmp.UnlockBits(bmpData); // Draw the modified image. e.Graphics.DrawImage(bmp, 0, 150); }
要将内容转换为8bpp,我们将需要使用System.Drawing.Imaging.ColorMatrix类。我手头没有正确的半色调矩阵值,但是此示例灰度和值的调整应使我们对效果有所了解:
Graphics g = e.Graphics; Bitmap bmp = new Bitmap("sample.jpg"); g.FillRectangle(Brushes.White, this.ClientRectangle); // Create a color matrix // The value 0.6 in row 4, column 4 specifies the alpha value float[][] matrixItems = { new float[] {1, 0, 0, 0, 0}, new float[] {0, 1, 0, 0, 0}, new float[] {0, 0, 1, 0, 0}, new float[] {0, 0, 0, 0.6f, 0}, new float[] {0, 0, 0, 0, 1}}; ColorMatrix colorMatrix = new ColorMatrix(matrixItems); // Create an ImageAttributes object and set its color matrix ImageAttributes imageAtt = new ImageAttributes(); imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); // Now draw the semitransparent bitmap image. g.DrawImage(bmp, this.ClientRectangle, 0.0f, 0.0f, bmp.Width, bmp.Height, GraphicsUnit.Pixel, imageAtt); imageAtt.Dispose();
我将稍后尝试使用半色调的矩阵值进行更新,那里可能有很多0.5或者0.333值!
使用CreateDIBitmap而不是CreateDIBSection。
如果要删除副本(第2步),只需首先使用CreateDIBSection创建原始内存缓冲区。然后,我们可以为该位图创建一个兼容的DC,并将其用作BitBlt操作的源。
IE。如果首先使用CreateDIBSection位图而不是"普通存储器"缓冲区,则在发送消息之前,无需从"普通存储器"缓冲区将内存复制到CreateDIBSection位图。
毕竟,使用CreateDIBSection分配的缓冲区本质上只是与CreateCompatibleDC兼容的"普通内存"缓冲区,这正是我们要寻找的。
首先,我们是如何将屏幕内容放入此24bpp内存缓冲区的?
避免不必要的内存转移的明显方法是首先创建24bpp DIBSection,然后将其传递给screengrab函数作为目标缓冲区,以破坏原始的screengrab。
如果那不可能,我们仍然可以尝试通过创建一个描述内存缓冲区格式的BITMAPINFOHEADER来强制GDI进行繁重的工作,然后调用StretchDIBits将其blit到8bpp DIBSection上。