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