-1

I'm capturing a window using the winapi PrintWindow, and cropping only the desired region using BitBlt, but the region I'm trying to capture is a rounded rect, how I could round the corners of the image inside of the hdc?

I know about the method Graphics::FromHDC(HDC) but I'm not sure how to "round" the image got from the graphics.

RECT rc;
HWND hwnd = FindWindow(TEXT("Test"), NULL);
if (hwnd == NULL)
{
    cout << "Can't find the given window" << endl;
    return 0;
}
GetClientRect(hwnd, &rc);

// Capture the window screen.
HDC hdcScreen = GetDC(NULL);
HDC hdcSrc = CreateCompatibleDC(hdcScreen);
HBITMAP hbmp = CreateCompatibleBitmap(hdcScreen, 
    rc.right - rc.left, rc.bottom - rc.top);
SelectObject(hdcSrc , hbmp);

//Print to memory hdc
PrintWindow(hwnd, hdcSrc, PW_CLIENTONLY);


// Copy only the desired area.
HDC hdcScreen2 = GetDC(NULL);
HDC hdcDest = CreateCompatibleDC(hdcScreen2);
HBITMAP hbmp2 = CreateCompatibleBitmap(hdcScreen2, 
    300, 200);
SelectObject(hdcDest, hbmp2);

BitBlt(hdcDest, X, Y, W, H, hdcSrc, 0, 0, SRCCOPY);
  • If I am right, you can specify a region that has the desired shape, and only that region will be modified. –  Feb 22 '22 at 15:47
  • Specify the region in which function? –  Feb 22 '22 at 15:47
  • Don't know by heart, sorry, have a look at the GDI functions. Lookup Clipping. –  Feb 22 '22 at 15:48
  • [`SelectClipRgn`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectcliprgn) with [`CreateRoundedRectRgn`](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-createroundrectrgn). Clipping is a binary operation. This will not perform any kind of antialiasing and produce jagged edges. – IInspectable Feb 22 '22 at 16:21
  • Must be done before or after calling the BitBlc? @IInspectable –  Feb 22 '22 at 17:46
  • Clipping needs to be applied prior to the rendering operation (in this case `BitBlt`). See the reference documentation on [clipping](https://learn.microsoft.com/en-us/windows/win32/gdi/clipping) for more information. – IInspectable Feb 23 '22 at 13:42

1 Answers1

3

(I wrote this answer and thought the OP wanted Gdiplus for some reason ... regular GDI is pretty much the same thing although there actually is a CreatRoundRectRgn(...) in that case)

You can set the clipping region of a Gdiplus::Graphics object with its SetClip member function which takes a Region. You can create a Region from a Path but there is apparently no out-of-the-box way in Gdi+ of creating a round rectangle shaped path so you have to do it manually. There is an answer on StackOverflow about doing this in C#, and another one that ports it to C++/Win32 here.

Code below:

#include <windows.h>
#include <objidl.h>
#include <gdiplus.h>
namespace gdi = Gdiplus;
#pragma comment (lib,"Gdiplus.lib")

void GetRoundRectPath(gdi::GraphicsPath* pPath, gdi::Rect r, int dia)
{
    // diameter can't exceed width or height
    if (dia > r.Width)    dia = r.Width;
    if (dia > r.Height)    dia = r.Height;

    // define a corner 
    gdi::Rect Corner(r.X, r.Y, dia, dia);

    // begin path
    pPath->Reset();

    // top left
    pPath->AddArc(Corner, 180, 90);

    // tweak needed for radius of 10 (dia of 20)
    if (dia == 20)
    {
        Corner.Width += 1;
        Corner.Height += 1;
        r.Width -= 1; r.Height -= 1;
    }

    // top right
    Corner.X += (r.Width - dia - 1);
    pPath->AddArc(Corner, 270, 90);

    // bottom right
    Corner.Y += (r.Height - dia - 1);
    pPath->AddArc(Corner, 0, 90);

    // bottom left
    Corner.X -= (r.Width - dia - 1);
    pPath->AddArc(Corner, 90, 90);

    // end path
    pPath->CloseFigure();
}

VOID OnPaint(HDC hdc, gdi::Image* img)
{
    gdi::Graphics g(hdc);
    gdi::GraphicsPath path;
    GetRoundRectPath(&path, { 10,10,512,512 }, 50);
    gdi::Region rgn(&path);
    g.SetClip(&rgn);
    g.DrawImage(img, 10, 10);
}

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR, INT iCmdShow)
{
    HWND                hWnd;
    MSG                 msg;
    WNDCLASS            wndClass;
    gdi::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;

    // Initialize GDI+.
    gdi::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    {
        // nest everything in a scope so that the image doesnt
        // get destroyed after Gdiplus has been shut down.

        wndClass.style = CS_HREDRAW | CS_VREDRAW;
        wndClass.lpfnWndProc = WndProc;
        wndClass.cbClsExtra = 0;
        wndClass.cbWndExtra = 0;
        wndClass.hInstance = hInstance;
        wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
        wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
        wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
        wndClass.lpszMenuName = NULL;
        wndClass.lpszClassName = TEXT("cliptorrect");

        RegisterClass(&wndClass);

        gdi::Image img(TEXT("C:\\test\\lenna.png"));

        hWnd = CreateWindow(
            TEXT("cliptorrect"),  
            TEXT("clip to round rect"),  
            WS_OVERLAPPEDWINDOW,     
            CW_USEDEFAULT,           
            CW_USEDEFAULT,           
            548,
            572,
            NULL,                    
            NULL,                    
            hInstance,               
            &img
        );                    

        ShowWindow(hWnd, iCmdShow);
        UpdateWindow(hWnd);

        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    gdi::GdiplusShutdown(gdiplusToken);
    return msg.wParam;
}  // WinMain

LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
    WPARAM wParam, LPARAM lParam)
{
    HDC          hdc;
    PAINTSTRUCT  ps;

    switch (message)
    {
    case WM_CREATE: {
        CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG>(cs->lpCreateParams));
    } return 0;

    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        OnPaint(hdc, reinterpret_cast<gdi::Image*>(GetWindowLongPtr(hWnd, GWLP_USERDATA)));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
}

The output of the above looks like clip to a round rect

I'm not sure if there is a way in GDi+ to get that anti-aliased or not, however.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • I don't think you can use anti-aliasing here. Anti-Aliasing is for painting lines. After image is painted, you could try setting `g.SetSmoothingMode`, then select white pen and `DrawArc` at the corners. And `DrawRectangle` on top of it. – Barmak Shemirani Feb 22 '22 at 23:35
  • yeah or you could burn anti-aliasing to white/bkgd color into the image if it is always going to be painted clipped to the same round rectangle. – jwezorek Feb 22 '22 at 23:49
  • 1
    Actually, this should do it for anti-aliasing: `OnPaint() { ... g.DrawImage... g.SetSmoothingMode(gdi::SmoothingModeAntiAlias); gdi::Pen pen(gdi::Color::White, 1.0F); g.DrawPath(&pen, &path);}` (it's going to erase 1 pixel around the image) – Barmak Shemirani Feb 23 '22 at 01:00
  • The question was tagged as "gdi+", so it was reasonable to assume that the question concerned gdi plus. – Dwedit Mar 01 '22 at 19:49