2672 lines
82 KiB
C
2672 lines
82 KiB
C
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% 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);
|
||
}
|