| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % CCCC OOO M M PPPP OOO SSSSS IIIII TTTTT EEEEE % |
| % C O O MM MM P P O O SS I T E % |
| % C O O M M M PPPP O O SSS I T EEE % |
| % C O O M M P O O SS I T E % |
| % CCCC OOO M M P OOO SSSSS IIIII T EEEEE % |
| % % |
| % % |
| % MagickCore Image Composite Methods % |
| % % |
| % Software Design % |
| % Cristy % |
| % July 1992 % |
| % % |
| % % |
| % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % |
| % dedicated to making software imaging solutions freely available. % |
| % % |
| % You may not use this file except in compliance with the License. You may % |
| % obtain a copy of the License at % |
| % % |
| % https://imagemagick.org/script/license.php % |
| % % |
| % Unless required by applicable law or agreed to in writing, software % |
| % distributed under the License is distributed on an "AS IS" BASIS, % |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
| % See the License for the specific language governing permissions and % |
| % limitations under the License. % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % |
| % |
| */ |
| |
| /* |
| Include declarations. |
| */ |
| #include "MagickCore/studio.h" |
| #include "MagickCore/artifact.h" |
| #include "MagickCore/cache.h" |
| #include "MagickCore/cache-private.h" |
| #include "MagickCore/cache-view.h" |
| #include "MagickCore/channel.h" |
| #include "MagickCore/client.h" |
| #include "MagickCore/color.h" |
| #include "MagickCore/color-private.h" |
| #include "MagickCore/colorspace.h" |
| #include "MagickCore/colorspace-private.h" |
| #include "MagickCore/composite.h" |
| #include "MagickCore/composite-private.h" |
| #include "MagickCore/constitute.h" |
| #include "MagickCore/draw.h" |
| #include "MagickCore/fx.h" |
| #include "MagickCore/gem.h" |
| #include "MagickCore/geometry.h" |
| #include "MagickCore/image.h" |
| #include "MagickCore/image-private.h" |
| #include "MagickCore/list.h" |
| #include "MagickCore/log.h" |
| #include "MagickCore/monitor.h" |
| #include "MagickCore/monitor-private.h" |
| #include "MagickCore/memory_.h" |
| #include "MagickCore/option.h" |
| #include "MagickCore/pixel-accessor.h" |
| #include "MagickCore/property.h" |
| #include "MagickCore/quantum.h" |
| #include "MagickCore/resample.h" |
| #include "MagickCore/resource_.h" |
| #include "MagickCore/string_.h" |
| #include "MagickCore/thread-private.h" |
| #include "MagickCore/threshold.h" |
| #include "MagickCore/token.h" |
| #include "MagickCore/utility.h" |
| #include "MagickCore/utility-private.h" |
| #include "MagickCore/version.h" |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C o m p o s i t e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % CompositeImage() returns the second image composited onto the first |
| % at the specified offset, using the specified composite method. |
| % |
| % The format of the CompositeImage method is: |
| % |
| % MagickBooleanType CompositeImage(Image *image, |
| % const Image *source_image,const CompositeOperator compose, |
| % const MagickBooleanType clip_to_self,const ssize_t x_offset, |
| % const ssize_t y_offset,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the canvas image, modified by he composition |
| % |
| % o source_image: the source image. |
| % |
| % o compose: This operator affects how the composite is applied to |
| % the image. The operators and how they are utilized are listed here |
| % http://www.w3.org/TR/SVG12/#compositing. |
| % |
| % o clip_to_self: set to MagickTrue to limit composition to area composed. |
| % |
| % o x_offset: the column offset of the composited image. |
| % |
| % o y_offset: the row offset of the composited image. |
| % |
| % Extra Controls from Image meta-data in 'image' (artifacts) |
| % |
| % o "compose:args" |
| % A string containing extra numerical arguments for specific compose |
| % methods, generally expressed as a 'geometry' or a comma separated list |
| % of numbers. |
| % |
| % Compose methods needing such arguments include "BlendCompositeOp" and |
| % "DisplaceCompositeOp". |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| /* |
| Composition based on the SVG specification: |
| |
| A Composition is defined by... |
| Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors |
| Blending areas : X = 1 for area of overlap, ie: f(Sc,Dc) |
| Y = 1 for source preserved |
| Z = 1 for canvas preserved |
| |
| Conversion to transparency (then optimized) |
| Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa) |
| Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa) |
| |
| Where... |
| Sca = Sc*Sa normalized Source color divided by Source alpha |
| Dca = Dc*Da normalized Dest color divided by Dest alpha |
| Dc' = Dca'/Da' the desired color value for this channel. |
| |
| Da' in in the follow formula as 'gamma' The resulting alpla value. |
| |
| Most functions use a blending mode of over (X=1,Y=1,Z=1) this results in |
| the following optimizations... |
| gamma = Sa+Da-Sa*Da; |
| gamma = 1 - QuantumScale*alpha * QuantumScale*beta; |
| opacity = QuantumScale*alpha*beta; // over blend, optimized 1-Gamma |
| |
| The above SVG definitions also define that Mathematical Composition |
| methods should use a 'Over' blending mode for Alpha Channel. |
| It however was not applied for composition modes of 'Plus', 'Minus', |
| the modulus versions of 'Add' and 'Subtract'. |
| |
| Mathematical operator changes to be applied from IM v6.7... |
| |
| 1) Modulus modes 'Add' and 'Subtract' are obsoleted and renamed |
| 'ModulusAdd' and 'ModulusSubtract' for clarity. |
| |
| 2) All mathematical compositions work as per the SVG specification |
| with regard to blending. This now includes 'ModulusAdd' and |
| 'ModulusSubtract'. |
| |
| 3) When the special channel flag 'sync' (syncronize channel updates) |
| is turned off (enabled by default) then mathematical compositions are |
| only performed on the channels specified, and are applied |
| independantally of each other. In other words the mathematics is |
| performed as 'pure' mathematical operations, rather than as image |
| operations. |
| */ |
| |
| static void HCLComposite(const MagickRealType hue,const MagickRealType chroma, |
| const MagickRealType luma,MagickRealType *red,MagickRealType *green, |
| MagickRealType *blue) |
| { |
| MagickRealType |
| b, |
| c, |
| g, |
| h, |
| m, |
| r, |
| x; |
| |
| /* |
| Convert HCL to RGB colorspace. |
| */ |
| assert(red != (MagickRealType *) NULL); |
| assert(green != (MagickRealType *) NULL); |
| assert(blue != (MagickRealType *) NULL); |
| h=6.0*hue; |
| c=chroma; |
| x=c*(1.0-fabs(fmod(h,2.0)-1.0)); |
| r=0.0; |
| g=0.0; |
| b=0.0; |
| if ((0.0 <= h) && (h < 1.0)) |
| { |
| r=c; |
| g=x; |
| } |
| else |
| if ((1.0 <= h) && (h < 2.0)) |
| { |
| r=x; |
| g=c; |
| } |
| else |
| if ((2.0 <= h) && (h < 3.0)) |
| { |
| g=c; |
| b=x; |
| } |
| else |
| if ((3.0 <= h) && (h < 4.0)) |
| { |
| g=x; |
| b=c; |
| } |
| else |
| if ((4.0 <= h) && (h < 5.0)) |
| { |
| r=x; |
| b=c; |
| } |
| else |
| if ((5.0 <= h) && (h < 6.0)) |
| { |
| r=c; |
| b=x; |
| } |
| m=luma-(0.298839*r+0.586811*g+0.114350*b); |
| *red=QuantumRange*(r+m); |
| *green=QuantumRange*(g+m); |
| *blue=QuantumRange*(b+m); |
| } |
| |
| static void CompositeHCL(const MagickRealType red,const MagickRealType green, |
| const MagickRealType blue,MagickRealType *hue,MagickRealType *chroma, |
| MagickRealType *luma) |
| { |
| MagickRealType |
| b, |
| c, |
| g, |
| h, |
| max, |
| r; |
| |
| /* |
| Convert RGB to HCL colorspace. |
| */ |
| assert(hue != (MagickRealType *) NULL); |
| assert(chroma != (MagickRealType *) NULL); |
| assert(luma != (MagickRealType *) NULL); |
| r=red; |
| g=green; |
| b=blue; |
| max=MagickMax(r,MagickMax(g,b)); |
| c=max-(MagickRealType) MagickMin(r,MagickMin(g,b)); |
| h=0.0; |
| if (c == 0) |
| h=0.0; |
| else |
| if (red == max) |
| h=fmod((g-b)/c+6.0,6.0); |
| else |
| if (green == max) |
| h=((b-r)/c)+2.0; |
| else |
| if (blue == max) |
| h=((r-g)/c)+4.0; |
| *hue=(h/6.0); |
| *chroma=QuantumScale*c; |
| *luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b); |
| } |
| |
| static MagickBooleanType CompositeOverImage(Image *image, |
| const Image *source_image,const MagickBooleanType clip_to_self, |
| const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception) |
| { |
| #define CompositeImageTag "Composite/Image" |
| |
| CacheView |
| *image_view, |
| *source_view; |
| |
| const char |
| *value; |
| |
| MagickBooleanType |
| clamp, |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| y; |
| |
| /* |
| Composite image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| clamp=MagickTrue; |
| value=GetImageArtifact(image,"compose:clamp"); |
| if (value != (const char *) NULL) |
| clamp=IsStringTrue(value); |
| status=MagickTrue; |
| progress=0; |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(source_image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *pixels; |
| |
| PixelInfo |
| canvas_pixel, |
| source_pixel; |
| |
| const Quantum |
| *magick_restrict p; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| if (clip_to_self != MagickFalse) |
| { |
| if (y < y_offset) |
| continue; |
| if ((y-y_offset) >= (ssize_t) source_image->rows) |
| continue; |
| } |
| /* |
| If pixels is NULL, y is outside overlay region. |
| */ |
| pixels=(Quantum *) NULL; |
| p=(Quantum *) NULL; |
| if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows)) |
| { |
| p=GetCacheViewVirtualPixels(source_view,0,y-y_offset, |
| source_image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| pixels=p; |
| if (x_offset < 0) |
| p-=x_offset*(ssize_t) GetPixelChannels(source_image); |
| } |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| GetPixelInfo(image,&canvas_pixel); |
| GetPixelInfo(source_image,&source_pixel); |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| gamma; |
| |
| MagickRealType |
| alpha, |
| Da, |
| Dc, |
| Dca, |
| Sa, |
| Sc, |
| Sca; |
| |
| ssize_t |
| i; |
| |
| size_t |
| channels; |
| |
| if (clip_to_self != MagickFalse) |
| { |
| if (x < x_offset) |
| { |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| if ((x-x_offset) >= (ssize_t) source_image->columns) |
| break; |
| } |
| if ((pixels == (Quantum *) NULL) || (x < x_offset) || |
| ((x-x_offset) >= (ssize_t) source_image->columns)) |
| { |
| Quantum |
| source[MaxPixelChannels]; |
| |
| /* |
| Virtual composite: |
| Sc: source color. |
| Dc: canvas color. |
| */ |
| (void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,source, |
| exception); |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| MagickRealType |
| pixel; |
| |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| PixelTrait source_traits=GetPixelChannelTraits(source_image, |
| channel); |
| if ((traits == UndefinedPixelTrait) || |
| (source_traits == UndefinedPixelTrait)) |
| continue; |
| if (channel == AlphaPixelChannel) |
| pixel=(MagickRealType) TransparentAlpha; |
| else |
| pixel=(MagickRealType) q[i]; |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : |
| ClampToQuantum(pixel); |
| } |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| /* |
| Authentic composite: |
| Sa: normalized source alpha. |
| Da: normalized canvas alpha. |
| */ |
| Sa=QuantumScale*GetPixelAlpha(source_image,p); |
| Da=QuantumScale*GetPixelAlpha(image,q); |
| alpha=Sa+Da-Sa*Da; |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| MagickRealType |
| pixel; |
| |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| PixelTrait source_traits=GetPixelChannelTraits(source_image,channel); |
| if (traits == UndefinedPixelTrait) |
| continue; |
| if ((source_traits == UndefinedPixelTrait) && |
| (channel != AlphaPixelChannel)) |
| continue; |
| if (channel == AlphaPixelChannel) |
| { |
| /* |
| Set alpha channel. |
| */ |
| pixel=QuantumRange*alpha; |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : |
| ClampToQuantum(pixel); |
| continue; |
| } |
| /* |
| Sc: source color. |
| Dc: canvas color. |
| */ |
| Sc=(MagickRealType) GetPixelChannel(source_image,channel,p); |
| Dc=(MagickRealType) q[i]; |
| if ((traits & CopyPixelTrait) != 0) |
| { |
| /* |
| Copy channel. |
| */ |
| q[i]=ClampToQuantum(Sc); |
| continue; |
| } |
| /* |
| Porter-Duff compositions: |
| Sca: source normalized color multiplied by alpha. |
| Dca: normalized canvas color multiplied by alpha. |
| */ |
| Sca=QuantumScale*Sa*Sc; |
| Dca=QuantumScale*Da*Dc; |
| gamma=PerceptibleReciprocal(alpha); |
| pixel=QuantumRange*gamma*(Sca+Dca*(1.0-Sa)); |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel); |
| } |
| p+=GetPixelChannels(source_image); |
| channels=GetPixelChannels(source_image); |
| if (p >= (pixels+channels*source_image->columns)) |
| p=pixels; |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| source_view=DestroyCacheView(source_view); |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| MagickExport MagickBooleanType CompositeImage(Image *image, |
| const Image *composite,const CompositeOperator compose, |
| const MagickBooleanType clip_to_self,const ssize_t x_offset, |
| const ssize_t y_offset,ExceptionInfo *exception) |
| { |
| #define CompositeImageTag "Composite/Image" |
| |
| CacheView |
| *source_view, |
| *image_view; |
| |
| const char |
| *value; |
| |
| GeometryInfo |
| geometry_info; |
| |
| Image |
| *canvas_image, |
| *source_image; |
| |
| MagickBooleanType |
| clamp, |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| MagickRealType |
| amount, |
| canvas_dissolve, |
| midpoint, |
| percent_luma, |
| percent_chroma, |
| source_dissolve, |
| threshold; |
| |
| MagickStatusType |
| flags; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(composite != (Image *) NULL); |
| assert(composite->signature == MagickCoreSignature); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| source_image=CloneImage(composite,0,0,MagickTrue,exception); |
| if (source_image == (const Image *) NULL) |
| return(MagickFalse); |
| (void) SetImageColorspace(source_image,image->colorspace,exception); |
| if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp)) |
| { |
| status=CompositeOverImage(image,source_image,clip_to_self,x_offset, |
| y_offset,exception); |
| source_image=DestroyImage(source_image); |
| return(status); |
| } |
| amount=0.5; |
| canvas_image=(Image *) NULL; |
| canvas_dissolve=1.0; |
| clamp=MagickTrue; |
| value=GetImageArtifact(image,"compose:clamp"); |
| if (value != (const char *) NULL) |
| clamp=IsStringTrue(value); |
| SetGeometryInfo(&geometry_info); |
| percent_luma=100.0; |
| percent_chroma=100.0; |
| source_dissolve=1.0; |
| threshold=0.05f; |
| switch (compose) |
| { |
| case CopyCompositeOp: |
| { |
| if ((x_offset < 0) || (y_offset < 0)) |
| break; |
| if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns) |
| break; |
| if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows) |
| break; |
| if ((source_image->alpha_trait == UndefinedPixelTrait) && |
| (image->alpha_trait != UndefinedPixelTrait)) |
| (void) SetImageAlphaChannel(source_image,OpaqueAlphaChannel,exception); |
| status=MagickTrue; |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(status) \ |
| magick_number_threads(source_image,image,source_image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) source_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| const Quantum |
| *p; |
| |
| Quantum |
| *q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1, |
| exception); |
| q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset, |
| source_image->columns,1,exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) source_image->columns; x++) |
| { |
| ssize_t |
| i; |
| |
| if (GetPixelReadMask(source_image,p) <= (QuantumRange/2)) |
| { |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| for (i=0; i < (ssize_t) GetPixelChannels(source_image); i++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(source_image,i); |
| PixelTrait source_traits = GetPixelChannelTraits(source_image, |
| channel); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((source_traits == UndefinedPixelTrait) || |
| (traits == UndefinedPixelTrait)) |
| continue; |
| SetPixelChannel(image,channel,p[i],q); |
| } |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(image); |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType) |
| y,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| source_view=DestroyCacheView(source_view); |
| image_view=DestroyCacheView(image_view); |
| source_image=DestroyImage(source_image); |
| return(status); |
| } |
| case IntensityCompositeOp: |
| { |
| if ((x_offset < 0) || (y_offset < 0)) |
| break; |
| if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns) |
| break; |
| if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows) |
| break; |
| status=MagickTrue; |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(status) \ |
| magick_number_threads(source_image,image,source_image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) source_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| const Quantum |
| *p; |
| |
| Quantum |
| *q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1, |
| exception); |
| q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset, |
| source_image->columns,1,exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) source_image->columns; x++) |
| { |
| if (GetPixelReadMask(source_image,p) <= (QuantumRange/2)) |
| { |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| SetPixelAlpha(image,clamp != MagickFalse ? |
| ClampPixel(GetPixelIntensity(source_image,p)) : |
| ClampToQuantum(GetPixelIntensity(source_image,p)),q); |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(image); |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType) |
| y,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| source_view=DestroyCacheView(source_view); |
| image_view=DestroyCacheView(image_view); |
| source_image=DestroyImage(source_image); |
| return(status); |
| } |
| case CopyAlphaCompositeOp: |
| case ChangeMaskCompositeOp: |
| { |
| /* |
| Modify canvas outside the overlaid region and require an alpha |
| channel to exist, to add transparency. |
| */ |
| if (image->alpha_trait == UndefinedPixelTrait) |
| (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); |
| break; |
| } |
| case BlurCompositeOp: |
| { |
| CacheView |
| *canvas_view; |
| |
| double |
| angle_range, |
| angle_start, |
| height, |
| width; |
| |
| PixelInfo |
| pixel; |
| |
| ResampleFilter |
| *resample_filter; |
| |
| SegmentInfo |
| blur; |
| |
| /* |
| Blur Image by resampling dictated by an overlay gradient map: |
| X = red_channel; Y = green_channel; compose:args = |
| x_scale[,y_scale[,angle]]. |
| */ |
| canvas_image=CloneImage(image,0,0,MagickTrue,exception); |
| if (canvas_image == (Image *) NULL) |
| { |
| source_image=DestroyImage(source_image); |
| return(MagickFalse); |
| } |
| /* |
| Gather the maximum blur sigma values from user. |
| */ |
| flags=NoValue; |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (const char *) NULL) |
| flags=ParseGeometry(value,&geometry_info); |
| if ((flags & WidthValue) == 0) |
| { |
| (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning, |
| "InvalidSetting","'%s' '%s'","compose:args",value); |
| source_image=DestroyImage(source_image); |
| canvas_image=DestroyImage(canvas_image); |
| return(MagickFalse); |
| } |
| /* |
| Users input sigma now needs to be converted to the EWA ellipse size. |
| The filter defaults to a sigma of 0.5 so to make this match the users |
| input the ellipse size needs to be doubled. |
| */ |
| width=2.0*geometry_info.rho; |
| height=width; |
| if ((flags & HeightValue) != 0) |
| height=2.0*geometry_info.sigma; |
| /* |
| Default the unrotated ellipse width and height axis vectors. |
| */ |
| blur.x1=width; |
| blur.x2=0.0; |
| blur.y1=0.0; |
| blur.y2=height; |
| if ((flags & XValue) != 0 ) |
| { |
| MagickRealType |
| angle; |
| |
| /* |
| Rotate vectors if a rotation angle is given. |
| */ |
| angle=DegreesToRadians(geometry_info.xi); |
| blur.x1=width*cos(angle); |
| blur.x2=width*sin(angle); |
| blur.y1=(-height*sin(angle)); |
| blur.y2=height*cos(angle); |
| } |
| angle_start=0.0; |
| angle_range=0.0; |
| if ((flags & YValue) != 0 ) |
| { |
| /* |
| Lets set a angle range and calculate in the loop. |
| */ |
| angle_start=DegreesToRadians(geometry_info.xi); |
| angle_range=DegreesToRadians(geometry_info.psi)-angle_start; |
| } |
| /* |
| Set up a gaussian cylindrical filter for EWA Bluring. |
| |
| As the minimum ellipse radius of support*1.0 the EWA algorithm |
| can only produce a minimum blur of 0.5 for Gaussian (support=2.0) |
| This means that even 'No Blur' will be still a little blurry! The |
| solution (as well as the problem of preventing any user expert filter |
| settings, is to set our own user settings, restore them afterwards. |
| */ |
| resample_filter=AcquireResampleFilter(image,exception); |
| SetResampleFilter(resample_filter,GaussianFilter); |
| /* |
| Perform the variable blurring of each pixel in image. |
| */ |
| GetPixelInfo(image,&pixel); |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| canvas_view=AcquireAuthenticCacheView(canvas_image,exception); |
| for (y=0; y < (ssize_t) source_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| const Quantum |
| *magick_restrict p; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows)) |
| continue; |
| p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1, |
| exception); |
| q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1, |
| exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| break; |
| for (x=0; x < (ssize_t) source_image->columns; x++) |
| { |
| if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns)) |
| { |
| p+=GetPixelChannels(source_image); |
| continue; |
| } |
| if (fabs(angle_range) > MagickEpsilon) |
| { |
| MagickRealType |
| angle; |
| |
| angle=angle_start+angle_range*QuantumScale* |
| GetPixelBlue(source_image,p); |
| blur.x1=width*cos(angle); |
| blur.x2=width*sin(angle); |
| blur.y1=(-height*sin(angle)); |
| blur.y2=height*cos(angle); |
| } |
| ScaleResampleFilter(resample_filter, |
| blur.x1*QuantumScale*GetPixelRed(source_image,p), |
| blur.y1*QuantumScale*GetPixelGreen(source_image,p), |
| blur.x2*QuantumScale*GetPixelRed(source_image,p), |
| blur.y2*QuantumScale*GetPixelGreen(source_image,p) ); |
| (void) ResamplePixelColor(resample_filter,(double) x_offset+x, |
| (double) y_offset+y,&pixel,exception); |
| SetPixelViaPixelInfo(canvas_image,&pixel,q); |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(canvas_image); |
| } |
| sync=SyncCacheViewAuthenticPixels(canvas_view,exception); |
| if (sync == MagickFalse) |
| break; |
| } |
| resample_filter=DestroyResampleFilter(resample_filter); |
| source_view=DestroyCacheView(source_view); |
| canvas_view=DestroyCacheView(canvas_view); |
| source_image=DestroyImage(source_image); |
| source_image=canvas_image; |
| break; |
| } |
| case DisplaceCompositeOp: |
| case DistortCompositeOp: |
| { |
| CacheView |
| *canvas_view; |
| |
| MagickRealType |
| horizontal_scale, |
| vertical_scale; |
| |
| PixelInfo |
| pixel; |
| |
| PointInfo |
| center, |
| offset; |
| |
| /* |
| Displace/Distort based on overlay gradient map: |
| X = red_channel; Y = green_channel; |
| compose:args = x_scale[,y_scale[,center.x,center.y]] |
| */ |
| canvas_image=CloneImage(image,0,0,MagickTrue,exception); |
| if (canvas_image == (Image *) NULL) |
| { |
| source_image=DestroyImage(source_image); |
| return(MagickFalse); |
| } |
| SetGeometryInfo(&geometry_info); |
| flags=NoValue; |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| flags=ParseGeometry(value,&geometry_info); |
| if ((flags & (WidthValue | HeightValue)) == 0 ) |
| { |
| if ((flags & AspectValue) == 0) |
| { |
| horizontal_scale=(MagickRealType) (source_image->columns-1)/2.0; |
| vertical_scale=(MagickRealType) (source_image->rows-1)/2.0; |
| } |
| else |
| { |
| horizontal_scale=(MagickRealType) (image->columns-1)/2.0; |
| vertical_scale=(MagickRealType) (image->rows-1)/2.0; |
| } |
| } |
| else |
| { |
| horizontal_scale=geometry_info.rho; |
| vertical_scale=geometry_info.sigma; |
| if ((flags & PercentValue) != 0) |
| { |
| if ((flags & AspectValue) == 0) |
| { |
| horizontal_scale*=(source_image->columns-1)/200.0; |
| vertical_scale*=(source_image->rows-1)/200.0; |
| } |
| else |
| { |
| horizontal_scale*=(image->columns-1)/200.0; |
| vertical_scale*=(image->rows-1)/200.0; |
| } |
| } |
| if ((flags & HeightValue) == 0) |
| vertical_scale=horizontal_scale; |
| } |
| /* |
| Determine fixed center point for absolute distortion map |
| Absolute distort == |
| Displace offset relative to a fixed absolute point |
| Select that point according to +X+Y user inputs. |
| default = center of overlay image |
| arg flag '!' = locations/percentage relative to background image |
| */ |
| center.x=(MagickRealType) x_offset; |
| center.y=(MagickRealType) y_offset; |
| if (compose == DistortCompositeOp) |
| { |
| if ((flags & XValue) == 0) |
| if ((flags & AspectValue) != 0) |
| center.x=(MagickRealType) ((image->columns-1)/2.0); |
| else |
| center.x=(MagickRealType) (x_offset+(source_image->columns-1)/ |
| 2.0); |
| else |
| if ((flags & AspectValue) != 0) |
| center.x=geometry_info.xi; |
| else |
| center.x=(MagickRealType) (x_offset+geometry_info.xi); |
| if ((flags & YValue) == 0) |
| if ((flags & AspectValue) != 0) |
| center.y=(MagickRealType) ((image->rows-1)/2.0); |
| else |
| center.y=(MagickRealType) (y_offset+(source_image->rows-1)/2.0); |
| else |
| if ((flags & AspectValue) != 0) |
| center.y=geometry_info.psi; |
| else |
| center.y=(MagickRealType) (y_offset+geometry_info.psi); |
| } |
| /* |
| Shift the pixel offset point as defined by the provided, |
| displacement/distortion map. -- Like a lens... |
| */ |
| GetPixelInfo(image,&pixel); |
| image_view=AcquireVirtualCacheView(image,exception); |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| canvas_view=AcquireAuthenticCacheView(canvas_image,exception); |
| for (y=0; y < (ssize_t) source_image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| const Quantum |
| *magick_restrict p; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows)) |
| continue; |
| p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1, |
| exception); |
| q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1, |
| exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| break; |
| for (x=0; x < (ssize_t) source_image->columns; x++) |
| { |
| if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns)) |
| { |
| p+=GetPixelChannels(source_image); |
| continue; |
| } |
| /* |
| Displace the offset. |
| */ |
| offset.x=(double) (horizontal_scale*(GetPixelRed(source_image,p)- |
| (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType) |
| QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ? |
| x : 0); |
| offset.y=(double) (vertical_scale*(GetPixelGreen(source_image,p)- |
| (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType) |
| QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ? |
| y : 0); |
| status=InterpolatePixelInfo(image,image_view, |
| UndefinedInterpolatePixel,(double) offset.x,(double) offset.y, |
| &pixel,exception); |
| if (status == MagickFalse) |
| break; |
| /* |
| Mask with the 'invalid pixel mask' in alpha channel. |
| */ |
| pixel.alpha=(MagickRealType) QuantumRange*(QuantumScale*pixel.alpha)* |
| (QuantumScale*GetPixelAlpha(source_image,p)); |
| SetPixelViaPixelInfo(canvas_image,&pixel,q); |
| p+=GetPixelChannels(source_image); |
| q+=GetPixelChannels(canvas_image); |
| } |
| if (x < (ssize_t) source_image->columns) |
| break; |
| sync=SyncCacheViewAuthenticPixels(canvas_view,exception); |
| if (sync == MagickFalse) |
| break; |
| } |
| canvas_view=DestroyCacheView(canvas_view); |
| source_view=DestroyCacheView(source_view); |
| image_view=DestroyCacheView(image_view); |
| source_image=DestroyImage(source_image); |
| source_image=canvas_image; |
| break; |
| } |
| case DissolveCompositeOp: |
| { |
| /* |
| Geometry arguments to dissolve factors. |
| */ |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| source_dissolve=geometry_info.rho/100.0; |
| canvas_dissolve=1.0; |
| if ((source_dissolve-MagickEpsilon) < 0.0) |
| source_dissolve=0.0; |
| if ((source_dissolve+MagickEpsilon) > 1.0) |
| { |
| canvas_dissolve=2.0-source_dissolve; |
| source_dissolve=1.0; |
| } |
| if ((flags & SigmaValue) != 0) |
| canvas_dissolve=geometry_info.sigma/100.0; |
| if ((canvas_dissolve-MagickEpsilon) < 0.0) |
| canvas_dissolve=0.0; |
| } |
| break; |
| } |
| case BlendCompositeOp: |
| { |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| source_dissolve=geometry_info.rho/100.0; |
| canvas_dissolve=1.0-source_dissolve; |
| if ((flags & SigmaValue) != 0) |
| canvas_dissolve=geometry_info.sigma/100.0; |
| } |
| break; |
| } |
| case MathematicsCompositeOp: |
| { |
| /* |
| Just collect the values from "compose:args", setting. |
| Unused values are set to zero automagically. |
| |
| Arguments are normally a comma separated list, so this probably should |
| be changed to some 'general comma list' parser, (with a minimum |
| number of values) |
| */ |
| SetGeometryInfo(&geometry_info); |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| (void) ParseGeometry(value,&geometry_info); |
| break; |
| } |
| case ModulateCompositeOp: |
| { |
| /* |
| Determine the luma and chroma scale. |
| */ |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| percent_luma=geometry_info.rho; |
| if ((flags & SigmaValue) != 0) |
| percent_chroma=geometry_info.sigma; |
| } |
| break; |
| } |
| case ThresholdCompositeOp: |
| { |
| /* |
| Determine the amount and threshold. |
| */ |
| value=GetImageArtifact(image,"compose:args"); |
| if (value != (char *) NULL) |
| { |
| flags=ParseGeometry(value,&geometry_info); |
| amount=geometry_info.rho; |
| threshold=geometry_info.sigma; |
| if ((flags & SigmaValue) == 0) |
| threshold=0.05f; |
| } |
| threshold*=QuantumRange; |
| break; |
| } |
| default: |
| break; |
| } |
| /* |
| Composite image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| midpoint=((MagickRealType) QuantumRange+1.0)/2; |
| source_view=AcquireVirtualCacheView(source_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(source_image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *pixels; |
| |
| MagickRealType |
| blue, |
| chroma, |
| green, |
| hue, |
| luma, |
| red; |
| |
| PixelInfo |
| canvas_pixel, |
| source_pixel; |
| |
| const Quantum |
| *magick_restrict p; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| if (clip_to_self != MagickFalse) |
| { |
| if (y < y_offset) |
| continue; |
| if ((y-y_offset) >= (ssize_t) source_image->rows) |
| continue; |
| } |
| /* |
| If pixels is NULL, y is outside overlay region. |
| */ |
| pixels=(Quantum *) NULL; |
| p=(Quantum *) NULL; |
| if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows)) |
| { |
| p=GetCacheViewVirtualPixels(source_view,0,y-y_offset, |
| source_image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| pixels=p; |
| if (x_offset < 0) |
| p-=x_offset*(ssize_t) GetPixelChannels(source_image); |
| } |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| hue=0.0; |
| chroma=0.0; |
| luma=0.0; |
| GetPixelInfo(image,&canvas_pixel); |
| GetPixelInfo(source_image,&source_pixel); |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| gamma; |
| |
| MagickRealType |
| alpha, |
| Da, |
| Dc, |
| Dca, |
| DcaDa, |
| Sa, |
| SaSca, |
| Sc, |
| Sca; |
| |
| ssize_t |
| i; |
| |
| size_t |
| channels; |
| |
| if (clip_to_self != MagickFalse) |
| { |
| if (x < x_offset) |
| { |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| if ((x-x_offset) >= (ssize_t) source_image->columns) |
| break; |
| } |
| if ((pixels == (Quantum *) NULL) || (x < x_offset) || |
| ((x-x_offset) >= (ssize_t) source_image->columns)) |
| { |
| Quantum |
| source[MaxPixelChannels]; |
| |
| /* |
| Virtual composite: |
| Sc: source color. |
| Dc: canvas color. |
| */ |
| (void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,source, |
| exception); |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| MagickRealType |
| pixel; |
| |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| PixelTrait source_traits=GetPixelChannelTraits(source_image, |
| channel); |
| if ((traits == UndefinedPixelTrait) || |
| (source_traits == UndefinedPixelTrait)) |
| continue; |
| switch (compose) |
| { |
| case AlphaCompositeOp: |
| case ChangeMaskCompositeOp: |
| case CopyAlphaCompositeOp: |
| case DstAtopCompositeOp: |
| case DstInCompositeOp: |
| case InCompositeOp: |
| case OutCompositeOp: |
| case SrcInCompositeOp: |
| case SrcOutCompositeOp: |
| { |
| if (channel == AlphaPixelChannel) |
| pixel=(MagickRealType) TransparentAlpha; |
| else |
| pixel=(MagickRealType) q[i]; |
| break; |
| } |
| case ClearCompositeOp: |
| case CopyCompositeOp: |
| case ReplaceCompositeOp: |
| case SrcCompositeOp: |
| { |
| if (channel == AlphaPixelChannel) |
| pixel=(MagickRealType) TransparentAlpha; |
| else |
| pixel=0.0; |
| break; |
| } |
| case BlendCompositeOp: |
| case DissolveCompositeOp: |
| { |
| if (channel == AlphaPixelChannel) |
| pixel=canvas_dissolve*GetPixelAlpha(source_image,source); |
| else |
| pixel=(MagickRealType) source[channel]; |
| break; |
| } |
| default: |
| { |
| pixel=(MagickRealType) source[channel]; |
| break; |
| } |
| } |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : |
| ClampToQuantum(pixel); |
| } |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| /* |
| Authentic composite: |
| Sa: normalized source alpha. |
| Da: normalized canvas alpha. |
| */ |
| Sa=QuantumScale*GetPixelAlpha(source_image,p); |
| Da=QuantumScale*GetPixelAlpha(image,q); |
| switch (compose) |
| { |
| case BumpmapCompositeOp: |
| { |
| alpha=GetPixelIntensity(source_image,p)*Sa; |
| break; |
| } |
| case ColorBurnCompositeOp: |
| case ColorDodgeCompositeOp: |
| case DarkenCompositeOp: |
| case DifferenceCompositeOp: |
| case DivideDstCompositeOp: |
| case DivideSrcCompositeOp: |
| case ExclusionCompositeOp: |
| case FreezeCompositeOp: |
| case HardLightCompositeOp: |
| case HardMixCompositeOp: |
| case InterpolateCompositeOp: |
| case LightenCompositeOp: |
| case LinearBurnCompositeOp: |
| case LinearDodgeCompositeOp: |
| case LinearLightCompositeOp: |
| case MathematicsCompositeOp: |
| case MinusDstCompositeOp: |
| case MinusSrcCompositeOp: |
| case MultiplyCompositeOp: |
| case NegateCompositeOp: |
| case OverlayCompositeOp: |
| case PegtopLightCompositeOp: |
| case PinLightCompositeOp: |
| case ReflectCompositeOp: |
| case ScreenCompositeOp: |
| case SoftBurnCompositeOp: |
| case SoftDodgeCompositeOp: |
| case SoftLightCompositeOp: |
| case StampCompositeOp: |
| case VividLightCompositeOp: |
| { |
| alpha=RoundToUnity(Sa+Da-Sa*Da); |
| break; |
| } |
| case DstAtopCompositeOp: |
| case DstInCompositeOp: |
| case InCompositeOp: |
| case SrcInCompositeOp: |
| { |
| alpha=Sa*Da; |
| break; |
| } |
| case DissolveCompositeOp: |
| { |
| alpha=source_dissolve*Sa*(-canvas_dissolve*Da)+source_dissolve*Sa+ |
| canvas_dissolve*Da; |
| break; |
| } |
| case DstOverCompositeOp: |
| case OverCompositeOp: |
| case SrcOverCompositeOp: |
| { |
| alpha=Sa+Da-Sa*Da; |
| break; |
| } |
| case DstOutCompositeOp: |
| { |
| alpha=Da*(1.0-Sa); |
| break; |
| } |
| case OutCompositeOp: |
| case SrcOutCompositeOp: |
| { |
| alpha=Sa*(1.0-Da); |
| break; |
| } |
| case BlendCompositeOp: |
| case PlusCompositeOp: |
| { |
| alpha=RoundToUnity(source_dissolve*Sa+canvas_dissolve*Da); |
| break; |
| } |
| case XorCompositeOp: |
| { |
| alpha=Sa+Da-2.0*Sa*Da; |
| break; |
| } |
| case ModulusAddCompositeOp: |
| { |
| if ((Sa+Da) <= 1.0) |
| { |
| alpha=(Sa+Da); |
| break; |
| } |
| alpha=((Sa+Da)-1.0); |
| break; |
| } |
| case ModulusSubtractCompositeOp: |
| { |
| if ((Sa-Da) >= 0.0) |
| { |
| alpha=(Sa-Da); |
| break; |
| } |
| alpha=((Sa-Da)+1.0); |
| break; |
| } |
| default: |
| { |
| alpha=1.0; |
| break; |
| } |
| } |
| switch (compose) |
| { |
| case ColorizeCompositeOp: |
| case HueCompositeOp: |
| case LuminizeCompositeOp: |
| case ModulateCompositeOp: |
| case RMSECompositeOp: |
| case SaturateCompositeOp: |
| { |
| GetPixelInfoPixel(source_image,p,&source_pixel); |
| GetPixelInfoPixel(image,q,&canvas_pixel); |
| break; |
| } |
| default: |
| break; |
| } |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| MagickRealType |
| pixel, |
| sans; |
| |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| PixelTrait source_traits = GetPixelChannelTraits(source_image,channel); |
| if (traits == UndefinedPixelTrait) |
| continue; |
| if ((channel == AlphaPixelChannel) && |
| ((traits & UpdatePixelTrait) != 0)) |
| { |
| /* |
| Set alpha channel. |
| */ |
| switch (compose) |
| { |
| case AlphaCompositeOp: |
| { |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| case AtopCompositeOp: |
| case CopyBlackCompositeOp: |
| case CopyBlueCompositeOp: |
| case CopyCyanCompositeOp: |
| case CopyGreenCompositeOp: |
| case CopyMagentaCompositeOp: |
| case CopyRedCompositeOp: |
| case CopyYellowCompositeOp: |
| case SrcAtopCompositeOp: |
| case DstCompositeOp: |
| case NoCompositeOp: |
| { |
| pixel=QuantumRange*Da; |
| break; |
| } |
| case ChangeMaskCompositeOp: |
| { |
| MagickBooleanType |
| equivalent; |
| |
| if (Da < 0.5) |
| { |
| pixel=(MagickRealType) TransparentAlpha; |
| break; |
| } |
| equivalent=IsFuzzyEquivalencePixel(source_image,p,image,q); |
| if (equivalent != MagickFalse) |
| pixel=(MagickRealType) TransparentAlpha; |
| else |
| pixel=(MagickRealType) OpaqueAlpha; |
| break; |
| } |
| case ClearCompositeOp: |
| { |
| pixel=(MagickRealType) TransparentAlpha; |
| break; |
| } |
| case ColorizeCompositeOp: |
| case HueCompositeOp: |
| case LuminizeCompositeOp: |
| case RMSECompositeOp: |
| case SaturateCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=QuantumRange*Da; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| if (Sa < Da) |
| { |
| pixel=QuantumRange*Da; |
| break; |
| } |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| case CopyAlphaCompositeOp: |
| { |
| if (source_image->alpha_trait == UndefinedPixelTrait) |
| pixel=GetPixelIntensity(source_image,p); |
| else |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| case BlurCompositeOp: |
| case CopyCompositeOp: |
| case DisplaceCompositeOp: |
| case DistortCompositeOp: |
| case DstAtopCompositeOp: |
| case ReplaceCompositeOp: |
| case SrcCompositeOp: |
| { |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| case DarkenIntensityCompositeOp: |
| { |
| pixel=Sa*GetPixelIntensity(source_image,p) < |
| Da*GetPixelIntensity(image,q) ? Sa : Da; |
| break; |
| } |
| case DifferenceCompositeOp: |
| { |
| pixel=QuantumRange*fabs(Sa-Da); |
| break; |
| } |
| case FreezeCompositeOp: |
| { |
| pixel=QuantumRange*(1.0-(1.0-Sa)*(1.0-Sa)* |
| PerceptibleReciprocal(Da)); |
| if (pixel < 0.0) |
| pixel=0.0; |
| break; |
| } |
| case InterpolateCompositeOp: |
| { |
| pixel=QuantumRange*(0.5-0.25*cos(MagickPI*Sa)-0.25* |
| cos(MagickPI*Da)); |
| break; |
| } |
| case LightenIntensityCompositeOp: |
| { |
| pixel=Sa*GetPixelIntensity(source_image,p) > |
| Da*GetPixelIntensity(image,q) ? Sa : Da; |
| break; |
| } |
| case ModulateCompositeOp: |
| { |
| pixel=QuantumRange*Da; |
| break; |
| } |
| case MultiplyCompositeOp: |
| { |
| pixel=QuantumRange*Sa*Da; |
| break; |
| } |
| case NegateCompositeOp: |
| { |
| pixel=QuantumRange*((1.0-Sa-Da)); |
| break; |
| } |
| case ReflectCompositeOp: |
| { |
| pixel=QuantumRange*(Sa*Sa*PerceptibleReciprocal(1.0-Da)); |
| if (pixel > QuantumRange) |
| pixel=QuantumRange; |
| break; |
| } |
| case StampCompositeOp: |
| { |
| pixel=QuantumRange*(Sa+Da*Da-1.0); |
| break; |
| } |
| case StereoCompositeOp: |
| { |
| pixel=QuantumRange*(Sa+Da)/2; |
| break; |
| } |
| default: |
| { |
| pixel=QuantumRange*alpha; |
| break; |
| } |
| } |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : |
| ClampToQuantum(pixel); |
| continue; |
| } |
| if (source_traits == UndefinedPixelTrait) |
| continue; |
| /* |
| Sc: source color. |
| Dc: canvas color. |
| */ |
| Sc=(MagickRealType) GetPixelChannel(source_image,channel,p); |
| Dc=(MagickRealType) q[i]; |
| if ((traits & CopyPixelTrait) != 0) |
| { |
| /* |
| Copy channel. |
| */ |
| q[i]=ClampToQuantum(Dc); |
| continue; |
| } |
| /* |
| Porter-Duff compositions: |
| Sca: source normalized color multiplied by alpha. |
| Dca: normalized canvas color multiplied by alpha. |
| */ |
| Sca=QuantumScale*Sa*Sc; |
| Dca=QuantumScale*Da*Dc; |
| SaSca=Sa*PerceptibleReciprocal(Sca); |
| DcaDa=Dca*PerceptibleReciprocal(Da); |
| switch (compose) |
| { |
| case DarkenCompositeOp: |
| case LightenCompositeOp: |
| case ModulusSubtractCompositeOp: |
| { |
| gamma=PerceptibleReciprocal(1.0-alpha); |
| break; |
| } |
| default: |
| { |
| gamma=PerceptibleReciprocal(alpha); |
| break; |
| } |
| } |
| pixel=Dc; |
| switch (compose) |
| { |
| case AlphaCompositeOp: |
| { |
| pixel=QuantumRange*Sa; |
| break; |
| } |
| case AtopCompositeOp: |
| case SrcAtopCompositeOp: |
| { |
| pixel=QuantumRange*(Sca*Da+Dca*(1.0-Sa)); |
| break; |
| } |
| case BlendCompositeOp: |
| { |
| pixel=gamma*(source_dissolve*Sa*Sc+canvas_dissolve*Da*Dc); |
| break; |
| } |
| case CopyCompositeOp: |
| case ReplaceCompositeOp: |
| case SrcCompositeOp: |
| { |
| pixel=QuantumRange*Sca; |
| break; |
| } |
| case BlurCompositeOp: |
| case DisplaceCompositeOp: |
| case DistortCompositeOp: |
| { |
| pixel=Sc; |
| break; |
| } |
| case BumpmapCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| pixel=QuantumScale*GetPixelIntensity(source_image,p)*Dc; |
| break; |
| } |
| case ChangeMaskCompositeOp: |
| { |
| pixel=Dc; |
| break; |
| } |
| case ClearCompositeOp: |
| { |
| pixel=0.0; |
| break; |
| } |
| case ColorBurnCompositeOp: |
| { |
| if ((Sca == 0.0) && (Dca == Da)) |
| { |
| pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa)); |
| break; |
| } |
| if (Sca == 0.0) |
| { |
| pixel=QuantumRange*gamma*(Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Sa*Da-Sa*Da*MagickMin(1.0,(1.0-DcaDa)* |
| SaSca)+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case ColorDodgeCompositeOp: |
| { |
| if ((Sca*Da+Dca*Sa) >= Sa*Da) |
| pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| else |
| pixel=QuantumRange*gamma*(Dca*Sa*Sa*PerceptibleReciprocal(Sa-Sca)+ |
| Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case ColorizeCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Sc; |
| break; |
| } |
| CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue, |
| &sans,&sans,&luma); |
| CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue, |
| &hue,&chroma,&sans); |
| HCLComposite(hue,chroma,luma,&red,&green,&blue); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=red; break; |
| case GreenPixelChannel: pixel=green; break; |
| case BluePixelChannel: pixel=blue; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case CopyAlphaCompositeOp: |
| { |
| pixel=Dc; |
| break; |
| } |
| case CopyBlackCompositeOp: |
| { |
| if (channel == BlackPixelChannel) |
| pixel=(MagickRealType) GetPixelBlack(source_image,p); |
| break; |
| } |
| case CopyBlueCompositeOp: |
| case CopyYellowCompositeOp: |
| { |
| if (channel == BluePixelChannel) |
| pixel=(MagickRealType) GetPixelBlue(source_image,p); |
| break; |
| } |
| case CopyGreenCompositeOp: |
| case CopyMagentaCompositeOp: |
| { |
| if (channel == GreenPixelChannel) |
| pixel=(MagickRealType) GetPixelGreen(source_image,p); |
| break; |
| } |
| case CopyRedCompositeOp: |
| case CopyCyanCompositeOp: |
| { |
| if (channel == RedPixelChannel) |
| pixel=(MagickRealType) GetPixelRed(source_image,p); |
| break; |
| } |
| case DarkenCompositeOp: |
| { |
| /* |
| Darken is equivalent to a 'Minimum' method |
| OR a greyscale version of a binary 'Or' |
| OR the 'Intersection' of pixel sets. |
| */ |
| if ((Sca*Da) < (Dca*Sa)) |
| { |
| pixel=QuantumRange*(Sca+Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*(Dca+Sca*(1.0-Da)); |
| break; |
| } |
| case DarkenIntensityCompositeOp: |
| { |
| pixel=Sa*GetPixelIntensity(source_image,p) < |
| Da*GetPixelIntensity(image,q) ? Sc : Dc; |
| break; |
| } |
| case DifferenceCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Sca+Dca-2.0*MagickMin(Sca*Da,Dca*Sa)); |
| break; |
| } |
| case DissolveCompositeOp: |
| { |
| pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa* |
| canvas_dissolve*Da*Dc+canvas_dissolve*Da*Dc); |
| break; |
| } |
| case DivideDstCompositeOp: |
| { |
| if ((fabs((double) Sca) < MagickEpsilon) && |
| (fabs((double) Dca) < MagickEpsilon)) |
| { |
| pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| if (fabs((double) Dca) < MagickEpsilon) |
| { |
| pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case DivideSrcCompositeOp: |
| { |
| if ((fabs((double) Dca) < MagickEpsilon) && |
| (fabs((double) Sca) < MagickEpsilon)) |
| { |
| pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da)); |
| break; |
| } |
| if (fabs((double) Sca) < MagickEpsilon) |
| { |
| pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Dca*Sa*SaSca+Dca*(1.0-Sa)+Sca*(1.0-Da)); |
| break; |
| } |
| case DstAtopCompositeOp: |
| { |
| pixel=QuantumRange*(Dca*Sa+Sca*(1.0-Da)); |
| break; |
| } |
| case DstCompositeOp: |
| case NoCompositeOp: |
| { |
| pixel=QuantumRange*Dca; |
| break; |
| } |
| case DstInCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Dca*Sa); |
| break; |
| } |
| case DstOutCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Dca*(1.0-Sa)); |
| break; |
| } |
| case DstOverCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Dca+Sca*(1.0-Da)); |
| break; |
| } |
| case ExclusionCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+ |
| Dca*(1.0-Sa)); |
| break; |
| } |
| case FreezeCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(1.0-(1.0-Sca)*(1.0-Sca)* |
| PerceptibleReciprocal(Dca)); |
| if (pixel < 0.0) |
| pixel=0.0; |
| break; |
| } |
| case HardLightCompositeOp: |
| { |
| if ((2.0*Sca) < Sa) |
| { |
| pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0- |
| Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+ |
| Dca*(1.0-Sa)); |
| break; |
| } |
| case HardMixCompositeOp: |
| { |
| pixel=gamma*(((Sca+Dca) < 1.0) ? 0.0 : QuantumRange); |
| break; |
| } |
| case HueCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Sc; |
| break; |
| } |
| CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue, |
| &hue,&chroma,&luma); |
| CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue, |
| &hue,&sans,&sans); |
| HCLComposite(hue,chroma,luma,&red,&green,&blue); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=red; break; |
| case GreenPixelChannel: pixel=green; break; |
| case BluePixelChannel: pixel=blue; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case InCompositeOp: |
| case SrcInCompositeOp: |
| { |
| pixel=QuantumRange*(Sca*Da); |
| break; |
| } |
| case InterpolateCompositeOp: |
| { |
| pixel=QuantumRange*(0.5-0.25*cos(MagickPI*Sca)-0.25* |
| cos(MagickPI*Dca)); |
| break; |
| } |
| case LinearBurnCompositeOp: |
| { |
| /* |
| LinearBurn: as defined by Abode Photoshop, according to |
| http://www.simplefilter.de/en/basics/mixmods.html is: |
| |
| f(Sc,Dc) = Sc + Dc - 1 |
| */ |
| pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da); |
| break; |
| } |
| case LinearDodgeCompositeOp: |
| { |
| pixel=gamma*(Sa*Sc+Da*Dc); |
| break; |
| } |
| case LinearLightCompositeOp: |
| { |
| /* |
| LinearLight: as defined by Abode Photoshop, according to |
| http://www.simplefilter.de/en/basics/mixmods.html is: |
| |
| f(Sc,Dc) = Dc + 2*Sc - 1 |
| */ |
| pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca); |
| break; |
| } |
| case LightenCompositeOp: |
| { |
| if ((Sca*Da) > (Dca*Sa)) |
| { |
| pixel=QuantumRange*(Sca+Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*(Dca+Sca*(1.0-Da)); |
| break; |
| } |
| case LightenIntensityCompositeOp: |
| { |
| /* |
| Lighten is equivalent to a 'Maximum' method |
| OR a greyscale version of a binary 'And' |
| OR the 'Union' of pixel sets. |
| */ |
| pixel=Sa*GetPixelIntensity(source_image,p) > |
| Da*GetPixelIntensity(image,q) ? Sc : Dc; |
| break; |
| } |
| case LuminizeCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Sc; |
| break; |
| } |
| CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue, |
| &hue,&chroma,&luma); |
| CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue, |
| &sans,&sans,&luma); |
| HCLComposite(hue,chroma,luma,&red,&green,&blue); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=red; break; |
| case GreenPixelChannel: pixel=green; break; |
| case BluePixelChannel: pixel=blue; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case MathematicsCompositeOp: |
| { |
| /* |
| 'Mathematics' a free form user control mathematical composition |
| is defined as... |
| |
| f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D |
| |
| Where the arguments A,B,C,D are (currently) passed to composite |
| as a command separated 'geometry' string in "compose:args" image |
| artifact. |
| |
| A = a->rho, B = a->sigma, C = a->xi, D = a->psi |
| |
| Applying the SVG transparency formula (see above), we get... |
| |
| Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa) |
| |
| Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) + |
| Dca*(1.0-Sa) |
| */ |
| pixel=QuantumRange*gamma*(geometry_info.rho*Sca*Dca+ |
| geometry_info.sigma*Sca*Da+geometry_info.xi*Dca*Sa+ |
| geometry_info.psi*Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case MinusDstCompositeOp: |
| { |
| pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa); |
| break; |
| } |
| case MinusSrcCompositeOp: |
| { |
| /* |
| Minus source from canvas. |
| |
| f(Sc,Dc) = Sc - Dc |
| */ |
| pixel=gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da); |
| break; |
| } |
| case ModulateCompositeOp: |
| { |
| ssize_t |
| offset; |
| |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| offset=(ssize_t) (GetPixelIntensity(source_image,p)-midpoint); |
| if (offset == 0) |
| { |
| pixel=Dc; |
| break; |
| } |
| CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue, |
| &hue,&chroma,&luma); |
| luma+=(0.01*percent_luma*offset)/midpoint; |
| chroma*=0.01*percent_chroma; |
| HCLComposite(hue,chroma,luma,&red,&green,&blue); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=red; break; |
| case GreenPixelChannel: pixel=green; break; |
| case BluePixelChannel: pixel=blue; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case ModulusAddCompositeOp: |
| { |
| if ((Sca+Dca) <= 1.0) |
| { |
| pixel=QuantumRange*(Sca+Dca); |
| break; |
| } |
| pixel=QuantumRange*((Sca+Dca)-1.0); |
| break; |
| } |
| case ModulusSubtractCompositeOp: |
| { |
| if ((Sca-Dca) >= 0.0) |
| { |
| pixel=QuantumRange*(Sca-Dca); |
| break; |
| } |
| pixel=QuantumRange*((Sca-Dca)+1.0); |
| break; |
| } |
| case MultiplyCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case NegateCompositeOp: |
| { |
| pixel=QuantumRange*(1.0-fabs(1.0-Sca-Dca)); |
| break; |
| } |
| case OutCompositeOp: |
| case SrcOutCompositeOp: |
| { |
| pixel=QuantumRange*(Sca*(1.0-Da)); |
| break; |
| } |
| case OverCompositeOp: |
| case SrcOverCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Sca+Dca*(1.0-Sa)); |
| break; |
| } |
| case OverlayCompositeOp: |
| { |
| if ((2.0*Dca) < Da) |
| { |
| pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*(1.0- |
| Da)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+ |
| Sca*(1.0-Da)); |
| break; |
| } |
| case PegtopLightCompositeOp: |
| { |
| /* |
| PegTop: A Soft-Light alternative: A continuous version of the |
| Softlight function, producing very similar results. |
| |
| f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc |
| |
| http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm. |
| */ |
| if (fabs((double) Da) < MagickEpsilon) |
| { |
| pixel=QuantumRange*gamma*Sca; |
| break; |
| } |
| pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0- |
| Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case PinLightCompositeOp: |
| { |
| /* |
| PinLight: A Photoshop 7 composition method |
| http://www.simplefilter.de/en/basics/mixmods.html |
| |
| f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc |
| */ |
| if ((Dca*Sa) < (Da*(2.0*Sca-Sa))) |
| { |
| pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa)); |
| break; |
| } |
| if ((Dca*Sa) > (2.0*Sca*Da)) |
| { |
| pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca); |
| break; |
| } |
| case PlusCompositeOp: |
| { |
| pixel=QuantumRange*(Sca+Dca); |
| break; |
| } |
| case ReflectCompositeOp: |
| { |
| pixel=QuantumRange*gamma*(Sca*Sca*PerceptibleReciprocal(1.0-Dca)); |
| if (pixel > QuantumRange) |
| pixel=QuantumRange; |
| break; |
| } |
| case RMSECompositeOp: |
| { |
| double |
| gray; |
| |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Sc; |
| break; |
| } |
| gray=sqrt( |
| (canvas_pixel.red-source_pixel.red)* |
| (canvas_pixel.red-source_pixel.red)+ |
| (canvas_pixel.green-source_pixel.green)* |
| (canvas_pixel.green-source_pixel.green)+ |
| (canvas_pixel.blue-source_pixel.blue)* |
| (canvas_pixel.blue-source_pixel.blue)/3.0); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=gray; break; |
| case GreenPixelChannel: pixel=gray; break; |
| case BluePixelChannel: pixel=gray; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case SaturateCompositeOp: |
| { |
| if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Dc; |
| break; |
| } |
| if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon) |
| { |
| pixel=Sc; |
| break; |
| } |
| CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue, |
| &hue,&chroma,&luma); |
| CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue, |
| &sans,&chroma,&sans); |
| HCLComposite(hue,chroma,luma,&red,&green,&blue); |
| switch (channel) |
| { |
| case RedPixelChannel: pixel=red; break; |
| case GreenPixelChannel: pixel=green; break; |
| case BluePixelChannel: pixel=blue; break; |
| default: pixel=Dc; break; |
| } |
| break; |
| } |
| case ScreenCompositeOp: |
| { |
| /* |
| Screen: a negated multiply: |
| |
| f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc) |
| */ |
| pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca); |
| break; |
| } |
| case SoftBurnCompositeOp: |
| { |
| if ((Sca+Dca) < 1.0) |
| pixel=QuantumRange*gamma*(0.5*Dca*PerceptibleReciprocal(1.0-Sca)); |
| else |
| pixel=QuantumRange*gamma*(1.0-0.5*(1.0-Sca)* |
| PerceptibleReciprocal(Dca)); |
| break; |
| } |
| case SoftDodgeCompositeOp: |
| { |
| if ((Sca+Dca) < 1.0) |
| pixel=QuantumRange*gamma*(0.5*Sca*PerceptibleReciprocal(1.0-Dca)); |
| else |
| pixel=QuantumRange*gamma*(1.0-0.5*(1.0-Dca)* |
| PerceptibleReciprocal(Sca)); |
| break; |
| } |
| case SoftLightCompositeOp: |
| { |
| if ((2.0*Sca) < Sa) |
| { |
| pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-DcaDa))+ |
| Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da)) |
| { |
| pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*DcaDa* |
| (4.0*DcaDa+1.0)*(DcaDa-1.0)+7.0*DcaDa)+Sca*(1.0-Da)+ |
| Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow(DcaDa,0.5)- |
| DcaDa)+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case StampCompositeOp: |
| { |
| pixel=QuantumRange*(Sca+Dca*Dca-1.0); |
| break; |
| } |
| case StereoCompositeOp: |
| { |
| if (channel == RedPixelChannel) |
| pixel=(MagickRealType) GetPixelRed(source_image,p); |
| break; |
| } |
| case ThresholdCompositeOp: |
| { |
| MagickRealType |
| delta; |
| |
| delta=Sc-Dc; |
| if ((MagickRealType) fabs((double) (2.0*delta)) < threshold) |
| { |
| pixel=gamma*Dc; |
| break; |
| } |
| pixel=gamma*(Dc+delta*amount); |
| break; |
| } |
| case VividLightCompositeOp: |
| { |
| /* |
| VividLight: A Photoshop 7 composition method. See |
| http://www.simplefilter.de/en/basics/mixmods.html. |
| |
| f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc)) |
| */ |
| if ((fabs((double) Sa) < MagickEpsilon) || |
| (fabs((double) (Sca-Sa)) < MagickEpsilon)) |
| { |
| pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| if ((2.0*Sca) <= Sa) |
| { |
| pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)* |
| PerceptibleReciprocal(2.0*Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| pixel=QuantumRange*gamma*(Dca*Sa*Sa*PerceptibleReciprocal(2.0* |
| (Sa-Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| case XorCompositeOp: |
| { |
| pixel=QuantumRange*(Sca*(1.0-Da)+Dca*(1.0-Sa)); |
| break; |
| } |
| default: |
| { |
| pixel=Sc; |
| break; |
| } |
| } |
| q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel); |
| } |
| p+=GetPixelChannels(source_image); |
| channels=GetPixelChannels(source_image); |
| if (p >= (pixels+channels*source_image->columns)) |
| p=pixels; |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| source_view=DestroyCacheView(source_view); |
| image_view=DestroyCacheView(image_view); |
| if (canvas_image != (Image * ) NULL) |
| canvas_image=DestroyImage(canvas_image); |
| else |
| source_image=DestroyImage(source_image); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % T e x t u r e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % TextureImage() repeatedly tiles the texture image across and down the image |
| % canvas. |
| % |
| % The format of the TextureImage method is: |
| % |
| % MagickBooleanType TextureImage(Image *image,const Image *texture, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o texture_image: This image is the texture to layer on the background. |
| % |
| */ |
| MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture, |
| ExceptionInfo *exception) |
| { |
| #define TextureImageTag "Texture/Image" |
| |
| CacheView |
| *image_view, |
| *texture_view; |
| |
| Image |
| *texture_image; |
| |
| MagickBooleanType |
| status; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"..."); |
| assert(image->signature == MagickCoreSignature); |
| if (texture == (const Image *) NULL) |
| return(MagickFalse); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| texture_image=CloneImage(texture,0,0,MagickTrue,exception); |
| if (texture_image == (const Image *) NULL) |
| return(MagickFalse); |
| (void) TransformImageColorspace(texture_image,image->colorspace,exception); |
| (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod, |
| exception); |
| status=MagickTrue; |
| if ((image->compose != CopyCompositeOp) && |
| ((image->compose != OverCompositeOp) || |
| (image->alpha_trait != UndefinedPixelTrait) || |
| (texture_image->alpha_trait != UndefinedPixelTrait))) |
| { |
| /* |
| Tile texture onto the image background. |
| */ |
| for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows) |
| { |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns) |
| { |
| MagickBooleanType |
| thread_status; |
| |
| thread_status=CompositeImage(image,texture_image,image->compose, |
| MagickTrue,x+texture_image->tile_offset.x,y+ |
| texture_image->tile_offset.y,exception); |
| if (thread_status == MagickFalse) |
| { |
| status=thread_status; |
| break; |
| } |
| } |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType) |
| image->rows,image->rows); |
| texture_image=DestroyImage(texture_image); |
| return(status); |
| } |
| /* |
| Tile texture onto the image background (optimized). |
| */ |
| status=MagickTrue; |
| texture_view=AcquireVirtualCacheView(texture_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(status) \ |
| magick_number_threads(texture_image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| const Quantum |
| *p, |
| *pixels; |
| |
| ssize_t |
| x; |
| |
| Quantum |
| *q; |
| |
| size_t |
| width; |
| |
| if (status == MagickFalse) |
| continue; |
| pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x, |
| (y+texture_image->tile_offset.y) % texture_image->rows, |
| texture_image->columns,1,exception); |
| q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns) |
| { |
| ssize_t |
| j; |
| |
| p=pixels; |
| width=texture_image->columns; |
| if ((x+(ssize_t) width) > (ssize_t) image->columns) |
| width=image->columns-x; |
| for (j=0; j < (ssize_t) width; j++) |
| { |
| ssize_t |
| i; |
| |
| for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(texture_image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| PixelTrait texture_traits=GetPixelChannelTraits(texture_image, |
| channel); |
| if ((traits == UndefinedPixelTrait) || |
| (texture_traits == UndefinedPixelTrait)) |
| continue; |
| SetPixelChannel(image,channel,p[i],q); |
| } |
| p+=GetPixelChannels(texture_image); |
| q+=GetPixelChannels(image); |
| } |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| texture_view=DestroyCacheView(texture_view); |
| image_view=DestroyCacheView(image_view); |
| texture_image=DestroyImage(texture_image); |
| return(status); |
| } |