blob: e4050068656406f7508602eaddb85d8115c9bdb4 [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% %
cristyfe676ee2013-11-18 13:03:38 +000020% Copyright 1999-2014 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,
cristyfeb3e962012-03-29 17:25:55 +000099% const Image *composite_image,const CompositeOperator compose,
100% 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%
105% o image: the destination image, modified by he composition
106%
cristyfeb3e962012-03-29 17:25:55 +0000107% o composite_image: the composite (source) image.
108%
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
140 Z = 1 for destination preserved
141
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 inline MagickRealType MagickMin(const MagickRealType x,
182 const MagickRealType y)
cristye4a40472011-12-22 02:56:19 +0000183{
184 if (x < y)
185 return(x);
186 return(y);
187}
cristyddeeea22012-04-12 01:33:09 +0000188
cristy7159f662012-10-28 17:32:43 +0000189static inline MagickRealType MagickMax(const MagickRealType x,
190 const MagickRealType y)
cristye4a40472011-12-22 02:56:19 +0000191{
192 if (x > y)
193 return(x);
194 return(y);
195}
196
cristy7159f662012-10-28 17:32:43 +0000197static void HCLComposite(const MagickRealType hue,const MagickRealType chroma,
198 const MagickRealType luma,MagickRealType *red,MagickRealType *green,
199 MagickRealType *blue)
cristyd8f16f72012-08-13 12:49:50 +0000200{
cristy7159f662012-10-28 17:32:43 +0000201 MagickRealType
cristyd8f16f72012-08-13 12:49:50 +0000202 b,
cristy7133e642012-08-14 11:04:11 +0000203 c,
cristyd8f16f72012-08-13 12:49:50 +0000204 g,
cristy7133e642012-08-14 11:04:11 +0000205 h,
206 m,
cristyd8f16f72012-08-13 12:49:50 +0000207 r,
cristy398fd022013-06-26 23:08:33 +0000208 x;
cristyd8f16f72012-08-13 12:49:50 +0000209
210 /*
cristy7133e642012-08-14 11:04:11 +0000211 Convert HCL to RGB colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000212 */
cristy7159f662012-10-28 17:32:43 +0000213 assert(red != (MagickRealType *) NULL);
214 assert(green != (MagickRealType *) NULL);
215 assert(blue != (MagickRealType *) NULL);
cristy7133e642012-08-14 11:04:11 +0000216 h=6.0*hue;
217 c=chroma;
218 x=c*(1.0-fabs(fmod(h,2.0)-1.0));
219 r=0.0;
220 g=0.0;
221 b=0.0;
222 if ((0.0 <= h) && (h < 1.0))
cristyd8f16f72012-08-13 12:49:50 +0000223 {
cristye1715282012-08-15 13:10:55 +0000224 r=c;
225 g=x;
cristyd8f16f72012-08-13 12:49:50 +0000226 }
cristyd8f16f72012-08-13 12:49:50 +0000227 else
cristy7133e642012-08-14 11:04:11 +0000228 if ((1.0 <= h) && (h < 2.0))
229 {
cristye1715282012-08-15 13:10:55 +0000230 r=x;
231 g=c;
cristy7133e642012-08-14 11:04:11 +0000232 }
233 else
234 if ((2.0 <= h) && (h < 3.0))
235 {
cristye1715282012-08-15 13:10:55 +0000236 g=c;
237 b=x;
cristy7133e642012-08-14 11:04:11 +0000238 }
239 else
240 if ((3.0 <= h) && (h < 4.0))
241 {
cristye1715282012-08-15 13:10:55 +0000242 g=x;
243 b=c;
cristy7133e642012-08-14 11:04:11 +0000244 }
245 else
246 if ((4.0 <= h) && (h < 5.0))
247 {
cristye1715282012-08-15 13:10:55 +0000248 r=x;
249 b=c;
cristy7133e642012-08-14 11:04:11 +0000250 }
251 else
252 if ((5.0 <= h) && (h < 6.0))
253 {
cristye1715282012-08-15 13:10:55 +0000254 r=c;
255 b=x;
cristy7133e642012-08-14 11:04:11 +0000256 }
cristy9e2436a2013-05-02 20:35:59 +0000257 m=luma-(0.298839*r+0.586811*g+0.114350*b);
cristy398fd022013-06-26 23:08:33 +0000258 *red=QuantumRange*(r+m);
259 *green=QuantumRange*(g+m);
260 *blue=QuantumRange*(b+m);
cristyd8f16f72012-08-13 12:49:50 +0000261}
262
cristy7159f662012-10-28 17:32:43 +0000263static void CompositeHCL(const MagickRealType red,const MagickRealType green,
264 const MagickRealType blue,MagickRealType *hue,MagickRealType *chroma,
265 MagickRealType *luma)
cristyd8f16f72012-08-13 12:49:50 +0000266{
cristy7159f662012-10-28 17:32:43 +0000267 MagickRealType
cristyd8f16f72012-08-13 12:49:50 +0000268 b,
cristy7133e642012-08-14 11:04:11 +0000269 c,
cristyd8f16f72012-08-13 12:49:50 +0000270 g,
cristy7133e642012-08-14 11:04:11 +0000271 h,
cristyd8f16f72012-08-13 12:49:50 +0000272 max,
cristyd8f16f72012-08-13 12:49:50 +0000273 r;
274
275 /*
cristy7133e642012-08-14 11:04:11 +0000276 Convert RGB to HCL colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000277 */
cristy7159f662012-10-28 17:32:43 +0000278 assert(hue != (MagickRealType *) NULL);
279 assert(chroma != (MagickRealType *) NULL);
280 assert(luma != (MagickRealType *) NULL);
cristy7133e642012-08-14 11:04:11 +0000281 r=red;
282 g=green;
283 b=blue;
cristyd8f16f72012-08-13 12:49:50 +0000284 max=MagickMax(r,MagickMax(g,b));
cristy7159f662012-10-28 17:32:43 +0000285 c=max-(MagickRealType) MagickMin(r,MagickMin(g,b));
cristy7133e642012-08-14 11:04:11 +0000286 h=0.0;
287 if (c == 0)
288 h=0.0;
cristyd8f16f72012-08-13 12:49:50 +0000289 else
cristy7133e642012-08-14 11:04:11 +0000290 if (red == max)
cristy9e2436a2013-05-02 20:35:59 +0000291 h=fmod((g-b)/c+6.0,6.0);
cristyd8f16f72012-08-13 12:49:50 +0000292 else
cristy7133e642012-08-14 11:04:11 +0000293 if (green == max)
294 h=((b-r)/c)+2.0;
295 else
296 if (blue == max)
297 h=((r-g)/c)+4.0;
298 *hue=(h/6.0);
299 *chroma=QuantumScale*c;
cristy9e2436a2013-05-02 20:35:59 +0000300 *luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b);
cristyd8f16f72012-08-13 12:49:50 +0000301}
302
cristye4a40472011-12-22 02:56:19 +0000303static MagickBooleanType CompositeOverImage(Image *image,
cristyfeb3e962012-03-29 17:25:55 +0000304 const Image *composite_image,const MagickBooleanType clip_to_self,
305 const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristye4a40472011-12-22 02:56:19 +0000306{
307#define CompositeImageTag "Composite/Image"
308
309 CacheView
310 *composite_view,
311 *image_view;
312
cristye4a40472011-12-22 02:56:19 +0000313 MagickBooleanType
cristye4a40472011-12-22 02:56:19 +0000314 status;
315
316 MagickOffsetType
317 progress;
318
319 ssize_t
320 y;
321
cristye4a40472011-12-22 02:56:19 +0000322 /*
cristye4a40472011-12-22 02:56:19 +0000323 Composite image.
324 */
325 status=MagickTrue;
326 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000327 composite_view=AcquireVirtualCacheView(composite_image,exception);
328 image_view=AcquireAuthenticCacheView(image,exception);
cristye4a40472011-12-22 02:56:19 +0000329#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000330 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000331 magick_threads(composite_image,image,image->rows,1)
cristye4a40472011-12-22 02:56:19 +0000332#endif
333 for (y=0; y < (ssize_t) image->rows; y++)
334 {
335 const Quantum
336 *pixels;
337
338 register const Quantum
339 *restrict p;
340
341 register Quantum
342 *restrict q;
343
344 register ssize_t
345 x;
346
cristy564a5692012-01-20 23:56:26 +0000347 size_t
348 channels;
349
cristye4a40472011-12-22 02:56:19 +0000350 if (status == MagickFalse)
351 continue;
cristyfeb3e962012-03-29 17:25:55 +0000352 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000353 {
354 if (y < y_offset)
355 continue;
356 if ((y-y_offset) >= (ssize_t) composite_image->rows)
357 continue;
358 }
359 /*
360 If pixels is NULL, y is outside overlay region.
361 */
362 pixels=(Quantum *) NULL;
363 p=(Quantum *) NULL;
364 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
365 {
366 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
367 composite_image->columns,1,exception);
368 if (p == (const Quantum *) NULL)
369 {
370 status=MagickFalse;
371 continue;
372 }
373 pixels=p;
374 if (x_offset < 0)
375 p-=x_offset*GetPixelChannels(composite_image);
376 }
377 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
378 if (q == (Quantum *) NULL)
379 {
380 status=MagickFalse;
381 continue;
382 }
383 for (x=0; x < (ssize_t) image->columns; x++)
384 {
cristy17028dc2013-01-24 12:28:39 +0000385 double
386 gamma;
387
cristy7159f662012-10-28 17:32:43 +0000388 MagickRealType
cristye4a40472011-12-22 02:56:19 +0000389 alpha,
390 Da,
391 Dc,
cristye4a40472011-12-22 02:56:19 +0000392 Sa,
393 Sc;
394
395 register ssize_t
396 i;
397
cristyfeb3e962012-03-29 17:25:55 +0000398 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000399 {
400 if (x < x_offset)
401 {
402 q+=GetPixelChannels(image);
403 continue;
404 }
405 if ((x-x_offset) >= (ssize_t) composite_image->columns)
406 break;
407 }
408 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
409 ((x-x_offset) >= (ssize_t) composite_image->columns))
410 {
411 Quantum
412 source[MaxPixelChannels];
413
414 /*
415 Virtual composite:
416 Sc: source color.
417 Dc: destination color.
418 */
cristy883fde12013-04-08 00:50:13 +0000419 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +0000420 {
421 q+=GetPixelChannels(image);
422 continue;
423 }
cristyc94ba6f2012-01-29 23:19:58 +0000424 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
425 source,exception);
cristye4a40472011-12-22 02:56:19 +0000426 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
427 {
cristy5a23c552013-02-13 14:34:28 +0000428 PixelChannel channel=GetPixelChannelChannel(image,i);
429 PixelTrait traits=GetPixelChannelTraits(image,channel);
430 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
431 channel);
cristye4a40472011-12-22 02:56:19 +0000432 if ((traits == UndefinedPixelTrait) ||
433 (composite_traits == UndefinedPixelTrait))
434 continue;
435 q[i]=source[channel];
436 }
437 q+=GetPixelChannels(image);
438 continue;
439 }
440 /*
441 Authentic composite:
442 Sa: normalized source alpha.
443 Da: normalized destination alpha.
444 */
cristy883fde12013-04-08 00:50:13 +0000445 if (GetPixelReadMask(composite_image,p) == 0)
cristyc94ba6f2012-01-29 23:19:58 +0000446 {
447 p+=GetPixelChannels(composite_image);
448 channels=GetPixelChannels(composite_image);
449 if (p >= (pixels+channels*composite_image->columns))
450 p=pixels;
451 q+=GetPixelChannels(image);
452 continue;
453 }
cristye4a40472011-12-22 02:56:19 +0000454 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
455 Da=QuantumScale*GetPixelAlpha(image,q);
456 alpha=Sa*(-Da)+Sa+Da;
457 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
458 {
cristy5a23c552013-02-13 14:34:28 +0000459 PixelChannel channel=GetPixelChannelChannel(image,i);
460 PixelTrait traits=GetPixelChannelTraits(image,channel);
461 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
462 channel);
cristye4a40472011-12-22 02:56:19 +0000463 if ((traits == UndefinedPixelTrait) ||
464 (composite_traits == UndefinedPixelTrait))
465 continue;
466 if ((traits & CopyPixelTrait) != 0)
467 {
468 if (channel != AlphaPixelChannel)
469 {
470 /*
471 Copy channel.
472 */
473 q[i]=GetPixelChannel(composite_image,channel,p);
474 continue;
475 }
476 /*
477 Set alpha channel.
478 */
479 q[i]=ClampToQuantum(QuantumRange*alpha);
480 continue;
481 }
482 /*
483 Sc: source color.
484 Dc: destination color.
485 */
cristy7159f662012-10-28 17:32:43 +0000486 Sc=(MagickRealType) GetPixelChannel(composite_image,channel,p);
487 Dc=(MagickRealType) q[i];
cristy3e3ec3a2012-11-03 23:11:06 +0000488 gamma=PerceptibleReciprocal(alpha);
cristye4a40472011-12-22 02:56:19 +0000489 q[i]=ClampToQuantum(gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc));
490 }
491 p+=GetPixelChannels(composite_image);
492 channels=GetPixelChannels(composite_image);
493 if (p >= (pixels+channels*composite_image->columns))
494 p=pixels;
495 q+=GetPixelChannels(image);
496 }
497 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
498 status=MagickFalse;
499 if (image->progress_monitor != (MagickProgressMonitor) NULL)
500 {
501 MagickBooleanType
502 proceed;
503
504#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000505 #pragma omp critical (MagickCore_CompositeImage)
cristye4a40472011-12-22 02:56:19 +0000506#endif
507 proceed=SetImageProgress(image,CompositeImageTag,progress++,
508 image->rows);
509 if (proceed == MagickFalse)
510 status=MagickFalse;
511 }
512 }
513 composite_view=DestroyCacheView(composite_view);
514 image_view=DestroyCacheView(image_view);
515 return(status);
516}
517
cristy4c08aed2011-07-01 19:47:50 +0000518MagickExport MagickBooleanType CompositeImage(Image *image,
cristya865ccd2012-07-28 00:33:10 +0000519 const Image *composite,const CompositeOperator compose,
cristy44c98412012-03-31 18:25:40 +0000520 const MagickBooleanType clip_to_self,const ssize_t x_offset,
521 const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +0000522{
cristy4c08aed2011-07-01 19:47:50 +0000523#define CompositeImageTag "Composite/Image"
524
525 CacheView
526 *composite_view,
527 *image_view;
528
cristy4c08aed2011-07-01 19:47:50 +0000529 GeometryInfo
530 geometry_info;
531
532 Image
cristya865ccd2012-07-28 00:33:10 +0000533 *composite_image,
cristy4c08aed2011-07-01 19:47:50 +0000534 *destination_image;
535
536 MagickBooleanType
cristy4c08aed2011-07-01 19:47:50 +0000537 status;
538
539 MagickOffsetType
540 progress;
541
cristy7159f662012-10-28 17:32:43 +0000542 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000543 amount,
544 destination_dissolve,
545 midpoint,
cristy7133e642012-08-14 11:04:11 +0000546 percent_luma,
547 percent_chroma,
cristy4c08aed2011-07-01 19:47:50 +0000548 source_dissolve,
549 threshold;
550
551 MagickStatusType
552 flags;
553
cristyd197cbb2012-01-13 02:14:12 +0000554 ssize_t
555 y;
556
cristy4c08aed2011-07-01 19:47:50 +0000557 assert(image != (Image *) NULL);
558 assert(image->signature == MagickSignature);
559 if (image->debug != MagickFalse)
560 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristya865ccd2012-07-28 00:33:10 +0000561 assert(composite!= (Image *) NULL);
562 assert(composite->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +0000563 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000564 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +0000565 composite_image=CloneImage(composite,0,0,MagickTrue,exception);
566 if (composite_image == (const Image *) NULL)
567 return(MagickFalse);
cristyde067e92013-04-18 00:47:50 +0000568 if (IsGrayColorspace(image->colorspace) != MagickFalse)
569 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristya4a22a22012-08-01 23:16:38 +0000570 (void) SetImageColorspace(composite_image,image->colorspace,exception);
cristy60dc90c2013-02-09 18:33:21 +0000571 if ((image->alpha_trait == BlendPixelTrait) &&
572 (composite_image->alpha_trait != BlendPixelTrait))
cristydc4bf872013-03-09 15:08:20 +0000573 (void) SetImageAlphaChannel(composite_image,SetAlphaChannel,exception);
cristye4a40472011-12-22 02:56:19 +0000574 if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp))
575 {
cristyfeb3e962012-03-29 17:25:55 +0000576 status=CompositeOverImage(image,composite_image,clip_to_self,x_offset,
577 y_offset,exception);
cristya865ccd2012-07-28 00:33:10 +0000578 composite_image=DestroyImage(composite_image);
cristye4a40472011-12-22 02:56:19 +0000579 return(status);
580 }
cristy4c08aed2011-07-01 19:47:50 +0000581 destination_image=(Image *) NULL;
582 amount=0.5;
583 destination_dissolve=1.0;
cristy7133e642012-08-14 11:04:11 +0000584 percent_luma=100.0;
585 percent_chroma=100.0;
cristy4c08aed2011-07-01 19:47:50 +0000586 source_dissolve=1.0;
587 threshold=0.05f;
588 switch (compose)
589 {
cristy4c08aed2011-07-01 19:47:50 +0000590 case CopyCompositeOp:
591 {
592 if ((x_offset < 0) || (y_offset < 0))
593 break;
594 if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns)
595 break;
596 if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows)
597 break;
598 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +0000599 composite_view=AcquireVirtualCacheView(composite_image,exception);
600 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000601#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000602 #pragma omp parallel for schedule(static,4) shared(status) \
cristy5e6b2592012-12-19 14:08:11 +0000603 magick_threads(composite_image,image,composite_image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +0000604#endif
605 for (y=0; y < (ssize_t) composite_image->rows; y++)
606 {
607 MagickBooleanType
608 sync;
609
610 register const Quantum
611 *p;
612
613 register Quantum
614 *q;
615
616 register ssize_t
617 x;
618
619 if (status == MagickFalse)
620 continue;
621 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
622 1,exception);
623 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
624 composite_image->columns,1,exception);
625 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
626 {
627 status=MagickFalse;
628 continue;
629 }
630 for (x=0; x < (ssize_t) composite_image->columns; x++)
631 {
cristybdecccc2011-12-24 22:52:16 +0000632 register ssize_t
633 i;
634
cristy883fde12013-04-08 00:50:13 +0000635 if (GetPixelReadMask(composite_image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +0000636 {
637 p+=GetPixelChannels(composite_image);
638 q+=GetPixelChannels(image);
639 continue;
640 }
cristybdecccc2011-12-24 22:52:16 +0000641 for (i=0; i < (ssize_t) GetPixelChannels(composite_image); i++)
642 {
cristy5a23c552013-02-13 14:34:28 +0000643 PixelChannel channel=GetPixelChannelChannel(composite_image,i);
644 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
645 channel);
646 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristybdecccc2011-12-24 22:52:16 +0000647 if ((traits == UndefinedPixelTrait) ||
648 (composite_traits == UndefinedPixelTrait))
649 continue;
650 SetPixelChannel(image,channel,p[i],q);
651 }
cristyed231572011-07-14 02:18:59 +0000652 p+=GetPixelChannels(composite_image);
653 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +0000654 }
655 sync=SyncCacheViewAuthenticPixels(image_view,exception);
656 if (sync == MagickFalse)
657 status=MagickFalse;
658 if (image->progress_monitor != (MagickProgressMonitor) NULL)
659 {
660 MagickBooleanType
661 proceed;
662
663#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +0000664 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +0000665#endif
666 proceed=SetImageProgress(image,CompositeImageTag,
667 (MagickOffsetType) y,image->rows);
668 if (proceed == MagickFalse)
669 status=MagickFalse;
670 }
671 }
672 composite_view=DestroyCacheView(composite_view);
673 image_view=DestroyCacheView(image_view);
cristy44886b92012-07-28 13:07:09 +0000674 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000675 return(status);
676 }
cristye4a40472011-12-22 02:56:19 +0000677 case CopyAlphaCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000678 case ChangeMaskCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +0000679 case IntensityCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000680 {
681 /*
682 Modify destination outside the overlaid region and require an alpha
683 channel to exist, to add transparency.
684 */
cristy8a46d822012-08-28 23:32:39 +0000685 if (image->alpha_trait != BlendPixelTrait)
cristy42c41de2012-05-05 18:36:31 +0000686 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy4c08aed2011-07-01 19:47:50 +0000687 break;
688 }
689 case BlurCompositeOp:
690 {
691 CacheView
692 *composite_view,
693 *destination_view;
694
cristyfeb3e962012-03-29 17:25:55 +0000695 const char
696 *value;
697
cristy7159f662012-10-28 17:32:43 +0000698 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000699 angle_range,
700 angle_start,
701 height,
702 width;
703
cristybce4f4a2012-10-14 14:57:47 +0000704 PixelInfo
705 pixel;
706
cristy4c08aed2011-07-01 19:47:50 +0000707 ResampleFilter
708 *resample_filter;
709
710 SegmentInfo
711 blur;
712
713 /*
anthony9cb63cc2012-04-25 06:10:49 +0000714 Blur Image by resampling.
715
cristy4c08aed2011-07-01 19:47:50 +0000716 Blur Image dictated by an overlay gradient map: X = red_channel;
717 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
718 */
719 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000720 exception);
cristy4c08aed2011-07-01 19:47:50 +0000721 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000722 {
723 composite_image=DestroyImage(composite_image);
724 return(MagickFalse);
725 }
cristy4c08aed2011-07-01 19:47:50 +0000726 /*
anthony9cb63cc2012-04-25 06:10:49 +0000727 Gather the maximum blur sigma values from user.
cristy4c08aed2011-07-01 19:47:50 +0000728 */
729 SetGeometryInfo(&geometry_info);
730 flags=NoValue;
cristy1a780952013-02-10 17:15:30 +0000731 value=GetImageArtifact(image,"compose:args");
732 if (value != (const char *) NULL)
cristy4c08aed2011-07-01 19:47:50 +0000733 flags=ParseGeometry(value,&geometry_info);
cristy1a780952013-02-10 17:15:30 +0000734 if ((flags & WidthValue) == 0)
735 {
736 (void) ThrowMagickException(exception,GetMagickModule(),OptionWarning,
737 "InvalidSetting","'%s' '%s'","compose:args",value);
cristy44886b92012-07-28 13:07:09 +0000738 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000739 destination_image=DestroyImage(destination_image);
740 return(MagickFalse);
741 }
anthony9cb63cc2012-04-25 06:10:49 +0000742 /*
743 Users input sigma now needs to be converted to the EWA ellipse size.
744 The filter defaults to a sigma of 0.5 so to make this match the
745 users input the ellipse size needs to be doubled.
anthonyd2923912012-04-23 13:06:53 +0000746 */
747 width=height=geometry_info.rho*2.0;
748 if ((flags & HeightValue) != 0 )
749 height=geometry_info.sigma*2.0;
cristy7159f662012-10-28 17:32:43 +0000750 /*
751 Default the unrotated ellipse width and height axis vectors.
752 */
anthonyd2923912012-04-23 13:06:53 +0000753 blur.x1=width;
cristy4c08aed2011-07-01 19:47:50 +0000754 blur.x2=0.0;
755 blur.y1=0.0;
anthonyd2923912012-04-23 13:06:53 +0000756 blur.y2=height;
757 /* rotate vectors if a rotation angle is given */
cristy4c08aed2011-07-01 19:47:50 +0000758 if ((flags & XValue) != 0 )
759 {
cristy7159f662012-10-28 17:32:43 +0000760 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000761 angle;
762
763 angle=DegreesToRadians(geometry_info.xi);
764 blur.x1=width*cos(angle);
765 blur.x2=width*sin(angle);
766 blur.y1=(-height*sin(angle));
767 blur.y2=height*cos(angle);
768 }
anthonyd2923912012-04-23 13:06:53 +0000769 /* Otherwise lets set a angle range and calculate in the loop */
770 angle_start=0.0;
771 angle_range=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000772 if ((flags & YValue) != 0 )
773 {
774 angle_start=DegreesToRadians(geometry_info.xi);
775 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
776 }
777 /*
anthony9cb63cc2012-04-25 06:10:49 +0000778 Set up a gaussian cylindrical filter for EWA Bluring.
anthonyd2923912012-04-23 13:06:53 +0000779
anthony9cb63cc2012-04-25 06:10:49 +0000780 As the minimum ellipse radius of support*1.0 the EWA algorithm
781 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
782 This means that even 'No Blur' will be still a little blurry!
anthonyd2923912012-04-23 13:06:53 +0000783
anthony9cb63cc2012-04-25 06:10:49 +0000784 The solution (as well as the problem of preventing any user
785 expert filter settings, is to set our own user settings, then
786 restore them afterwards.
cristy4c08aed2011-07-01 19:47:50 +0000787 */
cristy8a11cb12011-10-19 23:53:34 +0000788 resample_filter=AcquireResampleFilter(image,exception);
anthonyd2923912012-04-23 13:06:53 +0000789 SetResampleFilter(resample_filter,GaussianFilter);
anthony9cb63cc2012-04-25 06:10:49 +0000790
791 /* do the variable blurring of each pixel in image */
792 GetPixelInfo(image,&pixel);
cristy46ff2672012-12-14 15:32:26 +0000793 composite_view=AcquireVirtualCacheView(composite_image,exception);
794 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000795 for (y=0; y < (ssize_t) composite_image->rows; y++)
796 {
797 MagickBooleanType
798 sync;
799
800 register const Quantum
801 *restrict p;
802
803 register Quantum
804 *restrict q;
805
806 register ssize_t
807 x;
808
809 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
810 continue;
811 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
812 1,exception);
813 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +0000814 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000815 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
816 break;
817 for (x=0; x < (ssize_t) composite_image->columns; x++)
818 {
819 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
820 {
cristyed231572011-07-14 02:18:59 +0000821 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000822 continue;
823 }
824 if (fabs(angle_range) > MagickEpsilon)
825 {
cristy7159f662012-10-28 17:32:43 +0000826 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000827 angle;
828
829 angle=angle_start+angle_range*QuantumScale*
830 GetPixelBlue(composite_image,p);
831 blur.x1=width*cos(angle);
832 blur.x2=width*sin(angle);
833 blur.y1=(-height*sin(angle));
834 blur.y2=height*cos(angle);
835 }
anthonyd2923912012-04-23 13:06:53 +0000836#if 0
837 if ( x == 10 && y == 60 ) {
cristy7159f662012-10-28 17:32:43 +0000838 (void) fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",blur.x1,
839 blur.x2,blur.y1, blur.y2);
840 (void) fprintf(stderr, "scaled by=%lf,%lf\n",QuantumScale*
841 GetPixelRed(p),QuantumScale*GetPixelGreen(p));
anthonyd2923912012-04-23 13:06:53 +0000842#endif
843 ScaleResampleFilter(resample_filter,
844 blur.x1*QuantumScale*GetPixelRed(composite_image,p),
845 blur.y1*QuantumScale*GetPixelGreen(composite_image,p),
846 blur.x2*QuantumScale*GetPixelRed(composite_image,p),
847 blur.y2*QuantumScale*GetPixelGreen(composite_image,p) );
cristy4c08aed2011-07-01 19:47:50 +0000848 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
cristydb070952012-04-20 14:33:00 +0000849 (double) y_offset+y,&pixel,exception);
cristy803640d2011-11-17 02:11:32 +0000850 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +0000851 p+=GetPixelChannels(composite_image);
852 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +0000853 }
854 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
855 if (sync == MagickFalse)
856 break;
857 }
858 resample_filter=DestroyResampleFilter(resample_filter);
859 composite_view=DestroyCacheView(composite_view);
860 destination_view=DestroyCacheView(destination_view);
cristyf661c4d2012-07-28 12:57:17 +0000861 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000862 composite_image=destination_image;
863 break;
864 }
865 case DisplaceCompositeOp:
866 case DistortCompositeOp:
867 {
868 CacheView
869 *composite_view,
870 *destination_view,
871 *image_view;
872
cristyfeb3e962012-03-29 17:25:55 +0000873 const char
874 *value;
875
cristy4c08aed2011-07-01 19:47:50 +0000876 PixelInfo
877 pixel;
878
cristy7159f662012-10-28 17:32:43 +0000879 MagickRealType
cristy4c08aed2011-07-01 19:47:50 +0000880 horizontal_scale,
881 vertical_scale;
882
883 PointInfo
884 center,
885 offset;
886
887 /*
888 Displace/Distort based on overlay gradient map:
889 X = red_channel; Y = green_channel;
890 compose:args = x_scale[,y_scale[,center.x,center.y]]
891 */
892 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000893 exception);
cristy4c08aed2011-07-01 19:47:50 +0000894 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000895 {
896 composite_image=DestroyImage(composite_image);
897 return(MagickFalse);
898 }
cristy4c08aed2011-07-01 19:47:50 +0000899 SetGeometryInfo(&geometry_info);
900 flags=NoValue;
dirk55a83c72014-08-22 18:44:23 +0000901 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +0000902 if (value != (char *) NULL)
903 flags=ParseGeometry(value,&geometry_info);
904 if ((flags & (WidthValue|HeightValue)) == 0 )
905 {
906 if ((flags & AspectValue) == 0)
907 {
cristy7159f662012-10-28 17:32:43 +0000908 horizontal_scale=(MagickRealType) (composite_image->columns-1.0)/
cristy4c08aed2011-07-01 19:47:50 +0000909 2.0;
cristy7159f662012-10-28 17:32:43 +0000910 vertical_scale=(MagickRealType) (composite_image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000911 }
912 else
913 {
cristy7159f662012-10-28 17:32:43 +0000914 horizontal_scale=(MagickRealType) (image->columns-1.0)/2.0;
915 vertical_scale=(MagickRealType) (image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000916 }
917 }
918 else
919 {
920 horizontal_scale=geometry_info.rho;
921 vertical_scale=geometry_info.sigma;
922 if ((flags & PercentValue) != 0)
923 {
924 if ((flags & AspectValue) == 0)
925 {
926 horizontal_scale*=(composite_image->columns-1.0)/200.0;
927 vertical_scale*=(composite_image->rows-1.0)/200.0;
928 }
929 else
930 {
931 horizontal_scale*=(image->columns-1.0)/200.0;
932 vertical_scale*=(image->rows-1.0)/200.0;
933 }
934 }
935 if ((flags & HeightValue) == 0)
936 vertical_scale=horizontal_scale;
937 }
938 /*
939 Determine fixed center point for absolute distortion map
940 Absolute distort ==
941 Displace offset relative to a fixed absolute point
942 Select that point according to +X+Y user inputs.
943 default = center of overlay image
944 arg flag '!' = locations/percentage relative to background image
945 */
cristy7159f662012-10-28 17:32:43 +0000946 center.x=(MagickRealType) x_offset;
947 center.y=(MagickRealType) y_offset;
cristy4c08aed2011-07-01 19:47:50 +0000948 if (compose == DistortCompositeOp)
949 {
950 if ((flags & XValue) == 0)
951 if ((flags & AspectValue) == 0)
cristy7159f662012-10-28 17:32:43 +0000952 center.x=(MagickRealType) (x_offset+(composite_image->columns-1)/
953 2.0);
cristy4c08aed2011-07-01 19:47:50 +0000954 else
cristy7159f662012-10-28 17:32:43 +0000955 center.x=(MagickRealType) ((image->columns-1)/2);
cristy4c08aed2011-07-01 19:47:50 +0000956 else
957 if ((flags & AspectValue) == 0)
cristy7159f662012-10-28 17:32:43 +0000958 center.x=(MagickRealType) x_offset+geometry_info.xi;
cristy4c08aed2011-07-01 19:47:50 +0000959 else
960 center.x=geometry_info.xi;
961 if ((flags & YValue) == 0)
962 if ((flags & AspectValue) == 0)
cristy7159f662012-10-28 17:32:43 +0000963 center.y=(MagickRealType) (y_offset+(composite_image->rows-1)/
964 2.0);
cristy4c08aed2011-07-01 19:47:50 +0000965 else
cristy7159f662012-10-28 17:32:43 +0000966 center.y=(MagickRealType) ((image->rows-1)/2);
cristy4c08aed2011-07-01 19:47:50 +0000967 else
968 if ((flags & AspectValue) == 0)
cristy7159f662012-10-28 17:32:43 +0000969 center.y=(MagickRealType) y_offset+geometry_info.psi;
cristy4c08aed2011-07-01 19:47:50 +0000970 else
971 center.y=geometry_info.psi;
972 }
973 /*
974 Shift the pixel offset point as defined by the provided,
975 displacement/distortion map. -- Like a lens...
976 */
cristye10859a2011-12-18 22:28:59 +0000977 GetPixelInfo(image,&pixel);
cristy46ff2672012-12-14 15:32:26 +0000978 image_view=AcquireVirtualCacheView(image,exception);
979 composite_view=AcquireVirtualCacheView(composite_image,exception);
980 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000981 for (y=0; y < (ssize_t) composite_image->rows; y++)
982 {
983 MagickBooleanType
984 sync;
985
986 register const Quantum
987 *restrict p;
988
989 register Quantum
990 *restrict q;
991
992 register ssize_t
993 x;
994
995 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
996 continue;
997 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
998 1,exception);
999 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +00001000 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001001 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1002 break;
1003 for (x=0; x < (ssize_t) composite_image->columns; x++)
1004 {
1005 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1006 {
cristyed231572011-07-14 02:18:59 +00001007 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001008 continue;
1009 }
1010 /*
1011 Displace the offset.
1012 */
cristy7159f662012-10-28 17:32:43 +00001013 offset.x=(double) (horizontal_scale*(GetPixelRed(composite_image,p)-
1014 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
1015 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
1016 x : 0);
1017 offset.y=(double) (vertical_scale*(GetPixelGreen(composite_image,p)-
1018 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
1019 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
1020 y : 0);
cristy4c08aed2011-07-01 19:47:50 +00001021 (void) InterpolatePixelInfo(image,image_view,
1022 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
1023 &pixel,exception);
1024 /*
1025 Mask with the 'invalid pixel mask' in alpha channel.
1026 */
cristy7159f662012-10-28 17:32:43 +00001027 pixel.alpha=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale*
1028 pixel.alpha)*(1.0-QuantumScale*GetPixelAlpha(composite_image,p)));
cristy803640d2011-11-17 02:11:32 +00001029 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00001030 p+=GetPixelChannels(composite_image);
1031 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00001032 }
1033 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
1034 if (sync == MagickFalse)
1035 break;
1036 }
1037 destination_view=DestroyCacheView(destination_view);
1038 composite_view=DestroyCacheView(composite_view);
1039 image_view=DestroyCacheView(image_view);
cristyf661c4d2012-07-28 12:57:17 +00001040 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001041 composite_image=destination_image;
1042 break;
1043 }
1044 case DissolveCompositeOp:
1045 {
cristyfeb3e962012-03-29 17:25:55 +00001046 const char
1047 *value;
1048
cristy4c08aed2011-07-01 19:47:50 +00001049 /*
1050 Geometry arguments to dissolve factors.
1051 */
dirk55a83c72014-08-22 18:44:23 +00001052 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001053 if (value != (char *) NULL)
1054 {
1055 flags=ParseGeometry(value,&geometry_info);
1056 source_dissolve=geometry_info.rho/100.0;
1057 destination_dissolve=1.0;
1058 if ((source_dissolve-MagickEpsilon) < 0.0)
1059 source_dissolve=0.0;
1060 if ((source_dissolve+MagickEpsilon) > 1.0)
1061 {
1062 destination_dissolve=2.0-source_dissolve;
1063 source_dissolve=1.0;
1064 }
1065 if ((flags & SigmaValue) != 0)
1066 destination_dissolve=geometry_info.sigma/100.0;
1067 if ((destination_dissolve-MagickEpsilon) < 0.0)
1068 destination_dissolve=0.0;
anthony9cb63cc2012-04-25 06:10:49 +00001069 /* posible speed up? -- from IMv6 update
1070 clip_to_self=MagickFalse;
1071 if ((destination_dissolve+MagickEpsilon) > 1.0 )
1072 {
1073 destination_dissolve=1.0;
1074 clip_to_self=MagickTrue;
1075 }
1076 */
cristy4c08aed2011-07-01 19:47:50 +00001077 }
1078 break;
1079 }
1080 case BlendCompositeOp:
1081 {
cristyfeb3e962012-03-29 17:25:55 +00001082 const char
1083 *value;
1084
dirk55a83c72014-08-22 18:44:23 +00001085 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001086 if (value != (char *) NULL)
1087 {
1088 flags=ParseGeometry(value,&geometry_info);
1089 source_dissolve=geometry_info.rho/100.0;
1090 destination_dissolve=1.0-source_dissolve;
1091 if ((flags & SigmaValue) != 0)
1092 destination_dissolve=geometry_info.sigma/100.0;
cristy4c08aed2011-07-01 19:47:50 +00001093 }
1094 break;
1095 }
1096 case MathematicsCompositeOp:
1097 {
cristyfeb3e962012-03-29 17:25:55 +00001098 const char
1099 *value;
1100
cristy4c08aed2011-07-01 19:47:50 +00001101 /*
1102 Just collect the values from "compose:args", setting.
1103 Unused values are set to zero automagically.
1104
1105 Arguments are normally a comma separated list, so this probably should
1106 be changed to some 'general comma list' parser, (with a minimum
1107 number of values)
1108 */
1109 SetGeometryInfo(&geometry_info);
dirk55a83c72014-08-22 18:44:23 +00001110 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001111 if (value != (char *) NULL)
1112 (void) ParseGeometry(value,&geometry_info);
1113 break;
1114 }
1115 case ModulateCompositeOp:
1116 {
cristyfeb3e962012-03-29 17:25:55 +00001117 const char
1118 *value;
1119
cristy4c08aed2011-07-01 19:47:50 +00001120 /*
cristy7133e642012-08-14 11:04:11 +00001121 Determine the luma and chroma scale.
cristy4c08aed2011-07-01 19:47:50 +00001122 */
dirk55a83c72014-08-22 18:44:23 +00001123 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001124 if (value != (char *) NULL)
1125 {
1126 flags=ParseGeometry(value,&geometry_info);
cristy7133e642012-08-14 11:04:11 +00001127 percent_luma=geometry_info.rho;
cristy4c08aed2011-07-01 19:47:50 +00001128 if ((flags & SigmaValue) != 0)
cristy7133e642012-08-14 11:04:11 +00001129 percent_chroma=geometry_info.sigma;
cristy4c08aed2011-07-01 19:47:50 +00001130 }
1131 break;
1132 }
1133 case ThresholdCompositeOp:
1134 {
cristyfeb3e962012-03-29 17:25:55 +00001135 const char
1136 *value;
1137
cristy4c08aed2011-07-01 19:47:50 +00001138 /*
1139 Determine the amount and threshold.
1140 */
dirk55a83c72014-08-22 18:44:23 +00001141 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001142 if (value != (char *) NULL)
1143 {
1144 flags=ParseGeometry(value,&geometry_info);
1145 amount=geometry_info.rho;
1146 threshold=geometry_info.sigma;
1147 if ((flags & SigmaValue) == 0)
1148 threshold=0.05f;
1149 }
1150 threshold*=QuantumRange;
1151 break;
1152 }
1153 default:
1154 break;
1155 }
cristy4c08aed2011-07-01 19:47:50 +00001156 /*
1157 Composite image.
1158 */
1159 status=MagickTrue;
1160 progress=0;
cristy7159f662012-10-28 17:32:43 +00001161 midpoint=((MagickRealType) QuantumRange+1.0)/2;
cristy46ff2672012-12-14 15:32:26 +00001162 composite_view=AcquireVirtualCacheView(composite_image,exception);
1163 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001164#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001165 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001166 magick_threads(composite_image,image,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00001167#endif
1168 for (y=0; y < (ssize_t) image->rows; y++)
1169 {
1170 const Quantum
1171 *pixels;
1172
cristy7159f662012-10-28 17:32:43 +00001173 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001174 blue,
cristy7133e642012-08-14 11:04:11 +00001175 luma,
cristye4a40472011-12-22 02:56:19 +00001176 green,
cristy4c08aed2011-07-01 19:47:50 +00001177 hue,
cristye4a40472011-12-22 02:56:19 +00001178 red,
cristy7133e642012-08-14 11:04:11 +00001179 chroma;
cristy4c08aed2011-07-01 19:47:50 +00001180
cristyddeeea22012-04-12 01:33:09 +00001181 PixelInfo
1182 destination_pixel,
1183 source_pixel;
1184
cristy4c08aed2011-07-01 19:47:50 +00001185 register const Quantum
1186 *restrict p;
1187
1188 register Quantum
1189 *restrict q;
1190
1191 register ssize_t
1192 x;
1193
1194 if (status == MagickFalse)
1195 continue;
cristyfeb3e962012-03-29 17:25:55 +00001196 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001197 {
1198 if (y < y_offset)
1199 continue;
1200 if ((y-y_offset) >= (ssize_t) composite_image->rows)
1201 continue;
1202 }
1203 /*
1204 If pixels is NULL, y is outside overlay region.
1205 */
1206 pixels=(Quantum *) NULL;
1207 p=(Quantum *) NULL;
1208 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
1209 {
1210 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
1211 composite_image->columns,1,exception);
1212 if (p == (const Quantum *) NULL)
1213 {
1214 status=MagickFalse;
1215 continue;
1216 }
1217 pixels=p;
1218 if (x_offset < 0)
cristyed231572011-07-14 02:18:59 +00001219 p-=x_offset*GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001220 }
1221 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001222 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00001223 {
1224 status=MagickFalse;
1225 continue;
1226 }
cristy4c08aed2011-07-01 19:47:50 +00001227 hue=0.0;
cristy7133e642012-08-14 11:04:11 +00001228 chroma=0.0;
1229 luma=0.0;
cristyddeeea22012-04-12 01:33:09 +00001230 GetPixelInfo(image,&destination_pixel);
1231 GetPixelInfo(composite_image,&source_pixel);
cristy4c08aed2011-07-01 19:47:50 +00001232 for (x=0; x < (ssize_t) image->columns; x++)
1233 {
cristy17028dc2013-01-24 12:28:39 +00001234 double
1235 gamma;
1236
cristy7159f662012-10-28 17:32:43 +00001237 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001238 alpha,
1239 Da,
1240 Dc,
1241 Dca,
cristye4a40472011-12-22 02:56:19 +00001242 Sa,
1243 Sc,
1244 Sca;
1245
1246 register ssize_t
1247 i;
1248
cristy564a5692012-01-20 23:56:26 +00001249 size_t
1250 channels;
1251
cristyfeb3e962012-03-29 17:25:55 +00001252 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001253 {
1254 if (x < x_offset)
1255 {
cristyed231572011-07-14 02:18:59 +00001256 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001257 continue;
1258 }
1259 if ((x-x_offset) >= (ssize_t) composite_image->columns)
1260 break;
1261 }
cristye4a40472011-12-22 02:56:19 +00001262 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
1263 ((x-x_offset) >= (ssize_t) composite_image->columns))
1264 {
1265 Quantum
1266 source[MaxPixelChannels];
1267
1268 /*
1269 Virtual composite:
1270 Sc: source color.
1271 Dc: destination color.
1272 */
1273 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
1274 source,exception);
cristy883fde12013-04-08 00:50:13 +00001275 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001276 {
1277 q+=GetPixelChannels(image);
1278 continue;
1279 }
cristye4a40472011-12-22 02:56:19 +00001280 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1281 {
cristy7159f662012-10-28 17:32:43 +00001282 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001283 pixel;
1284
cristy5a23c552013-02-13 14:34:28 +00001285 PixelChannel channel=GetPixelChannelChannel(image,i);
1286 PixelTrait traits=GetPixelChannelTraits(image,channel);
1287 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
1288 channel);
cristye4a40472011-12-22 02:56:19 +00001289 if ((traits == UndefinedPixelTrait) ||
1290 (composite_traits == UndefinedPixelTrait))
1291 continue;
1292 switch (compose)
1293 {
cristyc8d63672012-01-11 13:03:13 +00001294 case AlphaCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001295 case ChangeMaskCompositeOp:
1296 case CopyAlphaCompositeOp:
1297 case DstAtopCompositeOp:
1298 case DstInCompositeOp:
1299 case InCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +00001300 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001301 case OutCompositeOp:
1302 case SrcInCompositeOp:
1303 case SrcOutCompositeOp:
1304 {
cristy7159f662012-10-28 17:32:43 +00001305 pixel=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001306 if (channel == AlphaPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001307 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001308 break;
1309 }
1310 case ClearCompositeOp:
1311 case CopyCompositeOp:
1312 case ReplaceCompositeOp:
1313 case SrcCompositeOp:
1314 {
1315 if (channel == AlphaPixelChannel)
1316 {
cristy7159f662012-10-28 17:32:43 +00001317 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001318 break;
1319 }
1320 pixel=0.0;
1321 break;
1322 }
cristy99abff32011-12-24 20:45:16 +00001323 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001324 case DissolveCompositeOp:
1325 {
1326 if (channel == AlphaPixelChannel)
1327 {
1328 pixel=destination_dissolve*GetPixelAlpha(composite_image,
1329 source);
1330 break;
1331 }
cristy7159f662012-10-28 17:32:43 +00001332 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001333 break;
1334 }
1335 default:
1336 {
cristy7159f662012-10-28 17:32:43 +00001337 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001338 break;
1339 }
1340 }
1341 q[i]=ClampToQuantum(pixel);
1342 }
1343 q+=GetPixelChannels(image);
1344 continue;
1345 }
1346 /*
1347 Authentic composite:
1348 Sa: normalized source alpha.
1349 Da: normalized destination alpha.
1350 */
1351 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
1352 Da=QuantumScale*GetPixelAlpha(image,q);
cristy4c08aed2011-07-01 19:47:50 +00001353 switch (compose)
1354 {
cristye4a40472011-12-22 02:56:19 +00001355 case BumpmapCompositeOp:
1356 {
1357 alpha=GetPixelIntensity(composite_image,p)*Sa;
1358 break;
1359 }
cristycdc168f2011-12-21 15:24:39 +00001360 case ColorBurnCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001361 case ColorDodgeCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001362 case DifferenceCompositeOp:
1363 case DivideDstCompositeOp:
1364 case DivideSrcCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001365 case ExclusionCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001366 case HardLightCompositeOp:
cristy99fc2172014-06-26 10:30:53 +00001367 case HardMixCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001368 case LinearBurnCompositeOp:
1369 case LinearDodgeCompositeOp:
1370 case LinearLightCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001371 case MathematicsCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001372 case MinusDstCompositeOp:
1373 case MinusSrcCompositeOp:
1374 case ModulusAddCompositeOp:
1375 case ModulusSubtractCompositeOp:
1376 case MultiplyCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001377 case OverlayCompositeOp:
1378 case PegtopLightCompositeOp:
1379 case PinLightCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001380 case ScreenCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001381 case SoftLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001382 case VividLightCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001383 {
1384 alpha=RoundToUnity(Sa+Da-Sa*Da);
1385 break;
1386 }
1387 case DarkenCompositeOp:
1388 case DstAtopCompositeOp:
1389 case DstInCompositeOp:
1390 case InCompositeOp:
1391 case LightenCompositeOp:
1392 case SrcInCompositeOp:
1393 {
1394 alpha=Sa*Da;
1395 break;
1396 }
1397 case DissolveCompositeOp:
1398 {
1399 alpha=source_dissolve*Sa*(-destination_dissolve*Da)+source_dissolve*
1400 Sa+destination_dissolve*Da;
1401 break;
1402 }
1403 case DstOverCompositeOp:
1404 {
1405 alpha=Da*(-Sa)+Da+Sa;
1406 break;
1407 }
1408 case DstOutCompositeOp:
1409 {
1410 alpha=Da*(1.0-Sa);
1411 break;
1412 }
1413 case OutCompositeOp:
1414 case SrcOutCompositeOp:
1415 {
1416 alpha=Sa*(1.0-Da);
1417 break;
1418 }
1419 case OverCompositeOp:
1420 case SrcOverCompositeOp:
1421 {
1422 alpha=Sa*(-Da)+Sa+Da;
1423 break;
1424 }
cristy99abff32011-12-24 20:45:16 +00001425 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001426 case PlusCompositeOp:
1427 {
1428 alpha=RoundToUnity(Sa+Da);
1429 break;
1430 }
cristy4c08aed2011-07-01 19:47:50 +00001431 case XorCompositeOp:
1432 {
cristye4a40472011-12-22 02:56:19 +00001433 alpha=Sa+Da-2.0*Sa*Da;
cristy4c08aed2011-07-01 19:47:50 +00001434 break;
1435 }
1436 default:
cristye4a40472011-12-22 02:56:19 +00001437 {
1438 alpha=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001439 break;
cristye4a40472011-12-22 02:56:19 +00001440 }
cristy4c08aed2011-07-01 19:47:50 +00001441 }
cristy883fde12013-04-08 00:50:13 +00001442 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001443 {
1444 p+=GetPixelChannels(composite_image);
1445 q+=GetPixelChannels(image);
1446 continue;
1447 }
cristy9d3d2792012-04-14 15:15:19 +00001448 switch (compose)
1449 {
1450 case ColorizeCompositeOp:
1451 case HueCompositeOp:
1452 case LuminizeCompositeOp:
1453 case ModulateCompositeOp:
1454 case SaturateCompositeOp:
1455 {
1456 GetPixelInfoPixel(composite_image,p,&source_pixel);
1457 GetPixelInfoPixel(image,q,&destination_pixel);
1458 break;
1459 }
1460 default:
1461 break;
1462 }
cristye4a40472011-12-22 02:56:19 +00001463 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1464 {
cristy7159f662012-10-28 17:32:43 +00001465 MagickRealType
1466 pixel,
cristy564a5692012-01-20 23:56:26 +00001467 sans;
1468
cristy5a23c552013-02-13 14:34:28 +00001469 PixelChannel channel=GetPixelChannelChannel(image,i);
1470 PixelTrait traits=GetPixelChannelTraits(image,channel);
1471 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
1472 channel);
cristy0cd1f212012-01-05 15:45:59 +00001473 if (traits == UndefinedPixelTrait)
1474 continue;
cristya7b07912012-01-11 20:01:32 +00001475 if ((compose != IntensityCompositeOp) &&
cristye4a40472011-12-22 02:56:19 +00001476 (composite_traits == UndefinedPixelTrait))
1477 continue;
1478 /*
1479 Sc: source color.
cristye4a40472011-12-22 02:56:19 +00001480 Dc: destination color.
cristye4a40472011-12-22 02:56:19 +00001481 */
cristy7159f662012-10-28 17:32:43 +00001482 Sc=(MagickRealType) GetPixelChannel(composite_image,channel,p);
1483 Dc=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001484 if ((traits & CopyPixelTrait) != 0)
cristye10859a2011-12-18 22:28:59 +00001485 {
cristye4a40472011-12-22 02:56:19 +00001486 if (channel != AlphaPixelChannel)
1487 {
1488 /*
1489 Copy channel.
1490 */
1491 q[i]=ClampToQuantum(Sc);
cristye10859a2011-12-18 22:28:59 +00001492 continue;
cristye10859a2011-12-18 22:28:59 +00001493 }
cristye4a40472011-12-22 02:56:19 +00001494 /*
1495 Set alpha channel.
1496 */
cristye10859a2011-12-18 22:28:59 +00001497 switch (compose)
1498 {
cristyc8d63672012-01-11 13:03:13 +00001499 case AlphaCompositeOp:
1500 {
cristya7b07912012-01-11 20:01:32 +00001501 pixel=QuantumRange*Sa;
cristyc8d63672012-01-11 13:03:13 +00001502 break;
1503 }
cristye4a40472011-12-22 02:56:19 +00001504 case AtopCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001505 case CopyBlackCompositeOp:
1506 case CopyBlueCompositeOp:
1507 case CopyCyanCompositeOp:
1508 case CopyGreenCompositeOp:
1509 case CopyMagentaCompositeOp:
1510 case CopyRedCompositeOp:
1511 case CopyYellowCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001512 case SrcAtopCompositeOp:
1513 case DstCompositeOp:
1514 case NoCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001515 {
cristye4a40472011-12-22 02:56:19 +00001516 pixel=QuantumRange*Da;
cristye10859a2011-12-18 22:28:59 +00001517 break;
1518 }
cristye10859a2011-12-18 22:28:59 +00001519 case ChangeMaskCompositeOp:
1520 {
cristye4a40472011-12-22 02:56:19 +00001521 MagickBooleanType
1522 equivalent;
1523
cristy7159f662012-10-28 17:32:43 +00001524 if (Da > ((MagickRealType) QuantumRange/2.0))
cristy99abff32011-12-24 20:45:16 +00001525 {
cristy7159f662012-10-28 17:32:43 +00001526 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001527 break;
1528 }
cristye4a40472011-12-22 02:56:19 +00001529 equivalent=IsFuzzyEquivalencePixel(composite_image,p,image,q);
cristy99abff32011-12-24 20:45:16 +00001530 if (equivalent != MagickFalse)
cristye4a40472011-12-22 02:56:19 +00001531 {
cristy7159f662012-10-28 17:32:43 +00001532 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001533 break;
1534 }
cristy7159f662012-10-28 17:32:43 +00001535 pixel=(MagickRealType) OpaqueAlpha;
cristye4a40472011-12-22 02:56:19 +00001536 break;
1537 }
cristy99abff32011-12-24 20:45:16 +00001538 case ClearCompositeOp:
1539 {
cristy7159f662012-10-28 17:32:43 +00001540 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001541 break;
1542 }
1543 case ColorizeCompositeOp:
1544 case HueCompositeOp:
1545 case LuminizeCompositeOp:
1546 case SaturateCompositeOp:
1547 {
1548 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1549 {
1550 pixel=QuantumRange*Da;
1551 break;
1552 }
1553 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
1554 {
1555 pixel=QuantumRange*Sa;
1556 break;
1557 }
1558 if (Sa < Da)
1559 {
1560 pixel=QuantumRange*Da;
1561 break;
1562 }
1563 pixel=QuantumRange*Sa;
1564 break;
1565 }
cristy99abff32011-12-24 20:45:16 +00001566 case CopyAlphaCompositeOp:
cristy24d5d722012-05-17 12:27:27 +00001567 {
1568 pixel=QuantumRange*Sa;
cristy1a941d72013-03-08 01:04:41 +00001569 if (composite_image->alpha_trait == BlendPixelTrait)
cristy24d5d722012-05-17 12:27:27 +00001570 pixel=GetPixelIntensity(composite_image,p);
1571 break;
1572 }
1573 case CopyCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001574 case DisplaceCompositeOp:
1575 case DistortCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001576 case DstAtopCompositeOp:
1577 case ReplaceCompositeOp:
1578 case SrcCompositeOp:
1579 {
1580 pixel=QuantumRange*Sa;
1581 break;
1582 }
1583 case DarkenIntensityCompositeOp:
1584 {
cristy99abff32011-12-24 20:45:16 +00001585 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1586 (1.0-Da)*GetPixelIntensity(image,q) ? Sa : Da;
cristye4a40472011-12-22 02:56:19 +00001587 break;
1588 }
cristy98621462011-12-31 22:31:11 +00001589 case IntensityCompositeOp:
1590 {
cristyf13c5942012-08-08 23:50:11 +00001591 pixel=GetPixelIntensity(composite_image,p);
cristy98621462011-12-31 22:31:11 +00001592 break;
1593 }
cristye4a40472011-12-22 02:56:19 +00001594 case LightenIntensityCompositeOp:
1595 {
1596 pixel=Sa*GetPixelIntensity(composite_image,p) >
1597 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye10859a2011-12-18 22:28:59 +00001598 break;
1599 }
cristy99abff32011-12-24 20:45:16 +00001600 case ModulateCompositeOp:
1601 {
1602 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1603 {
1604 pixel=QuantumRange*Da;
1605 break;
1606 }
1607 pixel=QuantumRange*Da;
1608 break;
1609 }
cristye10859a2011-12-18 22:28:59 +00001610 default:
1611 {
cristye4a40472011-12-22 02:56:19 +00001612 pixel=QuantumRange*alpha;
cristye10859a2011-12-18 22:28:59 +00001613 break;
1614 }
1615 }
cristye4a40472011-12-22 02:56:19 +00001616 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00001617 continue;
1618 }
1619 /*
cristy99abff32011-12-24 20:45:16 +00001620 Porter-Duff compositions:
1621 Sca: source normalized color multiplied by alpha.
1622 Dca: normalized destination color multiplied by alpha.
cristye10859a2011-12-18 22:28:59 +00001623 */
cristy99abff32011-12-24 20:45:16 +00001624 Sca=QuantumScale*Sa*Sc;
1625 Dca=QuantumScale*Da*Dc;
cristye10859a2011-12-18 22:28:59 +00001626 switch (compose)
1627 {
cristye10859a2011-12-18 22:28:59 +00001628 case DarkenCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001629 case LightenCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001630 case ModulusSubtractCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001631 {
cristy99abff32011-12-24 20:45:16 +00001632 gamma=1.0-alpha;
cristye10859a2011-12-18 22:28:59 +00001633 break;
1634 }
1635 default:
1636 break;
1637 }
cristy3e3ec3a2012-11-03 23:11:06 +00001638 gamma=PerceptibleReciprocal(alpha);
cristyd197cbb2012-01-13 02:14:12 +00001639 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001640 switch (compose)
1641 {
cristya7b07912012-01-11 20:01:32 +00001642 case AlphaCompositeOp:
1643 {
1644 pixel=QuantumRange*Sa;
1645 break;
1646 }
cristye4a40472011-12-22 02:56:19 +00001647 case AtopCompositeOp:
1648 case SrcAtopCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001649 {
cristye4a40472011-12-22 02:56:19 +00001650 pixel=Sc*Sa+Dc*(1.0-Sa);
1651 break;
cristye10859a2011-12-18 22:28:59 +00001652 }
cristye4a40472011-12-22 02:56:19 +00001653 case BlendCompositeOp:
1654 {
1655 pixel=gamma*(source_dissolve*Sa*Sc+destination_dissolve*Da*Dc);
1656 break;
1657 }
1658 case BlurCompositeOp:
1659 case DisplaceCompositeOp:
1660 case DistortCompositeOp:
1661 case CopyCompositeOp:
1662 case ReplaceCompositeOp:
1663 case SrcCompositeOp:
1664 {
1665 pixel=Sc;
1666 break;
1667 }
1668 case BumpmapCompositeOp:
1669 {
cristy99abff32011-12-24 20:45:16 +00001670 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001671 {
1672 pixel=Dc;
1673 break;
1674 }
1675 pixel=QuantumScale*GetPixelIntensity(composite_image,p)*Dc;
1676 break;
1677 }
cristy99abff32011-12-24 20:45:16 +00001678 case ChangeMaskCompositeOp:
1679 {
1680 pixel=Dc;
1681 break;
1682 }
1683 case ClearCompositeOp:
1684 {
1685 pixel=0.0;
1686 break;
1687 }
cristye4a40472011-12-22 02:56:19 +00001688 case ColorBurnCompositeOp:
1689 {
1690 /*
1691 Refer to the March 2009 SVG specification.
1692 */
1693 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
1694 {
cristy99abff32011-12-24 20:45:16 +00001695 pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001696 break;
1697 }
1698 if (Sca < MagickEpsilon)
1699 {
cristy99abff32011-12-24 20:45:16 +00001700 pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001701 break;
1702 }
cristy99abff32011-12-24 20:45:16 +00001703 pixel=QuantumRange*gamma*(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+
1704 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001705 break;
1706 }
1707 case ColorDodgeCompositeOp:
1708 {
cristy98a39202013-07-09 13:25:52 +00001709 if ((Sca*Da+Dca*Sa) >= Sa*Da)
cristye4a40472011-12-22 02:56:19 +00001710 {
cristy31784be2013-07-09 13:55:34 +00001711 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001712 break;
1713 }
cristy99abff32011-12-24 20:45:16 +00001714 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001715 (1.0-Sa));
1716 break;
1717 }
1718 case ColorizeCompositeOp:
1719 {
cristy99abff32011-12-24 20:45:16 +00001720 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001721 {
1722 pixel=Dc;
1723 break;
1724 }
cristy99abff32011-12-24 20:45:16 +00001725 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001726 {
1727 pixel=Sc;
1728 break;
1729 }
cristy7133e642012-08-14 11:04:11 +00001730 CompositeHCL(destination_pixel.red,destination_pixel.green,
1731 destination_pixel.blue,&sans,&sans,&luma);
1732 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1733 &hue,&chroma,&sans);
1734 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001735 switch (channel)
1736 {
1737 case RedPixelChannel: pixel=red; break;
1738 case GreenPixelChannel: pixel=green; break;
1739 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001740 default: pixel=Dc; break;
1741 }
1742 break;
1743 }
cristye4a40472011-12-22 02:56:19 +00001744 case CopyAlphaCompositeOp:
cristy98621462011-12-31 22:31:11 +00001745 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001746 {
cristy24d5d722012-05-17 12:27:27 +00001747 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001748 break;
1749 }
1750 case CopyBlackCompositeOp:
1751 {
cristyd197cbb2012-01-13 02:14:12 +00001752 if (channel == BlackPixelChannel)
cristy2fda00b2013-10-21 16:12:19 +00001753 pixel=(MagickRealType) (QuantumRange-
1754 GetPixelBlack(composite_image,p));
cristye4a40472011-12-22 02:56:19 +00001755 break;
1756 }
1757 case CopyBlueCompositeOp:
1758 case CopyYellowCompositeOp:
1759 {
cristyd197cbb2012-01-13 02:14:12 +00001760 if (channel == BluePixelChannel)
cristy7159f662012-10-28 17:32:43 +00001761 pixel=(MagickRealType) GetPixelBlue(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001762 break;
1763 }
1764 case CopyGreenCompositeOp:
1765 case CopyMagentaCompositeOp:
1766 {
cristyd197cbb2012-01-13 02:14:12 +00001767 if (channel == GreenPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001768 pixel=(MagickRealType) GetPixelGreen(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001769 break;
1770 }
1771 case CopyRedCompositeOp:
1772 case CopyCyanCompositeOp:
1773 {
cristyd197cbb2012-01-13 02:14:12 +00001774 if (channel == RedPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001775 pixel=(MagickRealType) GetPixelRed(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001776 break;
1777 }
cristy99abff32011-12-24 20:45:16 +00001778 case DarkenCompositeOp:
1779 {
1780 /*
1781 Darken is equivalent to a 'Minimum' method
1782 OR a greyscale version of a binary 'Or'
1783 OR the 'Intersection' of pixel sets.
1784 */
1785 if (Sc < Dc)
1786 {
1787 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
1788 break;
1789 }
1790 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1791 break;
1792 }
cristye4a40472011-12-22 02:56:19 +00001793 case DarkenIntensityCompositeOp:
1794 {
cristy99abff32011-12-24 20:45:16 +00001795 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1796 (1.0-Da)*GetPixelIntensity(image,q) ? Sc : Dc;
cristye4a40472011-12-22 02:56:19 +00001797 break;
1798 }
1799 case DifferenceCompositeOp:
1800 {
1801 pixel=gamma*(Sa*Sc+Da*Dc-Sa*Da*2.0*MagickMin(Sc,Dc));
1802 break;
1803 }
1804 case DissolveCompositeOp:
1805 {
1806 pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
1807 destination_dissolve*Da*Dc+destination_dissolve*Da*Dc);
1808 break;
1809 }
1810 case DivideDstCompositeOp:
1811 {
1812 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1813 {
cristy99abff32011-12-24 20:45:16 +00001814 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001815 break;
1816 }
1817 if (fabs(Dca) < MagickEpsilon)
1818 {
cristy99abff32011-12-24 20:45:16 +00001819 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001820 break;
1821 }
cristy99abff32011-12-24 20:45:16 +00001822 pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001823 break;
1824 }
1825 case DivideSrcCompositeOp:
1826 {
1827 if ((fabs(Dca) < MagickEpsilon) && (fabs(Sca) < MagickEpsilon))
1828 {
cristy99abff32011-12-24 20:45:16 +00001829 pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001830 break;
1831 }
1832 if (fabs(Sca) < MagickEpsilon)
1833 {
cristy99abff32011-12-24 20:45:16 +00001834 pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001835 break;
1836 }
cristy99abff32011-12-24 20:45:16 +00001837 pixel=QuantumRange*gamma*(Dca*Sa*Sa/Sca+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001838 break;
1839 }
1840 case DstAtopCompositeOp:
1841 {
1842 pixel=Dc*Da+Sc*(1.0-Da);
1843 break;
1844 }
1845 case DstCompositeOp:
1846 case NoCompositeOp:
1847 {
1848 pixel=Dc;
1849 break;
1850 }
1851 case DstInCompositeOp:
1852 {
1853 pixel=gamma*(Sa*Dc*Sa);
1854 break;
1855 }
1856 case DstOutCompositeOp:
1857 {
1858 pixel=gamma*(Da*Dc*(1.0-Sa));
1859 break;
1860 }
1861 case DstOverCompositeOp:
1862 {
1863 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1864 break;
1865 }
1866 case ExclusionCompositeOp:
1867 {
cristy99abff32011-12-24 20:45:16 +00001868 pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
1869 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001870 break;
1871 }
1872 case HardLightCompositeOp:
1873 {
1874 if ((2.0*Sca) < Sa)
1875 {
cristy99abff32011-12-24 20:45:16 +00001876 pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001877 (1.0-Sa));
1878 break;
1879 }
cristy99abff32011-12-24 20:45:16 +00001880 pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
cristye4a40472011-12-22 02:56:19 +00001881 Dca*(1.0-Sa));
1882 break;
1883 }
cristy99fc2172014-06-26 10:30:53 +00001884 case HardMixCompositeOp:
1885 {
cristya9781452014-06-26 10:58:54 +00001886 double
1887 gamma;
1888
cristyaa19c352014-06-26 12:46:02 +00001889 if ((Sa+Da) < 1.0)
cristya9781452014-06-26 10:58:54 +00001890 gamma=0.0;
1891 else
1892 gamma=1.0;
cristyaa19c352014-06-26 12:46:02 +00001893 pixel=(gamma*(1.0-Sca)*(1.0-Dca))+Sa*(1.0-Sca)*Dca+Da*(1.0-Dca)*Sca;
cristy99fc2172014-06-26 10:30:53 +00001894 break;
1895 }
cristye4a40472011-12-22 02:56:19 +00001896 case HueCompositeOp:
1897 {
cristy99abff32011-12-24 20:45:16 +00001898 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001899 {
1900 pixel=Dc;
1901 break;
1902 }
cristy99abff32011-12-24 20:45:16 +00001903 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001904 {
1905 pixel=Sc;
1906 break;
1907 }
cristy7133e642012-08-14 11:04:11 +00001908 CompositeHCL(destination_pixel.red,destination_pixel.green,
1909 destination_pixel.blue,&hue,&chroma,&luma);
1910 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
cristye4a40472011-12-22 02:56:19 +00001911 &hue,&sans,&sans);
cristy7133e642012-08-14 11:04:11 +00001912 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001913 switch (channel)
1914 {
1915 case RedPixelChannel: pixel=red; break;
1916 case GreenPixelChannel: pixel=green; break;
1917 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001918 default: pixel=Dc; break;
1919 }
1920 break;
1921 }
1922 case InCompositeOp:
1923 case SrcInCompositeOp:
1924 {
1925 pixel=gamma*(Da*Sc*Da);
1926 break;
1927 }
cristy99abff32011-12-24 20:45:16 +00001928 case LinearBurnCompositeOp:
1929 {
1930 /*
1931 LinearBurn: as defined by Abode Photoshop, according to
1932 http://www.simplefilter.de/en/basics/mixmods.html is:
1933
1934 f(Sc,Dc) = Sc + Dc - 1
1935 */
1936 pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
1937 break;
1938 }
1939 case LinearDodgeCompositeOp:
1940 {
1941 pixel=QuantumRange*gamma*(Sa*Sc+Da*Dc);
1942 break;
1943 }
1944 case LinearLightCompositeOp:
1945 {
1946 /*
1947 LinearLight: as defined by Abode Photoshop, according to
1948 http://www.simplefilter.de/en/basics/mixmods.html is:
1949
1950 f(Sc,Dc) = Dc + 2*Sc - 1
1951 */
1952 pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
1953 break;
1954 }
1955 case LightenCompositeOp:
1956 {
1957 if (Sc > Dc)
1958 {
cristy24d5d722012-05-17 12:27:27 +00001959 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristy99abff32011-12-24 20:45:16 +00001960 break;
1961 }
cristy24d5d722012-05-17 12:27:27 +00001962 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
cristy99abff32011-12-24 20:45:16 +00001963 break;
1964 }
cristye4a40472011-12-22 02:56:19 +00001965 case LightenIntensityCompositeOp:
1966 {
1967 /*
1968 Lighten is equivalent to a 'Maximum' method
1969 OR a greyscale version of a binary 'And'
1970 OR the 'Union' of pixel sets.
1971 */
1972 pixel=Sa*GetPixelIntensity(composite_image,p) >
1973 Da*GetPixelIntensity(image,q) ? Sc : Dc;
1974 break;
1975 }
cristye4a40472011-12-22 02:56:19 +00001976 case LuminizeCompositeOp:
1977 {
cristy99abff32011-12-24 20:45:16 +00001978 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001979 {
1980 pixel=Dc;
1981 break;
1982 }
cristy99abff32011-12-24 20:45:16 +00001983 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001984 {
1985 pixel=Sc;
1986 break;
1987 }
cristy7133e642012-08-14 11:04:11 +00001988 CompositeHCL(destination_pixel.red,destination_pixel.green,
1989 destination_pixel.blue,&hue,&chroma,&luma);
1990 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1991 &sans,&sans,&luma);
1992 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001993 switch (channel)
1994 {
1995 case RedPixelChannel: pixel=red; break;
1996 case GreenPixelChannel: pixel=green; break;
1997 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001998 default: pixel=Dc; break;
1999 }
2000 break;
2001 }
2002 case MathematicsCompositeOp:
2003 {
2004 /*
2005 'Mathematics' a free form user control mathematical composition
2006 is defined as...
2007
2008 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
2009
2010 Where the arguments A,B,C,D are (currently) passed to composite
2011 as a command separated 'geometry' string in "compose:args" image
2012 artifact.
2013
2014 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
2015
2016 Applying the SVG transparency formula (see above), we get...
2017
2018 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
2019
2020 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
2021 Dca*(1.0-Sa)
2022 */
2023 pixel=gamma*geometry_info.rho*Sa*Sc*Da*Dc+geometry_info.sigma*
2024 Sa*Sc*Da+geometry_info.xi*Da*Dc*Sa+geometry_info.psi*Sa*Da+
2025 Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa);
2026 break;
2027 }
2028 case MinusDstCompositeOp:
2029 {
2030 pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
2031 break;
2032 }
2033 case MinusSrcCompositeOp:
2034 {
2035 /*
2036 Minus source from destination.
2037
2038 f(Sc,Dc) = Sc - Dc
2039 */
cristy99abff32011-12-24 20:45:16 +00002040 pixel=QuantumRange*gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
cristye4a40472011-12-22 02:56:19 +00002041 break;
2042 }
2043 case ModulateCompositeOp:
2044 {
2045 ssize_t
2046 offset;
2047
cristy99abff32011-12-24 20:45:16 +00002048 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002049 {
2050 pixel=Dc;
2051 break;
2052 }
2053 offset=(ssize_t) (GetPixelIntensity(composite_image,p)-midpoint);
2054 if (offset == 0)
2055 {
2056 pixel=Dc;
2057 break;
2058 }
cristy7133e642012-08-14 11:04:11 +00002059 CompositeHCL(destination_pixel.red,destination_pixel.green,
2060 destination_pixel.blue,&hue,&chroma,&luma);
2061 luma+=(0.01*percent_luma*offset)/midpoint;
2062 chroma*=0.01*percent_chroma;
2063 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002064 switch (channel)
2065 {
2066 case RedPixelChannel: pixel=red; break;
2067 case GreenPixelChannel: pixel=green; break;
2068 case BluePixelChannel: pixel=blue; break;
2069 default: pixel=Dc; break;
2070 }
2071 break;
2072 }
2073 case ModulusAddCompositeOp:
2074 {
2075 pixel=Sc+Dc;
2076 if (pixel > QuantumRange)
cristy516edc12013-10-27 10:53:42 +00002077 pixel-=QuantumRange;
cristy91de9ea2013-11-17 14:45:54 +00002078 pixel=gamma*(Sa*Da*pixel+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002079 break;
2080 }
2081 case ModulusSubtractCompositeOp:
2082 {
cristy99abff32011-12-24 20:45:16 +00002083 pixel=Sc-Dc;
cristye4a40472011-12-22 02:56:19 +00002084 if (pixel < 0.0)
cristy516edc12013-10-27 10:53:42 +00002085 pixel+=QuantumRange;
cristy91de9ea2013-11-17 14:45:54 +00002086 pixel=gamma*(Sa*Da*pixel+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002087 break;
2088 }
2089 case MultiplyCompositeOp:
2090 {
cristy99abff32011-12-24 20:45:16 +00002091 pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002092 break;
2093 }
2094 case OutCompositeOp:
2095 case SrcOutCompositeOp:
2096 {
2097 pixel=gamma*(Sa*Sc*(1.0-Da));
2098 break;
2099 }
2100 case OverCompositeOp:
2101 case SrcOverCompositeOp:
2102 {
cristy99abff32011-12-24 20:45:16 +00002103 pixel=QuantumRange*gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002104 break;
2105 }
2106 case OverlayCompositeOp:
2107 {
2108 if ((2.0*Dca) < Da)
cristy99abff32011-12-24 20:45:16 +00002109 {
2110 pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*
2111 (1.0-Da));
2112 break;
2113 }
2114 pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+
2115 Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00002116 break;
2117 }
2118 case PegtopLightCompositeOp:
2119 {
2120 /*
2121 PegTop: A Soft-Light alternative: A continuous version of the
2122 Softlight function, producing very similar results.
2123
2124 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
2125
2126 http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
2127 */
2128 if (fabs(Da) < MagickEpsilon)
2129 {
cristy99abff32011-12-24 20:45:16 +00002130 pixel=QuantumRange*gamma*(Sca);
cristye4a40472011-12-22 02:56:19 +00002131 break;
2132 }
cristy99abff32011-12-24 20:45:16 +00002133 pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
2134 Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002135 break;
2136 }
2137 case PinLightCompositeOp:
2138 {
2139 /*
2140 PinLight: A Photoshop 7 composition method
2141 http://www.simplefilter.de/en/basics/mixmods.html
2142
2143 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
2144 */
2145 if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
2146 {
cristy99abff32011-12-24 20:45:16 +00002147 pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002148 break;
2149 }
2150 if ((Dca*Sa) > (2.0*Sca*Da))
2151 {
cristy99abff32011-12-24 20:45:16 +00002152 pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002153 break;
2154 }
cristy99abff32011-12-24 20:45:16 +00002155 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
cristye4a40472011-12-22 02:56:19 +00002156 break;
2157 }
2158 case PlusCompositeOp:
2159 {
cristy24d5d722012-05-17 12:27:27 +00002160 pixel=gamma*(Sa*Sc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002161 break;
2162 }
2163 case SaturateCompositeOp:
2164 {
cristy99abff32011-12-24 20:45:16 +00002165 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002166 {
2167 pixel=Dc;
2168 break;
2169 }
cristy99abff32011-12-24 20:45:16 +00002170 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002171 {
2172 pixel=Sc;
2173 break;
2174 }
cristy7133e642012-08-14 11:04:11 +00002175 CompositeHCL(destination_pixel.red,destination_pixel.green,
2176 destination_pixel.blue,&hue,&chroma,&luma);
2177 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2178 &sans,&chroma,&sans);
2179 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002180 switch (channel)
2181 {
2182 case RedPixelChannel: pixel=red; break;
2183 case GreenPixelChannel: pixel=green; break;
2184 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002185 default: pixel=Dc; break;
2186 }
2187 break;
2188 }
2189 case ScreenCompositeOp:
2190 {
2191 /*
2192 Screen: a negated multiply:
2193
2194 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
2195 */
cristy99abff32011-12-24 20:45:16 +00002196 pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
cristye4a40472011-12-22 02:56:19 +00002197 break;
2198 }
2199 case SoftLightCompositeOp:
2200 {
2201 /*
2202 Refer to the March 2009 SVG specification.
2203 */
2204 if ((2.0*Sca) < Sa)
2205 {
cristy99abff32011-12-24 20:45:16 +00002206 pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-(Dca/Da)))+
2207 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002208 break;
2209 }
2210 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
2211 {
cristy99abff32011-12-24 20:45:16 +00002212 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*(Dca/Da)*
2213 (4.0*(Dca/Da)+1.0)*((Dca/Da)-1.0)+7.0*(Dca/Da))+Sca*(1.0-Da)+
2214 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002215 break;
2216 }
cristy99abff32011-12-24 20:45:16 +00002217 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow((Dca/Da),0.5)-
2218 (Dca/Da))+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002219 break;
2220 }
2221 case ThresholdCompositeOp:
2222 {
cristy7159f662012-10-28 17:32:43 +00002223 MagickRealType
cristye4a40472011-12-22 02:56:19 +00002224 delta;
2225
2226 delta=Sc-Dc;
cristy7159f662012-10-28 17:32:43 +00002227 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
cristye4a40472011-12-22 02:56:19 +00002228 {
2229 pixel=gamma*Dc;
2230 break;
2231 }
2232 pixel=gamma*(Dc+delta*amount);
2233 break;
2234 }
2235 case VividLightCompositeOp:
2236 {
2237 /*
2238 VividLight: A Photoshop 7 composition method. See
2239 http://www.simplefilter.de/en/basics/mixmods.html.
2240
2241 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
2242 */
2243 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
2244 {
cristy99abff32011-12-24 20:45:16 +00002245 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002246 break;
2247 }
2248 if ((2.0*Sca) <= Sa)
2249 {
cristy99abff32011-12-24 20:45:16 +00002250 pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*
2251 (1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002252 break;
2253 }
cristy99abff32011-12-24 20:45:16 +00002254 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+
2255 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002256 break;
2257 }
2258 case XorCompositeOp:
2259 {
cristy99abff32011-12-24 20:45:16 +00002260 pixel=QuantumRange*gamma*(Sc*Sa*(1.0-Da)+Dc*Da*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002261 break;
2262 }
2263 default:
2264 {
2265 pixel=Sc;
2266 break;
2267 }
2268 }
2269 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00002270 }
cristyed231572011-07-14 02:18:59 +00002271 p+=GetPixelChannels(composite_image);
cristye4a40472011-12-22 02:56:19 +00002272 channels=GetPixelChannels(composite_image);
2273 if (p >= (pixels+channels*composite_image->columns))
cristy4c08aed2011-07-01 19:47:50 +00002274 p=pixels;
cristyed231572011-07-14 02:18:59 +00002275 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002276 }
2277 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2278 status=MagickFalse;
2279 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2280 {
2281 MagickBooleanType
2282 proceed;
2283
2284#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002285 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002286#endif
2287 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2288 image->rows);
2289 if (proceed == MagickFalse)
2290 status=MagickFalse;
2291 }
2292 }
2293 composite_view=DestroyCacheView(composite_view);
2294 image_view=DestroyCacheView(image_view);
2295 if (destination_image != (Image * ) NULL)
2296 destination_image=DestroyImage(destination_image);
cristyf661c4d2012-07-28 12:57:17 +00002297 else
2298 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00002299 return(status);
2300}
2301
2302/*
2303%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304% %
2305% %
2306% %
2307% T e x t u r e I m a g e %
2308% %
2309% %
2310% %
2311%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2312%
2313% TextureImage() repeatedly tiles the texture image across and down the image
2314% canvas.
2315%
2316% The format of the TextureImage method is:
2317%
cristy30d8c942012-02-07 13:44:59 +00002318% MagickBooleanType TextureImage(Image *image,const Image *texture,
cristye941a752011-10-15 01:52:48 +00002319% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002320%
2321% A description of each parameter follows:
2322%
2323% o image: the image.
2324%
cristye6178502011-12-23 17:02:29 +00002325% o texture_image: This image is the texture to layer on the background.
cristy4c08aed2011-07-01 19:47:50 +00002326%
2327*/
cristy30d8c942012-02-07 13:44:59 +00002328MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2329 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002330{
2331#define TextureImageTag "Texture/Image"
2332
2333 CacheView
2334 *image_view,
2335 *texture_view;
2336
cristy30d8c942012-02-07 13:44:59 +00002337 Image
2338 *texture_image;
2339
cristy4c08aed2011-07-01 19:47:50 +00002340 MagickBooleanType
2341 status;
2342
2343 ssize_t
2344 y;
2345
2346 assert(image != (Image *) NULL);
2347 if (image->debug != MagickFalse)
2348 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2349 assert(image->signature == MagickSignature);
cristy30d8c942012-02-07 13:44:59 +00002350 if (texture == (const Image *) NULL)
2351 return(MagickFalse);
2352 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2353 return(MagickFalse);
2354 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
cristye6178502011-12-23 17:02:29 +00002355 if (texture_image == (const Image *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002356 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +00002357 (void) TransformImageColorspace(texture_image,image->colorspace,exception);
cristy387430f2012-02-07 13:09:46 +00002358 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
2359 exception);
cristy4c08aed2011-07-01 19:47:50 +00002360 status=MagickTrue;
2361 if ((image->compose != CopyCompositeOp) &&
cristy974a2a82013-04-23 12:57:16 +00002362 ((image->compose != OverCompositeOp) ||
2363 (image->alpha_trait == BlendPixelTrait) ||
cristy8a46d822012-08-28 23:32:39 +00002364 (texture_image->alpha_trait == BlendPixelTrait)))
cristy4c08aed2011-07-01 19:47:50 +00002365 {
2366 /*
2367 Tile texture onto the image background.
2368 */
cristye6178502011-12-23 17:02:29 +00002369 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00002370 {
2371 register ssize_t
2372 x;
2373
2374 if (status == MagickFalse)
2375 continue;
cristye6178502011-12-23 17:02:29 +00002376 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002377 {
2378 MagickBooleanType
2379 thread_status;
2380
cristyfeb3e962012-03-29 17:25:55 +00002381 thread_status=CompositeImage(image,texture_image,image->compose,
2382 MagickFalse,x+texture_image->tile_offset.x,y+
2383 texture_image->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002384 if (thread_status == MagickFalse)
2385 {
2386 status=thread_status;
2387 break;
2388 }
2389 }
2390 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2391 {
2392 MagickBooleanType
2393 proceed;
2394
cristy4c08aed2011-07-01 19:47:50 +00002395 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2396 y,image->rows);
2397 if (proceed == MagickFalse)
2398 status=MagickFalse;
2399 }
2400 }
2401 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2402 image->rows,image->rows);
cristy30d8c942012-02-07 13:44:59 +00002403 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002404 return(status);
2405 }
2406 /*
2407 Tile texture onto the image background (optimized).
2408 */
2409 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00002410 texture_view=AcquireVirtualCacheView(texture_image,exception);
2411 image_view=AcquireAuthenticCacheView(image,exception);
cristye8914392012-12-16 21:16:11 +00002412#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyd6432472013-01-06 16:56:13 +00002413 #pragma omp parallel for schedule(static,4) shared(status) \
2414 magick_threads(texture_image,image,1,1)
cristye8914392012-12-16 21:16:11 +00002415#endif
cristy4c08aed2011-07-01 19:47:50 +00002416 for (y=0; y < (ssize_t) image->rows; y++)
2417 {
2418 MagickBooleanType
2419 sync;
2420
2421 register const Quantum
2422 *p,
2423 *pixels;
2424
2425 register ssize_t
2426 x;
2427
2428 register Quantum
2429 *q;
2430
2431 size_t
2432 width;
2433
2434 if (status == MagickFalse)
2435 continue;
cristye6178502011-12-23 17:02:29 +00002436 pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
2437 (y+texture_image->tile_offset.y) % texture_image->rows,
2438 texture_image->columns,1,exception);
2439 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002440 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2441 {
2442 status=MagickFalse;
2443 continue;
2444 }
cristye6178502011-12-23 17:02:29 +00002445 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002446 {
2447 register ssize_t
cristye6178502011-12-23 17:02:29 +00002448 j;
cristy4c08aed2011-07-01 19:47:50 +00002449
2450 p=pixels;
cristye6178502011-12-23 17:02:29 +00002451 width=texture_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002452 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2453 width=image->columns-x;
cristye6178502011-12-23 17:02:29 +00002454 for (j=0; j < (ssize_t) width; j++)
cristy4c08aed2011-07-01 19:47:50 +00002455 {
cristye6178502011-12-23 17:02:29 +00002456 register ssize_t
2457 i;
2458
cristy883fde12013-04-08 00:50:13 +00002459 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002460 {
2461 p+=GetPixelChannels(texture_image);
2462 q+=GetPixelChannels(image);
2463 continue;
2464 }
cristye6178502011-12-23 17:02:29 +00002465 for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
2466 {
cristy5a23c552013-02-13 14:34:28 +00002467 PixelChannel channel=GetPixelChannelChannel(texture_image,i);
2468 PixelTrait traits=GetPixelChannelTraits(image,channel);
2469 PixelTrait texture_traits=GetPixelChannelTraits(texture_image,
2470 channel);
cristye6178502011-12-23 17:02:29 +00002471 if ((traits == UndefinedPixelTrait) ||
2472 (texture_traits == UndefinedPixelTrait))
2473 continue;
2474 SetPixelChannel(image,channel,p[i],q);
2475 }
2476 p+=GetPixelChannels(texture_image);
cristyed231572011-07-14 02:18:59 +00002477 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002478 }
2479 }
2480 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2481 if (sync == MagickFalse)
2482 status=MagickFalse;
2483 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2484 {
2485 MagickBooleanType
2486 proceed;
2487
cristy4c08aed2011-07-01 19:47:50 +00002488 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2489 image->rows);
2490 if (proceed == MagickFalse)
2491 status=MagickFalse;
2492 }
2493 }
2494 texture_view=DestroyCacheView(texture_view);
2495 image_view=DestroyCacheView(image_view);
cristy30d8c942012-02-07 13:44:59 +00002496 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002497 return(status);
2498}