928 lines
28 KiB
C++
928 lines
28 KiB
C++
|
//-------------------------------------------------------------------------//
|
||
|
// Rgn.cpp - Bitmap-to-Region transforms
|
||
|
//
|
||
|
// History:
|
||
|
// 01/31/2000 scotthan created
|
||
|
//-------------------------------------------------------------------------//
|
||
|
|
||
|
#include "stdafx.h"
|
||
|
#include "rgn.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 )
|
||
|
{
|
||
|
_ASSERTE(prcDest);
|
||
|
_ASSERTE(prcSrc);
|
||
|
_ASSERTE(_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 );
|
||
|
|
||
|
int cxMax = cxImageOffset + cxImage;
|
||
|
int cyMax = cyImageOffset + cyImage;
|
||
|
|
||
|
// Compute a transparency mask if not specified.
|
||
|
if( -1 == rgbMask )
|
||
|
rgbMask = pdwBits[cxImageOffset + ((cyMax-1) * 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 = cyImageOffset; y < cyMax; 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;
|
||
|
hrgnData = GlobalReAlloc( hrgnData,
|
||
|
sizeof(RGNDATAHEADER) + (sizeof(RECT) * (nAllocRects + RECTBLOCK)),
|
||
|
GMEM_MOVEABLE );
|
||
|
|
||
|
if( hrgnData )
|
||
|
{
|
||
|
nAllocRects += RECTBLOCK;
|
||
|
prgnData = (RGNDATA*)GlobalLock( hrgnData );
|
||
|
prgrc = (LPRECT)prgnData->Buffer;
|
||
|
_ASSERTE(prgnData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// assign region rectangle
|
||
|
int x0 = (int)(pdw0 - pdwFirst);
|
||
|
int x = (int)(pdwPixel - pdwFirst);
|
||
|
int y0 = y - cyImageOffset;
|
||
|
|
||
|
SetRect( prgrc + prgnData->rdh.nCount,
|
||
|
x0, cyMax - (y0+1),
|
||
|
x, cyMax - y0 );
|
||
|
|
||
|
// 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 );
|
||
|
}
|
||
|
|
||
|
// 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 E_FAIL;
|
||
|
|
||
|
*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 );
|
||
|
_ASSERTE(bmMem.bmBitsPixel == 32);
|
||
|
_ASSERTE(bmMem.bmWidthBytes/bmMem.bmWidth == sizeof(DWORD));
|
||
|
LPDWORD pdwBits = (LPDWORD)bmMem.bmBits;
|
||
|
_ASSERTE(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 )
|
||
|
{
|
||
|
_ASSERTE(hrgnDest);
|
||
|
_ASSERTE(prcRemove);
|
||
|
_ASSERTE(!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;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
// _InternalHitTestRgn()
|
||
|
typedef enum // _InternalHitTestRgn return values:
|
||
|
{
|
||
|
HTRGN_ERROR = 0x0000,
|
||
|
HTRGN_LEFT = 0x0001,
|
||
|
HTRGN_TOP = 0x0002,
|
||
|
HTRGN_RIGHT = 0x0004,
|
||
|
HTRGN_BOTTOM = 0x0008,
|
||
|
HTRGN_INSIDE = 0x0010,
|
||
|
HTRGN_OUTSIDE = 0x0020,
|
||
|
} HTRGN;
|
||
|
|
||
|
UINT WINAPI _InternalHitTestRgn(
|
||
|
HRGN hrgn,
|
||
|
POINT pt,
|
||
|
DWORD dwHitMask,
|
||
|
UINT cxMargin,
|
||
|
UINT cyMargin )
|
||
|
{
|
||
|
UINT nRet = HTRGN_ERROR;
|
||
|
RECT rcBox;
|
||
|
|
||
|
nRet |= PtInRegion( hrgn, pt.x, pt.y ) ? HTRGN_INSIDE : HTRGN_OUTSIDE;
|
||
|
dwHitMask &= ~(HTRGN_INSIDE|HTRGN_OUTSIDE);
|
||
|
|
||
|
if( (nRet & HTRGN_INSIDE) != 0 &&
|
||
|
(dwHitMask != 0) && GetRgnBox( hrgn, &rcBox ) )
|
||
|
{
|
||
|
int x, y;
|
||
|
if( dwHitMask & HTRGN_LEFT )
|
||
|
{
|
||
|
if( cxMargin )
|
||
|
{
|
||
|
// move left until we're out of the region.
|
||
|
for( x = pt.x; x >= rcBox.left && PtInRegion( hrgn, x, pt.y ); x-- );
|
||
|
|
||
|
// are we still in test range?
|
||
|
if( pt.x - x < (int)cxMargin )
|
||
|
nRet |= HTRGN_LEFT;
|
||
|
}
|
||
|
else
|
||
|
nRet |= HTRGN_LEFT;
|
||
|
}
|
||
|
|
||
|
if( dwHitMask & HTRGN_RIGHT )
|
||
|
{
|
||
|
if( cxMargin )
|
||
|
{
|
||
|
// move right until we're out of the region.
|
||
|
for( x = pt.x; x <= rcBox.right && PtInRegion( hrgn, x, pt.y ); x++ );
|
||
|
|
||
|
// are we still in test range?
|
||
|
if( x - pt.x < (int)cxMargin )
|
||
|
nRet |= HTRGN_RIGHT;
|
||
|
}
|
||
|
else
|
||
|
nRet |= HTRGN_RIGHT;
|
||
|
}
|
||
|
|
||
|
if( dwHitMask & HTRGN_TOP )
|
||
|
{
|
||
|
if( cyMargin )
|
||
|
{
|
||
|
// move up until we're out of the region.
|
||
|
for( y = pt.y; y >= rcBox.top && PtInRegion( hrgn, pt.x, y ); y-- );
|
||
|
|
||
|
// are we still in test range?
|
||
|
if( pt.y - y < (int)cyMargin )
|
||
|
nRet |= HTRGN_TOP;
|
||
|
}
|
||
|
else
|
||
|
nRet |= HTRGN_TOP;
|
||
|
|
||
|
}
|
||
|
|
||
|
if( dwHitMask & HTRGN_BOTTOM )
|
||
|
{
|
||
|
if( cyMargin )
|
||
|
{
|
||
|
// move left until we're out of the region.
|
||
|
for( y = pt.y; y <= rcBox.bottom && PtInRegion( hrgn, pt.x, y ); y++ );
|
||
|
|
||
|
// are we still in test range?
|
||
|
if( y - pt.y < (int)cyMargin )
|
||
|
nRet |= HTRGN_BOTTOM;
|
||
|
}
|
||
|
else
|
||
|
nRet |= HTRGN_BOTTOM;
|
||
|
}
|
||
|
}
|
||
|
return nRet;
|
||
|
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
UINT _HitMaskFromHitCode( BOOL fHasCaption, WORD wSegmentHTCode, WORD* pnHTDefault )
|
||
|
{
|
||
|
UINT nHitMask = 0;
|
||
|
WORD nHTDefault = HTBORDER;
|
||
|
|
||
|
switch (wSegmentHTCode)
|
||
|
{
|
||
|
case HTLEFT:
|
||
|
nHitMask = HTRGN_LEFT;
|
||
|
break;
|
||
|
|
||
|
case HTTOPLEFT:
|
||
|
nHitMask = HTRGN_TOP | HTRGN_LEFT;
|
||
|
if( fHasCaption )
|
||
|
nHTDefault = HTCAPTION;
|
||
|
break;
|
||
|
|
||
|
case HTBOTTOMLEFT:
|
||
|
nHitMask = HTRGN_BOTTOM | HTRGN_LEFT;
|
||
|
break;
|
||
|
|
||
|
case HTTOP:
|
||
|
nHitMask = HTRGN_TOP;
|
||
|
if( fHasCaption )
|
||
|
nHTDefault = HTCAPTION;
|
||
|
break;
|
||
|
|
||
|
case HTBOTTOM:
|
||
|
nHitMask = HTRGN_BOTTOM;
|
||
|
break;
|
||
|
|
||
|
case HTTOPRIGHT:
|
||
|
nHitMask = HTRGN_TOP | HTRGN_RIGHT;
|
||
|
if( fHasCaption )
|
||
|
nHTDefault = HTCAPTION;
|
||
|
break;
|
||
|
|
||
|
case HTRIGHT:
|
||
|
nHitMask = HTRGN_RIGHT;
|
||
|
break;
|
||
|
|
||
|
case HTBOTTOMRIGHT:
|
||
|
nHitMask = HTRGN_BOTTOM | HTRGN_RIGHT;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if( pnHTDefault ) *pnHTDefault = nHTDefault;
|
||
|
return nHitMask;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
WORD _HitCodeFromHitMask( UINT nHitMask, WORD nHTDefault )
|
||
|
{
|
||
|
switch( nHitMask )
|
||
|
{
|
||
|
case HTRGN_LEFT:
|
||
|
return HTLEFT;
|
||
|
|
||
|
case HTRGN_RIGHT:
|
||
|
return HTRIGHT;
|
||
|
|
||
|
case HTRGN_TOP:
|
||
|
return HTTOP;
|
||
|
|
||
|
case HTRGN_BOTTOM:
|
||
|
return HTBOTTOM;
|
||
|
|
||
|
case HTRGN_TOP|HTRGN_LEFT:
|
||
|
return HTTOPLEFT;
|
||
|
|
||
|
case HTRGN_TOP|HTRGN_RIGHT:
|
||
|
return HTTOPRIGHT;
|
||
|
|
||
|
case HTRGN_BOTTOM|HTRGN_LEFT:
|
||
|
return HTBOTTOMLEFT;
|
||
|
|
||
|
case HTRGN_BOTTOM|HTRGN_RIGHT:
|
||
|
return HTBOTTOMRIGHT;
|
||
|
}
|
||
|
return nHTDefault;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
WORD WINAPI _HitTestRgn( // can merge with _HitTestRgn() when it has no other callers
|
||
|
HRGN hrgn,
|
||
|
POINT pt,
|
||
|
WORD wSegmentHTCode,
|
||
|
BOOL fHasCaption,
|
||
|
UINT cxMargin,
|
||
|
UINT cyMargin )
|
||
|
{
|
||
|
WORD nHTDefault = HTBORDER;
|
||
|
UINT nHitMask = _HitMaskFromHitCode( fHasCaption, wSegmentHTCode, &nHTDefault );
|
||
|
UINT fHTRgn = _InternalHitTestRgn( hrgn, pt, nHitMask|HTRGN_INSIDE,
|
||
|
cxMargin, cyMargin );
|
||
|
|
||
|
if( fHTRgn & HTRGN_INSIDE )
|
||
|
{
|
||
|
fHTRgn &= ~HTRGN_INSIDE;
|
||
|
return _HitCodeFromHitMask( fHTRgn, nHTDefault );
|
||
|
}
|
||
|
|
||
|
return HTNOWHERE;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
HRESULT WINAPI _ScaleRectsAndCreateRegion(
|
||
|
RGNDATA *prd,
|
||
|
const RECT *prc,
|
||
|
MARGINS *pMargins,
|
||
|
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 E_POINTER;
|
||
|
|
||
|
RECT rcBox = prd->rdh.rcBound;
|
||
|
|
||
|
//---- compute margin values ----
|
||
|
int lwFrom = rcBox.left + pMargins->cxLeftWidth;
|
||
|
int rwFrom = rcBox.right - pMargins->cxRightWidth;
|
||
|
int thFrom = rcBox.top + pMargins->cyTopHeight;
|
||
|
int bhFrom = rcBox.bottom - pMargins->cyBottomHeight;
|
||
|
|
||
|
int lwTo = prc->left + pMargins->cxLeftWidth;
|
||
|
int rwTo = prc->right - pMargins->cxRightWidth;
|
||
|
int thTo = prc->top + pMargins->cyTopHeight;
|
||
|
int bhTo = prc->bottom - pMargins->cyBottomHeight;
|
||
|
|
||
|
//---- compute offsets & factors ----
|
||
|
int iLeftXOffset = prc->left;
|
||
|
int iMiddleXOffset = lwTo;
|
||
|
int iRightXOffset = rwTo;
|
||
|
|
||
|
int iTopYOffset = prc->top;
|
||
|
int iMiddleYOffset = thTo;
|
||
|
int iBottomYOffset = bhTo;
|
||
|
|
||
|
int iXMult = rwTo - lwTo;
|
||
|
int iXDiv = rwFrom - lwFrom;
|
||
|
int iYMult = bhTo - thTo;
|
||
|
int iYDiv = bhFrom - thFrom;
|
||
|
|
||
|
//---- 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 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)
|
||
|
{
|
||
|
case GN_LEFTTOP: // left top
|
||
|
ptNew->x = pt->x + iLeftXOffset;
|
||
|
ptNew->y = pt->y + iTopYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_MIDDLETOP: // middle top
|
||
|
ptNew->x = (pt->x*iXMult)/iXDiv + iMiddleXOffset;
|
||
|
ptNew->y = pt->y + iTopYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_RIGHTTOP: // right top
|
||
|
ptNew->x = pt->x + iRightXOffset;
|
||
|
ptNew->y = pt->y + iTopYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_LEFTMIDDLE: // left middle
|
||
|
ptNew->x = pt->x + iLeftXOffset;
|
||
|
ptNew->y = (pt->y*iYMult)/iYDiv + iMiddleYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_MIDDLEMIDDLE: // middle middle
|
||
|
ptNew->x = (pt->x*iXMult)/iXDiv + iMiddleXOffset;
|
||
|
ptNew->y = (pt->y*iYMult)/iYDiv + iMiddleYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_RIGHTMIDDLE: // right middle
|
||
|
ptNew->x = pt->x + iRightXOffset;
|
||
|
ptNew->y = (pt->y*iYMult)/iYDiv + iMiddleYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_LEFTBOTTOM: // left bottom
|
||
|
ptNew->x = pt->x + iLeftXOffset;
|
||
|
ptNew->y = pt->y + iBottomYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_MIDDLEBOTTOM: // middle bottom
|
||
|
ptNew->x = (pt->x*iXMult)/iXDiv + iMiddleXOffset;
|
||
|
ptNew->y = pt->y + iBottomYOffset;
|
||
|
break;
|
||
|
|
||
|
case GN_RIGHTBOTTOM: // right bottom
|
||
|
ptNew->x = pt->x + iRightXOffset;
|
||
|
ptNew->y = pt->y + 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 GetLastError();
|
||
|
|
||
|
*phrgn = hrgn;
|
||
|
return S_OK;
|
||
|
}
|
||
|
|
||
|
//-------------------------------------------------------------------------//
|
||
|
WORD WINAPI _DefaultHitCodeFromSegCode( BOOL fHasCaption, WORD wSegHTcode )
|
||
|
{
|
||
|
WORD nHTDefault;
|
||
|
_HitMaskFromHitCode( fHasCaption, wSegHTcode, &nHTDefault );
|
||
|
return nHTDefault;
|
||
|
}
|
||
|
|
||
|
//---------------------------------------------------------------------------------//
|
||
|
#ifdef _DEBUG
|
||
|
void RegionDebug(
|
||
|
HRGN hrgn)
|
||
|
{
|
||
|
DWORD len = GetRegionData(hrgn, 0, NULL); // get required length
|
||
|
ASSERT(len);
|
||
|
|
||
|
RGNDATA *pRgnData = (RGNDATA *) new BYTE[len + sizeof(RGNDATAHEADER)];
|
||
|
DWORD len2 = GetRegionData(hrgn, len, pRgnData);
|
||
|
ASSERT(len == len2);
|
||
|
|
||
|
int cnt = pRgnData->rdh.nCount;
|
||
|
RECT rect = pRgnData->rdh.rcBound;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
CBitmapPixels::CBitmapPixels()
|
||
|
{
|
||
|
_hdrBitmap = NULL;
|
||
|
_iWidth = 0;
|
||
|
_iHeight = 0;
|
||
|
}
|
||
|
//-------------------------------------------------------------------------
|
||
|
CBitmapPixels::~CBitmapPixels()
|
||
|
{
|
||
|
if (_hdrBitmap)
|
||
|
delete [] (BYTE *)_hdrBitmap;
|
||
|
}
|
||
|
//-------------------------------------------------------------------------
|
||
|
HRESULT CBitmapPixels::OpenBitmap(HDC hdc, HBITMAP bitmap, BOOL fForceRGB32,
|
||
|
DWORD OUT **pPixels, OPTIONAL OUT int *piWidth, OPTIONAL OUT int *piHeight,
|
||
|
OPTIONAL OUT int *piBytesPerPixel, OPTIONAL OUT int *piBytesPerRow)
|
||
|
{
|
||
|
if (! pPixels)
|
||
|
return E_INVALIDARG;
|
||
|
|
||
|
BITMAP bminfo;
|
||
|
|
||
|
GetObject(bitmap, sizeof(bminfo), &bminfo);
|
||
|
_iWidth = bminfo.bmWidth;
|
||
|
_iHeight = bminfo.bmHeight;
|
||
|
|
||
|
int iBytesPerPixel;
|
||
|
if ((fForceRGB32) || (bminfo.bmBitsPixel == 32))
|
||
|
iBytesPerPixel = 4;
|
||
|
else
|
||
|
iBytesPerPixel = 3;
|
||
|
|
||
|
int iRawBytes = _iWidth * iBytesPerPixel;
|
||
|
int iBytesPerRow = 4*((iRawBytes+3)/4);
|
||
|
|
||
|
int size = sizeof(BITMAPINFOHEADER) + _iHeight*iBytesPerRow;
|
||
|
BYTE *dibBuff = new BYTE[size+100]; // avoid random GetDIBits() failures with 100 bytes padding (?)
|
||
|
if (! dibBuff)
|
||
|
return E_OUTOFMEMORY;
|
||
|
|
||
|
_hdrBitmap = (BITMAPINFOHEADER *)dibBuff;
|
||
|
memset(_hdrBitmap, 0, sizeof(BITMAPINFOHEADER));
|
||
|
|
||
|
_hdrBitmap->biSize = sizeof(BITMAPINFOHEADER);
|
||
|
_hdrBitmap->biWidth = _iWidth;
|
||
|
_hdrBitmap->biHeight = _iHeight;
|
||
|
_hdrBitmap->biPlanes = 1;
|
||
|
_hdrBitmap->biBitCount = 8*iBytesPerPixel;
|
||
|
_hdrBitmap->biCompression = BI_RGB;
|
||
|
|
||
|
bool fNeedRelease = false;
|
||
|
|
||
|
if (! hdc)
|
||
|
{
|
||
|
hdc = GetWindowDC(NULL);
|
||
|
fNeedRelease = true;
|
||
|
}
|
||
|
|
||
|
int linecnt = GetDIBits(hdc, bitmap, 0, _iHeight, DIBDATA(_hdrBitmap), (BITMAPINFO *)_hdrBitmap,
|
||
|
DIB_RGB_COLORS);
|
||
|
ASSERT(linecnt == _iHeight);
|
||
|
|
||
|
if (fNeedRelease)
|
||
|
ReleaseDC(NULL, hdc);
|
||
|
|
||
|
*pPixels = (DWORD *)DIBDATA(_hdrBitmap);
|
||
|
|
||
|
if (piWidth)
|
||
|
*piWidth = _iWidth;
|
||
|
if (piHeight)
|
||
|
*piHeight = _iHeight;
|
||
|
|
||
|
if (piBytesPerPixel)
|
||
|
*piBytesPerPixel = iBytesPerPixel;
|
||
|
if (piBytesPerRow)
|
||
|
*piBytesPerRow = iBytesPerRow;
|
||
|
|
||
|
return S_OK;
|
||
|
}
|
||
|
//-------------------------------------------------------------------------
|
||
|
void CBitmapPixels::CloseBitmap(HDC hdc, HBITMAP hBitmap)
|
||
|
{
|
||
|
if (_hdrBitmap)
|
||
|
{
|
||
|
if (hBitmap) // rewrite bitmap
|
||
|
{
|
||
|
bool fNeedRelease = false;
|
||
|
|
||
|
if (! hdc)
|
||
|
{
|
||
|
hdc = GetWindowDC(NULL);
|
||
|
fNeedRelease = true;
|
||
|
}
|
||
|
|
||
|
SetDIBits(hdc, hBitmap, 0, _iHeight, DIBDATA(_hdrBitmap), (BITMAPINFO *)_hdrBitmap,
|
||
|
DIB_RGB_COLORS);
|
||
|
|
||
|
if ((fNeedRelease) && (hdc))
|
||
|
ReleaseDC(NULL, hdc);
|
||
|
}
|
||
|
|
||
|
delete [] (BYTE *)_hdrBitmap;
|
||
|
_hdrBitmap = NULL;
|
||
|
}
|
||
|
}
|