如何将现有的内存缓冲区作为GDI的DC进行包装

3
我有一个与我的屏幕分辨率(1280x800,每像素24位)对应的内存缓冲区,其中包含24bpp格式的屏幕内容。我想将其转换为8bpp格式(即Windows中的Halftone调色板)。目前我是这样做的:
1.使用CreateDIBSection来分配一个新的1280x800 24bpp缓冲区,并将其作为DC访问,以及一个普通的内存缓冲区 2.使用memcpy从步骤1中的新缓冲区复制到我的原始缓冲区 3.使用BitBlt让GDI执行颜色转换
我想避免步骤2中额外的memcpy操作。为此,我可以考虑两种方法:
a.将我的原始内存缓冲区包装在一个DC中,直接从中执行BitBlt
b.编写自己的24bpp到8bpp颜色转换。我找不到关于Windows如何实现这种Halftone颜色转换的任何信息。此外,即使我找到了,我也无法使用BitBlt具有访问权限的GDI加速功能。
那么我该如何实现(a)或(b)呢?
谢谢!
4个回答

2

好的,针对问题的两个部分进行解答。

  1. the following code shows how to get at the pixels inside of a bitmap, change them and put them back into the bitmap. You could always generate a dummy bitmap of the correct size and format, open it up, copy over your data and you then have a bitmap object with your data:

    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值!


Scan0 传递到非托管代码进行操作是否可行? - Dmitri Nesteruk
我相信是这样的,只要你不释放锁,那么它就会被GC固定在原地。 - Ray Hayes

1
请使用CreateDIBitmap而不是CreateDIBSection。

1

如果你想要消除复制(第二步),只需在一开始使用CreateDIBSection创建原始内存缓冲区即可。然后,您可以为该位图创建兼容的DC,并将其用作BitBlt操作的源。

也就是说,如果您一开始就使用CreateDIBSection位图而不是“纯内存”缓冲区,则没有必要在位块传输之前从“纯内存”缓冲区复制内存到CreateDIBSection位图。

毕竟,使用CreateDIBSection分配的缓冲区本质上只是与CreateCompatibleDC兼容的“纯内存”缓冲区,这正是您要寻找的。


Win32程序员参考书第3卷中CreateDIBSection例程的描述完全错误。请查看网站获取正确的描述。https://learn.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createdibsection - user2707695

0

你是如何将屏幕内容首先存入这个24bpp内存缓冲区的?

避免不必要的memcpy的明显方法是通过创建24bpp DIBSection并将其作为目标缓冲区传递给screengrab函数来破坏原始screengrab。

如果不可能,您仍然可以尝试强制GDI执行艰苦的工作,方法是创建一个BITMAPINFOHEADER来描述内存缓冲区的格式,并调用StretchDIBits将其复制到8bpp DIBSection上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接