« Posts tagged icon

Merge two icons

The previous posts about ico formats and files have all been moving towards the end goal, merging two icons into a new single icon. This is useful for things like generating icon overlays and I personally used this method while building an icon overlay shell extension to combine multiple icon overlays into a single icon overlay. This is important because only windows only allows a single icon overlay extension to be “active” per file. So if you need to allow multiple overlays you need to merge the icons yourself and add additional logic to the shell extensions to get the correct result.

Here is a simple command line application that shows how to merge two icons. It takes three parameters,
expected usage:
icoMerge.exe [iconPath1] [iconPath2] [destinationPath]

Full source code is included. It is a combination of the source provided in the preivous ico related posts.
<download>

All Code is freely provided, no guarantees or warranties about its quality, use at your own risk.

ExtractIcon for larger Icons

Windows provides the function ExtractIconEx to extract icons from a exe or dll by index. It allows you to get a HICON handle to a “small” and a “large” version of the icon. Trouble is.. what are the actual dimensions of the small or large icons. The answer is to use GetSystemMetrics with the flags SM_CXICON, SM_CYICON, SM_CXSMICON, and SM_CYSMICON.

This is great, but unfortunately this function has not been updated for Vista or Windows 7, which use much larger icons and if you want a particular size ExtractIconEx will not work for you. So instead of relying on this function you can write some code that enumerates the resources in the target file yourself. The basic idea is to enumerate the resources and find the icons, then read the icon information to find the one that matches the desired size and format (bit count).

The example below implements this idea, it will find a 32bit icon of the requested size if it exists.

C++
<code>
 
HICON WINAPI ExtractIconAtSize(TCHAR* filePath, UINT nIconIndex, int sz)
{
  //Load the library as a data file
    HMODULE hLib = LoadLibraryEx(filePath, NULL, LOAD_LIBRARY_AS_DATAFILE);
    if( !hLib )
        return NULL;
 
    MyEnumData data;
    data.nIconIndex = nIconIndex;
    data.hIcon = NULL;
  data.sz = sz;
 
  //currently only interested in 32bit icons
  data.bitCount = 32;
 
  //find the resource item
    EnumResourceNames(hLib, RT_GROUP_ICON, (ENUMRESNAMEPROC)&FindGroupIconProc, (LONG)&data);
 
    FreeLibrary(hLib);
    return data.hIcon;
} 
 
typedef struct
{
  UINT      Width, Height, Colors; // Width, Height and bpp
  LPBYTE      lpBits;                // ptr to DIB bits
  DWORD      dwNumBytes;            // how many bytes?
  LPBITMAPINFO  lpbi;                  // ptr to header
  LPBYTE      lpXOR;                 // ptr to XOR image bits
  LPBYTE      lpAND;                 // ptr to AND image bits
} ICONIMAGE, *LPICONIMAGE;
 
#pragma pack( push )
#pragma pack( 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;         // how many bytes in this resource?
   WORD   nID;                  // the ID
} GRPICONDIRENTRY, *LPGRPICONDIRENTRY;
#pragma pack( pop )
 
#pragma pack( push )
#pragma pack( 2 )
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 )
 
struct MyEnumData
{
    UINT nIconIndex;
    HICON hIcon;
  int sz;
  int bitCount;
};
 
BOOL CALLBACK FindGroupIconProc(HANDLE hModule, LPCTSTR lpszType, LPTSTR lpszName, LONG lParam)
{
    MyEnumData *data = (MyEnumData*) lParam;
 
  //check if this is the index we want
    if( data->nIconIndex == 0 )
    {
        HRSRC hRsrc = FindResource((HMODULE)hModule, lpszName, lpszType);
        HGLOBAL hGroup = LoadResource((HMODULE)hModule, hRsrc);        
 
    GRPICONDIR * lpGrpIconDir = (LPGRPICONDIR)LockResource( hGroup ); 
        for( int i=0; i < lpGrpIconDir->idCount; ++i )
        {              
      //get icon header info
            GRPICONDIRENTRY * e = &lpGrpIconDir->idEntries[i];
            hRsrc = FindResource((HMODULE) hModule, MAKEINTRESOURCE( e->nID ), RT_ICON );
            HGLOBAL hGlobal = LoadResource( (HMODULE)hModule, hRsrc );
            ICONIMAGE *lpIconImage = (LPICONIMAGE)LockResource( hGlobal );
 
      //check the desired size and bit count
      if(e->bWidth == data->sz && e->wBitCount == data->bitCount)
      {
        data->hIcon = CreateIconFromResourceEx(
                        (PBYTE)lpIconImage,
                        e->dwBytesInRes,
                        TRUE,
                        0x00030000,//DWORD dwVersion,
                        e->bWidth,
                        e->bHeight,
                        0 );
        return FALSE;
      }
        };     
 
    return FALSE;
    }
 
    --(data->nIconIndex);
    return TRUE;
}
 
</code>

Read .ICO format.

Based on the last post below is a function for reading a .ICO file. The code only reads in the 32bit bitmaps in the icon skipping over the others. The function returns an array of HBITMAP’s of length bmpCount.

All Code is freely provided, no guarantees or warranties about its quality, use at your own risk.

C++
<code>
 
HBITMAP* ReadICO(TCHAR* srcPath, int* bmpCount)
{
 
  *bmpCount = 0;
 
  FILE* fp = _tfopen(srcPath,_T("rb"));
  if(fp == NULL)
    return false;
 
  ICONDIR icoDir;
 
  fread(&icoDir.idReserved,sizeof(WORD), 1, fp);
  fread(&icoDir.idType,sizeof(WORD), 1, fp);
  fread(&icoDir.idCount,sizeof(WORD), 1, fp);
 
  ICONDIRENTRY* entries = (ICONDIRENTRY*)malloc(sizeof(ICONDIRENTRY) * icoDir.idCount);
  memset(entries, 0, sizeof(ICONDIRENTRY) * icoDir.idCount);
 
  for(int i=0;i<icoDir.idCount;i++)
  {
    fread(&entries[i].bWidth,sizeof(BYTE), 1, fp);
    fread(&entries[i].bHeight,sizeof(BYTE), 1, fp);
    fread(&entries[i].bColorCount,sizeof(BYTE), 1, fp);
    fread(&entries[i].bReserved,sizeof(BYTE), 1, fp);
 
    fread(&entries[i].wPlanes,sizeof(WORD), 1, fp);
    fread(&entries[i].wBitCount,sizeof(WORD), 1, fp);
    fread(&entries[i].dwBytesInRes,sizeof(DWORD), 1, fp);
    fread(&entries[i].dwImageOffset,sizeof(DWORD), 1, fp);
 
  }
 
  HBITMAP* bmpResult = (HBITMAP*)malloc(sizeof(HBITMAP) * icoDir.idCount);
  memset(bmpResult, 0, sizeof(HBITMAP) * icoDir.idCount);
 
  HDC hdc = CreateCompatibleDC(NULL);
  for(int i=0;i<icoDir.idCount;i++)
  {
    if(entries[i].wBitCount != 32)
    {
      //only support 32bit
      continue;
    }
 
    fseek(fp, entries[i].dwImageOffset, SEEK_SET);
 
    ICONIMAGE ico;
    ico.lpBits = (LPBYTE)malloc(entries[i].dwBytesInRes);
    fread(ico.lpBits, 1, entries[i].dwBytesInRes, fp);
 
    ico.lpbi = (LPBITMAPINFO) ico.lpBits;    
 
    int height = ico.lpbi->bmiHeader.biHeight / 2;
    BITMAPINFO bmi;
    ::ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
    bmi.bmiHeader.biWidth = ico.lpbi->bmiHeader.biWidth;
    bmi.bmiHeader.biHeight = height;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = ico.lpbi->bmiHeader.biBitCount;
    bmi.bmiHeader.biSizeImage = 0;
    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biClrUsed = 0;
    bmi.bmiHeader.biClrImportant = 0;
 
 
    HBITMAP hBitmap = ::CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
    if(((int)hBitmap) != ERROR_INVALID_PARAMETER && ((int)hBitmap) != ERROR_NOT_ENOUGH_MEMORY)
    {
      bmpResult[*bmpCount] = hBitmap;
      *bmpCount += 1;
 
      ::SelectObject(hdc, hBitmap);
      ::SetDIBitsToDevice(hdc, 0, 0, bmi.bmiHeader.biWidth, height,
                0, 0, 0, height, ico.lpBits + sizeof(BITMAPINFO), &bmi, DIB_RGB_COLORS);
    }
 
  }
  DeleteDC(hdc);
 
  fclose(fp);
 
  return bmpResult;
}
 
</code>

Convert HBITMAP to .ICO file

There is surprisingly little documentation or example code about the .ICO format. There are easy to use windows API functions to load icons from files and resources but only with limited options. Recently I have had to work with this format in detail and here I will give a code example of how to save a HBITMAP as an .ico file. I will only consider 32bit bitmaps\icons for now.

There is a detailed article on MSDN that gives a lot of background information, the only problem it was written in 1995, and has no information about how to deal with 32bit icons. But it still gives important details about the internal structure of the format and is worth looking at. http://msdn.microsoft.com/en-us/library/ms997538.aspx

The key difference with 32bit icons is the XOR map is not added to the entry. Also the color table is not 3 components(RGB) it is 4 components to include the 8bit alpha channel.

All Code is freely provided, no guarantees or warranties about its quality, use at your own risk. In my next post I will show how to read the .ico format.

 

First we need to define some structures, these are the same as those used in the MSDN article

C++
// These next two structs represent how the icon information is stored
// in an ICO file.
typedef struct
{
  BYTE  bWidth;               // Width of the image
  BYTE  bHeight;              // Height of the image (times 2)
  BYTE  bColorCount;          // Number of colors in image (0 if >=8bpp)
  BYTE  bReserved;            // Reserved
  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
  WORD      idType;       // resource type (1 for icons)
  WORD      idCount;      // how many images?
  ICONDIRENTRY  idEntries[1]; // the entries for each image
} ICONDIR, *LPICONDIR;
 
typedef struct
{
  UINT      Width, Height, Colors; // Width, Height and bpp
  LPBYTE      lpBits;                // ptr to DIB bits
  DWORD      dwNumBytes;            // how many bytes?
  LPBITMAPINFO  lpbi;                  // ptr to header
  LPBYTE      lpXOR;                 // ptr to XOR image bits
  LPBYTE      lpAND;                 // ptr to AND image bits
} ICONIMAGE, *LPICONIMAGE;

Next we need a function that converts the HBITMAP into a ICONIMAGE structure, making sure the DIB is 32bit

C++
 
// How wide, in bytes, would this many bits be, DWORD aligned?
#define WIDTHBYTES(bits)      ((((bits) + 31)>>5)<<2)
 
ICONIMAGE CreateIconImageFromBitmap(HBITMAP bmp)
{
 
  BITMAPINFO info;
  ZeroMemory(&info,sizeof(BITMAPINFO));  
  info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
 
  //Get the size of the bitmap
  HDC hdc = GetDC(NULL);  
  GetDIBits(hdc, bmp, 0, 0, NULL, &info, DIB_RGB_COLORS);
 
  //allocate space for pixels
  DWORD sz = info.bmiHeader.biWidth * info.bmiHeader.biHeight * 4;
  BYTE* pPixels = (BYTE*)malloc(sizeof(BYTE)*sz);
  memset(pPixels, 0, sz);  
 
  //get pixel data in 32bit format
  info.bmiHeader.biSize = sizeof(info.bmiHeader);
  info.bmiHeader.biBitCount = 32;
  info.bmiHeader.biCompression = BI_RGB;
  info.bmiHeader.biHeight = (info.bmiHeader.biHeight < 0) ? (-info.bmiHeader.biHeight) : (info.bmiHeader.biHeight);  // correct the bottom-up ordering of lines
 
  GetDIBits(hdc, bmp, 0, info.bmiHeader.biHeight, (LPVOID)pPixels, &info, DIB_RGB_COLORS);  
 
  LONG width = info.bmiHeader.biWidth;
  LONG height = info.bmiHeader.biHeight;
 
  LONG imgSz = (info.bmiHeader.biWidth*info.bmiHeader.biHeight*(info.bmiHeader.biBitCount/8));
  ICONIMAGE ico;
  ico.Colors = info.bmiHeader.biBitCount;
  ico.Width = width;
  ico.Height = height;
 
  DWORD xorSz = 0; //not used in 32bit icons
  DWORD andSz = ico.Height * WIDTHBYTES( ico.Width );
  ico.dwNumBytes = sizeof( BITMAPINFOHEADER )
          + imgSz
          + xorSz  // XOR mask
                    + andSz; // AND mask
 
  ico.lpBits = (LPBYTE)malloc(ico.dwNumBytes);
  memset(ico.lpBits, 0, ico.dwNumBytes);
 
  // Copy the bits
    memcpy(ico.lpBits, &info.bmiHeader, sizeof(BITMAPINFOHEADER));
  memcpy(ico.lpBits + sizeof(BITMAPINFOHEADER), pPixels, imgSz);
 
    // Adjust internal pointers/variables for new image
    ico.lpbi = (LPBITMAPINFO)(ico.lpBits);    
  ico.lpXOR = ico.lpBits + sizeof(BITMAPINFOHEADER) + imgSz;
    ico.lpAND = ico.lpXOR + xorSz;
 
  if(xorSz > 0)
    memcpy( ico.lpXOR, ico.lpBits + sizeof(BITMAPINFOHEADER), xorSz );
 
  memset( ico.lpAND, 0x0, andSz );
 
  //fix bmp header for icon height, x2 required
  ico.lpbi->bmiHeader.biHeight *= 2;   
 
  free(pPixels);
 
  return ico;
}

 

And finally the function that takes a HBITMAP and a destination filepath for the target .ico file.
This function takes an array of HBITMAP’s of bmpsSz length and writes them all to a single .ico file. (there is no validation of the file path, so make sure it can be written to first and add a .ico extension)

 

C++
void ConvertBitmapToICO(HBITMAP* bmps, int bmpsSz, TCHAR* destPath)
{
 
  int entryCount = bmpsSz;
 
  //create icons from bitmaps
  ICONIMAGE* icons = (ICONIMAGE*)malloc(sizeof(ICONIMAGE) * entryCount);
  memset(icons, 0, sizeof(ICONIMAGE) * entryCount);
  for(int i=0;i<bmpsSz;i++)
  {    
    icons[i] = CreateIconImageFromBitmap(bmps[i]);
  }
 
  ICONDIR icoDir;
  icoDir.idReserved = 0;
  icoDir.idType = 1;
  icoDir.idCount = entryCount;
 
  //header
  FILE* fp = _tfopen(destPath,_T("wb"));
  fwrite(&icoDir.idReserved, sizeof(WORD), 1, fp);
  fwrite(&icoDir.idType, sizeof(WORD), 1, fp);
  fwrite(&icoDir.idCount, sizeof(WORD), 1, fp);
 
  long entriesPos = ftell(fp);
 
  //entries
  int entrySz = 16;
  ICONDIRENTRY* entries = (ICONDIRENTRY*)malloc(sizeof(ICONDIRENTRY) * entryCount);
  memset(entries, 0, sizeof(ICONDIRENTRY) * entryCount);
 
  for(int i=0;i<entrySz * entryCount;i++)
    fwrite(&entrySz,sizeof(BYTE),1, fp);
 
  //fseek(fp,entriesPos + entrySz * entryCount,SEEK_SET);
  for(int i=0;i<entryCount;i++)
  {    
    //seek to imge offset  
    entries[i].dwImageOffset = ftell(fp);
    entries[i].bWidth = icons[i].Width;
    entries[i].bHeight = icons[i].Height;
    entries[i].bColorCount = icons[i].lpbi->bmiHeader.biClrUsed;
    entries[i].wBitCount = icons[i].Colors;
    entries[i].wPlanes = 1;
 
    icons[i].lpbi->bmiHeader.biSizeImage = 0;
    //write icon image data    
    fwrite( icons[i].lpBits, icons[i].dwNumBytes, 1, fp);
    //
 
    entries[i].dwBytesInRes = ftell(fp) - entries[i].dwImageOffset;
    //
  }
 
  //fix entries
  fseek(fp, entriesPos, SEEK_SET);
  for(int i=0;i<entryCount;i++)
  {
    fwrite(&entries[i].bWidth,sizeof(BYTE), 1, fp);
    fwrite(&entries[i].bHeight,sizeof(BYTE), 1, fp);
    fwrite(&entries[i].bColorCount,sizeof(BYTE), 1, fp);
    fwrite(&entries[i].bReserved,sizeof(BYTE), 1, fp);
 
    fwrite(&entries[i].wPlanes,sizeof(WORD), 1, fp);
    fwrite(&entries[i].wBitCount,sizeof(WORD), 1, fp);
    fwrite(&entries[i].dwBytesInRes,sizeof(DWORD), 1, fp);
    fwrite(&entries[i].dwImageOffset,sizeof(DWORD), 1, fp);
  }
 
  fclose(fp);
 
 
  free(entries);
  for(int i=0;i<bmpsSz;i++)
  {
    free(icons[i].lpBits);
  }
  free(icons);
 
}

All Code is freely provided, no guarantees or warranties about its quality, use at your own risk.