43#include "magick/studio.h"
44#include "magick/annotate.h"
45#include "magick/client.h"
46#include "magick/color.h"
47#include "magick/composite.h"
48#include "magick/constitute.h"
49#include "magick/decorate.h"
50#include "magick/draw.h"
51#include "magick/effect.h"
52#include "magick/enhance.h"
53#include "magick/exception.h"
54#include "magick/exception-private.h"
55#include "magick/gem.h"
56#include "magick/geometry.h"
57#include "magick/image.h"
58#include "magick/image-private.h"
59#include "magick/list.h"
60#include "magick/memory_.h"
61#include "magick/monitor.h"
62#include "magick/monitor-private.h"
63#include "magick/montage.h"
64#include "magick/option.h"
65#include "magick/quantize.h"
66#include "magick/property.h"
67#include "magick/resize.h"
68#include "magick/resource_.h"
69#include "magick/string_.h"
70#include "magick/utility.h"
71#include "magick/version.h"
72#include "magick/visual-effects.h"
107 clone_info=(
MontageInfo *) AcquireMagickMemory(
sizeof(*clone_info));
109 ThrowFatalException(ResourceLimitFatalError,
"MemoryAllocationFailed");
110 GetMontageInfo(image_info,clone_info);
113 if (montage_info->geometry != (
char *) NULL)
114 clone_info->geometry=AcquireString(montage_info->geometry);
115 if (montage_info->tile != (
char *) NULL)
116 clone_info->tile=AcquireString(montage_info->tile);
117 if (montage_info->title != (
char *) NULL)
118 clone_info->title=AcquireString(montage_info->title);
119 if (montage_info->frame != (
char *) NULL)
120 clone_info->frame=AcquireString(montage_info->frame);
121 if (montage_info->texture != (
char *) NULL)
122 clone_info->texture=AcquireString(montage_info->texture);
123 if (montage_info->font != (
char *) NULL)
124 clone_info->font=AcquireString(montage_info->font);
125 clone_info->pointsize=montage_info->pointsize;
126 clone_info->border_width=montage_info->border_width;
127 clone_info->shadow=montage_info->shadow;
128 clone_info->fill=montage_info->fill;
129 clone_info->stroke=montage_info->stroke;
130 clone_info->background_color=montage_info->background_color;
131 clone_info->border_color=montage_info->border_color;
132 clone_info->matte_color=montage_info->matte_color;
133 clone_info->gravity=montage_info->gravity;
134 (void) CopyMagickString(clone_info->filename,montage_info->filename,
136 clone_info->debug=IsEventLogging();
166 assert(montage_info->signature == MagickCoreSignature);
167 if (IsEventLogging() != MagickFalse)
168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"...");
169 if (montage_info->geometry != (
char *) NULL)
170 montage_info->geometry=(
char *)
171 RelinquishMagickMemory(montage_info->geometry);
172 if (montage_info->tile != (
char *) NULL)
173 montage_info->tile=DestroyString(montage_info->tile);
174 if (montage_info->title != (
char *) NULL)
175 montage_info->title=DestroyString(montage_info->title);
176 if (montage_info->frame != (
char *) NULL)
177 montage_info->frame=DestroyString(montage_info->frame);
178 if (montage_info->texture != (
char *) NULL)
179 montage_info->texture=(
char *) RelinquishMagickMemory(
180 montage_info->texture);
181 if (montage_info->font != (
char *) NULL)
182 montage_info->font=DestroyString(montage_info->font);
183 montage_info->signature=(~MagickCoreSignature);
184 montage_info=(
MontageInfo *) RelinquishMagickMemory(montage_info);
185 return(montage_info);
213MagickExport
void GetMontageInfo(
const ImageInfo *image_info,
216 assert(image_info != (
const ImageInfo *) NULL);
217 assert(image_info->signature == MagickCoreSignature);
219 if (IsEventLogging() != MagickFalse)
220 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",
221 image_info->filename);
222 (void) memset(montage_info,0,
sizeof(*montage_info));
223 (void) CopyMagickString(montage_info->filename,image_info->filename,
225 montage_info->geometry=AcquireString(DefaultTileGeometry);
226 if (image_info->font != (
char *) NULL)
227 montage_info->font=AcquireString(image_info->font);
228 montage_info->gravity=CenterGravity;
229 montage_info->pointsize=image_info->pointsize;
230 montage_info->fill.opacity=OpaqueOpacity;
231 montage_info->stroke.opacity=(Quantum) TransparentOpacity;
232 montage_info->background_color=image_info->background_color;
233 montage_info->border_color=image_info->border_color;
234 montage_info->matte_color=image_info->matte_color;
235 montage_info->debug=IsEventLogging();
236 montage_info->signature=MagickCoreSignature;
271static void GetMontageGeometry(
char *geometry,
const size_t number_images,
272 ssize_t *x_offset,ssize_t *y_offset,
size_t *tiles_per_column,
273 size_t *tiles_per_row)
277 (void) GetGeometry(geometry,x_offset,y_offset,tiles_per_row,tiles_per_column);
278 if ((*tiles_per_column == 0) && (*tiles_per_row == 0))
279 *tiles_per_column=(size_t) sqrt((
double) number_images);
280 if ((*tiles_per_column == 0) && (*tiles_per_row != 0))
281 *tiles_per_column=(size_t) ceil((
double) number_images/(*tiles_per_row));
282 if ((*tiles_per_row == 0) && (*tiles_per_column != 0))
283 *tiles_per_row=(size_t) ceil((
double) number_images/(*tiles_per_column));
286#if defined(__cplusplus) || defined(c_plusplus)
290static int SceneCompare(
const void *x,
const void *y)
296 image_1=(
Image **) x;
297 image_2=(
Image **) y;
298 return((
int) ((*image_1)->scene-(*image_2)->scene));
301#if defined(__cplusplus) || defined(c_plusplus)
305MagickExport
Image *MontageImages(
const Image *images,
314 image_info=AcquireImageInfo();
315 montage_image=MontageImageList(image_info,montage_info,images,exception);
316 image_info=DestroyImageInfo(image_info);
317 return(montage_image);
323#define MontageImageTag "Montage/Image"
324#define TileImageTag "Tile/Image"
327 tile_geometry[MaxTextExtent],
359 MagickProgressMonitor
403 assert(images != (
Image *) NULL);
404 assert(images->signature == MagickCoreSignature);
406 assert(montage_info->signature == MagickCoreSignature);
408 assert(exception->signature == MagickCoreSignature);
409 if (IsEventLogging() != MagickFalse)
410 (void) LogMagickEvent(TraceEvent,GetMagickModule(),
"%s",images->filename);
411 number_images=GetImageListLength(images);
412 primary_list=ImageListToArray(images,exception);
413 if (primary_list == (
Image **) NULL)
414 return((
Image *) NULL);
415 image_list=primary_list;
417 thumbnail=NewImageList();
418 for (i=0; i < (ssize_t) number_images; i++)
420 image=CloneImage(image_list[i],0,0,MagickTrue,exception);
421 if (image == (
Image *) NULL)
423 (void) ParseAbsoluteGeometry(
"0x0+0+0",&image->page);
424 progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
426 (void) ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
427 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
428 if (thumbnail == (
Image *) NULL)
430 image_list[i]=thumbnail;
431 (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
432 proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
434 if (proceed == MagickFalse)
436 image=DestroyImage(image);
438 if (i < (ssize_t) number_images)
440 if (image != (
Image *) NULL)
441 image=DestroyImage(image);
442 if (thumbnail == (
Image *) NULL)
444 for (tile=0; (ssize_t) tile <= i; tile++)
445 if (image_list[tile] != (
Image *) NULL)
446 image_list[tile]=DestroyImage(image_list[tile]);
447 primary_list=(
Image **) RelinquishMagickMemory(primary_list);
448 return((
Image *) NULL);
453 for (i=0; i < (ssize_t) number_images; i++)
454 if (image_list[i]->scene == 0)
456 if (i == (ssize_t) number_images)
457 qsort((
void *) image_list,(
size_t) number_images,
sizeof(*image_list),
462 tiles_per_column=(size_t) sqrt((
double) number_images);
463 tiles_per_row=(size_t) ceil((
double) number_images/tiles_per_column);
466 if (montage_info->tile != (
char *) NULL)
467 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
468 &tiles_per_column,&tiles_per_row);
472 concatenate=MagickFalse;
473 SetGeometry(image_list[0],&extract_info);
474 extract_info.x=(ssize_t) montage_info->border_width;
475 extract_info.y=(ssize_t) montage_info->border_width;
476 if (montage_info->geometry != (
char *) NULL)
481 flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
482 &extract_info.width,&extract_info.height);
483 concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
484 MagickTrue : MagickFalse;
486 border_width=montage_info->border_width;
488 (void) memset(&frame_info,0,
sizeof(frame_info));
489 if (montage_info->frame != (
char *) NULL)
492 absolute_geometry[MaxTextExtent];
494 frame_info.width=extract_info.width;
495 frame_info.height=extract_info.height;
496 (void) FormatLocaleString(absolute_geometry,MaxTextExtent,
"%s!",
497 montage_info->frame);
498 flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
499 &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
500 if ((flags & HeightValue) == 0)
501 frame_info.height=frame_info.width;
502 if ((flags & XiValue) == 0)
503 frame_info.outer_bevel=(ssize_t) frame_info.width/2-1;
504 if ((flags & PsiValue) == 0)
505 frame_info.inner_bevel=frame_info.outer_bevel;
506 frame_info.x=(ssize_t) frame_info.width;
507 frame_info.y=(ssize_t) frame_info.height;
508 bevel_width=(size_t) MagickMax(frame_info.inner_bevel,
509 frame_info.outer_bevel);
510 border_width=(size_t) MagickMax((ssize_t) frame_info.width,
511 (ssize_t) frame_info.height);
513 for (i=0; i < (ssize_t) number_images; i++)
515 if (image_list[i]->columns > extract_info.width)
516 extract_info.width=image_list[i]->columns;
517 if (image_list[i]->rows > extract_info.height)
518 extract_info.height=image_list[i]->rows;
523 clone_info=CloneImageInfo(image_info);
524 clone_info->background_color=montage_info->background_color;
525 clone_info->border_color=montage_info->border_color;
526 draw_info=CloneDrawInfo(clone_info,(
DrawInfo *) NULL);
527 if (montage_info->font != (
char *) NULL)
528 (void) CloneString(&draw_info->font,montage_info->font);
529 if (montage_info->pointsize != 0.0)
530 draw_info->pointsize=montage_info->pointsize;
531 draw_info->gravity=CenterGravity;
532 draw_info->stroke=montage_info->stroke;
533 draw_info->fill=montage_info->fill;
534 draw_info->text=AcquireString(
"");
535 (void) GetTypeMetrics(image_list[0],draw_info,&metrics);
536 texture=NewImageList();
537 if (montage_info->texture != (
char *) NULL)
539 (void) CopyMagickString(clone_info->filename,montage_info->texture,
541 texture=ReadImage(clone_info,exception);
546 title=InterpretImageProperties(clone_info,image_list[0],montage_info->title);
548 if (montage_info->title != (
char *) NULL)
549 title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
550 MultilineCensus(title)+2*extract_info.y);
552 for (i=0; i < (ssize_t) number_images; i++)
554 value=GetImageProperty(image_list[i],
"label");
555 if (value == (
const char *) NULL)
557 if (MultilineCensus(value) > number_lines)
558 number_lines=MultilineCensus(value);
563 tile_image=AcquireImage(NULL);
564 montage=AcquireImage(clone_info);
565 montage->background_color=montage_info->background_color;
567 images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
569 total_tiles=(size_t) number_images;
570 for (i=0; i < (ssize_t) images_per_page; i++)
575 tiles_per_page=tiles_per_row*tiles_per_column;
578 if (montage_info->tile != (
char *) NULL)
579 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
581 tiles_per_page=tiles_per_row*tiles_per_column;
582 y_offset+=(ssize_t) title_offset;
587 for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
589 if (tile < (ssize_t) number_images)
591 width=concatenate != MagickFalse ? image_list[tile]->columns :
593 if (image_list[tile]->rows > max_height)
594 max_height=image_list[tile]->rows;
596 x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
597 if (x_offset > (ssize_t) bounds.width)
598 bounds.width=(size_t) x_offset;
599 if (((tile+1) == (ssize_t) tiles_per_page) ||
600 (((tile+1) % tiles_per_row) == 0))
603 if (montage_info->tile != (
char *) NULL)
604 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
606 height=concatenate != MagickFalse ? max_height : extract_info.height;
607 y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
608 (metrics.ascent-metrics.descent+4)*number_lines+
609 (montage_info->shadow != MagickFalse ? 4 : 0));
610 if (y_offset > (ssize_t) bounds.height)
611 bounds.height=(size_t) y_offset;
615 if (montage_info->shadow != MagickFalse)
620 (void) CopyMagickString(montage->filename,montage_info->filename,
622 montage->columns=(size_t) MagickMax((ssize_t) bounds.width,1);
623 montage->rows=(size_t) MagickMax((ssize_t) bounds.height,1);
624 (void) SetImageBackgroundColor(montage);
628 montage->montage=AcquireString((
char *) NULL);
631 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
633 extent+=strlen(image_list[tile]->filename)+1;
636 montage->directory=(
char *) AcquireQuantumMemory(extent,
637 sizeof(*montage->directory));
638 if ((montage->montage == (
char *) NULL) ||
639 (montage->directory == (
char *) NULL))
641 if (montage->montage != (
char *) NULL)
642 montage->montage=(
char *) RelinquishMagickMemory(montage->montage);
643 if (montage->directory != (
char *) NULL)
644 montage->directory=(
char *) RelinquishMagickMemory(
646 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
650 if (montage_info->tile != (
char *) NULL)
651 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
653 y_offset+=(ssize_t) title_offset;
654 (void) FormatLocaleString(montage->montage,MaxTextExtent,
655 "%.20gx%.20g%+.20g%+.20g",(
double) (extract_info.width+
656 (extract_info.x+border_width)*2),(
double) (extract_info.height+
657 (extract_info.y+border_width)*2+(
double) ((metrics.ascent-
658 metrics.descent+4)*number_lines+(montage_info->shadow != MagickFalse ? 4 :
659 0))),(
double) x_offset,(
double) y_offset);
660 *montage->directory=
'\0';
662 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
664 if (strchr(image_list[tile]->filename,(
int)
'\xff') == (
char *) NULL)
665 (void) ConcatenateMagickString(montage->directory,
666 image_list[tile]->filename,extent);
668 (
void) ThrowMagickException(exception,GetMagickModule(),OptionError,
669 "InvalidArgument",
"'%s'",image_list[tile]->filename);
670 (void) ConcatenateMagickString(montage->directory,
"\xff",extent);
673 progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
674 NULL,montage->client_data);
675 if (texture != (
Image *) NULL)
676 (void) TextureImage(montage,texture);
677 if (montage_info->title != (
char *) NULL)
680 geometry[MaxTextExtent];
691 clone_info=CloneDrawInfo(image_info,draw_info);
692 clone_info->gravity=CenterGravity;
693 clone_info->pointsize*=2.0;
694 (void) GetTypeMetrics(image_list[0],clone_info,&metrics);
695 (void) FormatLocaleString(geometry,MaxTextExtent,
696 "%.20gx%.20g%+.20g%+.20g",(
double) montage->columns,(
double)
697 (metrics.ascent-metrics.descent),0.0,(
double) extract_info.y+4);
698 (void) CloneString(&clone_info->geometry,geometry);
699 (void) CloneString(&clone_info->text,title);
700 (void) AnnotateImage(montage,clone_info);
701 clone_info=DestroyDrawInfo(clone_info);
703 (void) SetImageProgressMonitor(montage,progress_monitor,
704 montage->client_data);
710 if (montage_info->tile != (
char *) NULL)
711 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
713 x_offset+=extract_info.x;
714 y_offset+=(ssize_t) title_offset+extract_info.y;
717 for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
722 image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
723 if (image == (
Image *) NULL)
724 ThrowImageException(ResourceLimitError,
"MemoryAllocationFailed");
725 progress_monitor=SetImageProgressMonitor(image,
726 (MagickProgressMonitor) NULL,image->client_data);
727 width=concatenate != MagickFalse ? image->columns : extract_info.width;
728 if (image->rows > max_height)
729 max_height=image->rows;
730 height=concatenate != MagickFalse ? max_height : extract_info.height;
731 if (border_width != 0)
742 border_info.width=border_width;
743 border_info.height=border_width;
744 if (montage_info->frame != (
char *) NULL)
746 border_info.width=(width-image->columns+1)/2;
747 border_info.height=(height-image->rows+1)/2;
749 border_image=BorderImage(image,&border_info,exception);
750 if (border_image != (
Image *) NULL)
752 image=DestroyImage(image);
755 if ((montage_info->frame != (
char *) NULL) &&
756 (image->compose == DstOutCompositeOp))
757 (void) NegateImageChannel(image,OpacityChannel,MagickFalse);
762 tile_image->columns=width;
763 tile_image->rows=height;
764 tile_image->gravity=montage_info->gravity;
765 if (image->gravity != UndefinedGravity)
766 tile_image->gravity=image->gravity;
767 (void) FormatLocaleString(tile_geometry,MaxTextExtent,
"%.20gx%.20g+0+0",
768 (
double) image->columns,(
double) image->rows);
769 (void) ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
770 x=(ssize_t) (geometry.x+border_width);
771 y=(ssize_t) (geometry.y+border_width);
772 if ((montage_info->frame != (
char *) NULL) && (bevel_width > 0))
783 extract_info=frame_info;
784 extract_info.width=width+2*frame_info.width;
785 extract_info.height=height+2*frame_info.height;
786 value=GetImageProperty(image,
"label");
787 if (value != (
const char *) NULL)
788 extract_info.height+=(size_t) ((metrics.ascent-metrics.descent+4)*
789 MultilineCensus(value));
790 frame_image=FrameImage(image,&extract_info,exception);
791 if (frame_image != (
Image *) NULL)
793 image=DestroyImage(image);
799 if (LocaleCompare(image->magick,
"NULL") != 0)
804 if (montage_info->shadow != MagickFalse)
812 (void) QueryColorDatabase(
"#000000",&image->background_color,
814 shadow_image=ShadowImage(image,30.0,5.0,5,5,exception);
815 if (shadow_image != (
Image *) NULL)
817 InheritException(&shadow_image->exception,exception);
818 (void) CompositeImage(shadow_image,OverCompositeOp,image,0,0);
819 image=DestroyImage(image);
823 (void) CompositeImage(montage,image->compose,image,x_offset+x,
825 value=GetImageProperty(image,
"label");
826 if (value != (
const char *) NULL)
829 geometry[MaxTextExtent];
834 (void) FormatLocaleString(geometry,MaxTextExtent,
835 "%.20gx%.20g%+.20g%+.20g",(
double) ((montage_info->frame ?
836 image->columns : width)-2*border_width),(
double)
837 (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
838 (
double) (x_offset+border_width),(
double)
839 ((montage_info->frame ? y_offset+height+border_width+4 :
840 y_offset+extract_info.height+border_width+
841 (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
842 (void) CloneString(&draw_info->geometry,geometry);
843 (void) CloneString(&draw_info->text,value);
844 (void) AnnotateImage(montage,draw_info);
847 x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
848 if (((tile+1) == (ssize_t) tiles_per_page) ||
849 (((tile+1) % tiles_per_row) == 0))
851 x_offset=extract_info.x;
852 y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
853 (metrics.ascent-metrics.descent+4)*number_lines+
854 (montage_info->shadow != MagickFalse ? 4 : 0));
857 if (images->progress_monitor != (MagickProgressMonitor) NULL)
862 proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
863 if (proceed == MagickFalse)
866 image_list[tile]=DestroyImage(image_list[tile]);
867 image=DestroyImage(image);
871 if ((i+1) < (ssize_t) images_per_page)
876 AcquireNextImage(clone_info,montage);
877 if (GetNextImageInList(montage) == (
Image *) NULL)
879 montage=DestroyImageList(montage);
880 return((
Image *) NULL);
882 montage=GetNextImageInList(montage);
883 montage->background_color=montage_info->background_color;
884 image_list+=tiles_per_page;
885 number_images-=tiles_per_page;
888 tile_image=DestroyImage(tile_image);
889 if (texture != (
Image *) NULL)
890 texture=DestroyImage(texture);
891 title=DestroyString(title);
892 primary_list=(
Image **) RelinquishMagickMemory(primary_list);
893 draw_info=DestroyDrawInfo(draw_info);
894 clone_info=DestroyImageInfo(clone_info);
895 return(GetFirstImageInList(montage));