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 }