// Created by Joshua Flanagan // http://flimflan.com/blog // May 2004 // // You may freely use this code as you wish, I only ask that you retain my name in the source code // Modified by Pavel Janda // - added support for 32bpp bitmaps // November 2006 using System; using System.IO; using System.Collections; using System.Drawing; namespace FlimFlan.IconEncoder { /// /// Provides methods for converting between the bitmap and icon formats /// public class Converter { private Converter(){} public static Icon BitmapToIcon(Bitmap b) { IconHolder ico = BitmapToIconHolder(b); Icon newIcon; using (BinaryWriter bw = new BinaryWriter(new MemoryStream())) { ico.Save(bw); bw.BaseStream.Position = 0; newIcon = new Icon(bw.BaseStream); } return newIcon; } public static IconHolder BitmapToIconHolder(Bitmap b) { BitmapHolder bmp = new BitmapHolder();; using (MemoryStream stream = new MemoryStream()) { b.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); stream.Position = 0; bmp.Open(stream); } return BitmapToIconHolder(bmp); } public static IconHolder BitmapToIconHolder(BitmapHolder bmp) { bool mapColors = (bmp.info.infoHeader.biBitCount <= 24); int maximumColors = 1 << bmp.info.infoHeader.biBitCount; //Hashtable uniqueColors = new Hashtable(maximumColors); // actual colors is probably nowhere near maximum, so dont try to initialize the hashtable Hashtable uniqueColors = new Hashtable(); int sourcePosition = 0; int numPixels = bmp.info.infoHeader.biHeight * bmp.info.infoHeader.biWidth; byte[] indexedImage = new byte[numPixels]; byte colorIndex; if (mapColors) { for (int i=0; i < indexedImage.Length; i++) { //TODO: currently assumes source bitmap is 24bit color //read 3 bytes, convert to color byte[] pixel = new byte[3]; Array.Copy(bmp.imageData, sourcePosition, pixel, 0, 3); sourcePosition += 3; RGBQUAD color = new RGBQUAD(pixel); if (uniqueColors.Contains(color)) { colorIndex = Convert.ToByte(uniqueColors[color]); } else { if (uniqueColors.Count > byte.MaxValue) { throw new NotSupportedException(String.Format("The source image contains more than {0} colors.", byte.MaxValue)); } colorIndex = Convert.ToByte(uniqueColors.Count); uniqueColors.Add(color, colorIndex); } // store pixel as an index into the color table indexedImage[i] = colorIndex; } } else { // added by Pavel Janda on 14/11/2006 if (bmp.info.infoHeader.biBitCount == 32) { for (int i=0; i < indexedImage.Length; i++) { //TODO: currently assumes source bitmap is 32bit color with alpha set to zero //ignore first byte, read another 3 bytes, convert to color byte[] pixel = new byte[4]; Array.Copy(bmp.imageData, sourcePosition, pixel, 0, 4); sourcePosition += 4; RGBQUAD color = new RGBQUAD(pixel[0], pixel[1], pixel[2], pixel[3]); if (uniqueColors.Contains(color)) { colorIndex = Convert.ToByte(uniqueColors[color]); } else { if (uniqueColors.Count > byte.MaxValue) { throw new NotSupportedException(String.Format("The source image contains more than {0} colors.", byte.MaxValue)); } colorIndex = Convert.ToByte(uniqueColors.Count); uniqueColors.Add(color, colorIndex); } // store pixel as an index into the color table indexedImage[i] = colorIndex; } // end of addition } else { //TODO: implement converting an indexed bitmap throw new NotImplementedException("Unable to convert indexed bitmaps."); } } ushort bitCount = getBitCount(uniqueColors.Count); // *** Build Icon *** IconHolder ico = new IconHolder(); ico.iconDirectory.Entries = new ICONDIRENTRY[1]; //TODO: is it really safe to assume the bitmap width/height are bytes? ico.iconDirectory.Entries[0].Width = (byte) bmp.info.infoHeader.biWidth; ico.iconDirectory.Entries[0].Height = (byte) bmp.info.infoHeader.biHeight; ico.iconDirectory.Entries[0].BitCount = bitCount; // maybe 0? ico.iconDirectory.Entries[0].ColorCount = (uniqueColors.Count > byte.MaxValue) ? (byte)0 : (byte)uniqueColors.Count; //HACK: safe to assume that the first imageoffset is always 22 ico.iconDirectory.Entries[0].ImageOffset = 22; ico.iconDirectory.Entries[0].Planes = 0; ico.iconImages[0].Header.biBitCount = bitCount; ico.iconImages[0].Header.biWidth = ico.iconDirectory.Entries[0].Width; // height is doubled in header, to account for XOR and AND ico.iconImages[0].Header.biHeight = ico.iconDirectory.Entries[0].Height << 1; ico.iconImages[0].XOR = new byte[ico.iconImages[0].numBytesInXor()]; ico.iconImages[0].AND = new byte[ico.iconImages[0].numBytesInAnd()]; ico.iconImages[0].Header.biSize = 40; // always ico.iconImages[0].Header.biSizeImage = (uint)ico.iconImages[0].XOR.Length; ico.iconImages[0].Header.biPlanes = 1; ico.iconImages[0].Colors = buildColorTable(uniqueColors, bitCount); //BytesInRes = biSize + colors * 4 + XOR + AND ico.iconDirectory.Entries[0].BytesInRes = (uint)(ico.iconImages[0].Header.biSize + (ico.iconImages[0].Colors.Length * 4) + ico.iconImages[0].XOR.Length + ico.iconImages[0].AND.Length); // copy image data int bytePosXOR = 0; int bytePosAND = 0; byte transparentIndex = 0; transparentIndex = indexedImage[0]; //initialize AND ico.iconImages[0].AND[0] = byte.MaxValue; int pixelsPerByte; int bytesPerRow; // must be a long boundary (multiple of 4) int[] shiftCounts; switch (bitCount) { case 1: pixelsPerByte = 8; shiftCounts = new int[] {7, 6, 5, 4, 3, 2, 1, 0}; break; case 4: pixelsPerByte = 2; shiftCounts = new int[] {4, 0}; break; case 8: pixelsPerByte = 1; shiftCounts = new int[] {0}; break; default: throw new NotSupportedException("Bits per pixel must be 1, 4, or 8"); } bytesPerRow = ico.iconDirectory.Entries[0].Width / pixelsPerByte; int padBytes = bytesPerRow % 4; if (padBytes > 0) padBytes = 4 - padBytes; byte currentByte; sourcePosition = 0; for (int row=0; row < ico.iconDirectory.Entries[0].Height; ++row) { for (int rowByte=0; rowByte < bytesPerRow; ++rowByte) { currentByte = 0; for (int pixel=0; pixel < pixelsPerByte; ++pixel) { byte index = indexedImage[sourcePosition++]; byte shiftedIndex = (byte)(index << shiftCounts[pixel]); currentByte |= shiftedIndex; } ico.iconImages[0].XOR[bytePosXOR] = currentByte; ++bytePosXOR; } // make sure each scan line ends on a long boundary bytePosXOR += padBytes; } for(int i=0; i> (i % 8); if (index != transparentIndex) ico.iconImages[0].AND[bytePosAND] ^= (byte)bitPosAND; if (bitPosAND == 1) { // need to start another byte for next pixel if (bytePosAND % 2 ==1) { //TODO: fix assumption that icon is 16px wide //skip some bytes so that scanline ends on a long barrier bytePosAND += 3; } else { bytePosAND += 1; } if (bytePosAND < ico.iconImages[0].AND.Length) ico.iconImages[0].AND[bytePosAND] = byte.MaxValue; } } return ico; } private static ushort getBitCount(int uniqueColorCount) { if (uniqueColorCount <= 2) { return 1; } if (uniqueColorCount <= 16) { return 4; } if (uniqueColorCount <= 256) { return 8; } return 24; } private static RGBQUAD[] buildColorTable(Hashtable colors, ushort bpp) { //RGBQUAD[] colorTable = new RGBQUAD[colors.Count]; //HACK: it looks like the color array needs to be the max size based on bitcount int numColors = 1 << bpp; RGBQUAD[] colorTable = new RGBQUAD[numColors]; foreach(RGBQUAD color in colors.Keys) { int colorIndex = Convert.ToInt32( colors[color] ); colorTable[ colorIndex ] = color; } return colorTable; } // public static BitmapHolder IconToBitmap(IconHolder ico) // { // //TODO: implement // return new BitmapHolder(); // } } }