/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % 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-2009 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 "magick/studio.h" #include "magick/artifact.h" #include "magick/cache-view.h" #include "magick/client.h" #include "magick/color.h" #include "magick/color-private.h" #include "magick/colorspace.h" #include "magick/colorspace-private.h" #include "magick/composite.h" #include "magick/composite-private.h" #include "magick/constitute.h" #include "magick/draw.h" #include "magick/fx.h" #include "magick/gem.h" #include "magick/geometry.h" #include "magick/image.h" #include "magick/image-private.h" #include "magick/list.h" #include "magick/log.h" #include "magick/monitor.h" #include "magick/monitor-private.h" #include "magick/memory_.h" #include "magick/option.h" #include "magick/pixel-private.h" #include "magick/property.h" #include "magick/quantum.h" #include "magick/resample.h" #include "magick/resource_.h" #include "magick/string_.h" #include "magick/utility.h" #include "magick/version.h" /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % C o m p o s i t e I m a g e C h a n n e l % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % CompositeImageChannel() returns the second image composited onto the first % at the specified offset, using the specified composite method. % % The format of the CompositeImageChannel method is: % % MagickBooleanType CompositeImage(Image *image, % const CompositeOperator compose,Image *composite_image, % const long x_offset,const long y_offset) % MagickBooleanType CompositeImageChannel(Image *image, % const ChannelType channel,const CompositeOperator compose, % Image *composite_image,const long x_offset,const long y_offset) % % A description of each parameter follows: % % o image: the destination image, modified by he composition % % o channel: the channel. % % 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 */ static inline MagickRealType Add(const MagickRealType p,const MagickRealType q) { MagickRealType pixel; pixel=p+q; if (pixel > QuantumRange) pixel-=(QuantumRange+1.0); return(pixel); } static inline void CompositeAdd(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { composite->red=Add(p->red,q->red); composite->green=Add(p->green,q->green); composite->blue=Add(p->blue,q->blue); composite->opacity=Add(alpha,beta); if (q->colorspace == CMYKColorspace) composite->index=Add(p->index,q->index); } 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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ composite->opacity=beta; /* optimized 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->index=Atop(p->index,Sa,q->index,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 MagickPixelPacket *p, const MagickRealType magick_unused(alpha),const MagickPixelPacket *q, const MagickRealType magick_unused(beta),MagickPixelPacket *composite) { MagickRealType intensity; intensity=MagickPixelIntensity(p); composite->red=QuantumScale*intensity*q->red; composite->green=QuantumScale*intensity*q->green; composite->blue=QuantumScale*intensity*q->blue; composite->opacity=(MagickRealType) QuantumScale*intensity*p->opacity; if (q->colorspace == CMYKColorspace) composite->index=QuantumScale*intensity*q->index; } static inline void CompositeClear(const MagickPixelPacket *q, MagickPixelPacket *composite) { composite->opacity=(MagickRealType) TransparentOpacity; composite->red=0.0; composite->green=0.0; composite->blue=0.0; if (q->colorspace == CMYKColorspace) composite->index=0.0; } static MagickRealType ColorBurn(const MagickRealType Sca, const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da) { #if 0 /* Oct 2004 SVG specification. */ if (Sca*Da + Dca*Sa <= Sa*Da) return(Sca*(1.0-Da)+Dca*(1.0-Sa)); return(Sa*(Sca*Da+Dca*Sa-Sa*Da)/Sca + Sca*(1.0-Da) + Dca*(1.0-Sa)); #else /* March 2009 SVG specification. */ 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)); #endif } static inline void CompositeColorBurn(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*ColorBurn(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static MagickRealType ColorDodge(const MagickRealType Sca, const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da) { #if 0 /* Oct 2004 SVG specification. */ if ((Sca*Da+Dca*Sa) >= Sa*Da) 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) ); #endif #if 0 /* New specification, March 2009 SVG specification. This specification was also wrong of non-overlap cases. */ if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon)) return(Sca*(1.0-Da)); if (fabs(Sca-Sa) < MagickEpsilon) return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); return(Sa*MagickMin(Da,Dca*Sa/(Sa-Sca))); #endif /* Working from first principles using the original formula: f(Sc,Dc) = Dc/(1-Sc) This works correctly! Looks like the 2004 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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*ColorDodge(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType gamma; gamma=1.0-QuantumScale*QuantumScale*alpha*beta; composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*Darken(p->red,alpha,q->red,beta); composite->green=gamma*Darken(p->green,alpha,q->green,beta); composite->blue=gamma*Darken(p->blue,alpha,q->blue,beta); if (q->colorspace == CMYKColorspace) composite->index=gamma*Darken(p->index,alpha,q->index,beta); } 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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); /* Values not normalized as an optimization. */ 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->index=gamma*Difference(p->index,Sa,q->index,Da); } static MagickRealType Divide(const MagickRealType Sca,const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da) { /* Divide: f(Sc,Dc) = Sc/Dc But with appropriate handling for special case of Dc == 0 specifically f(Black,Black) = Black and f(non-Black,Black) = White. It is however also important to correctly do 'over' alpha blending which is why it becomes so complex looking. */ 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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*Divide(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType gamma, Sa, Da; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*Exclusion(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*HardLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*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 (max == 0.0) return; *saturation=(double) (1.0-min/max); delta=max-min; if (delta == 0.0) return; if (red == max) *hue=(double) ((green-blue)/delta); else if (green == max) *hue=(double) (2.0+(blue-red)/delta); else if (blue == max) *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 alpha,const MagickRealType magick_unused(q), const MagickRealType beta) { return((1.0-QuantumScale*alpha)*p*(1.0-QuantumScale*beta)); } static inline void CompositeIn(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType gamma; gamma=(1.0-QuantumScale*alpha)*(1.0-QuantumScale*beta); composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*In(p->red,alpha,q->red,beta); composite->green=gamma*In(p->green,alpha,q->green,beta); composite->blue=gamma*In(p->blue,alpha,q->blue,beta); if (q->colorspace == CMYKColorspace) composite->index=gamma*In(p->index,alpha,q->index,beta); } 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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType gamma; composite->opacity=QuantumScale*alpha*beta; /* optimized 1-gamma */ gamma=1.0-QuantumScale*composite->opacity; gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*Lighten(p->red,alpha,q->red,beta); composite->green=gamma*Lighten(p->green,alpha,q->green,beta); composite->blue=gamma*Lighten(p->blue,alpha,q->blue,beta); if (q->colorspace == CMYKColorspace) composite->index=gamma*Lighten(p->index,alpha,q->index,beta); } static inline void CompositeLinearDodge(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); /* Operation performed directly - not need for sub-routine. */ 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->index=gamma*(p->index*Sa+q->index*Da); } static inline MagickRealType LinearBurn(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 + Sc - 1 */ return(Sca+Dca-Sa*Da); } static inline void CompositeLinearBurn(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*LinearBurn(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static inline MagickRealType LinearLight(const MagickRealType Sca, const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) { #if 0 /* Previous formula, only valid for fully-opaque images. */ return(Dca+2*Sca-1.0); #else /* 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); #endif } static inline void CompositeLinearLight(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*LinearLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static inline MagickRealType Mathematics(const MagickRealType Sca, const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da, const GeometryInfo *geometry_info) { /* '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) */ return(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)); } static inline void CompositeMathematics(const MagickPixelPacket *p, const MagickPixelPacket *q,const GeometryInfo *args, MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*p->opacity; Da=1.0-QuantumScale*q->opacity; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*Mathematics(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da,args); } static inline MagickRealType Minus(const MagickRealType Sca, const MagickRealType Dca) { return(Sca-Dca); } static inline void CompositeMinus(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa-Da); /* is this correct? - I do not think so! */ composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*Minus(p->red*Sa,q->red*Da); composite->green=gamma*Minus(p->green*Sa,q->green*Da); composite->blue=gamma*Minus(p->blue*Sa,q->blue*Da); if (q->colorspace == CMYKColorspace) composite->index=gamma*Minus(p->index*Sa,q->index*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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*Multiply(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static inline MagickRealType Out(const MagickRealType p, const MagickRealType alpha,const MagickRealType magick_unused(q), const MagickRealType beta) { return((1.0-QuantumScale*alpha)*p*QuantumScale*beta); } static inline void CompositeOut(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType gamma; gamma=(1.0-QuantumScale*alpha)*QuantumScale*beta; composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*Out(p->red,alpha,q->red,beta); composite->green=gamma*Out(p->green,alpha,q->green,beta); composite->blue=gamma*Out(p->blue,alpha,q->blue,beta); if (q->colorspace == CMYKColorspace) composite->index=gamma*Out(p->index,alpha,q->index,beta); } static inline void CompositeOver(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickPixelCompositeOver(p,alpha,q,beta,composite); } static MagickRealType PegtopLight(const MagickRealType Sca, const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da) { /* PegTOP Soft-Light alternative: A continuous version of the Softlight function, producing very similar results however it does not take into account alpha channel. 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*Sca)/Da+Sca*(2*Dca+1-Da)+Dca*(1-Sa)); } static inline void CompositePegtopLight(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*PegtopLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*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*Sca-Sa)) return(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa)); if ((Dca*Sa) > (2*Sca*Da)) return(Sca*Da+Sca+Dca*(1.0-Sa)); return(Sca*(1.0-Da)+Dca); } static inline void CompositePinLight(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*PinLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static inline void CompositePlus(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickPixelCompositePlus(p,alpha,q,beta,composite); } static inline MagickRealType Screen(const MagickRealType Sca, const MagickRealType Dca) { return(Sca+Dca-Sca*Dca); } static inline void CompositeScreen(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma); composite->red=gamma*Screen(QuantumScale*p->red*Sa,QuantumScale* q->red*Da); composite->green=gamma*Screen(QuantumScale*p->green*Sa,QuantumScale* q->green*Da); composite->blue=gamma*Screen(QuantumScale*p->blue*Sa,QuantumScale* q->blue*Da); if (q->colorspace == CMYKColorspace) composite->index=gamma*Screen(QuantumScale*p->index*Sa,QuantumScale* q->index*Da); } static MagickRealType SoftLight(const MagickRealType Sca, const MagickRealType Sa, const MagickRealType Dca, const MagickRealType Da) { #if 0 /* Oct 2004 SVG specification -- spec discovered to be incorrect See http://lists.w3.org/Archives/Public/www-svg/2009Feb/0014.html. */ if (2.0*Sca < Sa) return(Dca*(Sa-(1.0-Dca/Da)*(2.0*Sca-Sa))+Sca*(1.0-Da)+Dca*(1.0-Sa)); if (8.0*Dca <= Da) return(Dca*(Sa-(1.0-Dca/Da)*(2.0*Sca-Sa)*(3.0-8.0*Dca/Da))+ Sca*(1.0-Da)+Dca*(1.0-Sa)); return((Dca*Sa+(pow(Dca/Da,0.5)*Da-Dca)*(2.0*Sca-Sa))+Sca*(1.0-Da)+ Dca*(1.0-Sa)); #else 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); #endif } static inline void CompositeSoftLight(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*SoftLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static inline MagickRealType Subtract(const MagickRealType p, const MagickRealType magick_unused(alpha),const MagickRealType q, const MagickRealType magick_unused(beta)) { MagickRealType pixel; pixel=p-q; if (pixel < 0.0) pixel+=(QuantumRange+1.0); return(pixel); } static inline void CompositeSubtract(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { composite->red=Subtract(p->red,alpha,q->red,beta); composite->green=Subtract(p->green,alpha,q->green,beta); composite->blue=Subtract(p->blue,alpha,q->blue,beta); if (q->colorspace == CMYKColorspace) composite->index=Subtract(p->index,alpha,q->index,beta); } static inline MagickRealType Threshold(const MagickRealType p, const MagickRealType magick_unused(alpha),const MagickRealType q, const MagickRealType magick_unused(beta),const MagickRealType threshold, const MagickRealType amount) { MagickRealType delta; delta=p-q; if ((MagickRealType) fabs((double) (2.0*delta)) < threshold) return(q); return(q+delta*amount); } static inline void CompositeThreshold(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,const MagickRealType threshold, const MagickRealType amount,MagickPixelPacket *composite) { composite->red=Threshold(p->red,alpha,q->red,beta,threshold,amount); composite->green=Threshold(p->green,alpha,q->green,beta,threshold,amount); composite->blue=Threshold(p->blue,alpha,q->blue,beta,threshold,amount); composite->opacity=(MagickRealType) QuantumRange- Threshold(p->opacity,alpha,q->opacity,beta,threshold,amount); if (q->colorspace == CMYKColorspace) composite->index=Threshold(p->index,alpha,q->index,beta,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*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 MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */ composite->opacity=(MagickRealType) QuantumRange*(1.0-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->index=gamma*VividLight(QuantumScale*p->index*Sa,Sa,QuantumScale* q->index*Da,Da); } static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da) { return(Sca*(1-Da)+Dca*(1-Sa)); } static inline void CompositeXor(const MagickPixelPacket *p, const MagickRealType alpha,const MagickPixelPacket *q, const MagickRealType beta,MagickPixelPacket *composite) { MagickRealType Da, gamma, Sa; Sa=1.0-QuantumScale*alpha; /* simplify and speed up equations */ Da=1.0-QuantumScale*beta; gamma=Sa+Da-2*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */ composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma); /* Optimized by multipling QuantumRange taken from 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->index=gamma*Xor(p->index*Sa,Sa,q->index*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 long x_offset,const long y_offset) { MagickBooleanType status; status=CompositeImageChannel(image,DefaultChannels,compose,composite_image, x_offset,y_offset); return(status); } MagickExport MagickBooleanType CompositeImageChannel(Image *image, const ChannelType magick_unused(channel),const CompositeOperator compose, const Image *composite_image,const long x_offset,const long y_offset) { #define CompositeImageTag "Composite/Image" CacheView *composite_view, *image_view; const char *value; double sans; ExceptionInfo *exception; GeometryInfo geometry_info; Image *destination_image; long progress, y; MagickBooleanType modify_outside_overlay, status; MagickPixelPacket zero; MagickRealType amount, destination_dissolve, midpoint, percent_brightness, percent_saturation, source_dissolve, threshold; MagickStatusType flags; /* 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); if (SetImageStorageClass(image,DirectClass) == MagickFalse) return(MagickFalse); GetMagickPixelPacket(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 OverCompositeOp: { if (image->matte != MagickFalse) break; if (composite_image->matte != MagickFalse) break; } case CopyCompositeOp: { if ((x_offset < 0) || (y_offset < 0)) break; if ((x_offset+(long) composite_image->columns) >= (long) image->columns) break; if ((y_offset+(long) composite_image->rows) >= (long) image->rows) break; status=MagickTrue; 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(status) #endif for (y=0; y < (long) composite_image->rows; y++) { MagickBooleanType sync; register const IndexPacket *composite_indexes; register const PixelPacket *p; register IndexPacket *indexes; register PixelPacket *q; 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 PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } composite_indexes=GetCacheViewVirtualIndexQueue(composite_view); indexes=GetCacheViewAuthenticIndexQueue(image_view); (void) CopyMagickMemory(q,p,composite_image->columns*sizeof(*p)); if ((indexes != (IndexPacket *) NULL) && (composite_indexes != (const IndexPacket *) NULL)) (void) CopyMagickMemory(indexes,composite_indexes, composite_image->columns*sizeof(*indexes)); 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,CompositeImageTag,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); modify_outside_overlay=MagickTrue; break; } case BlurCompositeOp: { MagickPixelPacket pixel; MagickRealType blur_xu, blur_xv, blur_yu, blur_yv; ResampleFilter *resample_filter; CacheView *composite_view, *dest_view; /* 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); } blur_xu=geometry_info.rho; blur_yv=geometry_info.sigma; blur_xv=blur_yu = 0.0; if ((flags & HeightValue) == 0) blur_yv=blur_xu; if ((flags & XValue) != 0) { MagickRealType angle, x, y; x=blur_xu; y=blur_yv; angle=DegreesToRadians(geometry_info.xi); blur_xu=x*cos(angle); blur_xv=x*sin(angle); blur_yu=(-y*sin(angle)); blur_yu=y*cos(angle); } /* Blur Image by resampling; */ pixel=zero; exception=(&image->exception); resample_filter=AcquireResampleFilter(image,&image->exception); SetResampleFilter(resample_filter,GaussianFilter,1.0); dest_view=AcquireCacheView(destination_image); composite_view=AcquireCacheView(composite_image); for (y=0; y < (long) composite_image->rows; y++) { MagickBooleanType sync; register const PixelPacket *restrict p; register PixelPacket *restrict r; register IndexPacket *restrict destination_indexes; register long x; if (((y+y_offset) < 0) || ((y+y_offset) >= (long) image->rows)) continue; p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns, 1,exception); r=QueueCacheViewAuthenticPixels(dest_view,0,y, destination_image->columns,1,&image->exception); if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL)) break; destination_indexes=GetCacheViewAuthenticIndexQueue(dest_view); for (x=0; x < (long) composite_image->columns; x++) { if (((x_offset+x) < 0) || ((x_offset+x) >= (long) image->columns)) { p++; continue; } ScaleResampleFilter(resample_filter,blur_xu*QuantumScale*p->red, blur_yu*QuantumScale*p->green,blur_xv*QuantumScale*p->red, blur_yv*QuantumScale*p->green); (void) ResamplePixelColor(resample_filter,(double) x_offset+x, (double) y_offset+y,&pixel); SetPixelPacket(destination_image,&pixel,r,destination_indexes+x); p++; r++; } sync=SyncCacheViewAuthenticPixels(dest_view,exception); if (sync == MagickFalse) break; } resample_filter=DestroyResampleFilter(resample_filter); composite_view=DestroyCacheView(composite_view); dest_view=DestroyCacheView(dest_view); composite_image=destination_image; break; } case DisplaceCompositeOp: case DistortCompositeOp: { MagickPixelPacket pixel; MagickRealType horizontal_scale, vertical_scale, x_center, y_center, x_lookup, y_lookup; register IndexPacket *restrict destination_indexes; register PixelPacket *restrict r; ResampleFilter *resample_filter; CacheView *composite_view, *dest_view; /* Displace/Distort based on overlay gradient map: X = red_channel; Y = green_channel; compose:args = x_scale[,y_scale[,x_center,y_center]] */ 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 lookup relative to a fixed absolute point Select that point according to +X+Y user inputs. default = center of overlay image flag '!' = locations/percentage relative to background image */ x_center=(MagickRealType) x_offset; y_center=(MagickRealType) y_offset; if (compose == DistortCompositeOp) { if ((flags & XValue) == 0) if ((flags & AspectValue) == 0) x_center=(MagickRealType) x_offset+ (composite_image->columns-1)/ 2.0; else x_center=((MagickRealType) image->columns-1)/2.0; else if ((flags & AspectValue) == 0) x_center=(MagickRealType) x_offset+geometry_info.xi; else x_center=geometry_info.xi; if ((flags & YValue) == 0) if ((flags & AspectValue) == 0) y_center=(MagickRealType) y_offset+ (composite_image->rows-1)/2.0; else y_center=((MagickRealType) image->rows-1)/2.0; else if ((flags & AspectValue) == 0) y_center=(MagickRealType) y_offset+geometry_info.psi; else y_center=geometry_info.psi; } /* Shift the pixel lookup point as defined by the provided, displacement/distortion map. -- Like a lens... */ pixel=zero; exception=(&image->exception); resample_filter=AcquireResampleFilter(image,&image->exception); dest_view=AcquireCacheView(destination_image); composite_view=AcquireCacheView(composite_image); for (y=0; y < (long) composite_image->rows; y++) { MagickBooleanType sync; register const PixelPacket *restrict p; register long x; if (((y+y_offset) < 0) || ((y+y_offset) >= (long) image->rows)) continue; p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns, 1,exception); r=QueueCacheViewAuthenticPixels(dest_view,0,y, destination_image->columns,1,&image->exception); if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL)) break; destination_indexes=GetCacheViewAuthenticIndexQueue(dest_view); for (x=0; x < (long) composite_image->columns; x++) { if (((x_offset+x) < 0) || ((x_offset+x) >= (long) image->columns)) { p++; continue; } /* Displace the lookup. */ x_lookup=(horizontal_scale*(p->red-(((MagickRealType) QuantumRange+ 1.0)/2.0)))/(((MagickRealType) QuantumRange+1.0)/2.0)+ x_center+((compose == DisplaceCompositeOp) ? x : 0); y_lookup=(vertical_scale*(p->green-(((MagickRealType) QuantumRange+ 1.0)/2.0)))/(((MagickRealType) QuantumRange+1.0)/2.0)+ y_center+((compose == DisplaceCompositeOp) ? y : 0); (void) ResamplePixelColor(resample_filter,(double) x_lookup, (double) y_lookup,&pixel); /* Mask with 'invalid pixel mask' in alpha channel. */ pixel.opacity = (MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale* pixel.opacity)*(1.0-QuantumScale*p->opacity)); SetPixelPacket(destination_image,&pixel,r,destination_indexes+x); p++; r++; } sync=SyncCacheViewAuthenticPixels(dest_view,exception); if (sync == MagickFalse) break; } resample_filter=DestroyResampleFilter(resample_filter); composite_view=DestroyCacheView(composite_view); dest_view=DestroyCacheView(dest_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; GetMagickPixelPacket(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 < (long) image->rows; y++) { const PixelPacket *pixels; double brightness, hue, saturation; MagickPixelPacket composite, destination, source; register const IndexPacket *restrict composite_indexes; register const PixelPacket *restrict p; register IndexPacket *restrict indexes; register long x; register PixelPacket *restrict q; if (status == MagickFalse) continue; if (modify_outside_overlay == MagickFalse) { if (y < y_offset) continue; if ((y-y_offset) >= (long) composite_image->rows) continue; } /* If pixels is NULL, y is outside overlay region. */ pixels=(PixelPacket *) NULL; p=(PixelPacket *) NULL; if ((y >= y_offset) && ((y-y_offset) < (long) composite_image->rows)) { p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset, composite_image->columns,1,exception); if (p == (const PixelPacket *) NULL) { status=MagickFalse; continue; } pixels=p; if (x_offset < 0) p-=x_offset; } q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1, exception); if (q == (PixelPacket *) NULL) { status=MagickFalse; continue; } indexes=GetCacheViewAuthenticIndexQueue(image_view); composite_indexes=GetCacheViewVirtualIndexQueue(composite_view); source=zero; destination=zero; hue=0.0; saturation=0.0; brightness=0.0; for (x=0; x < (long) image->columns; x++) { if (modify_outside_overlay == MagickFalse) { if (x < x_offset) { q++; continue; } if ((x-x_offset) >= (long) composite_image->columns) break; } destination.red=(MagickRealType) q->red; destination.green=(MagickRealType) q->green; destination.blue=(MagickRealType) q->blue; if (image->matte != MagickFalse) destination.opacity=(MagickRealType) q->opacity; if (image->colorspace == CMYKColorspace) { destination.red=(MagickRealType) QuantumRange-destination.red; destination.green=(MagickRealType) QuantumRange-destination.green; destination.blue=(MagickRealType) QuantumRange-destination.blue; destination.index=(MagickRealType) (QuantumRange-indexes[x]); } /* Handle destination modifications outside overlaid region. */ composite=destination; if ((pixels == (PixelPacket *) NULL) || (x < x_offset) || ((x-x_offset) >= (long) composite_image->columns)) { switch (compose) { case DissolveCompositeOp: case BlendCompositeOp: { composite.opacity=(MagickRealType) (QuantumRange- destination_dissolve*(QuantumRange-composite.opacity)); 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.opacity=(MagickRealType) TransparentOpacity; 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.index=(MagickRealType) QuantumRange-composite.index; } q->red=RoundToQuantum(composite.red); q->green=RoundToQuantum(composite.green); q->blue=RoundToQuantum(composite.blue); if (image->matte != MagickFalse) q->opacity=RoundToQuantum(composite.opacity); if (image->colorspace == CMYKColorspace) indexes[x]=RoundToQuantum(composite.index); q++; continue; } /* Handle normal overlay of source onto destination. */ source.red=(MagickRealType) p->red; source.green=(MagickRealType) p->green; source.blue=(MagickRealType) p->blue; if (composite_image->matte != MagickFalse) source.opacity=(MagickRealType) p->opacity; if (composite_image->colorspace == CMYKColorspace) { source.red=(MagickRealType) QuantumRange-source.red; source.green=(MagickRealType) QuantumRange-source.green; source.blue=(MagickRealType) QuantumRange-source.blue; source.index=(MagickRealType) QuantumRange-(MagickRealType) composite_indexes[x-x_offset]; } switch (compose) { case AddCompositeOp: { CompositeAdd(&source,source.opacity,&destination,destination.opacity, &composite); break; } case ClearCompositeOp: { CompositeClear(&destination,&composite); break; } case SrcCompositeOp: case CopyCompositeOp: case ReplaceCompositeOp: { composite=source; break; } case ChangeMaskCompositeOp: { if ((composite.opacity > ((MagickRealType) QuantumRange/2.0)) || (IsMagickColorSimilar(&source,&destination) != MagickFalse)) composite.opacity=(MagickRealType) TransparentOpacity; else composite.opacity=(MagickRealType) OpaqueOpacity; break; } case DivideCompositeOp: { CompositeDivide(&source,source.opacity,&destination, destination.opacity,&composite); break; } case DstCompositeOp: break; case OverCompositeOp: case SrcOverCompositeOp: { CompositeOver(&source,source.opacity,&destination,destination.opacity, &composite); break; } case DstOverCompositeOp: { CompositeOver(&destination,destination.opacity,&source,source.opacity, &composite); break; } case SrcInCompositeOp: case InCompositeOp: { CompositeIn(&source,source.opacity,&destination,destination.opacity, &composite); break; } case DstInCompositeOp: { CompositeIn(&destination,destination.opacity,&source,source.opacity, &composite); break; } case OutCompositeOp: case SrcOutCompositeOp: { CompositeOut(&source,source.opacity,&destination,destination.opacity, &composite); break; } case DstOutCompositeOp: { CompositeOut(&destination,destination.opacity,&source,source.opacity, &composite); break; } case AtopCompositeOp: case SrcAtopCompositeOp: { CompositeAtop(&source,source.opacity,&destination,destination.opacity, &composite); break; } case DstAtopCompositeOp: { CompositeAtop(&destination,destination.opacity,&source,source.opacity, &composite); break; } case XorCompositeOp: { CompositeXor(&source,source.opacity,&destination,destination.opacity, &composite); break; } case PlusCompositeOp: { CompositePlus(&source,source.opacity,&destination,destination.opacity, &composite); break; } case MultiplyCompositeOp: { CompositeMultiply(&source,source.opacity,&destination, destination.opacity,&composite); break; } case ScreenCompositeOp: { CompositeScreen(&source,source.opacity,&destination, destination.opacity,&composite); break; } case DarkenCompositeOp: { CompositeDarken(&source,source.opacity,&destination, destination.opacity,&composite); break; } case LightenCompositeOp: { CompositeLighten(&source,source.opacity,&destination, destination.opacity,&composite); break; } case ColorDodgeCompositeOp: { CompositeColorDodge(&source,source.opacity,&destination, destination.opacity,&composite); break; } case ColorBurnCompositeOp: { CompositeColorBurn(&source,source.opacity,&destination, destination.opacity,&composite); break; } case LinearDodgeCompositeOp: { CompositeLinearDodge(&source,source.opacity,&destination, destination.opacity,&composite); break; } case LinearBurnCompositeOp: { CompositeLinearBurn(&source,source.opacity,&destination, destination.opacity,&composite); break; } case HardLightCompositeOp: { CompositeHardLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case OverlayCompositeOp: { /* Reversed HardLight. */ CompositeHardLight(&destination,destination.opacity,&source, source.opacity,&composite); break; } case SoftLightCompositeOp: { CompositeSoftLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case LinearLightCompositeOp: { CompositeLinearLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case PegtopLightCompositeOp: { CompositePegtopLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case VividLightCompositeOp: { CompositeVividLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case PinLightCompositeOp: { CompositePinLight(&source,source.opacity,&destination, destination.opacity,&composite); break; } case DifferenceCompositeOp: { CompositeDifference(&source,source.opacity,&destination, destination.opacity,&composite); break; } case ExclusionCompositeOp: { CompositeExclusion(&source,source.opacity,&destination, destination.opacity,&composite); break; } case MinusCompositeOp: { CompositeMinus(&source,source.opacity,&destination, destination.opacity,&composite); break; } case BumpmapCompositeOp: { if (source.opacity == TransparentOpacity) break; CompositeBumpmap(&source,source.opacity,&destination, destination.opacity,&composite); break; } case DissolveCompositeOp: { CompositeOver(&source,(MagickRealType) (QuantumRange-source_dissolve* (QuantumRange-source.opacity)),&destination,(MagickRealType) (QuantumRange-destination_dissolve*(QuantumRange- destination.opacity)),&composite); break; } case BlendCompositeOp: { MagickPixelCompositeBlend(&source,source_dissolve,&destination, destination_dissolve,&composite); break; } case MathematicsCompositeOp: { CompositeMathematics(&source,&destination,&geometry_info,&composite); break; } case BlurCompositeOp: case DisplaceCompositeOp: case DistortCompositeOp: { composite=source; break; } case ThresholdCompositeOp: { CompositeThreshold(&source,source.opacity,&destination, destination.opacity,threshold,amount,&composite); break; } case ModulateCompositeOp: { long offset; if (source.opacity == TransparentOpacity) break; offset=(long) (MagickPixelIntensityToQuantum(&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.opacity == TransparentOpacity) break; if (destination.opacity == TransparentOpacity) { 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.opacity < destination.opacity) composite.opacity=source.opacity; break; } case SaturateCompositeOp: { if (source.opacity == TransparentOpacity) break; if (destination.opacity == TransparentOpacity) { 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.opacity < destination.opacity) composite.opacity=source.opacity; break; } case SubtractCompositeOp: { CompositeSubtract(&source,source.opacity,&destination, destination.opacity,&composite); break; } case LuminizeCompositeOp: { if (source.opacity == TransparentOpacity) break; if (destination.opacity == TransparentOpacity) { 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.opacity < destination.opacity) composite.opacity=source.opacity; break; } case ColorizeCompositeOp: { if (source.opacity == TransparentOpacity) break; if (destination.opacity == TransparentOpacity) { 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.opacity < destination.opacity) composite.opacity=source.opacity; 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.opacity=(MagickRealType) (QuantumRange- MagickPixelIntensityToQuantum(&source)); break; } composite.opacity=source.opacity; break; } case CopyBlackCompositeOp: { if (source.colorspace != CMYKColorspace) ConvertRGBToCMYK(&source); composite.index=source.index; 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.index=(MagickRealType) QuantumRange-composite.index; } q->red=RoundToQuantum(composite.red); q->green=RoundToQuantum(composite.green); q->blue=RoundToQuantum(composite.blue); q->opacity=RoundToQuantum(composite.opacity); if (image->colorspace == CMYKColorspace) indexes[x]=RoundToQuantum(composite.index); p++; if (p >= (pixels+composite_image->columns)) p=pixels; q++; } 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_CompositeImageChannel) #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; long y; MagickBooleanType status; 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); if (SetImageStorageClass(image,DirectClass) == 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) #endif for (y=0; y < (long) image->rows; y+=texture->rows) { register long x; if (status == MagickFalse) continue; for (x=0; x < (long) image->columns; x+=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,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; exception=(&image->exception); image_view=AcquireCacheView(image); texture_view=AcquireCacheView(texture); #if defined(MAGICKCORE_OPENMP_SUPPORT) #pragma omp parallel for schedule(dynamic,4) shared(status) #endif for (y=0; y < (long) image->rows; y++) { MagickBooleanType sync; register const IndexPacket *texture_indexes; register const PixelPacket *p; register IndexPacket *indexes; register long x; register PixelPacket *q; unsigned long width; if (status == MagickFalse) continue; p=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 ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL)) { status=MagickFalse; continue; } texture_indexes=GetCacheViewVirtualIndexQueue(texture_view); indexes=GetCacheViewAuthenticIndexQueue(image_view); for (x=0; x < (long) image->columns; x+=texture->columns) { width=texture->columns; if ((x+(long) width) > (long) image->columns) width=image->columns-x; (void) CopyMagickMemory(q,p,width*sizeof(*p)); if ((image->colorspace == CMYKColorspace) && (texture->colorspace == CMYKColorspace)) { (void) CopyMagickMemory(indexes,texture_indexes,width* sizeof(*indexes)); indexes+=width; } q+=width; } 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,y,image->rows); if (proceed == MagickFalse) status=MagickFalse; } } texture_view=DestroyCacheView(texture_view); image_view=DestroyCacheView(image_view); return(status); }