Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Introduction to Embedded Icons without Resource on ReactOS

0.00/5 (No votes)
21 Oct 2019 1  
How to embed icons into Win32 programs without utilizing resources - useful for platforms without resource editor/resorce compiler, e.g., ReactOS. Learn the missing things about the .ico format.

Introduction

This article goes beyond the great Microsoft online article about the .ico format and introduces the missing things for handling binary .ico data.

I wrote this article to save other developers the hassle of gathering all the necessary information about the .ico data format.

The motivation for the question "embedded icon without utilization of resources" comes from the fact that:

  • I didn't have a resource compiler available,
  • but wanted to embed the icons

for my application programming tests with ReactOS. If you're interested in ReactOS programming, you can also take a look at my tips "Introduction to OpenGL with C/C++ on ReactOS" and "Introduction to C# on ReactOS". This tip is a direct continuation of the "Introduction to OpenGL with C/C++ on ReactOS" tip.

Background

On ReactOS, the most stable programming interface is the plain old Win32 C API - and it provides a lot of icon functions (see Microsoft article "Icons"), but none of them work with a stream or a BYTE[]. The nearest one is LoadImage, that supports bitmaps, icons and cursors. But LoadImage loads from the file system and I don't think it's a good style to ship many .ico files with a program - and would prefer to embed the .ico files. Unfortunately, there is no suitable function for it in the plain old Win32 C API - and so I am forced to write my own function.

A Small Side Note About Icons

I didn't like the Windows feature "icon" very much until the intensive examination of icons. However, there were reasons why I think differently about it today:

  • Under ReactOS, the STATIC window class currently does not support bitmaps, but icons.
  • Appealing icons always need a masking of the transparent pixels. To achieve this with bitmaps, at least two files are required.
  • The old limitation of 16x16 pixels or 32x32 pixels at 16 or 256 colors is removed. Nowadays icons can also be much larger and in TrueColor.

Using the Code

NOTICE: The code I'll provide here is developed for icons with:

  • one image per file
  • 16x16 pixels and
  • 16 color palette

Other formats might work, but I doubt they'll do. Nevertheless, the code contains all information you need to extend the functionality according to your needs.

An icon can easily be embedded into a program. The .ico file must just be copied from any HEX editor to a C file and be formatted as BYTE[], e.g.:

WORD ICO_CLOCKWISE_16_ByteCount = 318;
BYTE ICO_CLOCKWISE_16_Bytes[318] = {
    0x00, 0x00, 0x01, 0x00, 0x01, 0x00,
    0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x04, 0x00,
    0x28, 0x01, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00,
// 00 + 22 = 22
    0x28, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x20, 0x00, 0x00, 0x00,
    0x01, 0x00, 0x04, 0x00,
    0x00, 0x00,
    0x00, 0x00,
    0x80, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
    0x10, 0x00, 0x00, 0x00,
// 22 + 40 = 62 - START COLORTABLE
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x80, 0x00,
    0x00, 0x80, 0x00, 0x00,
    0x00, 0x80, 0x80, 0x00,
    0x80, 0x00, 0x00, 0x00,
    0x80, 0x00, 0x80, 0x00,
    0x80, 0x80, 0x00, 0x00,
    0x40, 0x40, 0x40, 0x00,
    0x60, 0x60, 0x60, 0x00,
    0x00, 0x00, 0xff, 0x00,
    0x00, 0xff, 0x00, 0x00,
    0x00, 0xff, 0xff, 0x00,
    0xff, 0x00, 0x00, 0x00,
    0xff, 0x00, 0xff, 0x00,
    0xff, 0xff, 0x00, 0x00,
    0xff, 0xff, 0xff, 0x00,
// IMAGE
    0x00, 0x00, 0x87, 0x00, 0x07, 0x80, 0x00, 0x00,
    0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x07, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x80,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x70,
    0x07, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x08, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00,
    0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00,
    0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00,
    0x00, 0x00, 0x08, 0x70, 0x00, 0x78, 0x00, 0x00,
// MASK
    0x70, 0x1f, 0x00, 0x00, 0x20, 0x0f, 0x00, 0x00,
    0x01, 0xc7, 0x00, 0x00, 0x07, 0xe3, 0x00, 0x00,
    0x07, 0xf3, 0x00, 0x00, 0x03, 0xf1, 0x00, 0x00,
    0xff, 0xf1, 0x00, 0x00, 0xff, 0xf1, 0x00, 0x00,
    0x8f, 0xff, 0x00, 0x00, 0x8f, 0xff, 0x00, 0x00,
    0x8f, 0xe0, 0x00, 0x00, 0xcf, 0xf0, 0x00, 0x00,
    0xc7, 0xf0, 0x00, 0x00, 0xe1, 0xc0, 0x00, 0x00,
    0xf0, 0x04, 0x00, 0x00, 0xf8, 0x0e, 0x00, 0x00
};

This sample .ico definition contains just one image with 16x16 pixel. My application calls:

HICON hIcon = Utils_CreateIconFromBytes(ICO_CLOCKWISE_16_Bytes(),
                                        ICO_CLOCKWISE_16_ByteCount(), 16, 16);

I stick very close to the great Microsoft online article about the .ico format when buffering the data:

typedef struct tagICONIMAGE
{
    BITMAPINFOHEADER   iiHeader;       // DIB header
    RGBQUAD            iiColors[1];    // Color table
    BYTE*              iiXOR;          // DIB bits for XOR mask
    BYTE*              iiAND;          // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;

typedef struct tagICONDIRENTRY
{
    BYTE              deWidth;         // Width, in pixels, of the image
    BYTE              deHeight;        // Height, in pixels, of the image
    BYTE              deColorCount;    // Number of colors in image (0 if >=8bpp)
    BYTE              deReserved;      // Reserved ( must be 0)
    WORD              dePlanes;        // Color Planes
    WORD              deBitCount;      // Bits per pixel
    DWORD             deBytesInRes;    // How many bytes in this resource?
    DWORD             deImageOffset;   // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;

typedef struct tagICONDIR
{
    WORD              idReserved;      // Reserved (must be 0)
    WORD              idType;          // Resource Type (1 for icons)
    WORD              idCount;         // How many images?
    LPICONDIRENTRY    idEntries;       // An entry for each image.
} ICONDIR, *LPICONDIR;

This includes the data structures (above) and the first processing stage:

/// <summary>
/// Creates an icon from indicated icon bytes.
/// </summary>
/// <param name="pbIconBytes">The icon bytes.</param>
/// <param name="wByteCount">The number of icon bytes.</param>
/// <param name="lWidth">The requested icon width in pixel.</param>
/// <param name="lHeight">The requested icon height in pixel.</param>
/// <returns>The handle to the GDI icon object on success, or <c>NULL</c> otherwise.
/// The caller is responsible to free the GDI object with <c>DestroyIcon()</c>.</returns>
/// <remarks>For details see: "https://docs.microsoft.com/en-us/previous-versions/
///                            ms997538(v=msdn.10)?redirectedfrom=MSDN"</remarks>
__declspec(dllexport) HICON __cdecl Utils_CreateIconFromBytes(BYTE* pbIconBytes,
                                                              WORD wByteCount,
                                                              LONG lWidth, LONG lHeight)
{
    if (pbIconBytes == NULL)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The 'pbIconBytes' argument"
                           L" must not be NULL!\n");
        return NULL;
    }
    if (wByteCount <= 22)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The 'wByteCount' argument"
                           L" must be at least 22!\n");
        return NULL;
    }
    if (lWidth <= 0)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The 'lWidth' argument"
                           L" must be a valid width - at least 1!\n");
        return NULL;
    }
    if (lHeight <= 0)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The 'lHeight' argument"
                           L" must be a valid height - at least 1!\n");
        return NULL;
    }

    // --------------------------------------------------------------------------
    // ICON DIR
    // --------------------------------------------------------------------------

    ICONDIR aIconDir       = {0};

    aIconDir.idReserved    = (WORD)pbIconBytes[0] + (WORD)pbIconBytes[1] * (WORD)256;

    aIconDir.idType        = (WORD)pbIconBytes[2] + (WORD)pbIconBytes[3] * (WORD)256;
    if (aIconDir.idType   != (WORD)1 && aIconDir.idType    != (WORD)2)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The icon header's content"
                           L" type is not equal to '1' (for icon) or '2' (for cursor)!\n");
        return NULL;
    }

    aIconDir.idCount       = (WORD)pbIconBytes[4] + (WORD)pbIconBytes[5] * (WORD)256;
    if (aIconDir.idCount  == (WORD)0)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The icon bits contains '0' images!\n");
        return NULL;
    }

    int n = 6;

    aIconDir.idEntries = new ICONDIRENTRY[aIconDir.idCount];

    for (WORD wCountIconEntries = 0; wCountIconEntries < aIconDir.idCount; wCountIconEntries++)
    {
        // --------------------------------------------------------------------------
        // ICON DIRENTRY
        // --------------------------------------------------------------------------

        LPICONDIRENTRY pIconDirEntry  = &aIconDir.idEntries[wCountIconEntries];

        pIconDirEntry->deWidth         = (BYTE)pbIconBytes[n + 0];
        if (pIconDirEntry->deWidth    == 0)
            pIconDirEntry->deWidth     = (BYTE)256;

        pIconDirEntry->deHeight        = (BYTE)pbIconBytes[n + 1];
        if (pIconDirEntry->deHeight   == 0)
            pIconDirEntry->deHeight    = (BYTE)256;

        pIconDirEntry->deColorCount    = (BYTE)pbIconBytes[n + 2];

        pIconDirEntry->deReserved      = (BYTE)pbIconBytes[n + 3];

        pIconDirEntry->dePlanes        = (WORD)pbIconBytes[n + 4];
        pIconDirEntry->dePlanes       += (WORD)pbIconBytes[n + 5] * (WORD)256;
        if (pIconDirEntry->dePlanes   != (WORD)0 && pIconDirEntry->dePlanes   != (WORD)1)
        {
            Console::WriteText(Console::__ERROR,
                               L"Utils_CreateIconFromBits: The icon header's"
                           L" number of planes is not equal to '1'!\n");
            return NULL;
        }

        pIconDirEntry->deBitCount      = (WORD)pbIconBytes[n + 6];
        pIconDirEntry->deBitCount     += (WORD)pbIconBytes[n + 7] * (WORD)256;
        if (pIconDirEntry->deBitCount == (WORD)0)
        {
            Console::WriteText(Console::__ERROR,
                               L"Utils_CreateIconFromBits: The icon header's"
                           L" bits per pixel is '0'!\n");
            return NULL;
        }
        if (pIconDirEntry->deBitCount != (WORD)4 &&
            pIconDirEntry->deBitCount != (WORD)8 && pIconDirEntry->deBitCount != (WORD)32)
        {
            Console::WriteText(Console::__ERROR,
                               L"Utils_CreateIconFromBits: The icon header's"
                           L" bits per pixel is not equal to '4', '8' or '32'!\n");
            return NULL;
        }

        pIconDirEntry->deBytesInRes   = (DWORD)pbIconBytes[n +  8];
        pIconDirEntry->deBytesInRes  += (DWORD)pbIconBytes[n +  9] * (DWORD)256;
        pIconDirEntry->deBytesInRes  += (DWORD)pbIconBytes[n + 10] * (DWORD)65536;
        pIconDirEntry->deBytesInRes  += (DWORD)pbIconBytes[n + 11] * (DWORD)16777216;
        if (pIconDirEntry->deBytesInRes == (WORD)0)
        {
            Console::WriteText(Console::__ERROR,
                               L"Utils_CreateIconFromBits: The icon header's"
                           L" size of the bitmap is '0'!\n");
            return NULL;
        }

        pIconDirEntry->deImageOffset  = (DWORD)pbIconBytes[n + 12];
        pIconDirEntry->deImageOffset += (DWORD)pbIconBytes[n + 13] * (DWORD)256;
        pIconDirEntry->deImageOffset += (DWORD)pbIconBytes[n + 14] * (DWORD)65536;
        pIconDirEntry->deImageOffset += (DWORD)pbIconBytes[n + 15] * (DWORD)16777216;
        if (pIconDirEntry->deImageOffset == (WORD)0)
        {
            Console::WriteText(Console::__ERROR,
                               L"Utils_CreateIconFromBits: The icon header's"
                           L" offset of the bitmap is '0'!\n");
            return NULL;
        }

        n += 16;
    }

    WORD wPreferredIconEntry = -1;
    for (WORD wCountIconEntries = 0; wCountIconEntries < aIconDir.idCount; wCountIconEntries++)
    {
        LPICONDIRENTRY pIconDirEntry  = &aIconDir.idEntries[wCountIconEntries];

        if ((LONG)pIconDirEntry->deWidth == lWidth && (LONG)pIconDirEntry->deHeight == lHeight)
        {
            wPreferredIconEntry = wCountIconEntries;
            break;
        }
    }
    if (wPreferredIconEntry < 0)
    {
        Console::WriteText(Console::__ERROR,
                           L"Utils_CreateIconFromBits: The icon doesn't include"
                           L" an icon image of the requested width and height!\n");
        return NULL;
    }

    LPICONDIRENTRY pIDEntry  = &aIconDir.idEntries[wPreferredIconEntry];
    HICON          hIcon     = Utils_CreateIconFromBytesSub(pbIconBytes, n, pIDEntry);

    delete aIconDir.idEntries;

    return hIcon;
}

The processing within this function is well known. The rather unknown processing steps are carried out in CreateIconFromBytesSub().

HICON Utils_CreateIconFromBitsSub(BYTE* pbIconBytes, LONG lIconBytesBaseOffset,
                                  LPICONDIRENTRY pIDEntry)
{
    // --------------------------------------------------------------------------
    // ICON IMAGE
    // --------------------------------------------------------------------------

    ICONIMAGE aIconImage = {0};
    LPBITMAPINFOHEADER bmiHeader = &aIconImage.iiHeader;
    LONG n = lIconBytesBaseOffset;

    bmiHeader->biSize           = (DWORD)pbIconBytes[n +  0];
    bmiHeader->biSize          += (DWORD)pbIconBytes[n +  1] * (DWORD)256;
    bmiHeader->biSize          += (DWORD)pbIconBytes[n +  2] * (DWORD)65536;
    bmiHeader->biSize          += (DWORD)pbIconBytes[n +  3] * (DWORD)16777216;

    bmiHeader->biWidth          =  (LONG)pbIconBytes[n +  4];
    bmiHeader->biWidth         +=  (LONG)pbIconBytes[n +  5] * (LONG)256;
    bmiHeader->biWidth         +=  (LONG)pbIconBytes[n +  6] * (LONG)65536;
    bmiHeader->biWidth         +=  (LONG)pbIconBytes[n +  7] * (LONG)16777216;

    bmiHeader->biHeight         =  (LONG)pbIconBytes[n +  8];
    bmiHeader->biHeight        +=  (LONG)pbIconBytes[n +  9] * (LONG)256;
    bmiHeader->biHeight        +=  (LONG)pbIconBytes[n + 10] * (LONG)65536;
    bmiHeader->biHeight        +=  (LONG)pbIconBytes[n + 11] * (LONG)16777216;
    bmiHeader->biHeight        /=  2; // By convention.

    bmiHeader->biPlanes         =  (WORD)pbIconBytes[n + 12];
    bmiHeader->biPlanes        +=  (WORD)pbIconBytes[n + 13] * (WORD)256;

    bmiHeader->biBitCount       =  (WORD)pbIconBytes[n + 14];
    bmiHeader->biBitCount      +=  (WORD)pbIconBytes[n + 15] * (WORD)256;

    bmiHeader->biCompression    = (DWORD)pbIconBytes[n + 16];
    bmiHeader->biCompression   += (DWORD)pbIconBytes[n + 17] * (DWORD)256;
    bmiHeader->biCompression   += (DWORD)pbIconBytes[n + 18] * (DWORD)65536;
    bmiHeader->biCompression   += (DWORD)pbIconBytes[n + 19] * (DWORD)16777216;

    bmiHeader->biSizeImage      = (DWORD)pbIconBytes[n + 20];
    bmiHeader->biSizeImage     += (DWORD)pbIconBytes[n + 21] * (DWORD)256;
    bmiHeader->biSizeImage     += (DWORD)pbIconBytes[n + 22] * (DWORD)65536;
    bmiHeader->biSizeImage     += (DWORD)pbIconBytes[n + 23] * (DWORD)16777216;

    bmiHeader->biXPelsPerMeter  =  (LONG)pbIconBytes[n + 24];
    bmiHeader->biXPelsPerMeter +=  (LONG)pbIconBytes[n + 25] * (LONG)256;
    bmiHeader->biXPelsPerMeter +=  (LONG)pbIconBytes[n + 26] * (LONG)65536;
    bmiHeader->biXPelsPerMeter +=  (LONG)pbIconBytes[n + 27] * (LONG)16777216;

    bmiHeader->biYPelsPerMeter  =  (LONG)pbIconBytes[n + 28];
    bmiHeader->biYPelsPerMeter +=  (LONG)pbIconBytes[n + 29] * (LONG)256;
    bmiHeader->biYPelsPerMeter +=  (LONG)pbIconBytes[n + 30] * (LONG)65536;
    bmiHeader->biYPelsPerMeter +=  (LONG)pbIconBytes[n + 31] * (LONG)16777216;

    bmiHeader->biClrUsed        = (DWORD)pbIconBytes[n + 32];
    bmiHeader->biClrUsed       += (DWORD)pbIconBytes[n + 33] * (DWORD)256;
    bmiHeader->biClrUsed       += (DWORD)pbIconBytes[n + 34] * (DWORD)65536;
    bmiHeader->biClrUsed       += (DWORD)pbIconBytes[n + 35] * (DWORD)16777216;

    bmiHeader->biClrImportant   = (DWORD)pbIconBytes[n + 36];
    bmiHeader->biClrImportant  += (DWORD)pbIconBytes[n + 37] * (DWORD)256;
    bmiHeader->biClrImportant  += (DWORD)pbIconBytes[n + 38] * (DWORD)65536;
    bmiHeader->biClrImportant  += (DWORD)pbIconBytes[n + 39] * (DWORD)16777216;

    if (bmiHeader->biSize != 40)
    {
        Console::WriteText(Console::__ERROR, L"Utils_CreateIconFromBits: The icon header"
                           L" size is not equal to '40'!\n");
        return NULL;
    }

    if (bmiHeader->biPlanes != 1)
    {
        Console::WriteText(Console::__ERROR, L"Utils_CreateIconFromBits: The icon header's"
                           L" number of planes is not equal to '1'!\n");
        return NULL;
    }

    if (bmiHeader->biCompression != 0)
    {
        Console::WriteText(Console::__ERROR, L"Utils_CreateIconFromBits: The icon header's"
                           L" compression is not equal to '0'!\n");
        return NULL;
    }

    if (bmiHeader->biSizeImage != (DWORD)(bmiHeader->biWidth * bmiHeader->biHeight) *
                                          bmiHeader->biPlanes * bmiHeader->biBitCount / 8)
    {
        Console::WriteText(Console::__ERROR, L"Utils_CreateIconFromBits: The image size"
                           L" of bitmap '1' is not equal to the calculated size!\n");
        return NULL;
    }

    n += 40;

    // --------------------------------------------------------------------------
    // COLOR PALETTE
    // --------------------------------------------------------------------------

    COLORREF* aColorPalette = (COLORREF*)new COLORREF[pIDEntry->deColorCount];
    for (DWORD i = 0; i < pIDEntry->deColorCount; i++)
    {
        DWORD color = 0;
        color  = (DWORD)pbIconBytes[n + i * 4 + 0];
        color += (DWORD)pbIconBytes[n + i * 4 + 1] * (DWORD)256;
        color += (DWORD)pbIconBytes[n + i * 4 + 2] * (DWORD)65536;
        color += (DWORD)pbIconBytes[n + i * 4 + 3] * (DWORD)16777216;
        aColorPalette[i] = color;
    }

    n += sizeof(COLORREF) * pIDEntry->deColorCount;

    // --------------------------------------------------------------------------
    // XOR BITMAP
    // --------------------------------------------------------------------------

    aIconImage.iiXOR = (BYTE*)new COLORREF[bmiHeader->biWidth * bmiHeader->biHeight];
    for (LONG rowXOR = 0; rowXOR < bmiHeader->biHeight; rowXOR++)
    {
        for (LONG colXOR = 0; colXOR < bmiHeader->biWidth; colXOR++)
        {
            LONG sourcePos = rowXOR * bmiHeader->biWidth + colXOR;
            LONG targetPos = (bmiHeader->biHeight - 1 - rowXOR) * bmiHeader->biWidth + colXOR;

            if (bmiHeader->biBitCount == 4)
            {
                // Every BYTE stores the color index of two pixel.
                BYTE palIndex  = pbIconBytes[n + sourcePos / 2];
                COLORREF color;
                if (colXOR % 2 == 0)
                    color = aColorPalette[(palIndex & 0xf0) / 16];
                else
                    color = aColorPalette[(palIndex & 0x0f)     ];

                ((COLORREF*)aIconImage.iiXOR)[targetPos] = color;
            }
        }
    }

    n += bmiHeader->biSizeImage;

    // --------------------------------------------------------------------------
    // AND BITMAP
    // --------------------------------------------------------------------------

    aIconImage.iiAND = (BYTE*)new BYTE[bmiHeader->biSizeImage / 2];
    for (DWORD i = 0; i < bmiHeader->biSizeImage / 2; i++)
        aIconImage.iiAND[i] = pbIconBytes[n + i];

    LONG sourceOffset = 0;
    for (LONG rowAND = 0; rowAND < bmiHeader->biHeight; rowAND++)
    {
        // The mask is BIT-wise. Every BYTE holds the mask for 8 BITs.
        for (LONG colAND = 0; colAND < bmiHeader->biWidth; colAND += 8)
        {
            BYTE mask  = pbIconBytes[n + sourceOffset];
            sourceOffset++;

            LONG targetPos = (bmiHeader->biHeight - 1 - rowAND) *
                             bmiHeader->biWidth / 8 + colAND / 8;
            aIconImage.iiAND[targetPos] = mask;
        }
        while (sourceOffset % 4 != 0)
            sourceOffset++;
    }

    // --------------------------------------------------------------------------
    // ICON
    // --------------------------------------------------------------------------

    HICON hIcon        = NULL;

    Utils_InspectBitmapBytes (&(pbIconBytes[pIDEntry->deImageOffset]), pIDEntry->deBytesInRes);

    ICONINFO ii = {0};
    ii.fIcon = TRUE;
    ii.xHotspot = 0;
    ii.yHotspot = 0;
    ii.hbmColor = ::CreateBitmap(bmiHeader->biWidth,  bmiHeader->biHeight,
                                 bmiHeader->biPlanes, 32, aIconImage.iiXOR);
    ii.hbmMask  = ::CreateBitmap(bmiHeader->biWidth, bmiHeader->biHeight,
                                 1, 1, aIconImage.iiAND);

    hIcon = ::CreateIconIndirect(&ii);

    ::DeleteObject(ii.hbmMask);
    ::DeleteObject(ii.hbmColor);
    delete aColorPalette;
    delete aIconImage.iiXOR;
    delete aIconImage.iiAND;

    return hIcon;
}

That's it!

Points of Interest

What did I learn beyond the great Microsoft online article about the .ico format?

  • The value of BITMAPINFOHEADER.biHeight must be divided by 2.
  • The color table follows immediately after the BITMAPINFOHEADER data.
  • The length of the color table is already known by ICONDIRENTRY.deColorCount.
  • The ICONIMAGE.iiXOR data follows immediately after the color table, followed by the ICONIMAGE.iiAND data.

History

  • 22nd October, 2019: Initial tip

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here