]> granicus.if.org Git - imagemagick/blob - MagickCore/montage.c
62c4633a17232fba0e8e331563014e4473e02418
[imagemagick] / MagickCore / 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 %                                John Cristy                                  %
17 %                                 July 1992                                   %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2011 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 %    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 \f
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/fx.h"
56 #include "MagickCore/gem.h"
57 #include "MagickCore/geometry.h"
58 #include "MagickCore/image.h"
59 #include "MagickCore/image-private.h"
60 #include "MagickCore/list.h"
61 #include "MagickCore/memory_.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/quantize.h"
67 #include "MagickCore/property.h"
68 #include "MagickCore/resize.h"
69 #include "MagickCore/resource_.h"
70 #include "MagickCore/string_.h"
71 #include "MagickCore/utility.h"
72 #include "MagickCore/utility-private.h"
73 #include "MagickCore/version.h"
74 \f
75 /*
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
77 %                                                                             %
78 %                                                                             %
79 %                                                                             %
80 %   C l o n e M o n t a g e I n f o                                           %
81 %                                                                             %
82 %                                                                             %
83 %                                                                             %
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
85 %
86 %  CloneMontageInfo() makes a copy of the given montage info structure.  If
87 %  NULL is specified, a new image info structure is created initialized to
88 %  default values.
89 %
90 %  The format of the CloneMontageInfo method is:
91 %
92 %      MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
93 %        const MontageInfo *montage_info)
94 %
95 %  A description of each parameter follows:
96 %
97 %    o image_info: the image info.
98 %
99 %    o montage_info: the montage info.
100 %
101 */
102 MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
103   const MontageInfo *montage_info)
104 {
105   MontageInfo
106     *clone_info;
107
108   clone_info=(MontageInfo *) AcquireMagickMemory(sizeof(*clone_info));
109   if (clone_info == (MontageInfo *) NULL)
110     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
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->background_color=montage_info->background_color;
132   clone_info->border_color=montage_info->border_color;
133   clone_info->matte_color=montage_info->matte_color;
134   clone_info->gravity=montage_info->gravity;
135   (void) CopyMagickString(clone_info->filename,montage_info->filename,
136     MaxTextExtent);
137   clone_info->debug=IsEventLogging();
138   return(clone_info);
139 }
140 \f
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 */
164 MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
165 {
166   if (montage_info->debug != MagickFalse)
167     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
168   assert(montage_info != (MontageInfo *) NULL);
169   assert(montage_info->signature == MagickSignature);
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=(~MagickSignature);
185   montage_info=(MontageInfo *) RelinquishMagickMemory(montage_info);
186   return(montage_info);
187 }
188 \f
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 */
214 MagickExport void GetMontageInfo(const ImageInfo *image_info,
215   MontageInfo *montage_info)
216 {
217   assert(image_info != (const ImageInfo *) NULL);
218   assert(image_info->signature == MagickSignature);
219   if (image_info->debug != MagickFalse)
220     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
221       image_info->filename);
222   assert(montage_info != (MontageInfo *) NULL);
223   (void) ResetMagickMemory(montage_info,0,sizeof(*montage_info));
224   (void) CopyMagickString(montage_info->filename,image_info->filename,
225     MaxTextExtent);
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=OpaqueAlpha;
232   montage_info->stroke.alpha=(Quantum) TransparentAlpha;
233   montage_info->background_color=image_info->background_color;
234   montage_info->border_color=image_info->border_color;
235   montage_info->matte_color=image_info->matte_color;
236   montage_info->debug=IsEventLogging();
237   montage_info->signature=MagickSignature;
238 }
239 \f
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
272 static 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)
282     *tiles_per_column=(size_t)
283       ceil((double) number_images/(*tiles_per_row));
284   if (*tiles_per_row == 0)
285     *tiles_per_row=(size_t)
286       ceil((double) number_images/(*tiles_per_column));
287 }
288
289 static inline ssize_t MagickMax(const ssize_t x,const ssize_t y)
290 {
291   if (x > y)
292     return(x);
293   return(y);
294 }
295
296 static inline ssize_t MagickMin(const ssize_t x,const ssize_t y)
297 {
298   if (x < y)
299     return(x);
300   return(y);
301 }
302
303 #if defined(__cplusplus) || defined(c_plusplus)
304 extern "C" {
305 #endif
306
307 static int SceneCompare(const void *x,const void *y)
308 {
309   Image
310     **image_1,
311     **image_2;
312
313   image_1=(Image **) x;
314   image_2=(Image **) y;
315   return((int) ((*image_1)->scene-(*image_2)->scene));
316 }
317
318 #if defined(__cplusplus) || defined(c_plusplus)
319 }
320 #endif
321
322 MagickExport Image *MontageImages(const Image *images,
323   const MontageInfo *montage_info,ExceptionInfo *exception)
324 {
325   Image
326     *montage_image;
327
328   ImageInfo
329     *image_info;
330
331   image_info=AcquireImageInfo();
332   montage_image=MontageImageList(image_info,montage_info,images,exception);
333   image_info=DestroyImageInfo(image_info);
334   return(montage_image);
335 }
336
337 MagickExport Image *MontageImageList(const ImageInfo *image_info,
338   const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
339 {
340 #define MontageImageTag  "Montage/Image"
341 #define TileImageTag  "Tile/Image"
342
343   char
344     tile_geometry[MaxTextExtent],
345     *title;
346
347   const char
348     *value;
349
350   DrawInfo
351     *draw_info;
352
353   FrameInfo
354     frame_info;
355
356   Image
357     *image,
358     **image_list,
359     **master_list,
360     *montage,
361     *texture,
362     *tile_image,
363     *thumbnail;
364
365   ImageInfo
366     *clone_info;
367
368   MagickBooleanType
369     concatenate,
370     proceed,
371     status;
372
373   MagickOffsetType
374     tiles;
375
376   MagickProgressMonitor
377     progress_monitor;
378
379   MagickStatusType
380     flags;
381
382   register ssize_t
383     i;
384
385   RectangleInfo
386     bounds,
387     geometry,
388     extract_info;
389
390
391   size_t
392     bevel_width,
393     border_width,
394     extent,
395     height,
396     images_per_page,
397     max_height,
398     number_images,
399     number_lines,
400     sans,
401     tiles_per_column,
402     tiles_per_page,
403     tiles_per_row,
404     title_offset,
405     total_tiles,
406     width;
407
408   ssize_t
409     tile,
410     x,
411     x_offset,
412     y,
413     y_offset;
414
415   TypeMetric
416     metrics;
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();
436   for (i=0; i < (ssize_t) number_images; i++)
437   {
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);
450     proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
451       number_images);
452     if (proceed == MagickFalse)
453       break;
454     image=DestroyImage(image);
455   }
456   if (i < (ssize_t) number_images)
457     {
458       if (thumbnail == (Image *) NULL)
459         i--;
460       for (tile=0; (ssize_t) tile <= i; tile++)
461         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   */
469   for (i=0; i < (ssize_t) number_images; i++)
470     if (image_list[i]->scene == 0)
471       break;
472   if (i == (ssize_t) number_images)
473     qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
474       SceneCompare);
475   /*
476     Determine tiles per row and column.
477   */
478   tiles_per_column=(size_t) sqrt((double) number_images);
479   tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
480   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);
490   extract_info.x=(ssize_t) montage_info->border_width;
491   extract_info.y=(ssize_t) montage_info->border_width;
492   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) FormatLocaleString(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)
520         frame_info.outer_bevel=(ssize_t) frame_info.width/2;
521       if ((flags & PsiValue) == 0)
522         frame_info.inner_bevel=frame_info.outer_bevel;
523       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,
526         frame_info.outer_bevel);
527       border_width=(size_t) MagickMax((ssize_t) frame_info.width,
528         (ssize_t) frame_info.height);
529     }
530   for (i=0; i < (ssize_t) number_images; i++)
531   {
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,exception);
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)
566     title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
567       MultilineCensus(title)+2*extract_info.y);
568   number_lines=0;
569   for (i=0; i < (ssize_t) number_images; i++)
570   {
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);
582   montage->background_color=montage_info->background_color;
583   montage->scene=0;
584   images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
585   tiles=0;
586   total_tiles=(size_t) number_images;
587   for (i=0; i < (ssize_t) images_per_page; i++)
588   {
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;
599     y_offset+=(ssize_t) title_offset;
600     max_height=0;
601     bounds.width=0;
602     bounds.height=0;
603     width=0;
604     for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
605     {
606       if (tile < (ssize_t) number_images)
607         {
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         }
613       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
614       if (x_offset > (ssize_t) bounds.width)
615         bounds.width=(size_t) x_offset;
616       if (((tile+1) == (ssize_t) tiles_per_page) ||
617           (((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;
624           y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
625             (metrics.ascent-metrics.descent+4)*number_lines+
626             (montage_info->shadow != MagickFalse ? 4 : 0));
627           if (y_offset > (ssize_t) bounds.height)
628             bounds.height=(size_t) y_offset;
629           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;
648     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
649     {
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);
663     y_offset+=(ssize_t) title_offset;
664     (void) FormatLocaleString(montage->montage,MaxTextExtent,
665       "%.20gx%.20g%+.20g%+.20g",(double) (extract_info.width+
666       (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);
670     *montage->directory='\0';
671     tile=0;
672     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
673     {
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,exception);
701         (void) FormatLocaleString(geometry,MaxTextExtent,
702           "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
703           (metrics.ascent-metrics.descent),0.0,(double) extract_info.y+4);
704         (void) CloneString(&clone_info->geometry,geometry);
705         (void) CloneString(&clone_info->text,title);
706         (void) AnnotateImage(montage,clone_info,exception);
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;
720     y_offset+=(ssize_t) title_offset+extract_info.y;
721     max_height=0;
722     status=MagickTrue;
723     for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
724     {
725       /*
726         Copy this tile to the composite.
727       */
728       image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
729       progress_monitor=SetImageProgressMonitor(image,
730         (MagickProgressMonitor) NULL,image->client_data);
731       width=concatenate != MagickFalse ? image->columns : extract_info.width;
732       if (image->rows > max_height)
733         max_height=image->rows;
734       height=concatenate != MagickFalse ? max_height : extract_info.height;
735       if (border_width != 0)
736         {
737           Image
738             *border_image;
739
740           RectangleInfo
741             border_info;
742
743           /*
744             Put a border around the image.
745           */
746           border_info.width=border_width;
747           border_info.height=border_width;
748           if (montage_info->frame != (char *) NULL)
749             {
750               border_info.width=(width-image->columns+1)/2;
751               border_info.height=(height-image->rows+1)/2;
752             }
753           border_image=BorderImage(image,&border_info,exception);
754           if (border_image != (Image *) NULL)
755             {
756               image=DestroyImage(image);
757               image=border_image;
758             }
759           if ((montage_info->frame != (char *) NULL) &&
760               (image->compose == DstOutCompositeOp))
761             {
762               SetPixelChannelMap(image,AlphaChannel);
763               (void) NegateImage(image,MagickFalse,exception);
764               SetPixelChannelMap(image,DefaultChannels);
765             }
766         }
767       /*
768         Gravitate as specified by the tile gravity.
769       */
770       tile_image->columns=width;
771       tile_image->rows=height;
772       tile_image->gravity=montage_info->gravity;
773       if (image->gravity != UndefinedGravity)
774         tile_image->gravity=image->gravity;
775       (void) FormatLocaleString(tile_geometry,MaxTextExtent,"%.20gx%.20g+0+0",
776         (double) image->columns,(double) image->rows);
777       flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
778       x=(ssize_t) (geometry.x+border_width);
779       y=(ssize_t) (geometry.y+border_width);
780       if ((montage_info->frame != (char *) NULL) && (bevel_width != 0))
781         {
782           FrameInfo
783             extract_info;
784
785           Image
786             *frame_image;
787
788           /*
789             Put an ornamental border around this tile.
790           */
791           extract_info=frame_info;
792           extract_info.width=width+2*frame_info.width;
793           extract_info.height=height+2*frame_info.height;
794           value=GetImageProperty(image,"label");
795           if (value != (const char *) NULL)
796             extract_info.height+=(size_t) ((metrics.ascent-
797               metrics.descent+4)*MultilineCensus(value));
798           frame_image=FrameImage(image,&extract_info,exception);
799           if (frame_image != (Image *) NULL)
800             {
801               image=DestroyImage(image);
802               image=frame_image;
803             }
804           x=0;
805           y=0;
806         }
807       if (LocaleCompare(image->magick,"NULL") != 0)
808         {
809           /*
810             Composite background with tile.
811           */
812           if (montage_info->shadow != MagickFalse)
813             {
814               Image
815                 *shadow_image;
816
817               /*
818                 Shadow image.
819               */
820               (void) QueryColorDatabase("#00000000",&image->background_color,
821                 exception);
822               shadow_image=ShadowImage(image,80.0,2.0,5,5,exception);
823               if (shadow_image != (Image *) NULL)
824                 {
825                   InheritException(&shadow_image->exception,exception);
826                   (void) CompositeImage(shadow_image,OverCompositeOp,image,0,0);
827                   image=DestroyImage(image);
828                   image=shadow_image;
829                 }
830           }
831           (void) CompositeImage(montage,image->compose,image,x_offset+x,
832             y_offset+y);
833           value=GetImageProperty(image,"label");
834           if (value != (const char *) NULL)
835             {
836               char
837                 geometry[MaxTextExtent];
838
839               /*
840                 Annotate composite tile with label.
841               */
842               (void) FormatLocaleString(geometry,MaxTextExtent,
843                 "%.20gx%.20g%+.20g%+.20g",(double) ((montage_info->frame ?
844                 image->columns : width)-2*border_width),(double)
845                 (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
846                 (double) (x_offset+border_width),(double)
847                 ((montage_info->frame ? y_offset+height+border_width+4 :
848                 y_offset+extract_info.height+border_width+
849                 (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
850               (void) CloneString(&draw_info->geometry,geometry);
851               (void) CloneString(&draw_info->text,value);
852               (void) AnnotateImage(montage,draw_info,exception);
853             }
854         }
855       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
856       if (((tile+1) == (ssize_t) tiles_per_page) ||
857           (((tile+1) % tiles_per_row) == 0))
858         {
859           x_offset=extract_info.x;
860           y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
861             (metrics.ascent-metrics.descent+4)*number_lines+
862             (montage_info->shadow != MagickFalse ? 4 : 0));
863           max_height=0;
864         }
865       if (images->progress_monitor != (MagickProgressMonitor) NULL)
866         {
867           MagickBooleanType
868             proceed;
869
870           proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
871           if (proceed == MagickFalse)
872             status=MagickFalse;
873         }
874       image_list[tile]=DestroyImage(image_list[tile]);
875       image=DestroyImage(image);
876       tiles++;
877     }
878     (void) status;
879     if ((i+1) < (ssize_t) images_per_page)
880       {
881         /*
882           Allocate next image structure.
883         */
884         AcquireNextImage(clone_info,montage);
885         if (GetNextImageInList(montage) == (Image *) NULL)
886           {
887             montage=DestroyImageList(montage);
888             return((Image *) NULL);
889           }
890         montage=GetNextImageInList(montage);
891         montage->background_color=montage_info->background_color;
892         image_list+=tiles_per_page;
893         number_images-=tiles_per_page;
894       }
895   }
896   tile_image=DestroyImage(tile_image);
897   if (texture != (Image *) NULL)
898     texture=DestroyImage(texture);
899   master_list=(Image **) RelinquishMagickMemory(master_list);
900   draw_info=DestroyDrawInfo(draw_info);
901   clone_info=DestroyImageInfo(clone_info);
902   return(GetFirstImageInList(montage));
903 }