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