blob: 544a1468a83067f78b026072a88011fa820aad32 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
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% John Cristy %
17% July 1992 %
18% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% 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% http://www.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/fx.h"
56#include "magick/gem.h"
57#include "magick/geometry.h"
58#include "magick/image.h"
59#include "magick/image-private.h"
60#include "magick/list.h"
61#include "magick/memory_.h"
62#include "magick/monitor.h"
63#include "magick/monitor-private.h"
64#include "magick/montage.h"
65#include "magick/option.h"
66#include "magick/quantize.h"
67#include "magick/property.h"
68#include "magick/resize.h"
69#include "magick/resource_.h"
70#include "magick/string_.h"
71#include "magick/utility.h"
72#include "magick/version.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*/
101MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
102 const MontageInfo *montage_info)
103{
104 MontageInfo
105 *clone_info;
106
cristy73bd4a52010-10-05 11:24:23 +0000107 clone_info=(MontageInfo *) AcquireMagickMemory(sizeof(*clone_info));
cristy3ed852e2009-09-05 21:47:34 +0000108 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*/
163MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
164{
165 if (montage_info->debug != MagickFalse)
166 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
167 assert(montage_info != (MontageInfo *) NULL);
168 assert(montage_info->signature == MagickSignature);
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=(~MagickSignature);
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*/
213MagickExport void GetMontageInfo(const ImageInfo *image_info,
214 MontageInfo *montage_info)
215{
216 assert(image_info != (const ImageInfo *) NULL);
217 assert(image_info->signature == MagickSignature);
218 if (image_info->debug != MagickFalse)
219 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
220 image_info->filename);
221 assert(montage_info != (MontageInfo *) NULL);
222 (void) ResetMagickMemory(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=MagickSignature;
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
cristybb503372010-05-27 20:51:26 +0000271static 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)
cristy3ed852e2009-09-05 21:47:34 +0000274{
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))
cristybb503372010-05-27 20:51:26 +0000279 *tiles_per_column=(size_t) sqrt((double) number_images);
cristy3ed852e2009-09-05 21:47:34 +0000280 if (*tiles_per_column == 0)
cristybb503372010-05-27 20:51:26 +0000281 *tiles_per_column=(size_t)
cristy3ed852e2009-09-05 21:47:34 +0000282 ceil((double) number_images/(*tiles_per_row));
283 if (*tiles_per_row == 0)
cristybb503372010-05-27 20:51:26 +0000284 *tiles_per_row=(size_t)
cristy3ed852e2009-09-05 21:47:34 +0000285 ceil((double) number_images/(*tiles_per_column));
286}
287
cristybb503372010-05-27 20:51:26 +0000288static inline ssize_t MagickMax(const ssize_t x,const ssize_t y)
cristy3ed852e2009-09-05 21:47:34 +0000289{
290 if (x > y)
291 return(x);
292 return(y);
293}
294
cristybb503372010-05-27 20:51:26 +0000295static inline ssize_t MagickMin(const ssize_t x,const ssize_t y)
cristy3ed852e2009-09-05 21:47:34 +0000296{
297 if (x < y)
298 return(x);
299 return(y);
300}
301
302#if defined(__cplusplus) || defined(c_plusplus)
303extern "C" {
304#endif
305
306static int SceneCompare(const void *x,const void *y)
307{
308 Image
309 **image_1,
310 **image_2;
311
312 image_1=(Image **) x;
313 image_2=(Image **) y;
314 return((int) ((*image_1)->scene-(*image_2)->scene));
315}
316
317#if defined(__cplusplus) || defined(c_plusplus)
318}
319#endif
320
321MagickExport Image *MontageImages(const Image *images,
322 const MontageInfo *montage_info,ExceptionInfo *exception)
323{
324 Image
325 *montage_image;
326
327 ImageInfo
328 *image_info;
329
330 image_info=AcquireImageInfo();
331 montage_image=MontageImageList(image_info,montage_info,images,exception);
332 image_info=DestroyImageInfo(image_info);
333 return(montage_image);
334}
335
336MagickExport Image *MontageImageList(const ImageInfo *image_info,
337 const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
338{
339#define MontageImageTag "Montage/Image"
340#define TileImageTag "Tile/Image"
341
342 char
343 tile_geometry[MaxTextExtent],
344 *title;
345
346 const char
347 *value;
348
349 DrawInfo
350 *draw_info;
351
352 FrameInfo
353 frame_info;
354
355 Image
356 *image,
357 **image_list,
358 **master_list,
359 *montage,
360 *texture,
361 *tile_image,
362 *thumbnail;
363
364 ImageInfo
365 *clone_info;
366
cristybb503372010-05-27 20:51:26 +0000367 ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000368 tile,
369 x,
370 x_offset,
371 y,
372 y_offset;
373
374 MagickBooleanType
375 concatenate,
376 proceed,
377 status;
378
379 MagickOffsetType
380 tiles;
381
382 MagickStatusType
383 flags;
384
385 MagickProgressMonitor
386 progress_monitor;
387
cristybb503372010-05-27 20:51:26 +0000388 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000389 i;
390
391 RectangleInfo
392 bounds,
393 geometry,
394 extract_info;
395
396 size_t
397 extent;
398
399 TypeMetric
400 metrics;
401
cristybb503372010-05-27 20:51:26 +0000402 size_t
cristy3ed852e2009-09-05 21:47:34 +0000403 bevel_width,
404 border_width,
405 height,
406 images_per_page,
407 max_height,
408 number_images,
409 number_lines,
410 sans,
411 tiles_per_column,
412 tiles_per_page,
413 tiles_per_row,
414 title_offset,
415 total_tiles,
416 width;
417
418 /*
419 Create image tiles.
420 */
421 assert(images != (Image *) NULL);
422 assert(images->signature == MagickSignature);
423 if (images->debug != MagickFalse)
424 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
425 assert(montage_info != (MontageInfo *) NULL);
426 assert(montage_info->signature == MagickSignature);
427 assert(exception != (ExceptionInfo *) NULL);
428 assert(exception->signature == MagickSignature);
429 number_images=GetImageListLength(images);
430 master_list=ImageListToArray(images,exception);
431 image_list=master_list;
432 image=image_list[0];
433 if (master_list == (Image **) NULL)
434 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
435 thumbnail=NewImageList();
cristybb503372010-05-27 20:51:26 +0000436 for (i=0; i < (ssize_t) number_images; i++)
cristy3ed852e2009-09-05 21:47:34 +0000437 {
438 image=CloneImage(image_list[i],0,0,MagickTrue,exception);
439 if (image == (Image *) NULL)
440 break;
441 (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
442 progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
443 image->client_data);
444 flags=ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
445 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
446 if (thumbnail == (Image *) NULL)
447 break;
448 image_list[i]=thumbnail;
449 (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
cristybb503372010-05-27 20:51:26 +0000450 proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
451 number_images);
cristy3ed852e2009-09-05 21:47:34 +0000452 if (proceed == MagickFalse)
453 break;
454 image=DestroyImage(image);
455 }
cristybb503372010-05-27 20:51:26 +0000456 if (i < (ssize_t) number_images)
cristy3ed852e2009-09-05 21:47:34 +0000457 {
458 if (thumbnail == (Image *) NULL)
459 i--;
cristybb503372010-05-27 20:51:26 +0000460 for (tile=0; (ssize_t) tile <= i; tile++)
cristy3ed852e2009-09-05 21:47:34 +0000461 if (image_list[tile] != (Image *) NULL)
462 image_list[tile]=DestroyImage(image_list[tile]);
463 master_list=(Image **) RelinquishMagickMemory(master_list);
464 return((Image *) NULL);
465 }
466 /*
467 Sort image list by increasing tile number.
468 */
cristybb503372010-05-27 20:51:26 +0000469 for (i=0; i < (ssize_t) number_images; i++)
cristy3ed852e2009-09-05 21:47:34 +0000470 if (image_list[i]->scene == 0)
471 break;
cristybb503372010-05-27 20:51:26 +0000472 if (i == (ssize_t) number_images)
cristy3ed852e2009-09-05 21:47:34 +0000473 qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
474 SceneCompare);
475 /*
476 Determine tiles per row and column.
477 */
cristybb503372010-05-27 20:51:26 +0000478 tiles_per_column=(size_t) sqrt((double) number_images);
479 tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
cristy3ed852e2009-09-05 21:47:34 +0000480 x_offset=0;
481 y_offset=0;
482 if (montage_info->tile != (char *) NULL)
483 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
484 &tiles_per_column,&tiles_per_row);
485 /*
486 Determine tile sizes.
487 */
488 concatenate=MagickFalse;
489 SetGeometry(image_list[0],&extract_info);
cristybb503372010-05-27 20:51:26 +0000490 extract_info.x=(ssize_t) montage_info->border_width;
491 extract_info.y=(ssize_t) montage_info->border_width;
cristy3ed852e2009-09-05 21:47:34 +0000492 if (montage_info->geometry != (char *) NULL)
493 {
494 /*
495 Initialize tile geometry.
496 */
497 flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
498 &extract_info.width,&extract_info.height);
499 if ((extract_info.x == 0) && (extract_info.y == 0))
500 concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
501 MagickTrue : MagickFalse;
502 }
503 border_width=montage_info->border_width;
504 bevel_width=0;
505 if (montage_info->frame != (char *) NULL)
506 {
507 char
508 absolute_geometry[MaxTextExtent];
509
510 (void) ResetMagickMemory(&frame_info,0,sizeof(frame_info));
511 frame_info.width=extract_info.width;
512 frame_info.height=extract_info.height;
513 (void) FormatMagickString(absolute_geometry,MaxTextExtent,"%s!",
514 montage_info->frame);
515 flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
516 &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
517 if ((flags & HeightValue) == 0)
518 frame_info.height=frame_info.width;
519 if ((flags & XiValue) == 0)
cristybb503372010-05-27 20:51:26 +0000520 frame_info.outer_bevel=(ssize_t) frame_info.width/2;
cristy3ed852e2009-09-05 21:47:34 +0000521 if ((flags & PsiValue) == 0)
522 frame_info.inner_bevel=frame_info.outer_bevel;
cristybb503372010-05-27 20:51:26 +0000523 frame_info.x=(ssize_t) frame_info.width;
524 frame_info.y=(ssize_t) frame_info.height;
525 bevel_width=(size_t) MagickMax(frame_info.inner_bevel,
cristy3ed852e2009-09-05 21:47:34 +0000526 frame_info.outer_bevel);
cristybb503372010-05-27 20:51:26 +0000527 border_width=(size_t) MagickMax((ssize_t) frame_info.width,
528 (ssize_t) frame_info.height);
cristy3ed852e2009-09-05 21:47:34 +0000529 }
cristybb503372010-05-27 20:51:26 +0000530 for (i=0; i < (ssize_t) number_images; i++)
cristy3ed852e2009-09-05 21:47:34 +0000531 {
532 if (image_list[i]->columns > extract_info.width)
533 extract_info.width=image_list[i]->columns;
534 if (image_list[i]->rows > extract_info.height)
535 extract_info.height=image_list[i]->rows;
536 }
537 /*
538 Initialize draw attributes.
539 */
540 clone_info=CloneImageInfo(image_info);
541 clone_info->background_color=montage_info->background_color;
542 clone_info->border_color=montage_info->border_color;
543 draw_info=CloneDrawInfo(clone_info,(DrawInfo *) NULL);
544 if (montage_info->font != (char *) NULL)
545 (void) CloneString(&draw_info->font,montage_info->font);
546 if (montage_info->pointsize != 0.0)
547 draw_info->pointsize=montage_info->pointsize;
548 draw_info->gravity=CenterGravity;
549 draw_info->stroke=montage_info->stroke;
550 draw_info->fill=montage_info->fill;
551 draw_info->text=AcquireString("");
552 (void) GetTypeMetrics(image_list[0],draw_info,&metrics);
553 texture=NewImageList();
554 if (montage_info->texture != (char *) NULL)
555 {
556 (void) CopyMagickString(clone_info->filename,montage_info->texture,
557 MaxTextExtent);
558 texture=ReadImage(clone_info,exception);
559 }
560 /*
561 Determine the number of lines in an next label.
562 */
563 title=InterpretImageProperties(clone_info,image_list[0],montage_info->title);
564 title_offset=0;
565 if (montage_info->title != (char *) NULL)
cristybb503372010-05-27 20:51:26 +0000566 title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
cristy3ed852e2009-09-05 21:47:34 +0000567 MultilineCensus(title)+2*extract_info.y);
568 number_lines=0;
cristybb503372010-05-27 20:51:26 +0000569 for (i=0; i < (ssize_t) number_images; i++)
cristy3ed852e2009-09-05 21:47:34 +0000570 {
571 value=GetImageProperty(image_list[i],"label");
572 if (value == (const char *) NULL)
573 continue;
574 if (MultilineCensus(value) > number_lines)
575 number_lines=MultilineCensus(value);
576 }
577 /*
578 Allocate next structure.
579 */
580 tile_image=AcquireImage(NULL);
581 montage=AcquireImage(clone_info);
cristy44b776d2010-06-23 16:28:36 +0000582 montage->background_color=montage_info->background_color;
cristy3ed852e2009-09-05 21:47:34 +0000583 montage->scene=0;
584 images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
585 tiles=0;
cristybb503372010-05-27 20:51:26 +0000586 total_tiles=(size_t) number_images;
587 for (i=0; i < (ssize_t) images_per_page; i++)
cristy3ed852e2009-09-05 21:47:34 +0000588 {
589 /*
590 Determine bounding box.
591 */
592 tiles_per_page=tiles_per_row*tiles_per_column;
593 x_offset=0;
594 y_offset=0;
595 if (montage_info->tile != (char *) NULL)
596 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
597 &sans,&sans);
598 tiles_per_page=tiles_per_row*tiles_per_column;
cristybb503372010-05-27 20:51:26 +0000599 y_offset+=(ssize_t) title_offset;
cristy3ed852e2009-09-05 21:47:34 +0000600 max_height=0;
601 bounds.width=0;
602 bounds.height=0;
603 width=0;
cristybb503372010-05-27 20:51:26 +0000604 for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
cristy3ed852e2009-09-05 21:47:34 +0000605 {
cristybb503372010-05-27 20:51:26 +0000606 if (tile < (ssize_t) number_images)
cristy3ed852e2009-09-05 21:47:34 +0000607 {
608 width=concatenate != MagickFalse ? image_list[tile]->columns :
609 extract_info.width;
610 if (image_list[tile]->rows > max_height)
611 max_height=image_list[tile]->rows;
612 }
cristyd99b0962010-05-29 23:14:26 +0000613 x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
cristybb503372010-05-27 20:51:26 +0000614 if (x_offset > (ssize_t) bounds.width)
615 bounds.width=(size_t) x_offset;
616 if (((tile+1) == (ssize_t) tiles_per_page) ||
cristy3ed852e2009-09-05 21:47:34 +0000617 (((tile+1) % tiles_per_row) == 0))
618 {
619 x_offset=0;
620 if (montage_info->tile != (char *) NULL)
621 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
622 &sans,&sans);
623 height=concatenate != MagickFalse ? max_height : extract_info.height;
cristyd99b0962010-05-29 23:14:26 +0000624 y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
cristy3ed852e2009-09-05 21:47:34 +0000625 (metrics.ascent-metrics.descent+4)*number_lines+
626 (montage_info->shadow != MagickFalse ? 4 : 0));
cristybb503372010-05-27 20:51:26 +0000627 if (y_offset > (ssize_t) bounds.height)
628 bounds.height=(size_t) y_offset;
cristy3ed852e2009-09-05 21:47:34 +0000629 max_height=0;
630 }
631 }
632 if (montage_info->shadow != MagickFalse)
633 bounds.width+=4;
634 /*
635 Initialize montage image.
636 */
637 (void) CopyMagickString(montage->filename,montage_info->filename,
638 MaxTextExtent);
639 montage->columns=bounds.width;
640 montage->rows=bounds.height;
641 (void) SetImageBackgroundColor(montage);
642 /*
643 Set montage geometry.
644 */
645 montage->montage=AcquireString((char *) NULL);
646 tile=0;
647 extent=1;
cristybb503372010-05-27 20:51:26 +0000648 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
cristy3ed852e2009-09-05 21:47:34 +0000649 {
650 extent+=strlen(image_list[tile]->filename)+1;
651 tile++;
652 }
653 montage->directory=(char *) AcquireQuantumMemory(extent,
654 sizeof(*montage->directory));
655 if ((montage->montage == (char *) NULL) ||
656 (montage->directory == (char *) NULL))
657 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
658 x_offset=0;
659 y_offset=0;
660 if (montage_info->tile != (char *) NULL)
661 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
662 &sans,&sans);
cristybb503372010-05-27 20:51:26 +0000663 y_offset+=(ssize_t) title_offset;
cristye8c25f92010-06-03 00:53:06 +0000664 (void) FormatMagickString(montage->montage,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +0000665 "%.20gx%.20g%+.20g%+.20g",(double) (extract_info.width+
cristye8c25f92010-06-03 00:53:06 +0000666 (extract_info.x+border_width)*2),(double) (extract_info.height+
667 (extract_info.y+border_width)*2+(double) ((metrics.ascent-
668 metrics.descent+4)*number_lines+(montage_info->shadow != MagickFalse ? 4 :
669 0))),(double) x_offset,(double) y_offset);
cristy3ed852e2009-09-05 21:47:34 +0000670 *montage->directory='\0';
671 tile=0;
cristybb503372010-05-27 20:51:26 +0000672 while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
cristy3ed852e2009-09-05 21:47:34 +0000673 {
674 (void) ConcatenateMagickString(montage->directory,
675 image_list[tile]->filename,extent);
676 (void) ConcatenateMagickString(montage->directory,"\n",extent);
677 tile++;
678 }
679 progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
680 NULL,montage->client_data);
681 if (texture != (Image *) NULL)
682 (void) TextureImage(montage,texture);
683 if (montage_info->title != (char *) NULL)
684 {
685 char
686 geometry[MaxTextExtent];
687
688 DrawInfo
689 *clone_info;
690
691 TypeMetric
692 metrics;
693
694 /*
695 Annotate composite image with title.
696 */
697 clone_info=CloneDrawInfo(image_info,draw_info);
698 clone_info->gravity=CenterGravity;
699 clone_info->pointsize*=2.0;
700 (void) GetTypeMetrics(image_list[0],clone_info,&metrics);
cristye8c25f92010-06-03 00:53:06 +0000701 (void) FormatMagickString(geometry,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +0000702 "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
cristye8c25f92010-06-03 00:53:06 +0000703 (metrics.ascent-metrics.descent),0.0,(double) extract_info.y+4);
cristy3ed852e2009-09-05 21:47:34 +0000704 (void) CloneString(&clone_info->geometry,geometry);
705 (void) CloneString(&clone_info->text,title);
706 (void) AnnotateImage(montage,clone_info);
707 clone_info=DestroyDrawInfo(clone_info);
708 }
709 (void) SetImageProgressMonitor(montage,progress_monitor,
710 montage->client_data);
711 /*
712 Copy tile to the composite.
713 */
714 x_offset=0;
715 y_offset=0;
716 if (montage_info->tile != (char *) NULL)
717 GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
718 &sans,&sans);
719 x_offset+=extract_info.x;
cristybb503372010-05-27 20:51:26 +0000720 y_offset+=(ssize_t) title_offset+extract_info.y;
cristy3ed852e2009-09-05 21:47:34 +0000721 max_height=0;
cristybb503372010-05-27 20:51:26 +0000722 for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
cristy3ed852e2009-09-05 21:47:34 +0000723 {
724 /*
725 Copy this tile to the composite.
726 */
727 image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
728 progress_monitor=SetImageProgressMonitor(image,
729 (MagickProgressMonitor) NULL,image->client_data);
730 width=concatenate != MagickFalse ? image->columns : extract_info.width;
731 if (image->rows > max_height)
732 max_height=image->rows;
733 height=concatenate != MagickFalse ? max_height : extract_info.height;
734 if (border_width != 0)
735 {
736 Image
737 *border_image;
738
739 RectangleInfo
740 border_info;
741
742 /*
743 Put a border around the image.
744 */
745 border_info.width=border_width;
746 border_info.height=border_width;
747 if (montage_info->frame != (char *) NULL)
748 {
749 border_info.width=(width-image->columns+1)/2;
750 border_info.height=(height-image->rows+1)/2;
751 }
752 border_image=BorderImage(image,&border_info,exception);
753 if (border_image != (Image *) NULL)
754 {
755 image=DestroyImage(image);
756 image=border_image;
757 }
758 if ((montage_info->frame != (char *) NULL) &&
759 (image->compose == DstOutCompositeOp))
760 (void) NegateImageChannel(image,OpacityChannel,MagickFalse);
761 }
762 /*
763 Gravitate as specified by the tile gravity.
764 */
765 tile_image->columns=width;
766 tile_image->rows=height;
767 tile_image->gravity=montage_info->gravity;
768 if (image->gravity != UndefinedGravity)
769 tile_image->gravity=image->gravity;
cristye8c25f92010-06-03 00:53:06 +0000770 (void) FormatMagickString(tile_geometry,MaxTextExtent,"%.20gx%.20g+0+0",
771 (double) image->columns,(double) image->rows);
cristy3ed852e2009-09-05 21:47:34 +0000772 flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
cristybb503372010-05-27 20:51:26 +0000773 x=(ssize_t) (geometry.x+border_width);
774 y=(ssize_t) (geometry.y+border_width);
cristy3ed852e2009-09-05 21:47:34 +0000775 if ((montage_info->frame != (char *) NULL) && (bevel_width != 0))
776 {
777 FrameInfo
778 extract_info;
779
780 Image
781 *frame_image;
782
783 /*
784 Put an ornamental border around this tile.
785 */
786 extract_info=frame_info;
787 extract_info.width=width+2*frame_info.width;
788 extract_info.height=height+2*frame_info.height;
789 value=GetImageProperty(image,"label");
790 if (value != (const char *) NULL)
cristybb503372010-05-27 20:51:26 +0000791 extract_info.height+=(size_t) ((metrics.ascent-
cristy3ed852e2009-09-05 21:47:34 +0000792 metrics.descent+4)*MultilineCensus(value));
793 frame_image=FrameImage(image,&extract_info,exception);
794 if (frame_image != (Image *) NULL)
795 {
796 image=DestroyImage(image);
797 image=frame_image;
798 }
799 x=0;
800 y=0;
801 }
802 if (LocaleCompare(image->magick,"NULL") != 0)
803 {
804 /*
805 Composite background with tile.
806 */
807 if (montage_info->shadow != MagickFalse)
808 {
809 Image
810 *shadow_image;
811
812 /*
813 Shadow image.
814 */
815 (void) QueryColorDatabase("#000000",&image->background_color,
816 exception);
817 shadow_image=ShadowImage(image,80.0,2.0,5,5,exception);
818 if (shadow_image != (Image *) NULL)
819 {
820 InheritException(&shadow_image->exception,exception);
821 (void) CompositeImage(shadow_image,OverCompositeOp,image,0,0);
822 image=DestroyImage(image);
823 image=shadow_image;
824 }
825 }
cristyd5add0a2010-04-24 18:59:33 +0000826 (void) CompositeImage(montage,image->compose,image,x_offset+x,
cristy3ed852e2009-09-05 21:47:34 +0000827 y_offset+y);
828 value=GetImageProperty(image,"label");
829 if (value != (const char *) NULL)
830 {
831 char
832 geometry[MaxTextExtent];
833
834 /*
835 Annotate composite tile with label.
836 */
837 (void) FormatMagickString(geometry,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +0000838 "%.20gx%.20g%+.20g%+.20g",(double) ((montage_info->frame ?
cristye8c25f92010-06-03 00:53:06 +0000839 image->columns : width)-2*border_width),(double)
cristyf2faecf2010-05-28 19:19:36 +0000840 (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
cristye8c25f92010-06-03 00:53:06 +0000841 (double) (x_offset+border_width),(double)
842 ((montage_info->frame ? y_offset+height+border_width+4 :
843 y_offset+extract_info.height+border_width+
844 (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
cristy3ed852e2009-09-05 21:47:34 +0000845 (void) CloneString(&draw_info->geometry,geometry);
846 (void) CloneString(&draw_info->text,value);
847 (void) AnnotateImage(montage,draw_info);
848 }
849 }
cristyd99b0962010-05-29 23:14:26 +0000850 x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
cristybb503372010-05-27 20:51:26 +0000851 if (((tile+1) == (ssize_t) tiles_per_page) ||
cristy3ed852e2009-09-05 21:47:34 +0000852 (((tile+1) % tiles_per_row) == 0))
853 {
854 x_offset=extract_info.x;
cristyd99b0962010-05-29 23:14:26 +0000855 y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
cristy3ed852e2009-09-05 21:47:34 +0000856 (metrics.ascent-metrics.descent+4)*number_lines+
857 (montage_info->shadow != MagickFalse ? 4 : 0));
858 max_height=0;
859 }
cristy8b27a6d2010-02-14 03:31:15 +0000860 if (images->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000861 {
cristy8b27a6d2010-02-14 03:31:15 +0000862 MagickBooleanType
863 proceed;
864
865 proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
866 if (proceed == MagickFalse)
867 status=MagickFalse;
cristy3ed852e2009-09-05 21:47:34 +0000868 }
869 image_list[tile]=DestroyImage(image_list[tile]);
870 image=DestroyImage(image);
871 tiles++;
872 }
cristybb503372010-05-27 20:51:26 +0000873 if ((i+1) < (ssize_t) images_per_page)
cristy3ed852e2009-09-05 21:47:34 +0000874 {
875 /*
876 Allocate next image structure.
877 */
878 AcquireNextImage(clone_info,montage);
879 if (GetNextImageInList(montage) == (Image *) NULL)
880 {
881 montage=DestroyImageList(montage);
882 return((Image *) NULL);
883 }
884 montage=GetNextImageInList(montage);
cristy44b776d2010-06-23 16:28:36 +0000885 montage->background_color=montage_info->background_color;
cristy3ed852e2009-09-05 21:47:34 +0000886 image_list+=tiles_per_page;
887 number_images-=tiles_per_page;
888 }
889 }
890 tile_image=DestroyImage(tile_image);
891 if (texture != (Image *) NULL)
892 texture=DestroyImage(texture);
893 master_list=(Image **) RelinquishMagickMemory(master_list);
894 draw_info=DestroyDrawInfo(draw_info);
895 clone_info=DestroyImageInfo(clone_info);
cristy44b776d2010-06-23 16:28:36 +0000896 return(GetFirstImageInList(montage));
cristy3ed852e2009-09-05 21:47:34 +0000897}