This source file includes following definitions.
- ClearBounds
- IsBoundsCleared
- CoalesceImages
- DisposeImages
- ComparePixels
- CompareImageBounds
- CompareImageLayers
- DeconstructImages
- OptimizeLayerFrames
- OptimizeImageLayers
- OptimizePlusImageLayers
- OptimizeImageTransparency
- RemoveDuplicateLayers
- RemoveZeroDelayLayers
- CompositeCanvas
- CompositeLayers
- MergeImageLayers
#include "magick/studio.h"
#include "magick/artifact.h"
#include "magick/cache.h"
#include "magick/channel.h"
#include "magick/color.h"
#include "magick/color-private.h"
#include "magick/composite.h"
#include "magick/effect.h"
#include "magick/exception.h"
#include "magick/exception-private.h"
#include "magick/geometry.h"
#include "magick/image.h"
#include "magick/layer.h"
#include "magick/list.h"
#include "magick/memory_.h"
#include "magick/monitor.h"
#include "magick/monitor-private.h"
#include "magick/option.h"
#include "magick/pixel-private.h"
#include "magick/property.h"
#include "magick/profile.h"
#include "magick/resource_.h"
#include "magick/resize.h"
#include "magick/statistic.h"
#include "magick/string_.h"
#include "magick/transform.h"
static void ClearBounds(Image *image,RectangleInfo *bounds)
{
ExceptionInfo
*exception;
ssize_t
y;
if (bounds->x < 0)
return;
if (image->matte == MagickFalse)
(void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
exception=(&image->exception);
for (y=0; y < (ssize_t) bounds->height; y++)
{
register ssize_t
x;
register PixelPacket
*magick_restrict q;
q=GetAuthenticPixels(image,bounds->x,bounds->y+y,bounds->width,1,exception);
if (q == (PixelPacket *) NULL)
break;
for (x=0; x < (ssize_t) bounds->width; x++)
{
q->opacity=(Quantum) TransparentOpacity;
q++;
}
if (SyncAuthenticPixels(image,exception) == MagickFalse)
break;
}
}
static MagickBooleanType IsBoundsCleared(const Image *image1,
const Image *image2,RectangleInfo *bounds,ExceptionInfo *exception)
{
register const PixelPacket
*p,
*q;
register ssize_t
x;
ssize_t
y;
if (bounds->x < 0)
return(MagickFalse);
for (y=0; y < (ssize_t) bounds->height; y++)
{
p=GetVirtualPixels(image1,bounds->x,bounds->y+y,bounds->width,1,
exception);
q=GetVirtualPixels(image2,bounds->x,bounds->y+y,bounds->width,1,
exception);
if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
break;
for (x=0; x < (ssize_t) bounds->width; x++)
{
if ((GetPixelOpacity(p) <= (Quantum) (QuantumRange/2)) &&
(GetPixelOpacity(q) > (Quantum) (QuantumRange/2)))
break;
p++;
q++;
}
if (x < (ssize_t) bounds->width)
break;
}
return(y < (ssize_t) bounds->height ? MagickTrue : MagickFalse);
}
MagickExport Image *CoalesceImages(const Image *image,ExceptionInfo *exception)
{
Image
*coalesce_image,
*dispose_image,
*previous;
register Image
*next;
RectangleInfo
bounds;
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
next=GetFirstImageInList(image);
bounds=next->page;
if (bounds.width == 0)
{
bounds.width=next->columns;
if (bounds.x > 0)
bounds.width+=bounds.x;
}
if (bounds.height == 0)
{
bounds.height=next->rows;
if (bounds.y > 0)
bounds.height+=bounds.y;
}
bounds.x=0;
bounds.y=0;
coalesce_image=CloneImage(next,bounds.width,bounds.height,MagickTrue,
exception);
if (coalesce_image == (Image *) NULL)
return((Image *) NULL);
coalesce_image->background_color.opacity=(Quantum) TransparentOpacity;
(void) SetImageBackgroundColor(coalesce_image);
coalesce_image->matte=next->matte;
coalesce_image->page=bounds;
coalesce_image->dispose=NoneDispose;
dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
(void) CompositeImage(coalesce_image,CopyCompositeOp,next,next->page.x,
next->page.y);
next=GetNextImageInList(next);
for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
{
previous=GetPreviousImageInList(next);
bounds=previous->page;
bounds.width=previous->columns;
bounds.height=previous->rows;
if (bounds.x < 0)
{
bounds.width+=bounds.x;
bounds.x=0;
}
if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) coalesce_image->columns)
bounds.width=coalesce_image->columns-bounds.x;
if (bounds.y < 0)
{
bounds.height+=bounds.y;
bounds.y=0;
}
if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) coalesce_image->rows)
bounds.height=coalesce_image->rows-bounds.y;
if (GetPreviousImageInList(next)->dispose != PreviousDispose)
{
dispose_image=DestroyImage(dispose_image);
dispose_image=CloneImage(coalesce_image,0,0,MagickTrue,exception);
if (dispose_image == (Image *) NULL)
{
coalesce_image=DestroyImageList(coalesce_image);
return((Image *) NULL);
}
}
if (next->previous->dispose == BackgroundDispose)
ClearBounds(dispose_image, &bounds);
coalesce_image->next=CloneImage(dispose_image,0,0,MagickTrue,exception);
coalesce_image->next->previous=coalesce_image;
previous=coalesce_image;
coalesce_image=GetNextImageInList(coalesce_image);
(void) CompositeImage(coalesce_image,next->matte != MagickFalse ?
OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y);
(void) CloneImageProfiles(coalesce_image,next);
(void) CloneImageProperties(coalesce_image,next);
(void) CloneImageArtifacts(coalesce_image,next);
coalesce_image->page=previous->page;
if (IsBoundsCleared(previous,coalesce_image,&bounds,exception) != MagickFalse)
coalesce_image->dispose=BackgroundDispose;
else
coalesce_image->dispose=NoneDispose;
previous->dispose=coalesce_image->dispose;
}
dispose_image=DestroyImage(dispose_image);
return(GetFirstImageInList(coalesce_image));
}
MagickExport Image *DisposeImages(const Image *images,ExceptionInfo *exception)
{
Image
*dispose_image,
*dispose_images;
RectangleInfo
bounds;
register Image
*image,
*next;
assert(images != (Image *) NULL);
assert(images->signature == MagickSignature);
if (images->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",images->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
image=GetFirstImageInList(images);
dispose_image=CloneImage(image,image->page.width,image->page.height,
MagickTrue,exception);
if (dispose_image == (Image *) NULL)
return((Image *) NULL);
dispose_image->page=image->page;
dispose_image->page.x=0;
dispose_image->page.y=0;
dispose_image->dispose=NoneDispose;
dispose_image->background_color.opacity=(Quantum) TransparentOpacity;
(void) SetImageBackgroundColor(dispose_image);
dispose_images=NewImageList();
for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
{
Image
*current_image;
current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
if (current_image == (Image *) NULL)
{
dispose_images=DestroyImageList(dispose_images);
dispose_image=DestroyImage(dispose_image);
return((Image *) NULL);
}
(void) CompositeImage(current_image,next->matte != MagickFalse ?
OverCompositeOp : CopyCompositeOp,next,next->page.x,next->page.y);
if (next->dispose == BackgroundDispose)
{
bounds=next->page;
bounds.width=next->columns;
bounds.height=next->rows;
if (bounds.x < 0)
{
bounds.width+=bounds.x;
bounds.x=0;
}
if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
bounds.width=current_image->columns-bounds.x;
if (bounds.y < 0)
{
bounds.height+=bounds.y;
bounds.y=0;
}
if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
bounds.height=current_image->rows-bounds.y;
ClearBounds(current_image,&bounds);
}
if (next->dispose == PreviousDispose)
current_image=DestroyImage(current_image);
else
{
dispose_image=DestroyImage(dispose_image);
dispose_image=current_image;
current_image=(Image *) NULL;
}
{
Image
*dispose;
dispose=CloneImage(dispose_image,0,0,MagickTrue,exception);
if (dispose == (Image *) NULL)
{
dispose_images=DestroyImageList(dispose_images);
dispose_image=DestroyImage(dispose_image);
return((Image *) NULL);
}
(void) CloneImageProfiles(dispose,next);
(void) CloneImageProperties(dispose,next);
(void) CloneImageArtifacts(dispose,next);
dispose->page.x=0;
dispose->page.y=0;
dispose->dispose=next->dispose;
AppendImageToList(&dispose_images,dispose);
}
}
dispose_image=DestroyImage(dispose_image);
return(GetFirstImageInList(dispose_images));
}
static MagickBooleanType ComparePixels(const ImageLayerMethod method,
const MagickPixelPacket *p,const MagickPixelPacket *q)
{
MagickRealType
o1,
o2;
if (method == CompareAnyLayer)
return((MagickBooleanType)(IsMagickColorSimilar(p,q) == MagickFalse));
o1 = (p->matte != MagickFalse) ? GetPixelOpacity(p) : OpaqueOpacity;
o2 = (q->matte != MagickFalse) ? q->opacity : OpaqueOpacity;
if (method == CompareClearLayer)
return((MagickBooleanType) ( (o1 <= ((MagickRealType) QuantumRange/2.0)) &&
(o2 > ((MagickRealType) QuantumRange/2.0)) ) );
if (method == CompareOverlayLayer)
{
if (o2 > ((MagickRealType) QuantumRange/2.0))
return MagickFalse;
return((MagickBooleanType) (IsMagickColorSimilar(p,q) == MagickFalse));
}
return(MagickFalse);
}
static RectangleInfo CompareImageBounds(const Image *image1,const Image *image2,
const ImageLayerMethod method,ExceptionInfo *exception)
{
RectangleInfo
bounds;
MagickPixelPacket
pixel1,
pixel2;
register const IndexPacket
*indexes1,
*indexes2;
register const PixelPacket
*p,
*q;
register ssize_t
x;
ssize_t
y;
#if 0
assert(image1->columns == image2->columns);
assert(image1->rows == image2->rows);
#endif
GetMagickPixelPacket(image1,&pixel1);
GetMagickPixelPacket(image2,&pixel2);
for (x=0; x < (ssize_t) image1->columns; x++)
{
p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
if ((p == (const PixelPacket *) NULL) ||
(q == (const PixelPacket *) NULL))
break;
indexes1=GetVirtualIndexQueue(image1);
indexes2=GetVirtualIndexQueue(image2);
for (y=0; y < (ssize_t) image1->rows; y++)
{
SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
if (ComparePixels(method,&pixel1,&pixel2))
break;
p++;
q++;
}
if (y < (ssize_t) image1->rows)
break;
}
if (x >= (ssize_t) image1->columns)
{
bounds.x=-1;
bounds.y=-1;
bounds.width=1;
bounds.height=1;
return(bounds);
}
bounds.x=x;
for (x=(ssize_t) image1->columns-1; x >= 0; x--)
{
p=GetVirtualPixels(image1,x,0,1,image1->rows,exception);
q=GetVirtualPixels(image2,x,0,1,image2->rows,exception);
if ((p == (const PixelPacket *) NULL) ||
(q == (const PixelPacket *) NULL))
break;
indexes1=GetVirtualIndexQueue(image1);
indexes2=GetVirtualIndexQueue(image2);
for (y=0; y < (ssize_t) image1->rows; y++)
{
SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
if (ComparePixels(method,&pixel1,&pixel2))
break;
p++;
q++;
}
if (y < (ssize_t) image1->rows)
break;
}
bounds.width=(size_t) (x-bounds.x+1);
for (y=0; y < (ssize_t) image1->rows; y++)
{
p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
if ((p == (const PixelPacket *) NULL) ||
(q == (const PixelPacket *) NULL))
break;
indexes1=GetVirtualIndexQueue(image1);
indexes2=GetVirtualIndexQueue(image2);
for (x=0; x < (ssize_t) image1->columns; x++)
{
SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
if (ComparePixels(method,&pixel1,&pixel2))
break;
p++;
q++;
}
if (x < (ssize_t) image1->columns)
break;
}
bounds.y=y;
for (y=(ssize_t) image1->rows-1; y >= 0; y--)
{
p=GetVirtualPixels(image1,0,y,image1->columns,1,exception);
q=GetVirtualPixels(image2,0,y,image2->columns,1,exception);
if ((p == (const PixelPacket *) NULL) ||
(q == (const PixelPacket *) NULL))
break;
indexes1=GetVirtualIndexQueue(image1);
indexes2=GetVirtualIndexQueue(image2);
for (x=0; x < (ssize_t) image1->columns; x++)
{
SetMagickPixelPacket(image1,p,indexes1+x,&pixel1);
SetMagickPixelPacket(image2,q,indexes2+x,&pixel2);
if (ComparePixels(method,&pixel1,&pixel2))
break;
p++;
q++;
}
if (x < (ssize_t) image1->columns)
break;
}
bounds.height=(size_t) (y-bounds.y+1);
return(bounds);
}
MagickExport Image *CompareImageLayers(const Image *image,
const ImageLayerMethod method, ExceptionInfo *exception)
{
Image
*image_a,
*image_b,
*layers;
RectangleInfo
*bounds;
register const Image
*next;
register ssize_t
i;
assert(image != (const Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
assert((method == CompareAnyLayer) ||
(method == CompareClearLayer) ||
(method == CompareOverlayLayer));
next=GetFirstImageInList(image);
bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
GetImageListLength(next),sizeof(*bounds));
if (bounds == (RectangleInfo *) NULL)
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
image_a=CloneImage(next,next->page.width,next->page.height,
MagickTrue,exception);
if (image_a == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
return((Image *) NULL);
}
image_a->background_color.opacity=(Quantum) TransparentOpacity;
(void) SetImageBackgroundColor(image_a);
image_a->page=next->page;
image_a->page.x=0;
image_a->page.y=0;
(void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,next->page.y);
i=0;
next=GetNextImageInList(next);
for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
{
image_b=CloneImage(image_a,0,0,MagickTrue,exception);
if (image_b == (Image *) NULL)
{
image_a=DestroyImage(image_a);
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
return((Image *) NULL);
}
(void) CompositeImage(image_a,CopyCompositeOp,next,next->page.x,
next->page.y);
bounds[i]=CompareImageBounds(image_b,image_a,method,exception);
image_b=DestroyImage(image_b);
i++;
}
image_a=DestroyImage(image_a);
next=GetFirstImageInList(image);
layers=CloneImage(next,0,0,MagickTrue,exception);
if (layers == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
return((Image *) NULL);
}
i=0;
next=GetNextImageInList(next);
for ( ; next != (const Image *) NULL; next=GetNextImageInList(next))
{
if ((bounds[i].x == -1) && (bounds[i].y == -1) &&
(bounds[i].width == 1) && (bounds[i].height == 1))
{
i++;
continue;
}
image_a=CloneImage(next,0,0,MagickTrue,exception);
if (image_a == (Image *) NULL)
break;
image_b=CropImage(image_a,&bounds[i],exception);
image_a=DestroyImage(image_a);
if (image_b == (Image *) NULL)
break;
AppendImageToList(&layers,image_b);
i++;
}
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
if (next != (Image *) NULL)
{
layers=DestroyImageList(layers);
return((Image *) NULL);
}
return(GetFirstImageInList(layers));
}
MagickExport Image *DeconstructImages(const Image *images,
ExceptionInfo *exception)
{
return(CompareImageLayers(images,CompareAnyLayer,exception));
}
#define DupDispose ((DisposeType)9)
#define DelDispose ((DisposeType)8)
#define DEBUG_OPT_FRAME 0
static Image *OptimizeLayerFrames(const Image *image,
const ImageLayerMethod method, ExceptionInfo *exception)
{
ExceptionInfo
*sans_exception;
Image
*prev_image,
*dup_image,
*bgnd_image,
*optimized_image;
RectangleInfo
try_bounds,
bgnd_bounds,
dup_bounds,
*bounds;
MagickBooleanType
add_frames,
try_cleared,
cleared;
DisposeType
*disposals;
register const Image
*curr;
register ssize_t
i;
assert(image != (const Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
assert(method == OptimizeLayer ||
method == OptimizeImageLayer ||
method == OptimizePlusLayer);
add_frames=method == OptimizePlusLayer ? MagickTrue : MagickFalse;
curr=GetFirstImageInList(image);
for (; curr != (Image *) NULL; curr=GetNextImageInList(curr))
{
if ((curr->columns != image->columns) || (curr->rows != image->rows))
ThrowImageException(OptionError,"ImagesAreNotTheSameSize");
}
curr=GetFirstImageInList(image);
bounds=(RectangleInfo *) AcquireQuantumMemory((size_t)
GetImageListLength(curr),(add_frames != MagickFalse ? 2UL : 1UL)*
sizeof(*bounds));
if (bounds == (RectangleInfo *) NULL)
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
disposals=(DisposeType *) AcquireQuantumMemory((size_t)
GetImageListLength(image),(add_frames != MagickFalse ? 2UL : 1UL)*
sizeof(*disposals));
if (disposals == (DisposeType *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
}
prev_image=CloneImage(curr,curr->page.width,curr->page.height,
MagickTrue,exception);
if (prev_image == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
disposals=(DisposeType *) RelinquishMagickMemory(disposals);
return((Image *) NULL);
}
prev_image->page=curr->page;
prev_image->page.x=0;
prev_image->page.y=0;
prev_image->dispose=NoneDispose;
prev_image->background_color.opacity=(Quantum) TransparentOpacity;
(void) SetImageBackgroundColor(prev_image);
#if DEBUG_OPT_FRAME
i=0;
(void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
#endif
disposals[0]=NoneDispose;
bounds[0]=CompareImageBounds(prev_image,curr,CompareAnyLayer,exception);
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g\n\n",
(double) bounds[i].width,(double) bounds[i].height,
(double) bounds[i].x,(double) bounds[i].y );
#endif
i=1;
bgnd_image=(Image *) NULL;
dup_image=(Image *) NULL;
dup_bounds.width=0;
dup_bounds.height=0;
dup_bounds.x=0;
dup_bounds.y=0;
curr=GetNextImageInList(curr);
for ( ; curr != (const Image *) NULL; curr=GetNextImageInList(curr))
{
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "frame %.20g :-\n", (double) i);
#endif
bounds[i]=CompareImageBounds(curr->previous,curr,CompareAnyLayer,exception);
cleared=IsBoundsCleared(curr->previous,curr,&bounds[i],exception);
disposals[i-1]=NoneDispose;
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "overlay: %.20gx%.20g%+.20g%+.20g%s%s\n",
(double) bounds[i].width,(double) bounds[i].height,
(double) bounds[i].x,(double) bounds[i].y,
bounds[i].x < 0?" (unchanged)":"",
cleared?" (pixels cleared)":"");
#endif
if ( bounds[i].x < 0 ) {
if ( add_frames && i>=2 ) {
disposals[i-1]=DelDispose;
disposals[i]=NoneDispose;
bounds[i]=bounds[i-1];
i++;
continue;
}
}
else
{
try_bounds=CompareImageBounds(prev_image,curr,CompareAnyLayer,exception);
try_cleared=IsBoundsCleared(prev_image,curr,&try_bounds,exception);
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "test_prev: %.20gx%.20g%+.20g%+.20g%s\n",
(double) try_bounds.width,(double) try_bounds.height,
(double) try_bounds.x,(double) try_bounds.y,
try_cleared?" (pixels were cleared)":"");
#endif
if ( (!try_cleared && cleared ) ||
try_bounds.width * try_bounds.height
< bounds[i].width * bounds[i].height )
{
cleared=try_cleared;
bounds[i]=try_bounds;
disposals[i-1]=PreviousDispose;
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "previous: accepted\n");
} else {
(void) FormatLocaleFile(stderr, "previous: rejected\n");
#endif
}
dup_bounds.width=dup_bounds.height=0;
if ( add_frames )
{
dup_image=CloneImage(curr->previous,curr->previous->page.width,
curr->previous->page.height,MagickTrue,exception);
if (dup_image == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
disposals=(DisposeType *) RelinquishMagickMemory(disposals);
prev_image=DestroyImage(prev_image);
return((Image *) NULL);
}
dup_bounds=CompareImageBounds(dup_image,curr,CompareClearLayer,exception);
ClearBounds(dup_image,&dup_bounds);
try_bounds=CompareImageBounds(dup_image,curr,CompareAnyLayer,exception);
if ( cleared ||
dup_bounds.width*dup_bounds.height
+try_bounds.width*try_bounds.height
< bounds[i].width * bounds[i].height )
{
cleared=MagickFalse;
bounds[i]=try_bounds;
disposals[i-1]=DupDispose;
}
else
dup_bounds.width=dup_bounds.height=0;
}
bgnd_image=CloneImage(curr->previous,curr->previous->page.width,
curr->previous->page.height,MagickTrue,exception);
if (bgnd_image == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
disposals=(DisposeType *) RelinquishMagickMemory(disposals);
prev_image=DestroyImage(prev_image);
if ( dup_image != (Image *) NULL)
dup_image=DestroyImage(dup_image);
return((Image *) NULL);
}
bgnd_bounds=bounds[i-1];
ClearBounds(bgnd_image,&bgnd_bounds);
try_bounds=CompareImageBounds(bgnd_image,curr,CompareAnyLayer,exception);
try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "background: %s\n",
try_cleared?"(pixels cleared)":"");
#endif
if ( try_cleared )
{
try_bounds=CompareImageBounds(curr->previous,curr,CompareClearLayer,exception);
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "expand_clear: %.20gx%.20g%+.20g%+.20g%s\n",
(double) try_bounds.width,(double) try_bounds.height,
(double) try_bounds.x,(double) try_bounds.y,
try_bounds.x<0?" (no expand nessary)":"");
#endif
if ( bgnd_bounds.x < 0 )
bgnd_bounds = try_bounds;
else
{
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "expand_bgnd: %.20gx%.20g%+.20g%+.20g\n",
(double) bgnd_bounds.width,(double) bgnd_bounds.height,
(double) bgnd_bounds.x,(double) bgnd_bounds.y );
#endif
if ( try_bounds.x < bgnd_bounds.x )
{
bgnd_bounds.width+= bgnd_bounds.x-try_bounds.x;
if ( bgnd_bounds.width < try_bounds.width )
bgnd_bounds.width = try_bounds.width;
bgnd_bounds.x = try_bounds.x;
}
else
{
try_bounds.width += try_bounds.x - bgnd_bounds.x;
if ( bgnd_bounds.width < try_bounds.width )
bgnd_bounds.width = try_bounds.width;
}
if ( try_bounds.y < bgnd_bounds.y )
{
bgnd_bounds.height += bgnd_bounds.y - try_bounds.y;
if ( bgnd_bounds.height < try_bounds.height )
bgnd_bounds.height = try_bounds.height;
bgnd_bounds.y = try_bounds.y;
}
else
{
try_bounds.height += try_bounds.y - bgnd_bounds.y;
if ( bgnd_bounds.height < try_bounds.height )
bgnd_bounds.height = try_bounds.height;
}
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, " to : %.20gx%.20g%+.20g%+.20g\n",
(double) bgnd_bounds.width,(double) bgnd_bounds.height,
(double) bgnd_bounds.x,(double) bgnd_bounds.y );
#endif
}
ClearBounds(bgnd_image,&bgnd_bounds);
#if DEBUG_OPT_FRAME
try_bounds=CompareImageBounds(bgnd_image,curr,CompareClearLayer,exception);
(void) FormatLocaleFile(stderr, "expand_ctst: %.20gx%.20g%+.20g%+.20g\n",
(double) try_bounds.width,(double) try_bounds.height,
(double) try_bounds.x,(double) try_bounds.y );
try_bounds=CompareImageBounds(bgnd_image,curr,CompareAnyLayer,exception);
try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
(void) FormatLocaleFile(stderr, "expand_any : %.20gx%.20g%+.20g%+.20g%s\n",
(double) try_bounds.width,(double) try_bounds.height,
(double) try_bounds.x,(double) try_bounds.y,
try_cleared?" (pixels cleared)":"");
#endif
try_bounds=CompareImageBounds(bgnd_image,curr,CompareOverlayLayer,exception);
#if DEBUG_OPT_FRAME
try_cleared=IsBoundsCleared(bgnd_image,curr,&try_bounds,exception);
(void) FormatLocaleFile(stderr, "expand_test: %.20gx%.20g%+.20g%+.20g%s\n",
(double) try_bounds.width,(double) try_bounds.height,
(double) try_bounds.x,(double) try_bounds.y,
try_cleared?" (pixels cleared)":"");
#endif
}
if ( cleared ||
bgnd_bounds.width*bgnd_bounds.height
+try_bounds.width*try_bounds.height
< bounds[i-1].width*bounds[i-1].height
+dup_bounds.width*dup_bounds.height
+bounds[i].width*bounds[i].height )
{
cleared=MagickFalse;
bounds[i-1]=bgnd_bounds;
bounds[i]=try_bounds;
if ( disposals[i-1] == DupDispose )
dup_image=DestroyImage(dup_image);
disposals[i-1]=BackgroundDispose;
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "expand_bgnd: accepted\n");
} else {
(void) FormatLocaleFile(stderr, "expand_bgnd: reject\n");
#endif
}
}
if ( disposals[i-1] == DupDispose )
{
if (bgnd_image != (Image *) NULL)
bgnd_image=DestroyImage(bgnd_image);
prev_image=DestroyImage(prev_image);
prev_image=dup_image, dup_image=(Image *) NULL;
bounds[i+1]=bounds[i];
bounds[i]=dup_bounds;
disposals[i-1]=DupDispose;
disposals[i]=BackgroundDispose;
i++;
}
else
{
if ( dup_image != (Image *) NULL)
dup_image=DestroyImage(dup_image);
if ( disposals[i-1] != PreviousDispose )
prev_image=DestroyImage(prev_image);
if ( disposals[i-1] == BackgroundDispose )
prev_image=bgnd_image, bgnd_image=(Image *) NULL;
if (bgnd_image != (Image *) NULL)
bgnd_image=DestroyImage(bgnd_image);
if ( disposals[i-1] == NoneDispose )
{
prev_image=CloneImage(curr->previous,curr->previous->page.width,
curr->previous->page.height,MagickTrue,exception);
if (prev_image == (Image *) NULL)
{
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
disposals=(DisposeType *) RelinquishMagickMemory(disposals);
return((Image *) NULL);
}
}
}
assert(prev_image != (Image *) NULL);
disposals[i]=disposals[i-1];
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "final %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
(double) i-1,
CommandOptionToMnemonic(MagickDisposeOptions, disposals[i-1]),
(double) bounds[i-1].width, (double) bounds[i-1].height,
(double) bounds[i-1].x, (double) bounds[i-1].y );
#endif
#if DEBUG_OPT_FRAME
(void) FormatLocaleFile(stderr, "interum %.20g : %s %.20gx%.20g%+.20g%+.20g\n",
(double) i,
CommandOptionToMnemonic(MagickDisposeOptions, disposals[i]),
(double) bounds[i].width, (double) bounds[i].height,
(double) bounds[i].x, (double) bounds[i].y );
(void) FormatLocaleFile(stderr, "\n");
#endif
i++;
}
prev_image=DestroyImage(prev_image);
sans_exception=AcquireExceptionInfo();
i=0;
curr=GetFirstImageInList(image);
optimized_image=NewImageList();
while ( curr != (const Image *) NULL )
{
prev_image=CloneImage(curr,0,0,MagickTrue,exception);
if (prev_image == (Image *) NULL)
break;
if ( disposals[i] == DelDispose ) {
size_t time = 0;
while ( disposals[i] == DelDispose ) {
time += curr->delay*1000/curr->ticks_per_second;
curr=GetNextImageInList(curr);
i++;
}
time += curr->delay*1000/curr->ticks_per_second;
prev_image->ticks_per_second = 100L;
prev_image->delay = time*prev_image->ticks_per_second/1000;
}
bgnd_image=CropImage(prev_image,&bounds[i],sans_exception);
prev_image=DestroyImage(prev_image);
if (bgnd_image == (Image *) NULL)
break;
bgnd_image->dispose=disposals[i];
if ( disposals[i] == DupDispose ) {
bgnd_image->delay=0;
bgnd_image->dispose=NoneDispose;
}
else
curr=GetNextImageInList(curr);
AppendImageToList(&optimized_image,bgnd_image);
i++;
}
sans_exception=DestroyExceptionInfo(sans_exception);
bounds=(RectangleInfo *) RelinquishMagickMemory(bounds);
disposals=(DisposeType *) RelinquishMagickMemory(disposals);
if (curr != (Image *) NULL)
{
optimized_image=DestroyImageList(optimized_image);
return((Image *) NULL);
}
return(GetFirstImageInList(optimized_image));
}
MagickExport Image *OptimizeImageLayers(const Image *image,
ExceptionInfo *exception)
{
return(OptimizeLayerFrames(image,OptimizeImageLayer,exception));
}
MagickExport Image *OptimizePlusImageLayers(const Image *image,
ExceptionInfo *exception)
{
return OptimizeLayerFrames(image, OptimizePlusLayer, exception);
}
MagickExport void OptimizeImageTransparency(const Image *image,
ExceptionInfo *exception)
{
Image
*dispose_image;
register Image
*next;
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
next=GetFirstImageInList(image);
dispose_image=CloneImage(next,next->page.width,next->page.height,
MagickTrue,exception);
if (dispose_image == (Image *) NULL)
return;
dispose_image->page=next->page;
dispose_image->page.x=0;
dispose_image->page.y=0;
dispose_image->dispose=NoneDispose;
dispose_image->background_color.opacity=(Quantum) TransparentOpacity;
(void) SetImageBackgroundColor(dispose_image);
while ( next != (Image *) NULL )
{
Image
*current_image;
current_image=CloneImage(dispose_image,0,0,MagickTrue,exception);
if (current_image == (Image *) NULL)
{
dispose_image=DestroyImage(dispose_image);
return;
}
(void) CompositeImage(current_image,next->matte != MagickFalse ?
OverCompositeOp : CopyCompositeOp, next,next->page.x,next->page.y);
if (next->dispose == BackgroundDispose)
{
RectangleInfo
bounds=next->page;
bounds.width=next->columns;
bounds.height=next->rows;
if (bounds.x < 0)
{
bounds.width+=bounds.x;
bounds.x=0;
}
if ((ssize_t) (bounds.x+bounds.width) > (ssize_t) current_image->columns)
bounds.width=current_image->columns-bounds.x;
if (bounds.y < 0)
{
bounds.height+=bounds.y;
bounds.y=0;
}
if ((ssize_t) (bounds.y+bounds.height) > (ssize_t) current_image->rows)
bounds.height=current_image->rows-bounds.y;
ClearBounds(current_image, &bounds);
}
if (next->dispose != PreviousDispose)
{
dispose_image=DestroyImage(dispose_image);
dispose_image=current_image;
}
else
current_image=DestroyImage(current_image);
next=GetNextImageInList(next);
if ( next != (Image *) NULL ) {
(void) CompositeImage(next, ChangeMaskCompositeOp,
dispose_image, -(next->page.x), -(next->page.y) );
}
}
dispose_image=DestroyImage(dispose_image);
return;
}
MagickExport void RemoveDuplicateLayers(Image **images,
ExceptionInfo *exception)
{
register Image
*curr,
*next;
RectangleInfo
bounds;
assert((*images) != (const Image *) NULL);
assert((*images)->signature == MagickSignature);
if ((*images)->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
curr=GetFirstImageInList(*images);
for (; (next=GetNextImageInList(curr)) != (Image *) NULL; curr=next)
{
if ( curr->columns != next->columns || curr->rows != next->rows
|| curr->page.x != next->page.x || curr->page.y != next->page.y )
continue;
bounds=CompareImageBounds(curr,next,CompareAnyLayer,exception);
if ( bounds.x < 0 ) {
size_t time;
time = curr->delay*1000/curr->ticks_per_second;
time += next->delay*1000/next->ticks_per_second;
next->ticks_per_second = 100L;
next->delay = time*curr->ticks_per_second/1000;
next->iterations = curr->iterations;
*images = curr;
(void) DeleteImageFromList(images);
}
}
*images = GetFirstImageInList(*images);
}
MagickExport void RemoveZeroDelayLayers(Image **images,
ExceptionInfo *exception)
{
Image
*i;
assert((*images) != (const Image *) NULL);
assert((*images)->signature == MagickSignature);
if ((*images)->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",(*images)->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
i=GetFirstImageInList(*images);
for ( ; i != (Image *) NULL; i=GetNextImageInList(i))
if ( i->delay != 0L ) break;
if ( i == (Image *) NULL ) {
(void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
"ZeroTimeAnimation","`%s'",GetFirstImageInList(*images)->filename);
return;
}
i=GetFirstImageInList(*images);
while ( i != (Image *) NULL )
{
if ( i->delay == 0L ) {
(void) DeleteImageFromList(&i);
*images=i;
}
else
i=GetNextImageInList(i);
}
*images=GetFirstImageInList(*images);
}
static inline void CompositeCanvas(Image *destination,
const CompositeOperator compose, Image *source,ssize_t x_offset,
ssize_t y_offset )
{
x_offset+=source->page.x-destination->page.x;
y_offset+=source->page.y-destination->page.y;
(void) CompositeImage(destination,compose,source,x_offset,y_offset);
}
MagickExport void CompositeLayers(Image *destination,
const CompositeOperator compose, Image *source,const ssize_t x_offset,
const ssize_t y_offset,ExceptionInfo *exception)
{
assert(destination != (Image *) NULL);
assert(destination->signature == MagickSignature);
assert(source != (Image *) NULL);
assert(source->signature == MagickSignature);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
if (source->debug != MagickFalse || destination->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s - %s",
source->filename, destination->filename);
if ( source->next == (Image *) NULL )
while ( destination != (Image *) NULL )
{
CompositeCanvas(destination, compose, source, x_offset, y_offset);
destination=GetNextImageInList(destination);
}
else if ( destination->next == (Image *) NULL )
{
Image *dest = CloneImage(destination,0,0,MagickTrue,exception);
CompositeCanvas(destination, compose, source, x_offset, y_offset);
if ( source->next != (Image *) NULL )
{
destination->delay = source->delay;
destination->iterations = source->iterations;
}
source=GetNextImageInList(source);
while ( source != (Image *) NULL )
{
AppendImageToList(&destination,
CloneImage(dest,0,0,MagickTrue,exception));
destination=GetLastImageInList(destination);
CompositeCanvas(destination, compose, source, x_offset, y_offset);
destination->delay = source->delay;
destination->iterations = source->iterations;
source=GetNextImageInList(source);
}
dest=DestroyImage(dest);
}
else
while ( source != (Image *) NULL && destination != (Image *) NULL )
{
CompositeCanvas(destination, compose, source, x_offset, y_offset);
source=GetNextImageInList(source);
destination=GetNextImageInList(destination);
}
}
MagickExport Image *MergeImageLayers(Image *image,
const ImageLayerMethod method,ExceptionInfo *exception)
{
#define MergeLayersTag "Merge/Layers"
Image
*canvas;
MagickBooleanType
proceed;
RectangleInfo
page;
register const Image
*next;
size_t
number_images,
height,
width;
ssize_t
scene;
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
page=image->page;
width=image->columns;
height=image->rows;
switch (method)
{
case TrimBoundsLayer:
case MergeLayer:
default:
{
next=GetNextImageInList(image);
for ( ; next != (Image *) NULL; next=GetNextImageInList(next))
{
if (page.x > next->page.x)
{
width+=page.x-next->page.x;
page.x=next->page.x;
}
if (page.y > next->page.y)
{
height+=page.y-next->page.y;
page.y=next->page.y;
}
if ((ssize_t) width < (next->page.x+(ssize_t) next->columns-page.x))
width=(size_t) next->page.x+(ssize_t) next->columns-page.x;
if ((ssize_t) height < (next->page.y+(ssize_t) next->rows-page.y))
height=(size_t) next->page.y+(ssize_t) next->rows-page.y;
}
break;
}
case FlattenLayer:
{
if (page.width > 0)
width=page.width;
if (page.height > 0)
height=page.height;
page.x=0;
page.y=0;
break;
}
case MosaicLayer:
{
if (page.width > 0)
width=page.width;
if (page.height > 0)
height=page.height;
for (next=image; next != (Image *) NULL; next=GetNextImageInList(next))
{
if ((ssize_t) width < (next->page.x+(ssize_t) next->columns))
width=(size_t) next->page.x+next->columns;
if ((ssize_t) height < (next->page.y+(ssize_t) next->rows))
height=(size_t) next->page.y+next->rows;
}
page.width=width;
page.height=height;
page.x=0;
page.y=0;
break;
}
}
if (page.width == 0)
page.width=page.x < 0 ? width : width+page.x;
if (page.height == 0)
page.height=page.y < 0 ? height : height+page.y;
if (method == TrimBoundsLayer)
{
number_images=GetImageListLength(image);
for (scene=0; scene < (ssize_t) number_images; scene++)
{
image->page.x-=page.x;
image->page.y-=page.y;
image->page.width=width;
image->page.height=height;
proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
number_images);
if (proceed == MagickFalse)
break;
image=GetNextImageInList(image);
if (image == (Image *) NULL)
break;
}
return((Image *) NULL);
}
canvas=CloneImage(image,width,height,MagickTrue,exception);
if (canvas == (Image *) NULL)
return((Image *) NULL);
(void) SetImageBackgroundColor(canvas);
canvas->page=page;
canvas->dispose=UndefinedDispose;
number_images=GetImageListLength(image);
for (scene=0; scene < (ssize_t) number_images; scene++)
{
(void) CompositeImage(canvas,image->compose,image,image->page.x-
canvas->page.x,image->page.y-canvas->page.y);
proceed=SetImageProgress(image,MergeLayersTag,(MagickOffsetType) scene,
number_images);
if (proceed == MagickFalse)
break;
image=GetNextImageInList(image);
if (image == (Image *) NULL)
break;
}
return(canvas);
}