Mega Code Archive

 
Categories / Delphi / Graphic
 

Optimized Bitmap to Region (HRGN) Conversion

Title: Optimized Bitmap to Region (HRGN) Conversion Question: How to convert a bitmap with any pixel depth to HRGN (Region), choosing a color and specifying a color tolerance to be made transparent. Very fast! Answer: UPDATE 25/05/2000 - Please, don't forget to see article #1009 for the Delphi-like replacement for this function. This article describes how to create a Window Region from any bitmap (of any color depth). The code is extremely fast, processing 32-bit true color bitmaps in milliseconds. The code wasn't originally created by me, but the translation to Object Pascal is my work. This article is also a very good example on how to convert C++ code to Object Pascal. The translated code was deliberately left as close to the original as possible for you to understand the subtilities of translating C++ to Delphi. The source code could be made much smaller and easier to understand (for the beginner) if we used delphi classes (TBitmap) to do the work (using TBitmap.ScanLines property among others) instead of using API functions, but then this article would not be used as a reference to converting C++ code to Delphi. I can submit the "Delphi-Like" version if there is interest on it. Please let me know if it is of your interest. Also, this procedure is used in a component I wrote called TFormShapper which allows you to apply some predefined shapes to a form (circle, ellipsis, rounded rectangle, etc.) or choose a bitmap to be used as a mask for the shape of the form. I will submit an article about TFormShapper later. What the function does is very simple (the implementation is not, as you can see for yourself): 1. Creates a 32-bit true color bitmap the same size of the original bitmap passed in hBmp. This bitmap is a DIBSection and is stored in hBmp32 2. Allocs memory for an area to store PRgnData which will be passed to ExCreateRegion. This memory area will contain TRect's created to describe the region. 3. Find the consecutive pixels of the same color or under the tolerance defined and creates a rect on the area described above with the dimensions of the area of equal pixels. After doing if for all the image pixels we will have the "Rects" for every "transparent to-be" areas of the Bitmap. 4. Returns the HRGN returned from the ExCreateRegion, which received the PRgnData with all the rects to create our region. For some reason and limitations on Windows 98 ExtCreateRegion may fail if the number of rectangles is too large (ie: 4000). The solution was to call it in multiple spets and combining the regions afterwards. It is very simple to call this routine, but since the first parameter is a HBITMAP, some people could get confused on how to use it. It is easy: just call it passing the TBimap.Handle property, e.g.: procedure TForm1.ApplyShapeToFormFromFile(const FileName: string); // what a long procedure name... var Bitmap: TBimap; rgn: HRGN; begin Bitmap := TBitmap.Free; try Bitmap.LoadFromFile(FileName); rgn := CreateRegion(Bitmap.Handle); SetWindowRgn(Self.Handle { form (window) handle }, rgn, True { repaint window }); // do not DeleteObject the HRGN because it is now owned by the operating system finally Bitmap.Free; end; end; The procedure default values for TransparenColor is black and for tolerance is 0 (which means no tolerance). So all black pixels will be turned transparent as default. Simply pass the other two optional parameters to change this behavior. I classified this procedure Delphi 4+ compatible because it uses optional parameters, if you ommit them then this procedure will work fine with Delphi 3. Thank you for you interest, and please study the code carefully if you want to really understand it. Felipe Rocha Machado *** *** Delphi code begins here (following it is the original C++ code for your *** reference...) *** {--- BitmapToRegion - The code below is a translation from the original C language version by Jean-Edouard Lachand-Robert hBmp : Source bitmap cTransparentColor: Color base for the "transparent" pixels(default black) cTolerance : Color tolerance for the "transparent" pixels. A pixel is assumed to be transparent if the value of each of its 3 components (blue, green and red) is greater or equal to the corresponding value in cTransparentColor and is lower or equal to the corresponding value in cTransparentColor + cTolerance. --- } function BitmapToRegion(hBmp: HBITMAP; cTransparentColor: COLORREF=0; cTolerance: COLORREF=$101010): HRGN; const AllocUnit = 100; MaxRects: DWORD = AllocUnit; type TRectArray = Array[0..0] of TRect; LONG = LongInt; PLONG = ^LONG; var h, Rgn: HRGN; hMemDC, h_DC: HDC; hBmp32, HoldBmp: HBITMAP; bm, bm32: BITMAP; RGB32BITSBITMAPINFO: BITMAPINFOHEADER; BITMAP_INFO: ^BITMAPINFO; pbBits32: pointer; hData: THandle; pData: PRgnData; lr, lg, lb, hr, hg, hb: Byte; b: Byte; p32: PByte; p: PLONG; x,y, x0: Integer; pr: ^TRectArray; begin Rgn := 0; { Create a memory DC inside which we will scan the bitmap content } hMemDC := CreateCompatibleDC(0); if hMemDC 0 then begin { get Bitmap size } GetObject(hBmp, SizeOf(bm), @bm); { Create a 32-bit depth bitmap and select it into the memory DC } with RGB32BITSBITMAPINFO do begin biSize := sizeof(BITMAPINFOHEADER); // biSize biWidth := bm.bmWidth; // biWidth; biHeight := bm.bmHeight; // biHeight; biPlanes := 1; // biPlanes; biBitCount := 32; // biBitCount biCompression := BI_RGB; // biCompression; biSizeImage := 0; // biSizeImage; biXPelsPerMeter := 0; // biXPelsPerMeter; biYPelsPerMeter := 0; // biYPelsPerMeter; biClrUsed := 0; // biClrUsed; biClrImportant := 0; // biClrImportant; end; BITMAP_INFO := @RGB32BITSBITMAPINFO; // points to the previous structure hBmp32 := CreateDIBSection(hMemDC, BITMAP_INFO^, DIB_RGB_COLORS, pbBits32, 0, 0); if hBmp32 0 then begin HoldBmp := SelectObject(hMemDC, hBmp32); { Create DC just to copy bitmap into the memory DC } h_DC := CreateCompatibleDC(hMemDC); if h_DC 0 then begin { Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits) } GetObject(hBmp32, SizeOf(bm32), @bm32); while (bm32.bmWidthBytes mod 4 0) do Inc(bm32.bmWidthBytes); { Copy the bitmap into the memory DC } HoldBmp := SelectObject(h_DC, hBmp); BitBlt(hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, h_DC, 0, 0, SRCCOPY); { For better performance, we will use the ExtCreateRegion() function to create the region. This function take a RGNDATA structure on entry. We will add rectangles by amount of ALLOC_UNIT number in this structure. } MaxRects := AllocUnit; hData := GlobalAlloc(GMEM_MOVEABLE, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * maxRects)); pData := GlobalLock(hData); with pData^.rdh do begin dwSize := SizeOf(RGNDATAHEADER); iType := RDH_RECTANGLES; nCount := 0; nRgnSize := 0; SetRect(rcBound, MAXLONG, MAXLONG, 0, 0); end; { Keep on hand highest and lowest values for the "transparent" pixels } lr := GetRValue(cTransparentColor); lg := GetGValue(cTransparentColor); lb := GetBValue(cTransparentColor); hr := Min($ff, lr + GetRValue(cTolerance)); hg := Min($ff, lg + GetGValue(cTolerance)); hb := Min($ff, lb + GetBValue(cTolerance)); { Scan each bitmap row from bottom to top (the bitmap is inverted vertically) } p32 := PByte(Integer(bm32.bmBits) + (bm32.bmHeight - 1) * bm32.bmWidthBytes); for y := 0 to bm.bmHeight - 1 do begin { Scan each bitmap pixel from left to right } // for x := 0 to bm.bmWidth - 1 do x := 0; while x begin { Search for a continuos range of "non transparent pixels" } x0 := x; p := PLONG(Integer(p32)+x*SizeOf(LONG)); while x begin b := GetRValue(p^); if (b = lr) and (b begin b := GetGValue(p^); if (b = lg) and (b begin b := GetBValue(p^); if (b = lb) and (b begin Break; // this pixel is transparent end; end; end; inc(p); inc(x); end; // while x if x x0 then begin { Add the pixels (x0, y) to (x, y+1) as a new rectangle in the region } if pData^.rdh.nCount = maxRects then begin GlobalUnlock(hData); Inc(maxRects,AllocUnit); hData := GlobalReAlloc(hData, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * MaxRects), GMEM_MOVEABLE); pData := GlobalLock(hData); end; pr := @pData^.Buffer; SetRect(pr^[pData^.rdh.nCount], x0, y, x, y+1); with pData^.rdh do begin if x0 if y if x rcBound.Right then rcBound.Right := x; if y+1 rcBound.Bottom then rcBound.Bottom := y+1; Inc(nCount); end; end; { On Windows98, ExtCreateRegion() may fail if the number of rectangles is too large (ie: 4000). Therefore, we have to create the region by multiple steps. } if pData^.rdh.nCount = 2000 then begin h := ExtCreateRegion(nil, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * maxRects), pData^); if Rgn 0 then begin CombineRgn(Rgn, Rgn, h, RGN_OR); DeleteObject(h); end else Rgn := h; pData^.rdh.nCount := 0; SetRect(pData^.rdh.rcBound, MAXLONG, MAXLONG, 0, 0); end; Inc(x); end; { for x := 0 to bm.Width - 1 (used a While loop to be able to make Inc(x);) } { Go to next row (remember, the bitmap is inverted vertically) } Dec(p32, bm32.bmWidthBytes); end; // for y := 0 to bm.Height - 1 { Create or extend the region with the remaining rectangles } h := ExtCreateRegion(nil, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * MaxRects), pData^); if Rgn 0 then begin CombineRgn(Rgn, Rgn, h, RGN_OR); DeleteObject(h); end else Rgn := h; GlobalFree(hData); SelectObject(h_DC, holdBmp); DeleteDC(h_DC); end; // if h_DC 0 DeleteObject(SelectObject(hMemDC, holdBmp)); end; // if hBmp32 0 DeleteObject(hMemDC); end; // if hMemDC 0 Result := Rgn; end; *** *** END OF DELPHI CODE *** *** *** C++ Code - Reference only - you may skip this if you wish *** // // BitmapToRegion : Create a region from the "non-transparent" pixels of a bitmap // Author : Jean-Edouard Lachand-Robert (http: //www.geocities.com/Paris/LeftBank/1160/resume.htm), June 1998. // // hBmp : Source bitmap // cTransparentColor : Color base for the "transparent" pixels (default is black) // cTolerance : Color tolerance for the "transparent" pixels. // // A pixel is assumed to be transparent if the value of each of its 3 components (blue, green and red) is // greater or equal to the corresponding value in cTransparentColor and is lower or equal to the // corresponding value in cTransparentColor + cTolerance. // HRGN BitmapToRegion (HBITMAP hBmp, COLORREF cTransparentColor = 0, COLORREF cTolerance = 0x101010) { HRGN hRgn = NULL; if (hBmp) { // Create a memory DC inside which we will scan the bitmap content HDC hMemDC = CreateCompatibleDC(NULL); if (hMemDC) { // Get bitmap size BITMAP bm; GetObject(hBmp, sizeof(bm), &bm); // Create a 32 bits depth bitmap and select it into the memory DC BITMAPINFOHEADER RGB32BITSBITMAPINFO = { sizeof(BITMAPINFOHEADER), // biSize bm.bmWidth, // biWidth; bm.bmHeight, // biHeight; 1, // biPlanes; 32, // biBitCount BI_RGB, // biCompression; 0, // biSizeImage; 0, // biXPelsPerMeter; 0, // biYPelsPerMeter; 0, // biClrUsed; 0 // biClrImportant; }; VOID * pbits32; HBITMAP hbm32 = CreateDIBSection(hMemDC, (BITMAPINFO *) &RGB32BITSBITMAPINFO, DIB_RGB_COLORS, &pbits32, NULL, 0); if (hbm32) { HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32); // Create a DC just to copy the bitmap into the memory DC HDC hDC = CreateCompatibleDC(hMemDC); if (hDC) { // Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits) BITMAP bm32; GetObject(hbm32, sizeof(bm32), &bm32); while (bm32.bmWidthBytes % 4) bm32.bmWidthBytes++; // Copy the bitmap into the memory DC HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBmp); BitBlt(hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, hDC, 0, 0, SRCCOPY); // For better performances, we will use the ExtCreateRegion() function to create the // region. This function take a RGNDATA structure on entry. We will add rectangles by // amount of ALLOC_UNIT number in this structure. #define ALLOC_UNIT 100 DWORD maxRects = ALLOC_UNIT; HANDLE hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects)); RGNDATA *pData = (RGNDATA *)GlobalLock(hData); pData-rdh.dwSize = sizeof(RGNDATAHEADER); pData-rdh.iType = RDH_RECTANGLES; pData-rdh.nCount = pData-rdh.nRgnSize = 0; SetRect(&pData-rdh.rcBound, MAXLONG, MAXLONG, 0, 0); // Keep on hand highest and lowest values for the "transparent" pixels BYTE lr = GetRValue(cTransparentColor); BYTE lg = GetGValue(cTransparentColor); BYTE lb = GetBValue(cTransparentColor); BYTE hr = min(0xff, lr + GetRValue(cTolerance)); BYTE hg = min(0xff, lg + GetGValue(cTolerance)); BYTE hb = min(0xff, lb + GetBValue(cTolerance)); // Scan each bitmap row from bottom to top (the bitmap is inverted vertically) BYTE *p32 = (BYTE *)bm32.bmBits + (bm32.bmHeight - 1) * bm32.bmWidthBytes; for (int y = 0; y { // Scan each bitmap pixel from left to right for (int x = 0; x { // Search for a continuous range of "non transparent pixels" int x0 = x; LONG *p = (LONG *)p32 + x; while (x { BYTE b = GetRValue(*p); if (b = lr && b { b = GetGValue(*p); if (b = lg && b { b = GetBValue(*p); if (b = lb && b // This pixel is "transparent" break; } } p++; x++; } if (x x0) { // Add the pixels (x0, y) to (x, y+1) as a new rectangle in the region if (pData-rdh.nCount = maxRects) { GlobalUnlock(hData); maxRects += ALLOC_UNIT; hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), GMEM_MOVEABLE); pData = (RGNDATA *)GlobalLock(hData); } RECT *pr = (RECT *)&pData-Buffer; SetRect(&pr[pData-rdh.nCount], x0, y, x, y+1); if (x0 rdh.rcBound.left) pData-rdh.rcBound.left = x0; if (y rdh.rcBound.top) pData-rdh.rcBound.top = y; if (x pData-rdh.rcBound.right) pData-rdh.rcBound.right = x; if (y+1 pData-rdh.rcBound.bottom) pData-rdh.rcBound.bottom = y+1; pData-rdh.nCount++; // On Windows98, ExtCreateRegion() may fail if the number of rectangles is too // large (ie: 4000). Therefore, we have to create the region by multiple steps. if (pData-rdh.nCount == 2000) { HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData); if (hRgn) { CombineRgn(hRgn, hRgn, h, RGN_OR); DeleteObject(h); } else hRgn = h; pData-rdh.nCount = 0; SetRect(&pData-rdh.rcBound, MAXLONG, MAXLONG, 0, 0); } } } // Go to next row (remember, the bitmap is inverted vertically) p32 -= bm32.bmWidthBytes; } // Create or extend the region with the remaining rectangles HRGN h = ExtCreateRegion(NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), pData); if (hRgn) { CombineRgn(hRgn, hRgn, h, RGN_OR); DeleteObject(h); } else hRgn = h; // Clean up GlobalFree(hData); SelectObject(hDC, holdBmp); DeleteDC(hDC); } DeleteObject(SelectObject(hMemDC, holdBmp)); } DeleteDC(hMemDC); } // if hMemDC } // if hBmp return hRgn; }