2 // System.Windows.Forms.ImageList.cs
5 // Peter Bartok <pbartok@novell.com>
6 // Kornél Pál <http://www.kornelpal.hu/>
8 // Copyright (C) 2004-2005 Novell, Inc.
9 // Copyright (C) 2005 Kornél Pál
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 // Differences between MS.NET ImageList and this implementation:
38 // This is a fully managed image list implementation.
40 // Images are stored as Format32bppArgb internally but ColorDepth is applied
41 // to the colors of images. Images[index] returns a Format32bppArgb copy of
42 // the image so this difference is only internal.
44 // MS.NET has no alpha channel support (except for icons in 32-bit mode with
45 // comctl32.dll version 6.0) but this implementation has full alpha channel
46 // support in 32-bit mode.
48 // Handle should be an HIMAGELIST returned by ImageList_Create. This
49 // implementation uses (IntPtr)(-1) that is a non-zero but invalid handle.
51 // MS.NET creates handle when images are accessed. Add methods are caching the
52 // original images without modification. This implementation adds images in
53 // Add methods so handle is created in Add methods.
55 // MS.NET 1.x shares the same HIMAGELIST between ImageLists that were
56 // initialized from the same ImageListStreamer and doesn't update ImageSize
57 // and ColorDepth that are treated as bugs.
60 using System.Collections;
61 using System.ComponentModel;
63 using System.Drawing.Imaging;
64 using System.Runtime.InteropServices;
66 namespace System.Windows.Forms
68 [DefaultProperty("Images")]
69 [Designer("System.Windows.Forms.Design.ImageListDesigner, " + Consts.AssemblySystem_Design, "System.ComponentModel.Design.IDesigner")]
70 [ToolboxItemFilter("System.Windows.Forms", ToolboxItemFilterType.Allow)]
71 [TypeConverter("System.Windows.Forms.ImageListConverter, " + Consts.AssemblySystem_Windows_Forms)]
72 public sealed class ImageList : System.ComponentModel.Component
74 #region Private Fields
75 private EventHandler recreateHandle;
76 private readonly ImageCollection images;
77 #endregion // Private Fields
80 [Editor("System.Windows.Forms.Design.ImageCollectionEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
81 public sealed class ImageCollection : IList, ICollection, IEnumerable
83 private const int AlphaMask = unchecked((int)0xFF000000);
85 [StructLayout(LayoutKind.Explicit)]
86 private struct ArgbColor
106 class IndexedColorDepths
109 private IndexedColorDepths()
113 internal static readonly ArgbColor[] Palette4Bit;
114 internal static readonly ArgbColor[] Palette8Bit;
115 private static readonly int[] squares;
117 static IndexedColorDepths()
124 bitmap = new Bitmap(1, 1, PixelFormat.Format4bppIndexed);
125 palette = bitmap.Palette.Entries;
128 Palette4Bit = new ArgbColor[count = palette.Length];
129 for (index = 0; index < count; index++)
130 Palette4Bit[index].Argb = palette[index].ToArgb();
132 bitmap = new Bitmap(1, 1, PixelFormat.Format8bppIndexed);
133 palette = bitmap.Palette.Entries;
136 Palette8Bit = new ArgbColor[count = palette.Length];
137 for (index = 0; index < count; index++)
138 Palette8Bit[index].Argb = palette[index].ToArgb();
140 squares = new int[511];
141 for (index = 0; index < 256; index++)
142 squares[255 + index] = squares[255 - index] = index * index;
145 internal static int GetNearestColor(ArgbColor[] palette, int color)
156 count = palette.Length;
157 for (index = 0; index < count; index++)
158 if (palette[index].Argb == color)
161 red = unchecked((int)(unchecked((uint)color) >> 16) & 0xFF);
162 green = unchecked((int)(unchecked((uint)color) >> 8) & 0xFF);
164 nearestColor = AlphaMask;
165 minDistance = int.MaxValue;
167 for (index = 0; index < count; index++)
168 if ((distance = squares[255 + palette[index].Red - red] + squares[255 + palette[index].Green - green] + squares[255 + palette[index].Blue - blue]) < minDistance) {
169 nearestColor = palette[index].Argb;
170 minDistance = distance;
178 #region ImageCollection Private Fields
179 private ColorDepth colorDepth = ColorDepth.Depth8Bit;
180 private Color transparentColor = Color.Transparent;
181 private Size imageSize = new Size(16, 16);
182 private bool handleCreated;
183 private readonly ArrayList list = new ArrayList();
184 private readonly ImageList owner;
185 #endregion // ImageCollection Private Fields
187 #region ImageCollection Internal Constructors
188 // For use in ImageList
189 internal ImageCollection(ImageList owner)
193 #endregion // ImageCollection Internal Constructor
195 #region ImageCollection Internal Instance Properties
196 // For use in ImageList
197 internal ColorDepth ColorDepth {
199 return this.colorDepth;
203 if (!Enum.IsDefined(typeof(ColorDepth), value))
204 throw new InvalidEnumArgumentException("value", (int)value, typeof(ColorDepth));
206 if (this.colorDepth != value) {
207 this.colorDepth = value;
209 for (int i = 0; i < list.Count; i++) {
210 list [i] = ReduceColorDepth ((Bitmap) list [i]);
212 owner.OnRecreateHandle();
218 // For use in ImageList
219 internal IntPtr Handle {
221 this.handleCreated = true;
226 // For use in ImageList
227 internal bool HandleCreated {
229 return this.handleCreated;
233 // For use in ImageList
234 internal Size ImageSize {
236 return this.imageSize;
240 if (value.Width < 1 || value.Width > 256 || value.Height < 1 || value.Height > 256)
241 throw new ArgumentException("ImageSize.Width and Height must be between 1 and 256", "value");
243 if (this.imageSize != value) {
244 this.imageSize = value;
246 for (int i = 0; i < list.Count; i++) {
247 list [i] = new Bitmap ((Image) list [i], imageSize);
249 owner.OnRecreateHandle();
255 // For use in ImageList
256 internal ImageListStreamer ImageStream {
258 return list.Count == 0 ? null : new ImageListStreamer(this);
263 Image[] streamImages;
267 this.handleCreated = false;
271 else if ((streamImages = value.Images) != null) {
272 this.handleCreated = true;
275 for (index = 0; index < streamImages.Length; index++)
276 list.Add((Image)streamImages[index].Clone());
278 this.imageSize = value.ImageSize;
279 this.colorDepth = value.ColorDepth;
281 // Event is raised even when handle was not created yet.
282 owner.OnRecreateHandle();
288 // For use in ImageList
289 internal Color TransparentColor {
291 return this.transparentColor;
295 this.transparentColor = value;
298 #endregion // ImageCollection Internal Instance Properties
300 #region ImageCollection Public Instance Properties
310 return list.Count == 0;
314 public bool IsReadOnly {
321 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
322 public Image this[int index] {
324 return (Image)GetImage(index).Clone();
328 if (index < 0 || index >= list.Count)
329 throw new ArgumentOutOfRangeException("index");
331 list[index] = CreateImage(value, this.transparentColor);
334 #endregion // ImageCollection Public Instance Properties
336 #region ImageCollection Private Instance Methods
337 private Image CreateImage(Image value, Color transparentColor)
343 ImageAttributes imageAttributes;
346 throw new ArgumentNullException("value");
348 if (!(value is Bitmap))
349 throw new ArgumentException("Image must be a Bitmap.");
351 if (transparentColor.A == 0)
352 imageAttributes = null;
354 imageAttributes = new ImageAttributes();
355 imageAttributes.SetColorKey(transparentColor, transparentColor);
358 bitmap = new Bitmap(width = this.imageSize.Width, height = this.imageSize.Height, PixelFormat.Format32bppArgb);
359 graphics = Graphics.FromImage(bitmap);
360 graphics.DrawImage(value, new Rectangle(0, 0, width, height), 0, 0, value.Width, value.Height, GraphicsUnit.Pixel, imageAttributes);
363 return ReduceColorDepth(bitmap);
366 private unsafe Image ReduceColorDepth(Bitmap bitmap)
376 BitmapData bitmapData;
379 if (this.colorDepth < ColorDepth.Depth32Bit) {
380 bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
382 linePtr = (byte*)bitmapData.Scan0;
383 height = bitmapData.Height;
384 widthBytes = bitmapData.Width << 2;
385 stride = bitmapData.Stride;
387 if (this.colorDepth < ColorDepth.Depth16Bit) {
388 palette = this.colorDepth < ColorDepth.Depth8Bit ? IndexedColorDepths.Palette4Bit : IndexedColorDepths.Palette8Bit;
390 for (line = 0; line < height; line++) {
391 lineEndPtr = linePtr + widthBytes;
392 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
393 *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : IndexedColorDepths.GetNearestColor(palette, pixel | AlphaMask);
397 else if (this.colorDepth < ColorDepth.Depth24Bit) {
398 for (line = 0; line < height; line++) {
399 lineEndPtr = linePtr + widthBytes;
400 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
401 *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : (pixel & 0x00F8F8F8) | AlphaMask;
406 for (line = 0; line < height; line++) {
407 lineEndPtr = linePtr + widthBytes;
408 for (pixelPtr = linePtr; pixelPtr < lineEndPtr; pixelPtr += 4)
409 *(int*)pixelPtr = ((pixel = *(int*)pixelPtr) & AlphaMask) == 0 ? 0x00000000 : pixel | AlphaMask;
415 bitmap.UnlockBits(bitmapData);
421 #endregion // ImageCollection Private Instance Methods
423 #region ImageCollection Internal Instance Methods
424 // For use in ImageList
425 internal Image GetImage(int index)
427 if (index < 0 || index >= list.Count)
428 throw new ArgumentOutOfRangeException("index");
430 return (Image)list[index];
433 // For use in ImageListStreamer
434 internal Image[] ToArray()
436 Image[] images = new Image[list.Count];
441 #endregion // ImageCollection Internal Instance Methods
443 #region ImageCollection Public Instance Methods
444 public void Add(Icon value)
452 throw new ArgumentNullException("value");
454 this.handleCreated = true;
456 bitmap = new Bitmap(width = this.imageSize.Width, height = this.imageSize.Height, PixelFormat.Format32bppArgb);
457 graphics = Graphics.FromImage(bitmap);
458 graphics.DrawIcon(value, new Rectangle(0, 0, width, height));
461 list.Add(ReduceColorDepth(bitmap));
464 public void Add(Image value)
466 Add(value, this.transparentColor);
469 public int Add(Image value, Color transparentColor)
471 this.handleCreated = true;
472 return list.Add(CreateImage(value, transparentColor));
475 public int AddStrip(Image value)
485 ImageAttributes imageAttributes;
488 throw new ArgumentNullException("value");
490 if ((imageWidth = value.Width) == 0 || (imageWidth % (width = this.imageSize.Width)) != 0)
491 throw new ArgumentException("Width of image strip must be a positive multiple of ImageSize.Width.", "value");
493 if (value.Height != (height = this.imageSize.Height))
494 throw new ArgumentException("Height of image strip must be equal to ImageSize.Height.", "value");
496 if (!(value is Bitmap))
497 throw new ArgumentException("Image must be a Bitmap.");
499 this.handleCreated = true;
501 imageRect = new Rectangle(0, 0, width, height);
502 if (this.transparentColor.A == 0)
503 imageAttributes = null;
505 imageAttributes = new ImageAttributes();
506 imageAttributes.SetColorKey(this.transparentColor, this.transparentColor);
510 for (imageX = 0; imageX < imageWidth; imageX += width) {
511 bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
512 graphics = Graphics.FromImage(bitmap);
513 graphics.DrawImage(value, imageRect, imageX, 0, width, height, GraphicsUnit.Pixel, imageAttributes);
516 list.Add(ReduceColorDepth(bitmap));
527 public bool Contains(Image image)
529 throw new NotSupportedException();
533 public IEnumerator GetEnumerator()
535 Image[] images = new Image[list.Count];
538 for (index = 0; index < images.Length; index++)
539 images[index] = (Image)((Image)list[index]).Clone();
541 return images.GetEnumerator();
544 public int IndexOf(Image image)
546 throw new NotSupportedException();
549 public void Remove(Image image)
551 throw new NotSupportedException();
554 public void RemoveAt(int index)
556 if (index < 0 || index >= list.Count)
557 throw new ArgumentOutOfRangeException("index");
559 list.RemoveAt(index);
561 #endregion // ImageCollection Public Instance Methods
563 #region ImageCollection Interface Properties
564 object IList.this[int index] {
570 if (!(value is Image))
571 throw new ArgumentException("value");
573 this[index] = (Image)value;
577 bool IList.IsFixedSize {
583 bool ICollection.IsSynchronized {
589 object ICollection.SyncRoot {
594 #endregion // ImageCollection Interface Properties
596 #region ImageCollection Interface Methods
597 int IList.Add(object value)
601 if (!(value is Image))
602 throw new ArgumentException("value");
605 this.Add((Image)value);
609 bool IList.Contains(object value)
611 return value is Image ? this.Contains((Image)value) : false;
614 int IList.IndexOf(object value)
616 return value is Image ? this.IndexOf((Image)value) : -1;
619 void IList.Insert(int index, object value)
621 throw new NotSupportedException();
624 void IList.Remove(object value)
627 this.Remove((Image)value);
630 void ICollection.CopyTo(Array array, int index)
634 for (imageIndex = 0; imageIndex < this.Count; imageIndex++)
635 array.SetValue(this[index], index++);
637 #endregion // ImageCollection Interface Methods
639 #endregion // Sub-classes
641 #region Public Constructors
644 images = new ImageCollection(this);
647 public ImageList(System.ComponentModel.IContainer container) : this()
651 #endregion // Public Constructors
653 private void OnRecreateHandle()
655 if (recreateHandle != null)
656 recreateHandle(this, EventArgs.Empty);
659 #region Public Instance Properties
660 [DefaultValue(ColorDepth.Depth8Bit)]
661 public ColorDepth ColorDepth {
663 return images.ColorDepth;
667 images.ColorDepth = value;
672 [EditorBrowsable(EditorBrowsableState.Advanced)]
673 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
674 public IntPtr Handle {
676 return images.Handle;
681 [EditorBrowsable(EditorBrowsableState.Advanced)]
682 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
683 public bool HandleCreated {
685 return images.HandleCreated;
690 [MergableProperty(false)]
691 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
692 public ImageCollection Images {
699 public Size ImageSize {
701 return images.ImageSize;
705 images.ImageSize = value;
711 [EditorBrowsable(EditorBrowsableState.Advanced)]
712 public ImageListStreamer ImageStream {
714 return images.ImageStream;
718 images.ImageStream = value;
722 public Color TransparentColor {
724 return images.TransparentColor;
728 images.TransparentColor = value;
731 #endregion // Public Instance Properties
733 #region Public Instance Methods
734 public void Draw(Graphics g, Point pt, int index)
736 this.Draw(g, pt.X, pt.Y, index);
739 public void Draw(Graphics g, int x, int y, int index)
741 g.DrawImage(images.GetImage(index), x, y);
744 public void Draw(Graphics g, int x, int y, int width, int height, int index)
746 g.DrawImage(images.GetImage(index), x, y, width, height);
749 public override string ToString()
751 return base.ToString() + " Images.Count: " + images.Count.ToString() + ", ImageSize: " + images.ImageSize.ToString();
753 #endregion // Public Instance Methods
755 #region Protected Instance Methods
756 protected override void Dispose(bool disposing)
758 base.Dispose(disposing);
760 #endregion // Protected Instance Methods
764 [EditorBrowsable(EditorBrowsableState.Advanced)]
765 public event EventHandler RecreateHandle {
767 recreateHandle += value;
771 recreateHandle -= value;