HBITMAP CDpiHelper::CreateDeviceFromLogicalImage()

in ArchivedSamples/High-DPI_Images_Icons/Cpp/VsUIDpiHelper.cpp [412:548]


HBITMAP CDpiHelper::CreateDeviceFromLogicalImage(HBITMAP _In_ hImage, ImageScalingMode scalingMode, Color clrBackground)
{
    IfNullAssertRetNull(hImage, "No image given to convert");

    // Instead of doing HBITMAP resizing with StretchBlt from one memory DC into other memory DC and HALFTONE StretchBltMode
    // which uses nearest neighbor resize algorithm (fast but results in pixelation), we'll use a GdiPlus image to do the resize, 
    // which allows specifying the interpolation mode for the resize resulting in smoother result.
    VsUI::GdiplusImage gdiplusImage;

    // Attaching the bitmap uses Bitmap.FromHBITMAP which does not take ownership of the HBITMAP passed as argument.
    // DeleteObject still needs to be used on the hImage but that should happen after the Bitmap object is deleted or goes out of scope.
    // The caller will have to DeleteObject both the HBITMAP they passed in this function and the new HBITMAP we'll be returning when we detach the GDI+ Bitmap
    gdiplusImage.Attach(hImage);

#ifdef DEBUG
    static bool fDebugDPIHelperScaling = false;
    WCHAR rgTempFolder[MAX_PATH];
    static int imgIndex = 1;
    CStringW strFileName;
    CPath pathTempFile;

    if (fDebugDPIHelperScaling)
    {
        if (!GetTempPath(_countof(rgTempFolder), rgTempFolder))
            *rgTempFolder = '\0';
    
        strFileName.Format(_T("DPIHelper_%05d_Before.png"), imgIndex);

        pathTempFile.Combine(rgTempFolder, strFileName);
        gdiplusImage.Save(pathTempFile);
    }
#endif

    Bitmap* pBitmap = gdiplusImage.GetBitmap();
    PixelFormat format = pBitmap->GetPixelFormat();
    const Color *pclrActualBackground = &clrBackground; 
    InterpolationMode interpolationMode = GetInterpolationMode(scalingMode);
    ImageScalingMode actualScalingMode = GetActualScalingMode(scalingMode);

    if (actualScalingMode != ImageScalingMode::NearestNeighbor)
    {
        // Modify the image. If the image is 24bpp or lower, convert to 32bpp so we can use alpha values
        if (format != PixelFormat32bppARGB)
        {
            pBitmap->ConvertFormat(PixelFormat32bppARGB, DitherTypeNone, PaletteTypeCustom, nullptr/*ColorPalette*/, 0 /*alphaThresholdPercent - all opaque*/);
        }

        // Now that we have 32bpp image, let's play with the pixels
        // Detect magenta or near-green in the image and use that as background
        VsUI::GdiplusImage::ProcessBitmapBits(pBitmap, [&](ARGB * pPixelData) 
        {
            if (clrBackground.GetValue() != TransparentColor.GetValue())
            {
                if (*pPixelData == clrBackground.GetValue())
                {
                    *pPixelData = TransparentHaloColor.GetValue();
                    pclrActualBackground = &clrBackground;
                }
            }
            else
            {
                if (*pPixelData == MagentaColor.GetValue())
                {
                    *pPixelData = TransparentHaloColor.GetValue();
                    pclrActualBackground = &MagentaColor;
                }
                else if (*pPixelData == NearGreenColor.GetValue())
                {
                    *pPixelData = TransparentHaloColor.GetValue();
                    pclrActualBackground = &MagentaColor;
                }
            }
        });
    }

    // Convert the GdiPlus image if necessary
    LogicalToDeviceUnits(&gdiplusImage, scalingMode, TransparentHaloColor);

    // Get again the bitmap, after the resize
    pBitmap = gdiplusImage.GetBitmap();

    if (actualScalingMode != ImageScalingMode::NearestNeighbor)
    {
        // Now that the bitmap is scaled up, convert back the pixels. 
        // Anything that is not fully opaque, make it clrActualBackground
        VsUI::GdiplusImage::ProcessBitmapBits(pBitmap, [&](ARGB * pPixelData) 
        {
            if ((*pPixelData & ALPHA_MASK) != 0xFF000000)
            {
                *pPixelData = pclrActualBackground->GetValue();
            }
        });

        // Convert back to original format
        if (format != PixelFormat32bppARGB)
        {
            pBitmap->ConvertFormat(format, DitherTypeNone, PaletteTypeCustom, nullptr/*ColorPalette*/, 0 /*alphaThresholdPercent - all opaque*/);
        }
    }

#ifdef DEBUG
    if (fDebugDPIHelperScaling)
    {
        strFileName.Format(_T("DPIHelper_%05d_After.png"), imgIndex++);
        pathTempFile.Combine(rgTempFolder, strFileName);
        gdiplusImage.Save(pathTempFile);
    }
#endif
  
    // Get the converted image handle - this returns a new HBITMAP that will need to be deleted when no longer needed
    // Detach using TransparentColor (transparent-black). If the result bitmap is to be used with AlphaBlend, that function 
    // keeps the background if the transparent pixels are black
    HBITMAP hBmpResult = gdiplusImage.Detach( TransparentColor );

    // When the image has 32bpp RGB format, when we call GDI+ to return an HBITMAP for the image, the result is actually
    // an ARGB bitmap (with alpha bytes==0xFF instead of reserved=0x00). Many GDI functions work with it fine, but 
    // adding it to an imagelist with ImageList_AddMasked will produce the wrong result, because the clrTransparent color 
    // won't match any background pixels due to the alpha byte value. So we need to zero-out out those bytes... 
    // If the bitmap was scaled with a bicubic/bilinear interpolation, the colors are interpolated with the clrBackground 
    // which may be transparent, so the resultant image will have alpha channel of interest, and we'll return the image as is.
    if (format == PixelFormat32bppRGB)
    {
        BITMAP bmp = {0};
        if (GetObject(hBmpResult, sizeof(bmp), &bmp) == sizeof(bmp) && bmp.bmBits != nullptr)
        {
            RGBQUAD* pPixels = reinterpret_cast<RGBQUAD*>(bmp.bmBits);

            for (int i=0; i< bmp.bmWidth * bmp.bmHeight; i++)
            {
                pPixels[i].rgbReserved = 0;
            }
        }
    }

    // Return the created image
    return hBmpResult;
}