After what felt like a million try and fail, I found the answer. Microsoft's instruction for how to create a Bitmap Opacity Mask in here and here are very confusing and makes it sound like you need a black and white image as a mask, but that is far from the truth. What we really need is an image that only has alpha value information. Also I was not able to find any instruction for converting a HRGN
to an ID2D1Bitmap
. So I had to come up with my own which I am going to share here for anyone who might run into the same problem.
void HRGN_to_D2Bitmap(const DWORD bmpWidth, const DWORD bmpHeight, HRGN hShape, ID2D1RenderTarget* pRT, ID2D1Bitmap** ppBmpMask)
{
boost::scoped_ptr<unsigned char> pBmpBuff(new unsigned char[bmpWidth * bmpHeight]);
DWORD dwCount;
LPRGNDATA pRgnData;
memset(pBmpBuff.get(), 0, bmpWidth * bmpHeight);
dwCount = ::GetRegionData(hShape, 0, NULL);
boost::scoped_ptr<unsigned char> pRgnBuff(new unsigned char[dwCount]);
pRgnData = reinterpret_cast<LPRGNDATA>(pRgnBuff.get());
if (dwCount == ::GetRegionData(hShape, dwCount, pRgnData))
{
DWORD i, y;
RECT* pRect;
D2D1_BITMAP_PROPERTIES bmpPorp;
D2D1_SIZE_U bmpSize = D2D1::SizeU(bmpWidth, bmpHeight);
pRect = (RECT*)pRgnData->Buffer;
for (i = 0; i < pRgnData->rdh.nCount; i++)
{
for (y = (DWORD)pRect->top; ((y < (DWORD)pRect->bottom) && (y < bmpHeight)); y++)
memset(&pBmpBuff.get()[(y * bmpWidth) + pRect->left], 0xFFFFFFFF, pRect->right - pRect->left);
pRect++;
}
bmpPorp.dpiX = 0.0f;
bmpPorp.dpiY = 0.0f;
bmpPorp.pixelFormat.format = DXGI_FORMAT_A8_UNORM;
bmpPorp.pixelFormat.alphaMode = D2D1_ALPHA_MODE_STRAIGHT;
pRT->CreateBitmap(bmpSize, pBmpBuff.get(), bmpWidth, bmpPorp, ppBmpMask);
}
}
I have omitted most of the error checking for simplicity.
When the function returns, the last parameter should have a valid bitmap mask (if everything had worked correctly) to be used by FillOpacityMask
function of ID2D1RenderTarget
.
Now we need to turn our ID2D1Bitmap
into a ID2D1BitmapBrush
.
D2D1_BITMAP_BRUSH_PROPERTIES propertiesXClampYClamp = D2D1::BitmapBrushProperties(
D2D1_EXTEND_MODE_CLAMP,
D2D1_EXTEND_MODE_CLAMP,
D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR
);
pRT->CreateBitmapBrush(pOrigBmp, propertiesXClampYClamp, &bkBrush);
pOrigBmp
is the image that we want to move around and bkBrush
is a pointer to ID2D1BitmapBrush
.
If you have a HBITMAP
or in case of C++ Builder guys a TBitmap
, you can find the instruction for converting a bitmap to ID2D1Bitmap
here.
Now everytime you want to paint your window, you can simply do this:
pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
pRT->FillOpacityMask(pBmpMask, bkBrush, D2D1_OPACITY_MASK_CONTENT_GRAPHICS);
pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
For moving the image around we have to use SetTransform
. pBmpMask
is a ID2D1Bitmap
that was created using HRGN_to_D2Bitmap
. ie:
ID2D1Bitmap* pBmpMask;
HRGN_to_D2Bitmap(bmpWidth, bmpHeight, hShape, pRT, &pBmpMask);
Hope this is helpful.
Sam