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