New test.
[mono.git] / mcs / class / System.Drawing / System.Drawing / Icon.cs
1 //
2 // System.Drawing.Icon.cs
3 //
4 // Authors:
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)
9 //
10 // Copyright (C) 2002 Ximian, Inc. http://www.ximian.com
11 // Copyright (C) 2004-2006 Novell, Inc (http://www.novell.com)
12 //
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:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
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.
31 //
32
33 using System;
34 using System.Drawing.Imaging;
35 using System.IO;
36 using System.Runtime.Serialization;
37 using System.Runtime.InteropServices;
38 using System.ComponentModel;
39
40 namespace System.Drawing
41 {
42 #if !NET_2_0
43         [ComVisible (false)] 
44 #endif 
45         [Serializable]  
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
49         {
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 
60                 }; 
61
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
68                 };
69                 
70                 [StructLayout(LayoutKind.Sequential)]
71                 internal struct BitmapInfoHeader {
72                         internal uint   biSize; 
73                         internal int    biWidth; 
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; 
83                 };
84
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
91                 };      
92
93                 private Size iconSize;
94                 private IntPtr winHandle = IntPtr.Zero;
95                 private IconDir iconDir;
96                 private ushort id;
97                 private IconImage [] imageData;
98                 bool destroyIcon = true;
99                         
100                 private Icon ()
101                 {
102                 }
103 #if INTPTR_SUPPORTED
104                 [MonoTODO ("Implement fully")]
105                 private Icon (IntPtr handle)
106                 {
107                         this.winHandle = handle;
108
109                         IconInfo ii;
110                         GDIPlus.GetIconInfo (winHandle, out ii);
111                         if (ii.IsIcon) {
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);
114                         }
115                         else {
116                                 throw new NotImplementedException ();
117                         }
118
119                         this.destroyIcon = false;
120                 }
121 #endif
122                 public Icon (Icon original, int width, int height) : this (original, new Size(width, height))
123                 {                       
124                 }
125
126                 public Icon (Icon original, Size size)
127                 {
128                         this.iconSize = size;
129                         this.winHandle = original.winHandle;
130                         this.iconDir = original.iconDir;
131                         this.imageData = original.imageData;
132                         
133                         int count = iconDir.idCount;
134                         bool sizeObtained = false;
135                         for (int i=0; i<count; i++){
136                                 IconDirEntry ide = iconDir.idEntries [i];
137                                 if (!sizeObtained)   
138                                         if (ide.height==size.Height && ide.width==size.Width) {
139                                                 this.id = (ushort) i;
140                                                 sizeObtained = true;
141                                                 this.iconSize.Height = ide.height;
142                                                 this.iconSize.Width = ide.width;
143                                                 break;
144                                         }
145                         }
146
147                         if (!sizeObtained){
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;
155                                         }
156                                 }
157                         }
158                 }
159
160                 public Icon (Stream stream) : this (stream, 32, 32) 
161                 {
162                 }
163
164                 public Icon (Stream stream, int width, int height)
165                 {
166                         InitFromStreamWithSize (stream, width, height);
167                 }
168
169                 public Icon (string fileName) : this (new FileStream (fileName, FileMode.Open))
170                 {                       
171                 }
172
173                 public Icon (Type type, string resource)
174                 {
175                         using (Stream s = type.Assembly.GetManifestResourceStream (type, resource)) {
176                                 if (s == null) {
177                                         throw new FileNotFoundException ("Resource name was not found: `" + resource + "'");
178                                 }
179                                 InitFromStreamWithSize (s, 32, 32);             // 32x32 is default
180                         }
181                 }
182
183                 private Icon (SerializationInfo info, StreamingContext context)
184                 {
185                         MemoryStream dataStream = null;
186                         int width=0;
187                         int height=0;
188                         foreach (SerializationEntry serEnum in info) {
189                                 if (String.Compare(serEnum.Name, "IconData", true) == 0) {
190                                         dataStream = new MemoryStream ((byte []) serEnum.Value);
191                                 }
192                                 if (String.Compare(serEnum.Name, "IconSize", true) == 0) {
193                                         Size iconSize = (Size) serEnum.Value;
194                                         width = iconSize.Width;
195                                         height = iconSize.Height;
196                                 }
197                         }
198                         if ((dataStream != null) && (width == height)) {
199                                 dataStream.Seek (0, SeekOrigin.Begin);
200                                 InitFromStreamWithSize (dataStream, width, height);
201                         }
202                 }
203
204                 void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
205                 {
206                         MemoryStream ms = new MemoryStream ();
207                         Save (ms);
208                         info.AddValue ("IconSize", this.Size, typeof (Size));
209                         info.AddValue ("IconData", ms.ToArray ());
210                 }
211
212 #if NET_2_0
213                 public Icon (Stream stream, Size size) : this (stream, size.Width, size.Height) {}
214                 
215                 public Icon (string fileName, int width, int height): 
216                         this (new FileStream (fileName, FileMode.Open), width, height) {}
217         
218                 public Icon (string fileName, Size size) : 
219                         this (new FileStream (fileName, FileMode.Open), size) {}
220
221                 [MonoTODO]
222                 public static Icon ExtractAssociatedIcon (string filePath)
223                 {
224                         throw new NotImplementedException ();
225                 }       
226         
227 #endif
228
229                 public void Dispose ()
230                 {
231 #if !TARGET_JVM
232                         DisposeIcon ();
233                         GC.SuppressFinalize(this);
234 #endif
235                 }
236 #if !TARGET_JVM
237                 void DisposeIcon ()
238                 {
239                         if (winHandle ==IntPtr.Zero)
240                                 return;
241
242                         if (destroyIcon) {
243                                 //TODO: will have to call some win32 icon stuff
244                                 winHandle = IntPtr.Zero;
245                         }
246                 }
247 #endif
248
249                 public object Clone ()
250                 {
251                         return new Icon (this, this.Width, this.Height);
252                 }
253 #if INTPTR_SUPPORTED
254                 public static Icon FromHandle (IntPtr handle)
255                 {
256                         if (handle == IntPtr.Zero)
257                                 throw new ArgumentException ("handle");
258
259                         return new Icon (handle);
260                 }
261 #else
262                 public static Icon FromHandle (IntPtr handle)
263                 {
264                         throw new NotImplementedException ();
265                 }
266 #endif
267                 public void Save (Stream outputStream)
268                 {
269                         if (iconDir.idEntries!=null){
270                                 BinaryWriter bw = new BinaryWriter (outputStream);
271                                 //write icondir
272                                 bw.Write (iconDir.idReserved);
273                                 bw.Write (iconDir.idType);
274                                 ushort count = iconDir.idCount;
275                                 bw.Write (count);
276                                 
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);                             
288                                 }
289                                 
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);
304
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]);
309
310                                         //now write XOR Mask
311                                         bw.Write (imageData [i].iconXOR);
312                                         
313                                         //now write AND Mask
314                                         bw.Write (imageData [i].iconAND);
315                                 }
316                                 bw.Flush();                             
317                         }
318                 }
319
320                 public Bitmap ToBitmap() {
321                         IconImage               ii;
322                         BitmapInfoHeader        bih;
323                         int                     ncolors;
324                         Bitmap                  bmp;
325                         BitmapData              bits;
326                         ColorPalette            pal;
327                         int                     biHeight;
328                         int                     bytesPerLine;
329
330                         if (imageData == null) {
331                                 return new Bitmap(32, 32);
332                         }
333
334                         ii = imageData[this.id];
335                         bih = ii.iconHeader;
336                         biHeight = bih.biHeight / 2;
337
338                         ncolors = (int)bih.biClrUsed;
339                         if (ncolors == 0) {
340                                 if (bih.biBitCount < 24) {
341                                         ncolors = (int)(1 << bih.biBitCount);
342                                 }
343                         }
344
345                         switch(bih.biBitCount) {
346                                 case 1: {       // Monochrome
347                                         bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format1bppIndexed);
348                                         break;
349                                 }
350
351                                 case 4: {       // 4bpp
352                                         bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format4bppIndexed);
353                                         break;
354                                 }
355
356                                 case 8: {       // 8bpp
357                                         bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format8bppIndexed);
358                                         break;
359                                 }
360
361                                 case 24: {
362                                         bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format24bppRgb);
363                                         break;
364                                 }
365                                 case 32: {      // 32bpp
366                                         bmp = new Bitmap(bih.biWidth, biHeight, PixelFormat.Format32bppArgb);
367                                         break;
368                                 }
369
370                                 default: {
371                                         throw new Exception("Unexpected number of bits:" + bih.biBitCount.ToString());
372                                 }
373                         }
374
375                         if (bih.biBitCount < 24) {
376                                 pal = bmp.Palette;                              // Managed palette
377
378                                 for (int i = 0; i < ii.iconColors.Length; i++) {
379                                         pal.Entries[i] = Color.FromArgb((int)ii.iconColors[i] | unchecked((int)0xff000000));
380                                 }
381                                 bmp.Palette = pal;
382                         }
383
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);
386
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);
389                         }
390                         
391                         bmp.UnlockBits(bits);
392
393                         bmp = new Bitmap (bmp);// This makes a 32bpp image out of an indexed one
394
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);
402                                                 }
403                                         }
404                                 }
405                         }
406
407                         return bmp;
408                 }
409
410                 public override string ToString ()
411                 {
412                         //is this correct, this is what returned by .Net
413                         return "<Icon>";                        
414                 }
415
416                 [Browsable (false)]
417                 public IntPtr Handle {
418                         get { 
419                                 return winHandle;
420                         }
421                 }
422
423                 [Browsable (false)]
424                 public int Height {
425                         get {
426                                 return iconSize.Height;
427                         }
428                 }
429
430                 public Size Size {
431                         get {
432                                 return iconSize;
433                         }
434                 }
435
436                 [Browsable (false)]
437                 public int Width {
438                         get {
439                                 return iconSize.Width;
440                         }
441                 }
442
443 #if !TARGET_JVM
444                 ~Icon ()
445                 {
446                         DisposeIcon ();
447                 }
448 #endif
449                         
450                 private void InitFromStreamWithSize (Stream stream, int width, int height)
451                 {
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");
455                         
456                         BinaryReader reader = new BinaryReader (stream);
457
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");
462                         
463                         iconDir.idType = reader.ReadUInt16();
464                         if (iconDir.idType != 1) //must be 1
465                                 throw new System.ArgumentException ("Invalid Argument", "stream");
466
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++){
474                                 IconDirEntry ide;
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??
485                                 if (!sizeObtained)
486                                         if (ide.height==height && ide.width==width) {
487                                                 this.id = (ushort) i;
488                                                 sizeObtained = true;
489                                                 this.iconSize.Height = ide.height;
490                                                 this.iconSize.Width = ide.width;
491                                         }                       
492                         }
493                         //if we havent found the best match, return the one with the
494                         //largest size. Is this approach correct??
495                         if (!sizeObtained){
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;
503                                         }
504                                 }
505                         }
506                         
507                         //now read in the icon data
508                         for (int j = 0; j<dirEntryCount; j++)
509                         {
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 ();
527
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[]
531                                 int numColors;
532                                 switch (bih.biBitCount){
533                                         case 1: numColors = 2;
534                                                 break;
535                                         case 4: numColors = 16;
536                                                 break;
537                                         case 8: numColors = 256;
538                                                 break;
539                                         default: numColors = 0;
540                                                 break;
541                                 }
542                                 
543                                 iidata.iconColors = new uint [numColors];
544                                 for (int i=0; i<numColors; i++)
545                                         iidata.iconColors [i] = bihReader.ReadUInt32 ();
546
547                                 //XOR mask is immediately after ColorTable and its size is 
548                                 //icon height* no. of bytes per line
549                                 
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;
553                                 
554                                 //bytes per line should should be uint aligned
555                                 int numBytesPerLine = ((((bih.biWidth * bih.biPlanes * bih.biBitCount)+ 31)>>5)<<2);
556                                 
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");
563                                 
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");
571                                 
572                                 imageData [j] = iidata;
573                                 bihReader.Close();
574                         }                       
575
576                         reader.Close();
577                 }
578         }
579 }