user366312
user366312

Reputation: 16902

Code explanation for bitmap conversion

https://stackoverflow.com/a/2574798/159072

public static Bitmap BitmapTo1Bpp(Bitmap img) 
{
  int w = img.Width;
  int h = img.Height;
  //
  Bitmap bmp = new Bitmap(w, h, PixelFormat.Format1bppIndexed);
  BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);

Why this addition and division?

  byte[] scan = new byte[(w + 7) / 8];
  for (int y = 0; y < h; y++) 
  {
    for (int x = 0; x < w; x++) 
    {////Why this condition check?
      if (x % 8 == 0) 
      //Why divide by 8?
      scan[x / 8] = 0;
      Color c = img.GetPixel(x, y);
      //Why this condition check?
      if (c.GetBrightness() >= 0.5)
      { 
           // What is going on here?
           scan[x / 8] |= (byte)(0x80 >> (x % 8));
      }
    }
    // Why Martial.Copy() called here?
    Marshal.Copy(scan, 0, (IntPtr)((long)data.Scan0 + data.Stride * y), scan.Length);
  }
  bmp.UnlockBits(data);
  return bmp;
}

Upvotes: 0

Views: 175

Answers (1)

Hans Passant
Hans Passant

Reputation: 941455

The code uses some basic bit-hacking techniques, required because it needs to set bits and the minimum storage element you can address in C# is a byte. I intentionally avoided using the BitArray class.

   int w = img.Width;

I copy the Width and Height properties of the bitmap into a local variable to speed up the code, the properties are too expensive. Keep in mind that w are the number of pixels across the bitmap, it represents the number of bits in the final image.

   byte[] scan = new byte[(w + 7) / 8];

The scan variable stores the pixels in one scan line of the bitmap. The 1bpp format uses 1 bit per pixel so the total number of bytes in a scan line is w / 8. I add 7 to ensure the value is rounded up, necessary because integer division always truncates. w = 1..7 requires 1 byte, w = 8..15 requires 2 bytes, etcetera.

   if (x % 8 == 0) scan[x / 8] = 0;

The x % 8 expression represents the bit number, x / 8 is the byte number. This code sets all the pixels to Black when it progresses to the next byte in the scan line. Another way to do it would be re-allocating the byte[] in the outer loop or resetting it back to 0 with a for-loop.

   if (c.GetBrightness() >= 0.5)

The pixel should be set to White when the source pixel is bright enough. Otherwise it leaves it at Black. Using Color.Brightness is a simple way to avoid dealing with the human eye's non-linear perception of brightness (luminance ~= 0.299 * red + 0.587 * green + 0.114 * blue).

   scan[x / 8] |= (byte)(0x80 >> (x % 8));

Sets a bit to White in the scan line. As noted x % 8 is the bit number, it shifts 0x80 to the right by the bit number, they are stored in reverse order in this pixel format.

Upvotes: 1

Related Questions