// // BitmapToRegion : Create a region from the "non-transparent" pixels of a bitmap // // 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; ASSERT(hBmp); if (hBmp) { // Create a memory DC inside which we will scan the bitmap content HDC hMemDC = CreateCompatibleDC(NULL); ASSERT(hMemDC); 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); ASSERT(hbm32); if (hbm32) { HBITMAP holdBmp = (HBITMAP)SelectObject(hMemDC, hbm32); // Create a DC just to copy the bitmap into the memory DC HDC hDC = CreateCompatibleDC(hMemDC); ASSERT(hDC); if (hDC) { // Get how many bytes per row we have for the bitmap bits (rounded up to 32 bits) BITMAP bm32; VERIFY(GetObject(hbm32, sizeof(bm32), &bm32)); while (bm32.bmWidthBytes % 4) bm32.bmWidthBytes++; // Copy the bitmap into the memory DC HBITMAP holdBmp = (HBITMAP)SelectObject(hDC, hBmp); VERIFY(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 < bm.bmHeight; y++) { // Scan each bitmap pixel from left to right for (int x = 0; x < bm.bmWidth; x++) { // Search for a continuous range of "non transparent pixels" int x0 = x; LONG *p = (LONG *)p32 + x; while (x < bm.bmWidth) { BYTE b = GetRValue(*p); if (b >= lr && b <= hr) { b = GetGValue(*p); if (b >= lg && b <= hg) { b = GetBValue(*p); if (b >= lb && b <= hb) // 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; VERIFY(hData = GlobalReAlloc(hData, sizeof(RGNDATAHEADER) + (sizeof(RECT) * maxRects), GMEM_MOVEABLE)); pData = (RGNDATA *)GlobalLock(hData); ASSERT(pData); } RECT *pr = (RECT *)&pData->Buffer; SetRect(&pr[pData->rdh.nCount], x0, y, x, y+1); if (x0 < pData->rdh.rcBound.left) pData->rdh.rcBound.left = x0; if (y < pData->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); ASSERT(h); 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); ASSERT(h); if (hRgn) { CombineRgn(hRgn, hRgn, h, RGN_OR); DeleteObject(h); } else hRgn = h; // Clean up,这儿需要再调用一下GlobalFree,否则有内存泄漏 SelectObject(hDC, holdBmp); DeleteDC(hDC); } DeleteObject(SelectObject(hMemDC, holdBmp)); } DeleteDC(hMemDC); } } return hRgn; }
相应工程的示例代码:
另有Delphi版本的BitmapToRegion
function BitmapToRegion(bmp: TBitmap; TransparentColor: TColor = clBlack; RedTol: Byte = 1; GreenTol: Byte = 1; BlueTol: Byte = 1): HRGN; const AllocUnit = 100; type PRectArray = ^TRectArray; TRectArray = array[0..(MaxInt div SizeOf(TRect)) - 1] of TRect; var pr: PRectArray; // used to access the rects array of RgnData by index h: HRGN; // Handles to regions RgnData: PRgnData; // Pointer to structure RGNDATA used to create regions lr, lg, lb, hr, hg, hb: Byte; // values for lowest and hightest trans. colors x, y, x0: Integer; // coordinates of current rect of visible pixels b: PByteArray; // used to easy the task of testing the byte pixels (R,G,B) ScanLinePtr: Pointer; // Pointer to current ScanLine being scanned ScanLineInc: Integer; // Offset to next bitmap scanline (can be negative) maxRects: Cardinal; // Number of rects to realloc memory by chunks of AllocUnit begin Result := 0; { Keep on hand lowest and highest values for the "transparent" pixels } lr := GetRValue(TransparentColor); lg := GetGValue(TransparentColor); lb := GetBValue(TransparentColor); hr := Min($FF, lr + RedTol); hg := Min($FF, lg + GreenTol); hb := Min($FF, lb + BlueTol); { ensures that the pixel format is 32-bits per pixel } bmp.PixelFormat := pf32bit; { alloc initial region data } maxRects := AllocUnit; GetMem(RgnData, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * maxRects)); try with RgnData^.rdh do begin dwSize := SizeOf(RGNDATAHEADER); iType := RDH_RECTANGLES; nCount := 0; nRgnSize := 0; SetRect(rcBound, MAXLONG, MAXLONG, 0, 0); end; { scan each bitmap row - the orientation doesn't matter (Bottom-up or not) } ScanLinePtr := bmp.ScanLine[0]; ScanLineInc := Integer(bmp.ScanLine[1]) - Integer(ScanLinePtr); for y := 0 to bmp.Height - 1 do begin x := 0; while x < bmp.Width do begin x0 := x; while x < bmp.Width do begin b := @PByteArray(ScanLinePtr)[x * SizeOf(TRGBQuad)]; // BGR-RGB: Windows 32bpp BMPs are made of BGRa quads (not RGBa) if (b[2] >= lr) and (b[2] <= hr) and (b[1] >= lg) and (b[1] <= hg) and (b[0] >= lb) and (b[0] <= hb) then Break; // pixel is transparent Inc(x); end; { test to see if we have a non-transparent area in the image } if x > x0 then begin { increase RgnData by AllocUnit rects if we exceeds maxRects } if RgnData^.rdh.nCount >= maxRects then begin Inc(maxRects, AllocUnit); ReallocMem(RgnData, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * MaxRects)); end; { Add the rect (x0, y)-(x, y+1) as a new visible area in the region } pr := @RgnData^.Buffer; // Buffer is an array of rects with RgnData^.rdh do begin SetRect(pr[nCount], x0, y, x, y + 1); { adjust the bound rectangle of the region if we are "out-of-bounds" } if x0 < rcBound.Left then rcBound.Left := x0; if y < rcBound.Top then rcBound.Top := y; if x > rcBound.Right then rcBound.Right := x; if y + 1 > rcBound.Bottom then rcBound.Bottom := y + 1; Inc(nCount); end; end; // if x > x0 { Need to create the region by muliple calls to ExtCreateRegion, 'cause } { it will fail on Windows 98 if the number of rectangles is too large } if RgnData^.rdh.nCount = 2000 then begin h := ExtCreateRegion(nil, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * maxRects), RgnData^); if Result > 0 then begin // Expand the current region CombineRgn(Result, Result, h, RGN_OR); DeleteObject(h); end else // First region, assign it to Result Result := h; RgnData^.rdh.nCount := 0; SetRect(RgnData^.rdh.rcBound, MAXLONG, MAXLONG, 0, 0); end; Inc(x); end; // scan every sample byte of the image Inc(Integer(ScanLinePtr), ScanLineInc); end; { need to call ExCreateRegion one more time because we could have left } { a RgnData with less than 2000 rects, so it wasn't yet created/combined } h := ExtCreateRegion(nil, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * MaxRects), RgnData^); if Result > 0 then begin CombineRgn(Result, Result, h, RGN_OR); DeleteObject(h); end else Result := h; finally FreeMem(RgnData, SizeOf(RGNDATAHEADER) + (SizeOf(TRect) * MaxRects)); end; end; I've supplied a couple of simple examples of using this function for beginners: {This first example sets the region of a TForm} procedure TForm1.Button1Click(Sender: TObject); var ARgn: HRGN; ABitmap: TBitmap; begin ABitmap := TBitmap.Create; try ABitmap.LoadFromFile('C:MyImage.bmp'); ARgn := BitmapToRegion(ABitmap, clFuchsia); SetWindowRgn(Form1.Handle, ARgn, True); finally ABitmap.Free; end; end; {This second example sets the region of a TPanel} procedure TForm1.Button1Click(Sender: TObject); var ARgn: HRGN; ABitmap: TBitmap; begin ABitmap := TBitmap.Create; try ABitmap.LoadFromFile('C:MyImage.bmp'); ARgn := BitmapToRegion(ABitmap, clFuchsia); SetWindowRgn(Panel1.Handle, ARgn, True); finally ABitmap.Free; end; end;