October 25th, 2023

The format of icon resources, revisited

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

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

5 comments

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

  • Jan RingoÅ¡

    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 · Edited

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

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

      Also, uncompressed icons are faster to load than PNG that should be...

      Read more
  • Dimitriy Ryazantcev · Edited

    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?

  • Dimitriy Ryazantcev · Edited

    Thank you, Raymond!

    Another common mistake when using from memory is ignoring the fact that 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 could return DWORD-unaligned buffer. ))

    Read more
    • Raymond ChenMicrosoft employee Author

      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.