MagickCore  6.7.5
montage.c
Go to the documentation of this file.
00001 /*
00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00003 %                                                                             %
00004 %                                                                             %
00005 %                                                                             %
00006 %               M   M   OOO   N   N  TTTTT   AAA    GGGG  EEEEE               %
00007 %               MM MM  O   O  NN  N    T    A   A  G      E                   %
00008 %               M M M  O   O  N N N    T    AAAAA  G  GG  EEE                 %
00009 %               M   M  O   O  N  NN    T    A   A  G   G  E                   %
00010 %               M   M   OOO   N   N    T    A   A   GGG   EEEEE               %
00011 %                                                                             %
00012 %                                                                             %
00013 %                MagickCore Methods to Create Image Thumbnails                %
00014 %                                                                             %
00015 %                              Software Design                                %
00016 %                                John Cristy                                  %
00017 %                                 July 1992                                   %
00018 %                                                                             %
00019 %                                                                             %
00020 %  Copyright 1999-2012 ImageMagick Studio LLC, a non-profit organization      %
00021 %  dedicated to making software imaging solutions freely available.           %
00022 %                                                                             %
00023 %  You may not use this file except in compliance with the License.  You may  %
00024 %  obtain a copy of the License at                                            %
00025 %                                                                             %
00026 %    http://www.imagemagick.org/script/license.php                            %
00027 %                                                                             %
00028 %  Unless required by applicable law or agreed to in writing, software        %
00029 %  distributed under the License is distributed on an "AS IS" BASIS,          %
00030 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
00031 %  See the License for the specific language governing permissions and        %
00032 %  limitations under the License.                                             %
00033 %                                                                             %
00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00035 %
00036 %
00037 %
00038 */
00039 
00040 /*
00041   Include declarations.
00042 */
00043 #include "MagickCore/studio.h"
00044 #include "MagickCore/annotate.h"
00045 #include "MagickCore/client.h"
00046 #include "MagickCore/color.h"
00047 #include "MagickCore/composite.h"
00048 #include "MagickCore/constitute.h"
00049 #include "MagickCore/decorate.h"
00050 #include "MagickCore/draw.h"
00051 #include "MagickCore/effect.h"
00052 #include "MagickCore/enhance.h"
00053 #include "MagickCore/exception.h"
00054 #include "MagickCore/exception-private.h"
00055 #include "MagickCore/fx.h"
00056 #include "MagickCore/gem.h"
00057 #include "MagickCore/geometry.h"
00058 #include "MagickCore/image.h"
00059 #include "MagickCore/image-private.h"
00060 #include "MagickCore/list.h"
00061 #include "MagickCore/memory_.h"
00062 #include "MagickCore/monitor.h"
00063 #include "MagickCore/monitor-private.h"
00064 #include "MagickCore/montage.h"
00065 #include "MagickCore/option.h"
00066 #include "MagickCore/pixel.h"
00067 #include "MagickCore/quantize.h"
00068 #include "MagickCore/property.h"
00069 #include "MagickCore/resize.h"
00070 #include "MagickCore/resource_.h"
00071 #include "MagickCore/string_.h"
00072 #include "MagickCore/utility.h"
00073 #include "MagickCore/utility-private.h"
00074 #include "MagickCore/version.h"
00075 
00076 /*
00077 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00078 %                                                                             %
00079 %                                                                             %
00080 %                                                                             %
00081 %   C l o n e M o n t a g e I n f o                                           %
00082 %                                                                             %
00083 %                                                                             %
00084 %                                                                             %
00085 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00086 %
00087 %  CloneMontageInfo() makes a copy of the given montage info structure.  If
00088 %  NULL is specified, a new image info structure is created initialized to
00089 %  default values.
00090 %
00091 %  The format of the CloneMontageInfo method is:
00092 %
00093 %      MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
00094 %        const MontageInfo *montage_info)
00095 %
00096 %  A description of each parameter follows:
00097 %
00098 %    o image_info: the image info.
00099 %
00100 %    o montage_info: the montage info.
00101 %
00102 */
00103 MagickExport MontageInfo *CloneMontageInfo(const ImageInfo *image_info,
00104   const MontageInfo *montage_info)
00105 {
00106   MontageInfo
00107     *clone_info;
00108 
00109   clone_info=(MontageInfo *) AcquireMagickMemory(sizeof(*clone_info));
00110   if (clone_info == (MontageInfo *) NULL)
00111     ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
00112   GetMontageInfo(image_info,clone_info);
00113   if (montage_info == (MontageInfo *) NULL)
00114     return(clone_info);
00115   if (montage_info->geometry != (char *) NULL)
00116     clone_info->geometry=AcquireString(montage_info->geometry);
00117   if (montage_info->tile != (char *) NULL)
00118     clone_info->tile=AcquireString(montage_info->tile);
00119   if (montage_info->title != (char *) NULL)
00120     clone_info->title=AcquireString(montage_info->title);
00121   if (montage_info->frame != (char *) NULL)
00122     clone_info->frame=AcquireString(montage_info->frame);
00123   if (montage_info->texture != (char *) NULL)
00124     clone_info->texture=AcquireString(montage_info->texture);
00125   if (montage_info->font != (char *) NULL)
00126     clone_info->font=AcquireString(montage_info->font);
00127   clone_info->pointsize=montage_info->pointsize;
00128   clone_info->border_width=montage_info->border_width;
00129   clone_info->shadow=montage_info->shadow;
00130   clone_info->fill=montage_info->fill;
00131   clone_info->stroke=montage_info->stroke;
00132   clone_info->background_color=montage_info->background_color;
00133   clone_info->border_color=montage_info->border_color;
00134   clone_info->matte_color=montage_info->matte_color;
00135   clone_info->gravity=montage_info->gravity;
00136   (void) CopyMagickString(clone_info->filename,montage_info->filename,
00137     MaxTextExtent);
00138   clone_info->debug=IsEventLogging();
00139   return(clone_info);
00140 }
00141 
00142 /*
00143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00144 %                                                                             %
00145 %                                                                             %
00146 %                                                                             %
00147 %   D e s t r o y M o n t a g e I n f o                                       %
00148 %                                                                             %
00149 %                                                                             %
00150 %                                                                             %
00151 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00152 %
00153 %  DestroyMontageInfo() deallocates memory associated with montage_info.
00154 %
00155 %  The format of the DestroyMontageInfo method is:
00156 %
00157 %      MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
00158 %
00159 %  A description of each parameter follows:
00160 %
00161 %    o montage_info: Specifies a pointer to an MontageInfo structure.
00162 %
00163 %
00164 */
00165 MagickExport MontageInfo *DestroyMontageInfo(MontageInfo *montage_info)
00166 {
00167   if (montage_info->debug != MagickFalse)
00168     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
00169   assert(montage_info != (MontageInfo *) NULL);
00170   assert(montage_info->signature == MagickSignature);
00171   if (montage_info->geometry != (char *) NULL)
00172     montage_info->geometry=(char *)
00173       RelinquishMagickMemory(montage_info->geometry);
00174   if (montage_info->tile != (char *) NULL)
00175     montage_info->tile=DestroyString(montage_info->tile);
00176   if (montage_info->title != (char *) NULL)
00177     montage_info->title=DestroyString(montage_info->title);
00178   if (montage_info->frame != (char *) NULL)
00179     montage_info->frame=DestroyString(montage_info->frame);
00180   if (montage_info->texture != (char *) NULL)
00181     montage_info->texture=(char *) RelinquishMagickMemory(
00182       montage_info->texture);
00183   if (montage_info->font != (char *) NULL)
00184     montage_info->font=DestroyString(montage_info->font);
00185   montage_info->signature=(~MagickSignature);
00186   montage_info=(MontageInfo *) RelinquishMagickMemory(montage_info);
00187   return(montage_info);
00188 }
00189 
00190 /*
00191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00192 %                                                                             %
00193 %                                                                             %
00194 %                                                                             %
00195 %   G e t M o n t a g e I n f o                                               %
00196 %                                                                             %
00197 %                                                                             %
00198 %                                                                             %
00199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00200 %
00201 %  GetMontageInfo() initializes montage_info to default values.
00202 %
00203 %  The format of the GetMontageInfo method is:
00204 %
00205 %      void GetMontageInfo(const ImageInfo *image_info,
00206 %        MontageInfo *montage_info)
00207 %
00208 %  A description of each parameter follows:
00209 %
00210 %    o image_info: a structure of type ImageInfo.
00211 %
00212 %    o montage_info: Specifies a pointer to a MontageInfo structure.
00213 %
00214 */
00215 MagickExport void GetMontageInfo(const ImageInfo *image_info,
00216   MontageInfo *montage_info)
00217 {
00218   assert(image_info != (const ImageInfo *) NULL);
00219   assert(image_info->signature == MagickSignature);
00220   if (image_info->debug != MagickFalse)
00221     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
00222       image_info->filename);
00223   assert(montage_info != (MontageInfo *) NULL);
00224   (void) ResetMagickMemory(montage_info,0,sizeof(*montage_info));
00225   (void) CopyMagickString(montage_info->filename,image_info->filename,
00226     MaxTextExtent);
00227   montage_info->geometry=AcquireString(DefaultTileGeometry);
00228   if (image_info->font != (char *) NULL)
00229     montage_info->font=AcquireString(image_info->font);
00230   montage_info->gravity=CenterGravity;
00231   montage_info->pointsize=image_info->pointsize;
00232   montage_info->fill.alpha=OpaqueAlpha;
00233   montage_info->stroke.alpha=(Quantum) TransparentAlpha;
00234   montage_info->background_color=image_info->background_color;
00235   montage_info->border_color=image_info->border_color;
00236   montage_info->matte_color=image_info->matte_color;
00237   montage_info->debug=IsEventLogging();
00238   montage_info->signature=MagickSignature;
00239 }
00240 
00241 /*
00242 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00243 %                                                                             %
00244 %                                                                             %
00245 %                                                                             %
00246 %   M o n t a g e I m a g e L i s t                                           %
00247 %                                                                             %
00248 %                                                                             %
00249 %                                                                             %
00250 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00251 %
00252 %  MontageImageList() is a layout manager that lets you tile one or more
00253 %  thumbnails across an image canvas.
00254 %
00255 %  The format of the MontageImageList method is:
00256 %
00257 %      Image *MontageImageList(const ImageInfo *image_info,
00258 %        const MontageInfo *montage_info,Image *images,
00259 %        ExceptionInfo *exception)
00260 %
00261 %  A description of each parameter follows:
00262 %
00263 %    o image_info: the image info.
00264 %
00265 %    o montage_info: Specifies a pointer to a MontageInfo structure.
00266 %
00267 %    o images: Specifies a pointer to an array of Image structures.
00268 %
00269 %    o exception: return any errors or warnings in this structure.
00270 %
00271 */
00272 
00273 static void GetMontageGeometry(char *geometry,const size_t number_images,
00274   ssize_t *x_offset,ssize_t *y_offset,size_t *tiles_per_column,
00275   size_t *tiles_per_row)
00276 {
00277   *tiles_per_column=0;
00278   *tiles_per_row=0;
00279   (void) GetGeometry(geometry,x_offset,y_offset,tiles_per_row,tiles_per_column);
00280   if ((*tiles_per_column == 0) && (*tiles_per_row == 0))
00281     *tiles_per_column=(size_t) sqrt((double) number_images);
00282   if (*tiles_per_column == 0)
00283     *tiles_per_column=(size_t)
00284       ceil((double) number_images/(*tiles_per_row));
00285   if (*tiles_per_row == 0)
00286     *tiles_per_row=(size_t)
00287       ceil((double) number_images/(*tiles_per_column));
00288 }
00289 
00290 static inline ssize_t MagickMax(const ssize_t x,const ssize_t y)
00291 {
00292   if (x > y)
00293     return(x);
00294   return(y);
00295 }
00296 
00297 static inline ssize_t MagickMin(const ssize_t x,const ssize_t y)
00298 {
00299   if (x < y)
00300     return(x);
00301   return(y);
00302 }
00303 
00304 #if defined(__cplusplus) || defined(c_plusplus)
00305 extern "C" {
00306 #endif
00307 
00308 static int SceneCompare(const void *x,const void *y)
00309 {
00310   Image
00311     **image_1,
00312     **image_2;
00313 
00314   image_1=(Image **) x;
00315   image_2=(Image **) y;
00316   return((int) ((*image_1)->scene-(*image_2)->scene));
00317 }
00318 
00319 #if defined(__cplusplus) || defined(c_plusplus)
00320 }
00321 #endif
00322 
00323 MagickExport Image *MontageImages(const Image *images,
00324   const MontageInfo *montage_info,ExceptionInfo *exception)
00325 {
00326   Image
00327     *montage_image;
00328 
00329   ImageInfo
00330     *image_info;
00331 
00332   image_info=AcquireImageInfo();
00333   montage_image=MontageImageList(image_info,montage_info,images,exception);
00334   image_info=DestroyImageInfo(image_info);
00335   return(montage_image);
00336 }
00337 
00338 MagickExport Image *MontageImageList(const ImageInfo *image_info,
00339   const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
00340 {
00341 #define MontageImageTag  "Montage/Image"
00342 #define TileImageTag  "Tile/Image"
00343 
00344   char
00345     tile_geometry[MaxTextExtent],
00346     *title;
00347 
00348   const char
00349     *value;
00350 
00351   DrawInfo
00352     *draw_info;
00353 
00354   FrameInfo
00355     frame_info;
00356 
00357   Image
00358     *image,
00359     **image_list,
00360     **master_list,
00361     *montage,
00362     *texture,
00363     *tile_image,
00364     *thumbnail;
00365 
00366   ImageInfo
00367     *clone_info;
00368 
00369   MagickBooleanType
00370     concatenate,
00371     proceed,
00372     status;
00373 
00374   MagickOffsetType
00375     tiles;
00376 
00377   MagickProgressMonitor
00378     progress_monitor;
00379 
00380   MagickStatusType
00381     flags;
00382 
00383   register ssize_t
00384     i;
00385 
00386   RectangleInfo
00387     bounds,
00388     geometry,
00389     extract_info;
00390 
00391   size_t
00392     bevel_width,
00393     border_width,
00394     extent,
00395     height,
00396     images_per_page,
00397     max_height,
00398     number_images,
00399     number_lines,
00400     sans,
00401     tiles_per_column,
00402     tiles_per_page,
00403     tiles_per_row,
00404     title_offset,
00405     total_tiles,
00406     width;
00407 
00408   ssize_t
00409     tile,
00410     x,
00411     x_offset,
00412     y,
00413     y_offset;
00414 
00415   TypeMetric
00416     metrics;
00417 
00418   /*
00419     Create image tiles.
00420   */
00421   assert(images != (Image *) NULL);
00422   assert(images->signature == MagickSignature);
00423   if (images->debug != MagickFalse)
00424     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
00425   assert(montage_info != (MontageInfo *) NULL);
00426   assert(montage_info->signature == MagickSignature);
00427   assert(exception != (ExceptionInfo *) NULL);
00428   assert(exception->signature == MagickSignature);
00429   number_images=GetImageListLength(images);
00430   master_list=ImageListToArray(images,exception);
00431   image_list=master_list;
00432   image=image_list[0];
00433   if (master_list == (Image **) NULL)
00434     ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
00435   thumbnail=NewImageList();
00436   for (i=0; i < (ssize_t) number_images; i++)
00437   {
00438     image=CloneImage(image_list[i],0,0,MagickTrue,exception);
00439     if (image == (Image *) NULL)
00440       break;
00441     (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
00442     progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
00443       image->client_data);
00444     flags=ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
00445     thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
00446     if (thumbnail == (Image *) NULL)
00447       break;
00448     image_list[i]=thumbnail;
00449     (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
00450     proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
00451       number_images);
00452     if (proceed == MagickFalse)
00453       break;
00454     image=DestroyImage(image);
00455   }
00456   if (i < (ssize_t) number_images)
00457     {
00458       if (thumbnail == (Image *) NULL)
00459         i--;
00460       for (tile=0; (ssize_t) tile <= i; tile++)
00461         if (image_list[tile] != (Image *) NULL)
00462           image_list[tile]=DestroyImage(image_list[tile]);
00463       master_list=(Image **) RelinquishMagickMemory(master_list);
00464       return((Image *) NULL);
00465     }
00466   /*
00467     Sort image list by increasing tile number.
00468   */
00469   for (i=0; i < (ssize_t) number_images; i++)
00470     if (image_list[i]->scene == 0)
00471       break;
00472   if (i == (ssize_t) number_images)
00473     qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
00474       SceneCompare);
00475   /*
00476     Determine tiles per row and column.
00477   */
00478   tiles_per_column=(size_t) sqrt((double) number_images);
00479   tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
00480   x_offset=0;
00481   y_offset=0;
00482   if (montage_info->tile != (char *) NULL)
00483     GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
00484       &tiles_per_column,&tiles_per_row);
00485   /*
00486     Determine tile sizes.
00487   */
00488   concatenate=MagickFalse;
00489   SetGeometry(image_list[0],&extract_info);
00490   extract_info.x=(ssize_t) montage_info->border_width;
00491   extract_info.y=(ssize_t) montage_info->border_width;
00492   if (montage_info->geometry != (char *) NULL)
00493     {
00494       /*
00495         Initialize tile geometry.
00496       */
00497       flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
00498         &extract_info.width,&extract_info.height);
00499       if ((extract_info.x == 0) && (extract_info.y == 0))
00500         concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
00501           MagickTrue : MagickFalse;
00502     }
00503   border_width=montage_info->border_width;
00504   bevel_width=0;
00505   if (montage_info->frame != (char *) NULL)
00506     {
00507       char
00508         absolute_geometry[MaxTextExtent];
00509 
00510       (void) ResetMagickMemory(&frame_info,0,sizeof(frame_info));
00511       frame_info.width=extract_info.width;
00512       frame_info.height=extract_info.height;
00513       (void) FormatLocaleString(absolute_geometry,MaxTextExtent,"%s!",
00514         montage_info->frame);
00515       flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
00516         &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
00517       if ((flags & HeightValue) == 0)
00518         frame_info.height=frame_info.width;
00519       if ((flags & XiValue) == 0)
00520         frame_info.outer_bevel=(ssize_t) frame_info.width/2;
00521       if ((flags & PsiValue) == 0)
00522         frame_info.inner_bevel=frame_info.outer_bevel;
00523       frame_info.x=(ssize_t) frame_info.width;
00524       frame_info.y=(ssize_t) frame_info.height;
00525       bevel_width=(size_t) MagickMax(frame_info.inner_bevel,
00526         frame_info.outer_bevel);
00527       border_width=(size_t) MagickMax((ssize_t) frame_info.width,
00528         (ssize_t) frame_info.height);
00529     }
00530   for (i=0; i < (ssize_t) number_images; i++)
00531   {
00532     if (image_list[i]->columns > extract_info.width)
00533       extract_info.width=image_list[i]->columns;
00534     if (image_list[i]->rows > extract_info.height)
00535       extract_info.height=image_list[i]->rows;
00536   }
00537   /*
00538     Initialize draw attributes.
00539   */
00540   clone_info=CloneImageInfo(image_info);
00541   clone_info->background_color=montage_info->background_color;
00542   clone_info->border_color=montage_info->border_color;
00543   draw_info=CloneDrawInfo(clone_info,(DrawInfo *) NULL);
00544   if (montage_info->font != (char *) NULL)
00545     (void) CloneString(&draw_info->font,montage_info->font);
00546   if (montage_info->pointsize != 0.0)
00547     draw_info->pointsize=montage_info->pointsize;
00548   draw_info->gravity=CenterGravity;
00549   draw_info->stroke=montage_info->stroke;
00550   draw_info->fill=montage_info->fill;
00551   draw_info->text=AcquireString("");
00552   (void) GetTypeMetrics(image_list[0],draw_info,&metrics,exception);
00553   texture=NewImageList();
00554   if (montage_info->texture != (char *) NULL)
00555     {
00556       (void) CopyMagickString(clone_info->filename,montage_info->texture,
00557         MaxTextExtent);
00558       texture=ReadImage(clone_info,exception);
00559     }
00560   /*
00561     Determine the number of lines in an next label.
00562   */
00563   title=InterpretImageProperties(clone_info,image_list[0],montage_info->title,
00564     exception);
00565   title_offset=0;
00566   if (montage_info->title != (char *) NULL)
00567     title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
00568       MultilineCensus(title)+2*extract_info.y);
00569   number_lines=0;
00570   for (i=0; i < (ssize_t) number_images; i++)
00571   {
00572     value=GetImageProperty(image_list[i],"label",exception);
00573     if (value == (const char *) NULL)
00574       continue;
00575     if (MultilineCensus(value) > number_lines)
00576       number_lines=MultilineCensus(value);
00577   }
00578   /*
00579     Allocate next structure.
00580   */
00581   tile_image=AcquireImage((ImageInfo *) NULL,exception);
00582   montage=AcquireImage(clone_info,exception);
00583   montage->background_color=montage_info->background_color;
00584   montage->scene=0;
00585   images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
00586   tiles=0;
00587   total_tiles=(size_t) number_images;
00588   for (i=0; i < (ssize_t) images_per_page; i++)
00589   {
00590     /*
00591       Determine bounding box.
00592     */
00593     tiles_per_page=tiles_per_row*tiles_per_column;
00594     x_offset=0;
00595     y_offset=0;
00596     if (montage_info->tile != (char *) NULL)
00597       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
00598         &sans,&sans);
00599     tiles_per_page=tiles_per_row*tiles_per_column;
00600     y_offset+=(ssize_t) title_offset;
00601     max_height=0;
00602     bounds.width=0;
00603     bounds.height=0;
00604     width=0;
00605     for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
00606     {
00607       if (tile < (ssize_t) number_images)
00608         {
00609           width=concatenate != MagickFalse ? image_list[tile]->columns :
00610             extract_info.width;
00611           if (image_list[tile]->rows > max_height)
00612             max_height=image_list[tile]->rows;
00613         }
00614       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
00615       if (x_offset > (ssize_t) bounds.width)
00616         bounds.width=(size_t) x_offset;
00617       if (((tile+1) == (ssize_t) tiles_per_page) ||
00618           (((tile+1) % tiles_per_row) == 0))
00619         {
00620           x_offset=0;
00621           if (montage_info->tile != (char *) NULL)
00622             GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
00623               &sans,&sans);
00624           height=concatenate != MagickFalse ? max_height : extract_info.height;
00625           y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
00626             (metrics.ascent-metrics.descent+4)*number_lines+
00627             (montage_info->shadow != MagickFalse ? 4 : 0));
00628           if (y_offset > (ssize_t) bounds.height)
00629             bounds.height=(size_t) y_offset;
00630           max_height=0;
00631         }
00632     }
00633     if (montage_info->shadow != MagickFalse)
00634       bounds.width+=4;
00635     /*
00636       Initialize montage image.
00637     */
00638     (void) CopyMagickString(montage->filename,montage_info->filename,
00639       MaxTextExtent);
00640     montage->columns=(size_t) MagickMax((ssize_t) bounds.width,1);
00641     montage->rows=(size_t) MagickMax((ssize_t) bounds.height,1);
00642     (void) SetImageBackgroundColor(montage,exception);
00643     /*
00644       Set montage geometry.
00645     */
00646     montage->montage=AcquireString((char *) NULL);
00647     tile=0;
00648     extent=1;
00649     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
00650     {
00651       extent+=strlen(image_list[tile]->filename)+1;
00652       tile++;
00653     }
00654     montage->directory=(char *) AcquireQuantumMemory(extent,
00655       sizeof(*montage->directory));
00656     if ((montage->montage == (char *) NULL) ||
00657         (montage->directory == (char *) NULL))
00658       ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
00659     x_offset=0;
00660     y_offset=0;
00661     if (montage_info->tile != (char *) NULL)
00662       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
00663         &sans,&sans);
00664     y_offset+=(ssize_t) title_offset;
00665     (void) FormatLocaleString(montage->montage,MaxTextExtent,
00666       "%.20gx%.20g%+.20g%+.20g",(double) (extract_info.width+
00667       (extract_info.x+border_width)*2),(double) (extract_info.height+
00668       (extract_info.y+border_width)*2+(double) ((metrics.ascent-
00669       metrics.descent+4)*number_lines+(montage_info->shadow != MagickFalse ? 4 :
00670       0))),(double) x_offset,(double) y_offset);
00671     *montage->directory='\0';
00672     tile=0;
00673     while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
00674     {
00675       (void) ConcatenateMagickString(montage->directory,
00676         image_list[tile]->filename,extent);
00677       (void) ConcatenateMagickString(montage->directory,"\n",extent);
00678       tile++;
00679     }
00680     progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
00681       NULL,montage->client_data);
00682     if (texture != (Image *) NULL)
00683       (void) TextureImage(montage,texture,exception);
00684     if (montage_info->title != (char *) NULL)
00685       {
00686         char
00687           geometry[MaxTextExtent];
00688 
00689         DrawInfo
00690           *clone_info;
00691 
00692         TypeMetric
00693           metrics;
00694 
00695         /*
00696           Annotate composite image with title.
00697         */
00698         clone_info=CloneDrawInfo(image_info,draw_info);
00699         clone_info->gravity=CenterGravity;
00700         clone_info->pointsize*=2.0;
00701         (void) GetTypeMetrics(image_list[0],clone_info,&metrics,exception);
00702         (void) FormatLocaleString(geometry,MaxTextExtent,
00703           "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
00704           (metrics.ascent-metrics.descent),0.0,(double) extract_info.y+4);
00705         (void) CloneString(&clone_info->geometry,geometry);
00706         (void) CloneString(&clone_info->text,title);
00707         (void) AnnotateImage(montage,clone_info,exception);
00708         clone_info=DestroyDrawInfo(clone_info);
00709       }
00710     (void) SetImageProgressMonitor(montage,progress_monitor,
00711       montage->client_data);
00712     /*
00713       Copy tile to the composite.
00714     */
00715     x_offset=0;
00716     y_offset=0;
00717     if (montage_info->tile != (char *) NULL)
00718       GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
00719         &sans,&sans);
00720     x_offset+=extract_info.x;
00721     y_offset+=(ssize_t) title_offset+extract_info.y;
00722     max_height=0;
00723     status=MagickTrue;
00724     for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
00725     {
00726       /*
00727         Copy this tile to the composite.
00728       */
00729       image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
00730       progress_monitor=SetImageProgressMonitor(image,
00731         (MagickProgressMonitor) NULL,image->client_data);
00732       width=concatenate != MagickFalse ? image->columns : extract_info.width;
00733       if (image->rows > max_height)
00734         max_height=image->rows;
00735       height=concatenate != MagickFalse ? max_height : extract_info.height;
00736       if (border_width != 0)
00737         {
00738           Image
00739             *border_image;
00740 
00741           RectangleInfo
00742             border_info;
00743 
00744           /*
00745             Put a border around the image.
00746           */
00747           border_info.width=border_width;
00748           border_info.height=border_width;
00749           if (montage_info->frame != (char *) NULL)
00750             {
00751               border_info.width=(width-image->columns+1)/2;
00752               border_info.height=(height-image->rows+1)/2;
00753             }
00754           border_image=BorderImage(image,&border_info,image->compose,exception);
00755           if (border_image != (Image *) NULL)
00756             {
00757               image=DestroyImage(image);
00758               image=border_image;
00759             }
00760           if ((montage_info->frame != (char *) NULL) &&
00761               (image->compose == DstOutCompositeOp))
00762             {
00763               SetPixelChannelMapMask(image,AlphaChannel);
00764               (void) NegateImage(image,MagickFalse,exception);
00765               SetPixelChannelMapMask(image,DefaultChannels);
00766             }
00767         }
00768       /*
00769         Gravitate as specified by the tile gravity.
00770       */
00771       tile_image->columns=width;
00772       tile_image->rows=height;
00773       tile_image->gravity=montage_info->gravity;
00774       if (image->gravity != UndefinedGravity)
00775         tile_image->gravity=image->gravity;
00776       (void) FormatLocaleString(tile_geometry,MaxTextExtent,"%.20gx%.20g+0+0",
00777         (double) image->columns,(double) image->rows);
00778       flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
00779       x=(ssize_t) (geometry.x+border_width);
00780       y=(ssize_t) (geometry.y+border_width);
00781       if ((montage_info->frame != (char *) NULL) && (bevel_width != 0))
00782         {
00783           FrameInfo
00784             extract_info;
00785 
00786           Image
00787             *frame_image;
00788 
00789           /*
00790             Put an ornamental border around this tile.
00791           */
00792           extract_info=frame_info;
00793           extract_info.width=width+2*frame_info.width;
00794           extract_info.height=height+2*frame_info.height;
00795           value=GetImageProperty(image,"label",exception);
00796           if (value != (const char *) NULL)
00797             extract_info.height+=(size_t) ((metrics.ascent-
00798               metrics.descent+4)*MultilineCensus(value));
00799           frame_image=FrameImage(image,&extract_info,image->compose,exception);
00800           if (frame_image != (Image *) NULL)
00801             {
00802               image=DestroyImage(image);
00803               image=frame_image;
00804             }
00805           x=0;
00806           y=0;
00807         }
00808       if (LocaleCompare(image->magick,"NULL") != 0)
00809         {
00810           /*
00811             Composite background with tile.
00812           */
00813           if (montage_info->shadow != MagickFalse)
00814             {
00815               Image
00816                 *shadow_image;
00817 
00818               /*
00819                 Shadow image.
00820               */
00821               (void) QueryColorCompliance("#0000",AllCompliance,
00822                 &image->background_color,exception);
00823               shadow_image=ShadowImage(image,80.0,2.0,0.0,5,5,exception);
00824               if (shadow_image != (Image *) NULL)
00825                 {
00826                   (void) CompositeImage(shadow_image,OverCompositeOp,image,0,0,
00827                     exception);
00828                   image=DestroyImage(image);
00829                   image=shadow_image;
00830                 }
00831           }
00832           (void) CompositeImage(montage,image->compose,image,x_offset+x,
00833             y_offset+y,exception);
00834           value=GetImageProperty(image,"label",exception);
00835           if (value != (const char *) NULL)
00836             {
00837               char
00838                 geometry[MaxTextExtent];
00839 
00840               /*
00841                 Annotate composite tile with label.
00842               */
00843               (void) FormatLocaleString(geometry,MaxTextExtent,
00844                 "%.20gx%.20g%+.20g%+.20g",(double) ((montage_info->frame ?
00845                 image->columns : width)-2*border_width),(double)
00846                 (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
00847                 (double) (x_offset+border_width),(double)
00848                 ((montage_info->frame ? y_offset+height+border_width+4 :
00849                 y_offset+extract_info.height+border_width+
00850                 (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
00851               (void) CloneString(&draw_info->geometry,geometry);
00852               (void) CloneString(&draw_info->text,value);
00853               (void) AnnotateImage(montage,draw_info,exception);
00854             }
00855         }
00856       x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
00857       if (((tile+1) == (ssize_t) tiles_per_page) ||
00858           (((tile+1) % tiles_per_row) == 0))
00859         {
00860           x_offset=extract_info.x;
00861           y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
00862             (metrics.ascent-metrics.descent+4)*number_lines+
00863             (montage_info->shadow != MagickFalse ? 4 : 0));
00864           max_height=0;
00865         }
00866       if (images->progress_monitor != (MagickProgressMonitor) NULL)
00867         {
00868           MagickBooleanType
00869             proceed;
00870 
00871           proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
00872           if (proceed == MagickFalse)
00873             status=MagickFalse;
00874         }
00875       image_list[tile]=DestroyImage(image_list[tile]);
00876       image=DestroyImage(image);
00877       tiles++;
00878     }
00879     (void) status;
00880     if ((i+1) < (ssize_t) images_per_page)
00881       {
00882         /*
00883           Allocate next image structure.
00884         */
00885         AcquireNextImage(clone_info,montage,exception);
00886         if (GetNextImageInList(montage) == (Image *) NULL)
00887           {
00888             montage=DestroyImageList(montage);
00889             return((Image *) NULL);
00890           }
00891         montage=GetNextImageInList(montage);
00892         montage->background_color=montage_info->background_color;
00893         image_list+=tiles_per_page;
00894         number_images-=tiles_per_page;
00895       }
00896   }
00897   tile_image=DestroyImage(tile_image);
00898   if (texture != (Image *) NULL)
00899     texture=DestroyImage(texture);
00900   master_list=(Image **) RelinquishMagickMemory(master_list);
00901   draw_info=DestroyDrawInfo(draw_info);
00902   clone_info=DestroyImageInfo(clone_info);
00903   return(GetFirstImageInList(montage));
00904 }