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