The format of icon resources, revisited

Raymond Chen

Some time ago, I explained that a logical icon is represented in resources as a group icon directory and a series of individual icon directory entries, each of which in turn refers to another icon image resource.

But I forgot to discuss the format of those second-level resources.

If the icon image resource represents a cursor, then the resource data begins with two 16-bit signed integers representing the x- and y-coordinates of the cursor hotspot.

Next, after the optional hotspot information, comes one of various image formats.

One possibility is the legacy BITMAPCOREHEADER, followed by the color table (possibly empty), and then the bitmap pixels.

Another possibility is a BITMAPINFOHEADER, again followed by the possibly-empty color table, and then the bitmap pixels.

And a third possibility (starting in Windows Vista) is a PNG-compressed image.

Here’s a diagram (not to scale):

Legacy format Common format PNG format
int16_t xHotspot;
int16_t yHotspot;
(Present only for cursors.)
BITMAPCOREHEADER
color table (possibly empty)
pixels
BITMAPINFOHEADER
color table (possibly empty)
pixels
PNG image

You can access this raw icon image resource data by finding, loading, and locking the icon image resource ID with a resource of type RT_ICON. You can then pass a pointer to the raw icon image resource data to Create­Icon­From­Resource or Create­Icon­From­Resource­Ex to convert it to a proper HICON.

Alternatively, you can synthesize one of these formats in memory and pass it to Create­Icon­From­Resource­(Ex).

Note that in practice, you will want to use Create­Icon­From­Resource­Ex because Create­Icon­From­Resource creates icons at the system default icon size, rather than respecting the size reported by the the icon image data.

The fact that the icon image source data can take the form of a PNG image gives you a sneaky way to load a PNG image as an icon: Load the PNG image into memory and pass it directly to Create­Icon­From­Resource­Ex!

wil::unique_hicon LoadPngAsIcon(PCWSTR fileName)
{
    auto file = wil::unique_hfile{
        CreateFileW(fileName, GENERIC_READ, 0, nullptr,
                OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
    THROW_LAST_ERROR_IF(!file);

    LARGE_INTEGER size;
    THROW_IF_WIN32_BOOL_FALSE(GetFileSizeEx(file.get(), &size));
    THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE),
                size.HighPart != 0);
    auto bits = std::make_unique<BYTE[]>(size.LowPart);

    DWORD actual;
    THROW_IF_WIN32_BOOL_FALSE(ReadFile(file.get(), bits.get(),
            size.LowPart, &actual, nullptr));
    auto icon = wil::unique_hicon{
            CreateIconFromResourceEx(bits.get(), actual, true,
                0x00030000, 0, 0, 0) };
    THROW_LAST_ERROR_IF(!icon);

    return icon;
}

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Dimitriy Ryazantcev 0

    Thank you, Raymond!

    Another common mistake when using CreateIconFromResource(Ex) from memory is ignoring the fact that presbits should be a DWORD-aligned buffer pointer. This usually happens when manually parsing and reading icon data from a ico/cur file.

    I have recently added that info on the corresponding docs page: https://github.com/MicrosoftDocs/sdk-api/pull/1536

    PS: Seems your code can break since std::make_unique(size.LowPart) could return DWORD-unaligned buffer. ))

    • Raymond ChenMicrosoft employee 1

      I cheated a bit and am relying on the default operator new[] being a wrapper around HeapAlloc, and HeapAlloc guarantees MEMORY_ALLOCATION_ALIGNMENT alignment, which is 8 on 32-bit Windows and 16 on 64-bit Windows.

  • Dimitriy Ryazantcev 1

    Raymond, you did not mention RT_ANIICON (legacy?) and RT_ANICURSOR (.ani) resources.
    AFAIK CreateIconFromResource(Ex) can load them too.

    Specifically, I interested in description of multi-resolution animated cursors.

    Can you write a post about it?

  • Jan RingoÅ¡ 0

    I’m always surprised how few tools can generate wholly PNG icons. They all usually generate PNG for 256×256 size, but that’s all, other sizes are uncompressed bitmaps.

    ICOs consisting of PNGs can save quite lot of in terms of final executable size. Even Windows binaries still don’t use PNGs, when doing so could shave tens of MBs off of the whole OS footprint.

    • Dimitriy Ryazantcev 0

      Uncompressed bitmaps are there for backwards compatibility (with pre-Vista systems). Simply scaling down from the 256×256 size does not work since icon will lose details.

      Windows uses PNG compression for all 256×256 icons and requires to do so:
      >Only a 32-bit copy of the 256×256 pixel image should be included, and only the 256×256 pixel image should be compressed to keep the file size down.

      Also, uncompressed icons are faster to load than PNG that should be uncompressed and resized before it could be shown.

Feedback usabilla icon