Search code examples
c++user-interfacewinapigdi

Drawing Algorithm works with normal Device Context but not with Compatible Device Context + StretchBlt


Im writing an application which works as an API for Java Code using JNI. The Java application assumes the Window Client Area is 800x400 pixels. This value cannot be modified. All sizes and positions of image elements are relative to this format. Therefore, the 800x400-representation has to be "translated" into client-area content. Up until now, i did this by preventing the user from resizing the window:

LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
style &= ~(WS_THICKFRAME| WS_MAXIMIZEBOX);
SetWindowLongPtr(hwnd, GWL_STYLE, style);
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);

Obviously, this is bad in terms of user experience. Therefore, i wanted a way to resize the contents of the 800x400 area.

Up until now, the area was drawn by the following algorithm, which worked fine:

LRESULT CALLBACK WindowCallbackF(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    WindowInterface* wi;
    wi = getInterface(hwnd)/*This call always succeeds when HWND was created with this API;
What it does is irrelevant here(In case you are interested: it looks up hwnd in a global std::unordered_map of hwnd,WindowInterface* pairs*/;
    switch (uMsg)
    {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        ImageToRender* figure;
        if (!hwnd) {
            return 0;
        }
        HDC hdc = BeginPaint(hwnd, &ps);
        HDC compatible = CreateCompatibleDC(hdc);
        if ((wi->flags) && WindowFlags::BackgroundSet) {
            SelectObject(compatible, wi->backgroundImage/*HBITMAP for the Background Image*/);
            BitBlt(hdc, 0, 0, wi->initWidth/*800*/, wi->initHeight/*400*/, compatible, 0, 0, SRCCOPY);
        }
        wi->lock.lock();
        /*wi->lock is a std::unique_lock<std::mutex>, and wi->figures is a std::vector<ImageToRender*>*/
        for (size_t i = (wi->figures).size(); i > 0; --i) {
            figure = (wi->figures)[i - 1];
            if (figure->flags & ImageToRenderFlags::Visible) {
                SelectObject(compatible, figure->bitmap/*Bitmap for the Item*/);
                TransparentBlt(hdc,figure->xPos,figure->yPos/*Position of the figure in the client area*/,
figure->width/*width of the figure in the client area*/,
figure->height/*height of the figure in the client area*/,
compatible, 0, 0, 
figure->bmpWidth/*Width of the original bitmap*/, 
figure->bmpHeight/*Height of the original Bitmap*/, 
RGB(0xFF,0xFF,0xFF));
            }
        }
        DeleteDC(compatible);
        wi->lock.unlock();
        EndPaint(hwnd, &ps); }
        return 0;
[...]

My idea was to draw the result of the algorithm above into a Compatible Device Context with the 800x400 format, and then use StretchBlt to stretch it into the real window, while maintaining the ratio 2:1. To do so, i wrote the following algorithm:

LRESULT CALLBACK WindowCallbackF(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    WindowInterface* wi;
    wi = getInterface(hwnd);
    switch (uMsg)
    {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        ImageToRender* figure;
        if (!hwnd) {
            return 0;
        }
        HDC hdc = BeginPaint(hwnd, &ps);
        HDC drawDC = CreateCompatibleDC(hdc);
        HDC compatible = CreateCompatibleDC(hdc);
        if ((wi->flags) && WindowFlags::BackgroundSet) {
            SelectObject(compatible, wi->backgroundImage/*HBITMAP for the Background Image*/);
            BitBlt(drawDC, 0, 0, wi->initWidth/*800*/, wi->initHeight/*400*/, compatible, 0, 0, SRCCOPY);
        }
        wi->lock.lock();
        /*wi->lock is a std::unique_lock<std::mutex>, and wi->figures is a std::vector<ImageToRender*>*/
        for (size_t i = (wi->figures).size(); i > 0; --i) {
            figure = (wi->figures)[i - 1];
            if (figure->flags & ImageToRenderFlags::Visible) {
                SelectObject(compatible, figure->bitmap/*Bitmap for the Item*/);
                TransparentBlt(drawDC,figure->xPos,figure->yPos/*Position of the figure in the client area*/,
figure->width/*width of the figure in the client area*/,
figure->height/*height of the figure in the client area*/,
compatible, 0, 0, 
figure->bmpWidth/*Width of the original bitmap*/, 
figure->bmpHeight/*Height of the original Bitmap*/, 
SRCCOPY);
            }
        }
        DeleteDC(compatible);
        int height = wi->height;
        int width = wi->width;
        int xPos = 0;
        int yPos = 0;
        if (wi->flags & WindowFlags::Y_DIM_OVERRIDE) {
            height = wi->dimensionOverride;
            yPos = wi->dimensionOffset;
        }
        else if (wi->flags & WindowFlags::X_DIM_OVERRIDE) {
            width = wi->dimensionOverride;
            xPos = wi->dimensionOffset;
        }
        RECT fillRect;
        fillRect.top = 0;
        fillRect.left = 0;
        fillRect.right = wi->width + 1;
        fillRect.bottom = wi->height + 1;
        FillRect(hdc, &fillRect, reinterpret_cast<HBRUSH>(GetStockObject(BLACK_BRUSH)));
        //FillRect Sets the background to black. Due to the 2:1-Ratio being maintained, not everything is painted; The unpainted parts should have a black background. This call succeeds.
        StretchBlt(hdc, 0, 0, width/*destination width*/, height/*destination height*/, drawDC, 0, 0, wi->initWidth/*800*/, wi->initHeight/*400*/, RGB(0xFF,0xFF,0xFF));
        DeleteDC(drawDC);
        wi->lock.unlock();
        EndPaint(hwnd, &ps); }
        return 0;
    case WM_SIZE:
    {
        RECT clientRects;
        GetClientRect(hwnd, &clientRects);
        //Dimensions of the Window are updated here
        (*wi).width = clientRects.right - clientRects.left;
        (*wi).height = clientRects.bottom - clientRects.top;
        if (wi->width > (wi->height * 2)) {
            //Left+Right are filled with black background
            (*wi).flags &= ~WindowFlags::Y_DIM_OVERRIDE;
            (*wi).flags |= WindowFlags::X_DIM_OVERRIDE;
            (*wi).dimensionOverride = wi->height * 2;
            (*wi).dimensionOffset = (wi->width - wi->dimensionOverride) / 2;
        }
        else if (wi->width==wi->height*2) {
            //No black background when width==height*2
            (*wi).flags &= ~(WindowFlags::Y_DIM_OVERRIDE|WindowFlags::X_DIM_OVERRIDE);
        }
        else {
            //Top+Bottom are filled with black background
            (*wi).flags &= ~WindowFlags::X_DIM_OVERRIDE;
            (*wi).flags |= WindowFlags::Y_DIM_OVERRIDE;
            (*wi).dimensionOverride = wi->width / 2;
            (*wi).dimensionOffset = (wi->height - wi->dimensionOverride) / 2;
        }
        std::thread thread{RedrawWindow, hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN};//Starting RedrawWindow in a new Thread prevents deadlocks where the window message thread waits for RedrawWindow, and RedrawWindow waits for the window message thread to process WM_PAINT
        thread.detach();
    };
    return 0;
[...]

The problem here is that even though the paint operations worked when used directly on the HDC returned by BeginPaint, when used this way, something goes wrong, and the window is just black.This means, that FillRect succeeds, but StretchBlt seems to silently fail(it returned 1, but the changes were not applied). I have no idea what to do anymore, found nothing on the Internet, and asking ChatGPT was of no use either.


Solution

  • Bitmap created by CreateCompatibleDC is 1x1 1 bit. You need to create more interesting bitmap for it.

    HBITMAP drawBM = CreateCompatibleBitmap(hdc, 800, 400);
    HBITMAP oldBM = SelectObject(drawDC, drawBM);
    ... drawing
    SelectObject(drawDC, oldBM);
    DeleteObject(drawBM);