skip to Main Content

I need to draw parts of a bitmap (not rectangular) using Direct2D like images below. It is like how in Photoshop user can create a new layer based on the selected region (HRGN) and move it around. The closest example that I have been able to find was here, but I have not been able to make it work. Does anyone know how to do this?

Thank you

enter image description hereenter image description here

Edit

It looks like people have misunderstood what I was trying to achieve. This new animated GIF should explain it better.

enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. Ok, I got it. Do you want me to email you the project source? It’s C++ Builder 6, so your version should be able to convert it. I was right about the FullRepaint needing to be set to false for the TPanel.

    The basic idea is to put a TImage on a TPanel, then “cut out” the hRGN of the panel based on the transparent color of the image you want to use. Also, be sure to set the “FullRepaint” property of the TPanel to false, or else you’ll get that flickering you noted. This is how I did the moving animation for a couple games I made.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search