I basically had to do everything myself (with the help of the web, Stack Overflow, and several MSDN articles) so I think I'll just post my own solution here.
I ended up parsing the registry to find the locations of the icons of each previously registered file extension, since the API functions that should have easily gotten me the information I wanted have some... problems.
After that I spent several days manually observing the data formats at hand by observing output of an icon program, and with this information in hand I was able to construct an image loader.
I used Allegro game library to make dealing with BITMAP images easier - Win32/GDI is a bit too much to deal with and would have made the code exorbitantly messy.
Finding the Icon Location and Index:
(1) Look for extension under HKEY_CLASSES_ROOT, eg HKCR\.foo\(default) = "foofile"
(2) Default data of this is the next key to look at, eg HKCR\foofile\
(3) Default data here is the description eg HKCR\foofile\(default) = "Foo Document"
(4) The icon location may be in one of two places that I know of:
Either in HKCR\foofile\DefaultIcon\(default)
or there may be an entry something like HKCR\foofile\CurVer\(default) = "foofile.1"
which tells you to look at the key HKCR\foofile.1\DefaultIcon\(default)
for the icon location.
Parsing the Icon Location String:
The string is simply a path followed by a comma, white space, possibly a negative sign, and a number indicating the "index" of the icon.
Here's the big gotcha: Let the icon index be N. If N is negative (might want to check for negative zeros!), it is a resource ID within the file specified. If N is positive, it means to find the N'th icon within the file, but the icon is not necessarily at resource ID number N.
Parsing Icon Structures Manually:
This is the bulk of the code and time spent, but it works beautifully. First off, here's the data formats for the various sections of color and mask data.
Data Block Formats:
32bit ... Color Data:
====================================================================================
Little Endian 4 byte ARGB values.
The rows are stored in reverse order (bottom to top).
24bit ... Color Data:
====================================================================================
Little Endian 3 byte RGB values.
Tightly Packed (NO PADDING).
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
16bit ... Color Data:
====================================================================================
Little Endian 2 byte RGB values. 5 bits each with MSB = 0.
Tightly Packed (NO PADDING).
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
8bit ... Palette & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There *might* be up to 256 palette entries.
If number of colors is reported as zero, assume 256 color entires.
The Pixels are 1 byte index values.
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
4bit ... Palette & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There *might* be up to 16 palette entries.
If number of colors is reported as zero, assume 16 color entires.
The Pixels are nybble-length index values.
INSERT PADDING BYTES TO GO UP TO NEXT DWORD AT END OF ROW. SET THEM TO 0x00.
The rows are stored in reverse order (bottom to top).
Mask Data:
====================================================================================
Is a string of bytes with mask bits starting at MSB and going towards LSB.
There are ((imagewidth+31)>>5) DWORDS per row in *BIG ENDIAN* order.
Like the color data, there is a set of DWORDS for each row.
The rows are stored in reverse order (bottom to top).
Set unused padding bits/pixels at end of each row to 1.
0 indicates opaque and 1 indicates transparent.
1bit ... XOR Mask, AND Mask, & Color Data:
====================================================================================
The Palette is Little Endian 4 byte RGB0 values. No alpha.
There should be exactly 2 palette entries: usually 0x00000000 and 0x00FFFFFF.
The two masks follow the Mask Data format decribed above.
The following results from combining two mask bits:
XOR AND RESULT:
0 0 Color #0 (Black)
0 1 Transparent
1 0 Color #1 (White)
1 1 Invert Destination Bitmap
Of course I wouldn't have left it at this. There's code to be had!
The following code will load up and convert all of the icon images for a given icon location to a vector of 32bpp BITMAPs. If loading a given image fails, it will simply just not be added to the vector (or, in the case of a corrupt icon, it will most likely generate a corrupted image, so be careful).
The code does not support the "invert" color in monochrome images, and will just generate a different color that still has zero alpha.
WARNING: Some psuedo-code is included to shorten things to just the essentials.
Icon Loader Code (Supports: EXE, DLL, 32bit ICL, ICO):
// Code written by Simion32.
// Please feel free to use it anywhere.
// Credit would be nice but isn't required.
#include "include.h" //std::vectors and whatever else you need
#include <allegro.h>
#include <winalleg.h> //Allegro and Win32
#include "Shellapi.h"
// In the following block, the (required!!) pragmas
// force correct data alignment. Needed in at least GCC.
#pragma pack( push, 1 )
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved ( must be 0)
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // How many bytes in this resource?
DWORD dwImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
} ICONDIR, *LPICONDIR;
typedef struct
{
BITMAPINFOHEADER icHeader; // DIB header
RGBQUAD icColors[1]; // Color table
BYTE icXOR[1]; // DIB bits for XOR mask
BYTE icAND[1]; // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;
#pragma pack( pop)
#pragma pack( push, 2 )
typedef struct
{
BYTE bWidth; // Width, in pixels, of the image
BYTE bHeight; // Height, in pixels, of the image
BYTE bColorCount; // Number of colors in image (0 if >=8bpp)
BYTE bReserved; // Reserved
WORD wPlanes; // Color Planes
WORD wBitCount; // Bits per pixel
DWORD dwBytesInRes; // total size of the RT_ICON resource referenced by the nID member.
WORD nID; // resourceID of RT_ICON (LockResource to obtain a pointer to its ICONIMAGE)
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
typedef struct
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource type (1 for icons)
WORD idCount; // How many images?
GRPICONDIRENTRY idEntries[1]; // The entries for each image
} GRPICONDIR, *LPGRPICONDIR;
#pragma pack( pop )
uint32_t Convert16BitToARGB(uint16_t value)
{
return (0xFF000000|((value >> 7) & 0x0000F8)|((value << 6) & 0x00F800)|((value << 19) & 0xF80000));
}
uint32_t GetMaskBit(uint8_t* data, int x, int y, int w, int h)
{
uint32_t mask_data_rowsize = (((w+31)>>5) * 4);
return ((~(data[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) & 1) * 0xFFFFFFFF);
}
uint32_t GetColorMonochrome(uint8_t* xordata, uint8_t* anddata, int x, int y, int w, int h, uint32_t* pal)
{
uint32_t mask_data_rowsize = (((w+31)>>5) * 4);
uint32_t xor_bit = (((xordata[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) << 1) & 2);
uint32_t and_bit = (((anddata[(mask_data_rowsize * ((h-1)-y)) + (x >> 3)] >> (0x07 - (x & 0x07))) ) & 1);
uint32_t value = (xor_bit | and_bit);
return pal[value];
}
BITMAP* CreateBmp32bppFromIconResData(void* data, int size, int depth, int w, int h, int colors)
{
char* pngheader = "\211PNG\r\n\032\n";
char* cpd = (char*)data;
bool is_png = ((cpd[0]==pngheader[0])
&& (cpd[1]==pngheader[1])
&& (cpd[2]==pngheader[2])
&& (cpd[3]==pngheader[3])
&& (cpd[4]==pngheader[4])
&& (cpd[5]==pngheader[5])
&& (cpd[6]==pngheader[6])
&& (cpd[7]==pngheader[7]));
if(is_png)
{
//###########################################################
//# PSEUDO-CODE: Somehow convert the PNG file into a bitmap.
BITMAP* result = ConvertPngFileToBmp32bpp(data, size);
return result;
}
else
{
uint32_t ignore_size = ((BITMAPINFOHEADER*)(data))->biSize;
BITMAP* bmp = create_bitmap_ex(32,w,h);
uint32_t pixel_count = (w * h);
uint32_t color_data_size = ((((((w * depth)+7) >> 3) +3) & ~3) * h);
switch(depth)
{
default: return bmp; break;
case 32:
{
uint32_t* src = (uint32_t*)(((uint8_t*)data) + ignore_size);
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,src[0]);
src++;
}
//There should never be any padding to jump over here.
}
return bmp;
}
break;
case 24:
{
uint32_t* src = (uint32_t*)(((uint8_t*)data) + ignore_size);
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,((src[0] & 0x00FFFFFF) | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
src++;
src = (uint32_t*)(((uint8_t*)src)-1); //go back a byte due to packing
padding_checker += 3;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src = (uint32_t*)(((uint8_t*)src)+1);
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 16:
{
//Note: there might be a color table present! ignore it.
uint16_t* src = (uint16_t*)(((uint8_t*)data) + ignore_size + (colors << 2));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,Convert16BitToARGB(src[0]) & GetMaskBit(bitmask, xx, yy, w, h));
src++;
padding_checker += 2;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src = (uint16_t*)(((uint8_t*)src)+1);
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 8:
{
if(colors > 256) colors = 256; //Color Count must be restricted to 256 entries at the most.
if(colors <= 0) colors = 256; //Color Count might be reported as zero. This means 256.
uint8_t* src = (((uint8_t*)data) + ignore_size + (colors << 2));
uint32_t* pal = ((uint32_t*)(((uint8_t*)data) + ignore_size));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
uint8_t color = src[0];
if(color < colors){
_putpixel32(bmp,xx,yy,(pal[color] | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
}else{
_putpixel32(bmp,xx,yy,0x00FF00FF);
}
src++;
padding_checker++;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 4:
{
if(colors > 16) colors = 16; //Color Count must be restricted to 16 entries at the most.
if(colors <= 0) colors = 16; //Color Count might be reported as zero. This means 16.
uint8_t* src = (((uint8_t*)data) + ignore_size + (colors << 2));
uint32_t* pal = ((uint32_t*)(((uint8_t*)data) + ignore_size));
uint8_t* bitmask = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
int padding_checker = 0;
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
uint8_t color = src[0];
if(xx & 1) color = ( color & 0x0F);
else color = ((color >> 4) & 0x0F);
if(color < colors){
_putpixel32(bmp,xx,yy,(pal[color] | 0xFF000000) & GetMaskBit(bitmask, xx, yy, w, h));
}else{
_putpixel32(bmp,xx,yy,0x00FF00FF);
}
if(xx & 1)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
//if the pointer hasn't incremented to the next byte yet, do so.
if(w & 1) //odd width
{
src++;
padding_checker++;
padding_checker &= 3;
}
//This loop jumps over any padding bytes.
while(padding_checker)
{
src++;
padding_checker++;
padding_checker &= 3;
}
}
return bmp;
}
break;
case 1:
{
if(colors > 2) colors = 2; //Color Count must be restricted to 2 entries at the most.
if(colors <= 0) colors = 2; //Color Count might be reported as zero. This means 2.
uint32_t* pal = (uint32_t*)(((uint8_t*)data) + ignore_size);
uint8_t* bitmaskXOR = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2));
uint8_t* bitmaskAND = (uint8_t*)(((uint8_t*)data) + ignore_size + (colors << 2) + color_data_size);
uint32_t ret_colors[4] = {pal[0]|0xFF000000, 0x00FF00FF, pal[1]|0xFF000000, 0x0000FF00};
for(int yy = h-1; yy >= 0; --yy){
for(int xx = 0; xx < w; ++xx){
_putpixel32(bmp,xx,yy,GetColorMonochrome(bitmaskXOR, bitmaskAND, xx, yy, w, h, ret_colors));
}
}
return bmp;
}
break;
}
return bmp;
}
}
vector< BITMAP* > ResourceToBitmapVector(HMODULE hm, HRSRC hr, bool is_group_icon)
{
vector< BITMAP* > results;
if(is_group_icon)
{
HGLOBAL hg = LoadResource(hm,hr);
GRPICONDIR* gd = (GRPICONDIR*)LockResource(hg);
if(gd->idType == 1)
{
for(int i = 0; i < gd->idCount; ++i)
{
//WARNING: The GRPICONDIRENTRY's data might be wrong!
GRPICONDIRENTRY* ie = (GRPICONDIRENTRY*)&(gd->idEntries[i]);
HRSRC ihr = FindResource(hm,MAKEINTRESOURCE(ie->nID),RT_ICON);
if(ihr != NULL)
{
HGLOBAL ihg = LoadResource(hm,ihr);
void* data = (void*)LockResource(ihg);
DWORD size = SizeofResource(hm,ihr);
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
}
}
}
else
{
HGLOBAL ihg = LoadResource(hm,hr);
void* data = (void*)LockResource(ihg);
DWORD size = SizeofResource(hm,hr);
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
return results;
}
vector< BITMAP* > IconFileToBitmapVector(void* icon_data, uint32_t icon_size)
{
vector< BITMAP* > results;
ICONDIR* gd = (ICONDIR*)icon_data;
if(gd->idType == 1)
{
for(int i = 0; i < gd->idCount; ++i)
{
//WARNING: The ICONDIRENTRY's data might be wrong!
DWORD offset = gd->idEntries[i].dwImageOffset;
DWORD size = gd->idEntries[i].dwBytesInRes;
void* data = (void*)(((uint8_t*)icon_data) + ((uint32_t)offset));
uint32_t b = ((BITMAPINFOHEADER*)(data))->biBitCount;
uint32_t w = ((BITMAPINFOHEADER*)(data))->biWidth;
uint32_t h = (((BITMAPINFOHEADER*)(data))->biHeight >> 1); //icons have doubled height value.
uint32_t c = ((BITMAPINFOHEADER*)(data))->biClrUsed;
results.push_back(CreateBmp32bppFromIconResData(data, size, b, w, h, c));
}
}
return results;
}
vector< BITMAP* > UnearthIconResource(string& file, bool self_refrence, bool res_index, int index)
{
#define LOAD_IGNORE_CODE_AUTHZ_LEVEL 0x00000010
//prevents a negative indexing error
// (the boolean res_index handles whether it's icon index VS resource ID)
index = abs(index);
vector< BITMAP* > results; //array of results to return (pointers to 32bpp images)
//extract and 'demangle' the file extension by convertng to lowercase.
string ext = get_file_extension(file.c_str());
for(int i = 0; i < ext.size(); ++i) ext[i] = tolower(ext[i]);
bool is_icl = false;
if((ext == "exe") || (ext == "dll") || (ext == "scr") || (is_icl = (ext == "icl")))
{
// Portable Executable Resource (works for both DLL and EXE)
// Also works for any 32bit Icon Library (Microangelo Studio?)
HMODULE hm = LoadLibraryEx(file.c_str(), NULL,
(DONT_RESOLVE_DLL_REFERENCES | LOAD_IGNORE_CODE_AUTHZ_LEVEL | LOAD_LIBRARY_AS_DATAFILE));
if(hm != NULL)
{
HRSRC hr;
if(!self_refrence)
{
if(res_index)
{
//The icon we want is at the resource ID (==index)
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(index),RT_GROUP_ICON);
if(hr == NULL)
{
hr = FindResource(hm,MAKEINTRESOURCE(index),RT_ICON);
is_single_icon = (hr != NULL);
}
if(hr != NULL)
{
results = ResourceToBitmapVector(hm, hr, !is_single_icon);
}
}
else
{
//The icon we want is the (index)'th icon in the file
//We must preform a manual search for the resource ID!
//WARNING: Using EnumResourceNames() *DOES NOT WORK PROPERLY* for this.
for(int nicon = 0, i = 0; i < 0x8000; ++i)
{
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(i),RT_GROUP_ICON);
if(hr != NULL)
{
if(nicon == index)
{
results = ResourceToBitmapVector(hm, hr, true);
break;
}
nicon++;
}
}
}
}
else
{
//The icon we want is the "first" icon in the file.
//Happens when location is a %1.
//We must preform a manual search for the resource ID!
//WARNING: Using EnumResourceNames() *DOES NOT WORK PROPERLY* for this.
for(int i = 0; i < 0x8000; ++i)
{
bool is_single_icon = false;
hr = FindResource(hm,MAKEINTRESOURCE(i),RT_GROUP_ICON);
if(hr != NULL)
{
results = ResourceToBitmapVector(hm, hr, true);
break;
}
}
}
FreeLibrary(hm);
}
else /*if(is_icl)
{//OH NOES. We have to load a *16bit* .icl file!
//not supported yet. sorry. left as another excecise to the programmer.
}*/
}
else if(ext == "ico")
{
//Single Icon File
//###################################################
//# PSEUDO-CODE: Do the file loading yourself ;)
void* data_pointer = NULL;
uint32_t data_size = 0;
if(data_pointer = MyLoadFile(file.c_str(), &data_size))
{
if(data_size)
{
results = IconFileToBitmapVector((void*)data_pointer, data_size);
}
}
MyCloseFile(data_pointer);
}
return results;
}
I think that almost covers it all...
One last thing I should mention: Be sure to ignore the size and bit depth information coming from the icon directory entries. They can often be wrong. I've seen a few 256-color images reported as 24bit, causing data corruption inside the image loader.
SHGetImageList
,HIMAGELIST
, etc. because they aren't declared inshellapi.h
at all. I'm not using MFC if that is some sort of MFC nonsense. Is it an MFC interface? I couldn't find any conclusive evidence online. SHGetImageList appears to be listed in MSDN as a shell function. I've never used MFC before so I wouldn't know. Also,commoncontrols.h
apparently doesn't exist in my install. @Corey: How? A quick google suggests that ExtractIconEx won't get me what I want either (only 16x and 32x icons). – Simion32SHGFI_ICON
it's too late. An icon represents only a single size/image. You want the icon directory. For that, ask for the icon location, then go parse the icon directory from that location. – Raymond Chen