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