/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % AAA SSSSS H H L AAA RRRR % % A A SS H H L A A R R % % AAAAA SSS HHHHH L AAAAA RRRR % % A A SS H H L A A R R % % A A SSSSS H H LLLLL A A R R % % % % % % Write Ashlar Images % % % % Software Design % % Cristy % % July 2020 % % % % % % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % https://imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % */ /* Include declarations. */ #include "MagickCore/studio.h" #include "MagickCore/annotate.h" #include "MagickCore/blob.h" #include "MagickCore/blob-private.h" #include "MagickCore/client.h" #include "MagickCore/constitute.h" #include "MagickCore/display.h" #include "MagickCore/exception.h" #include "MagickCore/exception-private.h" #include "MagickCore/image.h" #include "MagickCore/image-private.h" #include "MagickCore/list.h" #include "MagickCore/magick.h" #include "MagickCore/memory_.h" #include "MagickCore/option.h" #include "MagickCore/property.h" #include "MagickCore/quantum-private.h" #include "MagickCore/static.h" #include "MagickCore/string_.h" #include "MagickCore/module.h" #include "MagickCore/utility.h" #include "MagickCore/xwindow.h" #include "MagickCore/xwindow-private.h" /* Forward declarations. */ static MagickBooleanType WriteASHLARImage(const ImageInfo *,Image *,ExceptionInfo *); /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r A S H L A R I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterASHLARImage() adds attributes for the ASHLAR image format to % the list of supported formats. The attributes include the image format % tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, % whether the format supports native in-memory I/O, and a brief % description of the format. % % The format of the RegisterASHLARImage method is: % % size_t RegisterASHLARImage(void) % */ ModuleExport size_t RegisterASHLARImage(void) { MagickInfo *entry; entry=AcquireMagickInfo("ASHLAR","ASHLAR", "Image sequence laid out in continuous irregular courses"); entry->encoder=(EncodeImageHandler *) WriteASHLARImage; (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r A S H L A R I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterASHLARImage() removes format registrations made by the % ASHLAR module from the list of supported formats. % % The format of the UnregisterASHLARImage method is: % % UnregisterASHLARImage(void) % */ ModuleExport void UnregisterASHLARImage(void) { (void) UnregisterMagickInfo("ASHLAR"); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % W r i t e A S H L A R I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % WriteASHLARImage() writes an image to a file in ASHLAR format. % % The format of the WriteASHLARImage method is: % % MagickBooleanType WriteASHLARImage(const ImageInfo *image_info, % Image *image,ExceptionInfo *exception) % % A description of each parameter follows. % % o image_info: the image info. % % o image: The image. % % o exception: return any errors or warnings in this structure. % */ typedef struct _NodeInfo { ssize_t x, y; struct _NodeInfo *next; } NodeInfo; typedef struct _AshlarInfo { size_t width, height; ssize_t align; size_t number_nodes; MagickBooleanType best_fit; NodeInfo *current, *free, head, sentinal; } AshlarInfo; typedef struct _CanvasInfo { ssize_t id; size_t width, height; ssize_t x, y, order; } CanvasInfo; typedef struct _TileInfo { ssize_t x, y; NodeInfo **previous; } TileInfo; static ssize_t FindMinimumTileLocation(NodeInfo *first,const ssize_t x, const size_t width,ssize_t *excess) { NodeInfo *node; ssize_t extent, y; /* Find minimum y location if it starts at x. */ *excess=0; y=0; extent=0; node=first; while (node->x < (ssize_t) (x+width)) { if (node->y > y) { *excess+=extent*(node->y-y); y=node->y; if (node->x < x) extent+=node->next->x-x; else extent+=node->next->x-node->x; } else { size_t delta = (size_t) (node->next->x-node->x); if ((delta+extent) > width) delta=width-extent; *excess+=delta*(y-node->y); extent+=delta; } node=node->next; } return(y); } static TileInfo AssignBestTileLocation(AshlarInfo *ashlar_info, size_t width,size_t height) { NodeInfo *node, **previous, *tail; ssize_t min_excess; TileInfo tile; /* Align along left edge. */ tile.previous=(NodeInfo **) NULL; width=(width+ashlar_info->align-1); width-=width % ashlar_info->align; if ((width > ashlar_info->width) || (height > ashlar_info->height)) { /* Tile can't fit, bail. */ tile.x=0; tile.y=0; return(tile); } tile.x=(ssize_t) MAGICK_SSIZE_MAX; tile.y=(ssize_t) MAGICK_SSIZE_MAX; min_excess=(ssize_t) MAGICK_SSIZE_MAX; node=ashlar_info->current; previous=(&ashlar_info->current); while ((width+node->x) <= ashlar_info->width) { ssize_t excess, y; y=FindMinimumTileLocation(node,node->x,width,&excess); if (ashlar_info->best_fit == MagickFalse) { if (y < tile.y) { tile.y=y; tile.previous=previous; } } else { if ((height+y) <= ashlar_info->height) if ((y < tile.y) || ((y == tile.y) && (excess < min_excess))) { tile.y=y; tile.previous=previous; min_excess=excess; } } previous=(&node->next); node=node->next; } tile.x=(tile.previous == (NodeInfo **) NULL) ? 0 : (*tile.previous)->x; if (ashlar_info->best_fit != MagickFalse) { /* Align along both left and right edges. */ tail=ashlar_info->current; node=ashlar_info->current; previous=(&ashlar_info->current); while (tail->x < (ssize_t) width) tail=tail->next; while (tail != (NodeInfo *) NULL) { ssize_t excess, x, y; x=tail->x-width; while (node->next->x <= x) { previous=(&node->next); node=node->next; } y=FindMinimumTileLocation(node,x,width,&excess); if ((height+y) <= ashlar_info->height) { if (y <= tile.y) if ((y < tile.y) || (excess < min_excess) || ((excess == min_excess) && (x < tile.x))) { tile.x=x; tile.y=y; min_excess=excess; tile.previous=previous; } } tail=tail->next; } } return(tile); } static TileInfo AssignTileLocation(AshlarInfo *ashlar_info,const size_t width, const size_t height) { NodeInfo *current, *node; TileInfo tile; /* Find the best location in the canvas for this tile. */ tile=AssignBestTileLocation(ashlar_info,width,height); if ((tile.previous == (NodeInfo **) NULL) || ((tile.y+(ssize_t) height) > (ssize_t) ashlar_info->height) || (ashlar_info->free == (NodeInfo *) NULL)) { tile.previous=(NodeInfo **) NULL; return(tile); } /* Create a new node. */ node=ashlar_info->free; node->x=(ssize_t) tile.x; node->y=(ssize_t) (tile.y+height); ashlar_info->free=node->next; /* Insert node. */ current=(*tile.previous); if (current->x >= tile.x) *tile.previous=node; else { NodeInfo *next = current->next; current->next=node; current=next; } while ((current->next != (NodeInfo *) NULL) && (current->next->x <= (tile.x+(ssize_t) width))) { /* Push current node to free list. */ NodeInfo *next = current->next; current->next=ashlar_info->free; ashlar_info->free=current; current=next; } node->next=current; if (current->x < (tile.x+(ssize_t) width)) current->x=(ssize_t) (tile.x+width); return(tile); } static int CompareTileHeight(const void *p_tile,const void *q_tile) { const CanvasInfo *p, *q; p=(const CanvasInfo *) p_tile; q=(const CanvasInfo *) q_tile; if (p->height > q->height) return(-1); if (p->height < q->height) return(1); return((p->width > q->width) ? -1 : (p->width < q->width) ? 1 : 0); } static int RestoreTileOrder(const void *p_tile,const void *q_tile) { const CanvasInfo *p, *q; p=(const CanvasInfo *) p_tile; q=(const CanvasInfo *) q_tile; return((p->order < q->order) ? -1 : (p->order > q->order) ? 1 : 0); } static MagickBooleanType PackAshlarTiles(AshlarInfo *ashlar_info, CanvasInfo *tiles,const size_t number_tiles) { MagickBooleanType status; ssize_t i; /* Pack tiles so they fit the canvas with minimum excess. */ for (i=0; i < (ssize_t) number_tiles; i++) tiles[i].order=(i); qsort((void *) tiles,number_tiles,sizeof(*tiles),CompareTileHeight); for (i=0; i < (ssize_t) number_tiles; i++) { tiles[i].x=0; tiles[i].y=0; if ((tiles[i].width != 0) && (tiles[i].height != 0)) { TileInfo tile_info; tile_info=AssignTileLocation(ashlar_info,tiles[i].width, tiles[i].height); tiles[i].x=(ssize_t) tile_info.x; tiles[i].y=(ssize_t) tile_info.y; if (tile_info.previous == (NodeInfo **) NULL) { tiles[i].x=(ssize_t) MAGICK_SSIZE_MAX; tiles[i].y=(ssize_t) MAGICK_SSIZE_MAX; } } } qsort((void *) tiles,number_tiles,sizeof(*tiles),RestoreTileOrder); status=MagickTrue; for (i=0; i < (ssize_t) number_tiles; i++) { tiles[i].order=(ssize_t) ((tiles[i].x != (ssize_t) MAGICK_SSIZE_MAX) || (tiles[i].y != (ssize_t) MAGICK_SSIZE_MAX) ? 1 : 0); if (tiles[i].order == 0) status=MagickFalse; } return(status); /* return true if room is found for all tiles */ } static MagickBooleanType WriteASHLARImage(const ImageInfo *image_info, Image *image,ExceptionInfo *exception) { AshlarInfo ashlar_info; CanvasInfo *tiles; const char *value; Image *ashlar_image, *next; ImageInfo *write_info; MagickBooleanType status; NodeInfo *nodes; RectangleInfo extent, geometry; ssize_t i, n; /* Convert image sequence laid out in continuous irregular courses. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickCoreSignature); assert(image != (Image *) NULL); assert(image->signature == MagickCoreSignature); if (image->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); if (image_info->extract != (char *) NULL) (void) ParseAbsoluteGeometry(image_info->extract,&geometry); else { /* Determine a sane canvas size and border width. */ (void) ParseAbsoluteGeometry("0x0+0+0",&geometry); for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) { geometry.width+=next->columns; geometry.height+=next->rows; } geometry.width=(size_t) geometry.width/7; geometry.height=(size_t) geometry.height/7; geometry.x=(ssize_t) pow((double) geometry.width,0.25); geometry.y=(ssize_t) pow((double) geometry.height,0.25); } /* Initialize image tiles. */ ashlar_image=AcquireImage(image_info,exception); status=SetImageExtent(ashlar_image,geometry.width,geometry.height,exception); if (status == MagickFalse) { ashlar_image=DestroyImageList(ashlar_image); return(MagickFalse); } (void) SetImageBackgroundColor(ashlar_image,exception); tiles=(CanvasInfo *) AcquireQuantumMemory(GetImageListLength(image), sizeof(*tiles)); ashlar_info.number_nodes=2*geometry.width; nodes=(NodeInfo *) AcquireQuantumMemory(ashlar_info.number_nodes, sizeof(*nodes)); if ((tiles == (CanvasInfo *) NULL) || (nodes == (NodeInfo *) NULL)) { if (tiles != (CanvasInfo *) NULL) tiles=(CanvasInfo *) RelinquishMagickMemory(tiles); if (nodes != (NodeInfo *) NULL) nodes=(NodeInfo *) RelinquishMagickMemory(tiles); ashlar_image=DestroyImageList(ashlar_image); ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed"); } /* Interate until we find a tile size that fits the canvas. */ value=GetImageOption(image_info,"ashlar:best-fit"); for (i=20; i > 0; i--) { ssize_t j; n=0; for (next=image; next != (Image *) NULL; next=GetNextImageInList(next)) { tiles[n].id=n; tiles[n].width=(size_t) (0.05*i*next->columns+2*geometry.x); tiles[n].height=(size_t) (0.05*i*next->rows+2*geometry.y); n++; } for (j=0; j < (ssize_t) ashlar_info.number_nodes-1; j++) nodes[j].next=nodes+j+1; nodes[j].next=(NodeInfo *) NULL; ashlar_info.best_fit=IsStringTrue(value) != MagickFalse ? MagickTrue : MagickFalse; ashlar_info.free=nodes; ashlar_info.current=(&ashlar_info.head); ashlar_info.width=geometry.width; ashlar_info.height=geometry.height; ashlar_info.align=(ssize_t) ((ashlar_info.width+ashlar_info.number_nodes-1)/ ashlar_info.number_nodes); ashlar_info.head.x=0; ashlar_info.head.y=0; ashlar_info.head.next=(&ashlar_info.sentinal); ashlar_info.sentinal.x=(ssize_t) geometry.width; ashlar_info.sentinal.y=(ssize_t) MAGICK_SSIZE_MAX; ashlar_info.sentinal.next=(NodeInfo *) NULL; status=PackAshlarTiles(&ashlar_info,tiles,(size_t) n); if (status != MagickFalse) break; } /* Determine layout of images tiles on the canvas. */ value=GetImageOption(image_info,"label"); extent.width=0; extent.height=0; for (i=0; i < n; i++) { Image *tile_image; if ((tiles[i].x == (ssize_t) MAGICK_SSIZE_MAX) || (tiles[i].y == (ssize_t) MAGICK_SSIZE_MAX)) continue; tile_image=ResizeImage(GetImageFromList(image,tiles[i].id),(size_t) (tiles[i].width-2*geometry.x),(size_t) (tiles[i].height-2*geometry.y), image->filter,exception); if (tile_image == (Image *) NULL) continue; (void) CompositeImage(ashlar_image,tile_image,image->compose,MagickTrue, tiles[i].x+geometry.x,tiles[i].y+geometry.y,exception); if (value != (const char *) NULL) { char *label, offset[MagickPathExtent]; DrawInfo *draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL); label=InterpretImageProperties((ImageInfo *) image_info,tile_image, value,exception); if (label != (const char *) NULL) { (void) CloneString(&draw_info->text,label); draw_info->pointsize=1.8*geometry.y; (void) FormatLocaleString(offset,MagickPathExtent,"%+g%+g",(double) tiles[i].x+geometry.x,(double) tiles[i].height+tiles[i].y+ geometry.y/2.0); (void) CloneString(&draw_info->geometry,offset); (void) AnnotateImage(ashlar_image,draw_info,exception); } } if ((tiles[i].width+tiles[i].x) > extent.width) extent.width=(size_t) (tiles[i].width+tiles[i].x); if ((tiles[i].height+tiles[i].y) > extent.height) extent.height=(size_t) (tiles[i].height+tiles[i].y); tile_image=DestroyImage(tile_image); } (void) SetImageExtent(ashlar_image,extent.width,extent.height,exception); nodes=(NodeInfo *) RelinquishMagickMemory(nodes); tiles=(CanvasInfo *) RelinquishMagickMemory(tiles); /* Write ASHLAR canvas. */ (void) CopyMagickString(ashlar_image->filename,image_info->filename, MagickPathExtent); write_info=CloneImageInfo(image_info); *write_info->magick='\0'; (void) SetImageInfo(write_info,1,exception); if ((*write_info->magick == '\0') || (LocaleCompare(write_info->magick,"ASHLAR") == 0)) (void) FormatLocaleString(ashlar_image->filename,MagickPathExtent, "miff:%s",write_info->filename); status=WriteImage(write_info,ashlar_image,exception); ashlar_image=DestroyImage(ashlar_image); write_info=DestroyImageInfo(write_info); return(MagickTrue); }