c++ – How to preview GDI+ images using Image Watch?


I’m trying to visualize on memory GDI+ images using the Image Watch extension.

I requested support on this feature long time ago directly on the developercommunity it got a lot of upvotes but MSFT just marked it as “under review”.

I tried to follow the extension documentation and implement the natvis myself, but i’m not suceeding on get it to work.

A reproducible example:

#include <windows.h>
#include <iostream>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment (lib, "Gdiplus.lib")


int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT num = 0;
    UINT size = 0;
    ImageCodecInfo* pImageCodecInfo = nullptr;
    GetImageEncodersSize(&num, &size);
    if (size == 0)
        return -1;

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == nullptr)
        return -1;

    GetImageEncoders(num, size, pImageCodecInfo);
    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return 1;
        }
    }

    free(pImageCodecInfo);
    return 0;
}

int main()
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);

    HDC hScreenDC      = GetDC(nullptr);
    HDC hMemoryDC      = CreateCompatibleDC(hScreenDC);
    int screenWidth    = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight   = GetSystemMetrics(SM_CYSCREEN);
    HBITMAP hBitmap    = CreateCompatibleBitmap(hScreenDC, screenWidth, screenHeight);
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);
    BitBlt(hMemoryDC, 0, 0, screenWidth, screenHeight, hScreenDC, 0, 0, SRCCOPY);

    Bitmap bitmap(hBitmap, nullptr);
    CLSID clsid;
    if (GetEncoderClsid(L"image/png", &clsid) == 1)
    {
        if (bitmap.Save(L"test.png", &clsid, nullptr) != Gdiplus::Ok)
            std::wcerr << L"Failed to save the screenshot." << std::endl;
    }
    else
        std::wcerr << L"Failed to get encoder CLSID." << std::endl;

    // ...
    // cleanups ignored as this code is just for debugging

}

And my current natvis implementation:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
    <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1"
                  MenuName="Add to Image Watch"/>

    <!-- GDI BITMAP -->
    <Type Name="tagBITMAP">
        <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
        <Expand>
            <Item Name="[width]">bmWidth</Item>
            <Item Name="[height]">bmHeight</Item>
            <Item Name="[stride]">bmWidthBytes</Item>
            <Synthetic Name="[channels]">
                <DisplayString Condition="bmBitsPixel == 8">L</DisplayString>
                <DisplayString Condition="bmBitsPixel == 24">RGB</DisplayString>
                <DisplayString Condition="bmBitsPixel == 32">RGBA</DisplayString>
            </Synthetic>
            <Synthetic Name="[type]">
                <DisplayString>UINT8</DisplayString>
            </Synthetic>
            <Item Name="[data]">bmBits</Item>
        </Expand>
    </Type>

    <!-- GDI HBITMAP -->
    <Type Name="HBITMAP__">
        <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
        <Expand>
            <CustomListItems>
                <Variable Name="pBitmap" InitialValue="0" />
                <Exec>pBitmap = (tagBITMAP*)alloca(sizeof(tagBITMAP))</Exec>
                <Exec>GetObject((HBITMAP)this, sizeof(tagBITMAP), pBitmap)</Exec>
                <Item Name="[width]">pBitmap->bmWidth</Item>
                <Item Name="[height]">pBitmap->bmHeight</Item>
                <Item Name="[stride]">pBitmap->bmWidthBytes</Item>
                <Item Name="[channels]">
                    pBitmap->bmBitsPixel == 8 ? "L" :
                    pBitmap->bmBitsPixel == 24 ? "RGB" :
                    pBitmap->bmBitsPixel == 32 ? "RGBA" : "UNKNOWN"
                </Item>
                <Item Name="[type]">"UINT8"</Item>
                <Item Name="[data]">pBitmap->bmBits</Item>
            </CustomListItems>
        </Expand>
    </Type>

    <!-- GDI+ Bitmap -->
    <Type Name="Gdiplus::Bitmap">
        <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
        <Expand>
            <CustomListItems>
                <Variable Name="width" InitialValue="0" />
                <Variable Name="height" InitialValue="0" />
                <Variable Name="stride" InitialValue="0" />
                <Variable Name="pixelFormat" InitialValue="0" />
                <Variable Name="scan0" InitialValue="0" />
                <Exec>this->GetWidth(&amp;width)</Exec>
                <Exec>this->GetHeight(&amp;height)</Exec>
                <Exec>Gdiplus::BitmapData bitmapData;</Exec>
                <Exec>Gdiplus::Rect rect(0, 0, width, height);</Exec>
                <Exec>this->LockBits(&amp;rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &amp;bitmapData)</Exec>
                <Exec>stride = bitmapData.Stride</Exec>
                <Exec>pixelFormat = bitmapData.PixelFormat</Exec>
                <Exec>scan0 = bitmapData.Scan0</Exec>
                <Exec>this->UnlockBits(&amp;bitmapData)</Exec>
                <Item Name="[width]">width</Item>
                <Item Name="[height]">height</Item>
                <Item Name="[stride]">stride</Item>
                <Item Name="[channels]">
                    pixelFormat == PixelFormat24bppRGB ? "RGB" :
                    pixelFormat == PixelFormat32bppARGB ? "RGBA" :
                    pixelFormat == PixelFormat8bppIndexed ? "L" : "UNKNOWN"
                </Item>
                <Item Name="[type]">"UINT8"</Item>
                <Item Name="[data]">scan0</Item>
            </CustomListItems>
        </Expand>
    </Type>
</AutoVisualizer>

Using the natvis above the extension is detecting the local images but it always show them as invalid:

enter image description here

I’m testing on VS22/Image Watch for VS22.

Looking for any help on this issue.

Leave a Reply

Your email address will not be published. Required fields are marked *