]> granicus.if.org Git - imagemagick/blob - magick/montage.c
(no commit message)
[imagemagick] / magick / 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 "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 \f
74 /*
75 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 %                                                                             %
77 %                                                                             %
78 %                                                                             %
79 %   C l o n e M o n t a g e I n f o                                           %
80 %                                                                             %
81 %                                                                             %
82 %                                                                             %
83 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84 %
85 %  CloneMontageInfo() makes a copy of the given montage info structure.  If
86 %  NULL is specified, a new image info structure is created initialized to
87 %  default values.
88 %
89 %  The format of the CloneMontageInfo method is:
90 %
91 %      MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
92 %        const MontageInfo *montage_info)
93 %
94 %  A description of each parameter follows:
95 %
96 %    o image_info: the image info.
97 %
98 %    o montage_info: the montage info.
99 %
100 */
101 MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
102   const MontageInfo *montage_info)
103 {
104   MontageInfo
105     *clone_info;
106
107   clone_info=(MontageInfo *) AcquireMagickMemory(sizeof(*clone_info));
108   if (clone_info == (MontageInfo *) NULL)
109     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
110   GetMontageInfo(image_info,clone_info);
111   if (montage_info == (MontageInfo *) NULL)
112     return(clone_info);
113   if (montage_info->geometry != (char *) NULL)
114     clone_info->geometry=AcquireString(montage_info->geometry);
115   if (montage_info->tile != (char *) NULL)
116     clone_info->tile=AcquireString(montage_info->tile);
117   if (montage_info->title != (char *) NULL)
118     clone_info->title=AcquireString(montage_info->title);
119   if (montage_info->frame != (char *) NULL)
120     clone_info->frame=AcquireString(montage_info->frame);
121   if (montage_info->texture != (char *) NULL)
122     clone_info->texture=AcquireString(montage_info->texture);
123   if (montage_info->font != (char *) NULL)
124     clone_info->font=AcquireString(montage_info->font);
125   clone_info->pointsize=montage_info->pointsize;
126   clone_info->border_width=montage_info->border_width;
127   clone_info->shadow=montage_info->shadow;
128   clone_info->fill=montage_info->fill;
129   clone_info->stroke=montage_info->stroke;
130   clone_info->background_color=montage_info->background_color;
131   clone_info->border_color=montage_info->border_color;
132   clone_info->matte_color=montage_info->matte_color;
133   clone_info->gravity=montage_info->gravity;
134   (void) CopyMagickString(clone_info->filename,montage_info->filename,
135     MaxTextExtent);
136   clone_info->debug=IsEventLogging();
137   return(clone_info);
138 }
139 \f
140 /*
141 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
142 %                                                                             %
143 %                                                                             %
144 %                                                                             %
145 %   D e s t r o y M o n t a g e I n f o                                       %
146 %                                                                             %
147 %                                                                             %
148 %                                                                             %
149 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
150 %
151 %  DestroyMontageInfo() deallocates memory associated with montage_info.
152 %
153 %  The format of the DestroyMontageInfo method is:
154 %
155 %      MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
156 %
157 %  A description of each parameter follows:
158 %
159 %    o montage_info: Specifies a pointer to an MontageInfo structure.
160 %
161 %
162 */
163 MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
164 {
165   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 \f
188 /*
189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
190 %                                                                             %
191 %                                                                             %
192 %                                                                             %
193 %   G e t M o n t a g e I n f o                                               %
194 %                                                                             %
195 %                                                                             %
196 %                                                                             %
197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
198 %
199 %  GetMontageInfo() initializes montage_info to default values.
200 %
201 %  The format of the GetMontageInfo method is:
202 %
203 %      void GetMontageInfo(const ImageInfo *image_info,
204 %        MontageInfo *montage_info)
205 %
206 %  A description of each parameter follows:
207 %
208 %    o image_info: a structure of type ImageInfo.
209 %
210 %    o montage_info: Specifies a pointer to a MontageInfo structure.
211 %
212 */
213 MagickExport void GetMontageInfo(const ImageInfo *image_info,
214   MontageInfo *montage_info)
215 {
216   assert(image_info != (const ImageInfo *) NULL);
217   assert(image_info->signature == 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 \f
239 /*
240 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241 %                                                                             %
242 %                                                                             %
243 %                                                                             %
244 %   M o n t a g e I m a g e L i s t                                           %
245 %                                                                             %
246 %                                                                             %
247 %                                                                             %
248 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
249 %
250 %  MontageImageList() is a layout manager that lets you tile one or more
251 %  thumbnails across an image canvas.
252 %
253 %  The format of the MontageImageList method is:
254 %
255 %      Image *MontageImageList(const ImageInfo *image_info,
256 %        const MontageInfo *montage_info,Image *images,
257 %        ExceptionInfo *exception)
258 %
259 %  A description of each parameter follows:
260 %
261 %    o image_info: the image info.
262 %
263 %    o montage_info: Specifies a pointer to a MontageInfo structure.
264 %
265 %    o images: Specifies a pointer to an array of Image structures.
266 %
267 %    o exception: return any errors or warnings in this structure.
268 %
269 */
270
271 static void GetMontageGeometry(char *geometry,const size_t number_images,
272   ssize_t *x_offset,ssize_t *y_offset,size_t *tiles_per_column,
273   size_t *tiles_per_row)
274 {
275   *tiles_per_column=0;
276   *tiles_per_row=0;
277   (void) GetGeometry(geometry,x_offset,y_offset,tiles_per_row,tiles_per_column);
278   if ((*tiles_per_column == 0) && (*tiles_per_row == 0))
279     *tiles_per_column=(size_t) sqrt((double) number_images);
280   if (*tiles_per_column == 0)
281     *tiles_per_column=(size_t)
282       ceil((double) number_images/(*tiles_per_row));
283   if (*tiles_per_row == 0)
284     *tiles_per_row=(size_t)
285       ceil((double) number_images/(*tiles_per_column));
286 }
287
288 static inline ssize_t MagickMax(const ssize_t x,const ssize_t y)
289 {
290   if (x > y)
291     return(x);
292   return(y);
293 }
294
295 static inline ssize_t MagickMin(const ssize_t x,const ssize_t y)
296 {
297   if (x < y)
298     return(x);
299   return(y);
300 }
301
302 #if defined(__cplusplus) || defined(c_plusplus)
303 extern "C" {
304 #endif
305
306 static 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
321 MagickExport 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
336 MagickExport 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
367   MagickBooleanType
368     concatenate,
369     proceed,
370     status;
371
372   MagickOffsetType
373     tiles;
374
375   MagickProgressMonitor
376     progress_monitor;
377
378   MagickStatusType
379     flags;
380
381   register ssize_t
382     i;
383
384   RectangleInfo
385     bounds,
386     geometry,
387     extract_info;
388
389
390   size_t
391     bevel_width,
392     border_width,
393     extent,
394     height,
395     images_per_page,
396     max_height,
397     number_images,
398     number_lines,
399     sans,
400     tiles_per_column,
401     tiles_per_page,
402     tiles_per_row,
403     title_offset,
404     total_tiles,
405     width;
406
407   ssize_t
408     tile,
409     x,
410     x_offset,
411     y,
412     y_offset;
413
414   TypeMetric
415     metrics;
416
417   /*
418     Create image tiles.
419   */
420   assert(images != (Image *) NULL);
421   assert(images->signature == MagickSignature);
422   if (images->debug != MagickFalse)
423     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
424   assert(montage_info != (MontageInfo *) NULL);
425   assert(montage_info->signature == MagickSignature);
426   assert(exception != (ExceptionInfo *) NULL);
427   assert(exception->signature == MagickSignature);
428   number_images=GetImageListLength(images);
429   master_list=ImageListToArray(images,exception);
430   image_list=master_list;
431   image=image_list[0];
432   if (master_list == (Image **) NULL)
433     ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
434   thumbnail=NewImageList();
435   for (i=0; i < (ssize_t) number_images; i++)
436   {
437     image=CloneImage(image_list[i],0,0,MagickTrue,exception);
438     if (image == (Image *) NULL)
439       break;
440     (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
441     progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
442       image->client_data);
443     flags=ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
444     thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
445     if (thumbnail == (Image *) NULL)
446       break;
447     image_list[i]=thumbnail;
448     (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
449     proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
450       number_images);
451     if (proceed == MagickFalse)
452       break;
453     image=DestroyImage(image);
454   }
455   if (i < (ssize_t) number_images)
456     {
457       if (thumbnail == (Image *) NULL)
458         i--;
459       for (tile=0; (ssize_t) tile <= i; tile++)
460         if (image_list[tile] != (Image *) NULL)
461           image_list[tile]=DestroyImage(image_list[tile]);
462       master_list=(Image **) RelinquishMagickMemory(master_list);
463       return((Image *) NULL);
464     }
465   /*
466     Sort image list by increasing tile number.
467   */
468   for (i=0; i < (ssize_t) number_images; i++)
469     if (image_list[i]->scene == 0)
470       break;
471   if (i == (ssize_t) number_images)
472     qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
473       SceneCompare);
474   /*
475     Determine tiles per row and column.
476   */
477   tiles_per_column=(size_t) sqrt((double) number_images);
478   tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
479   x_offset=0;
480   y_offset=0;
481   if (montage_info->tile != (char *) NULL)
482     GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
483       &tiles_per_column,&tiles_per_row);
484   /*
485     Determine tile sizes.
486   */
487   concatenate=MagickFalse;
488   SetGeometry(image_list[0],&extract_info);
489   extract_info.x=(ssize_t) montage_info->border_width;
490   extract_info.y=(ssize_t) montage_info->border_width;
491   if (montage_info->geometry != (char *) NULL)
492     {
493       /*
494         Initialize tile geometry.
495       */
496       flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
497         &extract_info.width,&extract_info.height);
498       if ((extract_info.x == 0) && (extract_info.y == 0))
499         concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
500           MagickTrue : MagickFalse;
501     }
502   border_width=montage_info->border_width;
503   bevel_width=0;
504   if (montage_info->frame != (char *) NULL)
505     {
506       char
507         absolute_geometry[MaxTextExtent];
508
509       (void) ResetMagickMemory(&frame_info,0,sizeof(frame_info));
510       frame_info.width=extract_info.width;
511       frame_info.height=extract_info.height;
512       (void) FormatLocaleString(absolute_geometry,MaxTextExtent,"%s!",
513         montage_info->frame);
514       flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
515         &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
516       if ((flags & HeightValue) == 0)
517         frame_info.height=frame_info.width;
518       if ((flags & XiValue) == 0)
519         frame_info.outer_bevel=(ssize_t) frame_info.width/2;
520       if ((flags & PsiValue) == 0)
521         frame_info.inner_bevel=frame_info.outer_bevel;
522       frame_info.x=(ssize_t) frame_info.width;
523       frame_info.y=(ssize_t) frame_info.height;
524       bevel_width=(size_t) MagickMax(frame_info.inner_bevel,
525         frame_info.outer_bevel);
526       border_width=(size_t) MagickMax((ssize_t) frame_info.width,
527         (ssize_t) frame_info.height);
528     }
529   for (i=0; i < (ssize_t) number_images; i++)
530   {
531     if (image_list[i]->columns > extract_info.width)
532       extract_info.width=image_list[i]->columns;
533     if (image_list[i]->rows > extract_info.height)
534       extract_info.height=image_list[i]->rows;
535   }
536   /*
537     Initialize draw attributes.
538   */
539   clone_info=CloneImageInfo(image_info);
540   clone_info->background_color=montage_info->background_color;
541   clone_info->border_color=montage_info->border_color;
542   draw_info=CloneDrawInfo(clone_info,(DrawInfo *) NULL);
543   if (montage_info->font != (char *) NULL)
544     (void) CloneString(&draw_info->font,montage_info->font);
545   if (montage_info->pointsize != 0.0)
546     draw_info->pointsize=montage_info->pointsize;
547   draw_info->gravity=CenterGravity;
548   draw_info->stroke=montage_info->stroke;
549   draw_info->fill=montage_info->fill;
550   draw_info->text=AcquireString("");
551   (void) GetTypeMetrics(image_list[0],draw_info,&metrics);
552   texture=NewImageList();
553   if (montage_info->texture != (char *) NULL)
554     {
555       (void) CopyMagickString(clone_info->filename,montage_info->texture,
556         MaxTextExtent);
557       texture=ReadImage(clone_info,exception);
558     }
559   /*
560     Determine the number of lines in an next label.
561   */
562   title=InterpretImageProperties(clone_info,image_list[0],montage_info->title);
563   title_offset=0;
564   if (montage_info->title != (char *) NULL)
565     title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
566       MultilineCensus(title)+2*extract_info.y);
567   number_lines=0;
568   for (i=0; i < (ssize_t) number_images; i++)
569   {
570     value=GetImageProperty(image_list[i],"label");
571     if (value == (const char *) NULL)
572       continue;
573     if (MultilineCensus(value) > number_lines)
574       number_lines=MultilineCensus(value);
575   }
576   /*
577     Allocate next structure.
578   */
579   tile_image=AcquireImage(NULL);
580   montage=AcquireImage(clone_info);
581   montage->background_color=montage_info->background_color;
582   montage->scene=0;
583   images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
584   tiles=0;
585   total_tiles=(size_t) number_images;
586   for (i=0; i < (ssize_t) images_per_page; i++)
587   {
588     /*
589       Determine bounding box.
590     */
591     tiles_per_page=tiles_per_row*tiles_per_column;
592     x_offset=0;
593     y_offset=0;
594     if (montage_info->tile != (char *) NULL)
595       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
596         &sans,&sans);
597     tiles_per_page=tiles_per_row*tiles_per_column;
598     y_offset+=(ssize_t) title_offset;
599     max_height=0;
600     bounds.width=0;
601     bounds.height=0;
602     width=0;
603     for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
604     {
605       if (tile < (ssize_t) number_images)
606         {
607           width=concatenate != MagickFalse ? image_list[tile]->columns :
608             extract_info.width;
609           if (image_list[tile]->rows > max_height)
610             max_height=image_list[tile]->rows;
611         }
612       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
613       if (x_offset > (ssize_t) bounds.width)
614         bounds.width=(size_t) x_offset;
615       if (((tile+1) == (ssize_t) tiles_per_page) ||
616           (((tile+1) % tiles_per_row) == 0))
617         {
618           x_offset=0;
619           if (montage_info->tile != (char *) NULL)
620             GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
621               &sans,&sans);
622           height=concatenate != MagickFalse ? max_height : extract_info.height;
623           y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
624             (metrics.ascent-metrics.descent+4)*number_lines+
625             (montage_info->shadow != MagickFalse ? 4 : 0));
626           if (y_offset > (ssize_t) bounds.height)
627             bounds.height=(size_t) y_offset;
628           max_height=0;
629         }
630     }
631     if (montage_info->shadow != MagickFalse)
632       bounds.width+=4;
633     /*
634       Initialize montage image.
635     */
636     (void) CopyMagickString(montage->filename,montage_info->filename,
637       MaxTextExtent);
638     montage->columns=bounds.width;
639     montage->rows=bounds.height;
640     (void) SetImageBackgroundColor(montage);
641     /*
642       Set montage geometry.
643     */
644     montage->montage=AcquireString((char *) NULL);
645     tile=0;
646     extent=1;
647     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
648     {
649       extent+=strlen(image_list[tile]->filename)+1;
650       tile++;
651     }
652     montage->directory=(char *) AcquireQuantumMemory(extent,
653       sizeof(*montage->directory));
654     if ((montage->montage == (char *) NULL) ||
655         (montage->directory == (char *) NULL))
656       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
657     x_offset=0;
658     y_offset=0;
659     if (montage_info->tile != (char *) NULL)
660       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
661         &sans,&sans);
662     y_offset+=(ssize_t) title_offset;
663     (void) FormatLocaleString(montage->montage,MaxTextExtent,
664       "%.20gx%.20g%+.20g%+.20g",(double) (extract_info.width+
665       (extract_info.x+border_width)*2),(double) (extract_info.height+
666       (extract_info.y+border_width)*2+(double) ((metrics.ascent-
667       metrics.descent+4)*number_lines+(montage_info->shadow != MagickFalse ? 4 :
668       0))),(double) x_offset,(double) y_offset);
669     *montage->directory='\0';
670     tile=0;
671     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
672     {
673       (void) ConcatenateMagickString(montage->directory,
674         image_list[tile]->filename,extent);
675       (void) ConcatenateMagickString(montage->directory,"\n",extent);
676       tile++;
677     }
678     progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
679       NULL,montage->client_data);
680     if (texture != (Image *) NULL)
681       (void) TextureImage(montage,texture);
682     if (montage_info->title != (char *) NULL)
683       {
684         char
685           geometry[MaxTextExtent];
686
687         DrawInfo
688           *clone_info;
689
690         TypeMetric
691           metrics;
692
693         /*
694           Annotate composite image with title.
695         */
696         clone_info=CloneDrawInfo(image_info,draw_info);
697         clone_info->gravity=CenterGravity;
698         clone_info->pointsize*=2.0;
699         (void) GetTypeMetrics(image_list[0],clone_info,&metrics);
700         (void) FormatLocaleString(geometry,MaxTextExtent,
701           "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
702           (metrics.ascent-metrics.descent),0.0,(double) extract_info.y+4);
703         (void) CloneString(&clone_info->geometry,geometry);
704         (void) CloneString(&clone_info->text,title);
705         (void) AnnotateImage(montage,clone_info);
706         clone_info=DestroyDrawInfo(clone_info);
707       }
708     (void) SetImageProgressMonitor(montage,progress_monitor,
709       montage->client_data);
710     /*
711       Copy tile to the composite.
712     */
713     x_offset=0;
714     y_offset=0;
715     if (montage_info->tile != (char *) NULL)
716       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
717         &sans,&sans);
718     x_offset+=extract_info.x;
719     y_offset+=(ssize_t) title_offset+extract_info.y;
720     max_height=0;
721     status=MagickTrue;
722     for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
723     {
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;
770       (void) FormatLocaleString(tile_geometry,MaxTextExtent,"%.20gx%.20g+0+0",
771         (double) image->columns,(double) image->rows);
772       flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
773       x=(ssize_t) (geometry.x+border_width);
774       y=(ssize_t) (geometry.y+border_width);
775       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)
791             extract_info.height+=(size_t) ((metrics.ascent-
792               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           }
826           (void) CompositeImage(montage,image->compose,image,x_offset+x,
827             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) FormatLocaleString(geometry,MaxTextExtent,
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+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));
845               (void) CloneString(&draw_info->geometry,geometry);
846               (void) CloneString(&draw_info->text,value);
847               (void) AnnotateImage(montage,draw_info);
848             }
849         }
850       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
851       if (((tile+1) == (ssize_t) tiles_per_page) ||
852           (((tile+1) % tiles_per_row) == 0))
853         {
854           x_offset=extract_info.x;
855           y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
856             (metrics.ascent-metrics.descent+4)*number_lines+
857             (montage_info->shadow != MagickFalse ? 4 : 0));
858           max_height=0;
859         }
860       if (images->progress_monitor != (MagickProgressMonitor) NULL)
861         {
862           MagickBooleanType
863             proceed;
864
865           proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
866           if (proceed == MagickFalse)
867             status=MagickFalse;
868         }
869       image_list[tile]=DestroyImage(image_list[tile]);
870       image=DestroyImage(image);
871       tiles++;
872     }
873     (void) status;
874     if ((i+1) < (ssize_t) images_per_page)
875       {
876         /*
877           Allocate next image structure.
878         */
879         AcquireNextImage(clone_info,montage);
880         if (GetNextImageInList(montage) == (Image *) NULL)
881           {
882             montage=DestroyImageList(montage);
883             return((Image *) NULL);
884           }
885         montage=GetNextImageInList(montage);
886         montage->background_color=montage_info->background_color;
887         image_list+=tiles_per_page;
888         number_images-=tiles_per_page;
889       }
890   }
891   tile_image=DestroyImage(tile_image);
892   if (texture != (Image *) NULL)
893     texture=DestroyImage(texture);
894   master_list=(Image **) RelinquishMagickMemory(master_list);
895   draw_info=DestroyDrawInfo(draw_info);
896   clone_info=DestroyImageInfo(clone_info);
897   return(GetFirstImageInList(montage));
898 }