1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.Drawing; 5 using System.IO; 6 using FreeImageAPI.Metadata; 7 using System.Runtime.InteropServices; 8 using System.Diagnostics; 9 10 namespace FreeImageAPI 11 { 12 /// <summary> 13 /// Provides methods for working with the standard bitmap palette. 14 /// </summary> 15 public sealed class Palette : MemoryArray<RGBQUAD> 16 { 17 [DebuggerBrowsable(DebuggerBrowsableState.Never)] 18 private GCHandle paletteHandle; 19 20 [DebuggerBrowsable(DebuggerBrowsableState.Never)] 21 private RGBQUAD[] array; 22 23 /// <summary> 24 /// Initializes a new instance for the given FreeImage bitmap. 25 /// </summary> 26 /// <param name="dib">Handle to a FreeImage bitmap.</param> 27 /// <exception cref="ArgumentNullException"><paramref name="dib"/> is null.</exception> 28 /// <exception cref="ArgumentException"><paramref name="dib"/> is not 29 /// <see cref="FREE_IMAGE_TYPE.FIT_BITMAP"/><para/>-or-<para/> 30 /// <paramref name="dib"/> has more than 8bpp.</exception> Palette(FIBITMAP dib)31 public Palette(FIBITMAP dib) 32 : base(FreeImage.GetPalette(dib), (int)FreeImage.GetColorsUsed(dib)) 33 { 34 if (dib.IsNull) 35 { 36 throw new ArgumentNullException("dib"); 37 } 38 if (FreeImage.GetImageType(dib) != FREE_IMAGE_TYPE.FIT_BITMAP) 39 { 40 throw new ArgumentException("dib"); 41 } 42 if (FreeImage.GetBPP(dib) > 8u) 43 { 44 throw new ArgumentException("dib"); 45 } 46 } 47 48 /// <summary> 49 /// Initializes a new instance for the given FITAG that contains 50 /// a palette. 51 /// </summary> 52 /// <param name="tag">The tag containing the palette.</param> 53 /// <exception cref="ArgumentNullException"><paramref name="tag"/> is null.</exception> 54 /// <exception cref="ArgumentException"><paramref name="tag"/> is not 55 /// <see cref="FREE_IMAGE_MDTYPE.FIDT_PALETTE"/>.</exception> Palette(FITAG tag)56 public Palette(FITAG tag) 57 : base(FreeImage.GetTagValue(tag), (int)FreeImage.GetTagCount(tag)) 58 { 59 if (FreeImage.GetTagType(tag) != FREE_IMAGE_MDTYPE.FIDT_PALETTE) 60 { 61 throw new ArgumentException("tag"); 62 } 63 } 64 65 /// <summary> 66 /// Initializes a new instance for the given MetadataTag that contains 67 /// a palette. 68 /// </summary> 69 /// <param name="tag">The tag containing the palette.</param> 70 /// <exception cref="ArgumentNullException"><paramref name="dib"/> is null.</exception> 71 /// <exception cref="ArgumentException"><paramref name="tag"/> is not 72 /// <see cref="FREE_IMAGE_MDTYPE.FIDT_PALETTE"/>.</exception> Palette(MetadataTag tag)73 public Palette(MetadataTag tag) 74 : base(FreeImage.GetTagValue(tag.tag), (int)tag.Count) 75 { 76 if (FreeImage.GetTagType(tag) != FREE_IMAGE_MDTYPE.FIDT_PALETTE) 77 { 78 throw new ArgumentException("tag"); 79 } 80 } 81 82 /// <summary> 83 /// Initializes a new instance for the given array of <see cref="RGBQUAD"/> that contains 84 /// a palette. 85 /// </summary> 86 /// <param name="palette">A RGBQUAD array containing the palette data to initialize this instance.</param> Palette(RGBQUAD[] palette)87 public Palette(RGBQUAD[] palette) 88 { 89 unsafe 90 { 91 this.array = (RGBQUAD[])palette.Clone(); 92 this.paletteHandle = GCHandle.Alloc(array, GCHandleType.Pinned); 93 94 base.baseAddress = (byte*)this.paletteHandle.AddrOfPinnedObject(); 95 base.length = (int)this.array.Length; 96 97 // Create an array containing a single element. 98 // Due to the fact, that it's not possible to create pointers 99 // of generic types, an array is used to obtain the memory 100 // address of an element of T. 101 base.buffer = new RGBQUAD[1]; 102 // The array is pinned immediately to prevent the GC from 103 // moving it to a different position in memory. 104 base.handle = GCHandle.Alloc(buffer, GCHandleType.Pinned); 105 // The array and its content have beed pinned, so that its address 106 // can be safely requested and stored for the whole lifetime 107 // of the instace. 108 base.ptr = (byte*)base.handle.AddrOfPinnedObject(); 109 } 110 } 111 112 /// <summary> 113 /// Initializes a new instance for the given array of <see cref="Color"/> that contains 114 /// a palette. 115 /// </summary> 116 /// <param name="palette">A Color array containing the palette data to initialize this instance.</param> Palette(Color[] palette)117 public Palette(Color[] palette) 118 : this(RGBQUAD.ToRGBQUAD(palette)) 119 { 120 } 121 122 /// <summary> 123 /// Initializes a new instance with the specified size. 124 /// </summary> 125 /// <param name="size">The size of the palette.</param> Palette(int size)126 public Palette(int size) 127 : this(new RGBQUAD[size]) 128 { 129 } 130 131 /// <summary> 132 /// Gets or sets the palette through an array of <see cref="RGBQUAD"/>. 133 /// </summary> 134 public RGBQUAD[] AsArray 135 { 136 get 137 { 138 return Data; 139 } 140 set 141 { 142 Data = value; 143 } 144 } 145 146 /// <summary> 147 /// Get an array of <see cref="System.Drawing.Color"/> that the block of memory represents. 148 /// This property is used for internal palette operations. 149 /// </summary> 150 internal unsafe Color[] ColorData 151 { 152 get 153 { 154 EnsureNotDisposed(); 155 Color[] data = new Color[length]; 156 for (int i = 0; i < length; i++) 157 { 158 data[i] = Color.FromArgb((int)(((uint*)baseAddress)[i] | 0xFF000000)); 159 } 160 return data; 161 } 162 } 163 164 /// <summary> 165 /// Returns the palette as an array of <see cref="RGBQUAD"/>. 166 /// </summary> 167 /// <returns>The palette as an array of <see cref="RGBQUAD"/>.</returns> ToArray()168 public RGBQUAD[] ToArray() 169 { 170 return Data; 171 } 172 173 /// <summary> 174 /// Creates a linear palette based on the provided <paramref name="color"/>. 175 /// </summary> 176 /// <param name="color">The <see cref="System.Drawing.Color"/> used to colorize the palette.</param> 177 /// <remarks> 178 /// Only call this method on linear palettes. 179 /// </remarks> Colorize(Color color)180 public void Colorize(Color color) 181 { 182 Colorize(color, 0.5d); 183 } 184 185 /// <summary> 186 /// Creates a linear palette based on the provided <paramref name="color"/>. 187 /// </summary> 188 /// <param name="color">The <see cref="System.Drawing.Color"/> used to colorize the palette.</param> 189 /// <param name="splitSize">The position of the color within the new palette. 190 /// 0 < <paramref name="splitSize"/> < 1.</param> 191 /// <remarks> 192 /// Only call this method on linear palettes. 193 /// </remarks> Colorize(Color color, double splitSize)194 public void Colorize(Color color, double splitSize) 195 { 196 Colorize(color, (int)(length * splitSize)); 197 } 198 199 /// <summary> 200 /// Creates a linear palette based on the provided <paramref name="color"/>. 201 /// </summary> 202 /// <param name="color">The <see cref="System.Drawing.Color"/> used to colorize the palette.</param> 203 /// <param name="splitSize">The position of the color within the new palette. 204 /// 0 < <paramref name="splitSize"/> < <see cref="MemoryArray<T>.Length"/>.</param> 205 /// <remarks> 206 /// Only call this method on linear palettes. 207 /// </remarks> Colorize(Color color, int splitSize)208 public void Colorize(Color color, int splitSize) 209 { 210 EnsureNotDisposed(); 211 if (splitSize < 1 || splitSize >= length) 212 { 213 throw new ArgumentOutOfRangeException("splitSize"); 214 } 215 216 RGBQUAD[] pal = new RGBQUAD[length]; 217 218 double red = color.R; 219 double green = color.G; 220 double blue = color.B; 221 222 int i = 0; 223 double r, g, b; 224 225 r = red / splitSize; 226 g = green / splitSize; 227 b = blue / splitSize; 228 229 for (; i <= splitSize; i++) 230 { 231 pal[i].rgbRed = (byte)(i * r); 232 pal[i].rgbGreen = (byte)(i * g); 233 pal[i].rgbBlue = (byte)(i * b); 234 } 235 236 r = (255 - red) / (length - splitSize); 237 g = (255 - green) / (length - splitSize); 238 b = (255 - blue) / (length - splitSize); 239 240 for (; i < length; i++) 241 { 242 pal[i].rgbRed = (byte)(red + ((i - splitSize) * r)); 243 pal[i].rgbGreen = (byte)(green + ((i - splitSize) * g)); 244 pal[i].rgbBlue = (byte)(blue + ((i - splitSize) * b)); 245 } 246 247 Data = pal; 248 } 249 250 /// <summary> 251 /// Creates a linear grayscale palette. 252 /// </summary> CreateGrayscalePalette()253 public void CreateGrayscalePalette() 254 { 255 Colorize(Color.White, length - 1); 256 } 257 258 /// <summary> 259 /// Creates a linear grayscale palette. 260 /// </summary> 261 /// <param name="inverse"><b>true</b> to create an inverse grayscale palette.</param> CreateGrayscalePalette(bool inverse)262 public void CreateGrayscalePalette(bool inverse) 263 { 264 Colorize(Color.White, inverse ? 0 : length - 1); 265 } 266 267 /// <summary> 268 /// Creates a linear palette with the specified <see cref="Color"/>. 269 /// </summary> 270 /// <remarks> 271 /// A linear grayscale palette contains all shades of colors from 272 /// black to white. This method creates a similar palette with the white 273 /// color being replaced by the specified color. 274 /// </remarks> 275 /// <param name="color">The <see cref="Color"/> used to create the palette.</param> 276 /// <param name="inverse"><b>true</b> to create an inverse palette.</param> CreateGrayscalePalette(Color color, bool inverse)277 public void CreateGrayscalePalette(Color color, bool inverse) 278 { 279 Colorize(color, inverse ? 0 : length - 1); 280 } 281 282 /// <summary> 283 /// Reverses the palette. 284 /// </summary> Reverse()285 public void Reverse() 286 { 287 EnsureNotDisposed(); 288 if (array != null) 289 { 290 Array.Reverse(array); 291 } 292 else 293 { 294 RGBQUAD[] localArray = Data; 295 Array.Reverse(localArray); 296 Data = localArray; 297 } 298 } 299 300 /// <summary> 301 /// Copies the values from the specified <see cref="Palette"/> to this instance. 302 /// </summary> 303 /// <param name="palette">The palette to copy from.</param> 304 /// <exception cref="ArgumentNullException"> 305 /// <paramref name="palette"/> is a null reference.</exception> CopyFrom(Palette palette)306 public void CopyFrom(Palette palette) 307 { 308 EnsureNotDisposed(); 309 if (palette == null) 310 { 311 throw new ArgumentNullException("palette"); 312 } 313 CopyFrom(palette.Data, 0, 0, Math.Min(palette.Length, this.Length)); 314 } 315 316 /// <summary> 317 /// Copies the values from the specified <see cref="Palette"/> to this instance, 318 /// starting at the specified <paramref name="offset"/>. 319 /// </summary> 320 /// <param name="palette">The palette to copy from.</param> 321 /// <param name="offset">The position in this instance where the values 322 /// will be copied to.</param> 323 /// <exception cref="ArgumentNullException"> 324 /// <paramref name="palette"/> is a null reference.</exception> 325 /// <exception cref="ArgumentOutOfRangeException"> 326 /// <paramref name="offset"/> is outside the range of valid indexes.</exception> CopyFrom(Palette palette, int offset)327 public void CopyFrom(Palette palette, int offset) 328 { 329 EnsureNotDisposed(); 330 CopyFrom(palette.Data, 0, offset, Math.Min(palette.Length, this.Length - offset)); 331 } 332 333 /// <summary> 334 /// Saves this <see cref="Palette"/> to the specified file. 335 /// </summary> 336 /// <param name="filename"> 337 /// A string that contains the name of the file to which to save this <see cref="Palette"/>. 338 /// </param> Save(string filename)339 public void Save(string filename) 340 { 341 using (Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write)) 342 { 343 Save(stream); 344 } 345 } 346 347 /// <summary> 348 /// Saves this <see cref="Palette"/> to the specified stream. 349 /// </summary> 350 /// <param name="stream"> 351 /// The <see cref="Stream"/> where the image will be saved. 352 /// </param> Save(Stream stream)353 public void Save(Stream stream) 354 { 355 Save(new BinaryWriter(stream)); 356 } 357 358 /// <summary> 359 /// Saves this <see cref="Palette"/> using the specified writer. 360 /// </summary> 361 /// <param name="writer"> 362 /// The <see cref="BinaryWriter"/> used to save the image. 363 /// </param> Save(BinaryWriter writer)364 public void Save(BinaryWriter writer) 365 { 366 EnsureNotDisposed(); 367 writer.Write(ToByteArray()); 368 } 369 370 /// <summary> 371 /// Loads a palette from the specified file. 372 /// </summary> 373 /// <param name="filename">The name of the palette file.</param> Load(string filename)374 public void Load(string filename) 375 { 376 using (Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) 377 { 378 Load(stream); 379 } 380 } 381 382 /// <summary> 383 /// Loads a palette from the specified stream. 384 /// </summary> 385 /// <param name="stream">The stream to load the palette from.</param> Load(Stream stream)386 public void Load(Stream stream) 387 { 388 Load(new BinaryReader(stream)); 389 } 390 391 /// <summary> 392 /// Loads a palette from the reader. 393 /// </summary> 394 /// <param name="reader">The reader to load the palette from.</param> Load(BinaryReader reader)395 public void Load(BinaryReader reader) 396 { 397 EnsureNotDisposed(); 398 unsafe 399 { 400 int size = length * sizeof(RGBQUAD); 401 byte[] data = reader.ReadBytes(size); 402 fixed (byte* src = data) 403 { 404 CopyMemory(baseAddress, src, data.Length); 405 } 406 } 407 } 408 409 /// <summary> 410 /// Releases allocated handles associated with this instance. 411 /// </summary> 412 /// <param name="disposing"><b>true</b> to release managed resources.</param> Dispose(bool disposing)413 protected override void Dispose(bool disposing) 414 { 415 if (paletteHandle.IsAllocated) 416 paletteHandle.Free(); 417 array = null; 418 419 base.Dispose(disposing); 420 } 421 } 422 }