0

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

Lee Taylor
  • 7,761
  • 16
  • 33
  • 49
Sam
  • 2,473
  • 3
  • 18
  • 29
  • Does it have to be AntiAliased? – kizeloo May 17 '16 at 20:29
  • It would be nice if that was possible, if not, I will be happy to just display it without Anti-aliasing. – Sam May 17 '16 at 20:35
  • How are you displaying it in your window as is? – kizeloo May 17 '16 at 20:46
  • At this moment I am using GDI which is very slow and when user moves the selected region it flickers. I am trying to update it to Direct2D. – Sam May 17 '16 at 20:56
  • Also, is your goal to simply make it transparent? There is an API function to make the image transparent based on the upper left pixel color (dark blue in your case). – kizeloo May 17 '16 at 20:58
  • The goal is to make pars of an image transparent based on an `HRGN` in Direct2D – Sam May 17 '16 at 21:04
  • The function I'm thinking of uses GDI as well (LoadImage). I'm not surprised it flickers when moving it. My IDE (C++ Builder) has a full repaint property that can be set to avoid flickering. Maybe using RedrawWindow with a certain flag will stop it. Just a thought. – kizeloo May 17 '16 at 21:06
  • Actualy I am using **C++ Builder**. The image is being painted in the background of `TCustomScrollBox`. The erase background problem can be taken care of by overriding the `WndProc` and simply returning **1** on `WM_ERASEBKGND`. The flicker happens because I have to draw that background image and then draw the floating selected image. I even tried to cash the whole thing and draw it all at once, but the CPU drawing is not fast enough to keep up with user moving the floating part on the fly. – Sam May 17 '16 at 21:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/112211/discussion-between-kizeloo-and-sam). – kizeloo May 17 '16 at 21:19
  • What version of C++ Builder are you using? I'm making an exaple project with your images. – kizeloo May 17 '16 at 21:42
  • I am using 10.1 Berlin – Sam May 17 '16 at 21:42

2 Answers2

1

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

Community
  • 1
  • 1
Sam
  • 2,473
  • 3
  • 18
  • 29
0

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.

kizeloo
  • 183
  • 8
  • Also, I made your PNGs into BMPs – kizeloo May 17 '16 at 23:04
  • I could also post instructions, but emailing it would probably be easier for you. – kizeloo May 17 '16 at 23:42
  • Thank you for the effort. I really appreciate it. I have changed the original post to explain the problem better. I already have a working version that uses GDI (TImage), But the images that I am working with are really big and doing it in GDI creates a flicker problem. I was hoping to upgrade it to Direct2D to make the movement of the floating part smoother and flicker free. Once again thanks for your efforts. By the way Stackoverflow does not have a private messaging system and posting the e-mail address is just asking for trouble. – Sam May 18 '16 at 15:05