« Posts tagged bitmap

WPF C#, CalculateAverageColor for bitmap

Here is a simple method to calculate the average color in a bitmap (WPF C#). The result is not always the best, it can tend to be muddy or more like a grey-scale effect. An improved method is to find the dominant color, though more computationally expensive – I will post an example soon.

public static Color CalculateAverageColor(BitmapSource source)
            if (source.Format.BitsPerPixel != 32)
                throw new ApplicationException("expected 32bit image");
            Color cl;
            System.Windows.Size sz = new System.Windows.Size(source.PixelWidth, source.PixelHeight);
            //read bitmap 
            int pixelsSz = (int)sz.Width * (int)sz.Height * (source.Format.BitsPerPixel / 8);
            int stride = ((int)sz.Width * source.Format.BitsPerPixel + 7) / 8;
            byte[] pixels = new byte[pixelsSz];
            source.CopyPixels(pixels, stride, 0);
            //find the average color for the image
            const int alphaThershold = 10;
            UInt64 r, g, b, a; r = g = b = a = 0;
            UInt64 pixelCount = 0;
            for (int y = 0; y < sz.Height; y++)
                for (int x = 0; x < sz.Width; x++)
                    int index = (int)((y * sz.Width) + x) * 4;
                    if (pixels[index + 3] <= alphaThershold) //ignore transparent
                    a += pixels[index + 3];
                    r += pixels[index + 2];
                    g += pixels[index + 1];
                    b += pixels[index];
            //average color result
            cl = Color.FromArgb((int)(a / pixelCount),
                                (int)(r / pixelCount),
                                (int)(g / pixelCount),
                                (int)(b / pixelCount));
            return cl;           

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.

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);
    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
#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
#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
#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(
                        0x00030000,//DWORD dwVersion,
                        0 );
        return FALSE;
    return FALSE;
    return TRUE;

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.

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
    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;
    ::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);
  return bmpResult;

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

// 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
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
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

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

// How wide, in bytes, would this many bits be, DWORD aligned?
#define WIDTHBYTES(bits)      ((((bits) + 31)>>5)<<2)
ICONIMAGE CreateIconImageFromBitmap(HBITMAP bmp)
  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));
  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;   
  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)


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;
  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);
  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);
  for(int i=0;i<bmpsSz;i++)

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