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,
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,
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,
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,
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;
RGBQUAD iiColors[1];
BYTE* iiXOR;
BYTE* iiAND;
} ICONIMAGE, *LPICONIMAGE;
typedef struct tagICONDIRENTRY
{
BYTE deWidth;
BYTE deHeight;
BYTE deColorCount;
BYTE deReserved;
WORD dePlanes;
WORD deBitCount;
DWORD deBytesInRes;
DWORD deImageOffset;
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct tagICONDIR
{
WORD idReserved;
WORD idType;
WORD idCount;
LPICONDIRENTRY idEntries;
} ICONDIR, *LPICONDIR;
This includes the data structures (above) and the first processing stage:
__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;
}
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++)
{
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)
{
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;
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;
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;
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)
{
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;
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++)
{
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++;
}
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