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;
}