|
|
/*
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
% %
|
|
|
% %
|
|
|
% %
|
|
|
% CCCC OOO M M PPPP OOO SSSSS IIIII TTTTT EEEEE %
|
|
|
% C O O MM MM P P O O SS I T E %
|
|
|
% C O O M M M PPPP O O SSS I T EEE %
|
|
|
% C O O M M P O O SS I T E %
|
|
|
% CCCC OOO M M P OOO SSSSS IIIII T EEEEE %
|
|
|
% %
|
|
|
% %
|
|
|
% MagickCore Image Composite Methods %
|
|
|
% %
|
|
|
% Software Design %
|
|
|
% Cristy %
|
|
|
% July 1992 %
|
|
|
% %
|
|
|
% %
|
|
|
% 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/artifact.h"
|
|
|
#include "MagickCore/cache.h"
|
|
|
#include "MagickCore/cache-private.h"
|
|
|
#include "MagickCore/cache-view.h"
|
|
|
#include "MagickCore/channel.h"
|
|
|
#include "MagickCore/client.h"
|
|
|
#include "MagickCore/color.h"
|
|
|
#include "MagickCore/color-private.h"
|
|
|
#include "MagickCore/colorspace.h"
|
|
|
#include "MagickCore/colorspace-private.h"
|
|
|
#include "MagickCore/composite.h"
|
|
|
#include "MagickCore/composite-private.h"
|
|
|
#include "MagickCore/constitute.h"
|
|
|
#include "MagickCore/draw.h"
|
|
|
#include "MagickCore/fx.h"
|
|
|
#include "MagickCore/gem.h"
|
|
|
#include "MagickCore/geometry.h"
|
|
|
#include "MagickCore/image.h"
|
|
|
#include "MagickCore/image-private.h"
|
|
|
#include "MagickCore/list.h"
|
|
|
#include "MagickCore/log.h"
|
|
|
#include "MagickCore/monitor.h"
|
|
|
#include "MagickCore/monitor-private.h"
|
|
|
#include "MagickCore/memory_.h"
|
|
|
#include "MagickCore/option.h"
|
|
|
#include "MagickCore/pixel-accessor.h"
|
|
|
#include "MagickCore/property.h"
|
|
|
#include "MagickCore/quantum.h"
|
|
|
#include "MagickCore/resample.h"
|
|
|
#include "MagickCore/resource_.h"
|
|
|
#include "MagickCore/string_.h"
|
|
|
#include "MagickCore/thread-private.h"
|
|
|
#include "MagickCore/threshold.h"
|
|
|
#include "MagickCore/token.h"
|
|
|
#include "MagickCore/utility.h"
|
|
|
#include "MagickCore/utility-private.h"
|
|
|
#include "MagickCore/version.h"
|
|
|
|
|
|
/*
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
% %
|
|
|
% %
|
|
|
% %
|
|
|
% C o m p o s i t e I m a g e %
|
|
|
% %
|
|
|
% %
|
|
|
% %
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
%
|
|
|
% CompositeImage() returns the second image composited onto the first
|
|
|
% at the specified offset, using the specified composite method.
|
|
|
%
|
|
|
% The format of the CompositeImage method is:
|
|
|
%
|
|
|
% MagickBooleanType CompositeImage(Image *image,
|
|
|
% const Image *source_image,const CompositeOperator compose,
|
|
|
% const MagickBooleanType clip_to_self,const ssize_t x_offset,
|
|
|
% const ssize_t y_offset,ExceptionInfo *exception)
|
|
|
%
|
|
|
% A description of each parameter follows:
|
|
|
%
|
|
|
% o image: the canvas image, modified by he composition
|
|
|
%
|
|
|
% o source_image: the source image.
|
|
|
%
|
|
|
% o compose: This operator affects how the composite is applied to
|
|
|
% the image. The operators and how they are utilized are listed here
|
|
|
% http://www.w3.org/TR/SVG12/#compositing.
|
|
|
%
|
|
|
% o clip_to_self: set to MagickTrue to limit composition to area composed.
|
|
|
%
|
|
|
% o x_offset: the column offset of the composited image.
|
|
|
%
|
|
|
% o y_offset: the row offset of the composited image.
|
|
|
%
|
|
|
% Extra Controls from Image meta-data in 'image' (artifacts)
|
|
|
%
|
|
|
% o "compose:args"
|
|
|
% A string containing extra numerical arguments for specific compose
|
|
|
% methods, generally expressed as a 'geometry' or a comma separated list
|
|
|
% of numbers.
|
|
|
%
|
|
|
% Compose methods needing such arguments include "BlendCompositeOp" and
|
|
|
% "DisplaceCompositeOp".
|
|
|
%
|
|
|
% o exception: return any errors or warnings in this structure.
|
|
|
%
|
|
|
*/
|
|
|
|
|
|
/*
|
|
|
Composition based on the SVG specification:
|
|
|
|
|
|
A Composition is defined by...
|
|
|
Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
|
|
|
Blending areas : X = 1 for area of overlap, ie: f(Sc,Dc)
|
|
|
Y = 1 for source preserved
|
|
|
Z = 1 for canvas preserved
|
|
|
|
|
|
Conversion to transparency (then optimized)
|
|
|
Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
|
|
|
Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
|
|
|
|
|
|
Where...
|
|
|
Sca = Sc*Sa normalized Source color divided by Source alpha
|
|
|
Dca = Dc*Da normalized Dest color divided by Dest alpha
|
|
|
Dc' = Dca'/Da' the desired color value for this channel.
|
|
|
|
|
|
Da' in in the follow formula as 'gamma' The resulting alpla value.
|
|
|
|
|
|
Most functions use a blending mode of over (X=1,Y=1,Z=1) this results in
|
|
|
the following optimizations...
|
|
|
gamma = Sa+Da-Sa*Da;
|
|
|
gamma = 1 - QuantumScale*alpha * QuantumScale*beta;
|
|
|
opacity = QuantumScale*alpha*beta; // over blend, optimized 1-Gamma
|
|
|
|
|
|
The above SVG definitions also define that Mathematical Composition
|
|
|
methods should use a 'Over' blending mode for Alpha Channel.
|
|
|
It however was not applied for composition modes of 'Plus', 'Minus',
|
|
|
the modulus versions of 'Add' and 'Subtract'.
|
|
|
|
|
|
Mathematical operator changes to be applied from IM v6.7...
|
|
|
|
|
|
1) Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
|
|
|
'ModulusAdd' and 'ModulusSubtract' for clarity.
|
|
|
|
|
|
2) All mathematical compositions work as per the SVG specification
|
|
|
with regard to blending. This now includes 'ModulusAdd' and
|
|
|
'ModulusSubtract'.
|
|
|
|
|
|
3) When the special channel flag 'sync' (syncronize channel updates)
|
|
|
is turned off (enabled by default) then mathematical compositions are
|
|
|
only performed on the channels specified, and are applied
|
|
|
independantally of each other. In other words the mathematics is
|
|
|
performed as 'pure' mathematical operations, rather than as image
|
|
|
operations.
|
|
|
*/
|
|
|
|
|
|
static void HCLComposite(const MagickRealType hue,const MagickRealType chroma,
|
|
|
const MagickRealType luma,MagickRealType *red,MagickRealType *green,
|
|
|
MagickRealType *blue)
|
|
|
{
|
|
|
MagickRealType
|
|
|
b,
|
|
|
c,
|
|
|
g,
|
|
|
h,
|
|
|
m,
|
|
|
r,
|
|
|
x;
|
|
|
|
|
|
/*
|
|
|
Convert HCL to RGB colorspace.
|
|
|
*/
|
|
|
assert(red != (MagickRealType *) NULL);
|
|
|
assert(green != (MagickRealType *) NULL);
|
|
|
assert(blue != (MagickRealType *) NULL);
|
|
|
h=6.0*hue;
|
|
|
c=chroma;
|
|
|
x=c*(1.0-fabs(fmod(h,2.0)-1.0));
|
|
|
r=0.0;
|
|
|
g=0.0;
|
|
|
b=0.0;
|
|
|
if ((0.0 <= h) && (h < 1.0))
|
|
|
{
|
|
|
r=c;
|
|
|
g=x;
|
|
|
}
|
|
|
else
|
|
|
if ((1.0 <= h) && (h < 2.0))
|
|
|
{
|
|
|
r=x;
|
|
|
g=c;
|
|
|
}
|
|
|
else
|
|
|
if ((2.0 <= h) && (h < 3.0))
|
|
|
{
|
|
|
g=c;
|
|
|
b=x;
|
|
|
}
|
|
|
else
|
|
|
if ((3.0 <= h) && (h < 4.0))
|
|
|
{
|
|
|
g=x;
|
|
|
b=c;
|
|
|
}
|
|
|
else
|
|
|
if ((4.0 <= h) && (h < 5.0))
|
|
|
{
|
|
|
r=x;
|
|
|
b=c;
|
|
|
}
|
|
|
else
|
|
|
if ((5.0 <= h) && (h < 6.0))
|
|
|
{
|
|
|
r=c;
|
|
|
b=x;
|
|
|
}
|
|
|
m=luma-(0.298839*r+0.586811*g+0.114350*b);
|
|
|
*red=QuantumRange*(r+m);
|
|
|
*green=QuantumRange*(g+m);
|
|
|
*blue=QuantumRange*(b+m);
|
|
|
}
|
|
|
|
|
|
static void CompositeHCL(const MagickRealType red,const MagickRealType green,
|
|
|
const MagickRealType blue,MagickRealType *hue,MagickRealType *chroma,
|
|
|
MagickRealType *luma)
|
|
|
{
|
|
|
MagickRealType
|
|
|
b,
|
|
|
c,
|
|
|
g,
|
|
|
h,
|
|
|
max,
|
|
|
r;
|
|
|
|
|
|
/*
|
|
|
Convert RGB to HCL colorspace.
|
|
|
*/
|
|
|
assert(hue != (MagickRealType *) NULL);
|
|
|
assert(chroma != (MagickRealType *) NULL);
|
|
|
assert(luma != (MagickRealType *) NULL);
|
|
|
r=red;
|
|
|
g=green;
|
|
|
b=blue;
|
|
|
max=MagickMax(r,MagickMax(g,b));
|
|
|
c=max-(MagickRealType) MagickMin(r,MagickMin(g,b));
|
|
|
h=0.0;
|
|
|
if (c == 0)
|
|
|
h=0.0;
|
|
|
else
|
|
|
if (red == max)
|
|
|
h=fmod((g-b)/c+6.0,6.0);
|
|
|
else
|
|
|
if (green == max)
|
|
|
h=((b-r)/c)+2.0;
|
|
|
else
|
|
|
if (blue == max)
|
|
|
h=((r-g)/c)+4.0;
|
|
|
*hue=(h/6.0);
|
|
|
*chroma=QuantumScale*c;
|
|
|
*luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b);
|
|
|
}
|
|
|
|
|
|
static MagickBooleanType CompositeOverImage(Image *image,
|
|
|
const Image *source_image,const MagickBooleanType clip_to_self,
|
|
|
const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
|
|
|
{
|
|
|
#define CompositeImageTag "Composite/Image"
|
|
|
|
|
|
CacheView
|
|
|
*image_view,
|
|
|
*source_view;
|
|
|
|
|
|
const char
|
|
|
*value;
|
|
|
|
|
|
MagickBooleanType
|
|
|
clamp,
|
|
|
status;
|
|
|
|
|
|
MagickOffsetType
|
|
|
progress;
|
|
|
|
|
|
ssize_t
|
|
|
y;
|
|
|
|
|
|
/*
|
|
|
Composite image.
|
|
|
*/
|
|
|
status=MagickTrue;
|
|
|
progress=0;
|
|
|
clamp=MagickTrue;
|
|
|
value=GetImageArtifact(image,"compose:clamp");
|
|
|
if (value != (const char *) NULL)
|
|
|
clamp=IsStringTrue(value);
|
|
|
status=MagickTrue;
|
|
|
progress=0;
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp parallel for schedule(static) shared(progress,status) \
|
|
|
magick_number_threads(source_image,image,image->rows,1)
|
|
|
#endif
|
|
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|
|
{
|
|
|
const Quantum
|
|
|
*pixels;
|
|
|
|
|
|
PixelInfo
|
|
|
canvas_pixel,
|
|
|
source_pixel;
|
|
|
|
|
|
const Quantum
|
|
|
*magick_restrict p;
|
|
|
|
|
|
Quantum
|
|
|
*magick_restrict q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
if (clip_to_self != MagickFalse)
|
|
|
{
|
|
|
if (y < y_offset)
|
|
|
continue;
|
|
|
if ((y-y_offset) >= (ssize_t) source_image->rows)
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
If pixels is NULL, y is outside overlay region.
|
|
|
*/
|
|
|
pixels=(Quantum *) NULL;
|
|
|
p=(Quantum *) NULL;
|
|
|
if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
|
|
|
{
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
|
|
|
source_image->columns,1,exception);
|
|
|
if (p == (const Quantum *) NULL)
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
pixels=p;
|
|
|
if (x_offset < 0)
|
|
|
p-=x_offset*(ssize_t) GetPixelChannels(source_image);
|
|
|
}
|
|
|
q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
|
|
|
if (q == (Quantum *) NULL)
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
GetPixelInfo(image,&canvas_pixel);
|
|
|
GetPixelInfo(source_image,&source_pixel);
|
|
|
for (x=0; x < (ssize_t) image->columns; x++)
|
|
|
{
|
|
|
double
|
|
|
gamma;
|
|
|
|
|
|
MagickRealType
|
|
|
alpha,
|
|
|
Da,
|
|
|
Dc,
|
|
|
Dca,
|
|
|
Sa,
|
|
|
Sc,
|
|
|
Sca;
|
|
|
|
|
|
ssize_t
|
|
|
i;
|
|
|
|
|
|
size_t
|
|
|
channels;
|
|
|
|
|
|
if (clip_to_self != MagickFalse)
|
|
|
{
|
|
|
if (x < x_offset)
|
|
|
{
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
if ((x-x_offset) >= (ssize_t) source_image->columns)
|
|
|
break;
|
|
|
}
|
|
|
if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
|
|
|
((x-x_offset) >= (ssize_t) source_image->columns))
|
|
|
{
|
|
|
Quantum
|
|
|
source[MaxPixelChannels];
|
|
|
|
|
|
/*
|
|
|
Virtual composite:
|
|
|
Sc: source color.
|
|
|
Dc: canvas color.
|
|
|
*/
|
|
|
(void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,source,
|
|
|
exception);
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
|
{
|
|
|
MagickRealType
|
|
|
pixel;
|
|
|
|
|
|
PixelChannel channel = GetPixelChannelChannel(image,i);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
PixelTrait source_traits=GetPixelChannelTraits(source_image,
|
|
|
channel);
|
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
|
(source_traits == UndefinedPixelTrait))
|
|
|
continue;
|
|
|
if (channel == AlphaPixelChannel)
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
else
|
|
|
pixel=(MagickRealType) q[i];
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
|
|
|
ClampToQuantum(pixel);
|
|
|
}
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Authentic composite:
|
|
|
Sa: normalized source alpha.
|
|
|
Da: normalized canvas alpha.
|
|
|
*/
|
|
|
Sa=QuantumScale*GetPixelAlpha(source_image,p);
|
|
|
Da=QuantumScale*GetPixelAlpha(image,q);
|
|
|
alpha=Sa+Da-Sa*Da;
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
|
{
|
|
|
MagickRealType
|
|
|
pixel;
|
|
|
|
|
|
PixelChannel channel = GetPixelChannelChannel(image,i);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
PixelTrait source_traits=GetPixelChannelTraits(source_image,channel);
|
|
|
if (traits == UndefinedPixelTrait)
|
|
|
continue;
|
|
|
if ((source_traits == UndefinedPixelTrait) &&
|
|
|
(channel != AlphaPixelChannel))
|
|
|
continue;
|
|
|
if (channel == AlphaPixelChannel)
|
|
|
{
|
|
|
/*
|
|
|
Set alpha channel.
|
|
|
*/
|
|
|
pixel=QuantumRange*alpha;
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
|
|
|
ClampToQuantum(pixel);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Sc: source color.
|
|
|
Dc: canvas color.
|
|
|
*/
|
|
|
Sc=(MagickRealType) GetPixelChannel(source_image,channel,p);
|
|
|
Dc=(MagickRealType) q[i];
|
|
|
if ((traits & CopyPixelTrait) != 0)
|
|
|
{
|
|
|
/*
|
|
|
Copy channel.
|
|
|
*/
|
|
|
q[i]=ClampToQuantum(Sc);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Porter-Duff compositions:
|
|
|
Sca: source normalized color multiplied by alpha.
|
|
|
Dca: normalized canvas color multiplied by alpha.
|
|
|
*/
|
|
|
Sca=QuantumScale*Sa*Sc;
|
|
|
Dca=QuantumScale*Da*Dc;
|
|
|
gamma=PerceptibleReciprocal(alpha);
|
|
|
pixel=QuantumRange*gamma*(Sca+Dca*(1.0-Sa));
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel);
|
|
|
}
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
channels=GetPixelChannels(source_image);
|
|
|
if (p >= (pixels+channels*source_image->columns))
|
|
|
p=pixels;
|
|
|
q+=GetPixelChannels(image);
|
|
|
}
|
|
|
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp atomic
|
|
|
#endif
|
|
|
progress++;
|
|
|
proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
return(status);
|
|
|
}
|
|
|
|
|
|
MagickExport MagickBooleanType CompositeImage(Image *image,
|
|
|
const Image *composite,const CompositeOperator compose,
|
|
|
const MagickBooleanType clip_to_self,const ssize_t x_offset,
|
|
|
const ssize_t y_offset,ExceptionInfo *exception)
|
|
|
{
|
|
|
#define CompositeImageTag "Composite/Image"
|
|
|
|
|
|
CacheView
|
|
|
*source_view,
|
|
|
*image_view;
|
|
|
|
|
|
const char
|
|
|
*value;
|
|
|
|
|
|
GeometryInfo
|
|
|
geometry_info;
|
|
|
|
|
|
Image
|
|
|
*canvas_image,
|
|
|
*source_image;
|
|
|
|
|
|
MagickBooleanType
|
|
|
clamp,
|
|
|
status;
|
|
|
|
|
|
MagickOffsetType
|
|
|
progress;
|
|
|
|
|
|
MagickRealType
|
|
|
amount,
|
|
|
canvas_dissolve,
|
|
|
midpoint,
|
|
|
percent_luma,
|
|
|
percent_chroma,
|
|
|
source_dissolve,
|
|
|
threshold;
|
|
|
|
|
|
MagickStatusType
|
|
|
flags;
|
|
|
|
|
|
ssize_t
|
|
|
y;
|
|
|
|
|
|
assert(image != (Image *) NULL);
|
|
|
assert(image->signature == MagickCoreSignature);
|
|
|
if (image->debug != MagickFalse)
|
|
|
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
|
|
|
assert(composite != (Image *) NULL);
|
|
|
assert(composite->signature == MagickCoreSignature);
|
|
|
if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
|
|
|
return(MagickFalse);
|
|
|
source_image=CloneImage(composite,0,0,MagickTrue,exception);
|
|
|
if (source_image == (const Image *) NULL)
|
|
|
return(MagickFalse);
|
|
|
(void) SetImageColorspace(source_image,image->colorspace,exception);
|
|
|
if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp))
|
|
|
{
|
|
|
status=CompositeOverImage(image,source_image,clip_to_self,x_offset,
|
|
|
y_offset,exception);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(status);
|
|
|
}
|
|
|
amount=0.5;
|
|
|
canvas_image=(Image *) NULL;
|
|
|
canvas_dissolve=1.0;
|
|
|
clamp=MagickTrue;
|
|
|
value=GetImageArtifact(image,"compose:clamp");
|
|
|
if (value != (const char *) NULL)
|
|
|
clamp=IsStringTrue(value);
|
|
|
SetGeometryInfo(&geometry_info);
|
|
|
percent_luma=100.0;
|
|
|
percent_chroma=100.0;
|
|
|
source_dissolve=1.0;
|
|
|
threshold=0.05f;
|
|
|
switch (compose)
|
|
|
{
|
|
|
case CopyCompositeOp:
|
|
|
{
|
|
|
if ((x_offset < 0) || (y_offset < 0))
|
|
|
break;
|
|
|
if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns)
|
|
|
break;
|
|
|
if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows)
|
|
|
break;
|
|
|
if ((source_image->alpha_trait == UndefinedPixelTrait) &&
|
|
|
(image->alpha_trait != UndefinedPixelTrait))
|
|
|
(void) SetImageAlphaChannel(source_image,OpaqueAlphaChannel,exception);
|
|
|
status=MagickTrue;
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
|
magick_number_threads(source_image,image,source_image->rows,1)
|
|
|
#endif
|
|
|
for (y=0; y < (ssize_t) source_image->rows; y++)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
sync;
|
|
|
|
|
|
const Quantum
|
|
|
*p;
|
|
|
|
|
|
Quantum
|
|
|
*q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
|
|
|
exception);
|
|
|
q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
|
|
|
source_image->columns,1,exception);
|
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
for (x=0; x < (ssize_t) source_image->columns; x++)
|
|
|
{
|
|
|
ssize_t
|
|
|
i;
|
|
|
|
|
|
if (GetPixelReadMask(source_image,p) <= (QuantumRange/2))
|
|
|
{
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(source_image); i++)
|
|
|
{
|
|
|
PixelChannel channel = GetPixelChannelChannel(source_image,i);
|
|
|
PixelTrait source_traits = GetPixelChannelTraits(source_image,
|
|
|
channel);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
if ((source_traits == UndefinedPixelTrait) ||
|
|
|
(traits == UndefinedPixelTrait))
|
|
|
continue;
|
|
|
SetPixelChannel(image,channel,p[i],q);
|
|
|
}
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(image);
|
|
|
}
|
|
|
sync=SyncCacheViewAuthenticPixels(image_view,exception);
|
|
|
if (sync == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType)
|
|
|
y,image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(status);
|
|
|
}
|
|
|
case IntensityCompositeOp:
|
|
|
{
|
|
|
if ((x_offset < 0) || (y_offset < 0))
|
|
|
break;
|
|
|
if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns)
|
|
|
break;
|
|
|
if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows)
|
|
|
break;
|
|
|
status=MagickTrue;
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
|
magick_number_threads(source_image,image,source_image->rows,1)
|
|
|
#endif
|
|
|
for (y=0; y < (ssize_t) source_image->rows; y++)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
sync;
|
|
|
|
|
|
const Quantum
|
|
|
*p;
|
|
|
|
|
|
Quantum
|
|
|
*q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
|
|
|
exception);
|
|
|
q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
|
|
|
source_image->columns,1,exception);
|
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
for (x=0; x < (ssize_t) source_image->columns; x++)
|
|
|
{
|
|
|
if (GetPixelReadMask(source_image,p) <= (QuantumRange/2))
|
|
|
{
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
SetPixelAlpha(image,clamp != MagickFalse ?
|
|
|
ClampPixel(GetPixelIntensity(source_image,p)) :
|
|
|
ClampToQuantum(GetPixelIntensity(source_image,p)),q);
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(image);
|
|
|
}
|
|
|
sync=SyncCacheViewAuthenticPixels(image_view,exception);
|
|
|
if (sync == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType)
|
|
|
y,image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(status);
|
|
|
}
|
|
|
case CopyAlphaCompositeOp:
|
|
|
case ChangeMaskCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Modify canvas outside the overlaid region and require an alpha
|
|
|
channel to exist, to add transparency.
|
|
|
*/
|
|
|
if (image->alpha_trait == UndefinedPixelTrait)
|
|
|
(void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
|
|
|
break;
|
|
|
}
|
|
|
case BlurCompositeOp:
|
|
|
{
|
|
|
CacheView
|
|
|
*canvas_view;
|
|
|
|
|
|
double
|
|
|
angle_range,
|
|
|
angle_start,
|
|
|
height,
|
|
|
width;
|
|
|
|
|
|
PixelInfo
|
|
|
pixel;
|
|
|
|
|
|
ResampleFilter
|
|
|
*resample_filter;
|
|
|
|
|
|
SegmentInfo
|
|
|
blur;
|
|
|
|
|
|
/*
|
|
|
Blur Image by resampling dictated by an overlay gradient map:
|
|
|
X = red_channel; Y = green_channel; compose:args =
|
|
|
x_scale[,y_scale[,angle]].
|
|
|
*/
|
|
|
canvas_image=CloneImage(image,0,0,MagickTrue,exception);
|
|
|
if (canvas_image == (Image *) NULL)
|
|
|
{
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(MagickFalse);
|
|
|
}
|
|
|
/*
|
|
|
Gather the maximum blur sigma values from user.
|
|
|
*/
|
|
|
flags=NoValue;
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (const char *) NULL)
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
if ((flags & WidthValue) == 0)
|
|
|
{
|
|
|
(void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
|
|
|
"InvalidSetting","'%s' '%s'","compose:args",value);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
canvas_image=DestroyImage(canvas_image);
|
|
|
return(MagickFalse);
|
|
|
}
|
|
|
/*
|
|
|
Users input sigma now needs to be converted to the EWA ellipse size.
|
|
|
The filter defaults to a sigma of 0.5 so to make this match the users
|
|
|
input the ellipse size needs to be doubled.
|
|
|
*/
|
|
|
width=2.0*geometry_info.rho;
|
|
|
height=width;
|
|
|
if ((flags & HeightValue) != 0)
|
|
|
height=2.0*geometry_info.sigma;
|
|
|
/*
|
|
|
Default the unrotated ellipse width and height axis vectors.
|
|
|
*/
|
|
|
blur.x1=width;
|
|
|
blur.x2=0.0;
|
|
|
blur.y1=0.0;
|
|
|
blur.y2=height;
|
|
|
if ((flags & XValue) != 0 )
|
|
|
{
|
|
|
MagickRealType
|
|
|
angle;
|
|
|
|
|
|
/*
|
|
|
Rotate vectors if a rotation angle is given.
|
|
|
*/
|
|
|
angle=DegreesToRadians(geometry_info.xi);
|
|
|
blur.x1=width*cos(angle);
|
|
|
blur.x2=width*sin(angle);
|
|
|
blur.y1=(-height*sin(angle));
|
|
|
blur.y2=height*cos(angle);
|
|
|
}
|
|
|
angle_start=0.0;
|
|
|
angle_range=0.0;
|
|
|
if ((flags & YValue) != 0 )
|
|
|
{
|
|
|
/*
|
|
|
Lets set a angle range and calculate in the loop.
|
|
|
*/
|
|
|
angle_start=DegreesToRadians(geometry_info.xi);
|
|
|
angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
|
|
|
}
|
|
|
/*
|
|
|
Set up a gaussian cylindrical filter for EWA Bluring.
|
|
|
|
|
|
As the minimum ellipse radius of support*1.0 the EWA algorithm
|
|
|
can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
|
|
|
This means that even 'No Blur' will be still a little blurry! The
|
|
|
solution (as well as the problem of preventing any user expert filter
|
|
|
settings, is to set our own user settings, restore them afterwards.
|
|
|
*/
|
|
|
resample_filter=AcquireResampleFilter(image,exception);
|
|
|
SetResampleFilter(resample_filter,GaussianFilter);
|
|
|
/*
|
|
|
Perform the variable blurring of each pixel in image.
|
|
|
*/
|
|
|
GetPixelInfo(image,&pixel);
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
|
|
|
for (y=0; y < (ssize_t) source_image->rows; y++)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
sync;
|
|
|
|
|
|
const Quantum
|
|
|
*magick_restrict p;
|
|
|
|
|
|
Quantum
|
|
|
*magick_restrict q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
|
|
|
continue;
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
|
|
|
exception);
|
|
|
q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1,
|
|
|
exception);
|
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
|
break;
|
|
|
for (x=0; x < (ssize_t) source_image->columns; x++)
|
|
|
{
|
|
|
if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
|
|
|
{
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
continue;
|
|
|
}
|
|
|
if (fabs(angle_range) > MagickEpsilon)
|
|
|
{
|
|
|
MagickRealType
|
|
|
angle;
|
|
|
|
|
|
angle=angle_start+angle_range*QuantumScale*
|
|
|
GetPixelBlue(source_image,p);
|
|
|
blur.x1=width*cos(angle);
|
|
|
blur.x2=width*sin(angle);
|
|
|
blur.y1=(-height*sin(angle));
|
|
|
blur.y2=height*cos(angle);
|
|
|
}
|
|
|
ScaleResampleFilter(resample_filter,
|
|
|
blur.x1*QuantumScale*GetPixelRed(source_image,p),
|
|
|
blur.y1*QuantumScale*GetPixelGreen(source_image,p),
|
|
|
blur.x2*QuantumScale*GetPixelRed(source_image,p),
|
|
|
blur.y2*QuantumScale*GetPixelGreen(source_image,p) );
|
|
|
(void) ResamplePixelColor(resample_filter,(double) x_offset+x,
|
|
|
(double) y_offset+y,&pixel,exception);
|
|
|
SetPixelViaPixelInfo(canvas_image,&pixel,q);
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(canvas_image);
|
|
|
}
|
|
|
sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
|
|
|
if (sync == MagickFalse)
|
|
|
break;
|
|
|
}
|
|
|
resample_filter=DestroyResampleFilter(resample_filter);
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
canvas_view=DestroyCacheView(canvas_view);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
source_image=canvas_image;
|
|
|
break;
|
|
|
}
|
|
|
case DisplaceCompositeOp:
|
|
|
case DistortCompositeOp:
|
|
|
{
|
|
|
CacheView
|
|
|
*canvas_view;
|
|
|
|
|
|
MagickRealType
|
|
|
horizontal_scale,
|
|
|
vertical_scale;
|
|
|
|
|
|
PixelInfo
|
|
|
pixel;
|
|
|
|
|
|
PointInfo
|
|
|
center,
|
|
|
offset;
|
|
|
|
|
|
/*
|
|
|
Displace/Distort based on overlay gradient map:
|
|
|
X = red_channel; Y = green_channel;
|
|
|
compose:args = x_scale[,y_scale[,center.x,center.y]]
|
|
|
*/
|
|
|
canvas_image=CloneImage(image,0,0,MagickTrue,exception);
|
|
|
if (canvas_image == (Image *) NULL)
|
|
|
{
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(MagickFalse);
|
|
|
}
|
|
|
SetGeometryInfo(&geometry_info);
|
|
|
flags=NoValue;
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
if ((flags & (WidthValue | HeightValue)) == 0 )
|
|
|
{
|
|
|
if ((flags & AspectValue) == 0)
|
|
|
{
|
|
|
horizontal_scale=(MagickRealType) (source_image->columns-1)/2.0;
|
|
|
vertical_scale=(MagickRealType) (source_image->rows-1)/2.0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
horizontal_scale=(MagickRealType) (image->columns-1)/2.0;
|
|
|
vertical_scale=(MagickRealType) (image->rows-1)/2.0;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
horizontal_scale=geometry_info.rho;
|
|
|
vertical_scale=geometry_info.sigma;
|
|
|
if ((flags & PercentValue) != 0)
|
|
|
{
|
|
|
if ((flags & AspectValue) == 0)
|
|
|
{
|
|
|
horizontal_scale*=(source_image->columns-1)/200.0;
|
|
|
vertical_scale*=(source_image->rows-1)/200.0;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
horizontal_scale*=(image->columns-1)/200.0;
|
|
|
vertical_scale*=(image->rows-1)/200.0;
|
|
|
}
|
|
|
}
|
|
|
if ((flags & HeightValue) == 0)
|
|
|
vertical_scale=horizontal_scale;
|
|
|
}
|
|
|
/*
|
|
|
Determine fixed center point for absolute distortion map
|
|
|
Absolute distort ==
|
|
|
Displace offset relative to a fixed absolute point
|
|
|
Select that point according to +X+Y user inputs.
|
|
|
default = center of overlay image
|
|
|
arg flag '!' = locations/percentage relative to background image
|
|
|
*/
|
|
|
center.x=(MagickRealType) x_offset;
|
|
|
center.y=(MagickRealType) y_offset;
|
|
|
if (compose == DistortCompositeOp)
|
|
|
{
|
|
|
if ((flags & XValue) == 0)
|
|
|
if ((flags & AspectValue) != 0)
|
|
|
center.x=(MagickRealType) ((image->columns-1)/2.0);
|
|
|
else
|
|
|
center.x=(MagickRealType) (x_offset+(source_image->columns-1)/
|
|
|
2.0);
|
|
|
else
|
|
|
if ((flags & AspectValue) != 0)
|
|
|
center.x=geometry_info.xi;
|
|
|
else
|
|
|
center.x=(MagickRealType) (x_offset+geometry_info.xi);
|
|
|
if ((flags & YValue) == 0)
|
|
|
if ((flags & AspectValue) != 0)
|
|
|
center.y=(MagickRealType) ((image->rows-1)/2.0);
|
|
|
else
|
|
|
center.y=(MagickRealType) (y_offset+(source_image->rows-1)/2.0);
|
|
|
else
|
|
|
if ((flags & AspectValue) != 0)
|
|
|
center.y=geometry_info.psi;
|
|
|
else
|
|
|
center.y=(MagickRealType) (y_offset+geometry_info.psi);
|
|
|
}
|
|
|
/*
|
|
|
Shift the pixel offset point as defined by the provided,
|
|
|
displacement/distortion map. -- Like a lens...
|
|
|
*/
|
|
|
GetPixelInfo(image,&pixel);
|
|
|
image_view=AcquireVirtualCacheView(image,exception);
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
|
|
|
for (y=0; y < (ssize_t) source_image->rows; y++)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
sync;
|
|
|
|
|
|
const Quantum
|
|
|
*magick_restrict p;
|
|
|
|
|
|
Quantum
|
|
|
*magick_restrict q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
|
|
|
continue;
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
|
|
|
exception);
|
|
|
q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1,
|
|
|
exception);
|
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
|
break;
|
|
|
for (x=0; x < (ssize_t) source_image->columns; x++)
|
|
|
{
|
|
|
if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
|
|
|
{
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Displace the offset.
|
|
|
*/
|
|
|
offset.x=(double) (horizontal_scale*(GetPixelRed(source_image,p)-
|
|
|
(((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
|
|
|
QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
|
|
|
x : 0);
|
|
|
offset.y=(double) (vertical_scale*(GetPixelGreen(source_image,p)-
|
|
|
(((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
|
|
|
QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
|
|
|
y : 0);
|
|
|
status=InterpolatePixelInfo(image,image_view,
|
|
|
UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
|
|
|
&pixel,exception);
|
|
|
if (status == MagickFalse)
|
|
|
break;
|
|
|
/*
|
|
|
Mask with the 'invalid pixel mask' in alpha channel.
|
|
|
*/
|
|
|
pixel.alpha=(MagickRealType) QuantumRange*(QuantumScale*pixel.alpha)*
|
|
|
(QuantumScale*GetPixelAlpha(source_image,p));
|
|
|
SetPixelViaPixelInfo(canvas_image,&pixel,q);
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
q+=GetPixelChannels(canvas_image);
|
|
|
}
|
|
|
if (x < (ssize_t) source_image->columns)
|
|
|
break;
|
|
|
sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
|
|
|
if (sync == MagickFalse)
|
|
|
break;
|
|
|
}
|
|
|
canvas_view=DestroyCacheView(canvas_view);
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
source_image=DestroyImage(source_image);
|
|
|
source_image=canvas_image;
|
|
|
break;
|
|
|
}
|
|
|
case DissolveCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Geometry arguments to dissolve factors.
|
|
|
*/
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
{
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
source_dissolve=geometry_info.rho/100.0;
|
|
|
canvas_dissolve=1.0;
|
|
|
if ((source_dissolve-MagickEpsilon) < 0.0)
|
|
|
source_dissolve=0.0;
|
|
|
if ((source_dissolve+MagickEpsilon) > 1.0)
|
|
|
{
|
|
|
canvas_dissolve=2.0-source_dissolve;
|
|
|
source_dissolve=1.0;
|
|
|
}
|
|
|
if ((flags & SigmaValue) != 0)
|
|
|
canvas_dissolve=geometry_info.sigma/100.0;
|
|
|
if ((canvas_dissolve-MagickEpsilon) < 0.0)
|
|
|
canvas_dissolve=0.0;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case BlendCompositeOp:
|
|
|
{
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
{
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
source_dissolve=geometry_info.rho/100.0;
|
|
|
canvas_dissolve=1.0-source_dissolve;
|
|
|
if ((flags & SigmaValue) != 0)
|
|
|
canvas_dissolve=geometry_info.sigma/100.0;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case MathematicsCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Just collect the values from "compose:args", setting.
|
|
|
Unused values are set to zero automagically.
|
|
|
|
|
|
Arguments are normally a comma separated list, so this probably should
|
|
|
be changed to some 'general comma list' parser, (with a minimum
|
|
|
number of values)
|
|
|
*/
|
|
|
SetGeometryInfo(&geometry_info);
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
(void) ParseGeometry(value,&geometry_info);
|
|
|
break;
|
|
|
}
|
|
|
case ModulateCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Determine the luma and chroma scale.
|
|
|
*/
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
{
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
percent_luma=geometry_info.rho;
|
|
|
if ((flags & SigmaValue) != 0)
|
|
|
percent_chroma=geometry_info.sigma;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case ThresholdCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Determine the amount and threshold.
|
|
|
*/
|
|
|
value=GetImageArtifact(image,"compose:args");
|
|
|
if (value != (char *) NULL)
|
|
|
{
|
|
|
flags=ParseGeometry(value,&geometry_info);
|
|
|
amount=geometry_info.rho;
|
|
|
threshold=geometry_info.sigma;
|
|
|
if ((flags & SigmaValue) == 0)
|
|
|
threshold=0.05f;
|
|
|
}
|
|
|
threshold*=QuantumRange;
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
/*
|
|
|
Composite image.
|
|
|
*/
|
|
|
status=MagickTrue;
|
|
|
progress=0;
|
|
|
midpoint=((MagickRealType) QuantumRange+1.0)/2;
|
|
|
source_view=AcquireVirtualCacheView(source_image,exception);
|
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp parallel for schedule(static) shared(progress,status) \
|
|
|
magick_number_threads(source_image,image,image->rows,1)
|
|
|
#endif
|
|
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|
|
{
|
|
|
const Quantum
|
|
|
*pixels;
|
|
|
|
|
|
MagickRealType
|
|
|
blue,
|
|
|
chroma,
|
|
|
green,
|
|
|
hue,
|
|
|
luma,
|
|
|
red;
|
|
|
|
|
|
PixelInfo
|
|
|
canvas_pixel,
|
|
|
source_pixel;
|
|
|
|
|
|
const Quantum
|
|
|
*magick_restrict p;
|
|
|
|
|
|
Quantum
|
|
|
*magick_restrict q;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
if (clip_to_self != MagickFalse)
|
|
|
{
|
|
|
if (y < y_offset)
|
|
|
continue;
|
|
|
if ((y-y_offset) >= (ssize_t) source_image->rows)
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
If pixels is NULL, y is outside overlay region.
|
|
|
*/
|
|
|
pixels=(Quantum *) NULL;
|
|
|
p=(Quantum *) NULL;
|
|
|
if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
|
|
|
{
|
|
|
p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
|
|
|
source_image->columns,1,exception);
|
|
|
if (p == (const Quantum *) NULL)
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
pixels=p;
|
|
|
if (x_offset < 0)
|
|
|
p-=x_offset*(ssize_t) GetPixelChannels(source_image);
|
|
|
}
|
|
|
q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
|
|
|
if (q == (Quantum *) NULL)
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
hue=0.0;
|
|
|
chroma=0.0;
|
|
|
luma=0.0;
|
|
|
GetPixelInfo(image,&canvas_pixel);
|
|
|
GetPixelInfo(source_image,&source_pixel);
|
|
|
for (x=0; x < (ssize_t) image->columns; x++)
|
|
|
{
|
|
|
double
|
|
|
gamma;
|
|
|
|
|
|
MagickRealType
|
|
|
alpha,
|
|
|
Da,
|
|
|
Dc,
|
|
|
Dca,
|
|
|
DcaDa,
|
|
|
Sa,
|
|
|
SaSca,
|
|
|
Sc,
|
|
|
Sca;
|
|
|
|
|
|
ssize_t
|
|
|
i;
|
|
|
|
|
|
size_t
|
|
|
channels;
|
|
|
|
|
|
if (clip_to_self != MagickFalse)
|
|
|
{
|
|
|
if (x < x_offset)
|
|
|
{
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
if ((x-x_offset) >= (ssize_t) source_image->columns)
|
|
|
break;
|
|
|
}
|
|
|
if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
|
|
|
((x-x_offset) >= (ssize_t) source_image->columns))
|
|
|
{
|
|
|
Quantum
|
|
|
source[MaxPixelChannels];
|
|
|
|
|
|
/*
|
|
|
Virtual composite:
|
|
|
Sc: source color.
|
|
|
Dc: canvas color.
|
|
|
*/
|
|
|
(void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,source,
|
|
|
exception);
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
|
{
|
|
|
MagickRealType
|
|
|
pixel;
|
|
|
|
|
|
PixelChannel channel = GetPixelChannelChannel(image,i);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
PixelTrait source_traits=GetPixelChannelTraits(source_image,
|
|
|
channel);
|
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
|
(source_traits == UndefinedPixelTrait))
|
|
|
continue;
|
|
|
switch (compose)
|
|
|
{
|
|
|
case AlphaCompositeOp:
|
|
|
case ChangeMaskCompositeOp:
|
|
|
case CopyAlphaCompositeOp:
|
|
|
case DstAtopCompositeOp:
|
|
|
case DstInCompositeOp:
|
|
|
case InCompositeOp:
|
|
|
case OutCompositeOp:
|
|
|
case SrcInCompositeOp:
|
|
|
case SrcOutCompositeOp:
|
|
|
{
|
|
|
if (channel == AlphaPixelChannel)
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
else
|
|
|
pixel=(MagickRealType) q[i];
|
|
|
break;
|
|
|
}
|
|
|
case ClearCompositeOp:
|
|
|
case CopyCompositeOp:
|
|
|
case ReplaceCompositeOp:
|
|
|
case SrcCompositeOp:
|
|
|
{
|
|
|
if (channel == AlphaPixelChannel)
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
else
|
|
|
pixel=0.0;
|
|
|
break;
|
|
|
}
|
|
|
case BlendCompositeOp:
|
|
|
case DissolveCompositeOp:
|
|
|
{
|
|
|
if (channel == AlphaPixelChannel)
|
|
|
pixel=canvas_dissolve*GetPixelAlpha(source_image,source);
|
|
|
else
|
|
|
pixel=(MagickRealType) source[channel];
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
{
|
|
|
pixel=(MagickRealType) source[channel];
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
|
|
|
ClampToQuantum(pixel);
|
|
|
}
|
|
|
q+=GetPixelChannels(image);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Authentic composite:
|
|
|
Sa: normalized source alpha.
|
|
|
Da: normalized canvas alpha.
|
|
|
*/
|
|
|
Sa=QuantumScale*GetPixelAlpha(source_image,p);
|
|
|
Da=QuantumScale*GetPixelAlpha(image,q);
|
|
|
switch (compose)
|
|
|
{
|
|
|
case BumpmapCompositeOp:
|
|
|
{
|
|
|
alpha=GetPixelIntensity(source_image,p)*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case ColorBurnCompositeOp:
|
|
|
case ColorDodgeCompositeOp:
|
|
|
case DarkenCompositeOp:
|
|
|
case DifferenceCompositeOp:
|
|
|
case DivideDstCompositeOp:
|
|
|
case DivideSrcCompositeOp:
|
|
|
case ExclusionCompositeOp:
|
|
|
case FreezeCompositeOp:
|
|
|
case HardLightCompositeOp:
|
|
|
case HardMixCompositeOp:
|
|
|
case InterpolateCompositeOp:
|
|
|
case LightenCompositeOp:
|
|
|
case LinearBurnCompositeOp:
|
|
|
case LinearDodgeCompositeOp:
|
|
|
case LinearLightCompositeOp:
|
|
|
case MathematicsCompositeOp:
|
|
|
case MinusDstCompositeOp:
|
|
|
case MinusSrcCompositeOp:
|
|
|
case MultiplyCompositeOp:
|
|
|
case NegateCompositeOp:
|
|
|
case OverlayCompositeOp:
|
|
|
case PegtopLightCompositeOp:
|
|
|
case PinLightCompositeOp:
|
|
|
case ReflectCompositeOp:
|
|
|
case ScreenCompositeOp:
|
|
|
case SoftBurnCompositeOp:
|
|
|
case SoftDodgeCompositeOp:
|
|
|
case SoftLightCompositeOp:
|
|
|
case StampCompositeOp:
|
|
|
case VividLightCompositeOp:
|
|
|
{
|
|
|
alpha=RoundToUnity(Sa+Da-Sa*Da);
|
|
|
break;
|
|
|
}
|
|
|
case DstAtopCompositeOp:
|
|
|
case DstInCompositeOp:
|
|
|
case InCompositeOp:
|
|
|
case SrcInCompositeOp:
|
|
|
{
|
|
|
alpha=Sa*Da;
|
|
|
break;
|
|
|
}
|
|
|
case DissolveCompositeOp:
|
|
|
{
|
|
|
alpha=source_dissolve*Sa*(-canvas_dissolve*Da)+source_dissolve*Sa+
|
|
|
canvas_dissolve*Da;
|
|
|
break;
|
|
|
}
|
|
|
case DstOverCompositeOp:
|
|
|
case OverCompositeOp:
|
|
|
case SrcOverCompositeOp:
|
|
|
{
|
|
|
alpha=Sa+Da-Sa*Da;
|
|
|
break;
|
|
|
}
|
|
|
case DstOutCompositeOp:
|
|
|
{
|
|
|
alpha=Da*(1.0-Sa);
|
|
|
break;
|
|
|
}
|
|
|
case OutCompositeOp:
|
|
|
case SrcOutCompositeOp:
|
|
|
{
|
|
|
alpha=Sa*(1.0-Da);
|
|
|
break;
|
|
|
}
|
|
|
case BlendCompositeOp:
|
|
|
case PlusCompositeOp:
|
|
|
{
|
|
|
alpha=RoundToUnity(source_dissolve*Sa+canvas_dissolve*Da);
|
|
|
break;
|
|
|
}
|
|
|
case XorCompositeOp:
|
|
|
{
|
|
|
alpha=Sa+Da-2.0*Sa*Da;
|
|
|
break;
|
|
|
}
|
|
|
case ModulusAddCompositeOp:
|
|
|
{
|
|
|
if ((Sa+Da) <= 1.0)
|
|
|
{
|
|
|
alpha=(Sa+Da);
|
|
|
break;
|
|
|
}
|
|
|
alpha=((Sa+Da)-1.0);
|
|
|
break;
|
|
|
}
|
|
|
case ModulusSubtractCompositeOp:
|
|
|
{
|
|
|
if ((Sa-Da) >= 0.0)
|
|
|
{
|
|
|
alpha=(Sa-Da);
|
|
|
break;
|
|
|
}
|
|
|
alpha=((Sa-Da)+1.0);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
{
|
|
|
alpha=1.0;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
switch (compose)
|
|
|
{
|
|
|
case ColorizeCompositeOp:
|
|
|
case HueCompositeOp:
|
|
|
case LuminizeCompositeOp:
|
|
|
case ModulateCompositeOp:
|
|
|
case RMSECompositeOp:
|
|
|
case SaturateCompositeOp:
|
|
|
{
|
|
|
GetPixelInfoPixel(source_image,p,&source_pixel);
|
|
|
GetPixelInfoPixel(image,q,&canvas_pixel);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
|
{
|
|
|
MagickRealType
|
|
|
pixel,
|
|
|
sans;
|
|
|
|
|
|
PixelChannel channel = GetPixelChannelChannel(image,i);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
PixelTrait source_traits = GetPixelChannelTraits(source_image,channel);
|
|
|
if (traits == UndefinedPixelTrait)
|
|
|
continue;
|
|
|
if ((channel == AlphaPixelChannel) &&
|
|
|
((traits & UpdatePixelTrait) != 0))
|
|
|
{
|
|
|
/*
|
|
|
Set alpha channel.
|
|
|
*/
|
|
|
switch (compose)
|
|
|
{
|
|
|
case AlphaCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case AtopCompositeOp:
|
|
|
case CopyBlackCompositeOp:
|
|
|
case CopyBlueCompositeOp:
|
|
|
case CopyCyanCompositeOp:
|
|
|
case CopyGreenCompositeOp:
|
|
|
case CopyMagentaCompositeOp:
|
|
|
case CopyRedCompositeOp:
|
|
|
case CopyYellowCompositeOp:
|
|
|
case SrcAtopCompositeOp:
|
|
|
case DstCompositeOp:
|
|
|
case NoCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Da;
|
|
|
break;
|
|
|
}
|
|
|
case ChangeMaskCompositeOp:
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
equivalent;
|
|
|
|
|
|
if (Da < 0.5)
|
|
|
{
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
break;
|
|
|
}
|
|
|
equivalent=IsFuzzyEquivalencePixel(source_image,p,image,q);
|
|
|
if (equivalent != MagickFalse)
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
else
|
|
|
pixel=(MagickRealType) OpaqueAlpha;
|
|
|
break;
|
|
|
}
|
|
|
case ClearCompositeOp:
|
|
|
{
|
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
|
break;
|
|
|
}
|
|
|
case ColorizeCompositeOp:
|
|
|
case HueCompositeOp:
|
|
|
case LuminizeCompositeOp:
|
|
|
case RMSECompositeOp:
|
|
|
case SaturateCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=QuantumRange*Da;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
if (Sa < Da)
|
|
|
{
|
|
|
pixel=QuantumRange*Da;
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case CopyAlphaCompositeOp:
|
|
|
{
|
|
|
if (source_image->alpha_trait == UndefinedPixelTrait)
|
|
|
pixel=GetPixelIntensity(source_image,p);
|
|
|
else
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case BlurCompositeOp:
|
|
|
case CopyCompositeOp:
|
|
|
case DisplaceCompositeOp:
|
|
|
case DistortCompositeOp:
|
|
|
case DstAtopCompositeOp:
|
|
|
case ReplaceCompositeOp:
|
|
|
case SrcCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case DarkenIntensityCompositeOp:
|
|
|
{
|
|
|
pixel=Sa*GetPixelIntensity(source_image,p) <
|
|
|
Da*GetPixelIntensity(image,q) ? Sa : Da;
|
|
|
break;
|
|
|
}
|
|
|
case DifferenceCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*fabs((double) (Sa-Da));
|
|
|
break;
|
|
|
}
|
|
|
case FreezeCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(1.0-(1.0-Sa)*(1.0-Sa)*
|
|
|
PerceptibleReciprocal(Da));
|
|
|
if (pixel < 0.0)
|
|
|
pixel=0.0;
|
|
|
break;
|
|
|
}
|
|
|
case InterpolateCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(0.5-0.25*cos(MagickPI*Sa)-0.25*
|
|
|
cos(MagickPI*Da));
|
|
|
break;
|
|
|
}
|
|
|
case LightenIntensityCompositeOp:
|
|
|
{
|
|
|
pixel=Sa*GetPixelIntensity(source_image,p) >
|
|
|
Da*GetPixelIntensity(image,q) ? Sa : Da;
|
|
|
break;
|
|
|
}
|
|
|
case ModulateCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Da;
|
|
|
break;
|
|
|
}
|
|
|
case MultiplyCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Sa*Da;
|
|
|
break;
|
|
|
}
|
|
|
case NegateCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*((1.0-Sa-Da));
|
|
|
break;
|
|
|
}
|
|
|
case ReflectCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sa*Sa*PerceptibleReciprocal(1.0-Da));
|
|
|
if (pixel > QuantumRange)
|
|
|
pixel=QuantumRange;
|
|
|
break;
|
|
|
}
|
|
|
case StampCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sa+Da*Da-1.0);
|
|
|
break;
|
|
|
}
|
|
|
case StereoCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sa+Da)/2;
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
{
|
|
|
pixel=QuantumRange*alpha;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
|
|
|
ClampToQuantum(pixel);
|
|
|
continue;
|
|
|
}
|
|
|
if (source_traits == UndefinedPixelTrait)
|
|
|
continue;
|
|
|
/*
|
|
|
Sc: source color.
|
|
|
Dc: canvas color.
|
|
|
*/
|
|
|
Sc=(MagickRealType) GetPixelChannel(source_image,channel,p);
|
|
|
Dc=(MagickRealType) q[i];
|
|
|
if ((traits & CopyPixelTrait) != 0)
|
|
|
{
|
|
|
/*
|
|
|
Copy channel.
|
|
|
*/
|
|
|
q[i]=ClampToQuantum(Dc);
|
|
|
continue;
|
|
|
}
|
|
|
/*
|
|
|
Porter-Duff compositions:
|
|
|
Sca: source normalized color multiplied by alpha.
|
|
|
Dca: normalized canvas color multiplied by alpha.
|
|
|
*/
|
|
|
Sca=QuantumScale*Sa*Sc;
|
|
|
Dca=QuantumScale*Da*Dc;
|
|
|
SaSca=Sa*PerceptibleReciprocal(Sca);
|
|
|
DcaDa=Dca*PerceptibleReciprocal(Da);
|
|
|
switch (compose)
|
|
|
{
|
|
|
case DarkenCompositeOp:
|
|
|
case LightenCompositeOp:
|
|
|
case ModulusSubtractCompositeOp:
|
|
|
{
|
|
|
gamma=PerceptibleReciprocal(1.0-alpha);
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
{
|
|
|
gamma=PerceptibleReciprocal(alpha);
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
pixel=Dc;
|
|
|
switch (compose)
|
|
|
{
|
|
|
case AlphaCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Sa;
|
|
|
break;
|
|
|
}
|
|
|
case AtopCompositeOp:
|
|
|
case SrcAtopCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca*Da+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case BlendCompositeOp:
|
|
|
{
|
|
|
pixel=gamma*(source_dissolve*Sa*Sc+canvas_dissolve*Da*Dc);
|
|
|
break;
|
|
|
}
|
|
|
case CopyCompositeOp:
|
|
|
case ReplaceCompositeOp:
|
|
|
case SrcCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Sca;
|
|
|
break;
|
|
|
}
|
|
|
case BlurCompositeOp:
|
|
|
case DisplaceCompositeOp:
|
|
|
case DistortCompositeOp:
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
case BumpmapCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumScale*GetPixelIntensity(source_image,p)*Dc;
|
|
|
break;
|
|
|
}
|
|
|
case ChangeMaskCompositeOp:
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
case ClearCompositeOp:
|
|
|
{
|
|
|
pixel=0.0;
|
|
|
break;
|
|
|
}
|
|
|
case ColorBurnCompositeOp:
|
|
|
{
|
|
|
if ((Sca == 0.0) && (Dca == Da))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
if (Sca == 0.0)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Sa*Da-Sa*Da*MagickMin(1.0,(1.0-DcaDa)*
|
|
|
SaSca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case ColorDodgeCompositeOp:
|
|
|
{
|
|
|
if ((Sca*Da+Dca*Sa) >= Sa*Da)
|
|
|
pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
else
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa*Sa*PerceptibleReciprocal(Sa-Sca)+
|
|
|
Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case ColorizeCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
|
|
|
&sans,&sans,&luma);
|
|
|
CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
|
|
|
&hue,&chroma,&sans);
|
|
|
HCLComposite(hue,chroma,luma,&red,&green,&blue);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=red; break;
|
|
|
case GreenPixelChannel: pixel=green; break;
|
|
|
case BluePixelChannel: pixel=blue; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case CopyAlphaCompositeOp:
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
case CopyBlackCompositeOp:
|
|
|
{
|
|
|
if (channel == BlackPixelChannel)
|
|
|
pixel=(MagickRealType) GetPixelBlack(source_image,p);
|
|
|
break;
|
|
|
}
|
|
|
case CopyBlueCompositeOp:
|
|
|
case CopyYellowCompositeOp:
|
|
|
{
|
|
|
if (channel == BluePixelChannel)
|
|
|
pixel=(MagickRealType) GetPixelBlue(source_image,p);
|
|
|
break;
|
|
|
}
|
|
|
case CopyGreenCompositeOp:
|
|
|
case CopyMagentaCompositeOp:
|
|
|
{
|
|
|
if (channel == GreenPixelChannel)
|
|
|
pixel=(MagickRealType) GetPixelGreen(source_image,p);
|
|
|
break;
|
|
|
}
|
|
|
case CopyRedCompositeOp:
|
|
|
case CopyCyanCompositeOp:
|
|
|
{
|
|
|
if (channel == RedPixelChannel)
|
|
|
pixel=(MagickRealType) GetPixelRed(source_image,p);
|
|
|
break;
|
|
|
}
|
|
|
case DarkenCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Darken is equivalent to a 'Minimum' method
|
|
|
OR a greyscale version of a binary 'Or'
|
|
|
OR the 'Intersection' of pixel sets.
|
|
|
*/
|
|
|
if ((Sca*Da) < (Dca*Sa))
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*(Dca+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case DarkenIntensityCompositeOp:
|
|
|
{
|
|
|
pixel=Sa*GetPixelIntensity(source_image,p) <
|
|
|
Da*GetPixelIntensity(image,q) ? Sc : Dc;
|
|
|
break;
|
|
|
}
|
|
|
case DifferenceCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca+Dca-2.0*MagickMin(Sca*Da,Dca*Sa));
|
|
|
break;
|
|
|
}
|
|
|
case DissolveCompositeOp:
|
|
|
{
|
|
|
pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
|
|
|
canvas_dissolve*Da*Dc+canvas_dissolve*Da*Dc);
|
|
|
break;
|
|
|
}
|
|
|
case DivideDstCompositeOp:
|
|
|
{
|
|
|
if ((fabs((double) Sca) < MagickEpsilon) &&
|
|
|
(fabs((double) Dca) < MagickEpsilon))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) Dca) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case DivideSrcCompositeOp:
|
|
|
{
|
|
|
if ((fabs((double) Dca) < MagickEpsilon) &&
|
|
|
(fabs((double) Sca) < MagickEpsilon))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) Sca) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa*SaSca+Dca*(1.0-Sa)+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case DstAtopCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Dca*Sa+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case DstCompositeOp:
|
|
|
case NoCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*Dca;
|
|
|
break;
|
|
|
}
|
|
|
case DstInCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa);
|
|
|
break;
|
|
|
}
|
|
|
case DstOutCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case DstOverCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case ExclusionCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
|
|
|
Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case FreezeCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(1.0-(1.0-Sca)*(1.0-Sca)*
|
|
|
PerceptibleReciprocal(Dca));
|
|
|
if (pixel < 0.0)
|
|
|
pixel=0.0;
|
|
|
break;
|
|
|
}
|
|
|
case HardLightCompositeOp:
|
|
|
{
|
|
|
if ((2.0*Sca) < Sa)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-
|
|
|
Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
|
|
|
Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case HardMixCompositeOp:
|
|
|
{
|
|
|
pixel=gamma*(((Sca+Dca) < 1.0) ? 0.0 : QuantumRange);
|
|
|
break;
|
|
|
}
|
|
|
case HueCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
|
|
|
&hue,&chroma,&luma);
|
|
|
CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
|
|
|
&hue,&sans,&sans);
|
|
|
HCLComposite(hue,chroma,luma,&red,&green,&blue);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=red; break;
|
|
|
case GreenPixelChannel: pixel=green; break;
|
|
|
case BluePixelChannel: pixel=blue; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case InCompositeOp:
|
|
|
case SrcInCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca*Da);
|
|
|
break;
|
|
|
}
|
|
|
case InterpolateCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(0.5-0.25*cos(MagickPI*Sca)-0.25*
|
|
|
cos(MagickPI*Dca));
|
|
|
break;
|
|
|
}
|
|
|
case LinearBurnCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
LinearBurn: as defined by Abode Photoshop, according to
|
|
|
http://www.simplefilter.de/en/basics/mixmods.html is:
|
|
|
|
|
|
f(Sc,Dc) = Sc + Dc - 1
|
|
|
*/
|
|
|
pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
|
|
|
break;
|
|
|
}
|
|
|
case LinearDodgeCompositeOp:
|
|
|
{
|
|
|
pixel=gamma*(Sa*Sc+Da*Dc);
|
|
|
break;
|
|
|
}
|
|
|
case LinearLightCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
LinearLight: as defined by Abode Photoshop, according to
|
|
|
http://www.simplefilter.de/en/basics/mixmods.html is:
|
|
|
|
|
|
f(Sc,Dc) = Dc + 2*Sc - 1
|
|
|
*/
|
|
|
pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
|
|
|
break;
|
|
|
}
|
|
|
case LightenCompositeOp:
|
|
|
{
|
|
|
if ((Sca*Da) > (Dca*Sa))
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*(Dca+Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case LightenIntensityCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Lighten is equivalent to a 'Maximum' method
|
|
|
OR a greyscale version of a binary 'And'
|
|
|
OR the 'Union' of pixel sets.
|
|
|
*/
|
|
|
pixel=Sa*GetPixelIntensity(source_image,p) >
|
|
|
Da*GetPixelIntensity(image,q) ? Sc : Dc;
|
|
|
break;
|
|
|
}
|
|
|
case LuminizeCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
|
|
|
&hue,&chroma,&luma);
|
|
|
CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
|
|
|
&sans,&sans,&luma);
|
|
|
HCLComposite(hue,chroma,luma,&red,&green,&blue);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=red; break;
|
|
|
case GreenPixelChannel: pixel=green; break;
|
|
|
case BluePixelChannel: pixel=blue; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case MathematicsCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
'Mathematics' a free form user control mathematical composition
|
|
|
is defined as...
|
|
|
|
|
|
f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
|
|
|
|
|
|
Where the arguments A,B,C,D are (currently) passed to composite
|
|
|
as a command separated 'geometry' string in "compose:args" image
|
|
|
artifact.
|
|
|
|
|
|
A = a->rho, B = a->sigma, C = a->xi, D = a->psi
|
|
|
|
|
|
Applying the SVG transparency formula (see above), we get...
|
|
|
|
|
|
Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
|
|
|
|
|
|
Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
|
|
|
Dca*(1.0-Sa)
|
|
|
*/
|
|
|
pixel=QuantumRange*gamma*(geometry_info.rho*Sca*Dca+
|
|
|
geometry_info.sigma*Sca*Da+geometry_info.xi*Dca*Sa+
|
|
|
geometry_info.psi*Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case MinusDstCompositeOp:
|
|
|
{
|
|
|
pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
|
|
|
break;
|
|
|
}
|
|
|
case MinusSrcCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Minus source from canvas.
|
|
|
|
|
|
f(Sc,Dc) = Sc - Dc
|
|
|
*/
|
|
|
pixel=gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
|
|
|
break;
|
|
|
}
|
|
|
case ModulateCompositeOp:
|
|
|
{
|
|
|
ssize_t
|
|
|
offset;
|
|
|
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
offset=(ssize_t) (GetPixelIntensity(source_image,p)-midpoint);
|
|
|
if (offset == 0)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
|
|
|
&hue,&chroma,&luma);
|
|
|
luma+=(0.01*percent_luma*offset)/midpoint;
|
|
|
chroma*=0.01*percent_chroma;
|
|
|
HCLComposite(hue,chroma,luma,&red,&green,&blue);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=red; break;
|
|
|
case GreenPixelChannel: pixel=green; break;
|
|
|
case BluePixelChannel: pixel=blue; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case ModulusAddCompositeOp:
|
|
|
{
|
|
|
if ((Sca+Dca) <= 1.0)
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca+Dca);
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*((Sca+Dca)-1.0);
|
|
|
break;
|
|
|
}
|
|
|
case ModulusSubtractCompositeOp:
|
|
|
{
|
|
|
if ((Sca-Dca) >= 0.0)
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca-Dca);
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*((Sca-Dca)+1.0);
|
|
|
break;
|
|
|
}
|
|
|
case MultiplyCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case NegateCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(1.0-fabs(1.0-Sca-Dca));
|
|
|
break;
|
|
|
}
|
|
|
case OutCompositeOp:
|
|
|
case SrcOutCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case OverCompositeOp:
|
|
|
case SrcOverCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case OverlayCompositeOp:
|
|
|
{
|
|
|
if ((2.0*Dca) < Da)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*(1.0-
|
|
|
Da));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+
|
|
|
Sca*(1.0-Da));
|
|
|
break;
|
|
|
}
|
|
|
case PegtopLightCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
PegTop: A Soft-Light alternative: A continuous version of the
|
|
|
Softlight function, producing very similar results.
|
|
|
|
|
|
f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
|
|
|
|
|
|
http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
|
|
|
*/
|
|
|
if (fabs((double) Da) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*Sca;
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
|
|
|
Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case PinLightCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
PinLight: A Photoshop 7 composition method
|
|
|
http://www.simplefilter.de/en/basics/mixmods.html
|
|
|
|
|
|
f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
|
|
|
*/
|
|
|
if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
if ((Dca*Sa) > (2.0*Sca*Da))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
|
|
|
break;
|
|
|
}
|
|
|
case PlusCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca+Dca);
|
|
|
break;
|
|
|
}
|
|
|
case ReflectCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sca*Sca*PerceptibleReciprocal(1.0-Dca));
|
|
|
if (pixel > QuantumRange)
|
|
|
pixel=QuantumRange;
|
|
|
break;
|
|
|
}
|
|
|
case RMSECompositeOp:
|
|
|
{
|
|
|
double
|
|
|
gray;
|
|
|
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
gray=sqrt(
|
|
|
(canvas_pixel.red-source_pixel.red)*
|
|
|
(canvas_pixel.red-source_pixel.red)+
|
|
|
(canvas_pixel.green-source_pixel.green)*
|
|
|
(canvas_pixel.green-source_pixel.green)+
|
|
|
(canvas_pixel.blue-source_pixel.blue)*
|
|
|
(canvas_pixel.blue-source_pixel.blue)/3.0);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=gray; break;
|
|
|
case GreenPixelChannel: pixel=gray; break;
|
|
|
case BluePixelChannel: pixel=gray; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case SaturateCompositeOp:
|
|
|
{
|
|
|
if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Dc;
|
|
|
break;
|
|
|
}
|
|
|
if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
|
|
|
&hue,&chroma,&luma);
|
|
|
CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
|
|
|
&sans,&chroma,&sans);
|
|
|
HCLComposite(hue,chroma,luma,&red,&green,&blue);
|
|
|
switch (channel)
|
|
|
{
|
|
|
case RedPixelChannel: pixel=red; break;
|
|
|
case GreenPixelChannel: pixel=green; break;
|
|
|
case BluePixelChannel: pixel=blue; break;
|
|
|
default: pixel=Dc; break;
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
|
case ScreenCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
Screen: a negated multiply:
|
|
|
|
|
|
f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
|
|
|
*/
|
|
|
pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
|
|
|
break;
|
|
|
}
|
|
|
case SoftBurnCompositeOp:
|
|
|
{
|
|
|
if ((Sca+Dca) < 1.0)
|
|
|
pixel=QuantumRange*gamma*(0.5*Dca*PerceptibleReciprocal(1.0-Sca));
|
|
|
else
|
|
|
pixel=QuantumRange*gamma*(1.0-0.5*(1.0-Sca)*
|
|
|
PerceptibleReciprocal(Dca));
|
|
|
break;
|
|
|
}
|
|
|
case SoftDodgeCompositeOp:
|
|
|
{
|
|
|
if ((Sca+Dca) < 1.0)
|
|
|
pixel=QuantumRange*gamma*(0.5*Sca*PerceptibleReciprocal(1.0-Dca));
|
|
|
else
|
|
|
pixel=QuantumRange*gamma*(1.0-0.5*(1.0-Dca)*
|
|
|
PerceptibleReciprocal(Sca));
|
|
|
break;
|
|
|
}
|
|
|
case SoftLightCompositeOp:
|
|
|
{
|
|
|
if ((2.0*Sca) < Sa)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-DcaDa))+
|
|
|
Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*DcaDa*
|
|
|
(4.0*DcaDa+1.0)*(DcaDa-1.0)+7.0*DcaDa)+Sca*(1.0-Da)+
|
|
|
Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow(DcaDa,0.5)-
|
|
|
DcaDa)+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case StampCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca+Dca*Dca-1.0);
|
|
|
break;
|
|
|
}
|
|
|
case StereoCompositeOp:
|
|
|
{
|
|
|
if (channel == RedPixelChannel)
|
|
|
pixel=(MagickRealType) GetPixelRed(source_image,p);
|
|
|
break;
|
|
|
}
|
|
|
case ThresholdCompositeOp:
|
|
|
{
|
|
|
MagickRealType
|
|
|
delta;
|
|
|
|
|
|
delta=Sc-Dc;
|
|
|
if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
|
|
|
{
|
|
|
pixel=gamma*Dc;
|
|
|
break;
|
|
|
}
|
|
|
pixel=gamma*(Dc+delta*amount);
|
|
|
break;
|
|
|
}
|
|
|
case VividLightCompositeOp:
|
|
|
{
|
|
|
/*
|
|
|
VividLight: A Photoshop 7 composition method. See
|
|
|
http://www.simplefilter.de/en/basics/mixmods.html.
|
|
|
|
|
|
f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
|
|
|
*/
|
|
|
if ((fabs((double) Sa) < MagickEpsilon) ||
|
|
|
(fabs((double) (Sca-Sa)) < MagickEpsilon))
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
if ((2.0*Sca) <= Sa)
|
|
|
{
|
|
|
pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)*
|
|
|
PerceptibleReciprocal(2.0*Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
pixel=QuantumRange*gamma*(Dca*Sa*Sa*PerceptibleReciprocal(2.0*
|
|
|
(Sa-Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
case XorCompositeOp:
|
|
|
{
|
|
|
pixel=QuantumRange*(Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
|
break;
|
|
|
}
|
|
|
default:
|
|
|
{
|
|
|
pixel=Sc;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel);
|
|
|
}
|
|
|
p+=GetPixelChannels(source_image);
|
|
|
channels=GetPixelChannels(source_image);
|
|
|
if (p >= (pixels+channels*source_image->columns))
|
|
|
p=pixels;
|
|
|
q+=GetPixelChannels(image);
|
|
|
}
|
|
|
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp atomic
|
|
|
#endif
|
|
|
progress++;
|
|
|
proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
source_view=DestroyCacheView(source_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
if (canvas_image != (Image * ) NULL)
|
|
|
canvas_image=DestroyImage(canvas_image);
|
|
|
else
|
|
|
source_image=DestroyImage(source_image);
|
|
|
return(status);
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
% %
|
|
|
% %
|
|
|
% %
|
|
|
% T e x t u r e I m a g e %
|
|
|
% %
|
|
|
% %
|
|
|
% %
|
|
|
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
|
|
%
|
|
|
% TextureImage() repeatedly tiles the texture image across and down the image
|
|
|
% canvas.
|
|
|
%
|
|
|
% The format of the TextureImage method is:
|
|
|
%
|
|
|
% MagickBooleanType TextureImage(Image *image,const Image *texture,
|
|
|
% ExceptionInfo *exception)
|
|
|
%
|
|
|
% A description of each parameter follows:
|
|
|
%
|
|
|
% o image: the image.
|
|
|
%
|
|
|
% o texture_image: This image is the texture to layer on the background.
|
|
|
%
|
|
|
*/
|
|
|
MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
|
|
|
ExceptionInfo *exception)
|
|
|
{
|
|
|
#define TextureImageTag "Texture/Image"
|
|
|
|
|
|
CacheView
|
|
|
*image_view,
|
|
|
*texture_view;
|
|
|
|
|
|
Image
|
|
|
*texture_image;
|
|
|
|
|
|
MagickBooleanType
|
|
|
status;
|
|
|
|
|
|
ssize_t
|
|
|
y;
|
|
|
|
|
|
assert(image != (Image *) NULL);
|
|
|
if (image->debug != MagickFalse)
|
|
|
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
|
|
|
assert(image->signature == MagickCoreSignature);
|
|
|
if (texture == (const Image *) NULL)
|
|
|
return(MagickFalse);
|
|
|
if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
|
|
|
return(MagickFalse);
|
|
|
texture_image=CloneImage(texture,0,0,MagickTrue,exception);
|
|
|
if (texture_image == (const Image *) NULL)
|
|
|
return(MagickFalse);
|
|
|
(void) TransformImageColorspace(texture_image,image->colorspace,exception);
|
|
|
(void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
|
|
|
exception);
|
|
|
status=MagickTrue;
|
|
|
if ((image->compose != CopyCompositeOp) &&
|
|
|
((image->compose != OverCompositeOp) ||
|
|
|
(image->alpha_trait != UndefinedPixelTrait) ||
|
|
|
(texture_image->alpha_trait != UndefinedPixelTrait)))
|
|
|
{
|
|
|
/*
|
|
|
Tile texture onto the image background.
|
|
|
*/
|
|
|
for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
|
|
|
{
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
thread_status;
|
|
|
|
|
|
thread_status=CompositeImage(image,texture_image,image->compose,
|
|
|
MagickTrue,x+texture_image->tile_offset.x,y+
|
|
|
texture_image->tile_offset.y,exception);
|
|
|
if (thread_status == MagickFalse)
|
|
|
{
|
|
|
status=thread_status;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
|
|
|
image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
(void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
|
|
|
image->rows,image->rows);
|
|
|
texture_image=DestroyImage(texture_image);
|
|
|
return(status);
|
|
|
}
|
|
|
/*
|
|
|
Tile texture onto the image background (optimized).
|
|
|
*/
|
|
|
status=MagickTrue;
|
|
|
texture_view=AcquireVirtualCacheView(texture_image,exception);
|
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
|
magick_number_threads(texture_image,image,image->rows,1)
|
|
|
#endif
|
|
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
sync;
|
|
|
|
|
|
const Quantum
|
|
|
*p,
|
|
|
*pixels;
|
|
|
|
|
|
ssize_t
|
|
|
x;
|
|
|
|
|
|
Quantum
|
|
|
*q;
|
|
|
|
|
|
size_t
|
|
|
width;
|
|
|
|
|
|
if (status == MagickFalse)
|
|
|
continue;
|
|
|
pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
|
|
|
(y+texture_image->tile_offset.y) % texture_image->rows,
|
|
|
texture_image->columns,1,exception);
|
|
|
q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
|
|
|
if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
|
{
|
|
|
status=MagickFalse;
|
|
|
continue;
|
|
|
}
|
|
|
for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
|
|
|
{
|
|
|
ssize_t
|
|
|
j;
|
|
|
|
|
|
p=pixels;
|
|
|
width=texture_image->columns;
|
|
|
if ((x+(ssize_t) width) > (ssize_t) image->columns)
|
|
|
width=image->columns-x;
|
|
|
for (j=0; j < (ssize_t) width; j++)
|
|
|
{
|
|
|
ssize_t
|
|
|
i;
|
|
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
|
|
|
{
|
|
|
PixelChannel channel = GetPixelChannelChannel(texture_image,i);
|
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
|
PixelTrait texture_traits=GetPixelChannelTraits(texture_image,
|
|
|
channel);
|
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
|
(texture_traits == UndefinedPixelTrait))
|
|
|
continue;
|
|
|
SetPixelChannel(image,channel,p[i],q);
|
|
|
}
|
|
|
p+=GetPixelChannels(texture_image);
|
|
|
q+=GetPixelChannels(image);
|
|
|
}
|
|
|
}
|
|
|
sync=SyncCacheViewAuthenticPixels(image_view,exception);
|
|
|
if (sync == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
|
|
{
|
|
|
MagickBooleanType
|
|
|
proceed;
|
|
|
|
|
|
proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
|
|
|
image->rows);
|
|
|
if (proceed == MagickFalse)
|
|
|
status=MagickFalse;
|
|
|
}
|
|
|
}
|
|
|
texture_view=DestroyCacheView(texture_view);
|
|
|
image_view=DestroyCacheView(image_view);
|
|
|
texture_image=DestroyImage(texture_image);
|
|
|
return(status);
|
|
|
}
|