MagickCore 7.1.1
Convert, Edit, Or Compose Bitmap Images
Loading...
Searching...
No Matches
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 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 "MagickCore/studio.h"
44#include "MagickCore/annotate.h"
45#include "MagickCore/client.h"
46#include "MagickCore/color.h"
47#include "MagickCore/composite.h"
48#include "MagickCore/constitute.h"
49#include "MagickCore/decorate.h"
50#include "MagickCore/draw.h"
51#include "MagickCore/effect.h"
52#include "MagickCore/enhance.h"
53#include "MagickCore/exception.h"
54#include "MagickCore/exception-private.h"
55#include "MagickCore/gem.h"
56#include "MagickCore/geometry.h"
57#include "MagickCore/image.h"
58#include "MagickCore/image-private.h"
59#include "MagickCore/list.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/memory-private.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/montage.h"
65#include "MagickCore/option.h"
66#include "MagickCore/pixel.h"
67#include "MagickCore/quantize.h"
68#include "MagickCore/property.h"
69#include "MagickCore/resize.h"
70#include "MagickCore/resource_.h"
71#include "MagickCore/string_.h"
72#include "MagickCore/utility.h"
73#include "MagickCore/utility-private.h"
74#include "MagickCore/version.h"
75#include "MagickCore/visual-effects.h"
76
77/*
78%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79% %
80% %
81% %
82% C l o n e M o n t a g e I n f o %
83% %
84% %
85% %
86%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87%
88% CloneMontageInfo() makes a copy of the given montage info structure. If
89% NULL is specified, a new image info structure is created initialized to
90% default values.
91%
92% The format of the CloneMontageInfo method is:
93%
94% MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
95% const MontageInfo *montage_info)
96%
97% A description of each parameter follows:
98%
99% o image_info: the image info.
100%
101% o montage_info: the montage info.
102%
103*/
104MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
105 const MontageInfo *montage_info)
106{
108 *clone_info;
109
110 clone_info=(MontageInfo *) AcquireCriticalMemory(sizeof(*clone_info));
111 GetMontageInfo(image_info,clone_info);
112 if (montage_info == (MontageInfo *) NULL)
113 return(clone_info);
114 if (montage_info->geometry != (char *) NULL)
115 clone_info->geometry=AcquireString(montage_info->geometry);
116 if (montage_info->tile != (char *) NULL)
117 clone_info->tile=AcquireString(montage_info->tile);
118 if (montage_info->title != (char *) NULL)
119 clone_info->title=AcquireString(montage_info->title);
120 if (montage_info->frame != (char *) NULL)
121 clone_info->frame=AcquireString(montage_info->frame);
122 if (montage_info->texture != (char *) NULL)
123 clone_info->texture=AcquireString(montage_info->texture);
124 if (montage_info->font != (char *) NULL)
125 clone_info->font=AcquireString(montage_info->font);
126 clone_info->pointsize=montage_info->pointsize;
127 clone_info->border_width=montage_info->border_width;
128 clone_info->shadow=montage_info->shadow;
129 clone_info->fill=montage_info->fill;
130 clone_info->stroke=montage_info->stroke;
131 clone_info->matte_color=montage_info->matte_color;
132 clone_info->background_color=montage_info->background_color;
133 clone_info->border_color=montage_info->border_color;
134 clone_info->gravity=montage_info->gravity;
135 (void) CopyMagickString(clone_info->filename,montage_info->filename,
136 MagickPathExtent);
137 clone_info->debug=IsEventLogging();
138 return(clone_info);
139}
140
141/*
142%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
143% %
144% %
145% %
146% D e s t r o y M o n t a g e I n f o %
147% %
148% %
149% %
150%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
151%
152% DestroyMontageInfo() deallocates memory associated with montage_info.
153%
154% The format of the DestroyMontageInfo method is:
155%
156% MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
157%
158% A description of each parameter follows:
159%
160% o montage_info: Specifies a pointer to an MontageInfo structure.
161%
162%
163*/
164MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
165{
166 assert(montage_info != (MontageInfo *) NULL);
167 assert(montage_info->signature == MagickCoreSignature);
168 if (IsEventLogging() != MagickFalse)
169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
170 if (montage_info->geometry != (char *) NULL)
171 montage_info->geometry=(char *)
172 RelinquishMagickMemory(montage_info->geometry);
173 if (montage_info->tile != (char *) NULL)
174 montage_info->tile=DestroyString(montage_info->tile);
175 if (montage_info->title != (char *) NULL)
176 montage_info->title=DestroyString(montage_info->title);
177 if (montage_info->frame != (char *) NULL)
178 montage_info->frame=DestroyString(montage_info->frame);
179 if (montage_info->texture != (char *) NULL)
180 montage_info->texture=(char *) RelinquishMagickMemory(
181 montage_info->texture);
182 if (montage_info->font != (char *) NULL)
183 montage_info->font=DestroyString(montage_info->font);
184 montage_info->signature=(~MagickCoreSignature);
185 montage_info=(MontageInfo *) RelinquishMagickMemory(montage_info);
186 return(montage_info);
187}
188
189/*
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191% %
192% %
193% %
194% G e t M o n t a g e I n f o %
195% %
196% %
197% %
198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199%
200% GetMontageInfo() initializes montage_info to default values.
201%
202% The format of the GetMontageInfo method is:
203%
204% void GetMontageInfo(const ImageInfo *image_info,
205% MontageInfo *montage_info)
206%
207% A description of each parameter follows:
208%
209% o image_info: a structure of type ImageInfo.
210%
211% o montage_info: Specifies a pointer to a MontageInfo structure.
212%
213*/
214MagickExport void GetMontageInfo(const ImageInfo *image_info,
215 MontageInfo *montage_info)
216{
217 assert(image_info != (const ImageInfo *) NULL);
218 assert(image_info->signature == MagickCoreSignature);
219 assert(montage_info != (MontageInfo *) NULL);
220 if (IsEventLogging() != MagickFalse)
221 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
222 image_info->filename);
223 (void) memset(montage_info,0,sizeof(*montage_info));
224 (void) CopyMagickString(montage_info->filename,image_info->filename,
225 MagickPathExtent);
226 montage_info->geometry=AcquireString(DefaultTileGeometry);
227 if (image_info->font != (char *) NULL)
228 montage_info->font=AcquireString(image_info->font);
229 montage_info->gravity=CenterGravity;
230 montage_info->pointsize=image_info->pointsize;
231 montage_info->fill.alpha=(MagickRealType) OpaqueAlpha;
232 montage_info->stroke.alpha=(MagickRealType) TransparentAlpha;
233 montage_info->matte_color=image_info->matte_color;
234 montage_info->background_color=image_info->background_color;
235 montage_info->border_color=image_info->border_color;
236 montage_info->debug=IsEventLogging();
237 montage_info->signature=MagickCoreSignature;
238}
239
240/*
241%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
242% %
243% %
244% %
245% M o n t a g e I m a g e L i s t %
246% %
247% %
248% %
249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250%
251% MontageImageList() is a layout manager that lets you tile one or more
252% thumbnails across an image canvas.
253%
254% The format of the MontageImageList method is:
255%
256% Image *MontageImageList(const ImageInfo *image_info,
257% const MontageInfo *montage_info,Image *images,
258% ExceptionInfo *exception)
259%
260% A description of each parameter follows:
261%
262% o image_info: the image info.
263%
264% o montage_info: Specifies a pointer to a MontageInfo structure.
265%
266% o images: Specifies a pointer to an array of Image structures.
267%
268% o exception: return any errors or warnings in this structure.
269%
270*/
271
272static void GetMontageGeometry(char *geometry,const size_t number_images,
273 ssize_t *x_offset,ssize_t *y_offset,size_t *tiles_per_column,
274 size_t *tiles_per_row)
275{
276 *tiles_per_column=0;
277 *tiles_per_row=0;
278 (void) GetGeometry(geometry,x_offset,y_offset,tiles_per_row,tiles_per_column);
279 if ((*tiles_per_column == 0) && (*tiles_per_row == 0))
280 *tiles_per_column=(size_t) sqrt((double) number_images);
281 if ((*tiles_per_column == 0) && (*tiles_per_row != 0))
282 *tiles_per_column=(size_t) ceil((double) number_images/(*tiles_per_row));
283 if ((*tiles_per_row == 0) && (*tiles_per_column != 0))
284 *tiles_per_row=(size_t) ceil((double) number_images/(*tiles_per_column));
285}
286
287#if defined(__cplusplus) || defined(c_plusplus)
288extern "C" {
289#endif
290
291static int SceneCompare(const void *x,const void *y)
292{
293 Image
294 **image_1,
295 **image_2;
296
297 image_1=(Image **) x;
298 image_2=(Image **) y;
299 return((int) ((*image_1)->scene-(*image_2)->scene));
300}
301
302#if defined(__cplusplus) || defined(c_plusplus)
303}
304#endif
305
306MagickExport Image *MontageImages(const Image *images,
307 const MontageInfo *montage_info,ExceptionInfo *exception)
308{
309 Image
310 *montage_image;
311
313 *image_info;
314
315 image_info=AcquireImageInfo();
316 montage_image=MontageImageList(image_info,montage_info,images,exception);
317 image_info=DestroyImageInfo(image_info);
318 return(montage_image);
319}
320
321MagickExport Image *MontageImageList(const ImageInfo *image_info,
322 const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
323{
324#define MontageImageTag "Montage/Image"
325#define TileImageTag "Tile/Image"
326
327 char
328 tile_geometry[MagickPathExtent],
329 *title;
330
331 const char
332 *value;
333
335 *draw_info;
336
338 frame_info;
339
340 Image
341 *image,
342 **image_list,
343 **primary_list,
344 *montage,
345 *texture,
346 *tile_image,
347 *thumbnail;
348
350 *clone_info;
351
352 MagickBooleanType
353 concatenate,
354 proceed,
355 status;
356
357 MagickOffsetType
358 tiles;
359
360 MagickProgressMonitor
361 progress_monitor;
362
363 MagickStatusType
364 flags;
365
366 ssize_t
367 i;
368
370 bounds,
371 geometry,
372 extract_info;
373
374 size_t
375 border_width,
376 extent,
377 height,
378 images_per_page,
379 max_height,
380 number_images,
381 number_lines,
382 sans,
383 tiles_per_column,
384 tiles_per_page,
385 tiles_per_row,
386 title_offset,
387 total_tiles,
388 width;
389
390 ssize_t
391 bevel_width,
392 tile,
393 x,
394 x_offset,
395 y,
396 y_offset;
397
399 metrics;
400
401 /*
402 Create image tiles.
403 */
404 assert(images != (Image *) NULL);
405 assert(images->signature == MagickCoreSignature);
406 assert(montage_info != (MontageInfo *) NULL);
407 assert(montage_info->signature == MagickCoreSignature);
408 assert(exception != (ExceptionInfo *) NULL);
409 assert(exception->signature == MagickCoreSignature);
410 if (IsEventLogging() != MagickFalse)
411 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
412 number_images=GetImageListLength(images);
413 primary_list=ImageListToArray(images,exception);
414 if (primary_list == (Image **) NULL)
415 return((Image *) NULL);
416 image_list=primary_list;
417 image=image_list[0];
418 thumbnail=NewImageList();
419 for (i=0; i < (ssize_t) number_images; i++)
420 {
421 image=CloneImage(image_list[i],0,0,MagickTrue,exception);
422 if (image == (Image *) NULL)
423 break;
424 (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
425 progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
426 image->client_data);
427 flags=ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
428 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
429 if (thumbnail == (Image *) NULL)
430 break;
431 image_list[i]=thumbnail;
432 (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
433 proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
434 number_images);
435 if (proceed == MagickFalse)
436 break;
437 image=DestroyImage(image);
438 }
439 if (i < (ssize_t) number_images)
440 {
441 if (image != (Image *) NULL)
442 image=DestroyImage(image);
443 if (thumbnail == (Image *) NULL)
444 i--;
445 for (tile=0; (ssize_t) tile <= i; tile++)
446 if (image_list[tile] != (Image *) NULL)
447 image_list[tile]=DestroyImage(image_list[tile]);
448 primary_list=(Image **) RelinquishMagickMemory(primary_list);
449 return((Image *) NULL);
450 }
451 /*
452 Sort image list by increasing tile number.
453 */
454 for (i=0; i < (ssize_t) number_images; i++)
455 if (image_list[i]->scene == 0)
456 break;
457 if (i == (ssize_t) number_images)
458 qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
459 SceneCompare);
460 /*
461 Determine tiles per row and column.
462 */
463 tiles_per_column=(size_t) sqrt((double) number_images);
464 tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
465 x_offset=0;
466 y_offset=0;
467 if (montage_info->tile != (char *) NULL)
468 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
469 &tiles_per_column,&tiles_per_row);
470 /*
471 Determine tile sizes.
472 */
473 concatenate=MagickFalse;
474 SetGeometry(image_list[0],&extract_info);
475 extract_info.x=(ssize_t) montage_info->border_width;
476 extract_info.y=(ssize_t) montage_info->border_width;
477 if (montage_info->geometry != (char *) NULL)
478 {
479 /*
480 Initialize tile geometry.
481 */
482 flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
483 &extract_info.width,&extract_info.height);
484 concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
485 MagickTrue : MagickFalse;
486 }
487 border_width=montage_info->border_width;
488 bevel_width=0;
489 (void) memset(&frame_info,0,sizeof(frame_info));
490 if (montage_info->frame != (char *) NULL)
491 {
492 char
493 absolute_geometry[MagickPathExtent];
494
495 frame_info.width=extract_info.width;
496 frame_info.height=extract_info.height;
497 (void) FormatLocaleString(absolute_geometry,MagickPathExtent,"%s!",
498 montage_info->frame);
499 flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
500 &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
501 if ((flags & HeightValue) == 0)
502 frame_info.height=frame_info.width;
503 if ((flags & XiValue) == 0)
504 frame_info.outer_bevel=(ssize_t) frame_info.width/2-1;
505 if ((flags & PsiValue) == 0)
506 frame_info.inner_bevel=frame_info.outer_bevel;
507 frame_info.x=(ssize_t) frame_info.width;
508 frame_info.y=(ssize_t) frame_info.height;
509 bevel_width=(ssize_t) MagickMax(frame_info.inner_bevel,
510 frame_info.outer_bevel);
511 border_width=(size_t) MagickMax((ssize_t) frame_info.width,
512 (ssize_t) frame_info.height);
513 }
514 for (i=0; i < (ssize_t) number_images; i++)
515 {
516 if (image_list[i]->columns > extract_info.width)
517 extract_info.width=image_list[i]->columns;
518 if (image_list[i]->rows > extract_info.height)
519 extract_info.height=image_list[i]->rows;
520 }
521 /*
522 Initialize draw attributes.
523 */
524 clone_info=CloneImageInfo(image_info);
525 clone_info->background_color=montage_info->background_color;
526 clone_info->border_color=montage_info->border_color;
527 draw_info=CloneDrawInfo(clone_info,(DrawInfo *) NULL);
528 if (montage_info->font != (char *) NULL)
529 (void) CloneString(&draw_info->font,montage_info->font);
530 if (montage_info->pointsize != 0.0)
531 draw_info->pointsize=montage_info->pointsize;
532 draw_info->gravity=CenterGravity;
533 draw_info->stroke=montage_info->stroke;
534 draw_info->fill=montage_info->fill;
535 draw_info->text=AcquireString("");
536 (void) GetTypeMetrics(image_list[0],draw_info,&metrics,exception);
537 texture=NewImageList();
538 if (montage_info->texture != (char *) NULL)
539 {
540 (void) CopyMagickString(clone_info->filename,montage_info->texture,
541 MagickPathExtent);
542 texture=ReadImage(clone_info,exception);
543 }
544 /*
545 Determine the number of lines in an next label.
546 */
547 title=InterpretImageProperties(clone_info,image_list[0],montage_info->title,
548 exception);
549 title_offset=0;
550 if (montage_info->title != (char *) NULL)
551 title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
552 MultilineCensus(title)+2*extract_info.y);
553 number_lines=0;
554 for (i=0; i < (ssize_t) number_images; i++)
555 {
556 value=GetImageProperty(image_list[i],"label",exception);
557 if (value == (const char *) NULL)
558 continue;
559 if (MultilineCensus(value) > number_lines)
560 number_lines=MultilineCensus(value);
561 }
562 /*
563 Allocate next structure.
564 */
565 tile_image=AcquireImage((ImageInfo *) NULL,exception);
566 montage=AcquireImage(clone_info,exception);
567 montage->background_color=montage_info->background_color;
568 montage->scene=0;
569 images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
570 tiles=0;
571 total_tiles=(size_t) number_images;
572 for (i=0; i < (ssize_t) images_per_page; i++)
573 {
574 /*
575 Determine bounding box.
576 */
577 tiles_per_page=tiles_per_row*tiles_per_column;
578 x_offset=0;
579 y_offset=0;
580 if (montage_info->tile != (char *) NULL)
581 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
582 &sans,&sans);
583 tiles_per_page=tiles_per_row*tiles_per_column;
584 y_offset+=(ssize_t) title_offset;
585 max_height=0;
586 bounds.width=0;
587 bounds.height=0;
588 width=0;
589 for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
590 {
591 if (tile < (ssize_t) number_images)
592 {
593 width=concatenate != MagickFalse ? image_list[tile]->columns :
594 extract_info.width;
595 if (image_list[tile]->rows > max_height)
596 max_height=image_list[tile]->rows;
597 }
598 x_offset+=((ssize_t) width+2*(extract_info.x+(ssize_t) border_width));
599 if (x_offset > (ssize_t) bounds.width)
600 bounds.width=(size_t) x_offset;
601 if (((tile+1) == (ssize_t) tiles_per_page) ||
602 (((tile+1) % (ssize_t) tiles_per_row) == 0))
603 {
604 x_offset=0;
605 if (montage_info->tile != (char *) NULL)
606 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
607 &sans,&sans);
608 height=concatenate != MagickFalse ? max_height : extract_info.height;
609 y_offset+=((ssize_t) height+(extract_info.y+(ssize_t) border_width)*2+
610 (metrics.ascent-metrics.descent+4)*(ssize_t) number_lines+
611 (montage_info->shadow != MagickFalse ? 4 : 0));
612 if (y_offset > (ssize_t) bounds.height)
613 bounds.height=(size_t) y_offset;
614 max_height=0;
615 }
616 }
617 if (montage_info->shadow != MagickFalse)
618 bounds.width+=4;
619 /*
620 Initialize montage image.
621 */
622 (void) CopyMagickString(montage->filename,montage_info->filename,
623 MagickPathExtent);
624 montage->columns=(size_t) MagickMax((ssize_t) bounds.width,1);
625 montage->rows=(size_t) MagickMax((ssize_t) bounds.height,1);
626 (void) SetImageBackgroundColor(montage,exception);
627 /*
628 Set montage geometry.
629 */
630 montage->montage=AcquireString((char *) NULL);
631 tile=0;
632 extent=1;
633 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
634 {
635 extent+=strlen(image_list[tile]->filename)+1;
636 tile++;
637 }
638 montage->directory=(char *) AcquireQuantumMemory(extent,
639 sizeof(*montage->directory));
640 if ((montage->montage == (char *) NULL) ||
641 (montage->directory == (char *) NULL))
642 {
643 if (montage->montage != (char *) NULL)
644 montage->montage=(char *) RelinquishMagickMemory(montage->montage);
645 if (montage->directory != (char *) NULL)
646 montage->directory=(char *) RelinquishMagickMemory(
647 montage->directory);
648 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
649 }
650 x_offset=0;
651 y_offset=0;
652 if (montage_info->tile != (char *) NULL)
653 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
654 &sans,&sans);
655 y_offset+=(ssize_t) title_offset;
656 (void) FormatLocaleString(montage->montage,MagickPathExtent,
657 "%.20gx%.20g%+.20g%+.20g",(double) ((ssize_t) extract_info.width+
658 (extract_info.x+(ssize_t) border_width)*2),(double) ((ssize_t)
659 extract_info.height+(extract_info.y+(ssize_t) border_width)*2+(double)
660 ((metrics.ascent-metrics.descent+4)*number_lines+
661 (montage_info->shadow != MagickFalse ? 4 : 0))),(double) x_offset,
662 (double) y_offset);
663 *montage->directory='\0';
664 tile=0;
665 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
666 {
667 if (strchr(image_list[tile]->filename,(int) '\xff') == (char *) NULL)
668 (void) ConcatenateMagickString(montage->directory,
669 image_list[tile]->filename,extent);
670 else
671 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
672 "InvalidArgument","'%s'",image_list[tile]->filename);
673 (void) ConcatenateMagickString(montage->directory,"\xff",extent);
674 tile++;
675 }
676 progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
677 NULL,montage->client_data);
678 if (texture != (Image *) NULL)
679 (void) TextureImage(montage,texture,exception);
680 if (montage_info->title != (char *) NULL)
681 {
683 *draw_clone_info;
684
686 tile_metrics;
687
688 /*
689 Annotate composite image with title.
690 */
691 draw_clone_info=CloneDrawInfo(image_info,draw_info);
692 draw_clone_info->gravity=CenterGravity;
693 draw_clone_info->pointsize*=2.0;
694 (void) GetTypeMetrics(image_list[0],draw_clone_info,&tile_metrics,
695 exception);
696 (void) FormatLocaleString(tile_geometry,MagickPathExtent,
697 "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
698 (tile_metrics.ascent-tile_metrics.descent),0.0,
699 (double) extract_info.y+4);
700 (void) CloneString(&draw_clone_info->geometry,tile_geometry);
701 (void) CloneString(&draw_clone_info->text,title);
702 (void) AnnotateImage(montage,draw_clone_info,exception);
703 draw_clone_info=DestroyDrawInfo(draw_clone_info);
704 }
705 (void) SetImageProgressMonitor(montage,progress_monitor,
706 montage->client_data);
707 /*
708 Copy tile to the composite.
709 */
710 x_offset=0;
711 y_offset=0;
712 if (montage_info->tile != (char *) NULL)
713 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
714 &sans,&sans);
715 x_offset+=extract_info.x;
716 y_offset+=(ssize_t) title_offset+extract_info.y;
717 max_height=0;
718 status=MagickTrue;
719 for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
720 {
721 /*
722 Copy this tile to the composite.
723 */
724 image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
725 if (image == (Image *) NULL)
726 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
727 progress_monitor=SetImageProgressMonitor(image,
728 (MagickProgressMonitor) NULL,image->client_data);
729 width=concatenate != MagickFalse ? image->columns : extract_info.width;
730 if (image->rows > max_height)
731 max_height=image->rows;
732 height=concatenate != MagickFalse ? max_height : extract_info.height;
733 if (border_width != 0)
734 {
735 Image
736 *border_image;
737
739 border_info;
740
741 /*
742 Put a border around the image.
743 */
744 border_info.width=border_width;
745 border_info.height=border_width;
746 if (montage_info->frame != (char *) NULL)
747 {
748 border_info.width=(width-image->columns+1)/2;
749 border_info.height=(height-image->rows+1)/2;
750 }
751 border_image=BorderImage(image,&border_info,image->compose,exception);
752 if (border_image != (Image *) NULL)
753 {
754 image=DestroyImage(image);
755 image=border_image;
756 }
757 if ((montage_info->frame != (char *) NULL) &&
758 (image->compose == DstOutCompositeOp))
759 {
760 (void) SetPixelChannelMask(image,AlphaChannel);
761 (void) NegateImage(image,MagickFalse,exception);
762 (void) SetPixelChannelMask(image,DefaultChannels);
763 }
764 }
765 /*
766 Gravitate as specified by the tile gravity.
767 */
768 tile_image->columns=width;
769 tile_image->rows=height;
770 tile_image->gravity=montage_info->gravity;
771 if (image->gravity != UndefinedGravity)
772 tile_image->gravity=image->gravity;
773 (void) FormatLocaleString(tile_geometry,MagickPathExtent,
774 "%.20gx%.20g+0+0",(double) image->columns,(double) image->rows);
775 flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
776 x=geometry.x+(ssize_t) border_width;
777 y=geometry.y+(ssize_t) border_width;
778 if ((montage_info->frame != (char *) NULL) && (bevel_width > 0))
779 {
781 frame_clone;
782
783 Image
784 *frame_image;
785
786 /*
787 Put an ornamental border around this tile.
788 */
789 frame_clone=frame_info;
790 frame_clone.width=width+2*frame_info.width;
791 frame_clone.height=height+2*frame_info.height;
792 value=GetImageProperty(image,"label",exception);
793 if (value != (const char *) NULL)
794 frame_clone.height+=(size_t) ((metrics.ascent-metrics.descent+4)*
795 MultilineCensus(value));
796 frame_image=FrameImage(image,&frame_clone,image->compose,exception);
797 if (frame_image != (Image *) NULL)
798 {
799 image=DestroyImage(image);
800 image=frame_image;
801 }
802 x=0;
803 y=0;
804 }
805 if (LocaleCompare(image->magick,"NULL") != 0)
806 {
807 /*
808 Composite background with tile.
809 */
810 if (montage_info->shadow != MagickFalse)
811 {
812 Image
813 *shadow_image;
814
815 /*
816 Shadow image.
817 */
818 (void) QueryColorCompliance("#0000",AllCompliance,
819 &image->background_color,exception);
820 shadow_image=ShadowImage(image,30.0,5.0,5,5,exception);
821 if (shadow_image != (Image *) NULL)
822 {
823 (void) CompositeImage(shadow_image,image,OverCompositeOp,
824 MagickTrue,0,0,exception);
825 image=DestroyImage(image);
826 image=shadow_image;
827 }
828 }
829 (void) CompositeImage(montage,image,image->compose,MagickTrue,
830 x_offset+x,y_offset+y,exception);
831 value=GetImageProperty(image,"label",exception);
832 if (value != (const char *) NULL)
833 {
834 /*
835 Annotate composite tile with label.
836 */
837 (void) FormatLocaleString(tile_geometry,MagickPathExtent,
838 "%.20gx%.20g%+.20g%+.20g",(double) ((montage_info->frame ?
839 image->columns : width)-2*border_width),(double)
840 (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
841 (double) (x_offset+(ssize_t) border_width),(double)
842 ((montage_info->frame ? y_offset+(ssize_t) height+(ssize_t)
843 border_width+4 : y_offset+(ssize_t) extract_info.height+
844 (ssize_t) border_width+
845 (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
846 (void) CloneString(&draw_info->geometry,tile_geometry);
847 (void) CloneString(&draw_info->text,value);
848 (void) AnnotateImage(montage,draw_info,exception);
849 }
850 }
851 x_offset+=(ssize_t) width+2*(extract_info.x+(ssize_t) border_width);
852 if (((tile+1) == (ssize_t) tiles_per_page) ||
853 (((tile+1) % (ssize_t) tiles_per_row) == 0))
854 {
855 x_offset=extract_info.x;
856 y_offset+=((ssize_t) height+(extract_info.y+(ssize_t) border_width)*2+
857 (metrics.ascent-metrics.descent+4)*number_lines+
858 (montage_info->shadow != MagickFalse ? 4 : 0));
859 max_height=0;
860 }
861 if (images->progress_monitor != (MagickProgressMonitor) NULL)
862 {
863 proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
864 if (proceed == MagickFalse)
865 status=MagickFalse;
866 }
867 image_list[tile]=DestroyImage(image_list[tile]);
868 image=DestroyImage(image);
869 tiles++;
870 }
871 (void) status;
872 if ((i+1) < (ssize_t) images_per_page)
873 {
874 /*
875 Allocate next image structure.
876 */
877 AcquireNextImage(clone_info,montage,exception);
878 if (GetNextImageInList(montage) == (Image *) NULL)
879 {
880 montage=DestroyImageList(montage);
881 return((Image *) NULL);
882 }
883 montage=GetNextImageInList(montage);
884 montage->background_color=montage_info->background_color;
885 image_list+=tiles_per_page;
886 number_images-=tiles_per_page;
887 }
888 }
889 tile_image=DestroyImage(tile_image);
890 if (texture != (Image *) NULL)
891 texture=DestroyImage(texture);
892 title=DestroyString(title);
893 primary_list=(Image **) RelinquishMagickMemory(primary_list);
894 draw_info=DestroyDrawInfo(draw_info);
895 clone_info=DestroyImageInfo(clone_info);
896 return(GetFirstImageInList(montage));
897}