Ejemplo n.º 1
0
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e I m a g e s                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  WriteImages() writes an image sequence into one or more files.  While
%  WriteImage() can write an image sequence, it is limited to writing
%  the sequence into a single file using a format which supports multiple
%  frames.   WriteImages(), however, does not have this limitation, instead it
%  generates multiple output files if necessary (or when requested).  When
%  ImageInfo's adjoin flag is set to MagickFalse, the file name is expected
%  to include a printf-style formatting string for the frame number (e.g.
%  "image%02d.png").
%
%  The format of the WriteImages method is:
%
%      MagickBooleanType WriteImages(const ImageInfo *image_info,Image *images,
%        const char *filename,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image_info: the image info.
%
%    o images: the image list.
%
%    o filename: the image filename.
%
%    o exception: return any errors or warnings in this structure.
%
*/
MagickExport MagickBooleanType WriteImages(const ImageInfo *image_info,
  Image *images,const char *filename,ExceptionInfo *exception)
{
#define WriteImageTag  "Write/Image"

  BlobInfo
    *blob;

  ExceptionInfo
    *sans_exception;

  ImageInfo
    *write_info;

  MagickBooleanType
    proceed;

  MagickOffsetType
    i;

  MagickProgressMonitor
    progress_monitor;

  MagickSizeType
    number_images;

  MagickStatusType
    status;

  register Image
    *p;

  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  assert(images != (Image *) NULL);
  assert(images->signature == MagickSignature);
  if (images->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
  assert(exception != (ExceptionInfo *) NULL);
  write_info=CloneImageInfo(image_info);
  images=GetFirstImageInList(images);
  blob=CloneBlobInfo(images->blob);  /* thread specific I/O handler */
  DestroyBlob(images);
  images->blob=blob;
  if (filename != (const char *) NULL)
    for (p=images; p != (Image *) NULL; p=GetNextImageInList(p))
      (void) CopyMagickString(p->filename,filename,MaxTextExtent);
  (void) CopyMagickString(write_info->filename,images->filename,MaxTextExtent);
  if (*write_info->magick == '\0')
    (void) CopyMagickString(write_info->magick,images->magick,MaxTextExtent);
  sans_exception=AcquireExceptionInfo();
  (void) SetImageInfo(write_info,(unsigned int) GetImageListLength(images),
    sans_exception);
  sans_exception=DestroyExceptionInfo(sans_exception);
  p=images;
  for ( ; GetNextImageInList(p) != (Image *) NULL; p=GetNextImageInList(p))
    if (p->scene >= GetNextImageInList(p)->scene)
      {
        register ssize_t
          i;

        /*
          Generate consistent scene numbers.
        */
        i=(ssize_t) images->scene;
        for (p=images; p != (Image *) NULL; p=GetNextImageInList(p))
          p->scene=(size_t) i++;
        break;
      }
  /*
    Write images.
  */
  status=MagickTrue;
  progress_monitor=(MagickProgressMonitor) NULL;
  i=0;
  number_images=GetImageListLength(images);
  for (p=images; p != (Image *) NULL; p=GetNextImageInList(p))
  {
    if (number_images != 1)
      progress_monitor=SetImageProgressMonitor(p,(MagickProgressMonitor) NULL,
        p->client_data);
    status&=WriteImage(write_info,p,exception);
    if (number_images != 1)
      (void) SetImageProgressMonitor(p,progress_monitor,p->client_data);
    if (write_info->adjoin != MagickFalse)
      break;
    if (number_images != 1)
      {
        proceed=SetImageProgress(p,WriteImageTag,i++,number_images);
        if (proceed == MagickFalse)
          break;
      }
  }
  write_info=DestroyImageInfo(write_info);
  return(status != 0 ? MagickTrue : MagickFalse);
}
Ejemplo n.º 2
0
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d T E X T I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  ReadTEXTImage() reads a text file and returns it as an image.  It
%  allocates the memory necessary for the new Image structure and returns a
%  pointer to the new image.
%
%  The format of the ReadTEXTImage method is:
%
%      Image *ReadTEXTImage(const ImageInfo *image_info,Image *image,
%        char *text,ExceptionInfo *exception)
%
%  A description of each parameter follows:
%
%    o image_info: the image info.
%
%    o image: the image.
%
%    o text: the text storage buffer.
%
%    o exception: return any errors or warnings in this structure.
%
*/
static Image *ReadTEXTImage(const ImageInfo *image_info,Image *image,
  char *text,ExceptionInfo *exception)
{
  char
    filename[MaxTextExtent],
    geometry[MaxTextExtent],
    *p;

  DrawInfo
    *draw_info;

  Image
    *texture;

  MagickBooleanType
    status;

  PointInfo
    delta;

  RectangleInfo
    page;

  ssize_t
    offset;

  TypeMetric
    metrics;

  /*
    Open image file.
  */
  assert(image_info != (const ImageInfo *) NULL);
  assert(image_info->signature == MagickSignature);
  if (image_info->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
      image_info->filename);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickSignature);
  /*
    Set the page geometry.
  */
  delta.x=DefaultResolution;
  delta.y=DefaultResolution;
  if ((image->x_resolution == 0.0) || (image->y_resolution == 0.0))
    {
      GeometryInfo
        geometry_info;

      MagickStatusType
        flags;

      flags=ParseGeometry(PSDensityGeometry,&geometry_info);
      image->x_resolution=geometry_info.rho;
      image->y_resolution=geometry_info.sigma;
      if ((flags & SigmaValue) == 0)
        image->y_resolution=image->x_resolution;
    }
  page.width=612;
  page.height=792;
  page.x=43;
  page.y=43;
  if (image_info->page != (char *) NULL)
    (void) ParseAbsoluteGeometry(image_info->page,&page);
  /*
    Initialize Image structure.
  */
  image->columns=(size_t) floor((((double) page.width*image->x_resolution)/
    delta.x)+0.5);
  image->rows=(size_t) floor((((double) page.height*image->y_resolution)/
    delta.y)+0.5);
  image->page.x=0;
  image->page.y=0;
  texture=(Image *) NULL;
  if (image_info->texture != (char *) NULL)
    {
      ImageInfo
        *read_info;

      read_info=CloneImageInfo(image_info);
      SetImageInfoBlob(read_info,(void *) NULL,0);
      (void) CopyMagickString(read_info->filename,image_info->texture,
        MaxTextExtent);
      texture=ReadImage(read_info,exception);
      read_info=DestroyImageInfo(read_info);
    }
  /*
    Annotate the text image.
  */
  (void) SetImageBackgroundColor(image);
  draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL);
  (void) CloneString(&draw_info->text,image_info->filename);
  (void) FormatLocaleString(geometry,MaxTextExtent,"0x0%+ld%+ld",(long) page.x,
    (long) page.y);
  (void) CloneString(&draw_info->geometry,geometry);
  status=GetTypeMetrics(image,draw_info,&metrics);
  if (status == MagickFalse)
    ThrowReaderException(TypeError,"UnableToGetTypeMetrics");
  page.y=(ssize_t) ceil((double) page.y+metrics.ascent-0.5);
  (void) FormatLocaleString(geometry,MaxTextExtent,"0x0%+ld%+ld",(long) page.x,
    (long) page.y);
  (void) CloneString(&draw_info->geometry,geometry);
  (void) CopyMagickString(filename,image_info->filename,MaxTextExtent);
  if (*draw_info->text != '\0')
    *draw_info->text='\0';
  p=text;
  for (offset=2*page.y; p != (char *) NULL; )
  {
    /*
      Annotate image with text.
    */
    (void) ConcatenateString(&draw_info->text,text);
    (void) ConcatenateString(&draw_info->text,"\n");
    offset+=(ssize_t) (metrics.ascent-metrics.descent);
    if (image->previous == (Image *) NULL)
      {
        status=SetImageProgress(image,LoadImageTag,offset,image->rows);
        if (status == MagickFalse)
          break;
      }
    p=ReadBlobString(image,text);
    if ((offset < (ssize_t) image->rows) && (p != (char *) NULL))
      continue;
    if (texture != (Image *) NULL)
      {
        MagickProgressMonitor
          progress_monitor;

        progress_monitor=SetImageProgressMonitor(image,
          (MagickProgressMonitor) NULL,image->client_data);
        (void) TextureImage(image,texture);
        (void) SetImageProgressMonitor(image,progress_monitor,
          image->client_data);
      }
    (void) AnnotateImage(image,draw_info);
    if (p == (char *) NULL)
      break;
    /*
      Page is full-- allocate next image structure.
    */
    *draw_info->text='\0';
    offset=2*page.y;
    AcquireNextImage(image_info,image);
    if (GetNextImageInList(image) == (Image *) NULL)
      {
        image=DestroyImageList(image);
        return((Image *) NULL);
      }
    image->next->columns=image->columns;
    image->next->rows=image->rows;
    image=SyncNextImageInList(image);
    (void) CopyMagickString(image->filename,filename,MaxTextExtent);
    (void) SetImageBackgroundColor(image);
    status=SetImageProgress(image,LoadImagesTag,TellBlob(image),
      GetBlobSize(image));
    if (status == MagickFalse)
      break;
  }
  if (texture != (Image *) NULL)
    {
      MagickProgressMonitor
        progress_monitor;

      progress_monitor=SetImageProgressMonitor(image,
        (MagickProgressMonitor) NULL,image->client_data);
      (void) TextureImage(image,texture);
      (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
    }
  (void) AnnotateImage(image,draw_info);
  if (texture != (Image *) NULL)
    texture=DestroyImage(texture);
  draw_info=DestroyDrawInfo(draw_info);
  (void) CloseBlob(image);
  return(GetFirstImageInList(image));
}
Ejemplo n.º 3
0
MagickExport Image *MontageImageList(const ImageInfo *image_info,
  const MontageInfo *montage_info,const Image *images,ExceptionInfo *exception)
{
#define MontageImageTag  "Montage/Image"
#define TileImageTag  "Tile/Image"

  char
    tile_geometry[MagickPathExtent],
    *title;

  const char
    *value;

  DrawInfo
    *draw_info;

  FrameInfo
    frame_info;

  Image
    *image,
    **image_list,
    **master_list,
    *montage,
    *texture,
    *tile_image,
    *thumbnail;

  ImageInfo
    *clone_info;

  MagickBooleanType
    concatenate,
    proceed,
    status;

  MagickOffsetType
    tiles;

  MagickProgressMonitor
    progress_monitor;

  MagickStatusType
    flags;

  register ssize_t
    i;

  RectangleInfo
    bounds,
    geometry,
    extract_info;

  size_t
    border_width,
    extent,
    height,
    images_per_page,
    max_height,
    number_images,
    number_lines,
    sans,
    tiles_per_column,
    tiles_per_page,
    tiles_per_row,
    title_offset,
    total_tiles,
    width;

  ssize_t
    bevel_width,
    tile,
    x,
    x_offset,
    y,
    y_offset;

  TypeMetric
    metrics;

  /*
    Create image tiles.
  */
  assert(images != (Image *) NULL);
  assert(images->signature == MagickCoreSignature);
  if (images->debug != MagickFalse)
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
  assert(montage_info != (MontageInfo *) NULL);
  assert(montage_info->signature == MagickCoreSignature);
  assert(exception != (ExceptionInfo *) NULL);
  assert(exception->signature == MagickCoreSignature);
  number_images=GetImageListLength(images);
  master_list=ImageListToArray(images,exception);
  image_list=master_list;
  image=image_list[0];
  if (master_list == (Image **) NULL)
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
  thumbnail=NewImageList();
  for (i=0; i < (ssize_t) number_images; i++)
  {
    image=CloneImage(image_list[i],0,0,MagickTrue,exception);
    if (image == (Image *) NULL)
      break;
    (void) ParseAbsoluteGeometry("0x0+0+0",&image->page);
    progress_monitor=SetImageProgressMonitor(image,(MagickProgressMonitor) NULL,
      image->client_data);
    flags=ParseRegionGeometry(image,montage_info->geometry,&geometry,exception);
    thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
    if (thumbnail == (Image *) NULL)
      break;
    image_list[i]=thumbnail;
    (void) SetImageProgressMonitor(image,progress_monitor,image->client_data);
    proceed=SetImageProgress(image,TileImageTag,(MagickOffsetType) i,
      number_images);
    if (proceed == MagickFalse)
      break;
    image=DestroyImage(image);
  }
  if (i < (ssize_t) number_images)
    {
      if (thumbnail == (Image *) NULL)
        i--;
      for (tile=0; (ssize_t) tile <= i; tile++)
        if (image_list[tile] != (Image *) NULL)
          image_list[tile]=DestroyImage(image_list[tile]);
      master_list=(Image **) RelinquishMagickMemory(master_list);
      return((Image *) NULL);
    }
  /*
    Sort image list by increasing tile number.
  */
  for (i=0; i < (ssize_t) number_images; i++)
    if (image_list[i]->scene == 0)
      break;
  if (i == (ssize_t) number_images)
    qsort((void *) image_list,(size_t) number_images,sizeof(*image_list),
      SceneCompare);
  /*
    Determine tiles per row and column.
  */
  tiles_per_column=(size_t) sqrt((double) number_images);
  tiles_per_row=(size_t) ceil((double) number_images/tiles_per_column);
  x_offset=0;
  y_offset=0;
  if (montage_info->tile != (char *) NULL)
    GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
      &tiles_per_column,&tiles_per_row);
  /*
    Determine tile sizes.
  */
  concatenate=MagickFalse;
  SetGeometry(image_list[0],&extract_info);
  extract_info.x=(ssize_t) montage_info->border_width;
  extract_info.y=(ssize_t) montage_info->border_width;
  if (montage_info->geometry != (char *) NULL)
    {
      /*
        Initialize tile geometry.
      */
      flags=GetGeometry(montage_info->geometry,&extract_info.x,&extract_info.y,
        &extract_info.width,&extract_info.height);
      concatenate=((flags & RhoValue) == 0) && ((flags & SigmaValue) == 0) ?
        MagickTrue : MagickFalse;
    }
  border_width=montage_info->border_width;
  bevel_width=0;
  (void) ResetMagickMemory(&frame_info,0,sizeof(frame_info));
  if (montage_info->frame != (char *) NULL)
    {
      char
        absolute_geometry[MagickPathExtent];

      frame_info.width=extract_info.width;
      frame_info.height=extract_info.height;
      (void) FormatLocaleString(absolute_geometry,MagickPathExtent,"%s!",
        montage_info->frame);
      flags=ParseMetaGeometry(absolute_geometry,&frame_info.outer_bevel,
        &frame_info.inner_bevel,&frame_info.width,&frame_info.height);
      if ((flags & HeightValue) == 0)
        frame_info.height=frame_info.width;
      if ((flags & XiValue) == 0)
        frame_info.outer_bevel=(ssize_t) frame_info.width/2-1;
      if ((flags & PsiValue) == 0)
        frame_info.inner_bevel=frame_info.outer_bevel;
      frame_info.x=(ssize_t) frame_info.width;
      frame_info.y=(ssize_t) frame_info.height;
      bevel_width=(ssize_t) MagickMax(frame_info.inner_bevel,
        frame_info.outer_bevel);
      border_width=(size_t) MagickMax((ssize_t) frame_info.width,
        (ssize_t) frame_info.height);
    }
  for (i=0; i < (ssize_t) number_images; i++)
  {
    if (image_list[i]->columns > extract_info.width)
      extract_info.width=image_list[i]->columns;
    if (image_list[i]->rows > extract_info.height)
      extract_info.height=image_list[i]->rows;
  }
  /*
    Initialize draw attributes.
  */
  clone_info=CloneImageInfo(image_info);
  clone_info->background_color=montage_info->background_color;
  clone_info->border_color=montage_info->border_color;
  draw_info=CloneDrawInfo(clone_info,(DrawInfo *) NULL);
  if (montage_info->font != (char *) NULL)
    (void) CloneString(&draw_info->font,montage_info->font);
  if (montage_info->pointsize != 0.0)
    draw_info->pointsize=montage_info->pointsize;
  draw_info->gravity=CenterGravity;
  draw_info->stroke=montage_info->stroke;
  draw_info->fill=montage_info->fill;
  draw_info->text=AcquireString("");
  (void) GetTypeMetrics(image_list[0],draw_info,&metrics,exception);
  texture=NewImageList();
  if (montage_info->texture != (char *) NULL)
    {
      (void) CopyMagickString(clone_info->filename,montage_info->texture,
        MagickPathExtent);
      texture=ReadImage(clone_info,exception);
    }
  /*
    Determine the number of lines in an next label.
  */
  title=InterpretImageProperties(clone_info,image_list[0],montage_info->title,
    exception);
  title_offset=0;
  if (montage_info->title != (char *) NULL)
    title_offset=(size_t) (2*(metrics.ascent-metrics.descent)*
      MultilineCensus(title)+2*extract_info.y);
  number_lines=0;
  for (i=0; i < (ssize_t) number_images; i++)
  {
    value=GetImageProperty(image_list[i],"label",exception);
    if (value == (const char *) NULL)
      continue;
    if (MultilineCensus(value) > number_lines)
      number_lines=MultilineCensus(value);
  }
  /*
    Allocate next structure.
  */
  tile_image=AcquireImage((ImageInfo *) NULL,exception);
  montage=AcquireImage(clone_info,exception);
  montage->background_color=montage_info->background_color;
  montage->scene=0;
  images_per_page=(number_images-1)/(tiles_per_row*tiles_per_column)+1;
  tiles=0;
  total_tiles=(size_t) number_images;
  for (i=0; i < (ssize_t) images_per_page; i++)
  {
    /*
      Determine bounding box.
    */
    tiles_per_page=tiles_per_row*tiles_per_column;
    x_offset=0;
    y_offset=0;
    if (montage_info->tile != (char *) NULL)
      GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
        &sans,&sans);
    tiles_per_page=tiles_per_row*tiles_per_column;
    y_offset+=(ssize_t) title_offset;
    max_height=0;
    bounds.width=0;
    bounds.height=0;
    width=0;
    for (tile=0; tile < (ssize_t) tiles_per_page; tile++)
    {
      if (tile < (ssize_t) number_images)
        {
          width=concatenate != MagickFalse ? image_list[tile]->columns :
            extract_info.width;
          if (image_list[tile]->rows > max_height)
            max_height=image_list[tile]->rows;
        }
      x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
      if (x_offset > (ssize_t) bounds.width)
        bounds.width=(size_t) x_offset;
      if (((tile+1) == (ssize_t) tiles_per_page) ||
          (((tile+1) % tiles_per_row) == 0))
        {
          x_offset=0;
          if (montage_info->tile != (char *) NULL)
            GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y,
              &sans,&sans);
          height=concatenate != MagickFalse ? max_height : extract_info.height;
          y_offset+=(ssize_t) (height+(extract_info.y+(ssize_t) border_width)*2+
            (metrics.ascent-metrics.descent+4)*number_lines+
            (montage_info->shadow != MagickFalse ? 4 : 0));
          if (y_offset > (ssize_t) bounds.height)
            bounds.height=(size_t) y_offset;
          max_height=0;
        }
    }
    if (montage_info->shadow != MagickFalse)
      bounds.width+=4;
    /*
      Initialize montage image.
    */
    (void) CopyMagickString(montage->filename,montage_info->filename,
      MagickPathExtent);
    montage->columns=(size_t) MagickMax((ssize_t) bounds.width,1);
    montage->rows=(size_t) MagickMax((ssize_t) bounds.height,1);
    (void) SetImageBackgroundColor(montage,exception);
    /*
      Set montage geometry.
    */
    montage->montage=AcquireString((char *) NULL);
    tile=0;
    extent=1;
    while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
    {
      extent+=strlen(image_list[tile]->filename)+1;
      tile++;
    }
    montage->directory=(char *) AcquireQuantumMemory(extent,
      sizeof(*montage->directory));
    if ((montage->montage == (char *) NULL) ||
        (montage->directory == (char *) NULL))
      {
        if (montage->montage != (char *) NULL)
          montage->montage=(char *) RelinquishMagickMemory(montage->montage);
        if (montage->directory != (char *) NULL)
          montage->directory=(char *) RelinquishMagickMemory(
            montage->directory);
        ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
      }
    x_offset=0;
    y_offset=0;
    if (montage_info->tile != (char *) NULL)
      GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
        &sans,&sans);
    y_offset+=(ssize_t) title_offset;
    (void) FormatLocaleString(montage->montage,MagickPathExtent,
      "%.20gx%.20g%+.20g%+.20g",(double) (extract_info.width+
      (extract_info.x+border_width)*2),(double) (extract_info.height+
      (extract_info.y+border_width)*2+(double) ((metrics.ascent-
      metrics.descent+4)*number_lines+(montage_info->shadow != MagickFalse ? 4 :
      0))),(double) x_offset,(double) y_offset);
    *montage->directory='\0';
    tile=0;
    while (tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images))
    {
      (void) ConcatenateMagickString(montage->directory,
        image_list[tile]->filename,extent);
      (void) ConcatenateMagickString(montage->directory,"\n",extent);
      tile++;
    }
    progress_monitor=SetImageProgressMonitor(montage,(MagickProgressMonitor)
      NULL,montage->client_data);
    if (texture != (Image *) NULL)
      (void) TextureImage(montage,texture,exception);
    if (montage_info->title != (char *) NULL)
      {
        DrawInfo
          *draw_clone_info;

        TypeMetric
          tile_metrics;

        /*
          Annotate composite image with title.
        */
        draw_clone_info=CloneDrawInfo(image_info,draw_info);
        draw_clone_info->gravity=CenterGravity;
        draw_clone_info->pointsize*=2.0;
        (void) GetTypeMetrics(image_list[0],draw_clone_info,&tile_metrics,
          exception);
        (void) FormatLocaleString(tile_geometry,MagickPathExtent,
          "%.20gx%.20g%+.20g%+.20g",(double) montage->columns,(double)
          (tile_metrics.ascent-tile_metrics.descent),0.0,
          (double) extract_info.y+4);
        (void) CloneString(&draw_clone_info->geometry,tile_geometry);
        (void) CloneString(&draw_clone_info->text,title);
        (void) AnnotateImage(montage,draw_clone_info,exception);
        draw_clone_info=DestroyDrawInfo(draw_clone_info);
      }
    (void) SetImageProgressMonitor(montage,progress_monitor,
      montage->client_data);
    /*
      Copy tile to the composite.
    */
    x_offset=0;
    y_offset=0;
    if (montage_info->tile != (char *) NULL)
      GetMontageGeometry(montage_info->tile,number_images,&x_offset,&y_offset,
        &sans,&sans);
    x_offset+=extract_info.x;
    y_offset+=(ssize_t) title_offset+extract_info.y;
    max_height=0;
    status=MagickTrue;
    for (tile=0; tile < MagickMin((ssize_t) tiles_per_page,(ssize_t) number_images); tile++)
    {
      /*
        Copy this tile to the composite.
      */
      image=CloneImage(image_list[tile],0,0,MagickTrue,exception);
      if (image == (Image *) NULL)
        ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
      progress_monitor=SetImageProgressMonitor(image,
        (MagickProgressMonitor) NULL,image->client_data);
      width=concatenate != MagickFalse ? image->columns : extract_info.width;
      if (image->rows > max_height)
        max_height=image->rows;
      height=concatenate != MagickFalse ? max_height : extract_info.height;
      if (border_width != 0)
        {
          Image
            *border_image;

          RectangleInfo
            border_info;

          /*
            Put a border around the image.
          */
          border_info.width=border_width;
          border_info.height=border_width;
          if (montage_info->frame != (char *) NULL)
            {
              border_info.width=(width-image->columns+1)/2;
              border_info.height=(height-image->rows+1)/2;
            }
          border_image=BorderImage(image,&border_info,image->compose,exception);
          if (border_image != (Image *) NULL)
            {
              image=DestroyImage(image);
              image=border_image;
            }
          if ((montage_info->frame != (char *) NULL) &&
              (image->compose == DstOutCompositeOp))
            {
              (void) SetPixelChannelMask(image,AlphaChannel);
              (void) NegateImage(image,MagickFalse,exception);
              (void) SetPixelChannelMask(image,DefaultChannels);
            }
        }
      /*
        Gravitate as specified by the tile gravity.
      */
      tile_image->columns=width;
      tile_image->rows=height;
      tile_image->gravity=montage_info->gravity;
      if (image->gravity != UndefinedGravity)
        tile_image->gravity=image->gravity;
      (void) FormatLocaleString(tile_geometry,MagickPathExtent,
        "%.20gx%.20g+0+0",(double) image->columns,(double) image->rows);
      flags=ParseGravityGeometry(tile_image,tile_geometry,&geometry,exception);
      x=(ssize_t) (geometry.x+border_width);
      y=(ssize_t) (geometry.y+border_width);
      if ((montage_info->frame != (char *) NULL) && (bevel_width > 0))
        {
          FrameInfo
            frame_clone;

          Image
            *frame_image;

          /*
            Put an ornamental border around this tile.
          */
          frame_clone=frame_info;
          frame_clone.width=width+2*frame_info.width;
          frame_clone.height=height+2*frame_info.height;
          value=GetImageProperty(image,"label",exception);
          if (value != (const char *) NULL)
            frame_clone.height+=(size_t) ((metrics.ascent-metrics.descent+4)*
              MultilineCensus(value));
          frame_image=FrameImage(image,&frame_clone,image->compose,exception);
          if (frame_image != (Image *) NULL)
            {
              image=DestroyImage(image);
              image=frame_image;
            }
          x=0;
          y=0;
        }
      if (LocaleCompare(image->magick,"NULL") != 0)
        {
          /*
            Composite background with tile.
          */
          if (montage_info->shadow != MagickFalse)
            {
              Image
                *shadow_image;

              /*
                Shadow image.
              */
              (void) QueryColorCompliance("#0000",AllCompliance,
                &image->background_color,exception);
              shadow_image=ShadowImage(image,80.0,2.0,5,5,exception);
              if (shadow_image != (Image *) NULL)
                {
                  (void) CompositeImage(shadow_image,image,OverCompositeOp,
                    MagickTrue,0,0,exception);
                  image=DestroyImage(image);
                  image=shadow_image;
                }
          }
          (void) CompositeImage(montage,image,image->compose,MagickTrue,
            x_offset+x,y_offset+y,exception);
          value=GetImageProperty(image,"label",exception);
          if (value != (const char *) NULL)
            {
              /*
                Annotate composite tile with label.
              */
              (void) FormatLocaleString(tile_geometry,MagickPathExtent,
                "%.20gx%.20g%+.20g%+.20g",(double) ((montage_info->frame ?
                image->columns : width)-2*border_width),(double)
                (metrics.ascent-metrics.descent+4)*MultilineCensus(value),
                (double) (x_offset+border_width),(double)
                ((montage_info->frame ? y_offset+height+border_width+4 :
                y_offset+extract_info.height+border_width+
                (montage_info->shadow != MagickFalse ? 4 : 0))+bevel_width));
              (void) CloneString(&draw_info->geometry,tile_geometry);
              (void) CloneString(&draw_info->text,value);
              (void) AnnotateImage(montage,draw_info,exception);
            }
        }
      x_offset+=(ssize_t) (width+2*(extract_info.x+border_width));
      if (((tile+1) == (ssize_t) tiles_per_page) ||
          (((tile+1) % tiles_per_row) == 0))
        {
          x_offset=extract_info.x;
          y_offset+=(ssize_t) (height+(extract_info.y+border_width)*2+
            (metrics.ascent-metrics.descent+4)*number_lines+
            (montage_info->shadow != MagickFalse ? 4 : 0));
          max_height=0;
        }
      if (images->progress_monitor != (MagickProgressMonitor) NULL)
        {
          proceed=SetImageProgress(image,MontageImageTag,tiles,total_tiles);
          if (proceed == MagickFalse)
            status=MagickFalse;
        }
      image_list[tile]=DestroyImage(image_list[tile]);
      image=DestroyImage(image);
      tiles++;
    }
    (void) status;
    if ((i+1) < (ssize_t) images_per_page)
      {
        /*
          Allocate next image structure.
        */
        AcquireNextImage(clone_info,montage,exception);
        if (GetNextImageInList(montage) == (Image *) NULL)
          {
            montage=DestroyImageList(montage);
            return((Image *) NULL);
          }
        montage=GetNextImageInList(montage);
        montage->background_color=montage_info->background_color;
        image_list+=tiles_per_page;
        number_images-=tiles_per_page;
      }
  }
  tile_image=DestroyImage(tile_image);
  if (texture != (Image *) NULL)
    texture=DestroyImage(texture);
  title=DestroyString(title);
  master_list=(Image **) RelinquishMagickMemory(master_list);
  draw_info=DestroyDrawInfo(draw_info);
  clone_info=DestroyImageInfo(clone_info);
  return(GetFirstImageInList(montage));
}