| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % 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 % |
| % John Cristy % |
| % July 1992 % |
| % % |
| % % |
| % Copyright 1999-2011 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 % |
| % % |
| % http://www.imagemagick.org/script/license.php % |
| % % |
| % Unless required by applicable law or agreed to in writing, software % |
| % distributed under the License is distributed on an "AS IS" BASIS, % |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
| % See the License for the specific language governing permissions and % |
| % limitations under the License. % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % |
| % |
| */ |
| |
| /* |
| Include declarations. |
| */ |
| #include "MagickCore/studio.h" |
| #include "MagickCore/artifact.h" |
| #include "MagickCore/cache.h" |
| #include "MagickCore/cache-view.h" |
| #include "MagickCore/client.h" |
| #include "MagickCore/color.h" |
| #include "MagickCore/color-private.h" |
| #include "MagickCore/colorspace.h" |
| #include "MagickCore/colorspace-private.h" |
| #include "MagickCore/composite.h" |
| #include "MagickCore/composite-private.h" |
| #include "MagickCore/constitute.h" |
| #include "MagickCore/draw.h" |
| #include "MagickCore/fx.h" |
| #include "MagickCore/gem.h" |
| #include "MagickCore/geometry.h" |
| #include "MagickCore/image.h" |
| #include "MagickCore/image-private.h" |
| #include "MagickCore/list.h" |
| #include "MagickCore/log.h" |
| #include "MagickCore/monitor.h" |
| #include "MagickCore/monitor-private.h" |
| #include "MagickCore/memory_.h" |
| #include "MagickCore/option.h" |
| #include "MagickCore/pixel-accessor.h" |
| #include "MagickCore/property.h" |
| #include "MagickCore/quantum.h" |
| #include "MagickCore/resample.h" |
| #include "MagickCore/resource_.h" |
| #include "MagickCore/string_.h" |
| #include "MagickCore/thread-private.h" |
| #include "MagickCore/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 CompositeOperator compose,Image *composite_image, |
| % const ssize_t x_offset,const ssize_t y_offset) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the destination image, modified by he composition |
| % |
| % 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 composite_image: the composite (source) image. |
| % |
| % 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 'composite_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 "compose:outside-overlay" |
| % Modify how the composition is to effect areas not directly covered |
| % by the 'composite_image' at the offset given. Normally this is |
| % dependant on the 'compose' method, especially Duff-Porter methods. |
| % |
| % If set to "false" then disable all normal handling of pixels not |
| % covered by the composite_image. Typically used for repeated tiling |
| % of the composite_image by the calling API. |
| % |
| % Previous to IM v6.5.3-3 this was called "modify-outside-overlay" |
| % |
| */ |
| |
| static inline double MagickMin(const double x,const double y) |
| { |
| if (x < y) |
| return(x); |
| return(y); |
| } |
| static inline double MagickMax(const double x,const double y) |
| { |
| if (x > y) |
| return(x); |
| return(y); |
| } |
| |
| /* |
| Programmers notes on SVG specification. |
| |
| A Composition is defined by... |
| Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors |
| Blending areas : X = 1 for area of overlap ie: f(Sc,Dc) |
| Y = 1 for source preserved |
| Z = 1 for destination preserved |
| |
| Conversion to transparency (then optimized) |
| Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa) |
| Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa) |
| |
| Where... |
| Sca = Sc*Sa normalized Source color divided by Source alpha |
| Dca = Dc*Da normalized Dest color divided by Dest alpha |
| Dc' = Dca'/Da' the desired color value for this channel. |
| |
| Da' in in the follow formula as 'gamma' The resulting alpla value. |
| |
| Most functions use a blending mode of over (X=1,Y=1,Z=1) |
| this results in the following optimizations... |
| gamma = Sa+Da-Sa*Da; |
| gamma = 1 - QuantiumScale*alpha * QuantiumScale*beta; |
| opacity = QuantiumScale*alpha*beta; // over blend, optimized 1-Gamma |
| |
| The above SVG definitions also definate that Mathematical Composition |
| methods should use a 'Over' blending mode for Alpha Channel. |
| It however was not applied for composition modes of 'Plus', 'Minus', |
| the modulus versions of 'Add' and 'Subtract'. |
| |
| Mathematical operator changes to be applied from IM v6.7... |
| |
| 1/ Modulus modes 'Add' and 'Subtract' are obsoleted and renamed |
| 'ModulusAdd' and 'ModulusSubtract' for clarity. |
| |
| 2/ All mathematical compositions work as per the SVG specification |
| with regard to blending. This now includes 'ModulusAdd' and |
| 'ModulusSubtract'. |
| |
| 3/ When the special channel flag 'sync' (syncronize channel updates) |
| is turned off (enabled by default) then mathematical compositions are |
| only performed on the channels specified, and are applied |
| independantally of each other. In other words the mathematics is |
| performed as 'pure' mathematical operations, rather than as image |
| operations. |
| */ |
| |
| static inline MagickRealType Atop(const MagickRealType p, |
| const MagickRealType Sa,const MagickRealType q, |
| const MagickRealType magick_unused(Da)) |
| { |
| return(p*Sa+q*(1.0-Sa)); /* Da optimized out, Da/gamma => 1.0 */ |
| } |
| |
| static inline void CompositeAtop(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| composite->alpha=q->alpha; /* optimized Da = 1.0-Gamma */ |
| composite->red=Atop(p->red,Sa,q->red,1.0); |
| composite->green=Atop(p->green,Sa,q->green,1.0); |
| composite->blue=Atop(p->blue,Sa,q->blue,1.0); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=Atop(p->black,Sa,q->black,1.0); |
| } |
| |
| /* |
| What is this Composition method for? Can't find any specification! |
| WARNING this is not doing correct 'over' blend handling (Anthony Thyssen). |
| */ |
| static inline void CompositeBumpmap(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| intensity; |
| |
| intensity=(MagickRealType) GetPixelInfoIntensity(p); |
| composite->red=QuantumScale*intensity*q->red; |
| composite->green=QuantumScale*intensity*q->green; |
| composite->blue=QuantumScale*intensity*q->blue; |
| composite->alpha=(MagickRealType) QuantumScale*intensity*p->alpha; |
| if (q->colorspace == CMYKColorspace) |
| composite->black=QuantumScale*intensity*q->black; |
| } |
| |
| static inline void CompositeClear(const PixelInfo *q,PixelInfo *composite) |
| { |
| composite->alpha=(MagickRealType) TransparentAlpha; |
| composite->red=0.0; |
| composite->green=0.0; |
| composite->blue=0.0; |
| if (q->colorspace == CMYKColorspace) |
| composite->black=0.0; |
| } |
| |
| static MagickRealType ColorBurn(const MagickRealType Sca, |
| const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da) |
| { |
| if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon)) |
| return(Sa*Da+Dca*(1.0-Sa)); |
| if (Sca < MagickEpsilon) |
| return(Dca*(1.0-Sa)); |
| return(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeColorBurn(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*ColorBurn(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*ColorBurn(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*ColorBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*ColorBurn(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| |
| static MagickRealType ColorDodge(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| Working from first principles using the original formula: |
| |
| f(Sc,Dc) = Dc/(1-Sc) |
| |
| This works correctly! Looks like the 2004 SVG model was right but just |
| required a extra condition for correct handling. |
| */ |
| if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon)) |
| return(Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| if (fabs(Sca-Sa) < MagickEpsilon) |
| return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| return(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeColorDodge(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*ColorDodge(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*ColorDodge(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*ColorDodge(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*ColorDodge(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static inline MagickRealType Darken(const MagickRealType p, |
| const MagickRealType alpha,const MagickRealType q,const MagickRealType beta) |
| { |
| if (p < q) |
| return(MagickOver_(p,alpha,q,beta)); /* src-over */ |
| return(MagickOver_(q,beta,p,alpha)); /* dst-over */ |
| } |
| |
| static inline void CompositeDarken(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| gamma; |
| |
| /* |
| Darken is equivalent to a 'Minimum' method OR a greyscale version of a |
| binary 'Or' OR the 'Intersection' of pixel sets. |
| */ |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=MagickMin(p->red,q->red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=MagickMin(p->green,q->green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=MagickMin(p->blue,q->blue); |
| if ((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0 && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=MagickMin(p->black,q->black); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=MagickMax(p->alpha,q->alpha); |
| return; |
| } |
| composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */ |
| gamma=1.0-QuantumScale*composite->alpha; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Darken(p->red,p->alpha,q->red,q->alpha); |
| composite->green=gamma*Darken(p->green,p->alpha,q->green,q->alpha); |
| composite->blue=gamma*Darken(p->blue,p->alpha,q->blue,q->alpha); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Darken(p->black,p->alpha,q->black,q->alpha); |
| } |
| |
| static inline void CompositeDarkenIntensity(const Image *image, |
| const PixelInfo *p,const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| Sa; |
| |
| /* |
| Select the pixel based on the intensity level. |
| If 'Sync' flag select whole pixel based on alpha weighted intensity. |
| Otherwise use intensity only, but restrict copy according to channel. |
| */ |
| if (image->sync == MagickFalse) |
| { |
| MagickBooleanType |
| from_p; |
| |
| from_p=GetPixelInfoIntensity(p) < GetPixelInfoIntensity(q) ? MagickTrue : |
| MagickFalse; |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=from_p != MagickFalse ? p->red : q->red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=from_p != MagickFalse ? p->green : q->green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=from_p != MagickFalse ? p->blue : q->blue; |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=from_p != MagickFalse ? p->black : q->black; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha; |
| return; |
| } |
| Sa=QuantumScale*p->alpha; |
| Da=QuantumScale*q->alpha; |
| *composite=(Sa*GetPixelInfoIntensity(p) < Da*GetPixelInfoIntensity(q)) ? |
| *p : *q; |
| } |
| |
| static inline MagickRealType Difference(const MagickRealType p, |
| const MagickRealType Sa,const MagickRealType q,const MagickRealType Da) |
| { |
| /* |
| Optimized by Multipling by QuantumRange (taken from gamma). |
| */ |
| return(Sa*p+Da*q-Sa*Da*2.0*MagickMin(p,q)); |
| } |
| |
| static inline void CompositeDifference(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=fabs((double) (p->red-q->red)); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=fabs((double) (p->green-q->green)); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=fabs((double) (p->blue-q->blue)); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=fabs((double) (p->black-q->black)); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=fabs((double) (p->alpha-q->alpha)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Difference(p->red,Sa,q->red,Da); |
| composite->green=gamma*Difference(p->green,Sa,q->green,Da); |
| composite->blue=gamma*Difference(p->blue,Sa,q->blue,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Difference(p->black,Sa,q->black,Da); |
| } |
| |
| static MagickRealType Divide(const MagickRealType Sca,const MagickRealType Sa, |
| const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| Divide Source by Destination |
| |
| f(Sc,Dc) = Sc / Dc |
| |
| But with appropriate handling for special case of Dc == 0 specifically |
| so that f(Black,Black)=Black and f(non-Black,Black)=White. |
| It is however also important to correctly do 'over' alpha blending which |
| is why the formula becomes so complex. |
| */ |
| if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon)) |
| return(Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| if (fabs(Dca) < MagickEpsilon) |
| return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| return(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeDivide(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=QuantumRange*Divide(QuantumScale*p->red,1.0, |
| QuantumScale*q->red,1.0); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=QuantumRange*Divide(QuantumScale*p->green,1.0, |
| QuantumScale*q->green,1.0); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=QuantumRange*Divide(QuantumScale*p->blue,1.0, |
| QuantumScale*q->blue,1.0); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=QuantumRange*Divide(QuantumScale*p->black,1.0, |
| QuantumScale*q->black,1.0); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-Divide(Sa,1.0,Da,1.0)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Divide(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*Divide(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*Divide(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Divide(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static MagickRealType Exclusion(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| return(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeExclusion(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| gamma, |
| Sa, |
| Da; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=QuantumRange*Exclusion(QuantumScale*p->red,1.0, |
| QuantumScale*q->red,1.0); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=QuantumRange*Exclusion(QuantumScale*p->green,1.0, |
| QuantumScale*q->green,1.0); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=QuantumRange*Exclusion(QuantumScale*p->blue,1.0, |
| QuantumScale*q->blue,1.0); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=QuantumRange*Exclusion(QuantumScale*p->black,1.0, |
| QuantumScale*q->black,1.0); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-Exclusion(Sa,1.0,Da,1.0)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Exclusion(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*Exclusion(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*Exclusion(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Exclusion(QuantumScale*p->black*Sa,Sa, |
| QuantumScale*q->black*Da,Da); |
| } |
| |
| static MagickRealType HardLight(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| if ((2.0*Sca) < Sa) |
| return(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| return(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeHardLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*HardLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*HardLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*HardLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*HardLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static void CompositeHSB(const MagickRealType red,const MagickRealType green, |
| const MagickRealType blue,double *hue,double *saturation,double *brightness) |
| { |
| MagickRealType |
| delta, |
| max, |
| min; |
| |
| /* |
| Convert RGB to HSB colorspace. |
| */ |
| assert(hue != (double *) NULL); |
| assert(saturation != (double *) NULL); |
| assert(brightness != (double *) NULL); |
| max=(red > green ? red : green); |
| if (blue > max) |
| max=blue; |
| min=(red < green ? red : green); |
| if (blue < min) |
| min=blue; |
| *hue=0.0; |
| *saturation=0.0; |
| *brightness=(double) (QuantumScale*max); |
| if (fabs(max) < MagickEpsilon) |
| return; |
| *saturation=(double) (1.0-min/max); |
| delta=max-min; |
| if (fabs(delta) < MagickEpsilon) |
| return; |
| if (fabs(red-max) < MagickEpsilon) |
| *hue=(double) ((green-blue)/delta); |
| else |
| if (fabs(green-max) < MagickEpsilon) |
| *hue=(double) (2.0+(blue-red)/delta); |
| else |
| if (fabs(blue-max) < MagickEpsilon) |
| *hue=(double) (4.0+(red-green)/delta); |
| *hue/=6.0; |
| if (*hue < 0.0) |
| *hue+=1.0; |
| } |
| |
| static inline MagickRealType In(const MagickRealType p,const MagickRealType Sa, |
| const MagickRealType magick_unused(q),const MagickRealType Da) |
| { |
| return(Sa*p*Da); |
| } |
| |
| static inline void CompositeIn(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| gamma, |
| Sa, |
| Da; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=Sa*Da; |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*In(p->red,Sa,q->red,Da); |
| composite->green=gamma*In(p->green,Sa,q->green,Da); |
| composite->blue=gamma*In(p->blue,Sa,q->blue,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*In(p->black,Sa,q->black,Da); |
| } |
| |
| static inline MagickRealType Lighten(const MagickRealType p, |
| const MagickRealType alpha,const MagickRealType q,const MagickRealType beta) |
| { |
| if (p > q) |
| return(MagickOver_(p,alpha,q,beta)); /* src-over */ |
| return(MagickOver_(q,beta,p,alpha)); /* dst-over */ |
| } |
| |
| static inline void CompositeLighten(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| gamma; |
| |
| /* |
| Lighten is also equvalent to a 'Maximum' method OR a greyscale version of a |
| binary 'And' OR the 'Union' of pixel sets. |
| */ |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=MagickMax(p->red,q->red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=MagickMax(p->green,q->green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=MagickMax(p->blue,q->blue); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=MagickMax(p->black,q->black); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=MagickMin(p->alpha,q->alpha); |
| return; |
| } |
| composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */ |
| gamma=1.0-QuantumScale*composite->alpha; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Lighten(p->red,p->alpha,q->red,q->alpha); |
| composite->green=gamma*Lighten(p->green,p->alpha,q->green,q->alpha); |
| composite->blue=gamma*Lighten(p->blue,p->alpha,q->blue,q->alpha); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Lighten(p->black,p->alpha,q->black,q->alpha); |
| } |
| |
| static inline void CompositeLightenIntensity(const Image *image, |
| const PixelInfo *p,const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| Sa; |
| |
| /* |
| Select the pixel based on the intensity level. |
| If 'Sync' flag select whole pixel based on alpha weighted intensity. |
| Otherwise use Intenisty only, but restrict copy according to channel. |
| */ |
| if (image->sync == MagickFalse) |
| { |
| MagickBooleanType |
| from_p; |
| |
| from_p=GetPixelInfoIntensity(p) > GetPixelInfoIntensity(q) ? MagickTrue : |
| MagickFalse; |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=from_p != MagickFalse ? p->red : q->red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=from_p != MagickFalse ? p->green : q->green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=from_p != MagickFalse ? p->blue : q->blue; |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=from_p != MagickFalse ? p->black : q->black; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha; |
| return; |
| } |
| Sa=QuantumScale*p->alpha; |
| Da=QuantumScale*q->alpha; |
| *composite=(Sa*GetPixelInfoIntensity(p) > Da*GetPixelInfoIntensity(q)) ? |
| *p : *q; |
| } |
| |
| static inline void CompositeLinearDodge(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*(p->red*Sa+q->red*Da); |
| composite->green=gamma*(p->green*Sa+q->green*Da); |
| composite->blue=gamma*(p->blue*Sa+q->blue*Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*(p->black*Sa+q->black*Da); |
| } |
| |
| |
| static inline MagickRealType LinearBurn(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| LinearBurn: as defined by Abode Photoshop, according to |
| http://www.simplefilter.de/en/basics/mixmods.html is: |
| |
| f(Sc,Dc) = Sc + Dc - 1 |
| */ |
| return(Sca+Dca-Sa*Da); |
| } |
| |
| static inline void CompositeLinearBurn(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*LinearBurn(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*LinearBurn(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*LinearBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*LinearBurn(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static inline MagickRealType LinearLight(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| LinearLight: as defined by Abode Photoshop, according to |
| http://www.simplefilter.de/en/basics/mixmods.html is: |
| |
| f(Sc,Dc) = Dc + 2*Sc - 1 |
| */ |
| return((Sca-Sa)*Da+Sca+Dca); |
| } |
| |
| static inline void CompositeLinearLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*LinearLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*LinearLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*LinearLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*LinearLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static inline MagickRealType Mathematics(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da, |
| const GeometryInfo *geometry_info) |
| { |
| MagickRealType |
| gamma; |
| |
| /* |
| '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) |
| */ |
| 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); |
| return(gamma); |
| } |
| |
| static inline void CompositeMathematics(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,const GeometryInfo *args,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* ??? - AT */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=QuantumRange*Mathematics(QuantumScale*p->red,1.0, |
| QuantumScale*q->red,1.0,args); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=QuantumRange*Mathematics(QuantumScale*p->green,1.0, |
| QuantumScale*q->green,1.0,args); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=QuantumRange*Mathematics(QuantumScale*p->blue,1.0, |
| QuantumScale*q->blue,1.0,args); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=QuantumRange*Mathematics(QuantumScale*p->black,1.0, |
| QuantumScale*q->black,1.0,args); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-Mathematics(Sa,1.0,Da,1.0,args)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Mathematics(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da,args); |
| composite->green=gamma*Mathematics(QuantumScale*p->green*Sa,Sa, |
| QuantumScale*q->green*Da,Da,args); |
| composite->blue=gamma*Mathematics(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da,args); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Mathematics(QuantumScale*p->black*Sa,Sa, |
| QuantumScale*q->black*Da,Da,args); |
| } |
| |
| static inline void CompositePlus(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| /* |
| NOTE: "Plus" does not use 'over' alpha-blending but uses a special |
| 'plus' form of alph-blending. It is the ONLY mathematical operator to |
| do this. this is what makes it different to the otherwise equivalent |
| "LinearDodge" composition method. |
| |
| Note however that color channels are still effected by the alpha channel |
| as a result of the blending, making it just as useless for independant |
| channel maths, just like all other mathematical composition methods. |
| |
| As such the removal of the 'sync' flag, is still a usful convention. |
| |
| The CompositePixelInfoPlus() function is defined in |
| "composite-private.h" so it can also be used for Image Blending. |
| */ |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=p->red+q->red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=p->green+q->green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=p->blue+q->blue; |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=p->black+q->black; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=p->alpha+q->alpha-QuantumRange; |
| return; |
| } |
| CompositePixelInfoPlus(p,p->alpha,q,q->alpha,composite); |
| } |
| |
| static inline MagickRealType Minus(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca, |
| const MagickRealType magick_unused(Da)) |
| { |
| /* |
| Minus Source from Destination |
| |
| f(Sc,Dc) = Sc - Dc |
| */ |
| return(Sca+Dca-2.0*Dca*Sa); |
| } |
| |
| static inline void CompositeMinus(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=p->red-q->red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=p->green-q->green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=p->blue-q->blue; |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=p->black-q->black; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-(Sa-Da)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Minus(p->red*Sa,Sa,q->red*Da,Da); |
| composite->green=gamma*Minus(p->green*Sa,Sa,q->green*Da,Da); |
| composite->blue=gamma*Minus(p->blue*Sa,Sa,q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Minus(p->black*Sa,Sa,q->black*Da,Da); |
| } |
| |
| static inline MagickRealType ModulusAdd(const MagickRealType p, |
| const MagickRealType Sa,const MagickRealType q, const MagickRealType Da) |
| { |
| MagickRealType |
| pixel; |
| |
| pixel=p+q; |
| if (pixel > QuantumRange) |
| pixel-=(QuantumRange+1.0); |
| return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa)); |
| } |
| |
| static inline void CompositeModulusAdd(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=ModulusAdd(p->red,1.0,q->red,1.0); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=ModulusAdd(p->green,1.0,q->green,1.0); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=ModulusAdd(p->blue,1.0,q->blue,1.0); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=ModulusAdd(p->black,1.0,q->black,1.0); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=ModulusAdd(p->alpha,1.0,q->alpha,1.0); |
| return; |
| } |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=ModulusAdd(p->red,Sa,q->red,Da); |
| composite->green=ModulusAdd(p->green,Sa,q->green,Da); |
| composite->blue=ModulusAdd(p->blue,Sa,q->blue,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=ModulusAdd(p->black,Sa,q->black,Da); |
| } |
| |
| static inline MagickRealType ModulusSubtract(const MagickRealType p, |
| const MagickRealType Sa,const MagickRealType q, const MagickRealType Da) |
| { |
| MagickRealType |
| pixel; |
| |
| pixel=p-q; |
| if (pixel < 0.0) |
| pixel+=(QuantumRange+1.0); |
| return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa)); |
| } |
| |
| static inline void CompositeModulusSubtract(const Image *image, |
| const PixelInfo *p,const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels, |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=ModulusSubtract(p->red,1.0,q->red,1.0); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=ModulusSubtract(p->green,1.0,q->green,1.0); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=ModulusSubtract(p->blue,1.0,q->blue,1.0); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=ModulusSubtract(p->black,1.0,q->black,1.0); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=ModulusSubtract(p->alpha,1.0,q->alpha,1.0); |
| return; |
| } |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma = RoundToUnity(Sa+Da-Sa*Da); |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=ModulusSubtract(p->red,Sa,q->red,Da); |
| composite->green=ModulusSubtract(p->green,Sa,q->green,Da); |
| composite->blue=ModulusSubtract(p->blue,Sa,q->blue,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=ModulusSubtract(p->black,Sa,q->black,Da); |
| } |
| |
| static inline MagickRealType Multiply(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| return(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeMultiply(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=QuantumScale*p->red*q->red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=QuantumScale*p->green*q->green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=QuantumScale*p->blue*q->blue; |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=QuantumScale*p->black*q->black; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-Sa*Da); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Multiply(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*Multiply(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*Multiply(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Multiply(QuantumScale*p->black*Sa,Sa, |
| QuantumScale*q->black*Da,Da); |
| } |
| |
| static inline MagickRealType Out(const MagickRealType p,const MagickRealType Sa, |
| const MagickRealType magick_unused(q),const MagickRealType Da) |
| { |
| return(Sa*p*(1.0-Da)); |
| } |
| |
| static inline void CompositeOut(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Sa, |
| Da, |
| gamma; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=Sa*(1.0-Da); |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Out(p->red,Sa,q->red,Da); |
| composite->green=gamma*Out(p->green,Sa,q->green,Da); |
| composite->blue=gamma*Out(p->blue,Sa,q->blue,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Out(p->black,Sa,q->black,Da); |
| } |
| |
| static MagickRealType PegtopLight(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| PegTop: A Soft-Light alternative: A continuous version of the Softlight |
| function, producing very similar results. |
| |
| f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc |
| |
| See http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm. |
| */ |
| if (fabs(Da) < MagickEpsilon) |
| return(Sca); |
| return(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositePegtopLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*PegtopLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*PegtopLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*PegtopLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*PegtopLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static MagickRealType PinLight(const MagickRealType Sca,const MagickRealType Sa, |
| const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| PinLight: A Photoshop 7 composition method |
| http://www.simplefilter.de/en/basics/mixmods.html |
| |
| f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc |
| */ |
| if (Dca*Sa < Da*(2.0*Sca-Sa)) |
| return(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa)); |
| if ((Dca*Sa) > (2.0*Sca*Da)) |
| return(Sca*Da+Sca+Dca*(1.0-Sa)); |
| return(Sca*(1.0-Da)+Dca); |
| } |
| |
| static inline void CompositePinLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*PinLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*PinLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*PinLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*PinLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static inline MagickRealType Screen(const MagickRealType Sca, |
| const MagickRealType Dca) |
| { |
| /* |
| Screen: A negated multiply |
| f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc) |
| */ |
| return(Sca+Dca-Sca*Dca); |
| } |
| |
| static inline void CompositeScreen(const Image *image,const PixelInfo *p, |
| const PixelInfo *q,PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| if (image->sync == MagickFalse) |
| { |
| /* |
| Handle channels as separate grayscale channels. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| composite->red=QuantumRange*Screen(QuantumScale*p->red, |
| QuantumScale*q->red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| composite->green=QuantumRange*Screen(QuantumScale*p->green, |
| QuantumScale*q->green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| composite->blue=QuantumRange*Screen(QuantumScale*p->blue, |
| QuantumScale*q->blue); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (q->colorspace == CMYKColorspace)) |
| composite->black=QuantumRange*Screen(QuantumScale*p->black, |
| QuantumScale*q->black); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| composite->alpha=QuantumRange*(1.0-Screen(Sa,Da)); |
| return; |
| } |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| Sa*=QuantumScale; Da*=QuantumScale; /* optimization */ |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Screen(p->red*Sa,q->red*Da); |
| composite->green=gamma*Screen(p->green*Sa,q->green*Da); |
| composite->blue=gamma*Screen(p->blue*Sa,q->blue*Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Screen(p->black*Sa,q->black*Da); |
| } |
| |
| static MagickRealType SoftLight(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| MagickRealType |
| alpha, |
| beta; |
| |
| /* |
| New specification: March 2009 SVG specification. |
| */ |
| alpha=Dca/Da; |
| if ((2.0*Sca) < Sa) |
| return(Dca*(Sa+(2.0*Sca-Sa)*(1.0-alpha))+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da)) |
| { |
| beta=Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*alpha*(4.0*alpha+1.0)*(alpha-1.0)+7.0* |
| alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa); |
| return(beta); |
| } |
| beta=Dca*Sa+Da*(2.0*Sca-Sa)*(pow(alpha,0.5)-alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa); |
| return(beta); |
| } |
| |
| static inline void CompositeSoftLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*SoftLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*SoftLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*SoftLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*SoftLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static inline MagickRealType Threshold(const MagickRealType p, |
| const MagickRealType q,const MagickRealType threshold, |
| const MagickRealType amount) |
| { |
| MagickRealType |
| delta; |
| |
| /* |
| Multiply difference by amount, if differance larger than threshold??? |
| What use this is is completely unknown. The Opacity calculation appears to |
| be inverted -- Anthony Thyssen |
| |
| Deprecated. |
| */ |
| delta=p-q; |
| if ((MagickRealType) fabs((double) (2.0*delta)) < threshold) |
| return(q); |
| return(q+delta*amount); |
| } |
| |
| static inline void CompositeThreshold(const PixelInfo *p,const PixelInfo *q, |
| const MagickRealType threshold,const MagickRealType amount, |
| PixelInfo *composite) |
| { |
| composite->red=Threshold(p->red,q->red,threshold,amount); |
| composite->green=Threshold(p->green,q->green,threshold,amount); |
| composite->blue=Threshold(p->blue,q->blue,threshold,amount); |
| composite->alpha=Threshold(p->alpha,q->alpha,threshold,amount); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=Threshold(p->black,q->black,threshold,amount); |
| } |
| |
| |
| static MagickRealType VividLight(const MagickRealType Sca, |
| const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) |
| { |
| /* |
| VividLight: A Photoshop 7 composition method. See |
| http://www.simplefilter.de/en/basics/mixmods.html. |
| |
| f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc)) |
| */ |
| if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon)) |
| return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| if ((2.0*Sca) <= Sa) |
| return(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| return(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeVividLight(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*VividLight(QuantumScale*p->red*Sa,Sa,QuantumScale* |
| q->red*Da,Da); |
| composite->green=gamma*VividLight(QuantumScale*p->green*Sa,Sa,QuantumScale* |
| q->green*Da,Da); |
| composite->blue=gamma*VividLight(QuantumScale*p->blue*Sa,Sa,QuantumScale* |
| q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*VividLight(QuantumScale*p->black*Sa,Sa,QuantumScale* |
| q->black*Da,Da); |
| } |
| |
| static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa, |
| const MagickRealType Dca,const MagickRealType Da) |
| { |
| return(Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| } |
| |
| static inline void CompositeXor(const PixelInfo *p,const PixelInfo *q, |
| PixelInfo *composite) |
| { |
| MagickRealType |
| Da, |
| gamma, |
| Sa; |
| |
| Sa=QuantumScale*p->alpha; /* simplify and speed up equations */ |
| Da=QuantumScale*q->alpha; |
| gamma=Sa+Da-2.0*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */ |
| composite->alpha=(MagickRealType) QuantumRange*gamma; |
| gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); |
| composite->red=gamma*Xor(p->red*Sa,Sa,q->red*Da,Da); |
| composite->green=gamma*Xor(p->green*Sa,Sa,q->green*Da,Da); |
| composite->blue=gamma*Xor(p->blue*Sa,Sa,q->blue*Da,Da); |
| if (q->colorspace == CMYKColorspace) |
| composite->black=gamma*Xor(p->black*Sa,Sa,q->black*Da,Da); |
| } |
| |
| static void HSBComposite(const double hue,const double saturation, |
| const double brightness,MagickRealType *red,MagickRealType *green, |
| MagickRealType *blue) |
| { |
| MagickRealType |
| f, |
| h, |
| p, |
| q, |
| t; |
| |
| /* |
| Convert HSB to RGB colorspace. |
| */ |
| assert(red != (MagickRealType *) NULL); |
| assert(green != (MagickRealType *) NULL); |
| assert(blue != (MagickRealType *) NULL); |
| if (saturation == 0.0) |
| { |
| *red=(MagickRealType) QuantumRange*brightness; |
| *green=(*red); |
| *blue=(*red); |
| return; |
| } |
| h=6.0*(hue-floor(hue)); |
| f=h-floor((double) h); |
| p=brightness*(1.0-saturation); |
| q=brightness*(1.0-saturation*f); |
| t=brightness*(1.0-saturation*(1.0-f)); |
| switch ((int) h) |
| { |
| case 0: |
| default: |
| { |
| *red=(MagickRealType) QuantumRange*brightness; |
| *green=(MagickRealType) QuantumRange*t; |
| *blue=(MagickRealType) QuantumRange*p; |
| break; |
| } |
| case 1: |
| { |
| *red=(MagickRealType) QuantumRange*q; |
| *green=(MagickRealType) QuantumRange*brightness; |
| *blue=(MagickRealType) QuantumRange*p; |
| break; |
| } |
| case 2: |
| { |
| *red=(MagickRealType) QuantumRange*p; |
| *green=(MagickRealType) QuantumRange*brightness; |
| *blue=(MagickRealType) QuantumRange*t; |
| break; |
| } |
| case 3: |
| { |
| *red=(MagickRealType) QuantumRange*p; |
| *green=(MagickRealType) QuantumRange*q; |
| *blue=(MagickRealType) QuantumRange*brightness; |
| break; |
| } |
| case 4: |
| { |
| *red=(MagickRealType) QuantumRange*t; |
| *green=(MagickRealType) QuantumRange*p; |
| *blue=(MagickRealType) QuantumRange*brightness; |
| break; |
| } |
| case 5: |
| { |
| *red=(MagickRealType) QuantumRange*brightness; |
| *green=(MagickRealType) QuantumRange*p; |
| *blue=(MagickRealType) QuantumRange*q; |
| break; |
| } |
| } |
| } |
| |
| MagickExport MagickBooleanType CompositeImage(Image *image, |
| const CompositeOperator compose,const Image *composite_image, |
| const ssize_t x_offset,const ssize_t y_offset) |
| { |
| #define CompositeImageTag "Composite/Image" |
| |
| CacheView |
| *composite_view, |
| *image_view; |
| |
| const char |
| *value; |
| |
| double |
| sans; |
| |
| ExceptionInfo |
| *exception; |
| |
| GeometryInfo |
| geometry_info; |
| |
| Image |
| *destination_image; |
| |
| MagickBooleanType |
| modify_outside_overlay, |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| MagickRealType |
| amount, |
| destination_dissolve, |
| midpoint, |
| percent_brightness, |
| percent_saturation, |
| source_dissolve, |
| threshold; |
| |
| MagickStatusType |
| flags; |
| |
| PixelInfo |
| zero; |
| |
| ssize_t |
| y; |
| |
| /* |
| Prepare composite image. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(composite_image != (Image *) NULL); |
| assert(composite_image->signature == MagickSignature); |
| exception=(&image->exception); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| GetPixelInfo(image,&zero); |
| destination_image=(Image *) NULL; |
| amount=0.5; |
| destination_dissolve=1.0; |
| modify_outside_overlay=MagickFalse; |
| percent_brightness=100.0; |
| percent_saturation=100.0; |
| source_dissolve=1.0; |
| threshold=0.05f; |
| switch (compose) |
| { |
| case ClearCompositeOp: |
| case SrcCompositeOp: |
| case InCompositeOp: |
| case SrcInCompositeOp: |
| case OutCompositeOp: |
| case SrcOutCompositeOp: |
| case DstInCompositeOp: |
| case DstAtopCompositeOp: |
| { |
| /* |
| Modify destination outside the overlaid region. |
| */ |
| modify_outside_overlay=MagickTrue; |
| break; |
| } |
| case CopyCompositeOp: |
| { |
| if ((x_offset < 0) || (y_offset < 0)) |
| break; |
| if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns) |
| break; |
| if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows) |
| break; |
| status=MagickTrue; |
| image_view=AcquireCacheView(image); |
| composite_view=AcquireCacheView(composite_image); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(dynamic,4) shared(status) |
| #endif |
| for (y=0; y < (ssize_t) composite_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| register const Quantum |
| *p; |
| |
| register Quantum |
| *q; |
| |
| register ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns, |
| 1,exception); |
| q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset, |
| composite_image->columns,1,exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) composite_image->columns; x++) |
| { |
| SetPixelRed(image,GetPixelRed(composite_image,p),q); |
| SetPixelGreen(image,GetPixelGreen(composite_image,p),q); |
| SetPixelBlue(image,GetPixelBlue(composite_image,p),q); |
| SetPixelAlpha(image,GetPixelAlpha(composite_image,p),q); |
| if (image->colorspace == CMYKColorspace) |
| SetPixelBlack(image,GetPixelBlack(composite_image,p),q); |
| p+=GetPixelChannels(composite_image); |
| q+=GetPixelChannels(image); |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp critical (MagickCore_CompositeImage) |
| #endif |
| proceed=SetImageProgress(image,CompositeImageTag, |
| (MagickOffsetType) y,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| composite_view=DestroyCacheView(composite_view); |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| case CopyOpacityCompositeOp: |
| case ChangeMaskCompositeOp: |
| { |
| /* |
| Modify destination outside the overlaid region and require an alpha |
| channel to exist, to add transparency. |
| */ |
| if (image->matte == MagickFalse) |
| (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); |
| modify_outside_overlay=MagickTrue; |
| break; |
| } |
| case BlurCompositeOp: |
| { |
| CacheView |
| *composite_view, |
| *destination_view; |
| |
| PixelInfo |
| pixel; |
| |
| MagickRealType |
| angle_range, |
| angle_start, |
| height, |
| width; |
| |
| ResampleFilter |
| *resample_filter; |
| |
| SegmentInfo |
| blur; |
| |
| /* |
| Blur Image dictated by an overlay gradient map: X = red_channel; |
| Y = green_channel; compose:args = x_scale[,y_scale[,angle]]. |
| */ |
| destination_image=CloneImage(image,image->columns,image->rows,MagickTrue, |
| &image->exception); |
| if (destination_image == (Image *) NULL) |
| return(MagickFalse); |
| /* |
| Determine the horizontal and vertical maximim blur. |
| */ |
| SetGeometryInfo(&geometry_info); |
| flags=NoValue; |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| flags=ParseGeometry(value,&geometry_info); |
| if ((flags & WidthValue) == 0 ) |
| { |
| destination_image=DestroyImage(destination_image); |
| return(MagickFalse); |
| } |
| width=geometry_info.rho; |
| height=geometry_info.sigma; |
| blur.x1=geometry_info.rho; |
| blur.x2=0.0; |
| blur.y1=0.0; |
| blur.y2=geometry_info.sigma; |
| angle_start=0.0; |
| angle_range=0.0; |
| if ((flags & HeightValue) == 0) |
| blur.y2=blur.x1; |
| if ((flags & XValue) != 0 ) |
| { |
| MagickRealType |
| angle; |
| |
| 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); |
| } |
| if ((flags & YValue) != 0 ) |
| { |
| angle_start=DegreesToRadians(geometry_info.xi); |
| angle_range=DegreesToRadians(geometry_info.psi)-angle_start; |
| } |
| /* |
| Blur Image by resampling. |
| */ |
| pixel=zero; |
| exception=(&image->exception); |
| resample_filter=AcquireResampleFilter(image,&image->exception); |
| SetResampleFilter(resample_filter,CubicFilter,2.0); |
| destination_view=AcquireCacheView(destination_image); |
| composite_view=AcquireCacheView(composite_image); |
| for (y=0; y < (ssize_t) composite_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| register const Quantum |
| *restrict p; |
| |
| register Quantum |
| *restrict q; |
| |
| register ssize_t |
| x; |
| |
| if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows)) |
| continue; |
| p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns, |
| 1,exception); |
| q=QueueCacheViewAuthenticPixels(destination_view,0,y, |
| destination_image->columns,1,&image->exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| break; |
| for (x=0; x < (ssize_t) composite_image->columns; x++) |
| { |
| if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns)) |
| { |
| p+=GetPixelChannels(composite_image); |
| continue; |
| } |
| if (fabs(angle_range) > MagickEpsilon) |
| { |
| MagickRealType |
| angle; |
| |
| angle=angle_start+angle_range*QuantumScale* |
| GetPixelBlue(composite_image,p); |
| blur.x1=width*cos(angle); |
| blur.x2=width*sin(angle); |
| blur.y1=(-height*sin(angle)); |
| blur.y2=height*cos(angle); |
| } |
| ScaleResampleFilter(resample_filter,blur.x1*QuantumScale* |
| GetPixelRed(composite_image,p),blur.y1*QuantumScale* |
| GetPixelGreen(composite_image,p),blur.x2*QuantumScale* |
| GetPixelRed(composite_image,p),blur.y2*QuantumScale* |
| GetPixelGreen(composite_image,p)); |
| (void) ResamplePixelColor(resample_filter,(double) x_offset+x, |
| (double) y_offset+y,&pixel); |
| SetPixelPixelInfo(destination_image,&pixel,q); |
| p+=GetPixelChannels(composite_image); |
| q+=GetPixelChannels(destination_image); |
| } |
| sync=SyncCacheViewAuthenticPixels(destination_view,exception); |
| if (sync == MagickFalse) |
| break; |
| } |
| resample_filter=DestroyResampleFilter(resample_filter); |
| composite_view=DestroyCacheView(composite_view); |
| destination_view=DestroyCacheView(destination_view); |
| composite_image=destination_image; |
| break; |
| } |
| case DisplaceCompositeOp: |
| case DistortCompositeOp: |
| { |
| CacheView |
| *composite_view, |
| *destination_view, |
| *image_view; |
| |
| PixelInfo |
| pixel; |
| |
| MagickRealType |
| horizontal_scale, |
| vertical_scale; |
| |
| 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]] |
| */ |
| destination_image=CloneImage(image,image->columns,image->rows,MagickTrue, |
| &image->exception); |
| if (destination_image == (Image *) NULL) |
| return(MagickFalse); |
| SetGeometryInfo(&geometry_info); |
| flags=NoValue; |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| flags=ParseGeometry(value,&geometry_info); |
| if ((flags & (WidthValue|HeightValue)) == 0 ) |
| { |
| if ((flags & AspectValue) == 0) |
| { |
| horizontal_scale=(MagickRealType) (composite_image->columns-1.0)/ |
| 2.0; |
| vertical_scale=(MagickRealType) (composite_image->rows-1.0)/2.0; |
| } |
| else |
| { |
| horizontal_scale=(MagickRealType) (image->columns-1.0)/2.0; |
| vertical_scale=(MagickRealType) (image->rows-1.0)/2.0; |
| } |
| } |
| else |
| { |
| horizontal_scale=geometry_info.rho; |
| vertical_scale=geometry_info.sigma; |
| if ((flags & PercentValue) != 0) |
| { |
| if ((flags & AspectValue) == 0) |
| { |
| horizontal_scale*=(composite_image->columns-1.0)/200.0; |
| vertical_scale*=(composite_image->rows-1.0)/200.0; |
| } |
| else |
| { |
| horizontal_scale*=(image->columns-1.0)/200.0; |
| vertical_scale*=(image->rows-1.0)/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) x_offset+(composite_image->columns-1)/ |
| 2.0; |
| else |
| center.x=((MagickRealType) image->columns-1)/2.0; |
| else |
| if ((flags & AspectValue) == 0) |
| center.x=(MagickRealType) x_offset+geometry_info.xi; |
| else |
| center.x=geometry_info.xi; |
| if ((flags & YValue) == 0) |
| if ((flags & AspectValue) == 0) |
| center.y=(MagickRealType) y_offset+(composite_image->rows-1)/2.0; |
| else |
| center.y=((MagickRealType) image->rows-1)/2.0; |
| else |
| if ((flags & AspectValue) == 0) |
| center.y=(MagickRealType) y_offset+geometry_info.psi; |
| else |
| center.y=geometry_info.psi; |
| } |
| /* |
| Shift the pixel offset point as defined by the provided, |
| displacement/distortion map. -- Like a lens... |
| */ |
| pixel=zero; |
| exception=(&image->exception); |
| image_view=AcquireCacheView(image); |
| destination_view=AcquireCacheView(destination_image); |
| composite_view=AcquireCacheView(composite_image); |
| for (y=0; y < (ssize_t) composite_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| register const Quantum |
| *restrict p; |
| |
| register Quantum |
| *restrict q; |
| |
| register ssize_t |
| x; |
| |
| if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows)) |
| continue; |
| p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns, |
| 1,exception); |
| q=QueueCacheViewAuthenticPixels(destination_view,0,y, |
| destination_image->columns,1,&image->exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| break; |
| for (x=0; x < (ssize_t) composite_image->columns; x++) |
| { |
| if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns)) |
| { |
| p+=GetPixelChannels(composite_image); |
| continue; |
| } |
| /* |
| Displace the offset. |
| */ |
| offset.x=(horizontal_scale*(GetPixelRed(composite_image,p)- |
| (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType) |
| QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ? |
| x : 0); |
| offset.y=(vertical_scale*(GetPixelGreen(composite_image,p)- |
| (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType) |
| QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ? |
| y : 0); |
| (void) InterpolatePixelInfo(image,image_view, |
| UndefinedInterpolatePixel,(double) offset.x,(double) offset.y, |
| &pixel,exception); |
| /* |
| Mask with the 'invalid pixel mask' in alpha channel. |
| */ |
| pixel.alpha=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale* |
| pixel.alpha)*(1.0-QuantumScale* |
| GetPixelAlpha(composite_image,p))); |
| SetPixelPixelInfo(destination_image,&pixel,q); |
| p+=GetPixelChannels(composite_image); |
| q+=GetPixelChannels(destination_image); |
| } |
| sync=SyncCacheViewAuthenticPixels(destination_view,exception); |
| if (sync == MagickFalse) |
| break; |
| } |
| destination_view=DestroyCacheView(destination_view); |
| composite_view=DestroyCacheView(composite_view); |
| image_view=DestroyCacheView(image_view); |
| composite_image=destination_image; |
| break; |
| } |
| case DissolveCompositeOp: |
| { |
| /* |
| Geometry arguments to dissolve factors. |
| */ |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| source_dissolve=geometry_info.rho/100.0; |
| destination_dissolve=1.0; |
| if ((source_dissolve-MagickEpsilon) < 0.0) |
| source_dissolve=0.0; |
| if ((source_dissolve+MagickEpsilon) > 1.0) |
| { |
| destination_dissolve=2.0-source_dissolve; |
| source_dissolve=1.0; |
| } |
| if ((flags & SigmaValue) != 0) |
| destination_dissolve=geometry_info.sigma/100.0; |
| if ((destination_dissolve-MagickEpsilon) < 0.0) |
| destination_dissolve=0.0; |
| modify_outside_overlay=MagickTrue; |
| if ((destination_dissolve+MagickEpsilon) > 1.0 ) |
| { |
| destination_dissolve=1.0; |
| modify_outside_overlay=MagickFalse; |
| } |
| } |
| break; |
| } |
| case BlendCompositeOp: |
| { |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| source_dissolve=geometry_info.rho/100.0; |
| destination_dissolve=1.0-source_dissolve; |
| if ((flags & SigmaValue) != 0) |
| destination_dissolve=geometry_info.sigma/100.0; |
| modify_outside_overlay=MagickTrue; |
| if ((destination_dissolve+MagickEpsilon) > 1.0) |
| modify_outside_overlay=MagickFalse; |
| } |
| break; |
| } |
| case MathematicsCompositeOp: |
| { |
| /* |
| Just collect the values from "compose:args", setting. |
| Unused values are set to zero automagically. |
| |
| Arguments are normally a comma separated list, so this probably should |
| be changed to some 'general comma list' parser, (with a minimum |
| number of values) |
| */ |
| SetGeometryInfo(&geometry_info); |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| (void) ParseGeometry(value,&geometry_info); |
| break; |
| } |
| case ModulateCompositeOp: |
| { |
| /* |
| Determine the brightness and saturation scale. |
| */ |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| percent_brightness=geometry_info.rho; |
| if ((flags & SigmaValue) != 0) |
| percent_saturation=geometry_info.sigma; |
| } |
| break; |
| } |
| case ThresholdCompositeOp: |
| { |
| /* |
| Determine the amount and threshold. |
| */ |
| value=GetImageArtifact(composite_image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| amount=geometry_info.rho; |
| threshold=geometry_info.sigma; |
| if ((flags & SigmaValue) == 0) |
| threshold=0.05f; |
| } |
| threshold*=QuantumRange; |
| break; |
| } |
| default: |
| break; |
| } |
| value=GetImageArtifact(composite_image,"compose:outside-overlay"); |
| if (value != (const char *) NULL) |
| modify_outside_overlay=IsMagickTrue(value); |
| /* |
| Composite image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| midpoint=((MagickRealType) QuantumRange+1.0)/2; |
| GetPixelInfo(composite_image,&zero); |
| exception=(&image->exception); |
| image_view=AcquireCacheView(image); |
| composite_view=AcquireCacheView(composite_image); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(dynamic,4) shared(progress,status) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *pixels; |
| |
| double |
| brightness, |
| hue, |
| saturation; |
| |
| PixelInfo |
| composite, |
| destination, |
| source; |
| |
| register const Quantum |
| *restrict p; |
| |
| register Quantum |
| *restrict q; |
| |
| register ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| if (modify_outside_overlay == MagickFalse) |
| { |
| if (y < y_offset) |
| continue; |
| if ((y-y_offset) >= (ssize_t) composite_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) composite_image->rows)) |
| { |
| p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset, |
| composite_image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| pixels=p; |
| if (x_offset < 0) |
| p-=x_offset*GetPixelChannels(composite_image); |
| } |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| source=zero; |
| destination=zero; |
| hue=0.0; |
| saturation=0.0; |
| brightness=0.0; |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| if (modify_outside_overlay == MagickFalse) |
| { |
| if (x < x_offset) |
| { |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| if ((x-x_offset) >= (ssize_t) composite_image->columns) |
| break; |
| } |
| destination.red=(MagickRealType) GetPixelRed(image,q); |
| destination.green=(MagickRealType) GetPixelGreen(image,q); |
| destination.blue=(MagickRealType) GetPixelBlue(image,q); |
| if (image->colorspace == CMYKColorspace) |
| destination.black=(MagickRealType) GetPixelBlack(image,q); |
| if (image->colorspace == CMYKColorspace) |
| { |
| destination.red=(MagickRealType) QuantumRange-destination.red; |
| destination.green=(MagickRealType) QuantumRange-destination.green; |
| destination.blue=(MagickRealType) QuantumRange-destination.blue; |
| destination.black=(MagickRealType) QuantumRange-destination.black; |
| } |
| if (image->matte != MagickFalse) |
| destination.alpha=(MagickRealType) GetPixelAlpha(image,q); |
| /* |
| Handle destination modifications outside overlaid region. |
| */ |
| composite=destination; |
| if ((pixels == (Quantum *) NULL) || (x < x_offset) || |
| ((x-x_offset) >= (ssize_t) composite_image->columns)) |
| { |
| switch (compose) |
| { |
| case DissolveCompositeOp: |
| case BlendCompositeOp: |
| { |
| composite.alpha=destination_dissolve*(composite.alpha); |
| break; |
| } |
| case ClearCompositeOp: |
| case SrcCompositeOp: |
| { |
| CompositeClear(&destination,&composite); |
| break; |
| } |
| case InCompositeOp: |
| case SrcInCompositeOp: |
| case OutCompositeOp: |
| case SrcOutCompositeOp: |
| case DstInCompositeOp: |
| case DstAtopCompositeOp: |
| case CopyOpacityCompositeOp: |
| case ChangeMaskCompositeOp: |
| { |
| composite.alpha=(MagickRealType) TransparentAlpha; |
| break; |
| } |
| default: |
| { |
| (void) GetOneVirtualMagickPixel(composite_image,x-x_offset,y- |
| y_offset,&composite,exception); |
| break; |
| } |
| } |
| if (image->colorspace == CMYKColorspace) |
| { |
| composite.red=(MagickRealType) QuantumRange-composite.red; |
| composite.green=(MagickRealType) QuantumRange-composite.green; |
| composite.blue=(MagickRealType) QuantumRange-composite.blue; |
| composite.black=(MagickRealType) QuantumRange-composite.black; |
| } |
| SetPixelRed(image,ClampToQuantum(composite.red),q); |
| SetPixelGreen(image,ClampToQuantum(composite.green),q); |
| SetPixelBlue(image,ClampToQuantum(composite.blue),q); |
| if (image->matte != MagickFalse) |
| SetPixelAlpha(image,ClampToQuantum(composite.alpha),q); |
| if (image->colorspace == CMYKColorspace) |
| SetPixelBlack(image,ClampToQuantum(composite.black),q); |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| /* |
| Handle normal overlay of source onto destination. |
| */ |
| source.red=(MagickRealType) GetPixelRed(composite_image,p); |
| source.green=(MagickRealType) GetPixelGreen(composite_image,p); |
| source.blue=(MagickRealType) GetPixelBlue(composite_image,p); |
| if (composite_image->colorspace == CMYKColorspace) |
| source.black=(MagickRealType) GetPixelBlack(composite_image,p); |
| if (composite_image->colorspace == CMYKColorspace) |
| { |
| source.red=(MagickRealType) QuantumRange-source.red; |
| source.green=(MagickRealType) QuantumRange-source.green; |
| source.blue=(MagickRealType) QuantumRange-source.blue; |
| source.black=(MagickRealType) QuantumRange-source.black; |
| } |
| if (composite_image->matte != MagickFalse) |
| source.alpha=(MagickRealType) GetPixelAlpha(composite_image,p); |
| /* |
| Porter-Duff compositions. |
| */ |
| switch (compose) |
| { |
| case ClearCompositeOp: |
| { |
| CompositeClear(&destination,&composite); |
| break; |
| } |
| case SrcCompositeOp: |
| case CopyCompositeOp: |
| case ReplaceCompositeOp: |
| { |
| composite=source; |
| break; |
| } |
| case NoCompositeOp: |
| case DstCompositeOp: |
| break; |
| case OverCompositeOp: |
| case SrcOverCompositeOp: |
| { |
| CompositePixelInfoOver(&source,source.alpha,&destination, |
| destination.alpha,&composite); |
| break; |
| } |
| case DstOverCompositeOp: |
| { |
| CompositePixelInfoOver(&destination,destination.alpha,&source, |
| source.alpha,&composite); |
| break; |
| } |
| case SrcInCompositeOp: |
| case InCompositeOp: |
| { |
| CompositeIn(&source,&destination,&composite); |
| break; |
| } |
| case DstInCompositeOp: |
| { |
| CompositeIn(&destination,&source,&composite); |
| break; |
| } |
| case OutCompositeOp: |
| case SrcOutCompositeOp: |
| { |
| CompositeOut(&source,&destination,&composite); |
| break; |
| } |
| case DstOutCompositeOp: |
| { |
| CompositeOut(&destination,&source,&composite); |
| break; |
| } |
| case AtopCompositeOp: |
| case SrcAtopCompositeOp: |
| { |
| CompositeAtop(&source,&destination,&composite); |
| break; |
| } |
| case DstAtopCompositeOp: |
| { |
| CompositeAtop(&destination,&source,&composite); |
| break; |
| } |
| case XorCompositeOp: |
| { |
| CompositeXor(&source,&destination,&composite); |
| break; |
| } |
| case PlusCompositeOp: |
| { |
| CompositePlus(image,&source,&destination,&composite); |
| break; |
| } |
| case MinusDstCompositeOp: |
| { |
| CompositeMinus(image,&source,&destination,&composite); |
| break; |
| } |
| case MinusSrcCompositeOp: |
| { |
| CompositeMinus(image,&destination,&source,&composite); |
| break; |
| } |
| case ModulusAddCompositeOp: |
| { |
| CompositeModulusAdd(image,&source,&destination,&composite); |
| break; |
| } |
| case ModulusSubtractCompositeOp: |
| { |
| CompositeModulusSubtract(image,&source,&destination,&composite); |
| break; |
| } |
| case DifferenceCompositeOp: |
| { |
| CompositeDifference(image,&source,&destination,&composite); |
| break; |
| } |
| case ExclusionCompositeOp: |
| { |
| CompositeExclusion(image,&source,&destination,&composite); |
| break; |
| } |
| case MultiplyCompositeOp: |
| { |
| CompositeMultiply(image,&source,&destination,&composite); |
| break; |
| } |
| case ScreenCompositeOp: |
| { |
| CompositeScreen(image,&source,&destination,&composite); |
| break; |
| } |
| case DivideDstCompositeOp: |
| { |
| CompositeDivide(image,&source,&destination,&composite); |
| break; |
| } |
| case DivideSrcCompositeOp: |
| { |
| CompositeDivide(image,&destination,&source,&composite); |
| break; |
| } |
| case DarkenCompositeOp: |
| { |
| CompositeDarken(image,&source,&destination,&composite); |
| break; |
| } |
| case LightenCompositeOp: |
| { |
| CompositeLighten(image,&source,&destination,&composite); |
| break; |
| } |
| case DarkenIntensityCompositeOp: |
| { |
| CompositeDarkenIntensity(image,&source,&destination,&composite); |
| break; |
| } |
| case LightenIntensityCompositeOp: |
| { |
| CompositeLightenIntensity(image,&source,&destination,&composite); |
| break; |
| } |
| case MathematicsCompositeOp: |
| { |
| CompositeMathematics(image,&source,&destination,&geometry_info, |
| &composite); |
| break; |
| } |
| case ColorDodgeCompositeOp: |
| { |
| CompositeColorDodge(&source,&destination,&composite); |
| break; |
| } |
| case ColorBurnCompositeOp: |
| { |
| CompositeColorBurn(&source,&destination,&composite); |
| break; |
| } |
| case LinearDodgeCompositeOp: |
| { |
| CompositeLinearDodge(&source,&destination,&composite); |
| break; |
| } |
| case LinearBurnCompositeOp: |
| { |
| CompositeLinearBurn(&source,&destination,&composite); |
| break; |
| } |
| case HardLightCompositeOp: |
| { |
| CompositeHardLight(&source,&destination,&composite); |
| break; |
| } |
| case OverlayCompositeOp: |
| { |
| CompositeHardLight(&destination,&source,&composite); |
| break; |
| } |
| case SoftLightCompositeOp: |
| { |
| CompositeSoftLight(&source,&destination,&composite); |
| break; |
| } |
| case LinearLightCompositeOp: |
| { |
| CompositeLinearLight(&source,&destination,&composite); |
| break; |
| } |
| case PegtopLightCompositeOp: |
| { |
| CompositePegtopLight(&source,&destination,&composite); |
| break; |
| } |
| case VividLightCompositeOp: |
| { |
| CompositeVividLight(&source,&destination,&composite); |
| break; |
| } |
| case PinLightCompositeOp: |
| { |
| CompositePinLight(&source,&destination,&composite); |
| break; |
| } |
| case ChangeMaskCompositeOp: |
| { |
| if ((composite.alpha > ((MagickRealType) QuantumRange/2.0)) || |
| (IsFuzzyEquivalencePixelInfo(&source,&destination) != MagickFalse)) |
| composite.alpha=(MagickRealType) TransparentAlpha; |
| else |
| composite.alpha=(MagickRealType) OpaqueAlpha; |
| break; |
| } |
| case BumpmapCompositeOp: |
| { |
| if (source.alpha == TransparentAlpha) |
| break; |
| CompositeBumpmap(&source,&destination,&composite); |
| break; |
| } |
| case DissolveCompositeOp: |
| { |
| CompositePixelInfoOver(&source,source_dissolve*source.alpha, |
| &destination,(MagickRealType) (destination_dissolve* |
| destination.alpha),&composite); |
| break; |
| } |
| case BlendCompositeOp: |
| { |
| CompositePixelInfoBlend(&source,source_dissolve,&destination, |
| destination_dissolve,&composite); |
| break; |
| } |
| case ThresholdCompositeOp: |
| { |
| CompositeThreshold(&source,&destination,threshold,amount,&composite); |
| break; |
| } |
| case ModulateCompositeOp: |
| { |
| ssize_t |
| offset; |
| |
| if (source.alpha == TransparentAlpha) |
| break; |
| offset=(ssize_t) (GetPixelInfoIntensity(&source)-midpoint); |
| if (offset == 0) |
| break; |
| CompositeHSB(destination.red,destination.green,destination.blue,&hue, |
| &saturation,&brightness); |
| brightness+=(0.01*percent_brightness*offset)/midpoint; |
| saturation*=0.01*percent_saturation; |
| HSBComposite(hue,saturation,brightness,&composite.red, |
| &composite.green,&composite.blue); |
| break; |
| } |
| case HueCompositeOp: |
| { |
| if (source.alpha == TransparentAlpha) |
| break; |
| if (destination.alpha == TransparentAlpha) |
| { |
| composite=source; |
| break; |
| } |
| CompositeHSB(destination.red,destination.green,destination.blue,&hue, |
| &saturation,&brightness); |
| CompositeHSB(source.red,source.green,source.blue,&hue,&sans,&sans); |
| HSBComposite(hue,saturation,brightness,&composite.red, |
| &composite.green,&composite.blue); |
| if (source.alpha < destination.alpha) |
| composite.alpha=source.alpha; |
| break; |
| } |
| case SaturateCompositeOp: |
| { |
| if (source.alpha == TransparentAlpha) |
| break; |
| if (destination.alpha == TransparentAlpha) |
| { |
| composite=source; |
| break; |
| } |
| CompositeHSB(destination.red,destination.green,destination.blue,&hue, |
| &saturation,&brightness); |
| CompositeHSB(source.red,source.green,source.blue,&sans,&saturation, |
| &sans); |
| HSBComposite(hue,saturation,brightness,&composite.red, |
| &composite.green,&composite.blue); |
| if (source.alpha < destination.alpha) |
| composite.alpha=source.alpha; |
| break; |
| } |
| case LuminizeCompositeOp: |
| { |
| if (source.alpha == TransparentAlpha) |
| break; |
| if (destination.alpha == TransparentAlpha) |
| { |
| composite=source; |
| break; |
| } |
| CompositeHSB(destination.red,destination.green,destination.blue,&hue, |
| &saturation,&brightness); |
| CompositeHSB(source.red,source.green,source.blue,&sans,&sans, |
| &brightness); |
| HSBComposite(hue,saturation,brightness,&composite.red, |
| &composite.green,&composite.blue); |
| if (source.alpha < destination.alpha) |
| composite.alpha=source.alpha; |
| break; |
| } |
| case ColorizeCompositeOp: |
| { |
| if (source.alpha == TransparentAlpha) |
| break; |
| if (destination.alpha == TransparentAlpha) |
| { |
| composite=source; |
| break; |
| } |
| CompositeHSB(destination.red,destination.green,destination.blue,&sans, |
| &sans,&brightness); |
| CompositeHSB(source.red,source.green,source.blue,&hue,&saturation, |
| &sans); |
| HSBComposite(hue,saturation,brightness,&composite.red, |
| &composite.green,&composite.blue); |
| if (source.alpha < destination.alpha) |
| composite.alpha=source.alpha; |
| break; |
| } |
| case CopyRedCompositeOp: |
| case CopyCyanCompositeOp: |
| { |
| composite.red=source.red; |
| break; |
| } |
| case CopyGreenCompositeOp: |
| case CopyMagentaCompositeOp: |
| { |
| composite.green=source.green; |
| break; |
| } |
| case CopyBlueCompositeOp: |
| case CopyYellowCompositeOp: |
| { |
| composite.blue=source.blue; |
| break; |
| } |
| case CopyOpacityCompositeOp: |
| { |
| if (source.matte == MagickFalse) |
| { |
| composite.alpha=(MagickRealType) GetPixelInfoIntensity(&source); |
| break; |
| } |
| composite.alpha=source.alpha; |
| break; |
| } |
| case CopyBlackCompositeOp: |
| { |
| if (source.colorspace != CMYKColorspace) |
| ConvertRGBToCMYK(&source); |
| composite.black=source.black; |
| break; |
| } |
| case BlurCompositeOp: |
| case DisplaceCompositeOp: |
| case DistortCompositeOp: |
| { |
| composite=source; |
| break; |
| } |
| default: |
| break; |
| } |
| if (image->colorspace == CMYKColorspace) |
| { |
| composite.red=(MagickRealType) QuantumRange-composite.red; |
| composite.green=(MagickRealType) QuantumRange-composite.green; |
| composite.blue=(MagickRealType) QuantumRange-composite.blue; |
| composite.black=(MagickRealType) QuantumRange-composite.black; |
| } |
| SetPixelRed(image,ClampToQuantum(composite.red),q); |
| SetPixelGreen(image,ClampToQuantum(composite.green),q); |
| SetPixelBlue(image,ClampToQuantum(composite.blue),q); |
| if (image->colorspace == CMYKColorspace) |
| SetPixelBlack(image,ClampToQuantum(composite.black),q); |
| SetPixelAlpha(image,ClampToQuantum(composite.alpha),q); |
| p+=GetPixelChannels(composite_image); |
| if (p >= (pixels+composite_image->columns*GetPixelChannels(composite_image))) |
| p=pixels; |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp critical (MagickCore_CompositeImage) |
| #endif |
| proceed=SetImageProgress(image,CompositeImageTag,progress++, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| composite_view=DestroyCacheView(composite_view); |
| image_view=DestroyCacheView(image_view); |
| if (destination_image != (Image * ) NULL) |
| destination_image=DestroyImage(destination_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) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o texture: This image is the texture to layer on the background. |
| % |
| */ |
| MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture) |
| { |
| #define TextureImageTag "Texture/Image" |
| |
| CacheView |
| *image_view, |
| *texture_view; |
| |
| ExceptionInfo |
| *exception; |
| |
| MagickBooleanType |
| status; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); |
| assert(image->signature == MagickSignature); |
| if (texture == (const Image *) NULL) |
| return(MagickFalse); |
| (void) SetImageVirtualPixelMethod(texture,TileVirtualPixelMethod); |
| exception=(&image->exception); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| status=MagickTrue; |
| if ((image->compose != CopyCompositeOp) && |
| ((image->compose != OverCompositeOp) || (image->matte != MagickFalse) || |
| (texture->matte != MagickFalse))) |
| { |
| /* |
| Tile texture onto the image background. |
| */ |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture->rows) |
| { |
| register ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture->columns) |
| { |
| MagickBooleanType |
| thread_status; |
| |
| thread_status=CompositeImage(image,image->compose,texture,x+ |
| texture->tile_offset.x,y+texture->tile_offset.y); |
| if (thread_status == MagickFalse) |
| { |
| status=thread_status; |
| break; |
| } |
| } |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp critical (MagickCore_TextureImage) |
| #endif |
| proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) |
| y,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType) |
| image->rows,image->rows); |
| return(status); |
| } |
| /* |
| Tile texture onto the image background (optimized). |
| */ |
| status=MagickTrue; |
| image_view=AcquireCacheView(image); |
| texture_view=AcquireCacheView(texture); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| register const Quantum |
| *p, |
| *pixels; |
| |
| register ssize_t |
| x; |
| |
| register Quantum |
| *q; |
| |
| size_t |
| width; |
| |
| if (status == MagickFalse) |
| continue; |
| pixels=GetCacheViewVirtualPixels(texture_view,texture->tile_offset.x,(y+ |
| texture->tile_offset.y) % texture->rows,texture->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->columns) |
| { |
| register ssize_t |
| i; |
| |
| p=pixels; |
| width=texture->columns; |
| if ((x+(ssize_t) width) > (ssize_t) image->columns) |
| width=image->columns-x; |
| for (i=0; i < (ssize_t) width; i++) |
| { |
| SetPixelRed(image,GetPixelRed(texture,p),q); |
| SetPixelGreen(image,GetPixelGreen(texture,p),q); |
| SetPixelBlue(image,GetPixelBlue(texture,p),q); |
| SetPixelAlpha(image,GetPixelAlpha(texture,p),q); |
| if ((image->colorspace == CMYKColorspace) && |
| (texture->colorspace == CMYKColorspace)) |
| SetPixelBlack(image,GetPixelBlack(texture,p),q); |
| p+=GetPixelChannels(texture); |
| q+=GetPixelChannels(image); |
| } |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp critical (MagickCore_TextureImage) |
| #endif |
| proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| texture_view=DestroyCacheView(texture_view); |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |