mirror of
https://github.com/ImageMagick/ImageMagick.git
synced 2026-05-25 11:24:54 +02:00
3781 lines
118 KiB
C
3781 lines
118 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 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/license/ %
|
|
% %
|
|
% 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/exception-private.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/memory_.h"
|
|
#include "MagickCore/monitor.h"
|
|
#include "MagickCore/monitor-private.h"
|
|
#include "MagickCore/morphology.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/string-private.h"
|
|
#include "MagickCore/thread-private.h"
|
|
#include "MagickCore/threshold.h"
|
|
#include "MagickCore/token.h"
|
|
#include "MagickCore/transform.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 normalized 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 alpha 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' (synchronize channel updates)
|
|
is turned off (enabled by default) then mathematical compositions are
|
|
only performed on the channels specified, and are applied
|
|
independently of each other. In other words the mathematics is
|
|
performed as 'pure' mathematical operations, rather than as image
|
|
operations.
|
|
*/
|
|
|
|
static Image *BlendConvolveImage(const Image *image,const char *kernel,
|
|
ExceptionInfo *exception)
|
|
{
|
|
Image
|
|
*clone_image,
|
|
*convolve_image;
|
|
|
|
KernelInfo
|
|
*kernel_info;
|
|
|
|
/*
|
|
Convolve image with a kernel.
|
|
*/
|
|
kernel_info=AcquireKernelInfo(kernel,exception);
|
|
if (kernel_info == (KernelInfo *) NULL)
|
|
return((Image *) NULL);
|
|
clone_image=CloneImage(image,0,0,MagickTrue,exception);
|
|
if (clone_image == (Image *) NULL)
|
|
{
|
|
kernel_info=DestroyKernelInfo(kernel_info);
|
|
return((Image *) NULL);
|
|
}
|
|
(void) SetImageAlphaChannel(clone_image,OffAlphaChannel,exception);
|
|
convolve_image=ConvolveImage(clone_image,kernel_info,exception);
|
|
kernel_info=DestroyKernelInfo(kernel_info);
|
|
clone_image=DestroyImage(clone_image);
|
|
return(convolve_image);
|
|
}
|
|
|
|
static Image *BlendMagnitudeImage(const Image *dx_image,const Image *dy_image,
|
|
ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*dx_view,
|
|
*dy_view,
|
|
*magnitude_view;
|
|
|
|
Image
|
|
*magnitude_image;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
ssize_t
|
|
y;
|
|
|
|
/*
|
|
Generate the magnitude between two images.
|
|
*/
|
|
magnitude_image=CloneImage(dx_image,0,0,MagickTrue,exception);
|
|
if (magnitude_image == (Image *) NULL)
|
|
return(magnitude_image);
|
|
dx_view=AcquireVirtualCacheView(dx_image,exception);
|
|
dy_view=AcquireVirtualCacheView(dy_image,exception);
|
|
magnitude_view=AcquireAuthenticCacheView(magnitude_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
magick_number_threads(dx_image,magnitude_image,dx_image->rows,1)
|
|
#endif
|
|
for (y=0; y < (ssize_t) dx_image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p,
|
|
*magick_restrict q;
|
|
|
|
Quantum
|
|
*magick_restrict r;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(dx_view,0,y,dx_image->columns,1,exception);
|
|
q=GetCacheViewVirtualPixels(dy_view,0,y,dx_image->columns,1,exception);
|
|
r=GetCacheViewAuthenticPixels(magnitude_view,0,y,dx_image->columns,1,
|
|
exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
|
|
(r == (Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
for (x=0; x < (ssize_t) dx_image->columns; x++)
|
|
{
|
|
ssize_t
|
|
i;
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(dx_image); i++)
|
|
{
|
|
PixelChannel channel = GetPixelChannelChannel(dx_image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(dx_image,channel);
|
|
PixelTrait dy_traits = GetPixelChannelTraits(dy_image,channel);
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
(dy_traits == UndefinedPixelTrait) ||
|
|
((dy_traits & UpdatePixelTrait) == 0))
|
|
continue;
|
|
r[i]=ClampToQuantum(hypot((double) p[i],(double)
|
|
GetPixelChannel(dy_image,channel,q)));
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(dx_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(dy_image);
|
|
r+=(ptrdiff_t) GetPixelChannels(magnitude_image);
|
|
}
|
|
if (SyncCacheViewAuthenticPixels(magnitude_view,exception) == MagickFalse)
|
|
status=MagickFalse;
|
|
}
|
|
magnitude_view=DestroyCacheView(magnitude_view);
|
|
dy_view=DestroyCacheView(dy_view);
|
|
dx_view=DestroyCacheView(dx_view);
|
|
if (status == MagickFalse)
|
|
magnitude_image=DestroyImage(magnitude_image);
|
|
return(magnitude_image);
|
|
}
|
|
|
|
static Image *BlendMaxMagnitudeImage(const Image *alpha_image,
|
|
const Image *beta_image,const Image *dx_image,const Image *dy_image,
|
|
ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*alpha_view,
|
|
*beta_view,
|
|
*dx_view,
|
|
*dy_view,
|
|
*magnitude_view;
|
|
|
|
Image
|
|
*magnitude_image;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
ssize_t
|
|
y;
|
|
|
|
/*
|
|
Select the larger of two magnitudes.
|
|
*/
|
|
magnitude_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
|
|
if (magnitude_image == (Image *) NULL)
|
|
return(magnitude_image);
|
|
alpha_view=AcquireVirtualCacheView(alpha_image,exception);
|
|
beta_view=AcquireVirtualCacheView(beta_image,exception);
|
|
dx_view=AcquireVirtualCacheView(dx_image,exception);
|
|
dy_view=AcquireVirtualCacheView(dy_image,exception);
|
|
magnitude_view=AcquireAuthenticCacheView(magnitude_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
magick_number_threads(alpha_image,magnitude_image,alpha_image->rows,1)
|
|
#endif
|
|
for (y=0; y < (ssize_t) alpha_image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p,
|
|
*magick_restrict q,
|
|
*magick_restrict r,
|
|
*magick_restrict s;
|
|
|
|
Quantum
|
|
*magick_restrict t;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(alpha_view,0,y,alpha_image->columns,1,
|
|
exception);
|
|
q=GetCacheViewVirtualPixels(beta_view,0,y,alpha_image->columns,1,exception);
|
|
r=GetCacheViewVirtualPixels(dx_view,0,y,alpha_image->columns,1,exception);
|
|
s=GetCacheViewVirtualPixels(dy_view,0,y,alpha_image->columns,1,exception);
|
|
t=GetCacheViewAuthenticPixels(magnitude_view,0,y,alpha_image->columns,1,
|
|
exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
|
|
(r == (const Quantum *) NULL) || (s == (const Quantum *) NULL) ||
|
|
(t == (Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
for (x=0; x < (ssize_t) alpha_image->columns; x++)
|
|
{
|
|
ssize_t
|
|
i;
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(alpha_image); i++)
|
|
{
|
|
PixelChannel channel = GetPixelChannelChannel(alpha_image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(alpha_image,channel);
|
|
PixelTrait beta_traits = GetPixelChannelTraits(beta_image,channel);
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
(beta_traits == UndefinedPixelTrait) ||
|
|
((beta_traits & UpdatePixelTrait) == 0))
|
|
continue;
|
|
if (p[i] > GetPixelChannel(beta_image,channel,q))
|
|
t[i]=GetPixelChannel(dx_image,channel,r);
|
|
else
|
|
t[i]=GetPixelChannel(dy_image,channel,s);
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(alpha_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(beta_image);
|
|
r+=(ptrdiff_t) GetPixelChannels(dx_image);
|
|
s+=(ptrdiff_t) GetPixelChannels(dy_image);
|
|
t+=(ptrdiff_t) GetPixelChannels(magnitude_image);
|
|
}
|
|
if (SyncCacheViewAuthenticPixels(magnitude_view,exception) == MagickFalse)
|
|
status=MagickFalse;
|
|
}
|
|
magnitude_view=DestroyCacheView(magnitude_view);
|
|
dy_view=DestroyCacheView(dy_view);
|
|
dx_view=DestroyCacheView(dx_view);
|
|
beta_view=DestroyCacheView(beta_view);
|
|
alpha_view=DestroyCacheView(alpha_view);
|
|
if (status == MagickFalse)
|
|
magnitude_image=DestroyImage(magnitude_image);
|
|
return(magnitude_image);
|
|
}
|
|
|
|
static Image *BlendSumImage(const Image *alpha_image,const Image *beta_image,
|
|
const double attenuate,const double sign,ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*alpha_view,
|
|
*beta_view,
|
|
*sum_view;
|
|
|
|
Image
|
|
*sum_image;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
ssize_t
|
|
y;
|
|
|
|
/*
|
|
Add or subtract and optionally attenuate two images.
|
|
*/
|
|
sum_image=CloneImage(alpha_image,0,0,MagickTrue,exception);
|
|
if (sum_image == (Image *) NULL)
|
|
return(sum_image);
|
|
alpha_view=AcquireVirtualCacheView(alpha_image,exception);
|
|
beta_view=AcquireVirtualCacheView(beta_image,exception);
|
|
sum_view=AcquireAuthenticCacheView(sum_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
magick_number_threads(alpha_image,sum_image,alpha_image->rows,1)
|
|
#endif
|
|
for (y=0; y < (ssize_t) alpha_image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p,
|
|
*magick_restrict q;
|
|
|
|
Quantum
|
|
*magick_restrict r;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(alpha_view,0,y,alpha_image->columns,1,
|
|
exception);
|
|
q=GetCacheViewVirtualPixels(beta_view,0,y,alpha_image->columns,1,exception);
|
|
r=GetCacheViewAuthenticPixels(sum_view,0,y,alpha_image->columns,1,
|
|
exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
|
|
(r == (Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
for (x=0; x < (ssize_t) alpha_image->columns; x++)
|
|
{
|
|
ssize_t
|
|
i;
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(alpha_image); i++)
|
|
{
|
|
PixelChannel channel = GetPixelChannelChannel(alpha_image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(alpha_image,channel);
|
|
PixelTrait beta_traits = GetPixelChannelTraits(beta_image,channel);
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
(beta_traits == UndefinedPixelTrait) ||
|
|
((beta_traits & UpdatePixelTrait) == 0))
|
|
continue;
|
|
r[i]=ClampToQuantum(attenuate*((double) p[i]+sign*
|
|
(double) GetPixelChannel(beta_image,channel,q)));
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(alpha_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(beta_image);
|
|
r+=(ptrdiff_t) GetPixelChannels(sum_image);
|
|
}
|
|
if (SyncCacheViewAuthenticPixels(sum_view,exception) == MagickFalse)
|
|
status=MagickFalse;
|
|
}
|
|
sum_view=DestroyCacheView(sum_view);
|
|
beta_view=DestroyCacheView(beta_view);
|
|
alpha_view=DestroyCacheView(alpha_view);
|
|
if (status == MagickFalse)
|
|
sum_image=DestroyImage(sum_image);
|
|
return(sum_image);
|
|
}
|
|
|
|
static Image *BlendDivergentImage(const Image *alpha_image,
|
|
const Image *beta_image,ExceptionInfo *exception)
|
|
{
|
|
#define FreeDivergentResources() \
|
|
{ \
|
|
if (dy_image != (Image *) NULL) \
|
|
dy_image=DestroyImage(dy_image); \
|
|
if (dx_image != (Image *) NULL) \
|
|
dx_image=DestroyImage(dx_image); \
|
|
if (magnitude_beta != (Image *) NULL) \
|
|
magnitude_beta=DestroyImage(magnitude_beta); \
|
|
if (dy_beta != (Image *) NULL) \
|
|
dy_beta=DestroyImage(dy_beta); \
|
|
if (dx_beta != (Image *) NULL) \
|
|
dx_beta=DestroyImage(dx_beta); \
|
|
if (magnitude_alpha != (Image *) NULL) \
|
|
magnitude_alpha=DestroyImage(magnitude_alpha); \
|
|
if (dy_alpha != (Image *) NULL) \
|
|
dy_alpha=DestroyImage(dy_alpha); \
|
|
if (dx_alpha != (Image *) NULL) \
|
|
dx_alpha=DestroyImage(dx_alpha); \
|
|
}
|
|
|
|
Image
|
|
*divergent_image = (Image *) NULL,
|
|
*dx_alpha = (Image *) NULL,
|
|
*dx_beta = (Image *) NULL,
|
|
*dx_divergent = (Image *) NULL,
|
|
*dx_image = (Image *) NULL,
|
|
*dy_alpha = (Image *) NULL,
|
|
*dy_beta = (Image *) NULL,
|
|
*dy_divergent = (Image *) NULL,
|
|
*dy_image = (Image *) NULL,
|
|
*magnitude_alpha = (Image *) NULL,
|
|
*magnitude_beta = (Image *) NULL;
|
|
|
|
/*
|
|
Create X and Y gradient images for alpha image and the magnitude.
|
|
*/
|
|
dx_alpha=BlendConvolveImage(alpha_image,"3x1:-0.5,0.0,0.5",exception);
|
|
if (dx_alpha == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
dy_alpha=BlendConvolveImage(alpha_image,"1x3:-0.5,0.0,0.5",exception);
|
|
if (dy_alpha == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
magnitude_alpha=BlendMagnitudeImage(dx_alpha,dy_alpha,exception);
|
|
if (magnitude_alpha == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
/*
|
|
Create X and Y gradient images for beta and the magnitude.
|
|
*/
|
|
dx_beta=BlendConvolveImage(beta_image,"3x1:-0.5,0.0,0.5",exception);
|
|
if (dx_beta == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
dy_beta=BlendConvolveImage(beta_image,"1x3:-0.5,0.0,0.5",exception);
|
|
if (dy_beta == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
magnitude_beta=BlendMagnitudeImage(dx_beta,dy_beta,exception);
|
|
if (magnitude_beta == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
/*
|
|
Select alpha or beta gradient for larger of two magnitudes.
|
|
*/
|
|
dx_image=BlendMaxMagnitudeImage(magnitude_alpha,magnitude_beta,dx_alpha,
|
|
dx_beta,exception);
|
|
if (dx_image == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
dy_image=BlendMaxMagnitudeImage(magnitude_alpha,magnitude_beta,dy_alpha,
|
|
dy_beta,exception);
|
|
if (dy_image == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
dx_beta=DestroyImage(dx_beta);
|
|
dx_alpha=DestroyImage(dx_alpha);
|
|
magnitude_beta=DestroyImage(magnitude_beta);
|
|
magnitude_alpha=DestroyImage(magnitude_alpha);
|
|
/*
|
|
Create divergence of gradients dx and dy and divide by 4 as guide image.
|
|
*/
|
|
dx_divergent=BlendConvolveImage(dx_image,"3x1:-0.5,0.0,0.5",exception);
|
|
if (dx_divergent == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
dy_divergent=BlendConvolveImage(dy_image,"1x3:-0.5,0.0,0.5",exception);
|
|
if (dy_divergent == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
divergent_image=BlendSumImage(dx_divergent,dy_divergent,0.25,1.0,exception);
|
|
dy_divergent=DestroyImage(dy_divergent);
|
|
dx_divergent=DestroyImage(dx_divergent);
|
|
if (divergent_image == (Image *) NULL)
|
|
{
|
|
FreeDivergentResources();
|
|
return((Image *) NULL);
|
|
}
|
|
FreeDivergentResources();
|
|
return(divergent_image);
|
|
}
|
|
|
|
static MagickBooleanType BlendMaskAlphaChannel(Image *image,
|
|
const Image *mask_image,ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*image_view,
|
|
*mask_view;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
ssize_t
|
|
y;
|
|
|
|
/*
|
|
Threshold the alpha channel.
|
|
*/
|
|
if (SetImageAlpha(image,OpaqueAlpha,exception) == MagickFalse)
|
|
return(MagickFalse);
|
|
image_view=AcquireAuthenticCacheView(image,exception);
|
|
mask_view=AcquireVirtualCacheView(mask_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
magick_number_threads(image,image,image->rows,2)
|
|
#endif
|
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p;
|
|
|
|
Quantum
|
|
*magick_restrict q;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(mask_view,0,y,image->columns,1,exception);
|
|
q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
for (x=0; x < (ssize_t) image->columns; x++)
|
|
{
|
|
Quantum
|
|
alpha = GetPixelAlpha(mask_image,p);
|
|
|
|
ssize_t
|
|
i = GetPixelChannelOffset(image,AlphaPixelChannel);
|
|
|
|
if (fabs((double) alpha) >= MagickEpsilon)
|
|
q[i]=(Quantum) 0;
|
|
p+=(ptrdiff_t) GetPixelChannels(mask_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(image);
|
|
}
|
|
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
|
|
status=MagickFalse;
|
|
}
|
|
mask_view=DestroyCacheView(mask_view);
|
|
image_view=DestroyCacheView(image_view);
|
|
return(status);
|
|
}
|
|
|
|
static Image *BlendMeanImage(Image *image,const Image *mask_image,
|
|
ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*alpha_view,
|
|
*mask_view,
|
|
*mean_view;
|
|
|
|
double
|
|
mean[MaxPixelChannels];
|
|
|
|
Image
|
|
*mean_image;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
ssize_t
|
|
j,
|
|
y;
|
|
|
|
/*
|
|
Compute the mean of the image.
|
|
*/
|
|
(void) memset(mean,0,MaxPixelChannels*sizeof(*mean));
|
|
alpha_view=AcquireVirtualCacheView(image,exception);
|
|
for (y=0; y < (ssize_t) image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
p=GetCacheViewVirtualPixels(alpha_view,0,y,image->columns,1,
|
|
exception);
|
|
if (p == (const Quantum *) NULL)
|
|
break;
|
|
for (x=0; x < (ssize_t) image->columns; x++)
|
|
{
|
|
ssize_t
|
|
i;
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
{
|
|
PixelChannel channel = GetPixelChannelChannel(image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(image,channel);
|
|
if (traits == UndefinedPixelTrait)
|
|
continue;
|
|
mean[i]+=QuantumScale*(double) p[i];
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(image);
|
|
}
|
|
}
|
|
alpha_view=DestroyCacheView(alpha_view);
|
|
if (y < (ssize_t) image->rows)
|
|
return((Image *) NULL);
|
|
for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
|
|
mean[j]=(double) QuantumRange*mean[j]/image->columns/
|
|
image->rows;
|
|
/*
|
|
Replace any unmasked pixels with the mean pixel.
|
|
*/
|
|
mean_image=CloneImage(image,0,0,MagickTrue,exception);
|
|
if (mean_image == (Image *) NULL)
|
|
return(mean_image);
|
|
mask_view=AcquireVirtualCacheView(mask_image,exception);
|
|
mean_view=AcquireAuthenticCacheView(mean_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
magick_number_threads(mask_image,mean_image,mean_image->rows,4)
|
|
#endif
|
|
for (y=0; y < (ssize_t) mean_image->rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p;
|
|
|
|
Quantum
|
|
*magick_restrict q;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(mask_view,0,y,mean_image->columns,1,exception);
|
|
q=GetCacheViewAuthenticPixels(mean_view,0,y,mean_image->columns,1,
|
|
exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
for (x=0; x < (ssize_t) mean_image->columns; x++)
|
|
{
|
|
Quantum
|
|
alpha = GetPixelAlpha(mask_image,p),
|
|
mask = GetPixelReadMask(mask_image,p);
|
|
|
|
ssize_t
|
|
i;
|
|
|
|
for (i=0; i < (ssize_t) GetPixelChannels(mean_image); i++)
|
|
{
|
|
PixelChannel channel = GetPixelChannelChannel(mean_image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(mean_image,channel);
|
|
if (traits == UndefinedPixelTrait)
|
|
continue;
|
|
if (mask <= (QuantumRange/2))
|
|
q[i]=(Quantum) 0;
|
|
else
|
|
if (fabs((double) alpha) >= MagickEpsilon)
|
|
q[i]=ClampToQuantum(mean[i]);
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(mask_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(mean_image);
|
|
}
|
|
if (SyncCacheViewAuthenticPixels(mean_view,exception) == MagickFalse)
|
|
status=MagickFalse;
|
|
}
|
|
mask_view=DestroyCacheView(mask_view);
|
|
mean_view=DestroyCacheView(mean_view);
|
|
if (status == MagickFalse)
|
|
mean_image=DestroyImage(mean_image);
|
|
return(mean_image);
|
|
}
|
|
|
|
static MagickBooleanType BlendRMSEResidual(const Image *alpha_image,
|
|
const Image *beta_image,double *residual,ExceptionInfo *exception)
|
|
{
|
|
CacheView
|
|
*alpha_view,
|
|
*beta_view;
|
|
|
|
double
|
|
area = 0.0,
|
|
channel_residual = 0.0;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue;
|
|
|
|
size_t
|
|
columns = MagickMax(alpha_image->columns,beta_image->columns),
|
|
rows = MagickMax(alpha_image->rows,beta_image->rows);
|
|
|
|
ssize_t
|
|
y;
|
|
|
|
alpha_view=AcquireVirtualCacheView(alpha_image,exception);
|
|
beta_view=AcquireVirtualCacheView(beta_image,exception);
|
|
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
|
#pragma omp parallel for schedule(static) shared(status) \
|
|
reduction(+:area) reduction(+:channel_residual) \
|
|
magick_number_threads(alpha_image,alpha_image,rows,1)
|
|
#endif
|
|
for (y=0; y < (ssize_t) rows; y++)
|
|
{
|
|
const Quantum
|
|
*magick_restrict p,
|
|
*magick_restrict q;
|
|
|
|
ssize_t
|
|
x;
|
|
|
|
if (status == MagickFalse)
|
|
continue;
|
|
p=GetCacheViewVirtualPixels(alpha_view,0,y,columns,1,exception);
|
|
q=GetCacheViewVirtualPixels(beta_view,0,y,columns,1,exception);
|
|
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
channel_residual=0.0;
|
|
for (x=0; x < (ssize_t) columns; x++)
|
|
{
|
|
double
|
|
Da,
|
|
Sa;
|
|
|
|
ssize_t
|
|
i;
|
|
|
|
if ((GetPixelReadMask(alpha_image,p) <= (QuantumRange/2)) ||
|
|
(GetPixelReadMask(beta_image,q) <= (QuantumRange/2)))
|
|
{
|
|
p+=(ptrdiff_t) GetPixelChannels(alpha_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(beta_image);
|
|
continue;
|
|
}
|
|
Sa=QuantumScale*(double) GetPixelAlpha(alpha_image,p);
|
|
Da=QuantumScale*(double) GetPixelAlpha(beta_image,q);
|
|
for (i=0; i < (ssize_t) GetPixelChannels(alpha_image); i++)
|
|
{
|
|
double
|
|
distance;
|
|
|
|
PixelChannel channel = GetPixelChannelChannel(alpha_image,i);
|
|
PixelTrait traits = GetPixelChannelTraits(alpha_image,channel);
|
|
PixelTrait beta_traits = GetPixelChannelTraits(beta_image,channel);
|
|
if ((traits == UndefinedPixelTrait) ||
|
|
(beta_traits == UndefinedPixelTrait) ||
|
|
((beta_traits & UpdatePixelTrait) == 0))
|
|
continue;
|
|
if (channel == AlphaPixelChannel)
|
|
distance=QuantumScale*((double) p[i]-(double) GetPixelChannel(
|
|
beta_image,channel,q));
|
|
else
|
|
distance=QuantumScale*(Sa*(double) p[i]-Da*(double) GetPixelChannel(
|
|
beta_image,channel,q));
|
|
channel_residual+=distance*distance;
|
|
}
|
|
area++;
|
|
p+=(ptrdiff_t) GetPixelChannels(alpha_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(beta_image);
|
|
}
|
|
}
|
|
beta_view=DestroyCacheView(beta_view);
|
|
alpha_view=DestroyCacheView(alpha_view);
|
|
area=MagickSafeReciprocal(area);
|
|
*residual=sqrt(area*channel_residual/(double) GetImageChannels(alpha_image));
|
|
return(status);
|
|
}
|
|
|
|
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,CastDoubleToSsizeT((double) y-
|
|
y_offset),source_image->columns,1,exception);
|
|
if (p == (const Quantum *) NULL)
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
pixels=p;
|
|
if (x_offset < 0)
|
|
p-=(ptrdiff_t) CastDoubleToSsizeT((double) x_offset*
|
|
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+=(ptrdiff_t) 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,
|
|
CastDoubleToSsizeT((double) x-x_offset),
|
|
CastDoubleToSsizeT((double) 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+=(ptrdiff_t) GetPixelChannels(image);
|
|
continue;
|
|
}
|
|
/*
|
|
Authentic composite:
|
|
Sa: normalized source alpha.
|
|
Da: normalized canvas alpha.
|
|
*/
|
|
Sa=QuantumScale*(double) GetPixelAlpha(source_image,p);
|
|
Da=QuantumScale*(double) 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=(double) 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=MagickSafeReciprocal(alpha);
|
|
pixel=(double) QuantumRange*gamma*(Sca+Dca*(1.0-Sa));
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel);
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
channels=GetPixelChannels(source_image);
|
|
if (p >= (pixels+channels*source_image->columns))
|
|
p=pixels;
|
|
q+=(ptrdiff_t) 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);
|
|
}
|
|
|
|
static MagickBooleanType SaliencyBlendImage(Image *image,
|
|
const Image *source_image,const ssize_t x_offset,const ssize_t y_offset,
|
|
const double iterations,const double residual_threshold,const size_t tick,
|
|
ExceptionInfo *exception)
|
|
{
|
|
Image
|
|
*crop_image,
|
|
*divergent_image,
|
|
*relax_image,
|
|
*residual_image = (Image *) NULL;
|
|
|
|
KernelInfo
|
|
*kernel_info;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue,
|
|
verbose = MagickFalse;
|
|
|
|
RectangleInfo
|
|
crop_info = {
|
|
source_image->columns,
|
|
source_image->rows,
|
|
x_offset,
|
|
y_offset
|
|
};
|
|
|
|
ssize_t
|
|
i;
|
|
|
|
/*
|
|
Saliency blend composite operator.
|
|
*/
|
|
crop_image=CropImage(image,&crop_info,exception);
|
|
if (crop_image == (Image *) NULL)
|
|
return(MagickFalse);
|
|
DisableCompositeClampUnlessSpecified(crop_image);
|
|
divergent_image=BlendDivergentImage(crop_image,source_image,exception);
|
|
if (divergent_image == (Image *) NULL)
|
|
{
|
|
crop_image=DestroyImage(crop_image);
|
|
return(MagickFalse);
|
|
}
|
|
(void) ResetImagePage(crop_image,"0x0+0+0");
|
|
relax_image=BlendMeanImage(crop_image,source_image,exception);
|
|
if (relax_image == (Image *) NULL)
|
|
{
|
|
crop_image=DestroyImage(crop_image);
|
|
divergent_image=DestroyImage(divergent_image);
|
|
return(MagickFalse);
|
|
}
|
|
status=BlendMaskAlphaChannel(crop_image,source_image,exception);
|
|
if (status == MagickFalse)
|
|
{
|
|
crop_image=DestroyImage(crop_image);
|
|
divergent_image=DestroyImage(divergent_image);
|
|
return(MagickFalse);
|
|
}
|
|
residual_image=CloneImage(relax_image,0,0,MagickTrue,exception);
|
|
if (residual_image == (Image *) NULL)
|
|
{
|
|
crop_image=DestroyImage(crop_image);
|
|
relax_image=DestroyImage(relax_image);
|
|
return(MagickFalse);
|
|
}
|
|
/*
|
|
Convolve relaxed image and blur area of interest.
|
|
*/
|
|
kernel_info=AcquireKernelInfo("3x3:0,0.25,0,0.25,0,0.25,0,0.25,0",exception);
|
|
if (kernel_info == (KernelInfo *) NULL)
|
|
{
|
|
crop_image=DestroyImage(crop_image);
|
|
residual_image=DestroyImage(residual_image);
|
|
relax_image=DestroyImage(relax_image);
|
|
return(MagickFalse);
|
|
}
|
|
verbose=IsStringTrue(GetImageArtifact(image,"verbose"));
|
|
if (verbose != MagickFalse)
|
|
(void) FormatLocaleFile(stderr,"saliency blending:\n");
|
|
for (i=0; i < (ssize_t) iterations; i++)
|
|
{
|
|
double
|
|
residual = 1.0;
|
|
|
|
Image
|
|
*convolve_image,
|
|
*sum_image;
|
|
|
|
convolve_image=ConvolveImage(relax_image,kernel_info,exception);
|
|
if (convolve_image == (Image *) NULL)
|
|
break;
|
|
relax_image=DestroyImage(relax_image);
|
|
relax_image=convolve_image;
|
|
sum_image=BlendSumImage(relax_image,divergent_image,1.0,-1.0,exception);
|
|
if (sum_image == (Image *) NULL)
|
|
break;
|
|
relax_image=DestroyImage(relax_image);
|
|
relax_image=sum_image;
|
|
status=CompositeOverImage(relax_image,crop_image,MagickTrue,0,0,exception);
|
|
if (status == MagickFalse)
|
|
break;
|
|
status=BlendRMSEResidual(relax_image,residual_image,&residual,exception);
|
|
if (status == MagickFalse)
|
|
break;
|
|
if ((verbose != MagickFalse) && ((i % MagickMax(tick,1)) == 0))
|
|
(void) FormatLocaleFile(stderr," %g: %g\n",(double) i,(double) residual);
|
|
if (residual < residual_threshold)
|
|
{
|
|
if (verbose != MagickFalse)
|
|
(void) FormatLocaleFile(stderr," %g: %g\n",(double) i,(double)
|
|
residual);
|
|
break;
|
|
}
|
|
residual_image=DestroyImage(residual_image);
|
|
residual_image=CloneImage(relax_image,0,0,MagickTrue,exception);
|
|
if (residual_image == (Image *) NULL)
|
|
break;
|
|
}
|
|
kernel_info=DestroyKernelInfo(kernel_info);
|
|
crop_image=DestroyImage(crop_image);
|
|
divergent_image=DestroyImage(divergent_image);
|
|
residual_image=DestroyImage(residual_image);
|
|
/*
|
|
Composite relaxed over the background image.
|
|
*/
|
|
status=CompositeOverImage(image,relax_image,MagickTrue,x_offset,y_offset,
|
|
exception);
|
|
relax_image=DestroyImage(relax_image);
|
|
return(status);
|
|
}
|
|
|
|
static MagickBooleanType SeamlessBlendImage(Image *image,
|
|
const Image *source_image,const ssize_t x_offset,const ssize_t y_offset,
|
|
const double iterations,const double residual_threshold,const size_t tick,
|
|
ExceptionInfo *exception)
|
|
{
|
|
Image
|
|
*crop_image,
|
|
*foreground_image,
|
|
*mean_image,
|
|
*relax_image,
|
|
*residual_image,
|
|
*sum_image;
|
|
|
|
KernelInfo
|
|
*kernel_info;
|
|
|
|
MagickBooleanType
|
|
status = MagickTrue,
|
|
verbose = MagickFalse;
|
|
|
|
RectangleInfo
|
|
crop_info = {
|
|
source_image->columns,
|
|
source_image->rows,
|
|
x_offset,
|
|
y_offset
|
|
};
|
|
|
|
ssize_t
|
|
i;
|
|
|
|
/*
|
|
Seamless blend composite operator.
|
|
*/
|
|
crop_image=CropImage(image,&crop_info,exception);
|
|
if (crop_image == (Image *) NULL)
|
|
return(MagickFalse);
|
|
DisableCompositeClampUnlessSpecified(crop_image);
|
|
(void) ResetImagePage(crop_image,"0x0+0+0");
|
|
sum_image=BlendSumImage(crop_image,source_image,1.0,-1.0,exception);
|
|
crop_image=DestroyImage(crop_image);
|
|
if (sum_image == (Image *) NULL)
|
|
return(MagickFalse);
|
|
mean_image=BlendMeanImage(sum_image,source_image,exception);
|
|
sum_image=DestroyImage(sum_image);
|
|
if (mean_image == (Image *) NULL)
|
|
return(MagickFalse);
|
|
relax_image=CloneImage(mean_image,0,0,MagickTrue,exception);
|
|
if (relax_image == (Image *) NULL)
|
|
{
|
|
mean_image=DestroyImage(mean_image);
|
|
return(MagickFalse);
|
|
}
|
|
status=BlendMaskAlphaChannel(mean_image,source_image,exception);
|
|
if (status == MagickFalse)
|
|
{
|
|
relax_image=DestroyImage(relax_image);
|
|
mean_image=DestroyImage(mean_image);
|
|
return(MagickFalse);
|
|
}
|
|
residual_image=CloneImage(relax_image,0,0,MagickTrue,exception);
|
|
if (residual_image == (Image *) NULL)
|
|
{
|
|
relax_image=DestroyImage(relax_image);
|
|
mean_image=DestroyImage(mean_image);
|
|
return(MagickFalse);
|
|
}
|
|
/*
|
|
Convolve relaxed image and blur area of interest.
|
|
*/
|
|
kernel_info=AcquireKernelInfo("3x3:0,0.25,0,0.25,0,0.25,0,0.25,0",exception);
|
|
if (kernel_info == (KernelInfo *) NULL)
|
|
{
|
|
residual_image=DestroyImage(residual_image);
|
|
relax_image=DestroyImage(relax_image);
|
|
mean_image=DestroyImage(mean_image);
|
|
return(MagickFalse);
|
|
}
|
|
verbose=IsStringTrue(GetImageArtifact(image,"verbose"));
|
|
if (verbose != MagickFalse)
|
|
(void) FormatLocaleFile(stderr,"seamless blending:\n");
|
|
for (i=0; i < (ssize_t) iterations; i++)
|
|
{
|
|
double
|
|
residual = 1.0;
|
|
|
|
Image
|
|
*convolve_image;
|
|
|
|
convolve_image=ConvolveImage(relax_image,kernel_info,exception);
|
|
if (convolve_image == (Image *) NULL)
|
|
break;
|
|
relax_image=DestroyImage(relax_image);
|
|
relax_image=convolve_image;
|
|
status=CompositeOverImage(relax_image,mean_image,MagickTrue,0,0,exception);
|
|
if (status == MagickFalse)
|
|
break;
|
|
status=BlendRMSEResidual(relax_image,residual_image,&residual,exception);
|
|
if (status == MagickFalse)
|
|
break;
|
|
if ((verbose != MagickFalse) && ((i % MagickMax(tick,1)) == 0))
|
|
(void) FormatLocaleFile(stderr," %g: %g\n",(double) i,(double) residual);
|
|
if (residual < residual_threshold)
|
|
{
|
|
if (verbose != MagickFalse)
|
|
(void) FormatLocaleFile(stderr," %g: %g\n",(double) i,(double)
|
|
residual);
|
|
break;
|
|
}
|
|
if (residual_image != (Image *) NULL)
|
|
residual_image=DestroyImage(residual_image);
|
|
residual_image=CloneImage(relax_image,0,0,MagickTrue,exception);
|
|
if (residual_image == (Image *) NULL)
|
|
break;
|
|
}
|
|
kernel_info=DestroyKernelInfo(kernel_info);
|
|
mean_image=DestroyImage(mean_image);
|
|
residual_image=DestroyImage(residual_image);
|
|
/*
|
|
Composite the foreground image over the background image.
|
|
*/
|
|
foreground_image=BlendSumImage(source_image,relax_image,1.0,1.0,exception);
|
|
relax_image=DestroyImage(relax_image);
|
|
if (foreground_image == (Image *) NULL)
|
|
return(MagickFalse);
|
|
(void) SetImageMask(foreground_image,ReadPixelMask,(const Image *) NULL,
|
|
exception);
|
|
status=CompositeOverImage(image,foreground_image,MagickTrue,x_offset,y_offset,
|
|
exception);
|
|
foreground_image=DestroyImage(foreground_image);
|
|
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;
|
|
|
|
ColorspaceType
|
|
colorspace = HCLColorspace;
|
|
|
|
const char
|
|
*artifact;
|
|
|
|
double
|
|
white_luminance = 10000.0;
|
|
|
|
GeometryInfo
|
|
geometry_info;
|
|
|
|
IlluminantType
|
|
illuminant = D65Illuminant;
|
|
|
|
Image
|
|
*canvas_image,
|
|
*source_image;
|
|
|
|
MagickBooleanType
|
|
clamp,
|
|
compose_sync,
|
|
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);
|
|
assert(composite != (Image *) NULL);
|
|
assert(composite->signature == MagickCoreSignature);
|
|
if (IsEventLogging() != MagickFalse)
|
|
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
|
|
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;
|
|
white_luminance=10000.0;
|
|
artifact=GetImageArtifact(image,"compose:white-luminance");
|
|
if (artifact != (const char *) NULL)
|
|
white_luminance=StringToDouble(artifact,(char **) NULL);
|
|
artifact=GetImageArtifact(image,"compose:illuminant");
|
|
if (artifact != (const char *) NULL)
|
|
{
|
|
ssize_t
|
|
illuminant_type;
|
|
|
|
illuminant_type=ParseCommandOption(MagickIlluminantOptions,MagickFalse,
|
|
artifact);
|
|
if (illuminant_type < 0)
|
|
illuminant=UndefinedIlluminant;
|
|
else
|
|
illuminant=(IlluminantType) illuminant_type;
|
|
}
|
|
artifact=GetImageArtifact(image,"compose:colorspace");
|
|
if (artifact != (const char *) NULL)
|
|
{
|
|
ssize_t
|
|
colorspace_type;
|
|
|
|
colorspace_type=ParseCommandOption(MagickColorspaceOptions,MagickFalse,
|
|
artifact);
|
|
if (colorspace_type < 0)
|
|
colorspace=UndefinedColorspace;
|
|
else
|
|
colorspace=(ColorspaceType) colorspace_type;
|
|
}
|
|
clamp=MagickTrue;
|
|
artifact=GetImageArtifact(image,"compose:clamp");
|
|
if (artifact != (const char *) NULL)
|
|
clamp=IsStringTrue(artifact);
|
|
compose_sync=MagickTrue;
|
|
artifact=GetImageArtifact(image,"compose:sync");
|
|
if (artifact != (const char *) NULL)
|
|
compose_sync=IsStringTrue(artifact);
|
|
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,4)
|
|
#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+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) 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+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) 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,4)
|
|
#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+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) GetPixelChannels(image);
|
|
continue;
|
|
}
|
|
SetPixelAlpha(image,clamp != MagickFalse ?
|
|
ClampPixel(GetPixelIntensity(source_image,p)) :
|
|
ClampToQuantum(GetPixelIntensity(source_image,p)),q);
|
|
p+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) 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 & BlendPixelTrait) == 0)
|
|
(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;
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (const char *) NULL)
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
if ((flags & WidthValue) == 0)
|
|
{
|
|
(void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
|
|
"InvalidSetting","'%s' '%s'","compose:args",artifact);
|
|
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 Blurring.
|
|
|
|
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+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
continue;
|
|
}
|
|
if (fabs(angle_range) > MagickEpsilon)
|
|
{
|
|
MagickRealType
|
|
angle;
|
|
|
|
angle=angle_start+angle_range*QuantumScale*(double)
|
|
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*(double) GetPixelRed(source_image,p),
|
|
blur.y1*QuantumScale*(double) GetPixelGreen(source_image,p),
|
|
blur.x2*QuantumScale*(double) GetPixelRed(source_image,p),
|
|
blur.y2*QuantumScale*(double) GetPixelGreen(source_image,p) );
|
|
(void) ResamplePixelColor(resample_filter,(double) x_offset+x,
|
|
(double) y_offset+y,&pixel,exception);
|
|
SetPixelViaPixelInfo(canvas_image,&pixel,q);
|
|
p+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) 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;
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
flags=ParseGeometry(artifact,&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+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
continue;
|
|
}
|
|
/*
|
|
Displace the offset.
|
|
*/
|
|
offset.x=(double) (horizontal_scale*((double) 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*((double) 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*(double) GetPixelAlpha(source_image,p));
|
|
SetPixelViaPixelInfo(canvas_image,&pixel,q);
|
|
p+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
q+=(ptrdiff_t) 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.
|
|
*/
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&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;
|
|
if ((canvas_dissolve+MagickEpsilon) > 1.0)
|
|
canvas_dissolve=1.0;
|
|
}
|
|
break;
|
|
}
|
|
case BlendCompositeOp:
|
|
{
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&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 SaliencyBlendCompositeOp:
|
|
{
|
|
double
|
|
residual_threshold = 0.0002,
|
|
iterations = 400.0;
|
|
|
|
size_t
|
|
tick = 100;
|
|
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
iterations=geometry_info.rho;
|
|
if ((flags & SigmaValue) != 0)
|
|
residual_threshold=geometry_info.sigma;
|
|
if ((flags & XiValue) != 0)
|
|
tick=(size_t) geometry_info.xi;
|
|
}
|
|
status=SaliencyBlendImage(image,composite,x_offset,y_offset,iterations,
|
|
residual_threshold,tick,exception);
|
|
source_image=DestroyImage(source_image);
|
|
return(status);
|
|
}
|
|
case SeamlessBlendCompositeOp:
|
|
{
|
|
double
|
|
residual_threshold = 0.0002,
|
|
iterations = 400.0;
|
|
|
|
size_t
|
|
tick = 100;
|
|
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
iterations=geometry_info.rho;
|
|
if ((flags & SigmaValue) != 0)
|
|
residual_threshold=geometry_info.sigma;
|
|
if ((flags & XiValue) != 0)
|
|
tick=(size_t) geometry_info.xi;
|
|
}
|
|
status=SeamlessBlendImage(image,composite,x_offset,y_offset,iterations,
|
|
residual_threshold,tick,exception);
|
|
source_image=DestroyImage(source_image);
|
|
return(status);
|
|
}
|
|
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);
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
if (flags == NoValue)
|
|
(void) ThrowMagickException(exception,GetMagickModule(),OptionError,
|
|
"InvalidGeometry","`%s'",artifact);
|
|
}
|
|
break;
|
|
}
|
|
case ModulateCompositeOp:
|
|
{
|
|
/*
|
|
Determine the luma and chroma scale.
|
|
*/
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
percent_luma=geometry_info.rho;
|
|
if ((flags & SigmaValue) != 0)
|
|
percent_chroma=geometry_info.sigma;
|
|
}
|
|
break;
|
|
}
|
|
case ThresholdCompositeOp:
|
|
{
|
|
/*
|
|
Determine the amount and threshold.
|
|
*/
|
|
artifact=GetImageArtifact(image,"compose:args");
|
|
if (artifact != (char *) NULL)
|
|
{
|
|
flags=ParseGeometry(artifact,&geometry_info);
|
|
amount=geometry_info.rho;
|
|
threshold=geometry_info.sigma;
|
|
if ((flags & SigmaValue) == 0)
|
|
threshold=0.05f;
|
|
}
|
|
threshold*=(double) 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
|
|
*magick_restrict p,
|
|
*pixels;
|
|
|
|
MagickRealType
|
|
blue = 0.0,
|
|
chroma = 0.0,
|
|
green = 0.0,
|
|
hue = 0.0,
|
|
luma = 0.0,
|
|
red = 0.0;
|
|
|
|
PixelInfo
|
|
canvas_pixel,
|
|
source_pixel;
|
|
|
|
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,
|
|
CastDoubleToSsizeT((double) y-y_offset),source_image->columns,1,
|
|
exception);
|
|
if (p == (const Quantum *) NULL)
|
|
{
|
|
status=MagickFalse;
|
|
continue;
|
|
}
|
|
pixels=p;
|
|
if (x_offset < 0)
|
|
p-=(ptrdiff_t) CastDoubleToSsizeT((double) x_offset*
|
|
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 = 0.0;
|
|
|
|
MagickRealType
|
|
alpha = 0.0,
|
|
blend = 0.0,
|
|
D = 0.0,
|
|
Da = 0.0,
|
|
Dc = 0.0,
|
|
Dca = 0.0,
|
|
Di = 0.0,
|
|
S = 0.0,
|
|
Sa = 0.0,
|
|
Sc = 0.0,
|
|
Sca = 0.0,
|
|
Si = 0.0;
|
|
|
|
size_t
|
|
channels;
|
|
|
|
ssize_t
|
|
i;
|
|
|
|
if (clip_to_self != MagickFalse)
|
|
{
|
|
if (x < x_offset)
|
|
{
|
|
q+=(ptrdiff_t) 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,
|
|
CastDoubleToSsizeT((double) x-x_offset),
|
|
CastDoubleToSsizeT((double) y-y_offset),source,exception);
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
{
|
|
MagickRealType
|
|
pixel = 0.0;
|
|
|
|
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:
|
|
{
|
|
if (channel == AlphaPixelChannel)
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
else
|
|
pixel=0.0;
|
|
break;
|
|
}
|
|
case BlendCompositeOp:
|
|
case DissolveCompositeOp:
|
|
{
|
|
if (channel == AlphaPixelChannel)
|
|
pixel=canvas_dissolve*(double) 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+=(ptrdiff_t) GetPixelChannels(image);
|
|
continue;
|
|
}
|
|
/*
|
|
Authentic composite:
|
|
Sa: normalized source alpha.
|
|
Da: normalized canvas alpha.
|
|
*/
|
|
Sa=QuantumScale*(double) GetPixelAlpha(source_image,p);
|
|
Da=QuantumScale*(double) GetPixelAlpha(image,q);
|
|
switch (compose)
|
|
{
|
|
case BumpmapCompositeOp:
|
|
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:
|
|
{
|
|
Si=GetPixelIntensity(source_image,p);
|
|
GetPixelInfoPixel(source_image,p,&source_pixel);
|
|
GetPixelInfoPixel(image,q,&canvas_pixel);
|
|
break;
|
|
}
|
|
case BumpmapCompositeOp:
|
|
case CopyAlphaCompositeOp:
|
|
case DarkenIntensityCompositeOp:
|
|
case LightenIntensityCompositeOp:
|
|
{
|
|
Si=GetPixelIntensity(source_image,p);
|
|
Di=GetPixelIntensity(image,q);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
|
|
{
|
|
MagickRealType
|
|
pixel = 0.0,
|
|
sans = 0.0;
|
|
|
|
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=(double) 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=(double) QuantumRange*Da;
|
|
break;
|
|
}
|
|
case BumpmapCompositeOp:
|
|
{
|
|
pixel=Si*Da;
|
|
break;
|
|
}
|
|
case ChangeMaskCompositeOp:
|
|
{
|
|
if (IsFuzzyEquivalencePixel(source_image,p,image,q) != MagickFalse)
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
else
|
|
pixel=(double) QuantumRange*Da;
|
|
break;
|
|
}
|
|
case ClearCompositeOp:
|
|
{
|
|
pixel=(MagickRealType) TransparentAlpha;
|
|
break;
|
|
}
|
|
case ColorizeCompositeOp:
|
|
case HueCompositeOp:
|
|
case LuminizeCompositeOp:
|
|
case RMSECompositeOp:
|
|
case SaturateCompositeOp:
|
|
{
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=(double) QuantumRange*Da;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=(double) QuantumRange*Sa;
|
|
break;
|
|
}
|
|
if (Sa < Da)
|
|
{
|
|
pixel=(double) QuantumRange*Da;
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*Sa;
|
|
break;
|
|
}
|
|
case CopyAlphaCompositeOp:
|
|
{
|
|
if (source_image->alpha_trait == UndefinedPixelTrait)
|
|
pixel=Si;
|
|
else
|
|
pixel=(double) QuantumRange*Sa;
|
|
break;
|
|
}
|
|
case BlurCompositeOp:
|
|
case CopyCompositeOp:
|
|
case DisplaceCompositeOp:
|
|
case DistortCompositeOp:
|
|
case DstAtopCompositeOp:
|
|
case ReplaceCompositeOp:
|
|
case SrcCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*Sa;
|
|
break;
|
|
}
|
|
case DarkenIntensityCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=Si < Di ? Sa : Da;
|
|
break;
|
|
}
|
|
pixel=Sa*Si < Da*Di ? Sa : Da;
|
|
break;
|
|
}
|
|
case DifferenceCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*fabs((double) (Sa-Da));
|
|
break;
|
|
}
|
|
case FreezeCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(1.0-(1.0-Sa)*(1.0-Sa)*
|
|
MagickSafeReciprocal(Da));
|
|
if (pixel < 0.0)
|
|
pixel=0.0;
|
|
break;
|
|
}
|
|
case InterpolateCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(0.5-0.25*cos(MagickPI*Sa)-0.25*
|
|
cos(MagickPI*Da));
|
|
break;
|
|
}
|
|
case LightenIntensityCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=Si > Di ? Sa : Da;
|
|
break;
|
|
}
|
|
pixel=Sa*Si > Da*Di ? Sa : Da;
|
|
break;
|
|
}
|
|
case ModulateCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*Da;
|
|
break;
|
|
}
|
|
case MultiplyCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(double) QuantumRange*Sa*Da;
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*alpha;
|
|
break;
|
|
}
|
|
case NegateCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*((1.0-Sa-Da));
|
|
break;
|
|
}
|
|
case ReflectCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Sa*Sa*
|
|
MagickSafeReciprocal(1.0-Da));
|
|
if (pixel > (double) QuantumRange)
|
|
pixel=(double) QuantumRange;
|
|
break;
|
|
}
|
|
case StampCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Sa+Da*Da-1.0);
|
|
break;
|
|
}
|
|
case StereoCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Sa+Da)/2;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
pixel=(double) 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;
|
|
switch (compose)
|
|
{
|
|
case DarkenCompositeOp:
|
|
case LightenCompositeOp:
|
|
case ModulusSubtractCompositeOp:
|
|
{
|
|
gamma=MagickSafeReciprocal(1.0-alpha);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
gamma=MagickSafeReciprocal(alpha);
|
|
break;
|
|
}
|
|
}
|
|
pixel=Dc;
|
|
switch (compose)
|
|
{
|
|
case AlphaCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*Sa;
|
|
break;
|
|
}
|
|
case AtopCompositeOp:
|
|
case SrcAtopCompositeOp:
|
|
{
|
|
pixel=(double) 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:
|
|
{
|
|
pixel=(double) QuantumRange*Sca;
|
|
break;
|
|
}
|
|
case BlurCompositeOp:
|
|
case DisplaceCompositeOp:
|
|
case DistortCompositeOp:
|
|
case SrcCompositeOp:
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
case BumpmapCompositeOp:
|
|
{
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
pixel=(QuantumScale*Si)*Dc;
|
|
break;
|
|
}
|
|
case ChangeMaskCompositeOp:
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
case ClearCompositeOp:
|
|
{
|
|
pixel=0.0;
|
|
break;
|
|
}
|
|
case ColorBurnCompositeOp:
|
|
{
|
|
D=(Da > 0.0) ? RoundToUnity(Dca/Da) : 0.0;
|
|
S=(Sa > 0.0) ? RoundToUnity(Sca/Sa) : 0.0;
|
|
if (S <= 0.0)
|
|
blend=0.0;
|
|
else
|
|
blend=1.0-(1.0-D)/S;
|
|
pixel=(double) QuantumRange*gamma*RoundToUnity(Sa*Da*
|
|
RoundToUnity(blend)+Sa*(1.0-Da)*S+Da*(1.0-Sa)*D);
|
|
break;
|
|
}
|
|
case ColorDodgeCompositeOp:
|
|
{
|
|
if (Sa > 0.0)
|
|
S=RoundToUnity(Sca/Sa);
|
|
else
|
|
S=0.0;
|
|
if (Da > 0.0)
|
|
D=RoundToUnity(Dca/Da);
|
|
else
|
|
D=0.0;
|
|
if (S >= 1.0)
|
|
blend=1.0;
|
|
else
|
|
if (D <= 0.0)
|
|
blend=0.0;
|
|
else
|
|
{
|
|
if ((1.0-S) <= 0.0)
|
|
blend=1.0;
|
|
else
|
|
blend=MagickMin(1.0,D/(1.0-S));
|
|
}
|
|
pixel=(double) QuantumRange*gamma*(Sa*Da*blend+Sca*(1.0-Da)+Dca*
|
|
(1.0-Sa));
|
|
break;
|
|
}
|
|
case ColorizeCompositeOp:
|
|
{
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
ConvertRGBToGeneric(colorspace,(double) canvas_pixel.red,
|
|
(double) canvas_pixel.green,(double) canvas_pixel.blue,
|
|
white_luminance,illuminant,&sans,&sans,&luma);
|
|
ConvertRGBToGeneric(colorspace,(double) source_pixel.red,
|
|
(double) source_pixel.green,(double) source_pixel.blue,
|
|
white_luminance,illuminant,&hue,&chroma,&sans);
|
|
ConvertGenericToRGB(colorspace,hue,chroma,luma,
|
|
white_luminance,illuminant,&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:
|
|
case DstCompositeOp:
|
|
{
|
|
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:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=RoundToUnity(MagickMin(Sca,Dca)+Sca*(1.0-Da)+Dca*
|
|
(1.0-Sa));
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*RoundToUnity(MagickMin(Sca,Dca)+Sca*
|
|
(1.0-Da)+Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
case DarkenIntensityCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=RoundToUnity(MagickMin(Sca,Dca)+Sca*(1.0-Di)+Dca*
|
|
(1.0-Si));
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*RoundToUnity(MagickMin(Sca,Dca)+Sca*
|
|
(1.0-Di)+Dca*(1.0-Si));
|
|
break;
|
|
}
|
|
case DifferenceCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(double) QuantumRange*RoundToUnity(fabs((double) Sc-
|
|
(double) Dc));
|
|
break;
|
|
}
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
pixel=(double) QuantumRange*RoundToUnity(fabs(S-D));
|
|
break;
|
|
}
|
|
case DissolveCompositeOp:
|
|
{
|
|
pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
|
|
canvas_dissolve*Da*Dc+canvas_dissolve*Da*Dc);
|
|
break;
|
|
}
|
|
case DivideDstCompositeOp:
|
|
{
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
if (S <= 0.0)
|
|
blend=1.0;
|
|
else
|
|
blend=MagickMin(1.0,D/S);
|
|
pixel=(double) QuantumRange*RoundToUnity(Sca*(1.0-Da)+Dca*(1.0-Sa)+
|
|
Sa*Da*blend);
|
|
break;
|
|
}
|
|
case DivideSrcCompositeOp:
|
|
{
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
if (D <= 0.0)
|
|
blend=1.0;
|
|
else
|
|
blend=MagickMin(1.0,S/D);
|
|
pixel=(double) QuantumRange*RoundToUnity(Sca*(1.0-Da)+Dca*(1.0-Sa)+
|
|
Sa*Da*blend);
|
|
break;
|
|
}
|
|
case DstAtopCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Dca*Sa+Sca*(1.0-Da));
|
|
break;
|
|
}
|
|
case DstInCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(Dca*Sa);
|
|
break;
|
|
}
|
|
case DstOutCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
case DstOverCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(Dca+Sca*(1.0-Da));
|
|
break;
|
|
}
|
|
case ExclusionCompositeOp:
|
|
{
|
|
S=(Sa > 0.0) ? Sca/Sa : 0.0;
|
|
D=(Da > 0.0) ? Dca/Da : 0.0;
|
|
blend=RoundToUnity(S+D-2.0*S*D);
|
|
pixel=(double) QuantumRange*RoundToUnity((blend*Sa+D*(1.0-Sa))*
|
|
(Sa+Da-Sa*Da));
|
|
break;
|
|
}
|
|
case FreezeCompositeOp:
|
|
{
|
|
if (Dca != 0.0)
|
|
blend=1.0-(1.0-Sca)*(1.0-Sca)/Dca;
|
|
else
|
|
blend=0.0;
|
|
pixel=(double) QuantumRange*gamma*RoundToUnity(blend);
|
|
break;
|
|
}
|
|
case HardLightCompositeOp:
|
|
{
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
if (S <= 0.5)
|
|
blend=2.0*S*D;
|
|
else
|
|
blend=1.0-2.0*(1.0-S)*(1.0-D);
|
|
pixel=(double) QuantumRange*gamma*RoundToUnity((1.0-Da)*Sca+
|
|
(1.0-Sa)*Dca+blend*Sa*Da);
|
|
break;
|
|
}
|
|
case HardMixCompositeOp:
|
|
{
|
|
if (Sca < 0.5)
|
|
{
|
|
if ((2.0*Sca) == 0.0)
|
|
blend=0.0;
|
|
else
|
|
blend=1.0-(1.0-Dca)/(2.0*Sca);
|
|
}
|
|
else
|
|
{
|
|
if ((1.0-((2.0*Sca)-1.0)) == 0.0)
|
|
blend=1.0;
|
|
else
|
|
blend=Dca/(1.0-(2.0*Sca-1.0));
|
|
}
|
|
pixel=gamma*((blend < 0.5) ? 0.0 : (double) QuantumRange);
|
|
break;
|
|
}
|
|
case HueCompositeOp:
|
|
{
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
ConvertRGBToGeneric(colorspace,(double) canvas_pixel.red,
|
|
(double) canvas_pixel.green,(double) canvas_pixel.blue,
|
|
white_luminance,illuminant,&hue,&chroma,&luma);
|
|
ConvertRGBToGeneric(colorspace,(double) source_pixel.red,
|
|
(double) source_pixel.green,(double) source_pixel.blue,
|
|
white_luminance,illuminant,&hue,&sans,&sans);
|
|
ConvertGenericToRGB(colorspace,hue,chroma,luma,
|
|
white_luminance,illuminant,&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=(double) QuantumRange*(Sca*Da);
|
|
break;
|
|
}
|
|
case InterpolateCompositeOp:
|
|
{
|
|
pixel=(double) 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=(double) QuantumRange*(Sca+Dca-Sa*Da);
|
|
break;
|
|
}
|
|
case LinearDodgeCompositeOp:
|
|
{
|
|
pixel=gamma*(Sa*Sc+Da*Dc);
|
|
break;
|
|
}
|
|
case LinearLightCompositeOp:
|
|
{
|
|
/*
|
|
Linear Light (Adobe standard):
|
|
f(Sc, Dc) = Dc + 2*Sc - 1
|
|
Applied in linear (HDRI) space with clamping.
|
|
*/
|
|
D=(Da > 0.0) ? Dca/Da : 0.0,
|
|
S=(Sa > 0.0) ? Sca/Sa : 0.0;
|
|
pixel=(double) QuantumRange*gamma*(RoundToUnity(D+2.0*S-1.0)*Da);
|
|
break;
|
|
}
|
|
case LightenCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=MagickMax(Sc,Dc);
|
|
break;
|
|
}
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
pixel=(double) QuantumRange*RoundToUnity(Sca*(1.0-Da)+Dca*(1.0-Sa)+
|
|
MagickMax(S,D)*Sa*Da);
|
|
break;
|
|
}
|
|
case LightenIntensityCompositeOp:
|
|
{
|
|
pixel=Si > Di ? Sc : Dc;
|
|
break;
|
|
}
|
|
case LuminizeCompositeOp:
|
|
{
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
ConvertRGBToGeneric(colorspace,(double) canvas_pixel.red,
|
|
(double) canvas_pixel.green,(double) canvas_pixel.blue,
|
|
white_luminance,illuminant,&hue,&chroma,&luma);
|
|
ConvertRGBToGeneric(colorspace,(double) source_pixel.red,
|
|
(double) source_pixel.green,(double) source_pixel.blue,
|
|
white_luminance,illuminant,&sans,&sans,&luma);
|
|
ConvertGenericToRGB(colorspace,hue,chroma,luma,
|
|
white_luminance,illuminant,&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)
|
|
*/
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=geometry_info.rho*Sc*Dc+geometry_info.sigma*Sc+
|
|
geometry_info.xi*Dc+geometry_info.psi;
|
|
break;
|
|
}
|
|
pixel=(double) 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:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=Dc-Sc;
|
|
break;
|
|
}
|
|
pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
|
|
break;
|
|
}
|
|
case MinusSrcCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=Sc-Dc;
|
|
break;
|
|
}
|
|
pixel=gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
|
|
break;
|
|
}
|
|
case ModulateCompositeOp:
|
|
{
|
|
ssize_t
|
|
offset;
|
|
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
offset=(ssize_t) (Si-midpoint);
|
|
if (offset == 0)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
ConvertRGBToGeneric(colorspace,(double) canvas_pixel.red,
|
|
(double) canvas_pixel.green,(double) canvas_pixel.blue,
|
|
white_luminance,illuminant,&hue,&chroma,&luma);
|
|
luma+=(0.01*percent_luma*offset)/midpoint;
|
|
chroma*=0.01*percent_chroma;
|
|
ConvertGenericToRGB(colorspace,hue,chroma,luma,
|
|
white_luminance,illuminant,&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 (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(Quantum) QuantumRange*((Sc+Dc)-floor(Sc+Dc));
|
|
break;
|
|
}
|
|
pixel=(Quantum) QuantumRange*((Sca+Dca)-floor(Sca+Dca));
|
|
break;
|
|
}
|
|
case ModulusSubtractCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(Quantum) QuantumRange*((Sc-Dc)-floor(Sc-Dc));
|
|
break;
|
|
}
|
|
pixel=(Quantum) QuantumRange*((Sca-Dca)-floor(Sca-Dca));
|
|
break;
|
|
}
|
|
case MultiplyCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(double) QuantumRange*Sc*Dc;
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
case NegateCompositeOp:
|
|
{
|
|
D=(Da > 0.0) ? Dca/Da : 0.0;
|
|
S=(Sa > 0.0) ? Sca/Sa : 0.0;
|
|
pixel=(double) QuantumRange*((1.0-fabs(1.0-S-D))*Da);
|
|
break;
|
|
}
|
|
case NoCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*Dca;
|
|
break;
|
|
}
|
|
case OutCompositeOp:
|
|
case SrcOutCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Sca*(1.0-Da));
|
|
break;
|
|
}
|
|
case OverCompositeOp:
|
|
case SrcOverCompositeOp:
|
|
{
|
|
if ((Sa+Da*(1.0-Sa)) <= MagickEpsilon)
|
|
pixel=0.0;
|
|
else
|
|
pixel=(double) QuantumRange*gamma*((Sca+Dca*(1.0-Sa))/
|
|
(Sa+Da*(1.0-Sa)));
|
|
break;
|
|
}
|
|
case OverlayCompositeOp:
|
|
{
|
|
if ((2.0*Dca) < Da)
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+
|
|
Sca*(1.0-Da));
|
|
break;
|
|
}
|
|
pixel=(double) QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*
|
|
(1.0-Sa)+Sca*(1.0-Da));
|
|
break;
|
|
}
|
|
case PegtopLightCompositeOp:
|
|
{
|
|
if (fabs((double) Da) < MagickEpsilon)
|
|
{
|
|
pixel=(double) QuantumRange*gamma*Sca;
|
|
break;
|
|
}
|
|
if (RoundToUnity(Sca) <= 0.5)
|
|
blend=RoundToUnity(Dca/Da)-(1.0-2.0*RoundToUnity(Sca))*
|
|
RoundToUnity(Dca/Da)*(1.0-RoundToUnity(Dca/Da));
|
|
else
|
|
{
|
|
if (RoundToUnity(Dca/Da) <= 0.25)
|
|
blend=((16.0*RoundToUnity(Dca/Da)-12.0)*RoundToUnity(Dca/Da)+
|
|
4.0)*RoundToUnity(Dca/Da);
|
|
else
|
|
blend=sqrt(RoundToUnity(Dca/Da));
|
|
blend=RoundToUnity(Dca/Da)+(2.0*RoundToUnity(Sca)-1.0)*
|
|
(blend-RoundToUnity(Dca/Da));
|
|
}
|
|
pixel=(double) QuantumRange*gamma*(RoundToUnity(blend)*Da*Sa+Dca*
|
|
(1.0-Sa));
|
|
break;
|
|
}
|
|
case PinLightCompositeOp:
|
|
{
|
|
/*
|
|
Adobe Pin Light (colors in [0,1]):
|
|
|
|
if (Cs <= 0.5)
|
|
f = min(Cd, 2*Cs);
|
|
else
|
|
f = max(Cd, 2*Cs - 1);
|
|
*/
|
|
D=(Da > 0.0) ? RoundToUnity(Dca/Da) : 0.0;
|
|
S=(Sa > 0.0) ? RoundToUnity(Sca/Sa) : 0.0;
|
|
if (S <= 0.5)
|
|
blend=MagickMin(D,2.0*S);
|
|
else
|
|
blend=MagickMax(D,2.0*S-1.0);
|
|
pixel=(double) QuantumRange*gamma*(blend*RoundToUnity(Sa+Da-Sa*Da));
|
|
break;
|
|
}
|
|
case PlusCompositeOp:
|
|
{
|
|
D=(Da > 0.0) ? Dca/Da : 0.0;
|
|
S=(Sa > 0.0) ? Sca/Sa : 0.0;
|
|
pixel=(double) QuantumRange*(RoundToUnity(Sa+Da-Sa*Da)*
|
|
RoundToUnity(S+D));
|
|
break;
|
|
}
|
|
case ReflectCompositeOp:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
if (Dc < 1.0)
|
|
blend=(Sc*Sc)/(1.0-Dc);
|
|
else
|
|
blend=1.0;
|
|
pixel=(double) QuantumRange*RoundToUnity(blend);
|
|
break;
|
|
}
|
|
if (Sa > 0.0)
|
|
S=Sca/Sa;
|
|
else
|
|
S=0.0;
|
|
if (Da > 0.0)
|
|
D=Dca/Da;
|
|
else
|
|
D=0.0;
|
|
if (D < 1.0)
|
|
blend=(S*S)/(1.0-D);
|
|
else
|
|
blend=1.0;
|
|
pixel=(double) QuantumRange*RoundToUnity((Sa+Da-Sa*Da)*blend);
|
|
break;
|
|
}
|
|
case RMSECompositeOp:
|
|
{
|
|
double
|
|
gray;
|
|
|
|
if (fabs((double) QuantumRange*Sa-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) 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-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Dc;
|
|
break;
|
|
}
|
|
if (fabs((double) QuantumRange*Da-(double) TransparentAlpha) < MagickEpsilon)
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
ConvertRGBToGeneric(colorspace,(double) canvas_pixel.red,
|
|
(double) canvas_pixel.green,(double) canvas_pixel.blue,
|
|
white_luminance,illuminant,&hue,&chroma,&luma);
|
|
ConvertRGBToGeneric(colorspace,(double) source_pixel.red,
|
|
(double) source_pixel.green,(double) source_pixel.blue,
|
|
white_luminance,illuminant,&sans,&chroma,&sans);
|
|
ConvertGenericToRGB(colorspace,hue,chroma,luma,
|
|
white_luminance,illuminant,&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:
|
|
{
|
|
if (compose_sync == MagickFalse)
|
|
{
|
|
pixel=(double) QuantumRange*RoundToUnity(Sc+Dc-Sc*Dc);
|
|
break;
|
|
}
|
|
if (Sa > 0.0)
|
|
S=Sca/Sa;
|
|
else
|
|
S=0.0;
|
|
if (Da > 0.0)
|
|
D=Dca/Da;
|
|
else
|
|
D=0.0;
|
|
if ((Sa+Da-Sa*Da) > 0.0)
|
|
pixel=(double) QuantumRange*RoundToUnity((Sa+Da-Sa*Da)*(S+D-S*D));
|
|
else
|
|
pixel=0.0;
|
|
break;
|
|
}
|
|
case SoftBurnCompositeOp:
|
|
{
|
|
if (RoundToUnity(Dca) <= 0.0)
|
|
blend = 0.0;
|
|
else
|
|
if (RoundToUnity(Sca) >= 1.0)
|
|
blend = 1.0;
|
|
else
|
|
blend=1.0-MagickMin(1.0,(1.0-RoundToUnity(Dca))/
|
|
RoundToUnity(Sca));
|
|
pixel=(double) QuantumRange*gamma*RoundToUnity(blend);
|
|
break;
|
|
}
|
|
case SoftDodgeCompositeOp:
|
|
{
|
|
if (RoundToUnity(Sca) <= 0.0)
|
|
blend=RoundToUnity(Dca);
|
|
else
|
|
if (RoundToUnity(Dca) >= 1.0)
|
|
blend=1.0;
|
|
else
|
|
blend=MagickMin(1.0,RoundToUnity(Dca)/(1.0-RoundToUnity(Sca)));
|
|
pixel=(double) QuantumRange*gamma*RoundToUnity(blend);
|
|
break;
|
|
}
|
|
case SoftLightCompositeOp:
|
|
{
|
|
if (RoundToUnity(Sca) <= 0.5)
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(RoundToUnity(Dca)*Sa+Da*
|
|
(RoundToUnity(Dca)-(1.0-2.0*RoundToUnity(Sca))*
|
|
RoundToUnity(Dca)*(1.0-RoundToUnity(Dca)))+RoundToUnity(Sca)*
|
|
(1.0-Da)+RoundToUnity(Dca)*(1.0-Sa));
|
|
break;
|
|
}
|
|
if (RoundToUnity(Dca) > 0.25)
|
|
blend=sqrt(RoundToUnity(Dca));
|
|
else
|
|
blend=((16.0*RoundToUnity(Dca)-12.0)*RoundToUnity(Dca)+4.0)*
|
|
RoundToUnity(Dca);
|
|
pixel=(double) QuantumRange*gamma*(RoundToUnity(Dca)*Sa+Da*
|
|
(RoundToUnity(Dca)+(2.0*RoundToUnity(Sca)-1.0)*
|
|
(RoundToUnity(blend)-RoundToUnity(Dca)))+RoundToUnity(Sca)*
|
|
(1.0-Da)+RoundToUnity(Dca)*(1.0-Sa));
|
|
break;
|
|
}
|
|
case StampCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*RoundToUnity(Sca+Dca-1.0);
|
|
break;
|
|
}
|
|
case StereoCompositeOp:
|
|
{
|
|
if (channel == RedPixelChannel)
|
|
pixel=(MagickRealType) GetPixelRed(source_image,p);
|
|
break;
|
|
}
|
|
case ThresholdCompositeOp:
|
|
{
|
|
S=(Sa > 0.0) ? (Sca/Sa) : 0.0;
|
|
D=(Da > 0.0) ? (Dca/Da) : 0.0;
|
|
if (fabs(2.0*(S-D)) < threshold)
|
|
blend=D;
|
|
else
|
|
blend=D+(S-D)*amount;
|
|
pixel=(double) QuantumRange*(Sa*Da*RoundToUnity(blend)+Sca*
|
|
(1.0-Da)+Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
case VividLightCompositeOp:
|
|
{
|
|
if ((fabs((double) Sa) < MagickEpsilon) ||
|
|
(fabs((double) Da) < MagickEpsilon))
|
|
{
|
|
pixel=(double) QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*
|
|
(1.0-Sa));
|
|
break;
|
|
}
|
|
if (RoundToUnity(Sca/Sa) <= 0.0)
|
|
blend=0.0;
|
|
else
|
|
if (RoundToUnity(Sca/Sa) < 0.5)
|
|
blend=1.0-(1.0-RoundToUnity(Dca/Da))/(2.0*RoundToUnity(Sca/Sa));
|
|
else
|
|
if (RoundToUnity(Sca/Sa) < 1.0)
|
|
blend=RoundToUnity(Dca/Da)/(2.0*(1.0-RoundToUnity(Sca/Sa)));
|
|
else
|
|
blend=1.0;
|
|
pixel=(double) QuantumRange*gamma*(Sa*Da*RoundToUnity(blend)+Sca*
|
|
(1.0-Da)+Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
case XorCompositeOp:
|
|
{
|
|
pixel=(double) QuantumRange*(Sca*(1.0-Da)+Dca*(1.0-Sa));
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
pixel=Sc;
|
|
break;
|
|
}
|
|
}
|
|
q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel);
|
|
}
|
|
p+=(ptrdiff_t) GetPixelChannels(source_image);
|
|
channels=GetPixelChannels(source_image);
|
|
if (p >= (pixels+channels*source_image->columns))
|
|
p=pixels;
|
|
q+=(ptrdiff_t) 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);
|
|
assert(image->signature == MagickCoreSignature);
|
|
if (IsEventLogging() != MagickFalse)
|
|
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
|
|
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,2)
|
|
#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) % (ssize_t) 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-(size_t) 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+=(ptrdiff_t) GetPixelChannels(texture_image);
|
|
q+=(ptrdiff_t) 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);
|
|
}
|