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 &lt; <paramref name="splitSize"/> &lt; 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 &lt; <paramref name="splitSize"/> &lt; <see cref="MemoryArray&lt;T&gt;.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 }