MagickCore  6.9.12-67
Convert, Edit, Or Compose Bitmap Images
 All Data Structures
montage.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % M M OOO N N TTTTT AAA GGGG EEEEE %
7 % MM MM O O NN N T A A G E %
8 % M M M O O N N N T AAAAA G GG EEE %
9 % M M O O N NN T A A G G E %
10 % M M OOO N N T A A GGG EEEEE %
11 % %
12 % %
13 % MagickCore Methods to Create Image Thumbnails %
14 % %
15 % Software Design %
16 % Cristy %
17 % July 1992 %
18 % %
19 % %
20 % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 %
38 */
39 
40 /*
41  Include declarations.
42 */
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"
73 
74 /*
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 % %
77 % %
78 % %
79 % C l o n e M o n t a g e I n f o %
80 % %
81 % %
82 % %
83 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84 %
85 % CloneMontageInfo() makes a copy of the given montage info structure. If
86 % NULL is specified, a new image info structure is created initialized to
87 % default values.
88 %
89 % The format of the CloneMontageInfo method is:
90 %
91 % MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
92 % const MontageInfo *montage_info)
93 %
94 % A description of each parameter follows:
95 %
96 % o image_info: the image info.
97 %
98 % o montage_info: the montage info.
99 %
100 */
101 MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
102  const MontageInfo *montage_info)
103 {
105  *clone_info;
106 
107  clone_info=(MontageInfo *) AcquireMagickMemory(sizeof(*clone_info));
108  if (clone_info == (MontageInfo *) NULL)
109  ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
110  GetMontageInfo(image_info,clone_info);
111  if (montage_info == (MontageInfo *) NULL)
112  return(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,
135  MaxTextExtent);
136  clone_info->debug=IsEventLogging();
137  return(clone_info);
138 }
139 
140 /*
141 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
142 % %
143 % %
144 % %
145 % D e s t r o y M o n t a g e I n f o %
146 % %
147 % %
148 % %
149 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
150 %
151 % DestroyMontageInfo() deallocates memory associated with montage_info.
152 %
153 % The format of the DestroyMontageInfo method is:
154 %
155 % MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
156 %
157 % A description of each parameter follows:
158 %
159 % o montage_info: Specifies a pointer to an MontageInfo structure.
160 %
161 %
162 */
163 MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
164 {
165  assert(montage_info != (MontageInfo *) NULL);
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);
186 }
187 
188 /*
189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190 % %
191 % %
192 % %
193 % G e t M o n t a g e I n f o %
194 % %
195 % %
196 % %
197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
198 %
199 % GetMontageInfo() initializes montage_info to default values.
200 %
201 % The format of the GetMontageInfo method is:
202 %
203 % void GetMontageInfo(const ImageInfo *image_info,
204 % MontageInfo *montage_info)
205 %
206 % A description of each parameter follows:
207 %
208 % o image_info: a structure of type ImageInfo.
209 %
210 % o montage_info: Specifies a pointer to a MontageInfo structure.
211 %
212 */
213 MagickExport void GetMontageInfo(const ImageInfo *image_info,
214  MontageInfo *montage_info)
215 {
216  assert(image_info != (const ImageInfo *) NULL);
217  assert(image_info->signature == MagickCoreSignature);
218  assert(montage_info != (MontageInfo *) NULL);
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,
224  MaxTextExtent);
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;
237 }
238 
239 /*
240 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241 % %
242 % %
243 % %
244 % M o n t a g e I m a g e L i s t %
245 % %
246 % %
247 % %
248 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
249 %
250 % MontageImageList() is a layout manager that lets you tile one or more
251 % thumbnails across an image canvas.
252 %
253 % The format of the MontageImageList method is:
254 %
255 % Image *MontageImageList(const ImageInfo *image_info,
256 % const MontageInfo *montage_info,Image *images,
257 % ExceptionInfo *exception)
258 %
259 % A description of each parameter follows:
260 %
261 % o image_info: the image info.
262 %
263 % o montage_info: Specifies a pointer to a MontageInfo structure.
264 %
265 % o images: Specifies a pointer to an array of Image structures.
266 %
267 % o exception: return any errors or warnings in this structure.
268 %
269 */
270 
271 static 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)
274 {
275  *tiles_per_column=0;
276  *tiles_per_row=0;
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));
284 }
285 
286 #if defined(__cplusplus) || defined(c_plusplus)
287 extern "C" {
288 #endif
289 
290 static int SceneCompare(const void *x,const void *y)
291 {
292  Image
293  **image_1,
294  **image_2;
295 
296  image_1=(Image **) x;
297  image_2=(Image **) y;
298  return((int) ((*image_1)->scene-(*image_2)->scene));
299 }
300 
301 #if defined(__cplusplus) || defined(c_plusplus)
302 }
303 #endif
304 
305 MagickExport Image *MontageImages(const Image *images,
306  const MontageInfo *montage_info,ExceptionInfo *exception)
307 {
308  Image
309  *montage_image;
310 
311  ImageInfo
312  *image_info;
313 
314  image_info=AcquireImageInfo();
315  montage_image=MontageImageList(image_info,montage_info,images,exception);
316  image_info=DestroyImageInfo(image_info);
317  return(montage_image);
318 }
319 
320 MagickExport Image *MontageImageList(const ImageInfo *image_info,
321  const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
322 {
323 #define MontageImageTag "Montage/Image"
324 #define TileImageTag "Tile/Image"
325 
326  char
327  tile_geometry[MaxTextExtent],
328  *title;
329 
330  const char
331  *value;
332 
333  DrawInfo
334  *draw_info;
335 
336  FrameInfo
337  frame_info;
338 
339  Image
340  *image,
341  **image_list,
342  **primary_list,
343  *montage,
344  *texture,
345  *tile_image,
346  *thumbnail;
347 
348  ImageInfo
349  *clone_info;
350 
351  MagickBooleanType
352  concatenate,
353  proceed,
354  status;
355 
356  MagickOffsetType
357  tiles;
358 
359  MagickProgressMonitor
360  progress_monitor;
361 
362  MagickStatusType
363  flags;
364 
365  ssize_t
366  i;
367 
369  bounds,
370  geometry,
371  extract_info;
372 
373  size_t
374  border_width,
375  extent,
376  height,
377  images_per_page,
378  max_height,
379  number_images,
380  number_lines,
381  sans,
382  tiles_per_column,
383  tiles_per_page,
384  tiles_per_row,
385  title_offset,
386  total_tiles,
387  width;
388 
389  ssize_t
390  bevel_width,
391  tile,
392  x,
393  x_offset,
394  y,
395  y_offset;
396 
397  TypeMetric
398  metrics;
399 
400  /*
401  Create image tiles.
402  */
403  assert(images != (Image *) NULL);
404  assert(images->signature == MagickCoreSignature);
405  assert(montage_info != (MontageInfo *) NULL);
406  assert(montage_info->signature == MagickCoreSignature);
407  assert(exception != (ExceptionInfo *) NULL);
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;
416  image=image_list[0];
417  thumbnail=NewImageList();
418  for (i=0; i < (ssize_t) number_images; i++)
419  {
420  image=CloneImage(image_list[i],0,0,MagickTrue,exception);
421  if (image == (Image *) NULL)
422  break;
423  (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
424  progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
425  image->client_data);
426  (void) ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
427  thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
428  if (thumbnail == (Image *) NULL)
429  break;
430  image_list[i]=thumbnail;
431  (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
432  proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
433  number_images);
434  if (proceed == MagickFalse)
435  break;
436  image=DestroyImage(image);
437  }
438  if (i < (ssize_t) number_images)
439  {
440  if (image != (Image *) NULL)
441  image=DestroyImage(image);
442  if (thumbnail == (Image *) NULL)
443  i--;
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);
449  }
450  /*
451  Sort image list by increasing tile number.
452  */
453  for (i=0; i < (ssize_t) number_images; i++)
454  if (image_list[i]->scene == 0)
455  break;
456  if (i == (ssize_t) number_images)
457  qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
458  SceneCompare);
459  /*
460  Determine tiles per row and column.
461  */
462  tiles_per_column=(size_t) sqrt((double) number_images);
463  tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
464  x_offset=0;
465  y_offset=0;
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);
469  /*
470  Determine tile sizes.
471  */
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)
477  {
478  /*
479  Initialize tile geometry.
480  */
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;
485  }
486  border_width=montage_info->border_width;
487  bevel_width=0;
488  (void) memset(&frame_info,0,sizeof(frame_info));
489  if (montage_info->frame != (char *) NULL)
490  {
491  char
492  absolute_geometry[MaxTextExtent];
493 
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);
512  }
513  for (i=0; i < (ssize_t) number_images; i++)
514  {
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;
519  }
520  /*
521  Initialize draw attributes.
522  */
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)
538  {
539  (void) CopyMagickString(clone_info->filename,montage_info->texture,
540  MaxTextExtent);
541  texture=ReadImage(clone_info,exception);
542  }
543  /*
544  Determine the number of lines in an next label.
545  */
546  title=InterpretImageProperties(clone_info,image_list[0],montage_info->title);
547  title_offset=0;
548  if (montage_info->title != (char *) NULL)
549  title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
550  MultilineCensus(title)+2*extract_info.y);
551  number_lines=0;
552  for (i=0; i < (ssize_t) number_images; i++)
553  {
554  value=GetImageProperty(image_list[i],"label");
555  if (value == (const char *) NULL)
556  continue;
557  if (MultilineCensus(value) > number_lines)
558  number_lines=MultilineCensus(value);
559  }
560  /*
561  Allocate next structure.
562  */
563  tile_image=AcquireImage(NULL);
564  montage=AcquireImage(clone_info);
565  montage->background_color=montage_info->background_color;
566  montage->scene=0;
567  images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
568  tiles=0;
569  total_tiles=(size_t) number_images;
570  for (i=0; i < (ssize_t) images_per_page; i++)
571  {
572  /*
573  Determine bounding box.
574  */
575  tiles_per_page=tiles_per_row*tiles_per_column;
576  x_offset=0;
577  y_offset=0;
578  if (montage_info->tile != (char *) NULL)
579  GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
580  &sans,&sans);
581  tiles_per_page=tiles_per_row*tiles_per_column;
582  y_offset+=(ssize_t) title_offset;
583  max_height=0;
584  bounds.width=0;
585  bounds.height=0;
586  width=0;
587  for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
588  {
589  if (tile < (ssize_t) number_images)
590  {
591  width=concatenate != MagickFalse ? image_list[tile]->columns :
592  extract_info.width;
593  if (image_list[tile]->rows > max_height)
594  max_height=image_list[tile]->rows;
595  }
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))
601  {
602  x_offset=0;
603  if (montage_info->tile != (char *) NULL)
604  GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
605  &sans,&sans);
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;
612  max_height=0;
613  }
614  }
615  if (montage_info->shadow != MagickFalse)
616  bounds.width+=4;
617  /*
618  Initialize montage image.
619  */
620  (void) CopyMagickString(montage->filename,montage_info->filename,
621  MaxTextExtent);
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);
625  /*
626  Set montage geometry.
627  */
628  montage->montage=AcquireString((char *) NULL);
629  tile=0;
630  extent=1;
631  while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
632  {
633  extent+=strlen(image_list[tile]->filename)+1;
634  tile++;
635  }
636  montage->directory=(char *) AcquireQuantumMemory(extent,
637  sizeof(*montage->directory));
638  if ((montage->montage == (char *) NULL) ||
639  (montage->directory == (char *) NULL))
640  {
641  if (montage->montage != (char *) NULL)
642  montage->montage=(char *) RelinquishMagickMemory(montage->montage);
643  if (montage->directory != (char *) NULL)
644  montage->directory=(char *) RelinquishMagickMemory(
645  montage->directory);
646  ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
647  }
648  x_offset=0;
649  y_offset=0;
650  if (montage_info->tile != (char *) NULL)
651  GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
652  &sans,&sans);
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';
661  tile=0;
662  while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
663  {
664  if (strchr(image_list[tile]->filename,'\xff') == (char *) NULL)
665  (void) ConcatenateMagickString(montage->directory,
666  image_list[tile]->filename,extent);
667  else
668  (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
669  "InvalidArgument","'%s'",image_list[tile]->filename);
670  (void) ConcatenateMagickString(montage->directory,"\xff",extent);
671  tile++;
672  }
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)
678  {
679  char
680  geometry[MaxTextExtent];
681 
682  DrawInfo
683  *clone_info;
684 
685  TypeMetric
686  metrics;
687 
688  /*
689  Annotate composite image with title.
690  */
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);
702  }
703  (void) SetImageProgressMonitor(montage,progress_monitor,
704  montage->client_data);
705  /*
706  Copy tile to the composite.
707  */
708  x_offset=0;
709  y_offset=0;
710  if (montage_info->tile != (char *) NULL)
711  GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
712  &sans,&sans);
713  x_offset+=extract_info.x;
714  y_offset+=(ssize_t) title_offset+extract_info.y;
715  max_height=0;
716  status=MagickTrue;
717  for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
718  {
719  /*
720  Copy this tile to the composite.
721  */
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)
732  {
733  Image
734  *border_image;
735 
737  border_info;
738 
739  /*
740  Put a border around the image.
741  */
742  border_info.width=border_width;
743  border_info.height=border_width;
744  if (montage_info->frame != (char *) NULL)
745  {
746  border_info.width=(width-image->columns+1)/2;
747  border_info.height=(height-image->rows+1)/2;
748  }
749  border_image=BorderImage(image,&border_info,exception);
750  if (border_image != (Image *) NULL)
751  {
752  image=DestroyImage(image);
753  image=border_image;
754  }
755  if ((montage_info->frame != (char *) NULL) &&
756  (image->compose == DstOutCompositeOp))
757  (void) NegateImageChannel(image,OpacityChannel,MagickFalse);
758  }
759  /*
760  Gravitate as specified by the tile gravity.
761  */
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))
773  {
774  FrameInfo
775  extract_info;
776 
777  Image
778  *frame_image;
779 
780  /*
781  Put an ornamental border around this tile.
782  */
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)
792  {
793  image=DestroyImage(image);
794  image=frame_image;
795  }
796  x=0;
797  y=0;
798  }
799  if (LocaleCompare(image->magick,"NULL") != 0)
800  {
801  /*
802  Composite background with tile.
803  */
804  if (montage_info->shadow != MagickFalse)
805  {
806  Image
807  *shadow_image;
808 
809  /*
810  Shadow image.
811  */
812  (void) QueryColorDatabase("#000000",&image->background_color,
813  exception);
814  shadow_image=ShadowImage(image,80.0,2.0,5,5,exception);
815  if (shadow_image != (Image *) NULL)
816  {
817  InheritException(&shadow_image->exception,exception);
818  (void) CompositeImage(shadow_image,OverCompositeOp,image,0,0);
819  image=DestroyImage(image);
820  image=shadow_image;
821  }
822  }
823  (void) CompositeImage(montage,image->compose,image,x_offset+x,
824  y_offset+y);
825  value=GetImageProperty(image,"label");
826  if (value != (const char *) NULL)
827  {
828  char
829  geometry[MaxTextExtent];
830 
831  /*
832  Annotate composite tile with label.
833  */
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);
845  }
846  }
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))
850  {
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));
855  max_height=0;
856  }
857  if (images->progress_monitor != (MagickProgressMonitor) NULL)
858  {
859  MagickBooleanType
860  proceed;
861 
862  proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
863  if (proceed == MagickFalse)
864  status=MagickFalse;
865  }
866  image_list[tile]=DestroyImage(image_list[tile]);
867  image=DestroyImage(image);
868  tiles++;
869  }
870  (void) status;
871  if ((i+1) < (ssize_t) images_per_page)
872  {
873  /*
874  Allocate next image structure.
875  */
876  AcquireNextImage(clone_info,montage);
877  if (GetNextImageInList(montage) == (Image *) NULL)
878  {
879  montage=DestroyImageList(montage);
880  return((Image *) NULL);
881  }
882  montage=GetNextImageInList(montage);
883  montage->background_color=montage_info->background_color;
884  image_list+=tiles_per_page;
885  number_images-=tiles_per_page;
886  }
887  }
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));
896 }
Definition: image.h:152