0

I'm using this code to save a bitmap as binary data.

Bitmap bmp = new Bitmap(screenWidth, position);            
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);    


Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);

System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);

IntPtr ptr = bmpData.Scan0;

int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];

System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);            
File.WriteAllBytes(filename, bmp);    
g.Dispose();

As I only need the first channel's values, is it possible to retrieve that from the bitmap? Performance is essential.

Lance U. Matthews
  • 15,725
  • 6
  • 48
  • 68
tommy_pop
  • 11
  • 1
  • 1
    Not sure how `File.WriteAllBytes(filename, bmp);` is ever supposed to work; bmp is a bitmap object, not a byte array. – Nyerguds Jul 25 '19 at 06:21
  • See marked duplicate for one of the many existing Stack Overflow questions with answers explaining how to extract individual color channels (RGBA) from a bitmap. – Peter Duniho Jul 25 '19 at 06:51
  • @PeterDuniho Kind of a bizarre answer there, though; it combines optimised `BitmapData` techniques with super-slow `SetPixel` ones. And, for the reasons mentioned in my answer here, it'll crash and burn on any input that's not 32bpp ARGB. – Nyerguds Jul 25 '19 at 08:08

1 Answers1

0

You're almost there, but there are a few key details missing:

  • Instead of using bmp.PixelFormat, force the pixel format for the BitmapData object to PixelFormat.Format32BppArgb, then you're 100% sure what structure you will get, and in 32-bit mode, the stride will always exactly match a predictable width * 4. If you don't do this, you may get unexpected results if the read image happens to be paletted or some sort of 16bpp format where each pixel can't be divided into simple colour component bytes.
  • Loop over the data and extract the channel. The order of the letters 'ARGB' refers to the a hexadecimal value 0xAARRGGBB (like, for example, 0xFF428ED0), which is a little-endian Uint32 value, meaning the actual order of the colour component bytes is the reverse: { BB, GG, RR, AA }.

So, to extract your channel:

// Channels are: B=0, G=1, R=2, A=3
Int32 channel = 1 // for this example, extract the Green channel.

Int32 width;
Int32 height;
Byte[] rgbaValues;
using (Bitmap bmp = new Bitmap(screenWidth, position))
using (Graphics g = Graphics.FromImage(bmp))
{
    width = bmp.Width
    height = bmp.Height;
    g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);    
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
    Int32 bytes = bmpData.Stride * bmp.Height;
    rgbaValues = new byte[bytes];
    Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
    bmp.UnlockBits(bmpData);
    g.Dispose();
}
Byte[] channelValues = new byte[width * height];

Int32 lineStart = 0;
Int32 lineStartChannel = 0;
for (Int32 y = 0; y < height; ++y)
{
    Int32 offset = lineStart;
    Int32 offsetChannel = lineStartChannel;
    for (Int32 x = 0; x < width; ++x)
    {
        // For reference:
        //Byte blue  = rgbaValues[offset + 0];
        //Byte green = rgbaValues[offset + 1];
        //Byte red   = rgbaValues[offset + 2];
        //Byte alpha = rgbaValues[offset + 3];
        channelValues[offsetChannel] = rgbaValues[offset + channel];
        offset += 4;
        offsetChannel++;
    }
    lineStart += stride;
    lineStartChannel += width;
}
File.WriteAllBytes(filename, channelValues);

This just saves the data as byte array. If you want to write it as image, the simplest way is probably to make an 8-bit bitmap, open a BitmapData object on it, and write the lines into it one by one, and then set its colour palette to a generated range from 0,0,0 to 255,255,255.

I posted a function that takes a byte array, image dimensions and a palette and makes an image out of it in this answer.

Nyerguds
  • 5,360
  • 1
  • 31
  • 63