// ported form VB6 (Ctls\PortUtil\StdCtl.cpp:6176) private unsafe bool DICopy(IntPtr hdcDest, IntPtr hdcSrc, RECT rect, bool bStretch) { long i; // Get the bitmap from the DC by selecting in a 1x1 pixel temp bitmap IntPtr hNullBitmap = SafeNativeMethods.CreateBitmap(1, 1, 1, 1, IntPtr.Zero); if (hNullBitmap == IntPtr.Zero) { return(false); } try { IntPtr hBitmap = Gdi32.SelectObject(hdcSrc, hNullBitmap); if (hBitmap == IntPtr.Zero) { return(false); } // Restore original bitmap Gdi32.SelectObject(hdcSrc, hBitmap); if (!Gdi32.GetObjectW(hBitmap, out Gdi32.BITMAP bmp)) { return(false); } var lpbmi = new Gdi32.BITMAPINFO { bmiHeader = new Gdi32.BITMAPINFOHEADER { biSize = (uint)sizeof(Gdi32.BITMAPINFOHEADER), biWidth = bmp.bmWidth, biHeight = bmp.bmHeight, biPlanes = 1, biBitCount = bmp.bmBitsPixel, biCompression = Gdi32.BI.RGB }, bmiColors = new byte[Gdi32.BITMAPINFO.MaxColorSize * 4] }; // Include the palette for 256 color bitmaps long iColors = 1 << (bmp.bmBitsPixel * bmp.bmPlanes); if (iColors <= 256) { byte[] aj = new byte[sizeof(Gdi32.PALETTEENTRY) * 256]; SafeNativeMethods.GetSystemPaletteEntries(hdcSrc, 0, (int)iColors, aj); fixed(byte *pcolors = lpbmi.bmiColors) { fixed(byte *ppal = aj) { Gdi32.RGBQUAD * prgb = (Gdi32.RGBQUAD *)pcolors; Gdi32.PALETTEENTRY *lppe = (Gdi32.PALETTEENTRY *)ppal; // Convert the palette entries to RGB quad entries for (i = 0; i < (int)iColors; i++) { prgb[i].rgbRed = lppe[i].peRed; prgb[i].rgbBlue = lppe[i].peBlue; prgb[i].rgbGreen = lppe[i].peGreen; } } } } // Allocate memory to hold the bitmap bits long bitsPerScanLine = bmp.bmBitsPixel * (long)bmp.bmWidth; long bytesPerScanLine = (bitsPerScanLine + 7) / 8; long totalBytesReqd = bytesPerScanLine * bmp.bmHeight; byte[] lpBits = new byte[totalBytesReqd]; // Get the bitmap bits int diRet = Gdi32.GetDIBits( hdcSrc, hBitmap, 0, (uint)bmp.bmHeight, lpBits, ref lpbmi, Gdi32.DIB.RGB_COLORS); if (diRet == 0) { return(false); } // Set the destination coordiates depending on whether stretch-to-fit was chosen int xDest, yDest, cxDest, cyDest; if (bStretch) { xDest = rect.left; yDest = rect.top; cxDest = rect.right - rect.left; cyDest = rect.bottom - rect.top; } else { xDest = rect.left; yDest = rect.top; cxDest = bmp.bmWidth; cyDest = bmp.bmHeight; } // Paint the bitmap int iRet = Gdi32.StretchDIBits( hdcDest, xDest, yDest, cxDest, cyDest, 0, 0, bmp.bmWidth, bmp.bmHeight, lpBits, ref lpbmi, Gdi32.DIB.RGB_COLORS, Gdi32.ROP.SRCCOPY); if (iRet == NativeMethods.GDI_ERROR) { return(false); } } finally { Gdi32.DeleteObject(hNullBitmap); } return(true); }
/// <summary> /// Copies a bitmap into a 1bpp/8bpp bitmap of the same dimensions, very fast. /// </summary> /// <param name="bitmap">A <see cref="Bitmap"/> with the output bitmap.</param> /// <param name="bitPerPixel">1 or 8, target bits per pixel.</param> /// <returns>A <see cref="Bitmap"/> with a converted copy of the bitmap.</returns> public static Bitmap CopyToBpp(Bitmap bitmap, int bitPerPixel) { if (bitPerPixel != 1 && bitPerPixel != 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 = bitmap.Width, h = bitmap.Height; IntPtr hbm = bitmap.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 Gdi32.BITMAPINFO bmi = new Gdi32.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)bitPerPixel; // ie. 1bpp or 8bpp bmi.BiCompression = BIRGB; // 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 << bitPerPixel; // 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 (bitPerPixel == 1) { bmi.Cols[0] = MAKERGB(0, 0, 0); bmi.Cols[1] = MAKERGB(255, 255, 255); } else { for (int i = 0; i < 180; i++) { bmi.Cols[i] = MAKERGB(i, i, i); } } // For 8bpp we've created an palette with just grayscale 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]); // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization // // 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 = Gdi32.CreateDIBSection( IntPtr.Zero, ref bmi, DIBRGBCOLORS, out bits0, IntPtr.Zero, 0); // Step (3): use GDI's BitBlt function to copy from output hbitmap into monocrhome bitmap // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC". IntPtr sdc = User32.GetDC(IntPtr.Zero); // First we obtain the DC for the screen // Next, create a DC for the output hbitmap IntPtr hdc = Gdi32.CreateCompatibleDC(sdc); Gdi32.SelectObject(hdc, hbm); // and create a DC for the monochrome hbitmap IntPtr hdc0 = Gdi32.CreateCompatibleDC(sdc); Gdi32.SelectObject(hdc0, hbm0); // Now we can do the BitBlt: Gdi32.BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, Gdi32.TernaryRasterOperations.SRCCOPY); // Step (4): convert this monochrome hbitmap back into a Bitmap: Bitmap b0 = Bitmap.FromHbitmap(hbm0); // Finally some cleanup. Gdi32.DeleteDC(hdc); Gdi32.DeleteDC(hdc0); User32.ReleaseDC(IntPtr.Zero, sdc); Gdi32.DeleteObject(hbm); Gdi32.DeleteObject(hbm0); return(b0); }