windows-nt/Source/XPSP1/NT/shell/themes/uxtheme/rgn.cpp
2020-09-26 16:20:57 +08:00

664 lines
22 KiB
C++

//-------------------------------------------------------------------------//
// Rgn.cpp - Bitmap-to-Region transforms
//
// History:
// 01/31/2000 scotthan created
//-------------------------------------------------------------------------//
#include "stdafx.h"
#include "rgn.h"
#include "tmutils.h"
//------------//
// Helpers:
//-------------------------------------------------------------------------//
#define CX_USEDEFAULT -1
#define CY_USEDEFAULT -1
#define _ABS( val1, val2 ) ((val1)>(val2) ? (val1)-(val2) : (val2)-(val1))
//-------------------------------------------------------------------------//
inline BOOL IsColorMatch( COLORREF rgb1, COLORREF rgb2, int nTolerance = 0 )
{
if( nTolerance == 0 )
return (rgb1 << 8) == (rgb2 << 8);
return _ABS(GetRValue(rgb1),GetRValue(rgb2)) <= nTolerance &&
_ABS(GetGValue(rgb1),GetGValue(rgb2)) <= nTolerance &&
_ABS(GetBValue(rgb1),GetBValue(rgb2)) <= nTolerance;
}
//-------------------------------------------------------------------------//
inline BOOL _IsNormalRect( IN LPCRECT prc )
{
return (prc->right >= prc->left) &&
(prc->bottom >= prc->top);
}
//-------------------------------------------------------------------------//
inline BOOL _IsOnScreenRect( IN LPCRECT prc )
{
return prc->left >= 0 && prc->top >= 0 &&
prc->right >= 0 && prc->bottom >= 0;
}
//-------------------------------------------------------------------------//
inline void _InPlaceUnionRect( IN OUT LPRECT prcDest, IN LPCRECT prcSrc )
{
ASSERT(prcDest);
ASSERT(prcSrc);
ASSERT(_IsNormalRect(prcSrc));
if( prcDest->left == -1 || prcDest->left > prcSrc ->left )
prcDest->left = prcSrc ->left;
if( prcDest->right == -1 || prcDest->right < prcSrc ->right )
prcDest->right = prcSrc ->right;
if( prcDest->top == -1 || prcDest->top > prcSrc ->top )
prcDest->top = prcSrc ->top;
if( prcDest->bottom == -1 || prcDest->bottom < prcSrc ->bottom )
prcDest->bottom = prcSrc ->bottom;
}
//-------------------------------------------------------------------------//
// Walks the pixels and computes the region
HRGN WINAPI _PixelsToRgn(
DWORD *pdwBits,
int cxImageOffset, // image cell horz offset
int cyImageOffset, // image cell vert offset
int cxImage, // image cell width
int cyImage, // image cell height
int cxSrc, // src bitmap width
int cySrc, // src bitmap height
BOOL fAlphaChannel,
int iAlphaThreshold,
COLORREF rgbMask,
int nMaskTolerance )
{
// Establish a series of rectangles, each corresponding to a scan line (row)
// in the bitmap, that will comprise the region.
const UINT RECTBLOCK = 512;
UINT nAllocRects = 0;
HRGN hrgnRet = NULL;
HGLOBAL hrgnData = GlobalAlloc( GMEM_MOVEABLE,
sizeof(RGNDATAHEADER) + (sizeof(RECT) * (nAllocRects + RECTBLOCK)) );
if( hrgnData )
{
nAllocRects += RECTBLOCK;
RGNDATA* prgnData = (RGNDATA*)GlobalLock( hrgnData );
LPRECT prgrc = (LPRECT)prgnData->Buffer;
ZeroMemory( prgnData, sizeof(prgnData->rdh) );
prgnData->rdh.dwSize = sizeof(prgnData->rdh);
prgnData->rdh.iType = RDH_RECTANGLES;
SetRect( &prgnData->rdh.rcBound, -1, -1, -1, -1 );
// invert offset in y dimension since bits are arrayed bottom to top
int cyRow0 = cySrc - (cyImage + cyImageOffset);
int cyRowN = (cyRow0 + cyImage) - 1 ; // index of the last row
// Compute a transparency mask if not specified.
if( -1 == rgbMask )
rgbMask = pdwBits[cxImageOffset + (cyRowN * cxSrc)];
//---- pixels in pdwBits[] have RBG's reversed ----
//---- reverse our mask to match ----
rgbMask = REVERSE3(rgbMask);
//---- rows in pdwBits[] are reversed (bottom to top) ----
for( int y = cyRow0; y <= cyRowN; y++ ) // working bottom-to-top
{
//---- Scanning pixels left to right ----
DWORD *pdwFirst = &pdwBits[cxImageOffset + (y * cxSrc)];
DWORD *pdwLast = pdwFirst + cxImage - 1;
DWORD *pdwPixel = pdwFirst;
while (pdwPixel <= pdwLast)
{
//---- skip TRANSPARENT pixels to find next OPAQUE (on this row) ----
if (fAlphaChannel)
{
while ((pdwPixel <= pdwLast) && (ALPHACHANNEL(*pdwPixel) < iAlphaThreshold))
pdwPixel++;
}
else
{
while ((pdwPixel <= pdwLast) && (IsColorMatch(*pdwPixel, rgbMask, nMaskTolerance)))
pdwPixel++;
}
if (pdwPixel > pdwLast) // too far; try next row
break;
DWORD *pdw0 = pdwPixel;
pdwPixel++; // skip over current opaque pixel
//---- skip OPAQUE pixels to find next TRANSPARENT (on this row) ----
if (fAlphaChannel)
{
while ((pdwPixel <= pdwLast) && (ALPHACHANNEL(*pdwPixel) >= iAlphaThreshold))
pdwPixel++;
}
else
{
while ((pdwPixel <= pdwLast) && (! IsColorMatch(*pdwPixel, rgbMask, nMaskTolerance)))
pdwPixel++;
}
//---- got a stream of 1 or more opaque pixels on this row ----
// allocate more region rects if necessary (a particularly complex line)
if( prgnData->rdh.nCount >= nAllocRects )
{
GlobalUnlock( hrgnData );
prgnData = NULL;
HGLOBAL hNew = GlobalReAlloc( hrgnData,
sizeof(RGNDATAHEADER) + (sizeof(RECT) * (nAllocRects + RECTBLOCK)),
GMEM_MOVEABLE );
if( hNew )
{
hrgnData = hNew;
nAllocRects += RECTBLOCK;
prgnData = (RGNDATA*)GlobalLock( hrgnData );
prgrc = (LPRECT)prgnData->Buffer;
ASSERT(prgnData);
}
else
goto exit; // out of memory
}
// assign region rectangle
int x0 = (int)(pdw0 - pdwFirst);
int x = (int)(pdwPixel - pdwFirst);
int y0 = cyRowN - y;
SetRect( prgrc + prgnData->rdh.nCount,
x0, y0, x, y0+1 /* each rectangle is always 1 pixel high */ );
// merge into bounding box
_InPlaceUnionRect( &prgnData->rdh.rcBound,
prgrc + prgnData->rdh.nCount );
prgnData->rdh.nCount++;
} // while ()
} // for(y)
if( prgnData->rdh.nCount && _IsOnScreenRect(&prgnData->rdh.rcBound) )
{
// Create the region representing the scan line.
hrgnRet = ExtCreateRegion( NULL, sizeof(RGNDATAHEADER) + (sizeof(RECT) * nAllocRects),
prgnData );
}
exit:
// Free region def block.
GlobalUnlock( hrgnData );
GlobalFree( hrgnData );
}
return hrgnRet;
}
//-------------------------------------------------------------------------//
// Creates a region based on a text string in the indicated font.
HRGN WINAPI CreateTextRgn( HFONT hf, LPCTSTR pszText )
{
HRGN hrgnRet = NULL;
if( pszText && *pszText )
{
int cchText = lstrlen( pszText );
// Create a composite DC for assembling the region.
HDC hdcMem = CreateCompatibleDC( NULL );
SetBkMode( hdcMem, TRANSPARENT );
SetTextAlign( hdcMem, TA_TOP|TA_LEFT );
HFONT hfOld = (HFONT)SelectObject( hdcMem, hf );
// Derive a region from a path.
BeginPath( hdcMem );
TextOut( hdcMem, 0, 0, pszText, cchText );
EndPath( hdcMem );
hrgnRet = PathToRegion( hdcMem );
// Clean up composite DC
SelectObject( hdcMem, hfOld );
DeleteDC( hdcMem );
}
return hrgnRet;
}
//-------------------------------------------------------------------------//
// Creates a region based on an arbitrary bitmap, transparency-keyed on a
// RGB value within a specified tolerance. The key value is optional
// (-1 == use the value of the first pixel as the key).
//
HRESULT WINAPI CreateBitmapRgn(
HBITMAP hbm,
int cxOffset,
int cyOffset,
int cx,
int cy,
BOOL fAlphaChannel,
int iAlphaThreshold,
COLORREF rgbMask,
int nMaskTolerance,
OUT HRGN *phrgn)
{
CBitmapPixels BitmapPixels;
DWORD *prgdwPixels;
int cwidth, cheight;
HRESULT hr = BitmapPixels.OpenBitmap(NULL, hbm, TRUE, &prgdwPixels, &cwidth, &cheight);
if (FAILED(hr))
return hr;
if (cx <= 0)
cx = cwidth;
if (cy <= 0)
cy = cheight;
HRGN hrgn = _PixelsToRgn(prgdwPixels, cxOffset, cyOffset, cx, cy, cwidth, cheight, fAlphaChannel,
iAlphaThreshold, rgbMask, nMaskTolerance);
if (! hrgn)
return MakeError32(E_FAIL); // unknown reason for failure
*phrgn = hrgn;
return S_OK;
}
//-------------------------------------------------------------------------//
// Creates a region based on an arbitrary bitmap, transparency-keyed on a
// RGB value within a specified tolerance. The key value is optional (-1 ==
// use the value of the first pixel as the key).
//
HRGN WINAPI CreateScaledBitmapRgn(
HBITMAP hbm,
int cx,
int cy,
COLORREF rgbMask,
int nMaskTolerance )
{
HRGN hrgnRet = NULL;
BITMAP bm;
if( hbm && GetObject( hbm, sizeof(bm), &bm ) )
{
// Create a memory DC to do the pixel walk
HDC hdcMem = NULL;
if( (hdcMem = CreateCompatibleDC(NULL)) != NULL )
{
if( CX_USEDEFAULT == cx )
cx = bm.bmWidth;
if( CY_USEDEFAULT == cy )
cy = bm.bmHeight;
// Create a 32-bit empty bitmap for the walk
BITMAPINFO bmi;
ZeroMemory( &bmi, sizeof(bmi) );
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth = cx;
bmi.bmiHeader.biHeight = cy;
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB; // uncompressed.
VOID* pvBits = NULL;
HBITMAP hbmMem = CreateDIBSection( hdcMem, &bmi, DIB_RGB_COLORS, &pvBits, NULL, NULL );
BITMAP bmMem;
if( hbmMem )
{
// Transfer the image to our 32-bit format for the pixel walk.
HBITMAP hbmMemOld = (HBITMAP)SelectObject( hdcMem, hbmMem );
HDC hdc = CreateCompatibleDC( hdcMem );
HBITMAP hbmOld = (HBITMAP)SelectObject( hdc, hbm );
StretchBlt( hdcMem, 0, 0, cx, cy,
hdc, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY );
SelectObject( hdc, hbmOld );
DeleteDC( hdc );
GetObject( hbmMem, sizeof(bmMem), &bmMem );
ASSERT(bmMem.bmBitsPixel == 32);
ASSERT(bmMem.bmWidthBytes/bmMem.bmWidth == sizeof(DWORD));
LPDWORD pdwBits = (LPDWORD)bmMem.bmBits;
ASSERT(pdwBits != NULL);
hrgnRet = _PixelsToRgn(pdwBits, 0, 0, cx, cy, cx, cy, FALSE, 0, rgbMask, nMaskTolerance);
// Delete 32-bit memory bitmap
SelectObject( hdcMem, hbmMemOld );
DeleteObject( hbmMem );
}
// Delete memory DC
DeleteDC(hdcMem);
}
}
return hrgnRet;
}
//-------------------------------------------------------------------------//
int WINAPI AddToCompositeRgn(
IN OUT HRGN* phrgnComposite,
IN OUT HRGN hrgnSrc,
IN int cxOffset,
IN int cyOffset )
{
int nRet = ERROR;
if( NULL != phrgnComposite && NULL != hrgnSrc )
{
nRet = OffsetRgn( hrgnSrc, cxOffset, cyOffset );
if( nRet != ERROR )
{
int nMode = RGN_OR;
if( NULL == *phrgnComposite )
{
*phrgnComposite = CreateRectRgn(0,0,1,1);
if( NULL == *phrgnComposite )
return ERROR;
nMode = RGN_COPY;
}
nRet = CombineRgn( *phrgnComposite, hrgnSrc, *phrgnComposite, nMode );
}
}
return nRet;
}
//-------------------------------------------------------------------------//
int WINAPI RemoveFromCompositeRgn(
HRGN hrgnDest,
LPCRECT prcRemove )
{
ASSERT(hrgnDest);
ASSERT(prcRemove);
ASSERT(!IsRectEmpty(prcRemove));
int nRet = ERROR;
RECT rc = *prcRemove;
HRGN hrgn;
if( (hrgn = CreateRectRgnIndirect( &rc )) != NULL )
{
nRet = CombineRgn( hrgnDest, hrgnDest, hrgn, RGN_DIFF );
DeleteObject( hrgn );
}
return nRet;
}
//-------------------------------------------------------------------------//
HRGN WINAPI CreateTiledRectRgn(
IN HRGN hrgnSrc,
IN int cxSrc,
IN int cySrc,
IN int cxDest,
IN int cyDest )
{
HRGN hrgnBound = NULL; // return value
HRGN hrgnTile = _DupRgn( hrgnSrc );
if( hrgnTile )
{
// Build up an unplaced, unclipped composite
HRGN hrgnTmp = NULL;
for( int y = 0; y < cyDest; y += cySrc )
{
for( int x = 0; x < cxDest; x += cxSrc )
{
AddToCompositeRgn( &hrgnTmp, hrgnTile,
(x ? cxSrc : 0), (y ? cySrc : 0) );
}
}
if( NULL != hrgnTmp )
{
// Clip the composite to the specified rectangle
hrgnBound = CreateRectRgn( 0, 0, cxDest, cyDest );
if( hrgnBound )
{
if( ERROR == CombineRgn( hrgnBound, hrgnTmp, hrgnBound, RGN_AND ) )
{
DeleteObject( hrgnBound );
hrgnBound = NULL;
}
}
DeleteObject( hrgnTmp );
}
DeleteObject( hrgnTile );
}
return hrgnBound;
}
//-------------------------------------------------------------------------//
HRGN WINAPI _DupRgn( HRGN hrgnSrc )
{
if( hrgnSrc )
{
HRGN hrgnDest = CreateRectRgn(0,0,1,1);
if (hrgnDest)
{
if (CombineRgn( hrgnDest, hrgnSrc, NULL, RGN_COPY ) )
return hrgnDest;
DeleteObject(hrgnDest);
}
}
return NULL;
}
//-------------------------------------------------------------------------//
void FixMarginOverlaps(int szDest, int *pm1, int *pm2)
{
int szSrc = (*pm1 + *pm2);
if ((szSrc > szDest) && (szSrc > 0))
{
//---- reduce each but maintain ratio ----
*pm1 = int(.5 + float(*pm1 * szDest)/float(szSrc));
*pm2 = szDest - *pm1;
}
}
//-------------------------------------------------------------------------//
HRESULT _ScaleRectsAndCreateRegion(
RGNDATA *prd,
const RECT *prc,
MARGINS *pMargins,
SIZE *pszSrcImage,
HRGN *phrgn)
{
//---- note: "prd" is region data with the 2 points in each ----
//---- rectangle made relative to its grid. Also, after the points, ----
//---- there is a BYTE for each point signifying the grid id (0-8) ----
//---- that each point lies within. the grid is determined using ----
//---- the original region with the background "margins". This is ----
//---- done to make scaling the points as fast as possible. ----
if (! prd) // required
return MakeError32(E_POINTER);
//---- easy access variables ----
int lw = pMargins->cxLeftWidth;
int rw = pMargins->cxRightWidth;
int th = pMargins->cyTopHeight;
int bh = pMargins->cyBottomHeight;
int iDestW = WIDTH(*prc);
int iDestH = HEIGHT(*prc);
//---- prevent left/right dest margins from overlapping ----
FixMarginOverlaps(iDestW, &lw, &rw);
//---- prevent top/bottom dest margins from overlapping ----
FixMarginOverlaps(iDestH, &th, &bh);
int lwFrom = lw;
int rwFrom = pszSrcImage->cx - rw;
int thFrom = th;
int bhFrom = pszSrcImage->cy - bh;
int lwTo = prc->left + lw;
int rwTo = prc->right - rw;
int thTo = prc->top + th;
int bhTo = prc->bottom - bh;
//---- compute offsets & factors ----
int iLeftXOffset = prc->left;
int iMiddleXOffset = lwTo;
int iRightXOffset = rwTo;
int iTopYOffset = prc->top;
int iMiddleYOffset = thTo;
int iBottomYOffset = bhTo;
int iToMiddleWidth = rwTo - lwTo;
int iFromMiddleWidth = rwFrom - lwFrom;
int iToMiddleHeight = bhTo - thTo;
int iFromMiddleHeight = bhFrom - thFrom;
if (! iFromMiddleWidth) // avoid divide by zero
{
//--- map point to x=0 ----
iToMiddleWidth = 0;
iFromMiddleWidth = 1;
}
if (! iFromMiddleHeight) // avoid divide by zero
{
//--- map point to y=0 ----
iToMiddleHeight = 0;
iFromMiddleHeight = 1;
}
//---- clipping values for adjusted lw/rw/th/bh ----
int lwMaxVal = __max(lw - 1, 0);
int rwMinVal = __min(pMargins->cxRightWidth - rw, __max(pMargins->cxRightWidth-1, 0));
int thMaxVal = __max(th - 1, 0);
int bhMinVal = __min(pMargins->cyBottomHeight - bh, __max(pMargins->cyBottomHeight-1, 0));
//---- allocte a buffer for the new points (rects) ----
int newlen = sizeof(RGNDATAHEADER) + prd->rdh.nRgnSize; // same # of rects
BYTE *newData = (BYTE *)new BYTE[newlen];
RGNDATA *prdNew = (RGNDATA *)newData;
if (! prdNew)
return MakeError32(E_OUTOFMEMORY);
ZeroMemory(prdNew, sizeof(prd->rdh));
prdNew->rdh.dwSize = sizeof(prdNew->rdh);
prdNew->rdh.iType = RDH_RECTANGLES;
int cRects = prd->rdh.nCount;
prdNew->rdh.nCount = cRects;
SetRect(&prdNew->rdh.rcBound, -1, -1, -1, -1);
//---- step thru our custom data (POINT + BYTE combos) ----
POINT *pt = (POINT *)prd->Buffer;
BYTE *pByte = (BYTE *)prd->Buffer + prd->rdh.nRgnSize;
int cPoints = 2 * cRects;
POINT *ptNew = (POINT *)prdNew->Buffer;
for (int i=0; i < cPoints; i++, pt++, pByte++, ptNew++) // transform each "point"
{
switch (*pByte)
{
//---- in the "don't scale" areas, we clip the translated values ----
//---- for the case where the destination areas are too small ----
//---- using the below "__min()" and "__max()" calls ----
//---- remember: each point has been made 0-relative to its grid ----
case GN_LEFTTOP: // left top
ptNew->x = __min(pt->x, lwMaxVal) + iLeftXOffset;
ptNew->y = __min(pt->y, thMaxVal) + iTopYOffset;
break;
case GN_MIDDLETOP: // middle top
ptNew->x = (pt->x*iToMiddleWidth)/iFromMiddleWidth + iMiddleXOffset;
ptNew->y = __min(pt->y, thMaxVal) + iTopYOffset;
break;
case GN_RIGHTTOP: // right top
ptNew->x = __max(pt->x, rwMinVal) + iRightXOffset;
ptNew->y = __min(pt->y, thMaxVal) + iTopYOffset;
break;
case GN_LEFTMIDDLE: // left middle
ptNew->x = __min(pt->x, lwMaxVal) + iLeftXOffset;
ptNew->y = (pt->y*iToMiddleHeight)/iFromMiddleHeight + iMiddleYOffset;
break;
case GN_MIDDLEMIDDLE: // middle middle
ptNew->x = (pt->x*iToMiddleWidth)/iFromMiddleWidth + iMiddleXOffset;
ptNew->y = (pt->y*iToMiddleHeight)/iFromMiddleHeight + iMiddleYOffset;
break;
case GN_RIGHTMIDDLE: // right middle
ptNew->x = __max(pt->x, rwMinVal) + iRightXOffset;
ptNew->y = (pt->y*iToMiddleHeight)/iFromMiddleHeight + iMiddleYOffset;
break;
case GN_LEFTBOTTOM: // left bottom
ptNew->x = __min(pt->x, lwMaxVal) + iLeftXOffset;
ptNew->y = __max(pt->y, bhMinVal) + iBottomYOffset;
break;
case GN_MIDDLEBOTTOM: // middle bottom
ptNew->x = (pt->x*iToMiddleWidth)/iFromMiddleWidth + iMiddleXOffset;
ptNew->y = __max(pt->y, bhMinVal) + iBottomYOffset;
break;
case GN_RIGHTBOTTOM: // right bottom
ptNew->x = __max(pt->x, rwMinVal) + iRightXOffset;
ptNew->y = __max(pt->y, bhMinVal) + iBottomYOffset;
break;
}
}
//---- compute bounding box of new region ----
RECT *pRect = (RECT *)prdNew->Buffer;
RECT newBox = {-1, -1, -1, -1};
for (i=0; i < cRects; i++, pRect++)
_InPlaceUnionRect(&newBox, pRect);
//---- create the new region ----
prdNew->rdh.rcBound = newBox;
HRGN hrgn = ExtCreateRegion(NULL, newlen, prdNew);
delete [] newData; // free prdNew (aka newdata)
if (! hrgn)
return MakeErrorLast();
*phrgn = hrgn;
return S_OK;
}
//---------------------------------------------------------------------------------//
#ifdef _DEBUG
void RegionDebug(
HRGN hrgn)
{
DWORD len = GetRegionData(hrgn, 0, NULL); // get required length
ATLASSERT(len);
RGNDATA *pRgnData = (RGNDATA *) new BYTE[len + sizeof(RGNDATAHEADER)];
DWORD len2 = GetRegionData(hrgn, len, pRgnData);
ATLASSERT(len == len2);
}
#endif