You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

684 lines
20 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

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