| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % 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); |
| } |