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