Search code examples
winapimfcwindows-10gdibitmapimage

Saving Bitmaps on Windows 10 with GDI scaling active


I have a MFC application with toolbars (using CMFCToolbar). I create the toolbar bitmap on the fly using bitmaps from files and resources. The DIBs have different color formats.

  • So I create an empty bitmap toolbar image compatible to screen DC.
  • Then I open all the bitmaps and blit the content to the toolbar bitmap (GDI does colorspace conversion and stretching for me).
  • Then I save the bitmap to a 24-bit DIB file.
  • Then I create the toolbar object and load the image.

That has worked for ages and is working now except for one case:

Recently we had to enable GDI scaling for Windows 10 1703 and later. On a system with high resolution display and 200% scaling (like Surface) the following effect occurs:

All toolbar icons are distorted.

I also found the reason:

When saving the composed image I only get the top-left quarter of the image. Width and height of the bitmap did not change (say 1024x15) compared to normal resolution display without GDI scaling. But that bitmap only contains the pixels of the top-left quarter (see example below).

So I assume the device context tells Windows about 200% scaling. When blitting from source to target the image gets scaled up automatically but the dimension of the bitmap does not change.

How can I save the unscaled bitmap?

-or-

How can I correctly save the scaled bitmap? Where to get the missing pixels? Where to get proper dimensions? (HBITMAP refers only to unscaled dimensions).

Example:

no GDI scaling, correct:

no scaling

200% scaling, same dimensions, but only top-left quarter of the correct image:

with 200 scaling


Solution

  • Summary and solution:

    Let's say we create a memory bitmap compatible to screen format (DDB):

    CBitmap toolBitmap;
    toolBitmap.CreateCompatibleBitmap (pDC, 1000, 20);
    

    Later we blit something into the memory bitmap (does not matter here). Now we want to save the bitmap (write as DIB to file).

    Although we know the dimensions (here: 1000x20) we should not use them. Because on Window 10 and process has GDI scaling activated and high resolution display with scaling is used - the dimensions might have changed internally. Thus the bitmap is not 1000x20 anymore.

    This one fails:

    BITMAP bmHdr;
    toolBitmap.GetObject(sizeof(BITMAP), &bmHdr);
    

    The Bitmap header contains the original dimensions (1000x20). Using them for saving to file results in incomplete image. Only upper left part will be stored.

    This one works - we can retrieve scaled dimensions:

    BITMAPINFO bi = {};
    bi.dwSize = sizeof(bi);
    int result = GetDIBits(pDC->GetSafeHdc(), (HBITMAP)toolBitmap.GetSafeHandle(), 0, 0, NULL, &bi, DIB_RGB_COLORS);
    

    Now we can proceed with new dimensions.

    I ended up using GDI+ functions, which also save the complete (scaled) bitmap:

    Gdiplus::Bitmap bm((HBITMAP)toolBitmap.GetSafeHandle(), NULL);
    Gdiplus::Status status = bm.Save(pwszFileName, &clsidEncoder, NULL);
    

    I presume there are tons of old MFC and GDI code which will not work correctly with activated GDI scaling on Windows 10.