Manual decoding of ICO file format – small c++ cross-platform decoder lib

This post is related to one simple task – decode ICO file format from C++ manually (I needed this for one of my projects where i wanted to display fav icons related to web sites and decoder has to be part of my cross-platform framework). As a result you can download small c++ code below (ico.cpp). The description of ICO format you can find here – http://www.daubnet.com/en/file-format-ico. Image data is stored uncompressed so we don’t need to implement some smart decompression algorithms – just read some headers, get data and apply bit mask to fill alpha channel.

icons

As solution i wanted a function like this:  

bool IcoDecoder::decode(unsigned charbuffer,///< input buffer data

    int size,///< size of buffer

    unsigned intwidth,///< output – width

    unsigned intheight,///< output – height

    std::vector<unsigned char>&image///< output – image data

) 

and this function should extract the largest possible image from multiresolution icon.

We need some header structs (i found them somewhere over internet). I skip them – you can find everything in source code below. As first step we check is it really ICO header and get number of images inside:

LPICONDIR icoDir (LPICONDIR)buffer;

int iconsCount icoDir->idCount;

if (icoDir->idReserved!=0return false;

if (icoDir->idType!=1return false;

if (iconsCount==0return false;

if (iconsCount>20return false;

 

Get offset of the largest icon within file:

unsigned charcursor buffer;

cursor += 6;

ICONDIRENTRYdirEntry=(ICONDIRENTRY*)(cursor);

int maxSize=0;

int offset=0;

for(int i=0i<iconsCounti++)

{

    int w=dirEntry->bWidth;

    int h=dirEntry->bHeight;

    int colorCount=dirEntry->bColorCount;

    int bitCount=dirEntry->wBitCount;

    if(w*h>maxSize)// we choose icon with max resolution

    {

         width=w;

         height=h;

         offset=dirEntry->dwImageOffset;

         maxSize = w * h;

    }

    dirEntry++;

}

if (offset==0return false;

 

Get image bit count and bit mask existance flag

cursor=buffer;

cursor+=offset;

ICONIMAGEicon (ICONIMAGE*)(cursor);

int realBitsCount=(int)icon->icHeader.biBitCount;

bool hasAndMask=(height!=icon->icHeader.biHeight);

cursor+=40;

int numBytes=width*height*4;

 

In case of 32 bit data (realBitsCount == 32) we just copy data to output with optional vertical flip:

int shift;

int shift2;

for (int x=0x<widthx++)

for (int y=0y<heighty++)

{

    shift=4*(x+y*width);

    shift2=4*(x+(heighty1)*width);

    image[shift]=cursor[shift2+2];

    image[shift+1]=cursor[shift2+1];

    image[shift+2]=cursor[shift2];

    image[shift+3]=cursor[shift2+3];

}

 

If color count is limited to 256 or 16 we take colors from color table. Here is the part for 256 color:

unsigned charcolors=(unsigned char*)cursor;

cursor+=256*4;

int shift;

int shift2;

int index;

for (int x=0x<widthx++)

    for (int y=0y<heighty++)

    {

         shift=4*(x+y*width);

         shift2=(x+(heighty1)*width);

         index=4*cursor[shift2];

         image[shift]=colors[index+2];

         image[shift+1]=colors[index+1];

         image[shift+2]=colors[index];

         image[shift+3]=255;

    }

 

And final step is optional bit mask (this is not applicable only to icons with 32bit color).

And that all. See complete file to enjoy how compact the decoding is. Feel free to use it. I have tested this on a lot of icons but i cannot guarantee anything. Also i skipped some range checks and stuff to make module more robust – but you are free to modify it as you like.

UPDATE 14.08.2014: Code was updated to rev2  to support 1-bit encoding. Also contains fix for bit mask application.

SOURCE CODE: download ico.cpp (last version – rev2 15.08.2014)

PS. Please post in comments info about any icons which fail to decode using code above.