I have a memory buffer corresponding to my screen resolution (1280x800 at 24-bits-per-pixel) that contains my screen contents at 24bpp. I want to convert this to 8-bpp (ie. Halftone color palette in Windows). I currently do this: 1. Use CreateDIBSection to allocate a new 1280x800 24-bpp buffer and access it as a DC, as well as a plain memory buffer 2. Use memcpy to copy from my original buffer to this new buffer from step 1 3. Use BitBlt to let GDI perform the color conversion

I want to avoid the extra memcpy of step 2. To do this, I can think of two approaches:

a. Wrap my original mem buf in a DC to perform BitBlt directly from it

b. Write my own 24-bpp to 8-bpp color conversion. I can't find any info on how Windows implements this halftone color conversion. Besides even if I find out, I won't be using the accelerated features of GDI that BitBlt has access to.

So how do I do either (a) or (b)?


+2  A: 

OK, to address the two parts of the problem.

  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,
       // 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.
       // Draw the modified image.
       e.Graphics.DrawImage(bmp, 0, 150);

To convert the contents to 8bpp you'll want to use the System.Drawing.Imaging.ColorMatrix class. I don't have at hand the correct matrix values for half-tone, but this example grayscales and adjustment of the values should give you an idea of the effect:

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);


I shall try and update later with the matrix values for half-tone, it's likely to be lots 0.5 or 0.333 values in there!

Ray Hayes
Is it okay to pass `Scan0` into unmanaged code to be operated upon?
Dmitri Nesteruk
I believe so, as long as you don't release your lock then it's pinned in place by the GC.
Ray Hayes
+1  A: 

Use CreateDIBitmap rather than CreateDIBSection.

Mike Dimmick

If you want to eliminate the copy (step 2), just use CreateDIBSection to create your original memory buffer in the first place. Then you can just create a compatible DC for that bitmap and use it as the source for the BitBlt operation.

I.e. there is no need to copy the memory from a "plain memory" buffer to a CreateDIBSection bitmap prior to blitting if you use a CreateDIBSection bitmap instead of a "plain memory" buffer in the first place.

After all, a buffer allocated using CreateDIBSection is essentially just a "plain memory" buffer that is compatible with CreateCompatibleDC, which is what you are looking for.


How did you get the screen contents into this 24bpp memory buffer in the first place?

The obvious route to avoiding a needless memcpy is to subvert the original screengrab by creating the 24bpp DIBSection first, and passing it to the screengrab function as the destination buffer.

If thats not possible, you can still try and coerce GDI into doing the hard lifting by creating a BITMAPINFOHEADER describing the format of the memory buffer, and just call StretchDIBits to blit it onto your 8bpp DIBSection.

Chris Becke