blob: 27ac6c66bc77fd4352528bc7a2ede16876222e08 [file] [log] [blame]
cristy0b3cd892012-08-01 23:13:50 +00001/*
cristy4c08aed2011-07-01 19:47:50 +00002%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% CCCC OOO M M PPPP OOO SSSSS IIIII TTTTT EEEEE %
7% C O O MM MM P P O O SS I T E %
8% C O O M M M PPPP O O SSS I T EEE %
9% C O O M M P O O SS I T E %
10% CCCC OOO M M P OOO SSSSS IIIII T EEEEE %
11% %
12% %
13% MagickCore Image Composite Methods %
14% %
15% Software Design %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy4c08aed2011-07-01 19:47:50 +000017% July 1992 %
18% %
19% %
cristyb56bb242014-11-25 17:12:48 +000020% Copyright 1999-2015 ImageMagick Studio LLC, a non-profit organization %
cristy4c08aed2011-07-01 19:47:50 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
43#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
45#include "MagickCore/cache.h"
cristy52010022011-10-21 18:07:37 +000046#include "MagickCore/cache-private.h"
cristy4c08aed2011-07-01 19:47:50 +000047#include "MagickCore/cache-view.h"
cristy6a2180c2013-05-27 10:28:36 +000048#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000049#include "MagickCore/client.h"
50#include "MagickCore/color.h"
51#include "MagickCore/color-private.h"
52#include "MagickCore/colorspace.h"
53#include "MagickCore/colorspace-private.h"
54#include "MagickCore/composite.h"
55#include "MagickCore/composite-private.h"
56#include "MagickCore/constitute.h"
57#include "MagickCore/draw.h"
58#include "MagickCore/fx.h"
59#include "MagickCore/gem.h"
60#include "MagickCore/geometry.h"
61#include "MagickCore/image.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/memory_.h"
68#include "MagickCore/option.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantum.h"
72#include "MagickCore/resample.h"
73#include "MagickCore/resource_.h"
74#include "MagickCore/string_.h"
75#include "MagickCore/thread-private.h"
cristy191c0b72012-08-12 16:29:52 +000076#include "MagickCore/threshold.h"
cristy63a81872012-03-22 15:52:52 +000077#include "MagickCore/token.h"
cristy4c08aed2011-07-01 19:47:50 +000078#include "MagickCore/utility.h"
cristyd1dd6e42011-09-04 01:46:08 +000079#include "MagickCore/utility-private.h"
cristy4c08aed2011-07-01 19:47:50 +000080#include "MagickCore/version.h"
81
82/*
83%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84% %
85% %
86% %
cristyf4ad9df2011-07-08 16:49:03 +000087% C o m p o s i t e I m a g e %
cristy4c08aed2011-07-01 19:47:50 +000088% %
89% %
90% %
91%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92%
cristyf4ad9df2011-07-08 16:49:03 +000093% CompositeImage() returns the second image composited onto the first
cristy4c08aed2011-07-01 19:47:50 +000094% at the specified offset, using the specified composite method.
95%
cristyf4ad9df2011-07-08 16:49:03 +000096% The format of the CompositeImage method is:
cristy4c08aed2011-07-01 19:47:50 +000097%
98% MagickBooleanType CompositeImage(Image *image,
cristye8e8eff2015-01-11 00:28:26 +000099% const Image *source_image,const CompositeOperator compose,
cristyfeb3e962012-03-29 17:25:55 +0000100% const MagickBooleanType clip_to_self,const ssize_t x_offset,
101% const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +0000102%
103% A description of each parameter follows:
104%
cristye8e8eff2015-01-11 00:28:26 +0000105% o image: the canvas image, modified by he composition
cristy4c08aed2011-07-01 19:47:50 +0000106%
cristye8e8eff2015-01-11 00:28:26 +0000107% o source_image: the source image.
cristyfeb3e962012-03-29 17:25:55 +0000108%
cristy4c08aed2011-07-01 19:47:50 +0000109% o compose: This operator affects how the composite is applied to
110% the image. The operators and how they are utilized are listed here
111% http://www.w3.org/TR/SVG12/#compositing.
112%
cristyfeb3e962012-03-29 17:25:55 +0000113% o clip_to_self: set to MagickTrue to limit composition to area composed.
cristy4c08aed2011-07-01 19:47:50 +0000114%
115% o x_offset: the column offset of the composited image.
116%
117% o y_offset: the row offset of the composited image.
118%
dirk55a83c72014-08-22 18:44:23 +0000119% Extra Controls from Image meta-data in 'image' (artifacts)
cristy4c08aed2011-07-01 19:47:50 +0000120%
121% o "compose:args"
122% A string containing extra numerical arguments for specific compose
123% methods, generally expressed as a 'geometry' or a comma separated list
124% of numbers.
125%
126% Compose methods needing such arguments include "BlendCompositeOp" and
127% "DisplaceCompositeOp".
128%
cristye941a752011-10-15 01:52:48 +0000129% o exception: return any errors or warnings in this structure.
130%
cristy4c08aed2011-07-01 19:47:50 +0000131*/
132
anthonyea068a52012-04-09 05:46:25 +0000133/*
134 Composition based on the SVG specification:
135
136 A Composition is defined by...
137 Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
138 Blending areas : X = 1 for area of overlap, ie: f(Sc,Dc)
139 Y = 1 for source preserved
cristye8e8eff2015-01-11 00:28:26 +0000140 Z = 1 for canvas preserved
anthonyea068a52012-04-09 05:46:25 +0000141
142 Conversion to transparency (then optimized)
143 Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
144 Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
145
146 Where...
147 Sca = Sc*Sa normalized Source color divided by Source alpha
148 Dca = Dc*Da normalized Dest color divided by Dest alpha
149 Dc' = Dca'/Da' the desired color value for this channel.
150
151 Da' in in the follow formula as 'gamma' The resulting alpla value.
152
153 Most functions use a blending mode of over (X=1,Y=1,Z=1) this results in
154 the following optimizations...
155 gamma = Sa+Da-Sa*Da;
156 gamma = 1 - QuantiumScale*alpha * QuantiumScale*beta;
157 opacity = QuantiumScale*alpha*beta; // over blend, optimized 1-Gamma
158
159 The above SVG definitions also definate that Mathematical Composition
160 methods should use a 'Over' blending mode for Alpha Channel.
161 It however was not applied for composition modes of 'Plus', 'Minus',
162 the modulus versions of 'Add' and 'Subtract'.
163
164 Mathematical operator changes to be applied from IM v6.7...
165
166 1) Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
167 'ModulusAdd' and 'ModulusSubtract' for clarity.
168
169 2) All mathematical compositions work as per the SVG specification
170 with regard to blending. This now includes 'ModulusAdd' and
171 'ModulusSubtract'.
172
173 3) When the special channel flag 'sync' (syncronize channel updates)
174 is turned off (enabled by default) then mathematical compositions are
175 only performed on the channels specified, and are applied
176 independantally of each other. In other words the mathematics is
177 performed as 'pure' mathematical operations, rather than as image
178 operations.
179*/
cristy4c08aed2011-07-01 19:47:50 +0000180
cristy7159f662012-10-28 17:32:43 +0000181static void HCLComposite(const MagickRealType hue,const MagickRealType chroma,
182 const MagickRealType luma,MagickRealType *red,MagickRealType *green,
183 MagickRealType *blue)
cristyd8f16f72012-08-13 12:49:50 +0000184{
cristy7159f662012-10-28 17:32:43 +0000185 MagickRealType
cristyd8f16f72012-08-13 12:49:50 +0000186 b,
cristy7133e642012-08-14 11:04:11 +0000187 c,
cristyd8f16f72012-08-13 12:49:50 +0000188 g,
cristy7133e642012-08-14 11:04:11 +0000189 h,
190 m,
cristyd8f16f72012-08-13 12:49:50 +0000191 r,
cristy398fd022013-06-26 23:08:33 +0000192 x;
cristyd8f16f72012-08-13 12:49:50 +0000193
194 /*
cristy7133e642012-08-14 11:04:11 +0000195 Convert HCL to RGB colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000196 */
cristy7159f662012-10-28 17:32:43 +0000197 assert(red != (MagickRealType *) NULL);
198 assert(green != (MagickRealType *) NULL);
199 assert(blue != (MagickRealType *) NULL);
cristy7133e642012-08-14 11:04:11 +0000200 h=6.0*hue;
201 c=chroma;
202 x=c*(1.0-fabs(fmod(h,2.0)-1.0));
203 r=0.0;
204 g=0.0;
205 b=0.0;
206 if ((0.0 <= h) && (h < 1.0))
cristyd8f16f72012-08-13 12:49:50 +0000207 {
cristye1715282012-08-15 13:10:55 +0000208 r=c;
209 g=x;
cristyd8f16f72012-08-13 12:49:50 +0000210 }
cristyd8f16f72012-08-13 12:49:50 +0000211 else
cristy7133e642012-08-14 11:04:11 +0000212 if ((1.0 <= h) && (h < 2.0))
213 {
cristye1715282012-08-15 13:10:55 +0000214 r=x;
215 g=c;
cristy7133e642012-08-14 11:04:11 +0000216 }
217 else
218 if ((2.0 <= h) && (h < 3.0))
219 {
cristye1715282012-08-15 13:10:55 +0000220 g=c;
221 b=x;
cristy7133e642012-08-14 11:04:11 +0000222 }
223 else
224 if ((3.0 <= h) && (h < 4.0))
225 {
cristye1715282012-08-15 13:10:55 +0000226 g=x;
227 b=c;
cristy7133e642012-08-14 11:04:11 +0000228 }
229 else
230 if ((4.0 <= h) && (h < 5.0))
231 {
cristye1715282012-08-15 13:10:55 +0000232 r=x;
233 b=c;
cristy7133e642012-08-14 11:04:11 +0000234 }
235 else
236 if ((5.0 <= h) && (h < 6.0))
237 {
cristye1715282012-08-15 13:10:55 +0000238 r=c;
239 b=x;
cristy7133e642012-08-14 11:04:11 +0000240 }
cristy9e2436a2013-05-02 20:35:59 +0000241 m=luma-(0.298839*r+0.586811*g+0.114350*b);
cristy398fd022013-06-26 23:08:33 +0000242 *red=QuantumRange*(r+m);
243 *green=QuantumRange*(g+m);
244 *blue=QuantumRange*(b+m);
cristyd8f16f72012-08-13 12:49:50 +0000245}
246
cristy7159f662012-10-28 17:32:43 +0000247static void CompositeHCL(const MagickRealType red,const MagickRealType green,
248 const MagickRealType blue,MagickRealType *hue,MagickRealType *chroma,
249 MagickRealType *luma)
cristyd8f16f72012-08-13 12:49:50 +0000250{
cristy7159f662012-10-28 17:32:43 +0000251 MagickRealType
cristyd8f16f72012-08-13 12:49:50 +0000252 b,
cristy7133e642012-08-14 11:04:11 +0000253 c,
cristyd8f16f72012-08-13 12:49:50 +0000254 g,
cristy7133e642012-08-14 11:04:11 +0000255 h,
cristyd8f16f72012-08-13 12:49:50 +0000256 max,
cristyd8f16f72012-08-13 12:49:50 +0000257 r;
258
259 /*
cristy7133e642012-08-14 11:04:11 +0000260 Convert RGB to HCL colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000261 */
cristy7159f662012-10-28 17:32:43 +0000262 assert(hue != (MagickRealType *) NULL);
263 assert(chroma != (MagickRealType *) NULL);
264 assert(luma != (MagickRealType *) NULL);
cristy7133e642012-08-14 11:04:11 +0000265 r=red;
266 g=green;
267 b=blue;
cristyd8f16f72012-08-13 12:49:50 +0000268 max=MagickMax(r,MagickMax(g,b));
cristy7159f662012-10-28 17:32:43 +0000269 c=max-(MagickRealType) MagickMin(r,MagickMin(g,b));
cristy7133e642012-08-14 11:04:11 +0000270 h=0.0;
271 if (c == 0)
272 h=0.0;
cristyd8f16f72012-08-13 12:49:50 +0000273 else
cristy7133e642012-08-14 11:04:11 +0000274 if (red == max)
cristy9e2436a2013-05-02 20:35:59 +0000275 h=fmod((g-b)/c+6.0,6.0);
cristyd8f16f72012-08-13 12:49:50 +0000276 else
cristy7133e642012-08-14 11:04:11 +0000277 if (green == max)
278 h=((b-r)/c)+2.0;
279 else
280 if (blue == max)
281 h=((r-g)/c)+4.0;
282 *hue=(h/6.0);
283 *chroma=QuantumScale*c;
cristy9e2436a2013-05-02 20:35:59 +0000284 *luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b);
cristyd8f16f72012-08-13 12:49:50 +0000285}
286
cristye4a40472011-12-22 02:56:19 +0000287static MagickBooleanType CompositeOverImage(Image *image,
cristye8e8eff2015-01-11 00:28:26 +0000288 const Image *source_image,const MagickBooleanType clip_to_self,
cristyfeb3e962012-03-29 17:25:55 +0000289 const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristye4a40472011-12-22 02:56:19 +0000290{
291#define CompositeImageTag "Composite/Image"
292
293 CacheView
cristye8e8eff2015-01-11 00:28:26 +0000294 *source_view,
cristye4a40472011-12-22 02:56:19 +0000295 *image_view;
296
cristy47c61c92015-04-21 12:15:20 +0000297 const char
298 *value;
299
cristye4a40472011-12-22 02:56:19 +0000300 MagickBooleanType
cristy47c61c92015-04-21 12:15:20 +0000301 clamp,
cristye4a40472011-12-22 02:56:19 +0000302 status;
303
304 MagickOffsetType
305 progress;
306
307 ssize_t
308 y;
309
cristye4a40472011-12-22 02:56:19 +0000310 /*
cristye4a40472011-12-22 02:56:19 +0000311 Composite image.
312 */
313 status=MagickTrue;
314 progress=0;
cristy47c61c92015-04-21 12:15:20 +0000315 clamp=MagickTrue;
316 value=GetImageArtifact(image,"compose:clamp");
317 if (value != (const char *) NULL)
318 clamp=IsStringTrue(value);
cristye8e8eff2015-01-11 00:28:26 +0000319 source_view=AcquireVirtualCacheView(source_image,exception);
cristy46ff2672012-12-14 15:32:26 +0000320 image_view=AcquireAuthenticCacheView(image,exception);
cristye4a40472011-12-22 02:56:19 +0000321#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000322 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristye8e8eff2015-01-11 00:28:26 +0000323 magick_threads(source_image,image,image->rows,1)
cristye4a40472011-12-22 02:56:19 +0000324#endif
325 for (y=0; y < (ssize_t) image->rows; y++)
326 {
327 const Quantum
328 *pixels;
329
330 register const Quantum
331 *restrict p;
332
333 register Quantum
334 *restrict q;
335
336 register ssize_t
337 x;
338
cristy564a5692012-01-20 23:56:26 +0000339 size_t
340 channels;
341
cristye4a40472011-12-22 02:56:19 +0000342 if (status == MagickFalse)
343 continue;
cristyfeb3e962012-03-29 17:25:55 +0000344 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000345 {
346 if (y < y_offset)
347 continue;
cristye8e8eff2015-01-11 00:28:26 +0000348 if ((y-y_offset) >= (ssize_t) source_image->rows)
cristye4a40472011-12-22 02:56:19 +0000349 continue;
350 }
351 /*
352 If pixels is NULL, y is outside overlay region.
353 */
354 pixels=(Quantum *) NULL;
355 p=(Quantum *) NULL;
cristye8e8eff2015-01-11 00:28:26 +0000356 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
cristye4a40472011-12-22 02:56:19 +0000357 {
cristye8e8eff2015-01-11 00:28:26 +0000358 p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
359 source_image->columns,1,exception);
cristye4a40472011-12-22 02:56:19 +0000360 if (p == (const Quantum *) NULL)
361 {
362 status=MagickFalse;
363 continue;
364 }
365 pixels=p;
366 if (x_offset < 0)
cristye8e8eff2015-01-11 00:28:26 +0000367 p-=x_offset*GetPixelChannels(source_image);
cristye4a40472011-12-22 02:56:19 +0000368 }
369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
370 if (q == (Quantum *) NULL)
371 {
372 status=MagickFalse;
373 continue;
374 }
375 for (x=0; x < (ssize_t) image->columns; x++)
376 {
cristy7159f662012-10-28 17:32:43 +0000377 MagickRealType
cristye4a40472011-12-22 02:56:19 +0000378 Da,
379 Dc,
cristy2bd6f5b2014-12-09 00:27:57 +0000380 Dca,
cristy95a92ef2015-06-12 18:58:35 +0000381 gamma,
cristye4a40472011-12-22 02:56:19 +0000382 Sa,
cristy2bd6f5b2014-12-09 00:27:57 +0000383 Sc,
384 Sca;
cristye4a40472011-12-22 02:56:19 +0000385
386 register ssize_t
387 i;
388
cristyfeb3e962012-03-29 17:25:55 +0000389 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000390 {
391 if (x < x_offset)
392 {
393 q+=GetPixelChannels(image);
394 continue;
395 }
cristye8e8eff2015-01-11 00:28:26 +0000396 if ((x-x_offset) >= (ssize_t) source_image->columns)
cristye4a40472011-12-22 02:56:19 +0000397 break;
398 }
399 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
cristye8e8eff2015-01-11 00:28:26 +0000400 ((x-x_offset) >= (ssize_t) source_image->columns))
cristye4a40472011-12-22 02:56:19 +0000401 {
402 Quantum
403 source[MaxPixelChannels];
404
405 /*
406 Virtual composite:
407 Sc: source color.
cristye8e8eff2015-01-11 00:28:26 +0000408 Dc: canvas color.
cristye4a40472011-12-22 02:56:19 +0000409 */
cristy883fde12013-04-08 00:50:13 +0000410 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +0000411 {
412 q+=GetPixelChannels(image);
413 continue;
414 }
cristye8e8eff2015-01-11 00:28:26 +0000415 (void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,
cristyc94ba6f2012-01-29 23:19:58 +0000416 source,exception);
cristye4a40472011-12-22 02:56:19 +0000417 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
418 {
cristy5a23c552013-02-13 14:34:28 +0000419 PixelChannel channel=GetPixelChannelChannel(image,i);
420 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristye8e8eff2015-01-11 00:28:26 +0000421 PixelTrait source_traits=GetPixelChannelTraits(source_image,
cristy5a23c552013-02-13 14:34:28 +0000422 channel);
cristye4a40472011-12-22 02:56:19 +0000423 if ((traits == UndefinedPixelTrait) ||
cristye8e8eff2015-01-11 00:28:26 +0000424 (source_traits == UndefinedPixelTrait))
cristye4a40472011-12-22 02:56:19 +0000425 continue;
426 q[i]=source[channel];
427 }
428 q+=GetPixelChannels(image);
429 continue;
430 }
431 /*
432 Authentic composite:
433 Sa: normalized source alpha.
cristye8e8eff2015-01-11 00:28:26 +0000434 Da: normalized canvas alpha.
cristye4a40472011-12-22 02:56:19 +0000435 */
cristye8e8eff2015-01-11 00:28:26 +0000436 if (GetPixelReadMask(source_image,p) == 0)
cristyc94ba6f2012-01-29 23:19:58 +0000437 {
cristye8e8eff2015-01-11 00:28:26 +0000438 p+=GetPixelChannels(source_image);
439 channels=GetPixelChannels(source_image);
440 if (p >= (pixels+channels*source_image->columns))
cristyc94ba6f2012-01-29 23:19:58 +0000441 p=pixels;
442 q+=GetPixelChannels(image);
443 continue;
444 }
cristye8e8eff2015-01-11 00:28:26 +0000445 Sa=QuantumScale*GetPixelAlpha(source_image,p);
cristye4a40472011-12-22 02:56:19 +0000446 Da=QuantumScale*GetPixelAlpha(image,q);
cristy95a92ef2015-06-12 18:58:35 +0000447 gamma=Sa+Da-Sa*Da;
448 gamma=PerceptibleReciprocal(gamma);
cristye4a40472011-12-22 02:56:19 +0000449 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
450 {
cristy5a23c552013-02-13 14:34:28 +0000451 PixelChannel channel=GetPixelChannelChannel(image,i);
452 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristye8e8eff2015-01-11 00:28:26 +0000453 PixelTrait source_traits=GetPixelChannelTraits(source_image,
cristy5a23c552013-02-13 14:34:28 +0000454 channel);
cristye4a40472011-12-22 02:56:19 +0000455 if ((traits == UndefinedPixelTrait) ||
cristye8e8eff2015-01-11 00:28:26 +0000456 (source_traits == UndefinedPixelTrait))
cristye4a40472011-12-22 02:56:19 +0000457 continue;
458 if ((traits & CopyPixelTrait) != 0)
459 {
cristye224ed72014-12-10 23:55:01 +0000460 /*
461 Copy channel.
462 */
cristye8e8eff2015-01-11 00:28:26 +0000463 q[i]=GetPixelChannel(source_image,channel,p);
cristye224ed72014-12-10 23:55:01 +0000464 continue;
465 }
466 if (channel == AlphaPixelChannel)
467 {
cristye4a40472011-12-22 02:56:19 +0000468 /*
469 Set alpha channel.
470 */
cristy47c61c92015-04-21 12:15:20 +0000471 q[i]=clamp != MagickFalse ?
472 ClampPixel(QuantumRange*(Sa+Da-Sa*Da)) :
473 ClampToQuantum(QuantumRange*(Sa+Da-Sa*Da));
cristye4a40472011-12-22 02:56:19 +0000474 continue;
475 }
476 /*
477 Sc: source color.
cristye8e8eff2015-01-11 00:28:26 +0000478 Dc: canvas color.
cristye4a40472011-12-22 02:56:19 +0000479 */
cristye8e8eff2015-01-11 00:28:26 +0000480 Sc=(MagickRealType) GetPixelChannel(source_image,channel,p);
cristy7159f662012-10-28 17:32:43 +0000481 Dc=(MagickRealType) q[i];
cristy2bd6f5b2014-12-09 00:27:57 +0000482 Sca=QuantumScale*Sa*Sc;
483 Dca=QuantumScale*Da*Dc;
cristy47c61c92015-04-21 12:15:20 +0000484 q[i]=clamp != MagickFalse ?
cristy95a92ef2015-06-12 18:58:35 +0000485 ClampPixel(gamma*QuantumRange*(Sca+Dca*(1.0-Sa))) :
486 ClampToQuantum(gamma*QuantumRange*(Sca+Dca*(1.0-Sa)));
cristye4a40472011-12-22 02:56:19 +0000487 }
cristye8e8eff2015-01-11 00:28:26 +0000488 p+=GetPixelChannels(source_image);
489 channels=GetPixelChannels(source_image);
490 if (p >= (pixels+channels*source_image->columns))
cristye4a40472011-12-22 02:56:19 +0000491 p=pixels;
492 q+=GetPixelChannels(image);
493 }
494 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
495 status=MagickFalse;
496 if (image->progress_monitor != (MagickProgressMonitor) NULL)
497 {
498 MagickBooleanType
499 proceed;
500
501#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000502 #pragma omp critical (MagickCore_CompositeImage)
cristye4a40472011-12-22 02:56:19 +0000503#endif
504 proceed=SetImageProgress(image,CompositeImageTag,progress++,
505 image->rows);
506 if (proceed == MagickFalse)
507 status=MagickFalse;
508 }
509 }
cristye8e8eff2015-01-11 00:28:26 +0000510 source_view=DestroyCacheView(source_view);
cristye4a40472011-12-22 02:56:19 +0000511 image_view=DestroyCacheView(image_view);
512 return(status);
513}
514
cristy4c08aed2011-07-01 19:47:50 +0000515MagickExport MagickBooleanType CompositeImage(Image *image,
cristya865ccd2012-07-28 00:33:10 +0000516 const Image *composite,const CompositeOperator compose,
cristy44c98412012-03-31 18:25:40 +0000517 const MagickBooleanType clip_to_self,const ssize_t x_offset,
518 const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +0000519{
cristy4c08aed2011-07-01 19:47:50 +0000520#define CompositeImageTag "Composite/Image"
521
522 CacheView
cristye8e8eff2015-01-11 00:28:26 +0000523 *source_view,
cristy4c08aed2011-07-01 19:47:50 +0000524 *image_view;
525
cristy47c61c92015-04-21 12:15:20 +0000526 const char
527 *value;
528
cristy4c08aed2011-07-01 19:47:50 +0000529 GeometryInfo
530 geometry_info;
531
532 Image
cristye8e8eff2015-01-11 00:28:26 +0000533 *canvas_image,
534 *source_image;
cristy4c08aed2011-07-01 19:47:50 +0000535
536 MagickBooleanType
cristy47c61c92015-04-21 12:15:20 +0000537 clamp,
cristy4c08aed2011-07-01 19:47:50 +0000538 status;
539
540 MagickOffsetType
541 progress;
542
cristy7159f662012-10-28 17:32:43 +0000543 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000544 amount,
cristye8e8eff2015-01-11 00:28:26 +0000545 canvas_dissolve,
cristy4c08aed2011-07-01 19:47:50 +0000546 midpoint,
cristy7133e642012-08-14 11:04:11 +0000547 percent_luma,
548 percent_chroma,
cristy4c08aed2011-07-01 19:47:50 +0000549 source_dissolve,
550 threshold;
551
552 MagickStatusType
553 flags;
554
cristyd197cbb2012-01-13 02:14:12 +0000555 ssize_t
556 y;
557
cristy4c08aed2011-07-01 19:47:50 +0000558 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000559 assert(image->signature == MagickCoreSignature);
cristy4c08aed2011-07-01 19:47:50 +0000560 if (image->debug != MagickFalse)
561 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristya865ccd2012-07-28 00:33:10 +0000562 assert(composite!= (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000563 assert(composite->signature == MagickCoreSignature);
cristy574cc262011-08-05 01:23:58 +0000564 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000565 return(MagickFalse);
cristye8e8eff2015-01-11 00:28:26 +0000566 source_image=CloneImage(composite,0,0,MagickTrue,exception);
567 if (source_image == (const Image *) NULL)
cristya865ccd2012-07-28 00:33:10 +0000568 return(MagickFalse);
cristyde067e92013-04-18 00:47:50 +0000569 if (IsGrayColorspace(image->colorspace) != MagickFalse)
570 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristye8e8eff2015-01-11 00:28:26 +0000571 (void) SetImageColorspace(source_image,image->colorspace,exception);
dirk597358b2014-12-26 02:46:43 +0000572 if ((image->alpha_trait != UndefinedPixelTrait) &&
cristye8e8eff2015-01-11 00:28:26 +0000573 (source_image->alpha_trait == UndefinedPixelTrait))
574 (void) SetImageAlphaChannel(source_image,SetAlphaChannel,exception);
cristye4a40472011-12-22 02:56:19 +0000575 if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp))
576 {
cristye8e8eff2015-01-11 00:28:26 +0000577 status=CompositeOverImage(image,source_image,clip_to_self,x_offset,
cristyfeb3e962012-03-29 17:25:55 +0000578 y_offset,exception);
cristye8e8eff2015-01-11 00:28:26 +0000579 source_image=DestroyImage(source_image);
cristye4a40472011-12-22 02:56:19 +0000580 return(status);
581 }
cristy4c08aed2011-07-01 19:47:50 +0000582 amount=0.5;
cristy47c61c92015-04-21 12:15:20 +0000583 canvas_image=(Image *) NULL;
cristye8e8eff2015-01-11 00:28:26 +0000584 canvas_dissolve=1.0;
cristy47c61c92015-04-21 12:15:20 +0000585 clamp=MagickTrue;
586 value=GetImageArtifact(image,"compose:clamp");
587 if (value != (const char *) NULL)
588 clamp=IsStringTrue(value);
cristye92d6b52015-06-08 22:24:52 +0000589 SetGeometryInfo(&geometry_info);
cristy7133e642012-08-14 11:04:11 +0000590 percent_luma=100.0;
591 percent_chroma=100.0;
cristy4c08aed2011-07-01 19:47:50 +0000592 source_dissolve=1.0;
593 threshold=0.05f;
594 switch (compose)
595 {
cristy4c08aed2011-07-01 19:47:50 +0000596 case CopyCompositeOp:
597 {
598 if ((x_offset < 0) || (y_offset < 0))
599 break;
cristye8e8eff2015-01-11 00:28:26 +0000600 if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns)
cristy4c08aed2011-07-01 19:47:50 +0000601 break;
cristye8e8eff2015-01-11 00:28:26 +0000602 if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows)
cristy4c08aed2011-07-01 19:47:50 +0000603 break;
604 status=MagickTrue;
cristye8e8eff2015-01-11 00:28:26 +0000605 source_view=AcquireVirtualCacheView(source_image,exception);
cristy46ff2672012-12-14 15:32:26 +0000606 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000607#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000608 #pragma omp parallel for schedule(static,4) shared(status) \
cristye8e8eff2015-01-11 00:28:26 +0000609 magick_threads(source_image,image,source_image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +0000610#endif
cristye8e8eff2015-01-11 00:28:26 +0000611 for (y=0; y < (ssize_t) source_image->rows; y++)
cristy4c08aed2011-07-01 19:47:50 +0000612 {
613 MagickBooleanType
614 sync;
615
616 register const Quantum
617 *p;
618
619 register Quantum
620 *q;
621
622 register ssize_t
623 x;
624
625 if (status == MagickFalse)
626 continue;
cristy2fb92282015-04-07 00:34:03 +0000627 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
628 exception);
cristy4c08aed2011-07-01 19:47:50 +0000629 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
cristye8e8eff2015-01-11 00:28:26 +0000630 source_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000631 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
632 {
633 status=MagickFalse;
634 continue;
635 }
cristye8e8eff2015-01-11 00:28:26 +0000636 for (x=0; x < (ssize_t) source_image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +0000637 {
cristybdecccc2011-12-24 22:52:16 +0000638 register ssize_t
639 i;
640
cristye8e8eff2015-01-11 00:28:26 +0000641 if (GetPixelReadMask(source_image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000642 {
cristye8e8eff2015-01-11 00:28:26 +0000643 p+=GetPixelChannels(source_image);
cristy10a6c612012-01-29 21:41:05 +0000644 q+=GetPixelChannels(image);
645 continue;
646 }
cristye8e8eff2015-01-11 00:28:26 +0000647 for (i=0; i < (ssize_t) GetPixelChannels(source_image); i++)
cristybdecccc2011-12-24 22:52:16 +0000648 {
cristye8e8eff2015-01-11 00:28:26 +0000649 PixelChannel channel=GetPixelChannelChannel(source_image,i);
650 PixelTrait source_traits=GetPixelChannelTraits(source_image,
cristy5a23c552013-02-13 14:34:28 +0000651 channel);
652 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristybdecccc2011-12-24 22:52:16 +0000653 if ((traits == UndefinedPixelTrait) ||
cristye8e8eff2015-01-11 00:28:26 +0000654 (source_traits == UndefinedPixelTrait))
cristybdecccc2011-12-24 22:52:16 +0000655 continue;
656 SetPixelChannel(image,channel,p[i],q);
657 }
cristye8e8eff2015-01-11 00:28:26 +0000658 p+=GetPixelChannels(source_image);
cristyed231572011-07-14 02:18:59 +0000659 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +0000660 }
661 sync=SyncCacheViewAuthenticPixels(image_view,exception);
662 if (sync == MagickFalse)
663 status=MagickFalse;
664 if (image->progress_monitor != (MagickProgressMonitor) NULL)
665 {
666 MagickBooleanType
667 proceed;
668
669#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +0000670 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +0000671#endif
672 proceed=SetImageProgress(image,CompositeImageTag,
673 (MagickOffsetType) y,image->rows);
674 if (proceed == MagickFalse)
675 status=MagickFalse;
676 }
677 }
cristye8e8eff2015-01-11 00:28:26 +0000678 source_view=DestroyCacheView(source_view);
cristy4c08aed2011-07-01 19:47:50 +0000679 image_view=DestroyCacheView(image_view);
cristye8e8eff2015-01-11 00:28:26 +0000680 source_image=DestroyImage(source_image);
cristy4c08aed2011-07-01 19:47:50 +0000681 return(status);
682 }
cristy17a88592014-11-29 15:05:48 +0000683 case IntensityCompositeOp:
684 {
685 if ((x_offset < 0) || (y_offset < 0))
686 break;
cristye8e8eff2015-01-11 00:28:26 +0000687 if ((x_offset+(ssize_t) source_image->columns) > (ssize_t) image->columns)
cristy17a88592014-11-29 15:05:48 +0000688 break;
cristye8e8eff2015-01-11 00:28:26 +0000689 if ((y_offset+(ssize_t) source_image->rows) > (ssize_t) image->rows)
cristy17a88592014-11-29 15:05:48 +0000690 break;
691 status=MagickTrue;
cristye8e8eff2015-01-11 00:28:26 +0000692 source_view=AcquireVirtualCacheView(source_image,exception);
cristy17a88592014-11-29 15:05:48 +0000693 image_view=AcquireAuthenticCacheView(image,exception);
694#if defined(MAGICKCORE_OPENMP_SUPPORT)
695 #pragma omp parallel for schedule(static,4) shared(status) \
cristye8e8eff2015-01-11 00:28:26 +0000696 magick_threads(source_image,image,source_image->rows,1)
cristy17a88592014-11-29 15:05:48 +0000697#endif
cristye8e8eff2015-01-11 00:28:26 +0000698 for (y=0; y < (ssize_t) source_image->rows; y++)
cristy17a88592014-11-29 15:05:48 +0000699 {
700 MagickBooleanType
701 sync;
702
703 register const Quantum
704 *p;
705
706 register Quantum
707 *q;
708
709 register ssize_t
710 x;
711
712 if (status == MagickFalse)
713 continue;
cristy2fb92282015-04-07 00:34:03 +0000714 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
715 exception);
cristy17a88592014-11-29 15:05:48 +0000716 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
cristye8e8eff2015-01-11 00:28:26 +0000717 source_image->columns,1,exception);
cristy17a88592014-11-29 15:05:48 +0000718 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
719 {
720 status=MagickFalse;
721 continue;
722 }
cristye8e8eff2015-01-11 00:28:26 +0000723 for (x=0; x < (ssize_t) source_image->columns; x++)
cristy17a88592014-11-29 15:05:48 +0000724 {
cristye8e8eff2015-01-11 00:28:26 +0000725 if (GetPixelReadMask(source_image,p) == 0)
cristy17a88592014-11-29 15:05:48 +0000726 {
cristye8e8eff2015-01-11 00:28:26 +0000727 p+=GetPixelChannels(source_image);
cristy17a88592014-11-29 15:05:48 +0000728 q+=GetPixelChannels(image);
729 continue;
730 }
cristy47c61c92015-04-21 12:15:20 +0000731 SetPixelAlpha(image,clamp != MagickFalse ?
732 ClampPixel(GetPixelIntensity(source_image,p)) :
733 ClampToQuantum(GetPixelIntensity(source_image,p)),q);
cristye8e8eff2015-01-11 00:28:26 +0000734 p+=GetPixelChannels(source_image);
cristy17a88592014-11-29 15:05:48 +0000735 q+=GetPixelChannels(image);
736 }
737 sync=SyncCacheViewAuthenticPixels(image_view,exception);
738 if (sync == MagickFalse)
739 status=MagickFalse;
740 if (image->progress_monitor != (MagickProgressMonitor) NULL)
741 {
742 MagickBooleanType
743 proceed;
744
745#if defined(MAGICKCORE_OPENMP_SUPPORT)
746 #pragma omp critical (MagickCore_CompositeImage)
747#endif
748 proceed=SetImageProgress(image,CompositeImageTag,
749 (MagickOffsetType) y,image->rows);
750 if (proceed == MagickFalse)
751 status=MagickFalse;
752 }
753 }
cristye8e8eff2015-01-11 00:28:26 +0000754 source_view=DestroyCacheView(source_view);
cristy17a88592014-11-29 15:05:48 +0000755 image_view=DestroyCacheView(image_view);
cristye8e8eff2015-01-11 00:28:26 +0000756 source_image=DestroyImage(source_image);
cristy17a88592014-11-29 15:05:48 +0000757 return(status);
758 }
cristye4a40472011-12-22 02:56:19 +0000759 case CopyAlphaCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000760 case ChangeMaskCompositeOp:
761 {
762 /*
cristye8e8eff2015-01-11 00:28:26 +0000763 Modify canvas outside the overlaid region and require an alpha
cristy4c08aed2011-07-01 19:47:50 +0000764 channel to exist, to add transparency.
765 */
cristy17f11b02014-12-20 19:37:04 +0000766 if (image->alpha_trait == UndefinedPixelTrait)
cristy42c41de2012-05-05 18:36:31 +0000767 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy4c08aed2011-07-01 19:47:50 +0000768 break;
769 }
770 case BlurCompositeOp:
771 {
772 CacheView
cristye8e8eff2015-01-11 00:28:26 +0000773 *canvas_view,
774 *source_view;
cristy4c08aed2011-07-01 19:47:50 +0000775
cristyfeb3e962012-03-29 17:25:55 +0000776 const char
777 *value;
778
cristy7159f662012-10-28 17:32:43 +0000779 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000780 angle_range,
781 angle_start,
782 height,
783 width;
784
cristybce4f4a2012-10-14 14:57:47 +0000785 PixelInfo
786 pixel;
787
cristy4c08aed2011-07-01 19:47:50 +0000788 ResampleFilter
789 *resample_filter;
790
791 SegmentInfo
792 blur;
793
794 /*
anthony9cb63cc2012-04-25 06:10:49 +0000795 Blur Image by resampling.
796
cristy4c08aed2011-07-01 19:47:50 +0000797 Blur Image dictated by an overlay gradient map: X = red_channel;
798 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
799 */
cristye8e8eff2015-01-11 00:28:26 +0000800 canvas_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000801 exception);
cristye8e8eff2015-01-11 00:28:26 +0000802 if (canvas_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000803 {
cristye8e8eff2015-01-11 00:28:26 +0000804 source_image=DestroyImage(source_image);
cristy44886b92012-07-28 13:07:09 +0000805 return(MagickFalse);
806 }
cristy4c08aed2011-07-01 19:47:50 +0000807 /*
anthony9cb63cc2012-04-25 06:10:49 +0000808 Gather the maximum blur sigma values from user.
cristy4c08aed2011-07-01 19:47:50 +0000809 */
cristy4c08aed2011-07-01 19:47:50 +0000810 flags=NoValue;
cristy1a780952013-02-10 17:15:30 +0000811 value=GetImageArtifact(image,"compose:args");
812 if (value != (const char *) NULL)
cristy4c08aed2011-07-01 19:47:50 +0000813 flags=ParseGeometry(value,&geometry_info);
cristy1a780952013-02-10 17:15:30 +0000814 if ((flags & WidthValue) == 0)
815 {
816 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
817 "InvalidSetting","'%s' '%s'","compose:args",value);
cristye8e8eff2015-01-11 00:28:26 +0000818 source_image=DestroyImage(source_image);
819 canvas_image=DestroyImage(canvas_image);
cristy4c08aed2011-07-01 19:47:50 +0000820 return(MagickFalse);
821 }
anthony9cb63cc2012-04-25 06:10:49 +0000822 /*
823 Users input sigma now needs to be converted to the EWA ellipse size.
824 The filter defaults to a sigma of 0.5 so to make this match the
825 users input the ellipse size needs to be doubled.
anthonyd2923912012-04-23 13:06:53 +0000826 */
827 width=height=geometry_info.rho*2.0;
828 if ((flags & HeightValue) != 0 )
829 height=geometry_info.sigma*2.0;
cristy7159f662012-10-28 17:32:43 +0000830 /*
831 Default the unrotated ellipse width and height axis vectors.
832 */
anthonyd2923912012-04-23 13:06:53 +0000833 blur.x1=width;
cristy4c08aed2011-07-01 19:47:50 +0000834 blur.x2=0.0;
835 blur.y1=0.0;
anthonyd2923912012-04-23 13:06:53 +0000836 blur.y2=height;
837 /* rotate vectors if a rotation angle is given */
cristy4c08aed2011-07-01 19:47:50 +0000838 if ((flags & XValue) != 0 )
839 {
cristy7159f662012-10-28 17:32:43 +0000840 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000841 angle;
842
843 angle=DegreesToRadians(geometry_info.xi);
844 blur.x1=width*cos(angle);
845 blur.x2=width*sin(angle);
846 blur.y1=(-height*sin(angle));
847 blur.y2=height*cos(angle);
848 }
anthonyd2923912012-04-23 13:06:53 +0000849 /* Otherwise lets set a angle range and calculate in the loop */
850 angle_start=0.0;
851 angle_range=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000852 if ((flags & YValue) != 0 )
853 {
854 angle_start=DegreesToRadians(geometry_info.xi);
855 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
856 }
857 /*
anthony9cb63cc2012-04-25 06:10:49 +0000858 Set up a gaussian cylindrical filter for EWA Bluring.
anthonyd2923912012-04-23 13:06:53 +0000859
anthony9cb63cc2012-04-25 06:10:49 +0000860 As the minimum ellipse radius of support*1.0 the EWA algorithm
861 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
862 This means that even 'No Blur' will be still a little blurry!
anthonyd2923912012-04-23 13:06:53 +0000863
anthony9cb63cc2012-04-25 06:10:49 +0000864 The solution (as well as the problem of preventing any user
865 expert filter settings, is to set our own user settings, then
866 restore them afterwards.
cristy4c08aed2011-07-01 19:47:50 +0000867 */
cristy8a11cb12011-10-19 23:53:34 +0000868 resample_filter=AcquireResampleFilter(image,exception);
anthonyd2923912012-04-23 13:06:53 +0000869 SetResampleFilter(resample_filter,GaussianFilter);
anthony9cb63cc2012-04-25 06:10:49 +0000870
871 /* do the variable blurring of each pixel in image */
872 GetPixelInfo(image,&pixel);
cristye8e8eff2015-01-11 00:28:26 +0000873 source_view=AcquireVirtualCacheView(source_image,exception);
874 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
875 for (y=0; y < (ssize_t) source_image->rows; y++)
cristy4c08aed2011-07-01 19:47:50 +0000876 {
877 MagickBooleanType
878 sync;
879
880 register const Quantum
881 *restrict p;
882
883 register Quantum
884 *restrict q;
885
886 register ssize_t
887 x;
888
889 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
890 continue;
cristy2fb92282015-04-07 00:34:03 +0000891 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
892 exception);
893 q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1,
894 exception);
cristy4c08aed2011-07-01 19:47:50 +0000895 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
896 break;
cristye8e8eff2015-01-11 00:28:26 +0000897 for (x=0; x < (ssize_t) source_image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +0000898 {
899 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
900 {
cristye8e8eff2015-01-11 00:28:26 +0000901 p+=GetPixelChannels(source_image);
cristy4c08aed2011-07-01 19:47:50 +0000902 continue;
903 }
cristy3bdd9252014-12-21 20:01:43 +0000904 if (fabs((double) angle_range) > MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000905 {
cristy7159f662012-10-28 17:32:43 +0000906 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000907 angle;
908
909 angle=angle_start+angle_range*QuantumScale*
cristye8e8eff2015-01-11 00:28:26 +0000910 GetPixelBlue(source_image,p);
cristy4c08aed2011-07-01 19:47:50 +0000911 blur.x1=width*cos(angle);
912 blur.x2=width*sin(angle);
913 blur.y1=(-height*sin(angle));
914 blur.y2=height*cos(angle);
915 }
anthonyd2923912012-04-23 13:06:53 +0000916#if 0
917 if ( x == 10 && y == 60 ) {
cristy7159f662012-10-28 17:32:43 +0000918 (void) fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",blur.x1,
919 blur.x2,blur.y1, blur.y2);
920 (void) fprintf(stderr, "scaled by=%lf,%lf\n",QuantumScale*
921 GetPixelRed(p),QuantumScale*GetPixelGreen(p));
anthonyd2923912012-04-23 13:06:53 +0000922#endif
923 ScaleResampleFilter(resample_filter,
cristye8e8eff2015-01-11 00:28:26 +0000924 blur.x1*QuantumScale*GetPixelRed(source_image,p),
925 blur.y1*QuantumScale*GetPixelGreen(source_image,p),
926 blur.x2*QuantumScale*GetPixelRed(source_image,p),
927 blur.y2*QuantumScale*GetPixelGreen(source_image,p) );
cristy4c08aed2011-07-01 19:47:50 +0000928 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
cristydb070952012-04-20 14:33:00 +0000929 (double) y_offset+y,&pixel,exception);
cristye8e8eff2015-01-11 00:28:26 +0000930 SetPixelViaPixelInfo(canvas_image,&pixel,q);
931 p+=GetPixelChannels(source_image);
932 q+=GetPixelChannels(canvas_image);
cristy4c08aed2011-07-01 19:47:50 +0000933 }
cristye8e8eff2015-01-11 00:28:26 +0000934 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
cristy4c08aed2011-07-01 19:47:50 +0000935 if (sync == MagickFalse)
936 break;
937 }
938 resample_filter=DestroyResampleFilter(resample_filter);
cristye8e8eff2015-01-11 00:28:26 +0000939 source_view=DestroyCacheView(source_view);
940 canvas_view=DestroyCacheView(canvas_view);
941 source_image=DestroyImage(source_image);
942 source_image=canvas_image;
cristy4c08aed2011-07-01 19:47:50 +0000943 break;
944 }
945 case DisplaceCompositeOp:
946 case DistortCompositeOp:
947 {
948 CacheView
cristye8e8eff2015-01-11 00:28:26 +0000949 *source_view,
950 *canvas_view,
cristy4c08aed2011-07-01 19:47:50 +0000951 *image_view;
952
cristyfeb3e962012-03-29 17:25:55 +0000953 const char
954 *value;
955
cristy7159f662012-10-28 17:32:43 +0000956 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000957 horizontal_scale,
958 vertical_scale;
959
cristy4f60be52015-01-14 23:55:59 +0000960 PixelInfo
961 pixel;
962
cristy4c08aed2011-07-01 19:47:50 +0000963 PointInfo
964 center,
965 offset;
966
967 /*
968 Displace/Distort based on overlay gradient map:
969 X = red_channel; Y = green_channel;
970 compose:args = x_scale[,y_scale[,center.x,center.y]]
971 */
cristye8e8eff2015-01-11 00:28:26 +0000972 canvas_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000973 exception);
cristye8e8eff2015-01-11 00:28:26 +0000974 if (canvas_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000975 {
cristye8e8eff2015-01-11 00:28:26 +0000976 source_image=DestroyImage(source_image);
cristy44886b92012-07-28 13:07:09 +0000977 return(MagickFalse);
978 }
cristy4c08aed2011-07-01 19:47:50 +0000979 SetGeometryInfo(&geometry_info);
980 flags=NoValue;
dirk55a83c72014-08-22 18:44:23 +0000981 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +0000982 if (value != (char *) NULL)
983 flags=ParseGeometry(value,&geometry_info);
cristy48054f32015-01-16 01:12:18 +0000984 if ((flags & (WidthValue | HeightValue)) == 0 )
cristy4c08aed2011-07-01 19:47:50 +0000985 {
986 if ((flags & AspectValue) == 0)
987 {
cristyad1b6502015-01-12 01:13:41 +0000988 horizontal_scale=(MagickRealType) (source_image->columns-1)/2.0;
989 vertical_scale=(MagickRealType) (source_image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000990 }
991 else
992 {
cristyad1b6502015-01-12 01:13:41 +0000993 horizontal_scale=(MagickRealType) (image->columns-1)/2.0;
994 vertical_scale=(MagickRealType) (image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000995 }
996 }
997 else
998 {
999 horizontal_scale=geometry_info.rho;
1000 vertical_scale=geometry_info.sigma;
1001 if ((flags & PercentValue) != 0)
1002 {
1003 if ((flags & AspectValue) == 0)
1004 {
cristyad1b6502015-01-12 01:13:41 +00001005 horizontal_scale*=(source_image->columns-1)/200.0;
1006 vertical_scale*=(source_image->rows-1)/200.0;
cristy4c08aed2011-07-01 19:47:50 +00001007 }
1008 else
1009 {
cristyad1b6502015-01-12 01:13:41 +00001010 horizontal_scale*=(image->columns-1)/200.0;
1011 vertical_scale*=(image->rows-1)/200.0;
cristy4c08aed2011-07-01 19:47:50 +00001012 }
1013 }
1014 if ((flags & HeightValue) == 0)
1015 vertical_scale=horizontal_scale;
1016 }
1017 /*
1018 Determine fixed center point for absolute distortion map
1019 Absolute distort ==
1020 Displace offset relative to a fixed absolute point
1021 Select that point according to +X+Y user inputs.
1022 default = center of overlay image
1023 arg flag '!' = locations/percentage relative to background image
1024 */
cristy7159f662012-10-28 17:32:43 +00001025 center.x=(MagickRealType) x_offset;
1026 center.y=(MagickRealType) y_offset;
cristy4c08aed2011-07-01 19:47:50 +00001027 if (compose == DistortCompositeOp)
1028 {
1029 if ((flags & XValue) == 0)
cristy190f4792015-01-16 21:04:23 +00001030 if ((flags & AspectValue) != 0)
1031 center.x=(MagickRealType) ((image->columns-1)/2.0);
1032 else
cristye8e8eff2015-01-11 00:28:26 +00001033 center.x=(MagickRealType) (x_offset+(source_image->columns-1)/
cristy7159f662012-10-28 17:32:43 +00001034 2.0);
cristy4c08aed2011-07-01 19:47:50 +00001035 else
cristy190f4792015-01-16 21:04:23 +00001036 if ((flags & AspectValue) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001037 center.x=geometry_info.xi;
cristy190f4792015-01-16 21:04:23 +00001038 else
1039 center.x=(MagickRealType) (x_offset+geometry_info.xi);
cristy4c08aed2011-07-01 19:47:50 +00001040 if ((flags & YValue) == 0)
cristy190f4792015-01-16 21:04:23 +00001041 if ((flags & AspectValue) != 0)
cristyad1b6502015-01-12 01:13:41 +00001042 center.y=(MagickRealType) ((image->rows-1)/2.0);
cristy4c08aed2011-07-01 19:47:50 +00001043 else
cristy190f4792015-01-16 21:04:23 +00001044 center.y=(MagickRealType) (y_offset+(source_image->rows-1)/2.0);
1045 else
1046 if ((flags & AspectValue) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001047 center.y=geometry_info.psi;
cristy190f4792015-01-16 21:04:23 +00001048 else
1049 center.y=(MagickRealType) (y_offset+geometry_info.psi);
cristy4c08aed2011-07-01 19:47:50 +00001050 }
1051 /*
1052 Shift the pixel offset point as defined by the provided,
1053 displacement/distortion map. -- Like a lens...
1054 */
cristye10859a2011-12-18 22:28:59 +00001055 GetPixelInfo(image,&pixel);
cristy46ff2672012-12-14 15:32:26 +00001056 image_view=AcquireVirtualCacheView(image,exception);
cristye8e8eff2015-01-11 00:28:26 +00001057 source_view=AcquireVirtualCacheView(source_image,exception);
1058 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
1059 for (y=0; y < (ssize_t) source_image->rows; y++)
cristy4c08aed2011-07-01 19:47:50 +00001060 {
1061 MagickBooleanType
1062 sync;
1063
1064 register const Quantum
1065 *restrict p;
1066
1067 register Quantum
1068 *restrict q;
1069
1070 register ssize_t
1071 x;
1072
1073 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1074 continue;
cristyad1b6502015-01-12 01:13:41 +00001075 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,1,
1076 exception);
1077 q=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,1,
1078 exception);
cristy4c08aed2011-07-01 19:47:50 +00001079 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1080 break;
cristye8e8eff2015-01-11 00:28:26 +00001081 for (x=0; x < (ssize_t) source_image->columns; x++)
cristy4c08aed2011-07-01 19:47:50 +00001082 {
1083 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1084 {
cristye8e8eff2015-01-11 00:28:26 +00001085 p+=GetPixelChannels(source_image);
cristy4c08aed2011-07-01 19:47:50 +00001086 continue;
1087 }
1088 /*
1089 Displace the offset.
1090 */
cristye8e8eff2015-01-11 00:28:26 +00001091 offset.x=(double) (horizontal_scale*(GetPixelRed(source_image,p)-
cristy7159f662012-10-28 17:32:43 +00001092 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
1093 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
1094 x : 0);
cristye8e8eff2015-01-11 00:28:26 +00001095 offset.y=(double) (vertical_scale*(GetPixelGreen(source_image,p)-
cristy7159f662012-10-28 17:32:43 +00001096 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
1097 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
1098 y : 0);
cristyc692a832015-01-12 11:10:02 +00001099 (void) InterpolatePixelInfo(image,image_view,
1100 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
1101 &pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00001102 /*
1103 Mask with the 'invalid pixel mask' in alpha channel.
1104 */
cristy4f60be52015-01-14 23:55:59 +00001105 pixel.alpha=(MagickRealType) QuantumRange*(QuantumScale*pixel.alpha)*
1106 (QuantumScale*GetPixelAlpha(source_image,p));
cristye8e8eff2015-01-11 00:28:26 +00001107 SetPixelViaPixelInfo(canvas_image,&pixel,q);
1108 p+=GetPixelChannels(source_image);
1109 q+=GetPixelChannels(canvas_image);
cristy4c08aed2011-07-01 19:47:50 +00001110 }
cristye8e8eff2015-01-11 00:28:26 +00001111 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
cristy4c08aed2011-07-01 19:47:50 +00001112 if (sync == MagickFalse)
1113 break;
1114 }
cristye8e8eff2015-01-11 00:28:26 +00001115 canvas_view=DestroyCacheView(canvas_view);
1116 source_view=DestroyCacheView(source_view);
cristy4c08aed2011-07-01 19:47:50 +00001117 image_view=DestroyCacheView(image_view);
cristye8e8eff2015-01-11 00:28:26 +00001118 source_image=DestroyImage(source_image);
1119 source_image=canvas_image;
cristy4c08aed2011-07-01 19:47:50 +00001120 break;
1121 }
1122 case DissolveCompositeOp:
1123 {
cristyfeb3e962012-03-29 17:25:55 +00001124 const char
1125 *value;
1126
cristy4c08aed2011-07-01 19:47:50 +00001127 /*
1128 Geometry arguments to dissolve factors.
1129 */
dirk55a83c72014-08-22 18:44:23 +00001130 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001131 if (value != (char *) NULL)
1132 {
1133 flags=ParseGeometry(value,&geometry_info);
1134 source_dissolve=geometry_info.rho/100.0;
cristye8e8eff2015-01-11 00:28:26 +00001135 canvas_dissolve=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001136 if ((source_dissolve-MagickEpsilon) < 0.0)
1137 source_dissolve=0.0;
1138 if ((source_dissolve+MagickEpsilon) > 1.0)
1139 {
cristye8e8eff2015-01-11 00:28:26 +00001140 canvas_dissolve=2.0-source_dissolve;
cristy4c08aed2011-07-01 19:47:50 +00001141 source_dissolve=1.0;
1142 }
1143 if ((flags & SigmaValue) != 0)
cristye8e8eff2015-01-11 00:28:26 +00001144 canvas_dissolve=geometry_info.sigma/100.0;
1145 if ((canvas_dissolve-MagickEpsilon) < 0.0)
1146 canvas_dissolve=0.0;
cristy4c08aed2011-07-01 19:47:50 +00001147 }
1148 break;
1149 }
1150 case BlendCompositeOp:
1151 {
cristyfeb3e962012-03-29 17:25:55 +00001152 const char
1153 *value;
1154
dirk55a83c72014-08-22 18:44:23 +00001155 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001156 if (value != (char *) NULL)
1157 {
1158 flags=ParseGeometry(value,&geometry_info);
1159 source_dissolve=geometry_info.rho/100.0;
cristye8e8eff2015-01-11 00:28:26 +00001160 canvas_dissolve=1.0-source_dissolve;
cristy4c08aed2011-07-01 19:47:50 +00001161 if ((flags & SigmaValue) != 0)
cristye8e8eff2015-01-11 00:28:26 +00001162 canvas_dissolve=geometry_info.sigma/100.0;
cristy4c08aed2011-07-01 19:47:50 +00001163 }
1164 break;
1165 }
1166 case MathematicsCompositeOp:
1167 {
cristyfeb3e962012-03-29 17:25:55 +00001168 const char
1169 *value;
1170
cristy4c08aed2011-07-01 19:47:50 +00001171 /*
1172 Just collect the values from "compose:args", setting.
1173 Unused values are set to zero automagically.
1174
1175 Arguments are normally a comma separated list, so this probably should
1176 be changed to some 'general comma list' parser, (with a minimum
1177 number of values)
1178 */
1179 SetGeometryInfo(&geometry_info);
dirk55a83c72014-08-22 18:44:23 +00001180 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001181 if (value != (char *) NULL)
1182 (void) ParseGeometry(value,&geometry_info);
1183 break;
1184 }
1185 case ModulateCompositeOp:
1186 {
cristyfeb3e962012-03-29 17:25:55 +00001187 const char
1188 *value;
1189
cristy4c08aed2011-07-01 19:47:50 +00001190 /*
cristy7133e642012-08-14 11:04:11 +00001191 Determine the luma and chroma scale.
cristy4c08aed2011-07-01 19:47:50 +00001192 */
dirk55a83c72014-08-22 18:44:23 +00001193 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001194 if (value != (char *) NULL)
1195 {
1196 flags=ParseGeometry(value,&geometry_info);
cristy7133e642012-08-14 11:04:11 +00001197 percent_luma=geometry_info.rho;
cristy4c08aed2011-07-01 19:47:50 +00001198 if ((flags & SigmaValue) != 0)
cristy7133e642012-08-14 11:04:11 +00001199 percent_chroma=geometry_info.sigma;
cristy4c08aed2011-07-01 19:47:50 +00001200 }
1201 break;
1202 }
1203 case ThresholdCompositeOp:
1204 {
cristyfeb3e962012-03-29 17:25:55 +00001205 const char
1206 *value;
1207
cristy4c08aed2011-07-01 19:47:50 +00001208 /*
1209 Determine the amount and threshold.
1210 */
dirk55a83c72014-08-22 18:44:23 +00001211 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001212 if (value != (char *) NULL)
1213 {
1214 flags=ParseGeometry(value,&geometry_info);
1215 amount=geometry_info.rho;
1216 threshold=geometry_info.sigma;
1217 if ((flags & SigmaValue) == 0)
1218 threshold=0.05f;
1219 }
1220 threshold*=QuantumRange;
1221 break;
1222 }
1223 default:
1224 break;
1225 }
cristy4c08aed2011-07-01 19:47:50 +00001226 /*
1227 Composite image.
1228 */
1229 status=MagickTrue;
1230 progress=0;
cristy7159f662012-10-28 17:32:43 +00001231 midpoint=((MagickRealType) QuantumRange+1.0)/2;
cristye8e8eff2015-01-11 00:28:26 +00001232 source_view=AcquireVirtualCacheView(source_image,exception);
cristy46ff2672012-12-14 15:32:26 +00001233 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001234#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001235 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristye8e8eff2015-01-11 00:28:26 +00001236 magick_threads(source_image,image,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00001237#endif
1238 for (y=0; y < (ssize_t) image->rows; y++)
1239 {
1240 const Quantum
1241 *pixels;
1242
cristy7159f662012-10-28 17:32:43 +00001243 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001244 blue,
cristy12e4d2e2015-01-20 17:00:03 +00001245 chroma,
cristye4a40472011-12-22 02:56:19 +00001246 green,
cristy4c08aed2011-07-01 19:47:50 +00001247 hue,
cristy12e4d2e2015-01-20 17:00:03 +00001248 luma,
1249 red;
cristy4c08aed2011-07-01 19:47:50 +00001250
cristyddeeea22012-04-12 01:33:09 +00001251 PixelInfo
cristye8e8eff2015-01-11 00:28:26 +00001252 canvas_pixel,
cristyddeeea22012-04-12 01:33:09 +00001253 source_pixel;
1254
cristy4c08aed2011-07-01 19:47:50 +00001255 register const Quantum
1256 *restrict p;
1257
1258 register Quantum
1259 *restrict q;
1260
1261 register ssize_t
1262 x;
1263
1264 if (status == MagickFalse)
1265 continue;
cristyfeb3e962012-03-29 17:25:55 +00001266 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001267 {
1268 if (y < y_offset)
1269 continue;
cristye8e8eff2015-01-11 00:28:26 +00001270 if ((y-y_offset) >= (ssize_t) source_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00001271 continue;
1272 }
1273 /*
1274 If pixels is NULL, y is outside overlay region.
1275 */
1276 pixels=(Quantum *) NULL;
1277 p=(Quantum *) NULL;
cristye8e8eff2015-01-11 00:28:26 +00001278 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
cristy4c08aed2011-07-01 19:47:50 +00001279 {
cristye8e8eff2015-01-11 00:28:26 +00001280 p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
1281 source_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001282 if (p == (const Quantum *) NULL)
1283 {
1284 status=MagickFalse;
1285 continue;
1286 }
1287 pixels=p;
1288 if (x_offset < 0)
cristye8e8eff2015-01-11 00:28:26 +00001289 p-=x_offset*GetPixelChannels(source_image);
cristy4c08aed2011-07-01 19:47:50 +00001290 }
1291 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001292 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00001293 {
1294 status=MagickFalse;
1295 continue;
1296 }
cristy4c08aed2011-07-01 19:47:50 +00001297 hue=0.0;
cristy7133e642012-08-14 11:04:11 +00001298 chroma=0.0;
1299 luma=0.0;
cristye8e8eff2015-01-11 00:28:26 +00001300 GetPixelInfo(image,&canvas_pixel);
1301 GetPixelInfo(source_image,&source_pixel);
cristy4c08aed2011-07-01 19:47:50 +00001302 for (x=0; x < (ssize_t) image->columns; x++)
1303 {
cristy17028dc2013-01-24 12:28:39 +00001304 double
1305 gamma;
1306
cristy7159f662012-10-28 17:32:43 +00001307 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001308 alpha,
1309 Da,
1310 Dc,
1311 Dca,
cristye4a40472011-12-22 02:56:19 +00001312 Sa,
1313 Sc,
1314 Sca;
1315
1316 register ssize_t
1317 i;
1318
cristy564a5692012-01-20 23:56:26 +00001319 size_t
1320 channels;
1321
cristyfeb3e962012-03-29 17:25:55 +00001322 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001323 {
1324 if (x < x_offset)
1325 {
cristyed231572011-07-14 02:18:59 +00001326 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001327 continue;
1328 }
cristye8e8eff2015-01-11 00:28:26 +00001329 if ((x-x_offset) >= (ssize_t) source_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00001330 break;
1331 }
cristye4a40472011-12-22 02:56:19 +00001332 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
cristye8e8eff2015-01-11 00:28:26 +00001333 ((x-x_offset) >= (ssize_t) source_image->columns))
cristye4a40472011-12-22 02:56:19 +00001334 {
1335 Quantum
1336 source[MaxPixelChannels];
1337
1338 /*
1339 Virtual composite:
1340 Sc: source color.
cristye8e8eff2015-01-11 00:28:26 +00001341 Dc: canvas color.
cristye4a40472011-12-22 02:56:19 +00001342 */
cristy2fb92282015-04-07 00:34:03 +00001343 (void) GetOneVirtualPixel(source_image,x-x_offset,y-y_offset,source,
1344 exception);
cristy883fde12013-04-08 00:50:13 +00001345 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001346 {
1347 q+=GetPixelChannels(image);
1348 continue;
1349 }
cristye4a40472011-12-22 02:56:19 +00001350 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1351 {
cristy7159f662012-10-28 17:32:43 +00001352 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001353 pixel;
1354
cristy5a23c552013-02-13 14:34:28 +00001355 PixelChannel channel=GetPixelChannelChannel(image,i);
1356 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristye8e8eff2015-01-11 00:28:26 +00001357 PixelTrait source_traits=GetPixelChannelTraits(source_image,
cristy5a23c552013-02-13 14:34:28 +00001358 channel);
cristye4a40472011-12-22 02:56:19 +00001359 if ((traits == UndefinedPixelTrait) ||
cristye8e8eff2015-01-11 00:28:26 +00001360 (source_traits == UndefinedPixelTrait))
cristye4a40472011-12-22 02:56:19 +00001361 continue;
1362 switch (compose)
1363 {
cristyc8d63672012-01-11 13:03:13 +00001364 case AlphaCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001365 case ChangeMaskCompositeOp:
1366 case CopyAlphaCompositeOp:
1367 case DstAtopCompositeOp:
1368 case DstInCompositeOp:
1369 case InCompositeOp:
1370 case OutCompositeOp:
1371 case SrcInCompositeOp:
1372 case SrcOutCompositeOp:
1373 {
cristye4a40472011-12-22 02:56:19 +00001374 if (channel == AlphaPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001375 pixel=(MagickRealType) TransparentAlpha;
dirk1474c082014-12-17 16:11:01 +00001376 else
1377 pixel=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001378 break;
1379 }
1380 case ClearCompositeOp:
1381 case CopyCompositeOp:
1382 case ReplaceCompositeOp:
1383 case SrcCompositeOp:
1384 {
1385 if (channel == AlphaPixelChannel)
dirk1474c082014-12-17 16:11:01 +00001386 pixel=(MagickRealType) TransparentAlpha;
1387 else
1388 pixel=0.0;
cristye4a40472011-12-22 02:56:19 +00001389 break;
1390 }
cristy99abff32011-12-24 20:45:16 +00001391 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001392 case DissolveCompositeOp:
1393 {
1394 if (channel == AlphaPixelChannel)
cristy12e4d2e2015-01-20 17:00:03 +00001395 pixel=canvas_dissolve*GetPixelAlpha(source_image,source);
dirk1474c082014-12-17 16:11:01 +00001396 else
1397 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001398 break;
1399 }
1400 default:
1401 {
cristy7159f662012-10-28 17:32:43 +00001402 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001403 break;
1404 }
1405 }
cristy47c61c92015-04-21 12:15:20 +00001406 q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
1407 ClampToQuantum(pixel);
cristye4a40472011-12-22 02:56:19 +00001408 }
1409 q+=GetPixelChannels(image);
1410 continue;
1411 }
1412 /*
1413 Authentic composite:
1414 Sa: normalized source alpha.
cristye8e8eff2015-01-11 00:28:26 +00001415 Da: normalized canvas alpha.
cristye4a40472011-12-22 02:56:19 +00001416 */
cristye8e8eff2015-01-11 00:28:26 +00001417 Sa=QuantumScale*GetPixelAlpha(source_image,p);
cristye4a40472011-12-22 02:56:19 +00001418 Da=QuantumScale*GetPixelAlpha(image,q);
cristy4c08aed2011-07-01 19:47:50 +00001419 switch (compose)
1420 {
cristye4a40472011-12-22 02:56:19 +00001421 case BumpmapCompositeOp:
1422 {
cristye8e8eff2015-01-11 00:28:26 +00001423 alpha=GetPixelIntensity(source_image,p)*Sa;
cristye4a40472011-12-22 02:56:19 +00001424 break;
1425 }
cristycdc168f2011-12-21 15:24:39 +00001426 case ColorBurnCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001427 case ColorDodgeCompositeOp:
cristy30dac202014-12-10 02:03:31 +00001428 case DarkenCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001429 case DifferenceCompositeOp:
1430 case DivideDstCompositeOp:
1431 case DivideSrcCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001432 case ExclusionCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001433 case HardLightCompositeOp:
cristy99fc2172014-06-26 10:30:53 +00001434 case HardMixCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001435 case LinearBurnCompositeOp:
1436 case LinearDodgeCompositeOp:
1437 case LinearLightCompositeOp:
cristy30dac202014-12-10 02:03:31 +00001438 case LightenCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001439 case MathematicsCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001440 case MinusDstCompositeOp:
1441 case MinusSrcCompositeOp:
1442 case ModulusAddCompositeOp:
1443 case ModulusSubtractCompositeOp:
1444 case MultiplyCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001445 case OverlayCompositeOp:
1446 case PegtopLightCompositeOp:
1447 case PinLightCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001448 case ScreenCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001449 case SoftLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001450 case VividLightCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001451 {
1452 alpha=RoundToUnity(Sa+Da-Sa*Da);
1453 break;
1454 }
cristye4a40472011-12-22 02:56:19 +00001455 case DstAtopCompositeOp:
1456 case DstInCompositeOp:
1457 case InCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001458 case SrcInCompositeOp:
1459 {
1460 alpha=Sa*Da;
1461 break;
1462 }
1463 case DissolveCompositeOp:
1464 {
cristy2fb92282015-04-07 00:34:03 +00001465 alpha=source_dissolve*Sa*(-canvas_dissolve*Da)+source_dissolve*Sa+
1466 canvas_dissolve*Da;
cristye4a40472011-12-22 02:56:19 +00001467 break;
1468 }
1469 case DstOverCompositeOp:
dirk17495972015-05-23 15:25:51 +00001470 case OverCompositeOp:
1471 case SrcOverCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001472 {
cristy30dac202014-12-10 02:03:31 +00001473 alpha=Sa+Da-Sa*Da;
cristye4a40472011-12-22 02:56:19 +00001474 break;
1475 }
1476 case DstOutCompositeOp:
1477 {
1478 alpha=Da*(1.0-Sa);
1479 break;
1480 }
1481 case OutCompositeOp:
1482 case SrcOutCompositeOp:
1483 {
1484 alpha=Sa*(1.0-Da);
1485 break;
1486 }
cristy99abff32011-12-24 20:45:16 +00001487 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001488 case PlusCompositeOp:
1489 {
cristyb37bf162015-06-19 20:38:33 +00001490 alpha=RoundToUnity(source_dissolve*Sa+canvas_dissolve*Da);
cristye4a40472011-12-22 02:56:19 +00001491 break;
1492 }
cristy4c08aed2011-07-01 19:47:50 +00001493 case XorCompositeOp:
1494 {
cristye4a40472011-12-22 02:56:19 +00001495 alpha=Sa+Da-2.0*Sa*Da;
cristy4c08aed2011-07-01 19:47:50 +00001496 break;
1497 }
1498 default:
cristye4a40472011-12-22 02:56:19 +00001499 {
1500 alpha=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001501 break;
cristye4a40472011-12-22 02:56:19 +00001502 }
cristy4c08aed2011-07-01 19:47:50 +00001503 }
cristy883fde12013-04-08 00:50:13 +00001504 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001505 {
cristye8e8eff2015-01-11 00:28:26 +00001506 p+=GetPixelChannels(source_image);
cristy10a6c612012-01-29 21:41:05 +00001507 q+=GetPixelChannels(image);
1508 continue;
1509 }
cristy9d3d2792012-04-14 15:15:19 +00001510 switch (compose)
1511 {
1512 case ColorizeCompositeOp:
1513 case HueCompositeOp:
1514 case LuminizeCompositeOp:
1515 case ModulateCompositeOp:
1516 case SaturateCompositeOp:
1517 {
cristye8e8eff2015-01-11 00:28:26 +00001518 GetPixelInfoPixel(source_image,p,&source_pixel);
1519 GetPixelInfoPixel(image,q,&canvas_pixel);
cristy9d3d2792012-04-14 15:15:19 +00001520 break;
1521 }
1522 default:
1523 break;
1524 }
cristye4a40472011-12-22 02:56:19 +00001525 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1526 {
cristy7159f662012-10-28 17:32:43 +00001527 MagickRealType
1528 pixel,
cristy564a5692012-01-20 23:56:26 +00001529 sans;
1530
cristy5a23c552013-02-13 14:34:28 +00001531 PixelChannel channel=GetPixelChannelChannel(image,i);
1532 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy2fb92282015-04-07 00:34:03 +00001533 PixelTrait source_traits=GetPixelChannelTraits(source_image,channel);
cristy0cd1f212012-01-05 15:45:59 +00001534 if (traits == UndefinedPixelTrait)
1535 continue;
cristye8e8eff2015-01-11 00:28:26 +00001536 if ((source_traits == UndefinedPixelTrait) &&
dirk597358b2014-12-26 02:46:43 +00001537 (((compose != CopyAlphaCompositeOp) &&
1538 (compose != ChangeMaskCompositeOp)) ||
1539 (channel != AlphaPixelChannel)))
1540 continue;
cristye4a40472011-12-22 02:56:19 +00001541 /*
1542 Sc: source color.
cristye8e8eff2015-01-11 00:28:26 +00001543 Dc: canvas color.
cristye4a40472011-12-22 02:56:19 +00001544 */
cristye8e8eff2015-01-11 00:28:26 +00001545 Sc=(MagickRealType) GetPixelChannel(source_image,channel,p);
cristy7159f662012-10-28 17:32:43 +00001546 Dc=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001547 if ((traits & CopyPixelTrait) != 0)
cristye10859a2011-12-18 22:28:59 +00001548 {
cristye224ed72014-12-10 23:55:01 +00001549 /*
1550 Copy channel.
1551 */
cristy47c61c92015-04-21 12:15:20 +00001552 q[i]=Sc;
cristye224ed72014-12-10 23:55:01 +00001553 continue;
1554 }
1555 if (channel == AlphaPixelChannel)
1556 {
cristye4a40472011-12-22 02:56:19 +00001557 /*
1558 Set alpha channel.
1559 */
cristye10859a2011-12-18 22:28:59 +00001560 switch (compose)
1561 {
cristyc8d63672012-01-11 13:03:13 +00001562 case AlphaCompositeOp:
1563 {
cristya7b07912012-01-11 20:01:32 +00001564 pixel=QuantumRange*Sa;
cristyc8d63672012-01-11 13:03:13 +00001565 break;
1566 }
cristye4a40472011-12-22 02:56:19 +00001567 case AtopCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001568 case CopyBlackCompositeOp:
1569 case CopyBlueCompositeOp:
1570 case CopyCyanCompositeOp:
1571 case CopyGreenCompositeOp:
1572 case CopyMagentaCompositeOp:
1573 case CopyRedCompositeOp:
1574 case CopyYellowCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001575 case SrcAtopCompositeOp:
1576 case DstCompositeOp:
1577 case NoCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001578 {
cristye4a40472011-12-22 02:56:19 +00001579 pixel=QuantumRange*Da;
cristye10859a2011-12-18 22:28:59 +00001580 break;
1581 }
cristye10859a2011-12-18 22:28:59 +00001582 case ChangeMaskCompositeOp:
1583 {
cristye4a40472011-12-22 02:56:19 +00001584 MagickBooleanType
1585 equivalent;
1586
cristy7159f662012-10-28 17:32:43 +00001587 if (Da > ((MagickRealType) QuantumRange/2.0))
cristy99abff32011-12-24 20:45:16 +00001588 {
cristy7159f662012-10-28 17:32:43 +00001589 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001590 break;
1591 }
cristye8e8eff2015-01-11 00:28:26 +00001592 equivalent=IsFuzzyEquivalencePixel(source_image,p,image,q);
cristy99abff32011-12-24 20:45:16 +00001593 if (equivalent != MagickFalse)
dirk40e1cb52014-12-22 21:38:03 +00001594 pixel=(MagickRealType) TransparentAlpha;
1595 else
1596 pixel=(MagickRealType) OpaqueAlpha;
cristye4a40472011-12-22 02:56:19 +00001597 break;
1598 }
cristy99abff32011-12-24 20:45:16 +00001599 case ClearCompositeOp:
1600 {
cristy7159f662012-10-28 17:32:43 +00001601 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001602 break;
1603 }
1604 case ColorizeCompositeOp:
1605 case HueCompositeOp:
1606 case LuminizeCompositeOp:
1607 case SaturateCompositeOp:
1608 {
cristy3bdd9252014-12-21 20:01:43 +00001609 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristy99abff32011-12-24 20:45:16 +00001610 {
1611 pixel=QuantumRange*Da;
1612 break;
1613 }
cristy3bdd9252014-12-21 20:01:43 +00001614 if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
cristy99abff32011-12-24 20:45:16 +00001615 {
1616 pixel=QuantumRange*Sa;
1617 break;
1618 }
1619 if (Sa < Da)
1620 {
1621 pixel=QuantumRange*Da;
1622 break;
1623 }
1624 pixel=QuantumRange*Sa;
1625 break;
1626 }
cristy99abff32011-12-24 20:45:16 +00001627 case CopyAlphaCompositeOp:
cristy24d5d722012-05-17 12:27:27 +00001628 {
cristy46598932015-03-28 00:34:18 +00001629 if ((source_traits & BlendPixelTrait) == 0)
cristye8e8eff2015-01-11 00:28:26 +00001630 pixel=GetPixelIntensity(source_image,p);
dirk40e1cb52014-12-22 21:38:03 +00001631 else
1632 pixel=QuantumRange*Sa;
cristy24d5d722012-05-17 12:27:27 +00001633 break;
1634 }
1635 case CopyCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001636 case DisplaceCompositeOp:
1637 case DistortCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001638 case DstAtopCompositeOp:
1639 case ReplaceCompositeOp:
1640 case SrcCompositeOp:
1641 {
1642 pixel=QuantumRange*Sa;
1643 break;
1644 }
1645 case DarkenIntensityCompositeOp:
1646 {
cristye8e8eff2015-01-11 00:28:26 +00001647 pixel=Sa*GetPixelIntensity(source_image,p) <
cristy076d0c92015-01-08 02:02:38 +00001648 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye4a40472011-12-22 02:56:19 +00001649 break;
1650 }
1651 case LightenIntensityCompositeOp:
1652 {
cristye8e8eff2015-01-11 00:28:26 +00001653 pixel=Sa*GetPixelIntensity(source_image,p) >
cristye4a40472011-12-22 02:56:19 +00001654 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye10859a2011-12-18 22:28:59 +00001655 break;
1656 }
cristy99abff32011-12-24 20:45:16 +00001657 case ModulateCompositeOp:
1658 {
cristy99abff32011-12-24 20:45:16 +00001659 pixel=QuantumRange*Da;
1660 break;
1661 }
cristye10859a2011-12-18 22:28:59 +00001662 default:
1663 {
cristye4a40472011-12-22 02:56:19 +00001664 pixel=QuantumRange*alpha;
cristye10859a2011-12-18 22:28:59 +00001665 break;
1666 }
1667 }
cristy47c61c92015-04-21 12:15:20 +00001668 q[i]=clamp != MagickFalse ? ClampPixel(pixel) :
1669 ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00001670 continue;
1671 }
1672 /*
cristy99abff32011-12-24 20:45:16 +00001673 Porter-Duff compositions:
1674 Sca: source normalized color multiplied by alpha.
cristye8e8eff2015-01-11 00:28:26 +00001675 Dca: normalized canvas color multiplied by alpha.
cristye10859a2011-12-18 22:28:59 +00001676 */
cristy99abff32011-12-24 20:45:16 +00001677 Sca=QuantumScale*Sa*Sc;
1678 Dca=QuantumScale*Da*Dc;
cristye10859a2011-12-18 22:28:59 +00001679 switch (compose)
1680 {
cristye10859a2011-12-18 22:28:59 +00001681 case DarkenCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001682 case LightenCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001683 case ModulusSubtractCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001684 {
cristyf0202be2015-05-11 23:30:53 +00001685 gamma=PerceptibleReciprocal(1.0-alpha);
cristye10859a2011-12-18 22:28:59 +00001686 break;
1687 }
1688 default:
dirk17495972015-05-23 15:25:51 +00001689 {
cristyf0202be2015-05-11 23:30:53 +00001690 gamma=PerceptibleReciprocal(alpha);
cristye10859a2011-12-18 22:28:59 +00001691 break;
cristyf0202be2015-05-11 23:30:53 +00001692 }
cristye10859a2011-12-18 22:28:59 +00001693 }
cristyd197cbb2012-01-13 02:14:12 +00001694 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001695 switch (compose)
1696 {
cristya7b07912012-01-11 20:01:32 +00001697 case AlphaCompositeOp:
1698 {
1699 pixel=QuantumRange*Sa;
1700 break;
1701 }
cristye4a40472011-12-22 02:56:19 +00001702 case AtopCompositeOp:
1703 case SrcAtopCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001704 {
cristye224ed72014-12-10 23:55:01 +00001705 pixel=QuantumRange*(Sca*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001706 break;
cristye10859a2011-12-18 22:28:59 +00001707 }
cristye4a40472011-12-22 02:56:19 +00001708 case BlendCompositeOp:
1709 {
cristye8e8eff2015-01-11 00:28:26 +00001710 pixel=gamma*(source_dissolve*Sa*Sc+canvas_dissolve*Da*Dc);
cristye4a40472011-12-22 02:56:19 +00001711 break;
1712 }
1713 case BlurCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001714 case CopyCompositeOp:
1715 case ReplaceCompositeOp:
1716 case SrcCompositeOp:
1717 {
cristy30dac202014-12-10 02:03:31 +00001718 pixel=QuantumRange*Sca;
cristye4a40472011-12-22 02:56:19 +00001719 break;
1720 }
cristy8e028782015-01-17 23:43:07 +00001721 case DisplaceCompositeOp:
1722 case DistortCompositeOp:
1723 {
1724 pixel=Sc;
1725 break;
1726 }
cristye4a40472011-12-22 02:56:19 +00001727 case BumpmapCompositeOp:
1728 {
cristy3bdd9252014-12-21 20:01:43 +00001729 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001730 {
1731 pixel=Dc;
1732 break;
1733 }
cristye8e8eff2015-01-11 00:28:26 +00001734 pixel=QuantumScale*GetPixelIntensity(source_image,p)*Dc;
cristye4a40472011-12-22 02:56:19 +00001735 break;
1736 }
cristy99abff32011-12-24 20:45:16 +00001737 case ChangeMaskCompositeOp:
1738 {
1739 pixel=Dc;
1740 break;
1741 }
1742 case ClearCompositeOp:
1743 {
1744 pixel=0.0;
1745 break;
1746 }
cristye4a40472011-12-22 02:56:19 +00001747 case ColorBurnCompositeOp:
1748 {
cristy30dac202014-12-10 02:03:31 +00001749 if ((Sca == 0.0) && (Dca == Da))
cristye4a40472011-12-22 02:56:19 +00001750 {
cristy95a92ef2015-06-12 18:58:35 +00001751 pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001752 break;
1753 }
cristy30dac202014-12-10 02:03:31 +00001754 if (Sca == 0.0)
cristye4a40472011-12-22 02:56:19 +00001755 {
cristy95a92ef2015-06-12 18:58:35 +00001756 pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001757 break;
1758 }
cristy95a92ef2015-06-12 18:58:35 +00001759 pixel=QuantumRange*gamma*(Sa*Da-Sa*Da*MagickMin(1.0,(1.0-Dca/Da)*Sa/
1760 Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001761 break;
1762 }
1763 case ColorDodgeCompositeOp:
1764 {
cristy30dac202014-12-10 02:03:31 +00001765 if ((Sca == Sa) && (Dca == 0.0))
1766 {
cristy95a92ef2015-06-12 18:58:35 +00001767 pixel=QuantumRange*gamma*(Sca*(1.0-Da));
cristy30dac202014-12-10 02:03:31 +00001768 break;
1769 }
1770 if (Sca == Sa)
cristye4a40472011-12-22 02:56:19 +00001771 {
cristy95a92ef2015-06-12 18:58:35 +00001772 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001773 break;
1774 }
cristy95a92ef2015-06-12 18:58:35 +00001775 pixel=QuantumRange*gamma*(Sa*Da*MagickMin(1.0,Dca/Da*Sa/(Sa-Sca)));
cristye4a40472011-12-22 02:56:19 +00001776 break;
1777 }
1778 case ColorizeCompositeOp:
1779 {
cristy3bdd9252014-12-21 20:01:43 +00001780 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001781 {
1782 pixel=Dc;
1783 break;
1784 }
cristy3bdd9252014-12-21 20:01:43 +00001785 if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001786 {
1787 pixel=Sc;
1788 break;
1789 }
cristy2fb92282015-04-07 00:34:03 +00001790 CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
1791 &sans,&sans,&luma);
cristy7133e642012-08-14 11:04:11 +00001792 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1793 &hue,&chroma,&sans);
1794 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001795 switch (channel)
1796 {
1797 case RedPixelChannel: pixel=red; break;
1798 case GreenPixelChannel: pixel=green; break;
1799 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001800 default: pixel=Dc; break;
1801 }
1802 break;
1803 }
cristye4a40472011-12-22 02:56:19 +00001804 case CopyAlphaCompositeOp:
1805 {
cristy24d5d722012-05-17 12:27:27 +00001806 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001807 break;
1808 }
1809 case CopyBlackCompositeOp:
1810 {
cristyd197cbb2012-01-13 02:14:12 +00001811 if (channel == BlackPixelChannel)
cristy2fda00b2013-10-21 16:12:19 +00001812 pixel=(MagickRealType) (QuantumRange-
cristye8e8eff2015-01-11 00:28:26 +00001813 GetPixelBlack(source_image,p));
cristye4a40472011-12-22 02:56:19 +00001814 break;
1815 }
1816 case CopyBlueCompositeOp:
1817 case CopyYellowCompositeOp:
1818 {
cristyd197cbb2012-01-13 02:14:12 +00001819 if (channel == BluePixelChannel)
cristye8e8eff2015-01-11 00:28:26 +00001820 pixel=(MagickRealType) GetPixelBlue(source_image,p);
cristye4a40472011-12-22 02:56:19 +00001821 break;
1822 }
1823 case CopyGreenCompositeOp:
1824 case CopyMagentaCompositeOp:
1825 {
cristyd197cbb2012-01-13 02:14:12 +00001826 if (channel == GreenPixelChannel)
cristye8e8eff2015-01-11 00:28:26 +00001827 pixel=(MagickRealType) GetPixelGreen(source_image,p);
cristye4a40472011-12-22 02:56:19 +00001828 break;
1829 }
1830 case CopyRedCompositeOp:
1831 case CopyCyanCompositeOp:
1832 {
cristyd197cbb2012-01-13 02:14:12 +00001833 if (channel == RedPixelChannel)
cristye8e8eff2015-01-11 00:28:26 +00001834 pixel=(MagickRealType) GetPixelRed(source_image,p);
cristye4a40472011-12-22 02:56:19 +00001835 break;
1836 }
cristy99abff32011-12-24 20:45:16 +00001837 case DarkenCompositeOp:
1838 {
1839 /*
1840 Darken is equivalent to a 'Minimum' method
1841 OR a greyscale version of a binary 'Or'
1842 OR the 'Intersection' of pixel sets.
1843 */
cristy30dac202014-12-10 02:03:31 +00001844 if ((Sca*Da) < (Dca*Sa))
cristy99abff32011-12-24 20:45:16 +00001845 {
cristye224ed72014-12-10 23:55:01 +00001846 pixel=QuantumRange*(Sca+Dca*(1.0-Sa));
cristy99abff32011-12-24 20:45:16 +00001847 break;
1848 }
cristye224ed72014-12-10 23:55:01 +00001849 pixel=QuantumRange*(Dca+Sca*(1.0-Da));
cristy99abff32011-12-24 20:45:16 +00001850 break;
1851 }
cristye4a40472011-12-22 02:56:19 +00001852 case DarkenIntensityCompositeOp:
1853 {
cristye8e8eff2015-01-11 00:28:26 +00001854 pixel=Sa*GetPixelIntensity(source_image,p) <
cristy076d0c92015-01-08 02:02:38 +00001855 Da*GetPixelIntensity(image,q) ? Sc : Dc;
cristye4a40472011-12-22 02:56:19 +00001856 break;
1857 }
1858 case DifferenceCompositeOp:
1859 {
cristy95a92ef2015-06-12 18:58:35 +00001860 pixel=QuantumRange*gamma*(Sca+Dca-2.0*MagickMin(Sca*Da,Dca*Sa));
cristye4a40472011-12-22 02:56:19 +00001861 break;
1862 }
1863 case DissolveCompositeOp:
1864 {
1865 pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
cristye8e8eff2015-01-11 00:28:26 +00001866 canvas_dissolve*Da*Dc+canvas_dissolve*Da*Dc);
cristye4a40472011-12-22 02:56:19 +00001867 break;
1868 }
1869 case DivideDstCompositeOp:
1870 {
cristy3bdd9252014-12-21 20:01:43 +00001871 if ((fabs((double) Sca) < MagickEpsilon) &&
1872 (fabs((double) Dca) < MagickEpsilon))
cristye4a40472011-12-22 02:56:19 +00001873 {
cristy95a92ef2015-06-12 18:58:35 +00001874 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001875 break;
1876 }
cristy3bdd9252014-12-21 20:01:43 +00001877 if (fabs((double) Dca) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001878 {
cristy95a92ef2015-06-12 18:58:35 +00001879 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001880 break;
1881 }
cristy95a92ef2015-06-12 18:58:35 +00001882 pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001883 break;
1884 }
1885 case DivideSrcCompositeOp:
1886 {
cristy3bdd9252014-12-21 20:01:43 +00001887 if ((fabs((double) Dca) < MagickEpsilon) &&
1888 (fabs((double) Sca) < MagickEpsilon))
cristye4a40472011-12-22 02:56:19 +00001889 {
cristy95a92ef2015-06-12 18:58:35 +00001890 pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001891 break;
1892 }
cristy3bdd9252014-12-21 20:01:43 +00001893 if (fabs((double) Sca) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001894 {
cristy95a92ef2015-06-12 18:58:35 +00001895 pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001896 break;
1897 }
cristy95a92ef2015-06-12 18:58:35 +00001898 pixel=QuantumRange*gamma*(Dca*Sa*Sa/Sca+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001899 break;
1900 }
1901 case DstAtopCompositeOp:
1902 {
cristye224ed72014-12-10 23:55:01 +00001903 pixel=QuantumRange*(Dca*Sa+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001904 break;
1905 }
1906 case DstCompositeOp:
1907 case NoCompositeOp:
1908 {
cristy30dac202014-12-10 02:03:31 +00001909 pixel=QuantumRange*Dca;
cristye4a40472011-12-22 02:56:19 +00001910 break;
1911 }
1912 case DstInCompositeOp:
1913 {
cristye224ed72014-12-10 23:55:01 +00001914 pixel=QuantumRange*(Dca*Sa);
cristye4a40472011-12-22 02:56:19 +00001915 break;
1916 }
1917 case DstOutCompositeOp:
1918 {
cristye224ed72014-12-10 23:55:01 +00001919 pixel=QuantumRange*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001920 break;
1921 }
1922 case DstOverCompositeOp:
1923 {
cristy95a92ef2015-06-12 18:58:35 +00001924 pixel=QuantumRange*gamma*(Dca+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001925 break;
1926 }
1927 case ExclusionCompositeOp:
1928 {
cristy95a92ef2015-06-12 18:58:35 +00001929 pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
1930 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001931 break;
1932 }
1933 case HardLightCompositeOp:
1934 {
1935 if ((2.0*Sca) < Sa)
1936 {
cristy95a92ef2015-06-12 18:58:35 +00001937 pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-
1938 Sa));
cristye4a40472011-12-22 02:56:19 +00001939 break;
1940 }
cristy95a92ef2015-06-12 18:58:35 +00001941 pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
1942 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001943 break;
1944 }
cristy99fc2172014-06-26 10:30:53 +00001945 case HardMixCompositeOp:
1946 {
dirk17495972015-05-23 15:25:51 +00001947 pixel=gamma*(((Sca+Dca) < 1.0) ? 0.0 : QuantumRange);
cristy99fc2172014-06-26 10:30:53 +00001948 break;
1949 }
cristye4a40472011-12-22 02:56:19 +00001950 case HueCompositeOp:
1951 {
cristy3bdd9252014-12-21 20:01:43 +00001952 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001953 {
1954 pixel=Dc;
1955 break;
1956 }
cristy3bdd9252014-12-21 20:01:43 +00001957 if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001958 {
1959 pixel=Sc;
1960 break;
1961 }
cristy2fb92282015-04-07 00:34:03 +00001962 CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
1963 &hue,&chroma,&luma);
cristy7133e642012-08-14 11:04:11 +00001964 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
cristye4a40472011-12-22 02:56:19 +00001965 &hue,&sans,&sans);
cristy7133e642012-08-14 11:04:11 +00001966 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001967 switch (channel)
1968 {
1969 case RedPixelChannel: pixel=red; break;
1970 case GreenPixelChannel: pixel=green; break;
1971 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001972 default: pixel=Dc; break;
1973 }
1974 break;
1975 }
1976 case InCompositeOp:
1977 case SrcInCompositeOp:
1978 {
cristye224ed72014-12-10 23:55:01 +00001979 pixel=QuantumRange*(Sca*Da);
cristye4a40472011-12-22 02:56:19 +00001980 break;
1981 }
cristy99abff32011-12-24 20:45:16 +00001982 case LinearBurnCompositeOp:
1983 {
1984 /*
1985 LinearBurn: as defined by Abode Photoshop, according to
1986 http://www.simplefilter.de/en/basics/mixmods.html is:
1987
1988 f(Sc,Dc) = Sc + Dc - 1
1989 */
cristy95a92ef2015-06-12 18:58:35 +00001990 pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
cristy99abff32011-12-24 20:45:16 +00001991 break;
1992 }
1993 case LinearDodgeCompositeOp:
1994 {
cristy95a92ef2015-06-12 18:58:35 +00001995 pixel=gamma*(Sa*Sc+Da*Dc);
cristy99abff32011-12-24 20:45:16 +00001996 break;
1997 }
1998 case LinearLightCompositeOp:
1999 {
2000 /*
2001 LinearLight: as defined by Abode Photoshop, according to
2002 http://www.simplefilter.de/en/basics/mixmods.html is:
2003
2004 f(Sc,Dc) = Dc + 2*Sc - 1
2005 */
cristy95a92ef2015-06-12 18:58:35 +00002006 pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
cristy99abff32011-12-24 20:45:16 +00002007 break;
2008 }
2009 case LightenCompositeOp:
2010 {
cristy30dac202014-12-10 02:03:31 +00002011 if ((Sca*Da) > (Dca*Sa))
cristy99abff32011-12-24 20:45:16 +00002012 {
cristye224ed72014-12-10 23:55:01 +00002013 pixel=QuantumRange*(Sca+Dca*(1.0-Sa));
cristy99abff32011-12-24 20:45:16 +00002014 break;
2015 }
cristye224ed72014-12-10 23:55:01 +00002016 pixel=QuantumRange*(Dca+Sca*(1.0-Da));
cristy99abff32011-12-24 20:45:16 +00002017 break;
2018 }
cristye4a40472011-12-22 02:56:19 +00002019 case LightenIntensityCompositeOp:
2020 {
2021 /*
2022 Lighten is equivalent to a 'Maximum' method
2023 OR a greyscale version of a binary 'And'
2024 OR the 'Union' of pixel sets.
2025 */
cristye8e8eff2015-01-11 00:28:26 +00002026 pixel=Sa*GetPixelIntensity(source_image,p) >
cristye4a40472011-12-22 02:56:19 +00002027 Da*GetPixelIntensity(image,q) ? Sc : Dc;
2028 break;
2029 }
cristye4a40472011-12-22 02:56:19 +00002030 case LuminizeCompositeOp:
2031 {
cristy3bdd9252014-12-21 20:01:43 +00002032 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002033 {
2034 pixel=Dc;
2035 break;
2036 }
cristy3bdd9252014-12-21 20:01:43 +00002037 if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002038 {
2039 pixel=Sc;
2040 break;
2041 }
cristy2fb92282015-04-07 00:34:03 +00002042 CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
2043 &hue,&chroma,&luma);
cristy7133e642012-08-14 11:04:11 +00002044 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2045 &sans,&sans,&luma);
2046 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002047 switch (channel)
2048 {
2049 case RedPixelChannel: pixel=red; break;
2050 case GreenPixelChannel: pixel=green; break;
2051 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002052 default: pixel=Dc; break;
2053 }
2054 break;
2055 }
2056 case MathematicsCompositeOp:
2057 {
2058 /*
2059 'Mathematics' a free form user control mathematical composition
2060 is defined as...
2061
2062 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
2063
2064 Where the arguments A,B,C,D are (currently) passed to composite
2065 as a command separated 'geometry' string in "compose:args" image
2066 artifact.
2067
2068 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
2069
2070 Applying the SVG transparency formula (see above), we get...
2071
2072 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
2073
2074 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
2075 Dca*(1.0-Sa)
2076 */
cristy95a92ef2015-06-12 18:58:35 +00002077 pixel=QuantumRange*gamma*(geometry_info.rho*Sca*Dca+
cristy744051e2015-01-18 14:10:46 +00002078 geometry_info.sigma*Sca*Da+geometry_info.xi*Dca*Sa+
2079 geometry_info.psi*Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002080 break;
2081 }
2082 case MinusDstCompositeOp:
2083 {
cristy95a92ef2015-06-12 18:58:35 +00002084 pixel=QuantumRange*gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
cristye4a40472011-12-22 02:56:19 +00002085 break;
2086 }
2087 case MinusSrcCompositeOp:
2088 {
2089 /*
cristye8e8eff2015-01-11 00:28:26 +00002090 Minus source from canvas.
cristye4a40472011-12-22 02:56:19 +00002091
2092 f(Sc,Dc) = Sc - Dc
2093 */
cristy95a92ef2015-06-12 18:58:35 +00002094 pixel=QuantumRange*gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
cristye4a40472011-12-22 02:56:19 +00002095 break;
2096 }
2097 case ModulateCompositeOp:
2098 {
2099 ssize_t
2100 offset;
2101
cristy3bdd9252014-12-21 20:01:43 +00002102 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002103 {
2104 pixel=Dc;
2105 break;
2106 }
cristye8e8eff2015-01-11 00:28:26 +00002107 offset=(ssize_t) (GetPixelIntensity(source_image,p)-midpoint);
cristye4a40472011-12-22 02:56:19 +00002108 if (offset == 0)
2109 {
2110 pixel=Dc;
2111 break;
2112 }
cristy2fb92282015-04-07 00:34:03 +00002113 CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
2114 &hue,&chroma,&luma);
cristy7133e642012-08-14 11:04:11 +00002115 luma+=(0.01*percent_luma*offset)/midpoint;
2116 chroma*=0.01*percent_chroma;
2117 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002118 switch (channel)
2119 {
2120 case RedPixelChannel: pixel=red; break;
2121 case GreenPixelChannel: pixel=green; break;
2122 case BluePixelChannel: pixel=blue; break;
2123 default: pixel=Dc; break;
2124 }
2125 break;
2126 }
2127 case ModulusAddCompositeOp:
2128 {
2129 pixel=Sc+Dc;
2130 if (pixel > QuantumRange)
cristy516edc12013-10-27 10:53:42 +00002131 pixel-=QuantumRange;
cristy91de9ea2013-11-17 14:45:54 +00002132 pixel=gamma*(Sa*Da*pixel+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002133 break;
2134 }
2135 case ModulusSubtractCompositeOp:
2136 {
cristy99abff32011-12-24 20:45:16 +00002137 pixel=Sc-Dc;
cristye4a40472011-12-22 02:56:19 +00002138 if (pixel < 0.0)
cristy516edc12013-10-27 10:53:42 +00002139 pixel+=QuantumRange;
cristy91de9ea2013-11-17 14:45:54 +00002140 pixel=gamma*(Sa*Da*pixel+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002141 break;
2142 }
2143 case MultiplyCompositeOp:
2144 {
cristy95a92ef2015-06-12 18:58:35 +00002145 pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002146 break;
2147 }
2148 case OutCompositeOp:
2149 case SrcOutCompositeOp:
2150 {
cristye224ed72014-12-10 23:55:01 +00002151 pixel=QuantumRange*(Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00002152 break;
2153 }
2154 case OverCompositeOp:
2155 case SrcOverCompositeOp:
2156 {
cristy95a92ef2015-06-12 18:58:35 +00002157 pixel=QuantumRange*gamma*(Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002158 break;
2159 }
2160 case OverlayCompositeOp:
2161 {
cristy30dac202014-12-10 02:03:31 +00002162 if ((2.0*Dca) <= Da)
cristy99abff32011-12-24 20:45:16 +00002163 {
cristye224ed72014-12-10 23:55:01 +00002164 pixel=QuantumRange*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristy99abff32011-12-24 20:45:16 +00002165 break;
2166 }
cristye224ed72014-12-10 23:55:01 +00002167 pixel=QuantumRange*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+Dca*
2168 (1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002169 break;
2170 }
2171 case PegtopLightCompositeOp:
2172 {
2173 /*
2174 PegTop: A Soft-Light alternative: A continuous version of the
2175 Softlight function, producing very similar results.
2176
2177 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
2178
2179 http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
2180 */
cristy3bdd9252014-12-21 20:01:43 +00002181 if (fabs((double) Da) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002182 {
cristy95a92ef2015-06-12 18:58:35 +00002183 pixel=QuantumRange*gamma*(Sca);
cristye4a40472011-12-22 02:56:19 +00002184 break;
2185 }
cristy95a92ef2015-06-12 18:58:35 +00002186 pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
2187 Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002188 break;
2189 }
2190 case PinLightCompositeOp:
2191 {
2192 /*
2193 PinLight: A Photoshop 7 composition method
2194 http://www.simplefilter.de/en/basics/mixmods.html
2195
2196 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
2197 */
2198 if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
2199 {
cristy95a92ef2015-06-12 18:58:35 +00002200 pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002201 break;
2202 }
2203 if ((Dca*Sa) > (2.0*Sca*Da))
2204 {
cristy95a92ef2015-06-12 18:58:35 +00002205 pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002206 break;
2207 }
cristy95a92ef2015-06-12 18:58:35 +00002208 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
cristye4a40472011-12-22 02:56:19 +00002209 break;
2210 }
2211 case PlusCompositeOp:
2212 {
cristye224ed72014-12-10 23:55:01 +00002213 pixel=QuantumRange*(Sca+Dca);
cristye4a40472011-12-22 02:56:19 +00002214 break;
2215 }
2216 case SaturateCompositeOp:
2217 {
cristy3bdd9252014-12-21 20:01:43 +00002218 if (fabs((double) (QuantumRange*Sa-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002219 {
2220 pixel=Dc;
2221 break;
2222 }
cristy3bdd9252014-12-21 20:01:43 +00002223 if (fabs((double) (QuantumRange*Da-TransparentAlpha)) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002224 {
2225 pixel=Sc;
2226 break;
2227 }
cristy2fb92282015-04-07 00:34:03 +00002228 CompositeHCL(canvas_pixel.red,canvas_pixel.green,canvas_pixel.blue,
2229 &hue,&chroma,&luma);
cristy7133e642012-08-14 11:04:11 +00002230 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2231 &sans,&chroma,&sans);
2232 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002233 switch (channel)
2234 {
2235 case RedPixelChannel: pixel=red; break;
2236 case GreenPixelChannel: pixel=green; break;
2237 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002238 default: pixel=Dc; break;
2239 }
2240 break;
2241 }
2242 case ScreenCompositeOp:
2243 {
2244 /*
2245 Screen: a negated multiply:
2246
2247 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
2248 */
cristy95a92ef2015-06-12 18:58:35 +00002249 pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
cristye4a40472011-12-22 02:56:19 +00002250 break;
2251 }
2252 case SoftLightCompositeOp:
2253 {
cristye4a40472011-12-22 02:56:19 +00002254 if ((2.0*Sca) < Sa)
2255 {
cristy95a92ef2015-06-12 18:58:35 +00002256 pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-(Dca/Da)))+
2257 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002258 break;
2259 }
2260 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
2261 {
cristy95a92ef2015-06-12 18:58:35 +00002262 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*(Dca/Da)*
2263 (4.0*(Dca/Da)+1.0)*((Dca/Da)-1.0)+7.0*(Dca/Da))+Sca*(1.0-Da)+
2264 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002265 break;
2266 }
cristy95a92ef2015-06-12 18:58:35 +00002267 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow((Dca/Da),0.5)-
2268 (Dca/Da))+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002269 break;
2270 }
2271 case ThresholdCompositeOp:
2272 {
cristy7159f662012-10-28 17:32:43 +00002273 MagickRealType
cristye4a40472011-12-22 02:56:19 +00002274 delta;
2275
2276 delta=Sc-Dc;
cristy7159f662012-10-28 17:32:43 +00002277 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
cristye4a40472011-12-22 02:56:19 +00002278 {
2279 pixel=gamma*Dc;
2280 break;
2281 }
2282 pixel=gamma*(Dc+delta*amount);
2283 break;
2284 }
2285 case VividLightCompositeOp:
2286 {
2287 /*
2288 VividLight: A Photoshop 7 composition method. See
2289 http://www.simplefilter.de/en/basics/mixmods.html.
2290
2291 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
2292 */
cristy3bdd9252014-12-21 20:01:43 +00002293 if ((fabs((double) Sa) < MagickEpsilon) ||
2294 (fabs((double) (Sca-Sa)) < MagickEpsilon))
cristye4a40472011-12-22 02:56:19 +00002295 {
cristy95a92ef2015-06-12 18:58:35 +00002296 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002297 break;
2298 }
2299 if ((2.0*Sca) <= Sa)
2300 {
cristy95a92ef2015-06-12 18:58:35 +00002301 pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*
2302 (1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002303 break;
2304 }
cristy95a92ef2015-06-12 18:58:35 +00002305 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*
cristye224ed72014-12-10 23:55:01 +00002306 (1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002307 break;
2308 }
2309 case XorCompositeOp:
2310 {
cristye224ed72014-12-10 23:55:01 +00002311 pixel=QuantumRange*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002312 break;
2313 }
2314 default:
2315 {
2316 pixel=Sc;
2317 break;
2318 }
2319 }
cristy47c61c92015-04-21 12:15:20 +00002320 q[i]=clamp != MagickFalse ? ClampPixel(pixel) : ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00002321 }
cristye8e8eff2015-01-11 00:28:26 +00002322 p+=GetPixelChannels(source_image);
2323 channels=GetPixelChannels(source_image);
2324 if (p >= (pixels+channels*source_image->columns))
cristy4c08aed2011-07-01 19:47:50 +00002325 p=pixels;
cristyed231572011-07-14 02:18:59 +00002326 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002327 }
2328 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2329 status=MagickFalse;
2330 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2331 {
2332 MagickBooleanType
2333 proceed;
2334
2335#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002336 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002337#endif
2338 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2339 image->rows);
2340 if (proceed == MagickFalse)
2341 status=MagickFalse;
2342 }
2343 }
cristye8e8eff2015-01-11 00:28:26 +00002344 source_view=DestroyCacheView(source_view);
cristy4c08aed2011-07-01 19:47:50 +00002345 image_view=DestroyCacheView(image_view);
cristye8e8eff2015-01-11 00:28:26 +00002346 if (canvas_image != (Image * ) NULL)
2347 canvas_image=DestroyImage(canvas_image);
cristyf661c4d2012-07-28 12:57:17 +00002348 else
cristye8e8eff2015-01-11 00:28:26 +00002349 source_image=DestroyImage(source_image);
cristy4c08aed2011-07-01 19:47:50 +00002350 return(status);
2351}
2352
2353/*
2354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2355% %
2356% %
2357% %
2358% T e x t u r e I m a g e %
2359% %
2360% %
2361% %
2362%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2363%
2364% TextureImage() repeatedly tiles the texture image across and down the image
2365% canvas.
2366%
2367% The format of the TextureImage method is:
2368%
cristy30d8c942012-02-07 13:44:59 +00002369% MagickBooleanType TextureImage(Image *image,const Image *texture,
cristye941a752011-10-15 01:52:48 +00002370% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002371%
2372% A description of each parameter follows:
2373%
2374% o image: the image.
2375%
cristye6178502011-12-23 17:02:29 +00002376% o texture_image: This image is the texture to layer on the background.
cristy4c08aed2011-07-01 19:47:50 +00002377%
2378*/
cristy30d8c942012-02-07 13:44:59 +00002379MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2380 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002381{
2382#define TextureImageTag "Texture/Image"
2383
2384 CacheView
2385 *image_view,
2386 *texture_view;
2387
cristy30d8c942012-02-07 13:44:59 +00002388 Image
2389 *texture_image;
2390
cristy4c08aed2011-07-01 19:47:50 +00002391 MagickBooleanType
2392 status;
2393
2394 ssize_t
2395 y;
2396
2397 assert(image != (Image *) NULL);
2398 if (image->debug != MagickFalse)
2399 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristye1c94d92015-06-28 12:16:33 +00002400 assert(image->signature == MagickCoreSignature);
cristy30d8c942012-02-07 13:44:59 +00002401 if (texture == (const Image *) NULL)
2402 return(MagickFalse);
2403 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2404 return(MagickFalse);
2405 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
cristye6178502011-12-23 17:02:29 +00002406 if (texture_image == (const Image *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002407 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +00002408 (void) TransformImageColorspace(texture_image,image->colorspace,exception);
cristy387430f2012-02-07 13:09:46 +00002409 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
2410 exception);
cristy4c08aed2011-07-01 19:47:50 +00002411 status=MagickTrue;
2412 if ((image->compose != CopyCompositeOp) &&
cristy974a2a82013-04-23 12:57:16 +00002413 ((image->compose != OverCompositeOp) ||
cristy17f11b02014-12-20 19:37:04 +00002414 (image->alpha_trait != UndefinedPixelTrait) ||
2415 (texture_image->alpha_trait != UndefinedPixelTrait)))
cristy4c08aed2011-07-01 19:47:50 +00002416 {
2417 /*
2418 Tile texture onto the image background.
2419 */
cristye6178502011-12-23 17:02:29 +00002420 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00002421 {
2422 register ssize_t
2423 x;
2424
2425 if (status == MagickFalse)
2426 continue;
cristye6178502011-12-23 17:02:29 +00002427 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002428 {
2429 MagickBooleanType
2430 thread_status;
2431
cristyfeb3e962012-03-29 17:25:55 +00002432 thread_status=CompositeImage(image,texture_image,image->compose,
2433 MagickFalse,x+texture_image->tile_offset.x,y+
2434 texture_image->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002435 if (thread_status == MagickFalse)
2436 {
2437 status=thread_status;
2438 break;
2439 }
2440 }
2441 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2442 {
2443 MagickBooleanType
2444 proceed;
2445
cristy2fb92282015-04-07 00:34:03 +00002446 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2447 image->rows);
cristy4c08aed2011-07-01 19:47:50 +00002448 if (proceed == MagickFalse)
2449 status=MagickFalse;
2450 }
2451 }
2452 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2453 image->rows,image->rows);
cristy30d8c942012-02-07 13:44:59 +00002454 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002455 return(status);
2456 }
2457 /*
2458 Tile texture onto the image background (optimized).
2459 */
2460 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00002461 texture_view=AcquireVirtualCacheView(texture_image,exception);
2462 image_view=AcquireAuthenticCacheView(image,exception);
cristye8914392012-12-16 21:16:11 +00002463#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyd6432472013-01-06 16:56:13 +00002464 #pragma omp parallel for schedule(static,4) shared(status) \
2465 magick_threads(texture_image,image,1,1)
cristye8914392012-12-16 21:16:11 +00002466#endif
cristy4c08aed2011-07-01 19:47:50 +00002467 for (y=0; y < (ssize_t) image->rows; y++)
2468 {
2469 MagickBooleanType
2470 sync;
2471
2472 register const Quantum
2473 *p,
2474 *pixels;
2475
2476 register ssize_t
2477 x;
2478
2479 register Quantum
2480 *q;
2481
2482 size_t
2483 width;
2484
2485 if (status == MagickFalse)
2486 continue;
cristye6178502011-12-23 17:02:29 +00002487 pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
2488 (y+texture_image->tile_offset.y) % texture_image->rows,
2489 texture_image->columns,1,exception);
2490 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002491 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2492 {
2493 status=MagickFalse;
2494 continue;
2495 }
cristye6178502011-12-23 17:02:29 +00002496 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002497 {
2498 register ssize_t
cristye6178502011-12-23 17:02:29 +00002499 j;
cristy4c08aed2011-07-01 19:47:50 +00002500
2501 p=pixels;
cristye6178502011-12-23 17:02:29 +00002502 width=texture_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002503 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2504 width=image->columns-x;
cristye6178502011-12-23 17:02:29 +00002505 for (j=0; j < (ssize_t) width; j++)
cristy4c08aed2011-07-01 19:47:50 +00002506 {
cristye6178502011-12-23 17:02:29 +00002507 register ssize_t
2508 i;
2509
cristy883fde12013-04-08 00:50:13 +00002510 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002511 {
2512 p+=GetPixelChannels(texture_image);
2513 q+=GetPixelChannels(image);
2514 continue;
2515 }
cristye6178502011-12-23 17:02:29 +00002516 for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
2517 {
cristy5a23c552013-02-13 14:34:28 +00002518 PixelChannel channel=GetPixelChannelChannel(texture_image,i);
2519 PixelTrait traits=GetPixelChannelTraits(image,channel);
2520 PixelTrait texture_traits=GetPixelChannelTraits(texture_image,
2521 channel);
cristye6178502011-12-23 17:02:29 +00002522 if ((traits == UndefinedPixelTrait) ||
2523 (texture_traits == UndefinedPixelTrait))
2524 continue;
2525 SetPixelChannel(image,channel,p[i],q);
2526 }
2527 p+=GetPixelChannels(texture_image);
cristyed231572011-07-14 02:18:59 +00002528 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002529 }
2530 }
2531 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2532 if (sync == MagickFalse)
2533 status=MagickFalse;
2534 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2535 {
2536 MagickBooleanType
2537 proceed;
2538
cristy4c08aed2011-07-01 19:47:50 +00002539 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2540 image->rows);
2541 if (proceed == MagickFalse)
2542 status=MagickFalse;
2543 }
2544 }
2545 texture_view=DestroyCacheView(texture_view);
2546 image_view=DestroyCacheView(image_view);
cristy30d8c942012-02-07 13:44:59 +00002547 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002548 return(status);
2549}