| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % 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+(long) composite_image->columns) < 0) |
| break; |
| if ((x_offset+(long) composite_image->columns) >= (long) image->columns) |
| break; |
| if ((y_offset+(long) composite_image->rows) < 0) |
| 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(_OPENMP) && (_OPENMP >= 200203) |
| #pragma omp parallel for schedule(static,1) 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=QueueCacheViewAuthenticPixels(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(_OPENMP) && (_OPENMP >= 200203) |
| #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(_OPENMP) && (_OPENMP >= 200203) |
| #pragma omp parallel for schedule(static,1) 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(_OPENMP) && (_OPENMP >= 200203) |
| #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); |
| if (SetImageStorageClass(image,DirectClass) == MagickFalse) |
| return(MagickFalse); |
| status=MagickTrue; |
| if ((image->compose != CopyCompositeOp) && |
| ((image->compose != OverCompositeOp) || (image->matte != MagickFalse))) |
| { |
| /* |
| Tile texture onto the image background. |
| */ |
| #if defined(_OPENMP) && (_OPENMP >= 200203) |
| #pragma omp parallel for schedule(static,1) 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(_OPENMP) && (_OPENMP >= 200203) |
| #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(_OPENMP) && (_OPENMP >= 200203) |
| #pragma omp parallel for schedule(static,1) 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,0,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(_OPENMP) && (_OPENMP >= 200203) |
| #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); |
| } |