Skip to content

Instantly share code, notes, and snippets.

@wavescholar
Created April 25, 2014 17:33
Show Gist options
  • Save wavescholar/11297223 to your computer and use it in GitHub Desktop.
Save wavescholar/11297223 to your computer and use it in GitHub Desktop.
using System;
class klGDIBitmapConversion
{
public static System.Drawing.Bitmap MainConv(System.Drawing.Bitmap img)
{
System.Drawing.Bitmap b = img;// new System.Drawing.Bitmap("test.jpg");
SplashImage(b, 0, 0);
DateTime dtFaq = DateTime.Now;
System.Drawing.Bitmap b0 = CopyToBpp(b, 8);
TimeSpan tsFaq = DateTime.Now - dtFaq;
return b0;
}
// Fast copy a bitmap into a 1bpp/8bpp bitmap of the same dimensions
static System.Drawing.Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp)
{
if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp");
// Plan: built into Windows GDI is the ability to convert
// bitmaps from one format to another. Most of the time, this
// job is actually done by the graphics hardware accelerator card
// and so is extremely fast. The rest of the time, the job is done by
// very fast native code.
// We will call into this GDI functionality from C#. Our plan:
// (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed)
// (2) Create a GDI monochrome hbitmap
// (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above)
// (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed)
int w = b.Width, h = b.Height;
IntPtr hbm = b.GetHbitmap(); // this is step (1)
//
// Step (2): create the monochrome bitmap.
// "BITMAPINFO" is an interop-struct which we define below.
// In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs
BITMAPINFO bmi = new BITMAPINFO();
bmi.biSize = 40; // the size of the BITMAPHEADERINFO struct
bmi.biWidth = w;
bmi.biHeight = h;
bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info.
bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp
bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes
bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8);
bmi.biXPelsPerMeter = 1000000; // not really important
bmi.biYPelsPerMeter = 1000000; // not really important
// Now for the colour table.
uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp
bmi.biClrUsed = ncols;
bmi.biClrImportant = ncols;
bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours
if (bpp == 1) { bmi.cols[0] = MAKERGB(0, 0, 0); bmi.cols[1] = MAKERGB(255, 255, 255); }
else { for (int i = 0; i < ncols; i++) bmi.cols[i] = MAKERGB(i, i, i); }
// For 8bpp we've created an palette with just greyscale colours.
// You can set up any palette you want here. Here are some possibilities:
// greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i);
// rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255};
// for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]);
//
// Now create the indexed bitmap "hbm0"
IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap.
IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0);
//
// Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap
// GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC".
IntPtr sdc = GetDC(IntPtr.Zero); // First we obtain the DC for the screen
// Next, create a DC for the original hbitmap
IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm);
// and create a DC for the monochrome hbitmap
IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0);
// Now we can do the BitBlt:
BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
// Step (4): convert this monochrome hbitmap back into a Bitmap:
System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0);
//
// Finally some cleanup.
DeleteDC(hdc);
DeleteDC(hdc0);
ReleaseDC(IntPtr.Zero, sdc);
DeleteObject(hbm);
DeleteObject(hbm0);
//
return b0;
}
// Draws a bitmap onto the screen. Note: this will be overpainted
// by other windows when they come to draw themselves. Only use it
// if you want to draw something quickly and can't be bothered with forms.
static void SplashImage(System.Drawing.Bitmap b, int x, int y)
{ // Drawing onto the screen is supported by GDI, but not by the Bitmap/Graphics class.
// So we use interop:
// (1) Copy the Bitmap into a GDI hbitmap
IntPtr hbm = b.GetHbitmap();
// (2) obtain the GDI equivalent of a "Graphics" for the screen
IntPtr sdc = GetDC(IntPtr.Zero);
// (3) obtain the GDI equivalent of a "Graphics" for the hbitmap
IntPtr hdc = CreateCompatibleDC(sdc);
SelectObject(hdc, hbm);
// (4) Draw from the hbitmap's "Graphics" onto the screen's "Graphics"
BitBlt(sdc, x, y, b.Width, b.Height, hdc, 0, 0, SRCCOPY);
// and do boring GDI cleanup:
DeleteDC(hdc);
ReleaseDC(IntPtr.Zero, sdc);
DeleteObject(hbm);
}
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int InvalidateRect(IntPtr hwnd, IntPtr rect, int bErase);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hwnd);
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc);
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int DeleteDC(IntPtr hdc);
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop);
static int SRCCOPY = 0x00CC0020;
//Specifies the raster operation to be performed.
//Raster-operation codes define how the GDI combines colors in output operations that involve a current brush, a possible source bitmap, and a destination bitmap.
//See BitBlt in the Platform SDK for a list of the raster-operation codes for dwRop and their descriptions
//dwRop
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset);
static uint BI_RGB = 0;
static uint DIB_RGB_COLORS = 0;
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct BITMAPINFO
{
public uint biSize;
public int biWidth, biHeight;
public short biPlanes, biBitCount;
public uint biCompression, biSizeImage;
public int biXPelsPerMeter, biYPelsPerMeter;
public uint biClrUsed, biClrImportant;
[System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256)]
public uint[] cols;
}
static uint MAKERGB(int r, int g, int b)
{
return ((uint)(b & 255)) | ((uint)((r & 255) << 8)) | ((uint)((g & 255) << 16));
}
static System.Drawing.Bitmap CopyTo1bpp(System.Drawing.Bitmap b)
{
int w = b.Width, h = b.Height; System.Drawing.Rectangle r = new System.Drawing.Rectangle(0, 0, w, h);
if (b.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
{
System.Drawing.Bitmap temp = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(temp);
g.DrawImage(b, r, 0, 0, w, h, System.Drawing.GraphicsUnit.Pixel);
g.Dispose(); b = temp;
}
System.Drawing.Imaging.BitmapData bdat = b.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadOnly, b.PixelFormat);
System.Drawing.Bitmap b0 = new System.Drawing.Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
System.Drawing.Imaging.BitmapData b0dat = b0.LockBits(r, System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
int index = y * bdat.Stride + (x * 4);
if (System.Drawing.Color.FromArgb(System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 2), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index + 1), System.Runtime.InteropServices.Marshal.ReadByte(bdat.Scan0, index)).GetBrightness() > 0.5f)
{
int index0 = y * b0dat.Stride + (x >> 3);
byte p = System.Runtime.InteropServices.Marshal.ReadByte(b0dat.Scan0, index0);
byte mask = (byte)(0x80 >> (x & 0x7));
System.Runtime.InteropServices.Marshal.WriteByte(b0dat.Scan0, index0, (byte)(p | mask));
}
}
}
b0.UnlockBits(b0dat);
b.UnlockBits(bdat);
return b0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment