|
| 1 | +// Created by Joshua Flanagan |
| 2 | +// http://flimflan.com/blog |
| 3 | +// May 2004 |
| 4 | +// |
| 5 | +// You may freely use this code as you wish, I only ask that you retain my name in the source code |
| 6 | + |
| 7 | +// Modified by Pavel Janda |
| 8 | +// - added support for 32bpp bitmaps |
| 9 | +// November 2006 |
| 10 | + |
| 11 | +using System; |
| 12 | +using System.IO; |
| 13 | +using System.Collections; |
| 14 | +using System.Drawing; |
| 15 | + |
| 16 | +namespace FlimFlan.IconEncoder |
| 17 | +{ |
| 18 | + /// <summary> |
| 19 | + /// Provides methods for converting between the bitmap and icon formats |
| 20 | + /// </summary> |
| 21 | + public class Converter |
| 22 | + { |
| 23 | + private Converter(){} |
| 24 | + public static Icon BitmapToIcon(Bitmap b) |
| 25 | + { |
| 26 | + IconHolder ico = BitmapToIconHolder(b); |
| 27 | + Icon newIcon; |
| 28 | + using (BinaryWriter bw = new BinaryWriter(new MemoryStream())) |
| 29 | + { |
| 30 | + ico.Save(bw); |
| 31 | + bw.BaseStream.Position = 0; |
| 32 | + newIcon = new Icon(bw.BaseStream); |
| 33 | + } |
| 34 | + return newIcon; |
| 35 | + } |
| 36 | + |
| 37 | + public static IconHolder BitmapToIconHolder(Bitmap b) |
| 38 | + { |
| 39 | + BitmapHolder bmp = new BitmapHolder();; |
| 40 | + using (MemoryStream stream = new MemoryStream()) |
| 41 | + { |
| 42 | + b.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp); |
| 43 | + stream.Position = 0; |
| 44 | + bmp.Open(stream); |
| 45 | + } |
| 46 | + return BitmapToIconHolder(bmp); |
| 47 | + } |
| 48 | + |
| 49 | + public static IconHolder BitmapToIconHolder(BitmapHolder bmp) |
| 50 | + { |
| 51 | + bool mapColors = (bmp.info.infoHeader.biBitCount <= 24); |
| 52 | + int maximumColors = 1 << bmp.info.infoHeader.biBitCount; |
| 53 | + //Hashtable uniqueColors = new Hashtable(maximumColors); |
| 54 | + // actual colors is probably nowhere near maximum, so dont try to initialize the hashtable |
| 55 | + Hashtable uniqueColors = new Hashtable(); |
| 56 | + |
| 57 | + int sourcePosition = 0; |
| 58 | + int numPixels = bmp.info.infoHeader.biHeight * bmp.info.infoHeader.biWidth; |
| 59 | + byte[] indexedImage = new byte[numPixels]; |
| 60 | + byte colorIndex; |
| 61 | + |
| 62 | + if (mapColors) |
| 63 | + { |
| 64 | + for (int i=0; i < indexedImage.Length; i++) |
| 65 | + { |
| 66 | + //TODO: currently assumes source bitmap is 24bit color |
| 67 | + //read 3 bytes, convert to color |
| 68 | + byte[] pixel = new byte[3]; |
| 69 | + Array.Copy(bmp.imageData, sourcePosition, pixel, 0, 3); |
| 70 | + sourcePosition += 3; |
| 71 | + |
| 72 | + RGBQUAD color = new RGBQUAD(pixel); |
| 73 | + if (uniqueColors.Contains(color)) |
| 74 | + { |
| 75 | + colorIndex = Convert.ToByte(uniqueColors[color]); |
| 76 | + } |
| 77 | + else |
| 78 | + { |
| 79 | + if (uniqueColors.Count > byte.MaxValue) |
| 80 | + { |
| 81 | + throw new NotSupportedException(String.Format("The source image contains more than {0} colors.", byte.MaxValue)); |
| 82 | + } |
| 83 | + colorIndex = Convert.ToByte(uniqueColors.Count); |
| 84 | + uniqueColors.Add(color, colorIndex); |
| 85 | + } |
| 86 | + // store pixel as an index into the color table |
| 87 | + indexedImage[i] = colorIndex; |
| 88 | + } |
| 89 | + } |
| 90 | + else |
| 91 | + { |
| 92 | + // added by Pavel Janda on 14/11/2006 |
| 93 | + if (bmp.info.infoHeader.biBitCount == 32) |
| 94 | + { |
| 95 | + for (int i=0; i < indexedImage.Length; i++) |
| 96 | + { |
| 97 | + //TODO: currently assumes source bitmap is 32bit color with alpha set to zero |
| 98 | + //ignore first byte, read another 3 bytes, convert to color |
| 99 | + byte[] pixel = new byte[4]; |
| 100 | + Array.Copy(bmp.imageData, sourcePosition, pixel, 0, 4); |
| 101 | + sourcePosition += 4; |
| 102 | + |
| 103 | + RGBQUAD color = new RGBQUAD(pixel[0], pixel[1], pixel[2], pixel[3]); |
| 104 | + if (uniqueColors.Contains(color)) |
| 105 | + { |
| 106 | + colorIndex = Convert.ToByte(uniqueColors[color]); |
| 107 | + } |
| 108 | + else |
| 109 | + { |
| 110 | + if (uniqueColors.Count > byte.MaxValue) |
| 111 | + { |
| 112 | + throw new NotSupportedException(String.Format("The source image contains more than {0} colors.", byte.MaxValue)); |
| 113 | + } |
| 114 | + colorIndex = Convert.ToByte(uniqueColors.Count); |
| 115 | + uniqueColors.Add(color, colorIndex); |
| 116 | + } |
| 117 | + // store pixel as an index into the color table |
| 118 | + indexedImage[i] = colorIndex; |
| 119 | + } |
| 120 | + // end of addition |
| 121 | + } |
| 122 | + else |
| 123 | + { |
| 124 | + //TODO: implement converting an indexed bitmap |
| 125 | + throw new NotImplementedException("Unable to convert indexed bitmaps."); |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + ushort bitCount = getBitCount(uniqueColors.Count); |
| 130 | + // *** Build Icon *** |
| 131 | + IconHolder ico = new IconHolder(); |
| 132 | + ico.iconDirectory.Entries = new ICONDIRENTRY[1]; |
| 133 | + //TODO: is it really safe to assume the bitmap width/height are bytes? |
| 134 | + ico.iconDirectory.Entries[0].Width = (byte) bmp.info.infoHeader.biWidth; |
| 135 | + ico.iconDirectory.Entries[0].Height = (byte) bmp.info.infoHeader.biHeight; |
| 136 | + ico.iconDirectory.Entries[0].BitCount = bitCount; // maybe 0? |
| 137 | + ico.iconDirectory.Entries[0].ColorCount = (uniqueColors.Count > byte.MaxValue) ? (byte)0 : (byte)uniqueColors.Count; |
| 138 | + //HACK: safe to assume that the first imageoffset is always 22 |
| 139 | + ico.iconDirectory.Entries[0].ImageOffset = 22; |
| 140 | + ico.iconDirectory.Entries[0].Planes = 0; |
| 141 | + ico.iconImages[0].Header.biBitCount = bitCount; |
| 142 | + ico.iconImages[0].Header.biWidth = ico.iconDirectory.Entries[0].Width; |
| 143 | + // height is doubled in header, to account for XOR and AND |
| 144 | + ico.iconImages[0].Header.biHeight = ico.iconDirectory.Entries[0].Height << 1; |
| 145 | + ico.iconImages[0].XOR = new byte[ico.iconImages[0].numBytesInXor()]; |
| 146 | + ico.iconImages[0].AND = new byte[ico.iconImages[0].numBytesInAnd()]; |
| 147 | + ico.iconImages[0].Header.biSize = 40; // always |
| 148 | + ico.iconImages[0].Header.biSizeImage = (uint)ico.iconImages[0].XOR.Length; |
| 149 | + ico.iconImages[0].Header.biPlanes = 1; |
| 150 | + ico.iconImages[0].Colors = buildColorTable(uniqueColors, bitCount); |
| 151 | + //BytesInRes = biSize + colors * 4 + XOR + AND |
| 152 | + ico.iconDirectory.Entries[0].BytesInRes = (uint)(ico.iconImages[0].Header.biSize |
| 153 | + + (ico.iconImages[0].Colors.Length * 4) |
| 154 | + + ico.iconImages[0].XOR.Length |
| 155 | + + ico.iconImages[0].AND.Length); |
| 156 | + |
| 157 | + // copy image data |
| 158 | + int bytePosXOR = 0; |
| 159 | + int bytePosAND = 0; |
| 160 | + byte transparentIndex = 0; |
| 161 | + transparentIndex = indexedImage[0]; |
| 162 | + //initialize AND |
| 163 | + ico.iconImages[0].AND[0] = byte.MaxValue; |
| 164 | + |
| 165 | + int pixelsPerByte; |
| 166 | + int bytesPerRow; // must be a long boundary (multiple of 4) |
| 167 | + int[] shiftCounts; |
| 168 | + |
| 169 | + switch (bitCount) |
| 170 | + { |
| 171 | + case 1: |
| 172 | + pixelsPerByte = 8; |
| 173 | + shiftCounts = new int[] {7, 6, 5, 4, 3, 2, 1, 0}; |
| 174 | + break; |
| 175 | + case 4: |
| 176 | + pixelsPerByte = 2; |
| 177 | + shiftCounts = new int[] {4, 0}; |
| 178 | + break; |
| 179 | + case 8: |
| 180 | + pixelsPerByte = 1; |
| 181 | + shiftCounts = new int[] {0}; |
| 182 | + break; |
| 183 | + default: |
| 184 | + throw new NotSupportedException("Bits per pixel must be 1, 4, or 8"); |
| 185 | + } |
| 186 | + bytesPerRow = ico.iconDirectory.Entries[0].Width / pixelsPerByte; |
| 187 | + int padBytes = bytesPerRow % 4; |
| 188 | + if (padBytes > 0) |
| 189 | + padBytes = 4 - padBytes; |
| 190 | + |
| 191 | + byte currentByte; |
| 192 | + sourcePosition = 0; |
| 193 | + for (int row=0; row < ico.iconDirectory.Entries[0].Height; ++row) |
| 194 | + { |
| 195 | + for (int rowByte=0; rowByte < bytesPerRow; ++rowByte) |
| 196 | + { |
| 197 | + currentByte = 0; |
| 198 | + for (int pixel=0; pixel < pixelsPerByte; ++pixel) |
| 199 | + { |
| 200 | + byte index = indexedImage[sourcePosition++]; |
| 201 | + byte shiftedIndex = (byte)(index << shiftCounts[pixel]); |
| 202 | + currentByte |= shiftedIndex; |
| 203 | + } |
| 204 | + ico.iconImages[0].XOR[bytePosXOR] = currentByte; |
| 205 | + ++bytePosXOR; |
| 206 | + } |
| 207 | + // make sure each scan line ends on a long boundary |
| 208 | + bytePosXOR += padBytes; |
| 209 | + } |
| 210 | + |
| 211 | + for(int i=0; i<indexedImage.Length; i++) |
| 212 | + { |
| 213 | + byte index = indexedImage[i]; |
| 214 | + int bitPosAND = 128 >> (i % 8); |
| 215 | + if (index != transparentIndex) |
| 216 | + ico.iconImages[0].AND[bytePosAND] ^= (byte)bitPosAND; |
| 217 | + if (bitPosAND == 1) |
| 218 | + { |
| 219 | + // need to start another byte for next pixel |
| 220 | + if (bytePosAND % 2 ==1) |
| 221 | + { |
| 222 | + //TODO: fix assumption that icon is 16px wide |
| 223 | + //skip some bytes so that scanline ends on a long barrier |
| 224 | + bytePosAND += 3; |
| 225 | + } |
| 226 | + else |
| 227 | + { |
| 228 | + bytePosAND += 1; |
| 229 | + } |
| 230 | + if (bytePosAND < ico.iconImages[0].AND.Length) |
| 231 | + ico.iconImages[0].AND[bytePosAND] = byte.MaxValue; |
| 232 | + } |
| 233 | + } |
| 234 | + return ico; |
| 235 | + } |
| 236 | + |
| 237 | + private static ushort getBitCount(int uniqueColorCount) |
| 238 | + { |
| 239 | + if (uniqueColorCount <= 2) |
| 240 | + { |
| 241 | + return 1; |
| 242 | + } |
| 243 | + if (uniqueColorCount <= 16) |
| 244 | + { |
| 245 | + return 4; |
| 246 | + } |
| 247 | + if (uniqueColorCount <= 256) |
| 248 | + { |
| 249 | + return 8; |
| 250 | + } |
| 251 | + return 24; |
| 252 | + } |
| 253 | + |
| 254 | + private static RGBQUAD[] buildColorTable(Hashtable colors, ushort bpp) |
| 255 | + { |
| 256 | + //RGBQUAD[] colorTable = new RGBQUAD[colors.Count]; |
| 257 | + //HACK: it looks like the color array needs to be the max size based on bitcount |
| 258 | + int numColors = 1 << bpp; |
| 259 | + RGBQUAD[] colorTable = new RGBQUAD[numColors]; |
| 260 | + foreach(RGBQUAD color in colors.Keys) |
| 261 | + { |
| 262 | + int colorIndex = Convert.ToInt32( colors[color] ); |
| 263 | + colorTable[ colorIndex ] = color; |
| 264 | + } |
| 265 | + return colorTable; |
| 266 | + } |
| 267 | + |
| 268 | + // public static BitmapHolder IconToBitmap(IconHolder ico) |
| 269 | + // { |
| 270 | + // //TODO: implement |
| 271 | + // return new BitmapHolder(); |
| 272 | + // } |
| 273 | + } |
| 274 | +} |
0 commit comments