blob: d57853ec984e426e1bd6d410ed0e501a40864d91 [file] [log] [blame]
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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);
}