1 /**
2  * Copyright: Mike Wey 2011
3  * License:   zlib (See accompanying LICENSE file)
4  * Authors:   Mike Wey
5  * 
6  * This module contains functions that operate on a array or list of images.
7  */
8 
9 module dmagick.Array;
10 
11 import std.string;
12 import core.time;
13 
14 import dmagick.Exception;
15 import dmagick.Geometry;
16 import dmagick.Image;
17 import dmagick.Montage;
18 import dmagick.Options;
19 
20 import dmagick.c.blob;
21 import dmagick.c.colorspace;
22 import dmagick.c.composite;
23 import dmagick.c.constitute;
24 import dmagick.c.display;
25 import dmagick.c.fx;
26 import dmagick.c.image : MagickCoreImage = Image;
27 import dmagick.c.layer;
28 import dmagick.c.magickType;
29 import dmagick.c.memory;
30 import dmagick.c.montage;
31 import dmagick.c.statistic;
32 import dmagick.c.quantize;
33 
34 version(DMagick_No_Display)
35 {
36 }
37 else
38 {
39 	version(Windows) import dmagick.internal.Windows;
40 }
41 
42 /// See_Also: $(CXREF layer, _ImageLayerMethod)
43 public alias dmagick.c.layer.ImageLayerMethod ImageLayerMethod;
44 
45 alias ptrdiff_t ssize_t;
46 
47 /**
48  * Set the animationDelay for all images in the array.
49  */
50 void animationDelay(Image[] images, Duration delay)
51 {
52 	size_t ticks = cast(size_t)(delay.total!"msecs"() * images[0].imageRef.ticks_per_second) / 1000;
53 
54 	foreach ( image; images )
55 	{
56 		image.imageRef.delay = ticks;
57 	}
58 }
59 
60 /**
61  * Number of iterations to loop an animation.
62  */
63 void animationIterations(Image[] images, size_t iterations)
64 {
65 	images[0].animationIterations = iterations;
66 }
67 
68 /**
69  * Averages all the images together. Each image in the image must have
70  * the same width and height.
71  */
72 Image average(Image[] images)
73 {
74 	linkImages(images);
75 	scope(exit) unlinkImages(images);
76 
77 	static if ( is(typeof(EvaluateImages)) )
78 	{
79 		MagickCoreImage* image =
80 			EvaluateImages(images[0].imageRef, MagickEvaluateOperator.MeanEvaluateOperator, DMagickExceptionInfo());
81 	}
82 	else
83 	{
84 		MagickCoreImage* image = AverageImages(images[0].imageRef, DMagickExceptionInfo());
85 	}
86 
87 	return new Image(image);
88 }
89 
90 /**
91  * Clone every image in the array.
92  */
93 Image[] clone(const(Image)[] images)
94 {
95 	Image[] newImages = new Image[images.length];
96 
97 	foreach ( i, image; images )
98 	{
99 		newImages[i] = image.clone();
100 	}
101 
102 	return newImages;
103 }
104 
105 /**
106  * Merges all the images in the imagelist into a new imagelist. Each image
107  * in the new imagelist is formed by flattening all the previous images.
108  * 
109  * The length of time between images in the new image is specified by the
110  * delay attribute of the input image. The position of the image on the
111  * merged images is specified by the page attribute of the input image.
112  */
113 Image[] coalesce(Image[] images)
114 {
115 	linkImages(images);
116 	scope(exit) unlinkImages(images);
117 
118 	MagickCoreImage* image = CoalesceImages(images[0].imageRef, DMagickExceptionInfo());
119 
120 	return imageListToArray(image);
121 }
122 
123 /**
124  * Compares each image with the next in a sequence and returns the minimum
125  * bounding region of all the pixel differences (of the ImageLayerMethod
126  * specified) it discovers.
127  * 
128  * Images do NOT have to be the same size, though it is best that all the
129  * images are 'coalesced' (images are all the same size, on a flattened
130  * canvas, so as to represent exactly how an specific frame should look).
131  * 
132  * No GIF dispose methods are applied, so GIF animations must be coalesced
133  * before applying this image operator to find differences to them.
134  */
135 Image[] compareLayers(Image[] images, ImageLayerMethod method)
136 {
137 	linkImages(images);
138 	scope(exit) unlinkImages(images);
139 
140 	MagickCoreImage* image =
141 		CompareImageLayers(images[0].imageRef, method, DMagickExceptionInfo());
142 
143 	return imageListToArray(image);
144 }
145 
146 /**
147  * An image from source is composited over an image from destination until
148  * one list is finished. Unlike a normal composite operation, the canvas
149  * offset is also included to the composite positioning. If one of the
150  * image lists only contains one image, that image is applied to all the
151  * images in the other image list, regardless of which list it is. In this
152  * case it is the image meta-data of the list which preserved.
153  */
154 void compositeLayers(
155 	ref Image[] destination,
156 	Image[] source,
157 	ssize_t xOffset,
158 	ssize_t yOffset,
159 	CompositeOperator operator = CompositeOperator.OverCompositeOp)
160 {
161 	linkImages(destination);
162 	linkImages(source);
163 	scope(exit) unlinkImages(source);
164 	scope(failure) unlinkImages(destination);
165 
166 	CompositeLayers(destination[0].imageRef, operator, source[0].imageRef, xOffset, yOffset, DMagickExceptionInfo());
167 
168 	destination = imageListToArray(destination[0].imageRef);
169 }
170 
171 /**
172  * Repeatedly displays an image sequence to a X window screen.
173  */
174 void display(Image[] images)
175 {
176 	version(DMagick_No_Display)
177 	{
178 	}
179 	else
180 	{
181 		version(Windows)
182 		{
183 			Window win = new Window(images);
184 			win.display();
185 		}
186 		else
187 		{
188 			linkImages(images);
189 			scope(exit) unlinkImages(images);
190 
191 			DisplayImages(images[0].options.imageInfo, images[0].imageRef);
192 
193 			DMagickException.throwException(&(images[0].imageRef.exception));
194 		}
195 	}
196 }
197 
198 /**
199  * Applies a mathematical expression to the specified images.
200  * 
201  * See_Aso:
202  *     $(LINK2 http://www.imagemagick.org/script/fx.php,
203  *     FX, The Special Effects Image Operator) for a detailed
204  *     discussion of this option.
205  */
206 void fx(Image[] images, string expression, ChannelType channel = ChannelType.DefaultChannels)
207 {
208 	linkImages(images);
209 	scope(exit) unlinkImages(images);
210 
211 	images[0].fx(expression, channel);
212 }
213 
214 /**
215  * Composes all the image layers from the current given image onward
216  * to produce a single image of the merged layers.
217  * 
218  * The inital canvas's size depends on the given ImageLayerMethod, and is
219  * initialized using the first images background color. The images are then
220  * compositied onto that image in sequence using the given composition that
221  * has been assigned to each individual image.
222  * 
223  * Params:
224  *     layers = The images to merge.
225  *     method = The method of selecting the size of the initial canvas.
226  *              $(LIST
227  *                  $(B MergeLayer:) Merge all layers onto a canvas just
228  *                      large enough to hold all the actual images. The
229  *                      virtual canvas of the first image is preserved but
230  *                      otherwise ignored.,
231  *                  $(B FlattenLayer:) Use the virtual canvas size of first
232  *                      image. Images which fall outside this canvas is
233  *                      clipped. This can be used to 'fill out' a given
234  *                      virtual canvas.,
235  *                  $(B MosaicLayer:) Start with the virtual canvas of the
236  *                      first image enlarging left and right edges to
237  *                      contain all images. Images with negative offsets
238  *                      will be clipped.,
239  *                  $(B TrimBoundsLayer:) Determine the overall bounds of
240  *                      all the image layers just as in "MergeLayer". Then
241  *                      adjust the the canvas and offsets to be relative to
242  *                      those bounds. Without overlaying the images.
243  * 
244  *                      $(RED Warning:) a new image is not returned the
245  *                      original image sequence page data is modified instead.
246  *              )
247  */
248 Image mergeLayers(Image[] layers, ImageLayerMethod method = ImageLayerMethod.FlattenLayer)
249 {
250 	linkImages(layers);
251 	scope(exit) unlinkImages(layers);
252 
253 	MagickCoreImage* image =
254 		MergeImageLayers(layers[0].imageRef, method, DMagickExceptionInfo());
255 
256 	return new Image(image);
257 }
258 
259 /**
260  * Creates a composite image by reducing the size of the input images and
261  * arranging them in a grid on the background color or texture of your
262  * choice. There are many configuration options. For example, you can
263  * specify the number of columns and rows, the distance between images,
264  * and include a label with each small image (called a tile).
265  * 
266  * To add labels to the tiles, assign a "Label" property to each image.
267  */
268 Image[] montage(Image[] images, Montage montageInfo)
269 {
270 	linkImages(images);
271 	scope(exit) unlinkImages(images);
272 
273 	MagickCoreImage* image =
274 		MontageImages(images[0].imageRef, montageInfo.montageInfoRef, DMagickExceptionInfo());
275 
276 	return imageListToArray(image);
277 }
278 
279 /**
280  * Transforms a image into another image by inserting n in-between images.
281  * Requires at least two images. If more images are present, the 2nd image
282  * is transformed into the 3rd, the 3rd to the 4th, etc.
283  * 
284  * Params:
285  *     images = The images to use.
286  *     frames = The number of frames to inster between the images.
287  */
288 Image[] morph(Image[] images, size_t frames)
289 {
290 	linkImages(images);
291 	scope(exit) unlinkImages(images);
292 
293 	MagickCoreImage* image =
294 		MorphImages(images[0].imageRef, frames, DMagickExceptionInfo());
295 
296 	return imageListToArray(image);
297 }
298 
299 /**
300  * For each image compares the GIF disposed forms of the previous image in
301  * the sequence. From this it attempts to select the smallest cropped image
302  * to replace each frame, while preserving the results of the GIF animation.
303  * 
304  * See_Also: $(LINK2 http://www.imagemagick.org/Usage/anim_opt/,
305  *     Examples of ImageMagick Usage)
306  */
307 Image[] optimizeLayers(Image[] images)
308 {
309 	linkImages(images);
310 	scope(exit) unlinkImages(images);
311 
312 	MagickCoreImage* image = OptimizeImageLayers(images[0].imageRef, DMagickExceptionInfo());
313 
314 	return imageListToArray(image);
315 }
316 
317 /**
318  * Is exactly as optimizeLayers, but may also add or even remove extra
319  * frames in the animation, if it improves the total number of pixels in
320  * the resulting GIF animation.
321  */
322 Image[] optimizePlusLayers(Image[] images)
323 {
324 	linkImages(images);
325 	scope(exit) unlinkImages(images);
326 
327 	MagickCoreImage* image = OptimizePlusImageLayers(images[0].imageRef, DMagickExceptionInfo());
328 
329 	return imageListToArray(image);
330 }
331 
332 /**
333  * Ping is similar to read except only enough of the image is read to
334  * determine the image columns, rows, and filesize. The columns, rows,
335  * and fileSize attributes are valid after invoking ping.
336  * The image data is not valid after calling ping.
337  */
338 Image[] ping(string filename)
339 {
340 	Options options = new Options();
341 	options.filename = filename;
342 
343 	MagickCoreImage* image = PingImages(options.imageInfo, DMagickExceptionInfo());
344 
345 	return imageListToArray(image);
346 }
347 
348 ///ditto
349 Image[] ping(void[] blob)
350 {
351 	return ping(blob, new Options());
352 }
353 
354 ///ditto
355 Image[] ping(void[] blob, Geometry size)
356 {
357 	Options options = new Options();
358 	options.size = size;
359 
360 	return ping(blob, options);
361 }
362 
363 ///ditto
364 Image[] ping(void[] blob, Geometry size, size_t depth)
365 {
366 	Options options = new Options();
367 	options.size = size;
368 	options.depth = depth;
369 
370 	return ping(blob, options);
371 }
372 
373 ///ditto
374 Image[] ping(void[] blob, Geometry size, size_t depth, string magick)
375 {
376 	Options options = new Options();
377 	options.size = size;
378 	options.depth = depth;
379 	options.magick = magick;
380 	//Also set the filename to the image format
381 	options.filename = magick ~":";
382 
383 	return ping(blob, options);
384 }
385 
386 ///ditto
387 Image[] ping(void[] blob, Geometry size, string magick)
388 {
389 	Options options = new Options();
390 	options.size = size;
391 	options.magick = magick;
392 	//Also set the filename to the image format
393 	options.filename = magick ~":";
394 
395 	return ping(blob, options);
396 }
397 
398 /**
399  * Analyzes the colors within a set of reference images and chooses a
400  * fixed number of colors to represent the set. The goal of the algorithm
401  * is to minimize the difference between the input and output images while
402  * minimizing the processing time.
403  * 
404  * Params:
405  *     images       = The images to quantize.
406  *     measureError = Set to true to calculate quantization errors
407  *                    when quantizing the image. These can be accessed
408  *                    with: normalizedMeanError, normalizedMaxError
409  *                    and meanErrorPerPixel.
410  */
411 void quantize(Image[] images, bool measureError = false)
412 {
413 	linkImages(images);
414 	scope(exit) unlinkImages(images);
415 
416 	bool originalmeasureError = images[0].options.quantizeInfo.measure_error != 0;
417 	images[0].options.quantizeInfo.measure_error = measureError;
418 	scope(exit) images[0].options.quantizeInfo.measure_error = originalmeasureError;
419 
420 	QuantizeImages(images[0].options.quantizeInfo, images[0].imageRef);
421 
422 	foreach ( image; images )
423 		DMagickException.throwException(&(image.imageRef.exception));
424 }
425 
426 /**
427  * Preferred number of _colors in the image.
428  * The actual number of _colors in the image may be less
429  * than your request, but never more. Images with less
430  * unique _colors than specified with this option will have
431  * any duplicate or unused _colors removed.
432  */
433 void quantizeColors(Image[] images, size_t colors)
434 {
435 	images[0].options.quantizeColors = colors;
436 }
437 ///ditto
438 size_t quantizeColors(const(Image)[] images)
439 {
440 	return images[0].options.quantizeColors;
441 }
442 
443 /**
444  * Colorspace to quantize colors in.
445  * Empirical evidence suggests that distances in color spaces
446  * such as YUV or YIQ correspond to perceptual color differences
447  * more closely than do distances in RGB space. These color spaces
448  * may give better results when color reducing an image.
449  * The default is RGB
450  */
451 void quantizeColorSpace(Image[] images, ColorspaceType type)
452 {
453 	images[0].options.quantizeColorSpace = type;
454 }
455 ///ditto
456 ColorspaceType quantizeColorSpace(const(Image)[] images)
457 {
458 	return images[0].options.quantizeColorSpace;
459 }
460 
461 /**
462  * The basic strategy of dithering is to trade intensity resolution for
463  * spatial resolution by averaging the intensities of several neighboring
464  * pixels. Images which suffer from severe contouring when reducing
465  * colors can be improved with this option. 
466  */
467 void quantizeDitherMethod(Image[] images, DitherMethod method)
468 {
469 	images[0].options.quantizeDitherMethod = method;
470 }
471 ///ditto
472 DitherMethod quantizeDitherMethod(const(Image)[] images)
473 {
474 	return images[0].options.quantizeDitherMethod;
475 }
476 
477 /**
478  * Depth of the quantization color classification tree.
479  * Values of 0 or 1 allow selection of the optimal tree _depth
480  * for the color reduction algorithm. Values between 2 and 8
481  * may be used to manually adjust the tree _depth.
482  */
483 void quantizeTreeDepth(Image[] images, size_t depth)
484 {
485 	images[0].options.quantizeTreeDepth = depth;
486 }
487 ///ditto
488 size_t quantizeTreeDepth(const(Image)[] images)
489 {
490 	return images[0].options.quantizeTreeDepth;
491 }
492 
493 /**
494  * Read a multi frame Image by reading from the file or
495  * URL specified by filename.
496  */
497 Image[] readImages(string filename)
498 {
499 	Options options = new Options();
500 	options.filename = filename;
501 
502 	return readImages(options);
503 }
504 
505 ///ditto
506 Image[] readImages(string filename, Geometry size)
507 {
508 	Options options = new Options();
509 	options.filename = filename;
510 	options.size = size;
511 
512 	return readImages(options);
513 }
514 
515 /**
516  * Reads a multi frame Image from an in-memory blob.
517  * The Blob size, depth and magick format may also be specified.
518  *
519  * Some image formats require size to be specified,
520  * the default depth Imagemagick uses is the Quantum size
521  * it's compiled with. If it doesn't match the depth of the image
522  * it may need to be specified.
523  *
524  * Imagemagick can usualy detect the image format, when the
525  * format can't be detected a magick format must be specified.
526  */
527 Image[] readImages(void[] blob)
528 {
529 	return readImages(blob, new Options());
530 }
531 
532 ///ditto
533 Image[] readImages(void[] blob, Geometry size)
534 {
535 	Options options = new Options();
536 	options.size = size;
537 
538 	return readImages(blob, options);
539 }
540 
541 ///ditto
542 Image[] readImages(void[] blob, Geometry size, size_t depth)
543 {
544 	Options options = new Options();
545 	options.size = size;
546 	options.depth = depth;
547 
548 	return readImages(blob, options);
549 }
550 
551 ///ditto
552 Image[] readImages(void[] blob, Geometry size, size_t depth, string magick)
553 {
554 	Options options = new Options();
555 	options.size = size;
556 	options.depth = depth;
557 	options.magick = magick;
558 	//Also set the filename to the image format
559 	options.filename = magick ~":";
560 
561 	return readImages(blob, options);
562 }
563 
564 ///ditto
565 Image[] readImages(void[] blob, Geometry size, string magick)
566 {
567 	Options options = new Options();
568 	options.size = size;
569 	options.magick = magick;
570 	//Also set the filename to the image format
571 	options.filename = magick ~":";
572 
573 	return readImages(blob, options);
574 }
575 
576 /**
577  * Reduce the colors used in the imagelist to the set of colors in
578  * reference image.
579  */
580 void remap(Image[] images, Image referenceImage)
581 {
582 	linkImages(images);
583 	scope(exit) unlinkImages(images);
584 
585 	RemapImages(images[0].options.quantizeInfo, images[0].imageRef, referenceImage.imageRef);
586 
587 	foreach ( image; images )
588 		DMagickException.throwException(&(image.imageRef.exception));
589 }
590 
591 /**
592  * Creates a Binary Large OBject, a direct-to-memory
593  * version of the image.
594  *
595  * if an image format is selected which is capable of supporting
596  * fewer colors than the original image or quantization has been
597  * requested, the original image will be quantized to fewer colors.
598  * Use a copy of the original if this is a problem.
599  *
600  * Note, some image formats do not permit multiple images to the same
601  * image stream (e.g. JPEG). in this instance, just the first image of
602  * the sequence is returned as a blob.
603  * 
604  * Params:
605  *     images = Images to write.
606  *     magick = Specifies the image format to write.
607  *     depth  = Specifies the image depth.
608  *     adjoin = Join images into a single multi-image file.
609  */
610 void[] toBlob(Image[] images, string magick = null, size_t depth = 0, bool adjoin = true)
611 {
612 	size_t length;
613 
614 	if ( magick !is null )
615 		images[0].magick = magick;
616 	if ( depth != 0 )
617 		images[0].depth = depth;
618 
619 	string originalFilename = images[0].filename;
620 	images[0].filename = images[0].magick ~ ":";
621 	scope(exit) images[0].filename = originalFilename;
622 
623 	bool originalAdjoin = images[0].options.imageInfo.adjoin != 0;
624 	images[0].options.imageInfo.adjoin = adjoin;
625 	scope(exit) images[0].options.imageInfo.adjoin = originalAdjoin;
626 
627 	linkImages(images);
628 	scope(exit) unlinkImages(images);
629 
630 	void* blob = ImagesToBlob(images[0].options.imageInfo, images[0].imageRef, &length, DMagickExceptionInfo());
631 
632 	void[] dBlob = blob[0 .. length].dup;
633 	RelinquishMagickMemory(blob);
634 
635 	return dBlob;	
636 }
637 
638 /**
639  * Writes the image to the specified file. ImageMagick
640  * determines image format from the prefix or extension.
641  * 
642  * WriteImages generates multiple output files if necessary
643  * (or when requested). When adjoin is set to false, the filename is
644  * expected to include a printf-style formatting string for the frame
645  * number (e.g. "image%02d.png").
646  * 
647  * If an image format is selected which is capable of supporting
648  * fewer colors than the original image or quantization has been
649  * requested, the original image will be quantized to fewer colors.
650  * Use a copy of the original if this is a problem.
651  * 
652  * Params:
653  *     images   = Images to write.
654  *     filename = The file name to write to.
655  *     adjoin   = Join images into a single multi-image file.
656  */
657 void writeImages(Image[] images, string filename, bool adjoin = true)
658 {
659 	linkImages(images);
660 	scope(exit) unlinkImages(images);
661 
662 	images[0].options.adjoin = adjoin;
663 
664 	WriteImages(images[0].options.imageInfo, images[0].imageRef, toStringz(filename), DMagickExceptionInfo());
665 }
666 
667 /**
668  * Turn an ImageMagick image list into a D array.
669  */
670 private Image[] imageListToArray(MagickCoreImage* imageList)
671 {
672 	Image[] images;
673 
674 	do
675 	{
676 		images ~= new Image(imageList);
677 
678 		imageList = imageList.next;
679 	}
680 	while ( imageList !is null );
681 
682 	unlinkImages(images);
683 
684 	return images;
685 }
686 
687 /**
688  * Create an ImageMagick ImageList.
689  */
690 private void linkImages(Image[] images)
691 {
692 	for ( int i = 0; i < images.length; i++ )
693 	{
694 		if ( i > 0 )
695 			images[i].imageRef.previous = images[i-1].imageRef;
696 
697 		if ( i < images.length-1 )
698 			images[i].imageRef.next = images[i+1].imageRef;
699 	}
700 }
701 
702 
703 /**
704  * Actual implementation for ping.
705  */
706 private Image[] ping(void[] blob, Options options)
707 {
708 	MagickCoreImage* image = 
709 		PingBlob(options.imageInfo, blob.ptr, blob.length, DMagickExceptionInfo());
710 
711 	return imageListToArray(image);
712 }
713 
714 /**
715  * Actual implementation for files.
716  */
717 private Image[] readImages(Options options)
718 {
719 	MagickCoreImage* image = ReadImage(options.imageInfo, DMagickExceptionInfo());
720 
721 	return imageListToArray(image);
722 }
723 
724 /**
725  * Actual implementation for blobs.
726  */
727 private Image[] readImages(void[] blob, Options options)
728 {
729 	MagickCoreImage* image = 
730 		BlobToImage(options.imageInfo, blob.ptr, blob.length, DMagickExceptionInfo());
731 
732 	return imageListToArray(image);
733 }
734 
735 /**
736  * Destroy the ImageMagick ImageList.
737  */
738 private void unlinkImages(Image[] images)
739 {
740 	foreach ( image; images )
741 	{
742 		image.imageRef.next = null;
743 		image.imageRef.previous = null;
744 	}
745 }