2 // System.Drawing.Icon.cs
5 // Dennis Hayes (dennish@Raytek.com)
6 // Andreas Nahr (ClassDevelopment@A-SoftTech.com)
7 // Sanjay Gupta (gsanjay@novell.com)
8 // Peter Dennis Bartok (pbartok@novell.com)
10 // Copyright (C) 2002 Ximian, Inc. http://www.ximian.com
11 // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 using System.Drawing.Imaging;
36 using System.Runtime.Serialization;
37 using System.Runtime.InteropServices;
38 using System.ComponentModel;
40 namespace System.Drawing
46 [Editor ("System.Drawing.Design.IconEditor, " + Consts.AssemblySystem_Drawing_Design, typeof (System.Drawing.Design.UITypeEditor))]
47 [TypeConverter(typeof(IconConverter))]
48 public sealed class Icon : MarshalByRefObject, ISerializable, ICloneable, IDisposable
50 [StructLayout(LayoutKind.Sequential)]
51 internal struct IconDirEntry {
52 internal byte width; // Width of icon
53 internal byte height; // Height of icon
54 internal byte colorCount; // colors in icon
55 internal byte reserved; // Reserved
56 internal ushort planes; // Color Planes
57 internal ushort bitCount; // Bits per pixel
58 internal uint bytesInRes; // bytes in resource
59 internal uint imageOffset; // position in file
62 [StructLayout(LayoutKind.Sequential)]
63 internal struct IconDir {
64 internal ushort idReserved; // Reserved
65 internal ushort idType; // resource type (1 for icons)
66 internal ushort idCount; // how many images?
67 internal IconDirEntry [] idEntries; // the entries for each image
70 [StructLayout(LayoutKind.Sequential)]
71 internal struct BitmapInfoHeader {
74 internal int biHeight;
75 internal ushort biPlanes;
76 internal ushort biBitCount;
77 internal uint biCompression;
78 internal uint biSizeImage;
79 internal int biXPelsPerMeter;
80 internal int biYPelsPerMeter;
81 internal uint biClrUsed;
82 internal uint biClrImportant;
85 [StructLayout(LayoutKind.Sequential)]
86 internal struct IconImage {
87 internal BitmapInfoHeader iconHeader; //image header
88 internal uint [] iconColors; //colors table
89 internal byte [] iconXOR; // bits for XOR mask
90 internal byte [] iconAND; //bits for AND mask
93 private Size iconSize;
94 private IntPtr winHandle = IntPtr.Zero;
95 private IconDir iconDir;
97 private IconImage [] imageData;
98 bool destroyIcon = true;
104 [MonoTODO ("Implement fully")]
105 private Icon (IntPtr handle)
107 this.winHandle = handle;
110 GDIPlus.GetIconInfo (winHandle, out ii);
112 // If this structure defines an icon, the hot spot is always in the center of the icon
113 iconSize = new Size (ii.xHotspot * 2, ii.yHotspot * 2);
116 throw new NotImplementedException ();
119 this.destroyIcon = false;
122 public Icon (Icon original, int width, int height) : this (original, new Size(width, height))
126 public Icon (Icon original, Size size)
128 this.iconSize = size;
129 this.winHandle = original.winHandle;
130 this.iconDir = original.iconDir;
131 this.imageData = original.imageData;
133 int count = iconDir.idCount;
134 bool sizeObtained = false;
135 for (int i=0; i<count; i++){
136 IconDirEntry ide = iconDir.idEntries [i];
138 if (ide.height==size.Height && ide.width==size.Width) {
139 this.id = (ushort) i;
141 this.iconSize.Height = ide.height;
142 this.iconSize.Width = ide.width;
148 uint largestSize = 0;
149 for (int j=0; j<count; j++){
150 if (iconDir.idEntries [j].bytesInRes >= largestSize){
151 largestSize = iconDir.idEntries [j].bytesInRes;
152 this.id = (ushort) j;
153 this.iconSize.Height = iconDir.idEntries [j].height;
154 this.iconSize.Width = iconDir.idEntries [j].width;
160 public Icon (Stream stream) : this (stream, 32, 32)
164 public Icon (Stream stream, int width, int height)
166 InitFromStreamWithSize (stream, width, height);
169 public Icon (string fileName) : this (new FileStream (fileName, FileMode.Open))
173 public Icon (Type type, string resource)
175 using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
177 throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
179 InitFromStreamWithSize (s, 32, 32); // 32x32 is default
183 private Icon (SerializationInfo info, StreamingContext context)
185 MemoryStream dataStream = null;
188 foreach (SerializationEntry serEnum in info) {
189 if (String.Compare(serEnum.Name, "IconData", true) == 0) {
190 dataStream = new MemoryStream ((byte []) serEnum.Value);
192 if (String.Compare(serEnum.Name, "IconSize", true) == 0) {
193 Size iconSize = (Size) serEnum.Value;
194 width = iconSize.Width;
195 height = iconSize.Height;
198 if ((dataStream != null) && (width == height)) {
199 dataStream.Seek (0, SeekOrigin.Begin);
200 InitFromStreamWithSize (dataStream, width, height);
204 void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
206 MemoryStream ms = new MemoryStream ();
208 info.AddValue ("IconSize", this.Size, typeof (Size));
209 info.AddValue ("IconData", ms.ToArray ());
213 public Icon (Stream stream, Size size) : this (stream, size.Width, size.Height) {}
215 public Icon (string fileName, int width, int height):
216 this (new FileStream (fileName, FileMode.Open), width, height) {}
218 public Icon (string fileName, Size size) :
219 this (new FileStream (fileName, FileMode.Open), size) {}
222 public static Icon ExtractAssociatedIcon (string filePath)
224 throw new NotImplementedException ();
229 public void Dispose ()
233 GC.SuppressFinalize(this);
239 if (winHandle ==IntPtr.Zero)
243 //TODO: will have to call some win32 icon stuff
244 winHandle = IntPtr.Zero;
249 public object Clone ()
251 return new Icon (this, this.Width, this.Height);
254 public static Icon FromHandle (IntPtr handle)
256 if (handle == IntPtr.Zero)
257 throw new ArgumentException ("handle");
259 return new Icon (handle);
262 public static Icon FromHandle (IntPtr handle)
264 throw new NotImplementedException ();
267 public void Save (Stream outputStream)
269 if (iconDir.idEntries!=null){
270 BinaryWriter bw = new BinaryWriter (outputStream);
272 bw.Write (iconDir.idReserved);
273 bw.Write (iconDir.idType);
274 ushort count = iconDir.idCount;
277 //now write iconDirEntries
278 for (int i=0; i<(int)count; i++){
279 IconDirEntry ide = iconDir.idEntries [i];
280 bw.Write (ide.width);
281 bw.Write (ide.height);
282 bw.Write (ide.colorCount);
283 bw.Write (ide.reserved);
284 bw.Write (ide.planes);
285 bw.Write (ide.bitCount);
286 bw.Write (ide.bytesInRes);
287 bw.Write (ide.imageOffset);
290 //now write iconImage data
291 for (int i=0; i<(int)count; i++){
292 BitmapInfoHeader bih = imageData [i].iconHeader;
293 bw.Write (bih.biSize);
294 bw.Write (bih.biWidth);
295 bw.Write (bih.biHeight);
296 bw.Write (bih.biPlanes);
297 bw.Write (bih.biBitCount);
298 bw.Write (bih.biCompression);
299 bw.Write (bih.biSizeImage);
300 bw.Write (bih.biXPelsPerMeter);
301 bw.Write (bih.biYPelsPerMeter);
302 bw.Write (bih.biClrUsed);
303 bw.Write (bih.biClrImportant);
305 //now write color table
306 int colCount = imageData [i].iconColors.Length;
307 for (int j=0; j<colCount; j++)
308 bw.Write (imageData [i].iconColors [j]);
311 bw.Write (imageData [i].iconXOR);
314 bw.Write (imageData [i].iconAND);
320 public Bitmap ToBitmap() {
322 BitmapInfoHeader bih;
330 if (imageData == null) {
331 return new Bitmap(32, 32);
334 ii = imageData[this.id];
336 biHeight = bih.biHeight / 2;
338 ncolors = (int)bih.biClrUsed;
340 if (bih.biBitCount < 24) {
341 ncolors = (int)(1 << bih.biBitCount);
345 switch(bih.biBitCount) {
346 case 1: { // Monochrome
347 bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
352 bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format4bppIndexed);
357 bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format8bppIndexed);
362 bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format24bppRgb);
366 bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format32bppArgb);
371 throw new Exception("Unexpected number of bits:" + bih.biBitCount.ToString());
375 if (bih.biBitCount < 24) {
376 pal = bmp.Palette; // Managed palette
378 for (int i = 0; i < ii.iconColors.Length; i++) {
379 pal.Entries[i] = Color.FromArgb((int)ii.iconColors[i] | unchecked((int)0xff000000));
384 bytesPerLine = (int)((((bih.biWidth * bih.biBitCount) + 31) & ~31) >> 3);
385 bits = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
387 for (int y = 0; y < biHeight; y++) {
388 Marshal.Copy(ii.iconXOR, bytesPerLine * y, (IntPtr)(bits.Scan0.ToInt64() + bits.Stride * (biHeight - 1 - y)), bytesPerLine);
391 bmp.UnlockBits(bits);
393 bmp = new Bitmap (bmp);// This makes a 32bpp image out of an indexed one
395 // Apply the mask to make properly transparent
396 bytesPerLine = (int)((((bih.biWidth) + 31) & ~31) >> 3);
397 for (int y = 0; y < biHeight; y++) {
398 for (int x = 0; x < bih.biWidth / 8; x++) {
399 for (int bit = 7; bit >= 0; bit--) {
400 if (((ii.iconAND[y * bytesPerLine +x] >> bit) & 1) != 0) {
401 bmp.SetPixel(x*8 + 7-bit, biHeight - y - 1, Color.Transparent);
410 public override string ToString ()
412 //is this correct, this is what returned by .Net
417 public IntPtr Handle {
426 return iconSize.Height;
439 return iconSize.Width;
450 private void InitFromStreamWithSize (Stream stream, int width, int height)
452 //read the icon header
453 if (stream == null || stream.Length == 0)
454 throw new System.ArgumentException ("The argument 'stream' must be a picture that can be used as a Icon", "stream");
456 BinaryReader reader = new BinaryReader (stream);
458 //iconDir = new IconDir ();
459 iconDir.idReserved = reader.ReadUInt16();
460 if (iconDir.idReserved != 0) //must be 0
461 throw new System.ArgumentException ("Invalid Argument", "stream");
463 iconDir.idType = reader.ReadUInt16();
464 if (iconDir.idType != 1) //must be 1
465 throw new System.ArgumentException ("Invalid Argument", "stream");
467 ushort dirEntryCount = reader.ReadUInt16();
468 iconDir.idCount = dirEntryCount;
469 iconDir.idEntries = new IconDirEntry [dirEntryCount];
470 imageData = new IconImage [dirEntryCount];
471 bool sizeObtained = false;
472 //now read in the IconDirEntry structures
473 for (int i=0; i<dirEntryCount; i++){
475 ide.width = reader.ReadByte ();
476 ide.height = reader.ReadByte ();
477 ide.colorCount = reader.ReadByte ();
478 ide.reserved = reader.ReadByte ();
479 ide.planes = reader.ReadUInt16 ();
480 ide.bitCount = reader.ReadUInt16 ();
481 ide.bytesInRes = reader.ReadUInt32 ();
482 ide.imageOffset = reader.ReadUInt32 ();
483 iconDir.idEntries [i] = ide;
484 //is this is the best fit??
486 if (ide.height==height && ide.width==width) {
487 this.id = (ushort) i;
489 this.iconSize.Height = ide.height;
490 this.iconSize.Width = ide.width;
493 //if we havent found the best match, return the one with the
494 //largest size. Is this approach correct??
496 uint largestSize = 0;
497 for (int j=0; j<dirEntryCount; j++){
498 if (iconDir.idEntries [j].bytesInRes >= largestSize) {
499 largestSize = iconDir.idEntries [j].bytesInRes;
500 this.id = (ushort) j;
501 this.iconSize.Height = iconDir.idEntries [j].height;
502 this.iconSize.Width = iconDir.idEntries [j].width;
507 //now read in the icon data
508 for (int j = 0; j<dirEntryCount; j++)
510 IconImage iidata = new IconImage();
511 BitmapInfoHeader bih = new BitmapInfoHeader();
512 stream.Seek (iconDir.idEntries [j].imageOffset, SeekOrigin.Begin);
513 byte [] buffer = new byte [iconDir.idEntries [j].bytesInRes];
514 stream.Read (buffer, 0, buffer.Length);
515 BinaryReader bihReader = new BinaryReader (new MemoryStream(buffer));
516 bih.biSize = bihReader.ReadUInt32 ();
517 bih.biWidth = bihReader.ReadInt32 ();
518 bih.biHeight = bihReader.ReadInt32 ();
519 bih.biPlanes = bihReader.ReadUInt16 ();
520 bih.biBitCount = bihReader.ReadUInt16 ();
521 bih.biCompression = bihReader.ReadUInt32 ();
522 bih.biSizeImage = bihReader.ReadUInt32 ();
523 bih.biXPelsPerMeter = bihReader.ReadInt32 ();
524 bih.biYPelsPerMeter = bihReader.ReadInt32 ();
525 bih.biClrUsed = bihReader.ReadUInt32 ();
526 bih.biClrImportant = bihReader.ReadUInt32 ();
528 iidata.iconHeader = bih;
529 //Read the number of colors used and corresponding memory occupied by
530 //color table. Fill this memory chunk into rgbquad[]
532 switch (bih.biBitCount){
533 case 1: numColors = 2;
535 case 4: numColors = 16;
537 case 8: numColors = 256;
539 default: numColors = 0;
543 iidata.iconColors = new uint [numColors];
544 for (int i=0; i<numColors; i++)
545 iidata.iconColors [i] = bihReader.ReadUInt32 ();
547 //XOR mask is immediately after ColorTable and its size is
548 //icon height* no. of bytes per line
550 //icon height is half of BITMAPINFOHEADER.biHeight, since it contains
551 //both XOR as well as AND mask bytes
552 int iconHeight = bih.biHeight/2;
554 //bytes per line should should be uint aligned
555 int numBytesPerLine = ((((bih.biWidth * bih.biPlanes * bih.biBitCount)+ 31)>>5)<<2);
557 //Determine the XOR array Size
558 int xorSize = numBytesPerLine * iconHeight;
559 iidata.iconXOR = new byte [xorSize];
560 int nread = bihReader.Read (iidata.iconXOR, 0, xorSize);
561 if (nread != xorSize)
562 throw new Exception ("Short file");
564 //Determine the AND array size
565 numBytesPerLine = (int)((((bih.biWidth) + 31) & ~31) >> 3);
566 int andSize = numBytesPerLine * iconHeight;
567 iidata.iconAND = new byte [andSize];
568 nread = bihReader.Read (iidata.iconAND, 0, andSize);
569 if (nread != andSize)
570 throw new Exception ("Short file");
572 imageData [j] = iidata;