System.Drawing: added email to icon and test file headers
[mono.git] / mcs / class / System.Drawing / System.Drawing / Icon.cs
1 //
2 // System.Drawing.Icon.cs
3 //
4 // Authors:
5 //   Gary Barnett (gary.barnett.mono@gmail.com)
6 //   Dennis Hayes (dennish@Raytek.com)
7 //   Andreas Nahr (ClassDevelopment@A-SoftTech.com)
8 //   Sanjay Gupta (gsanjay@novell.com)
9 //   Peter Dennis Bartok (pbartok@novell.com)
10 //   Sebastien Pouliot  <sebastien@ximian.com>
11 //
12 // Copyright (C) 2002 Ximian, Inc. http://www.ximian.com
13 // Copyright (C) 2004-2008 Novell, Inc (http://www.novell.com)
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34
35 using System.Collections;
36 using System.ComponentModel;
37 using System.Drawing.Imaging;
38 using System.IO;
39 using System.Runtime.Serialization;
40 using System.Runtime.InteropServices;
41 using System.Security.Permissions;
42
43 namespace System.Drawing
44 {
45 #if !NET_2_0
46         [ComVisible (false)] 
47 #endif 
48         [Serializable]  
49 #if !MONOTOUCH
50         [Editor ("System.Drawing.Design.IconEditor, " + Consts.AssemblySystem_Drawing_Design, typeof (System.Drawing.Design.UITypeEditor))]
51 #endif
52         [TypeConverter(typeof(IconConverter))]
53
54         public sealed class Icon : MarshalByRefObject, ISerializable, ICloneable, IDisposable
55         {
56                 [StructLayout(LayoutKind.Sequential)]
57                 internal struct IconDirEntry {          
58                         internal byte   width;          // Width of icon
59                         internal byte   height;         // Height of icon
60                         internal byte   colorCount;     // colors in icon 
61                         internal byte   reserved;       // Reserved
62                         internal ushort planes;         // Color Planes
63                         internal ushort bitCount;       // Bits per pixel
64                         internal uint   bytesInRes;     // bytes in resource
65                         internal uint   imageOffset;    // position in file 
66                         internal bool   ignore;         // for unsupported images (vista 256 png)
67                 }; 
68
69                 [StructLayout(LayoutKind.Sequential)]
70                 internal struct IconDir {
71                         internal ushort                 idReserved;   // Reserved
72                         internal ushort                 idType;       // resource type (1 for icons)
73                         internal ushort                 idCount;      // how many images?
74                         internal IconDirEntry []        idEntries;    // the entries for each image
75                 };
76                 
77                 [StructLayout(LayoutKind.Sequential)]
78                 internal struct BitmapInfoHeader {
79                         internal uint   biSize; 
80                         internal int    biWidth; 
81                         internal int    biHeight; 
82                         internal ushort biPlanes; 
83                         internal ushort biBitCount; 
84                         internal uint   biCompression; 
85                         internal uint   biSizeImage; 
86                         internal int    biXPelsPerMeter; 
87                         internal int    biYPelsPerMeter; 
88                         internal uint   biClrUsed; 
89                         internal uint   biClrImportant; 
90                 };
91
92                 [StructLayout(LayoutKind.Sequential)]   // added baseclass for non bmp image format support
93                 internal abstract class ImageData {
94                 };
95
96                 [StructLayout(LayoutKind.Sequential)]
97                 internal class IconImage : ImageData {
98                         internal BitmapInfoHeader       iconHeader;     //image header
99                         internal uint []                iconColors;     //colors table
100                         internal byte []                iconXOR;        // bits for XOR mask
101                         internal byte []                iconAND;        //bits for AND mask
102                 };
103
104                 [StructLayout(LayoutKind.Sequential)]
105                 internal class IconDump : ImageData {
106                         internal byte []                data;
107                 };
108
109                 private Size iconSize;
110                 private IntPtr handle = IntPtr.Zero;
111                 private IconDir iconDir;
112                 private ushort id;
113                 private ImageData [] imageData;
114                 private bool undisposable;
115                 private bool disposed;
116                 private Bitmap bitmap;
117
118                 private Icon ()
119                 {
120                 }
121
122 #if !MONOTOUCH
123                 private Icon (IntPtr handle)
124                 {
125                         this.handle = handle;
126                         bitmap = Bitmap.FromHicon (handle);
127                         iconSize = new Size (bitmap.Width, bitmap.Height);
128                         if (GDIPlus.RunningOnUnix ()) {
129                                 bitmap = Bitmap.FromHicon (handle);
130                                 iconSize = new Size (bitmap.Width, bitmap.Height);
131                                 // FIXME: we need to convert the bitmap into an icon
132                         } else {
133                                 IconInfo ii;
134                                 GDIPlus.GetIconInfo (handle, out ii);
135                                 if (!ii.IsIcon)
136                                         throw new NotImplementedException (Locale.GetText ("Handle doesn't represent an ICON."));
137
138                                 // If this structure defines an icon, the hot spot is always in the center of the icon
139                                 iconSize = new Size (ii.xHotspot * 2, ii.yHotspot * 2);
140                                 bitmap = (Bitmap) Image.FromHbitmap (ii.hbmColor);
141                         }
142                         undisposable = true;
143                 }
144 #endif
145
146                 public Icon (Icon original, int width, int height)
147                         : this (original, new Size (width, height))
148                 {
149                 }
150
151                 public Icon (Icon original, Size size)
152                 {
153                         if (original == null)
154                                 throw new ArgumentException ("original");
155
156                         iconSize = size;
157                         iconDir = original.iconDir;
158                         
159                         int count = iconDir.idCount;
160                         if (count > 0) {
161                                 imageData = original.imageData;
162                                 id = UInt16.MaxValue;
163
164                                 for (ushort i=0; i < count; i++) {
165                                         IconDirEntry ide = iconDir.idEntries [i];
166                                         if (((ide.height == size.Height) || (ide.width == size.Width)) && !ide.ignore) {
167                                                 id = i;
168                                                 break;
169                                         }
170                                 }
171
172                                 // if a perfect match isn't found we look for the biggest icon *smaller* than specified
173                                 if (id == UInt16.MaxValue) { 
174                                         int requested = Math.Min (size.Height, size.Width);
175                                         // previously best set to 1st image, as this might not be smallest changed loop to check all
176                                         IconDirEntry? best = null; 
177                                         for (ushort i=0; i < count; i++) {
178                                                 IconDirEntry ide = iconDir.idEntries [i];
179                                                 if (((ide.height < requested) || (ide.width < requested)) && !ide.ignore) {
180                                                         if (best == null) {
181                                                                 best = ide;
182                                                                 id = i;
183                                                         } else if ((ide.height > best.Value.height) || (ide.width > best.Value.width)) {
184                                                                 best = ide;
185                                                                 id = i;
186                                                         }
187                                                 }
188                                         }
189                                 }
190
191                                 // last one, if nothing better can be found
192                                 if (id == UInt16.MaxValue) {
193                                         int i = count;
194                                         while (id == UInt16.MaxValue && i > 0) {
195                                                 i--;
196                                                 if (!iconDir.idEntries [i].ignore)
197                                                         id = (ushort) i;
198                                         }
199                                 }
200
201                                 if (id == UInt16.MaxValue)
202                                         throw new ArgumentException ("Icon", "No valid icon image found");
203
204                                 iconSize.Height = iconDir.idEntries [id].height;
205                                 iconSize.Width = iconDir.idEntries [id].width;
206                         } else {
207                                 iconSize.Height = size.Height;
208                                 iconSize.Width = size.Width;
209                         }
210
211                         if (original.bitmap != null)
212                                 bitmap = (Bitmap) original.bitmap.Clone ();
213                 }
214
215                 public Icon (Stream stream) : this (stream, 32, 32) 
216                 {
217                 }
218
219                 public Icon (Stream stream, int width, int height)
220                 {
221                         InitFromStreamWithSize (stream, width, height);
222                 }
223
224                 public Icon (string fileName)
225                 {
226                         using (FileStream fs = File.OpenRead (fileName)) {
227                                 InitFromStreamWithSize (fs, 32, 32);
228                         }
229                 }
230
231                 public Icon (Type type, string resource)
232                 {
233                         if (resource == null)
234                                 throw new ArgumentException ("resource");
235
236                         using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
237                                 if (s == null) {
238                                         string msg = Locale.GetText ("Resource '{0}' was not found.", resource);
239                                         throw new FileNotFoundException (msg);
240                                 }
241                                 InitFromStreamWithSize (s, 32, 32);             // 32x32 is default
242                         }
243                 }
244
245                 private Icon (SerializationInfo info, StreamingContext context)
246                 {
247                         MemoryStream dataStream = null;
248                         int width=0;
249                         int height=0;
250                         foreach (SerializationEntry serEnum in info) {
251                                 if (String.Compare(serEnum.Name, "IconData", true) == 0) {
252                                         dataStream = new MemoryStream ((byte []) serEnum.Value);
253                                 }
254                                 if (String.Compare(serEnum.Name, "IconSize", true) == 0) {
255                                         Size iconSize = (Size) serEnum.Value;
256                                         width = iconSize.Width;
257                                         height = iconSize.Height;
258                                 }
259                         }
260                         if (dataStream != null) {
261                                 dataStream.Seek (0, SeekOrigin.Begin);
262                                 InitFromStreamWithSize (dataStream, width, height);
263                         }
264                 }
265
266                 internal Icon (string resourceName, bool undisposable)
267                 {
268                         using (Stream s = typeof (Icon).Assembly.GetManifestResourceStream (resourceName)) {
269                                 if (s == null) {
270                                         string msg = Locale.GetText ("Resource '{0}' was not found.", resourceName);
271                                         throw new FileNotFoundException (msg);
272                                 }
273                                 InitFromStreamWithSize (s, 32, 32);             // 32x32 is default
274                         }
275                         this.undisposable = true;
276                 }
277
278                 void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context)
279                 {
280                         MemoryStream ms = new MemoryStream ();
281                         Save (ms);
282                         si.AddValue ("IconSize", this.Size, typeof (Size));
283                         si.AddValue ("IconData", ms.ToArray ());
284                 }
285
286 #if NET_2_0
287                 public Icon (Stream stream, Size size) : 
288                         this (stream, size.Width, size.Height)
289                 {
290                 }
291                 
292                 public Icon (string fileName, int width, int height)
293                 {
294                         using (FileStream fs = File.OpenRead (fileName)) {
295                                 InitFromStreamWithSize (fs, width, height);
296                         }
297                 }
298         
299                 public Icon (string fileName, Size size)
300                 {
301                         using (FileStream fs = File.OpenRead (fileName)) {
302                                 InitFromStreamWithSize (fs, size.Width, size.Height);
303                         }
304                 }
305
306                 [MonoLimitation ("The same icon, SystemIcons.WinLogo, is returned for all file types.")]
307                 public static Icon ExtractAssociatedIcon (string filePath)
308                 {
309                         if (String.IsNullOrEmpty (filePath))
310                                 throw new ArgumentException (Locale.GetText ("Null or empty path."), "filePath");
311                         if (!File.Exists (filePath))
312                                 throw new FileNotFoundException (Locale.GetText ("Couldn't find specified file."), filePath);
313
314                         return SystemIcons.WinLogo;
315                 }       
316 #endif
317
318                 public void Dispose ()
319                 {
320                         // SystemIcons requires this
321                         if (undisposable)
322                                 return;
323                         
324                         if (!disposed) {
325 #if !MONOTOUCH
326                                 if (GDIPlus.RunningOnWindows () && (handle != IntPtr.Zero)) {
327                                         GDIPlus.DestroyIcon (handle);
328                                         handle = IntPtr.Zero;
329                                 }
330 #endif
331                                 if (bitmap != null) {
332                                         bitmap.Dispose ();
333                                         bitmap = null;
334                                 }
335                                 GC.SuppressFinalize (this);
336                         }
337                         disposed = true;
338                 }
339
340                 public object Clone ()
341                 {
342                         return new Icon (this, Size);
343                 }
344                 
345 #if !MONOTOUCH
346                 [SecurityPermission (SecurityAction.LinkDemand, UnmanagedCode = true)]
347                 public static Icon FromHandle (IntPtr handle)
348                 {
349                         if (handle == IntPtr.Zero)
350                                 throw new ArgumentException ("handle");
351
352                         return new Icon (handle);
353                 }
354 #endif
355                 private void SaveIconImage (BinaryWriter writer, IconImage ii)
356                 {
357                         BitmapInfoHeader bih = ii.iconHeader;
358                         writer.Write (bih.biSize);
359                         writer.Write (bih.biWidth);
360                         writer.Write (bih.biHeight);
361                         writer.Write (bih.biPlanes);
362                         writer.Write (bih.biBitCount);
363                         writer.Write (bih.biCompression);
364                         writer.Write (bih.biSizeImage);
365                         writer.Write (bih.biXPelsPerMeter);
366                         writer.Write (bih.biYPelsPerMeter);
367                         writer.Write (bih.biClrUsed);
368                         writer.Write (bih.biClrImportant);
369
370                         //now write color table
371                         int colCount = ii.iconColors.Length;
372                         for (int j=0; j < colCount; j++)
373                                 writer.Write (ii.iconColors [j]);
374
375                         //now write XOR Mask
376                         writer.Write (ii.iconXOR);
377
378                         //now write AND Mask
379                         writer.Write (ii.iconAND);
380                 }
381
382                 private void SaveIconDump (BinaryWriter writer, IconDump id)
383                 {
384                         writer.Write (id.data);
385                 }
386
387                 private void SaveIconDirEntry (BinaryWriter writer, IconDirEntry ide, uint offset)
388                 {
389                         writer.Write (ide.width);
390                         writer.Write (ide.height);
391                         writer.Write (ide.colorCount);
392                         writer.Write (ide.reserved);
393                         writer.Write (ide.planes);
394                         writer.Write (ide.bitCount);
395                         writer.Write (ide.bytesInRes);
396                         writer.Write ((offset == UInt32.MaxValue) ? ide.imageOffset : offset);
397                 }
398
399                 private void SaveAll (BinaryWriter writer)
400                 {
401                         writer.Write (iconDir.idReserved);
402                         writer.Write (iconDir.idType);
403                         ushort count = iconDir.idCount;
404                         writer.Write (count);
405
406                         for (int i=0; i < (int)count; i++) {
407                                 SaveIconDirEntry (writer, iconDir.idEntries [i], UInt32.MaxValue);
408                         }
409
410                         for (int i=0; i < (int)count; i++) {
411
412                                 //FIXME: HACK: 1 (out of the 8) vista type icons had additional bytes (value:0)
413                                 //between images. This fixes the issue, but perhaps shouldnt include in production?
414                                 while (writer.BaseStream.Length < iconDir.idEntries[i].imageOffset)
415                                         writer.Write ((byte) 0);
416
417                                 if (imageData [i] is IconDump)
418                                         SaveIconDump (writer, (IconDump) imageData [i]);
419                                 else
420                                         SaveIconImage (writer, (IconImage) imageData [i]);
421                         }
422                 }
423                 // TODO: check image not ignored (presently this method doesnt seem to be called unless width/height 
424                 // refer to image)
425                 private void SaveBestSingleIcon (BinaryWriter writer, int width, int height)
426                 {
427                         writer.Write (iconDir.idReserved);
428                         writer.Write (iconDir.idType);
429                         writer.Write ((ushort)1);
430
431                         // find best entry and save it
432                         int best = 0;
433                         int bitCount = 0;
434                         for (int i=0; i < iconDir.idCount; i++) {
435                                 IconDirEntry ide = iconDir.idEntries [i];
436                                 if ((width == ide.width) && (height == ide.height)) {
437                                         if (ide.bitCount >= bitCount) {
438                                                 bitCount = ide.bitCount;
439                                                 best = i;
440                                         }
441                                 }
442                         }
443
444                         SaveIconDirEntry (writer, iconDir.idEntries [best], 22);
445                         SaveIconImage (writer, (IconImage) imageData [best]);
446                 }
447
448                 private void SaveBitmapAsIcon (BinaryWriter writer)
449                 {
450                         writer.Write ((ushort)0);       // idReserved must be 0
451                         writer.Write ((ushort)1);       // idType must be 1
452                         writer.Write ((ushort)1);       // only one icon
453
454                         // when transformed into a bitmap only a single image exists
455                         IconDirEntry ide = new IconDirEntry ();
456                         ide.width = (byte) bitmap.Width;
457                         ide.height = (byte) bitmap.Height;
458                         ide.colorCount = 0;     // 32 bbp == 0, for palette size
459                         ide.reserved = 0;       // always 0
460                         ide.planes = 0;
461                         ide.bitCount = 32;
462                         ide.imageOffset = 22;   // 22 is the first icon position (for single icon files)
463
464                         BitmapInfoHeader bih = new BitmapInfoHeader ();
465                         bih.biSize = (uint) Marshal.SizeOf (typeof (BitmapInfoHeader));
466                         bih.biWidth = bitmap.Width;
467                         bih.biHeight = 2 * bitmap.Height; // include both XOR and AND images
468                         bih.biPlanes = 1;
469                         bih.biBitCount = 32;
470                         bih.biCompression = 0;
471                         bih.biSizeImage = 0;
472                         bih.biXPelsPerMeter = 0;
473                         bih.biYPelsPerMeter = 0;
474                         bih.biClrUsed = 0;
475                         bih.biClrImportant = 0;
476
477                         IconImage ii = new IconImage ();
478                         ii.iconHeader = bih;
479                         ii.iconColors = new uint [0];   // no palette
480                         int xor_size = (((bih.biBitCount * bitmap.Width + 31) & ~31) >> 3) * bitmap.Height;
481                         ii.iconXOR = new byte [xor_size];
482                         int p = 0;
483                         for (int y = bitmap.Height - 1; y >=0; y--) {
484                                 for (int x = 0; x < bitmap.Width; x++) {
485                                         Color c = bitmap.GetPixel (x, y);
486                                         ii.iconXOR [p++] = c.B;
487                                         ii.iconXOR [p++] = c.G;
488                                         ii.iconXOR [p++] = c.R;
489                                         ii.iconXOR [p++] = c.A;
490                                 }
491                         }
492                         int and_line_size = (((Width + 31) & ~31) >> 3);        // must be a multiple of 4 bytes
493                         int and_size = and_line_size * bitmap.Height;
494                         ii.iconAND = new byte [and_size];
495
496                         ide.bytesInRes = (uint) (bih.biSize + xor_size + and_size);
497
498                         SaveIconDirEntry (writer, ide, UInt32.MaxValue);
499                         SaveIconImage (writer, ii);
500                 }
501
502                 private void Save (Stream outputStream, int width, int height)
503                 {
504                         BinaryWriter writer = new BinaryWriter (outputStream);
505                         // if we have the icon information then save from this
506                         if (iconDir.idEntries != null) {
507                                 if ((width == -1) && (height == -1))
508                                         SaveAll (writer);
509                                 else
510                                         SaveBestSingleIcon (writer, width, height);
511                         } else if (bitmap != null) {
512                                 // if the icon was created from a bitmap then convert it
513                                 SaveBitmapAsIcon (writer);
514                         }
515                         writer.Flush ();
516                 }
517
518                 public void Save (Stream outputStream)
519                 {
520                         if (outputStream == null)
521                                 throw new NullReferenceException ("outputStream");
522
523                         // save every icons available
524                         Save (outputStream, -1, -1);
525                 }
526 #if !MONOTOUCH
527                 internal Bitmap BuildBitmapOnWin32 ()
528                 {
529                         Bitmap bmp;
530
531                         if (imageData == null)
532                                 return new Bitmap (32, 32);
533
534                         IconImage ii = (IconImage) imageData [id];
535                         BitmapInfoHeader bih = ii.iconHeader;
536                         int biHeight = bih.biHeight / 2;
537
538                         int ncolors = (int)bih.biClrUsed;
539                         if ((ncolors == 0) && (bih.biBitCount < 24))
540                                 ncolors = (int)(1 << bih.biBitCount);
541
542                         switch (bih.biBitCount) {
543                         case 1:
544                                 bmp = new Bitmap (bih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
545                                 break;
546                         case 4:
547                                 bmp = new Bitmap (bih.biWidth, biHeight, PixelFormat.Format4bppIndexed);
548                                 break;
549                         case 8:
550                                 bmp = new Bitmap (bih.biWidth, biHeight, PixelFormat.Format8bppIndexed);
551                                 break;
552                         case 24:
553                                 bmp = new Bitmap (bih.biWidth, biHeight, PixelFormat.Format24bppRgb);
554                                 break;
555                         case 32:
556                                 bmp = new Bitmap (bih.biWidth, biHeight, PixelFormat.Format32bppArgb);
557                                 break;
558                         default:
559                                 string msg = Locale.GetText ("Unexpected number of bits: {0}", bih.biBitCount);
560                                 throw new Exception (msg);
561                         }
562
563                         if (bih.biBitCount < 24) {
564                                 ColorPalette pal = bmp.Palette; // Managed palette
565
566                                 for (int i = 0; i < ii.iconColors.Length; i++) {
567                                         pal.Entries[i] = Color.FromArgb ((int)ii.iconColors[i] | unchecked((int)0xff000000));
568                                 }
569                                 bmp.Palette = pal;
570                         }
571
572                         int bytesPerLine = (int)((((bih.biWidth * bih.biBitCount) + 31) & ~31) >> 3);
573                         BitmapData bits = bmp.LockBits (new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
574
575                         for (int y = 0; y < biHeight; y++) {
576                                 Marshal.Copy (ii.iconXOR, bytesPerLine * y, 
577                                         (IntPtr)(bits.Scan0.ToInt64() + bits.Stride * (biHeight - 1 - y)), bytesPerLine);
578                         }
579                         
580                         bmp.UnlockBits (bits);
581
582                         bmp = new Bitmap (bmp); // This makes a 32bpp image out of an indexed one
583
584                         // Apply the mask to make properly transparent
585                         bytesPerLine = (int)((((bih.biWidth) + 31) & ~31) >> 3);
586                         for (int y = 0; y < biHeight; y++) {
587                                 for (int x = 0; x < bih.biWidth / 8; x++) {
588                                         for (int bit = 7; bit >= 0; bit--) {
589                                                 if (((ii.iconAND[y * bytesPerLine +x] >> bit) & 1) != 0) {
590                                                         bmp.SetPixel (x*8 + 7-bit, biHeight - y - 1, Color.Transparent);
591                                                 }
592                                         }
593                                 }
594                         }
595
596                         return bmp;
597                 }
598
599                 internal Bitmap GetInternalBitmap ()
600                 {
601                         if (bitmap == null) {
602                                 if (GDIPlus.RunningOnUnix ()) {
603                                         // Mono's libgdiplus doesn't require to keep the stream alive when loading images
604                                         using (MemoryStream ms = new MemoryStream ()) {
605                                                 // save the current icon
606                                                 Save (ms, Width, Height);
607                                                 ms.Position = 0;
608
609                                                 // libgdiplus can now decode icons
610                                                 bitmap = (Bitmap) Image.LoadFromStream (ms, false);
611                                         }
612                                 } else {
613                                         // MS GDI+ ICO codec is more limited than the MS Icon class
614                                         // so we can't, reliably, get bitmap using it. We need to do this the "slow" way
615                                         bitmap = BuildBitmapOnWin32 ();
616                                 }
617                         }
618                         return bitmap;
619                 }
620
621                 // note: all bitmaps are 32bits ARGB - no matter what the icon format (bitcount) was
622                 public Bitmap ToBitmap ()
623                 {
624                         if (disposed)
625                                 throw new ObjectDisposedException (Locale.GetText ("Icon instance was disposed."));
626
627                         // note: we can't return the original image because
628                         // (a) we have no control over the bitmap instance we return (i.e. it could be disposed)
629                         // (b) the palette, flags won't match MS results. See MonoTests.System.Drawing.Imaging.IconCodecTest.
630                         //     Image16 for the differences
631                         return new Bitmap (GetInternalBitmap ());
632                 }
633 #endif
634                 public override string ToString ()
635                 {
636                         //is this correct, this is what returned by .Net
637                         return "<Icon>";                        
638                 }
639                 
640 #if !MONOTOUCH
641                 [Browsable (false)]
642                 public IntPtr Handle {
643                         get {
644                                 // note: this handle doesn't survive the lifespan of the icon instance
645                                 if (!disposed && (handle == IntPtr.Zero)) {
646                                         if (GDIPlus.RunningOnUnix ()) {
647                                                 handle = GetInternalBitmap ().NativeObject;
648                                         } else {
649                                                 // remember that this block executes only with MS GDI+
650                                                 IconInfo ii = new IconInfo ();
651                                                 ii.IsIcon = true;
652                                                 ii.hbmColor = ToBitmap ().GetHbitmap ();
653                                                 ii.hbmMask = ii.hbmColor;
654                                                 handle = GDIPlus.CreateIconIndirect (ref ii);
655                                         }
656                                 }
657                                 return handle;
658                         }
659                 }
660 #endif
661                 [Browsable (false)]
662                 public int Height {
663                         get {
664                                 return iconSize.Height;
665                         }
666                 }
667
668                 public Size Size {
669                         get {
670                                 return iconSize;
671                         }
672                 }
673
674                 [Browsable (false)]
675                 public int Width {
676                         get {
677                                 return iconSize.Width;
678                         }
679                 }
680
681                 ~Icon ()
682                 {
683                         Dispose ();
684                 }
685                         
686                 private void InitFromStreamWithSize (Stream stream, int width, int height)
687                 {
688                         //read the icon header
689                         if (stream == null || stream.Length == 0)
690                                 throw new System.ArgumentException ("The argument 'stream' must be a picture that can be used as a Icon", "stream");
691                         
692                         BinaryReader reader = new BinaryReader (stream);
693
694                         //iconDir = new IconDir ();
695                         iconDir.idReserved = reader.ReadUInt16();
696                         if (iconDir.idReserved != 0) //must be 0
697                                 throw new System.ArgumentException ("Invalid Argument", "stream");
698                         
699                         iconDir.idType = reader.ReadUInt16();
700                         if (iconDir.idType != 1) //must be 1
701                                 throw new System.ArgumentException ("Invalid Argument", "stream");
702
703                         ushort dirEntryCount = reader.ReadUInt16();
704                         imageData = new ImageData [dirEntryCount]; 
705                         iconDir.idCount = dirEntryCount; 
706                         iconDir.idEntries = new IconDirEntry [dirEntryCount];
707                         bool sizeObtained = false;
708                         // now read in the IconDirEntry structures
709                         for (int i = 0; i < dirEntryCount; i++) {
710                                 IconDirEntry ide;
711                                 ide.width = reader.ReadByte ();
712                                 ide.height = reader.ReadByte ();
713                                 ide.colorCount = reader.ReadByte ();
714                                 ide.reserved = reader.ReadByte ();
715                                 ide.planes = reader.ReadUInt16 ();
716                                 ide.bitCount = reader.ReadUInt16 ();
717                                 ide.bytesInRes = reader.ReadUInt32 ();
718                                 ide.imageOffset = reader.ReadUInt32 ();
719 #if false
720 Console.WriteLine ("Entry: {0}", i);
721 Console.WriteLine ("\tide.width: {0}", ide.width);
722 Console.WriteLine ("\tide.height: {0}", ide.height);
723 Console.WriteLine ("\tide.colorCount: {0}", ide.colorCount);
724 Console.WriteLine ("\tide.reserved: {0}", ide.reserved);
725 Console.WriteLine ("\tide.planes: {0}", ide.planes);
726 Console.WriteLine ("\tide.bitCount: {0}", ide.bitCount);
727 Console.WriteLine ("\tide.bytesInRes: {0}", ide.bytesInRes);
728 Console.WriteLine ("\tide.imageOffset: {0}", ide.imageOffset);
729 #endif
730                                 // Vista 256x256 icons points directly to a PNG bitmap
731                                 // 256x256 icons are decoded as 0x0 (width and height are encoded as BYTE)
732                                 // and we ignore them just like MS does (at least up to fx 2.0) 
733                                 // Added: storing data so it can be saved back
734                                 if ((ide.width == 0) && (ide.height == 0))
735                                         ide.ignore = true;
736                                 else
737                                         ide.ignore = false;
738
739                                 iconDir.idEntries [i] = ide;
740
741                                 //is this is the best fit??
742                                 if (!sizeObtained) {
743                                         if (((ide.height == height) || (ide.width == width)) && !ide.ignore) {
744                                                 this.id = (ushort) i;
745                                                 sizeObtained = true;
746                                                 this.iconSize.Height = ide.height;
747                                                 this.iconSize.Width = ide.width;
748                                         }
749                                 }
750                         }
751
752                         // throw error if no valid entries found
753                         int valid = 0;
754                         for (int i = 0; i < dirEntryCount; i++) {
755                                 if (!(iconDir.idEntries [i].ignore))
756                                         valid++;
757                         }
758
759                         if (valid == 0) 
760                                 throw new Win32Exception (0, "No valid icon entry were found.");
761
762                         // if we havent found the best match, return the one with the
763                         // largest size. Is this approach correct??
764                         if (!sizeObtained){
765                                 uint largestSize = 0;
766                                 for (int j=0; j<dirEntryCount; j++){ 
767                                         if (iconDir.idEntries [j].bytesInRes >= largestSize && !iconDir.idEntries [j].ignore)   {
768                                                 largestSize = iconDir.idEntries [j].bytesInRes;
769                                                 this.id = (ushort) j;
770                                                 this.iconSize.Height = iconDir.idEntries [j].height;
771                                                 this.iconSize.Width = iconDir.idEntries [j].width;
772                                         }
773                                 }
774                         }
775                         
776                         //now read in the icon data
777                         for (int j = 0; j<dirEntryCount; j++) 
778                         {
779                                 // process ignored into IconDump
780                                 if (iconDir.idEntries [j].ignore) {
781                                         IconDump id = new IconDump ();
782                                         stream.Seek (iconDir.idEntries [j].imageOffset, SeekOrigin.Begin);
783                                         id.data = new byte [iconDir.idEntries [j].bytesInRes];
784                                         stream.Read (id.data, 0, id.data.Length);
785                                         imageData [j] = id;
786                                         continue;
787                                 }
788                                 // standard image
789                                 IconImage iidata = new IconImage();
790                                 BitmapInfoHeader bih = new BitmapInfoHeader();
791                                 stream.Seek (iconDir.idEntries [j].imageOffset, SeekOrigin.Begin);
792                                 byte [] buffer = new byte [iconDir.idEntries [j].bytesInRes];
793                                 stream.Read (buffer, 0, buffer.Length);
794                                 BinaryReader bihReader = new BinaryReader (new MemoryStream(buffer));
795                                 bih.biSize = bihReader.ReadUInt32 ();
796                                 bih.biWidth = bihReader.ReadInt32 ();
797                                 bih.biHeight = bihReader.ReadInt32 ();
798                                 bih.biPlanes = bihReader.ReadUInt16 ();
799                                 bih.biBitCount = bihReader.ReadUInt16 ();
800                                 bih.biCompression = bihReader.ReadUInt32 ();
801                                 bih.biSizeImage = bihReader.ReadUInt32 ();
802                                 bih.biXPelsPerMeter = bihReader.ReadInt32 ();
803                                 bih.biYPelsPerMeter = bihReader.ReadInt32 ();
804                                 bih.biClrUsed = bihReader.ReadUInt32 ();
805                                 bih.biClrImportant = bihReader.ReadUInt32 ();
806 #if false
807 Console.WriteLine ("Entry: {0}", j);
808 Console.WriteLine ("\tbih.biSize: {0}", bih.biSize);
809 Console.WriteLine ("\tbih.biWidth: {0}", bih.biWidth);
810 Console.WriteLine ("\tbih.biHeight: {0}", bih.biHeight);
811 Console.WriteLine ("\tbih.biPlanes: {0}", bih.biPlanes);
812 Console.WriteLine ("\tbih.biBitCount: {0}", bih.biBitCount);
813 Console.WriteLine ("\tbih.biCompression: {0}", bih.biCompression);
814 Console.WriteLine ("\tbih.biSizeImage: {0}", bih.biSizeImage);
815 Console.WriteLine ("\tbih.biXPelsPerMeter: {0}", bih.biXPelsPerMeter);
816 Console.WriteLine ("\tbih.biYPelsPerMeter: {0}", bih.biYPelsPerMeter);
817 Console.WriteLine ("\tbih.biClrUsed: {0}", bih.biClrUsed);
818 Console.WriteLine ("\tbih.biClrImportant: {0}", bih.biClrImportant);
819 #endif
820                                 iidata.iconHeader = bih;
821                                 //Read the number of colors used and corresponding memory occupied by
822                                 //color table. Fill this memory chunk into rgbquad[]
823                                 int numColors;
824                                 switch (bih.biBitCount){
825                                         case 1: numColors = 2;
826                                                 break;
827                                         case 4: numColors = 16;
828                                                 break;
829                                         case 8: numColors = 256;
830                                                 break;
831                                         default: numColors = 0;
832                                                 break;
833                                 }
834                                 
835                                 iidata.iconColors = new uint [numColors];
836                                 for (int i=0; i<numColors; i++)
837                                         iidata.iconColors [i] = bihReader.ReadUInt32 ();
838
839                                 //XOR mask is immediately after ColorTable and its size is 
840                                 //icon height* no. of bytes per line
841                                 
842                                 //icon height is half of BITMAPINFOHEADER.biHeight, since it contains
843                                 //both XOR as well as AND mask bytes
844                                 int iconHeight = bih.biHeight/2;
845                                 
846                                 //bytes per line should should be uint aligned
847                                 int numBytesPerLine = ((((bih.biWidth * bih.biPlanes * bih.biBitCount)+ 31)>>5)<<2);
848                                 
849                                 //Determine the XOR array Size
850                                 int xorSize = numBytesPerLine * iconHeight;
851                                 iidata.iconXOR = new byte [xorSize];
852                                 int nread = bihReader.Read (iidata.iconXOR, 0, xorSize);
853                                 if (nread != xorSize) {
854                                         string msg = Locale.GetText ("{0} data length expected {1}, read {2}", "XOR", xorSize, nread);
855                                         throw new ArgumentException (msg, "stream");
856                                 }
857                                 
858                                 //Determine the AND array size
859                                 numBytesPerLine = (int)((((bih.biWidth) + 31) & ~31) >> 3);
860                                 int andSize = numBytesPerLine * iconHeight;
861                                 iidata.iconAND = new byte [andSize];
862                                 nread = bihReader.Read (iidata.iconAND, 0, andSize);
863                                 if (nread != andSize) {
864                                         string msg = Locale.GetText ("{0} data length expected {1}, read {2}", "AND", andSize, nread);
865                                         throw new ArgumentException (msg, "stream");
866                                 }
867                                 
868                                 imageData [j] = iidata;
869                                 bihReader.Close();
870                         }                       
871
872                         reader.Close();
873                 }
874         }
875 }