HIMAGELIST CDpiHelper::CreateDeviceFromLogicalImage()

in ArchivedSamples/High-DPI_Images_Icons/Cpp/VsUIDpiHelper.cpp [568:694]


HIMAGELIST CDpiHelper::CreateDeviceFromLogicalImage(HIMAGELIST _In_ hImageList, ImageScalingMode scalingMode)
{
    IfNullAssertRetNull(hImageList, "No imagelist given to convert");

    // If no scaling is required, return an image copy
    if (!IsScalingRequired())
        return ImageList_Duplicate(hImageList);

    int nCount = ImageList_GetImageCount(hImageList);

    int cxImage = 0;
    int cyImage = 0;
    IfFailRetNull( ImageList_GetIconSize(hImageList, &cxImage, &cyImage) );

    int cxImageDevice = LogicalToDeviceUnitsX(cxImage);
    int cyImageDevice = LogicalToDeviceUnitsY(cyImage);

    // Create the new device imagelist. Use ILC_COLOR24 instead of ILC_COLOR32 because the images we're adding with
    // ImageList_AddMasked don't have alpha channel (have 0 bytes), which would otherwise be interpreted by imagelist themeing code
    // imagelist shell theming code as being completely transparent (and losing color information), later resulting in black pixels when the themed imagelist is drawn
    bool fImageListComplete = false;
    HIMAGELIST hImageListDevice = ImageList_Create(cxImageDevice, cyImageDevice, ILC_COLOR24 | ILC_MASK, nCount /*cInitial*/, 0 /*cGrow*/);
    IfNullRetNull(hImageListDevice);
    SCOPE_GUARD({ 
       if (!fImageListComplete) 
           ImageList_Destroy(hImageListDevice);
    });

    ImageList_SetBkColor(hImageListDevice, ImageList_GetBkColor(hImageList));
    
    if (nCount != 0)
    {
        CWinClientDC dcScreen(NULL);
        IfNullRetNull(dcScreen);

        CWinManagedDC dcMemoryLogical(CreateCompatibleDC(dcScreen));
        IfNullRetNull(dcMemoryLogical);

        HIMAGELIST hImageListNoTransparency = NULL;
        SCOPE_GUARD({ 
            if (hImageListNoTransparency)
               ImageList_Destroy(hImageListNoTransparency);
        });
        
        // If the source imagelist uses ILC_COLOR32, the color bitmap may have partial transparent pixels
        // If we were to paint them on a Magenta background for our ILC_COLOR24 output bitmap, those pixels 
        // would get a magenta tint. To get rid of the partial transparency, we'll create first a 24bpp 
        // Imagelist with background of Halo color, and we'll copy the images from the original list.
        // The imagelist background color is used for interpolation of partial transparent pixels.

        IMAGEINFO imageInfo = {0};
        if (ImageList_GetImageInfo(hImageList, 0, &imageInfo))
        {
            BITMAPINFO bi = {0};
            bi.bmiHeader.biSize = sizeof(bi.bmiHeader);
            
            // Call GetDIBits without the underlying array to determine bitmap attributes
            if (GetDIBits(dcScreen, imageInfo.hbmImage, /* uStartScan */ 0, /* cScanLines */ 0, /* lpvBits */ nullptr, &bi, DIB_RGB_COLORS))
            {
                if (bi.bmiHeader.biBitCount == 32)
                {
                    VSASSERT(imageInfo.hbmMask != NULL, "The imagelist contains 32bpp image with no mask, not supported yet. The results will be incorrect.");
                    hImageListNoTransparency = ImageList_Create(cxImage, cyImage, ILC_COLOR24 | ILC_MASK, nCount /*cInitial*/, 0 /*cGrow*/);
                    IfNullRetNull(hImageListNoTransparency);

                    ImageList_SetBkColor(hImageListNoTransparency, HaloColor.ToCOLORREF());
                    
                    for (int iImage = 0; iImage < nCount; iImage++)
                    {
                        // Unfortunately ImageList_Copy can only copy within same imagelist, 
                        // so we have to extract icons one by one and add into the other imagelist
                        HICON hIcon = ImageList_GetIcon(hImageList, iImage, 0);
                        IfNullRetNull(hIcon);
                        SCOPE_GUARD( DestroyIcon(hIcon); );

                        if (ImageList_AddIcon(hImageListNoTransparency, hIcon) == -1) 
                            return NULL;
                    }                    

                    // Set the background color, so further draw operations will use the mask
                    ImageList_SetBkColor(hImageListNoTransparency, CLR_NONE);
                }
            }
        }

        HIMAGELIST hImageListToDraw = hImageListNoTransparency ? hImageListNoTransparency : hImageList;

        // Use Magenta for transparency
        const Color& clrTransparency = MagentaColor;

        CWinManagedBrush brTransparent;
        brTransparent.CreateSolidBrush(clrTransparency.ToCOLORREF());
        IfNullRetNull(brTransparent);

        RECT rcImage = { 0, 0, cxImage, cyImage};

        for (int iImage = 0; iImage < nCount; iImage++)
        {
            CWinManagedBitmap bmpMemory;
            bmpMemory.CreateCompatibleBitmap(dcScreen, cxImage, cyImage);
            IfNullRetNull(bmpMemory);

            // Select the logical bitmap
            dcMemoryLogical.SelectBitmap(bmpMemory);

            // Draw image by image in dcMemoryLogical
            IfFailRetNull( dcMemoryLogical.FillRect(&rcImage, brTransparent) );
            IfFailRetNull( ImageList_Draw(hImageListToDraw, iImage, dcMemoryLogical, 0, 0, ILD_NORMAL) );

            // Restore the original bitmap in the DC
            dcMemoryLogical.SelectBitmap(dcMemoryLogical.m_hOriginalBitmap);

            // Now scale the image according with the current DPI 
            HBITMAP hbmp = bmpMemory.Detach();
            LogicalToDeviceUnits(&hbmp, scalingMode, clrTransparency);
            bmpMemory.Attach(hbmp);

            // Add the device image to the new imagelist
            if (ImageList_AddMasked(hImageListDevice, bmpMemory, clrTransparency.ToCOLORREF()) == -1)
                return NULL;
        }
    }

    // Flag that scop guard should not delete the image we'll be returning
    fImageListComplete = true;
    return hImageListDevice;
}