blob: cc8bd84bfe19cc2590e10d1660b860741cad9ccf [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;
cristy4c08aed2011-07-01 19:47:50 +00001069 }
1070 break;
1071 }
1072 case BlendCompositeOp:
1073 {
cristyfeb3e962012-03-29 17:25:55 +00001074 const char
1075 *value;
1076
dirk55a83c72014-08-22 18:44:23 +00001077 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001078 if (value != (char *) NULL)
1079 {
1080 flags=ParseGeometry(value,&geometry_info);
1081 source_dissolve=geometry_info.rho/100.0;
1082 destination_dissolve=1.0-source_dissolve;
1083 if ((flags & SigmaValue) != 0)
1084 destination_dissolve=geometry_info.sigma/100.0;
cristy4c08aed2011-07-01 19:47:50 +00001085 }
1086 break;
1087 }
1088 case MathematicsCompositeOp:
1089 {
cristyfeb3e962012-03-29 17:25:55 +00001090 const char
1091 *value;
1092
cristy4c08aed2011-07-01 19:47:50 +00001093 /*
1094 Just collect the values from "compose:args", setting.
1095 Unused values are set to zero automagically.
1096
1097 Arguments are normally a comma separated list, so this probably should
1098 be changed to some 'general comma list' parser, (with a minimum
1099 number of values)
1100 */
1101 SetGeometryInfo(&geometry_info);
dirk55a83c72014-08-22 18:44:23 +00001102 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001103 if (value != (char *) NULL)
1104 (void) ParseGeometry(value,&geometry_info);
1105 break;
1106 }
1107 case ModulateCompositeOp:
1108 {
cristyfeb3e962012-03-29 17:25:55 +00001109 const char
1110 *value;
1111
cristy4c08aed2011-07-01 19:47:50 +00001112 /*
cristy7133e642012-08-14 11:04:11 +00001113 Determine the luma and chroma scale.
cristy4c08aed2011-07-01 19:47:50 +00001114 */
dirk55a83c72014-08-22 18:44:23 +00001115 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001116 if (value != (char *) NULL)
1117 {
1118 flags=ParseGeometry(value,&geometry_info);
cristy7133e642012-08-14 11:04:11 +00001119 percent_luma=geometry_info.rho;
cristy4c08aed2011-07-01 19:47:50 +00001120 if ((flags & SigmaValue) != 0)
cristy7133e642012-08-14 11:04:11 +00001121 percent_chroma=geometry_info.sigma;
cristy4c08aed2011-07-01 19:47:50 +00001122 }
1123 break;
1124 }
1125 case ThresholdCompositeOp:
1126 {
cristyfeb3e962012-03-29 17:25:55 +00001127 const char
1128 *value;
1129
cristy4c08aed2011-07-01 19:47:50 +00001130 /*
1131 Determine the amount and threshold.
1132 */
dirk55a83c72014-08-22 18:44:23 +00001133 value=GetImageArtifact(image,"compose:args");
cristy4c08aed2011-07-01 19:47:50 +00001134 if (value != (char *) NULL)
1135 {
1136 flags=ParseGeometry(value,&geometry_info);
1137 amount=geometry_info.rho;
1138 threshold=geometry_info.sigma;
1139 if ((flags & SigmaValue) == 0)
1140 threshold=0.05f;
1141 }
1142 threshold*=QuantumRange;
1143 break;
1144 }
1145 default:
1146 break;
1147 }
cristy4c08aed2011-07-01 19:47:50 +00001148 /*
1149 Composite image.
1150 */
1151 status=MagickTrue;
1152 progress=0;
cristy7159f662012-10-28 17:32:43 +00001153 midpoint=((MagickRealType) QuantumRange+1.0)/2;
cristy46ff2672012-12-14 15:32:26 +00001154 composite_view=AcquireVirtualCacheView(composite_image,exception);
1155 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001156#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001157 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001158 magick_threads(composite_image,image,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00001159#endif
1160 for (y=0; y < (ssize_t) image->rows; y++)
1161 {
1162 const Quantum
1163 *pixels;
1164
cristy7159f662012-10-28 17:32:43 +00001165 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001166 blue,
cristy7133e642012-08-14 11:04:11 +00001167 luma,
cristye4a40472011-12-22 02:56:19 +00001168 green,
cristy4c08aed2011-07-01 19:47:50 +00001169 hue,
cristye4a40472011-12-22 02:56:19 +00001170 red,
cristy7133e642012-08-14 11:04:11 +00001171 chroma;
cristy4c08aed2011-07-01 19:47:50 +00001172
cristyddeeea22012-04-12 01:33:09 +00001173 PixelInfo
1174 destination_pixel,
1175 source_pixel;
1176
cristy4c08aed2011-07-01 19:47:50 +00001177 register const Quantum
1178 *restrict p;
1179
1180 register Quantum
1181 *restrict q;
1182
1183 register ssize_t
1184 x;
1185
1186 if (status == MagickFalse)
1187 continue;
cristyfeb3e962012-03-29 17:25:55 +00001188 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001189 {
1190 if (y < y_offset)
1191 continue;
1192 if ((y-y_offset) >= (ssize_t) composite_image->rows)
1193 continue;
1194 }
1195 /*
1196 If pixels is NULL, y is outside overlay region.
1197 */
1198 pixels=(Quantum *) NULL;
1199 p=(Quantum *) NULL;
1200 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
1201 {
1202 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
1203 composite_image->columns,1,exception);
1204 if (p == (const Quantum *) NULL)
1205 {
1206 status=MagickFalse;
1207 continue;
1208 }
1209 pixels=p;
1210 if (x_offset < 0)
cristyed231572011-07-14 02:18:59 +00001211 p-=x_offset*GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001212 }
1213 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001214 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00001215 {
1216 status=MagickFalse;
1217 continue;
1218 }
cristy4c08aed2011-07-01 19:47:50 +00001219 hue=0.0;
cristy7133e642012-08-14 11:04:11 +00001220 chroma=0.0;
1221 luma=0.0;
cristyddeeea22012-04-12 01:33:09 +00001222 GetPixelInfo(image,&destination_pixel);
1223 GetPixelInfo(composite_image,&source_pixel);
cristy4c08aed2011-07-01 19:47:50 +00001224 for (x=0; x < (ssize_t) image->columns; x++)
1225 {
cristy17028dc2013-01-24 12:28:39 +00001226 double
1227 gamma;
1228
cristy7159f662012-10-28 17:32:43 +00001229 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001230 alpha,
1231 Da,
1232 Dc,
1233 Dca,
cristye4a40472011-12-22 02:56:19 +00001234 Sa,
1235 Sc,
1236 Sca;
1237
1238 register ssize_t
1239 i;
1240
cristy564a5692012-01-20 23:56:26 +00001241 size_t
1242 channels;
1243
cristyfeb3e962012-03-29 17:25:55 +00001244 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001245 {
1246 if (x < x_offset)
1247 {
cristyed231572011-07-14 02:18:59 +00001248 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001249 continue;
1250 }
1251 if ((x-x_offset) >= (ssize_t) composite_image->columns)
1252 break;
1253 }
cristye4a40472011-12-22 02:56:19 +00001254 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
1255 ((x-x_offset) >= (ssize_t) composite_image->columns))
1256 {
1257 Quantum
1258 source[MaxPixelChannels];
1259
1260 /*
1261 Virtual composite:
1262 Sc: source color.
1263 Dc: destination color.
1264 */
1265 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
1266 source,exception);
cristy883fde12013-04-08 00:50:13 +00001267 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001268 {
1269 q+=GetPixelChannels(image);
1270 continue;
1271 }
cristye4a40472011-12-22 02:56:19 +00001272 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1273 {
cristy7159f662012-10-28 17:32:43 +00001274 MagickRealType
cristye4a40472011-12-22 02:56:19 +00001275 pixel;
1276
cristy5a23c552013-02-13 14:34:28 +00001277 PixelChannel channel=GetPixelChannelChannel(image,i);
1278 PixelTrait traits=GetPixelChannelTraits(image,channel);
1279 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
1280 channel);
cristye4a40472011-12-22 02:56:19 +00001281 if ((traits == UndefinedPixelTrait) ||
1282 (composite_traits == UndefinedPixelTrait))
1283 continue;
1284 switch (compose)
1285 {
cristyc8d63672012-01-11 13:03:13 +00001286 case AlphaCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001287 case ChangeMaskCompositeOp:
1288 case CopyAlphaCompositeOp:
1289 case DstAtopCompositeOp:
1290 case DstInCompositeOp:
1291 case InCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +00001292 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001293 case OutCompositeOp:
1294 case SrcInCompositeOp:
1295 case SrcOutCompositeOp:
1296 {
cristy7159f662012-10-28 17:32:43 +00001297 pixel=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001298 if (channel == AlphaPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001299 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001300 break;
1301 }
1302 case ClearCompositeOp:
1303 case CopyCompositeOp:
1304 case ReplaceCompositeOp:
1305 case SrcCompositeOp:
1306 {
1307 if (channel == AlphaPixelChannel)
1308 {
cristy7159f662012-10-28 17:32:43 +00001309 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001310 break;
1311 }
1312 pixel=0.0;
1313 break;
1314 }
cristy99abff32011-12-24 20:45:16 +00001315 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001316 case DissolveCompositeOp:
1317 {
1318 if (channel == AlphaPixelChannel)
1319 {
1320 pixel=destination_dissolve*GetPixelAlpha(composite_image,
1321 source);
1322 break;
1323 }
cristy7159f662012-10-28 17:32:43 +00001324 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001325 break;
1326 }
1327 default:
1328 {
cristy7159f662012-10-28 17:32:43 +00001329 pixel=(MagickRealType) source[channel];
cristye4a40472011-12-22 02:56:19 +00001330 break;
1331 }
1332 }
1333 q[i]=ClampToQuantum(pixel);
1334 }
1335 q+=GetPixelChannels(image);
1336 continue;
1337 }
1338 /*
1339 Authentic composite:
1340 Sa: normalized source alpha.
1341 Da: normalized destination alpha.
1342 */
1343 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
1344 Da=QuantumScale*GetPixelAlpha(image,q);
cristy4c08aed2011-07-01 19:47:50 +00001345 switch (compose)
1346 {
cristye4a40472011-12-22 02:56:19 +00001347 case BumpmapCompositeOp:
1348 {
1349 alpha=GetPixelIntensity(composite_image,p)*Sa;
1350 break;
1351 }
cristycdc168f2011-12-21 15:24:39 +00001352 case ColorBurnCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001353 case ColorDodgeCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001354 case DifferenceCompositeOp:
1355 case DivideDstCompositeOp:
1356 case DivideSrcCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001357 case ExclusionCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001358 case HardLightCompositeOp:
cristy99fc2172014-06-26 10:30:53 +00001359 case HardMixCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001360 case LinearBurnCompositeOp:
1361 case LinearDodgeCompositeOp:
1362 case LinearLightCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001363 case MathematicsCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001364 case MinusDstCompositeOp:
1365 case MinusSrcCompositeOp:
1366 case ModulusAddCompositeOp:
1367 case ModulusSubtractCompositeOp:
1368 case MultiplyCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001369 case OverlayCompositeOp:
1370 case PegtopLightCompositeOp:
1371 case PinLightCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001372 case ScreenCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001373 case SoftLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001374 case VividLightCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001375 {
1376 alpha=RoundToUnity(Sa+Da-Sa*Da);
1377 break;
1378 }
1379 case DarkenCompositeOp:
1380 case DstAtopCompositeOp:
1381 case DstInCompositeOp:
1382 case InCompositeOp:
1383 case LightenCompositeOp:
1384 case SrcInCompositeOp:
1385 {
1386 alpha=Sa*Da;
1387 break;
1388 }
1389 case DissolveCompositeOp:
1390 {
1391 alpha=source_dissolve*Sa*(-destination_dissolve*Da)+source_dissolve*
1392 Sa+destination_dissolve*Da;
1393 break;
1394 }
1395 case DstOverCompositeOp:
1396 {
1397 alpha=Da*(-Sa)+Da+Sa;
1398 break;
1399 }
1400 case DstOutCompositeOp:
1401 {
1402 alpha=Da*(1.0-Sa);
1403 break;
1404 }
1405 case OutCompositeOp:
1406 case SrcOutCompositeOp:
1407 {
1408 alpha=Sa*(1.0-Da);
1409 break;
1410 }
1411 case OverCompositeOp:
1412 case SrcOverCompositeOp:
1413 {
1414 alpha=Sa*(-Da)+Sa+Da;
1415 break;
1416 }
cristy99abff32011-12-24 20:45:16 +00001417 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001418 case PlusCompositeOp:
1419 {
1420 alpha=RoundToUnity(Sa+Da);
1421 break;
1422 }
cristy4c08aed2011-07-01 19:47:50 +00001423 case XorCompositeOp:
1424 {
cristye4a40472011-12-22 02:56:19 +00001425 alpha=Sa+Da-2.0*Sa*Da;
cristy4c08aed2011-07-01 19:47:50 +00001426 break;
1427 }
1428 default:
cristye4a40472011-12-22 02:56:19 +00001429 {
1430 alpha=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001431 break;
cristye4a40472011-12-22 02:56:19 +00001432 }
cristy4c08aed2011-07-01 19:47:50 +00001433 }
cristy883fde12013-04-08 00:50:13 +00001434 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001435 {
1436 p+=GetPixelChannels(composite_image);
1437 q+=GetPixelChannels(image);
1438 continue;
1439 }
cristy9d3d2792012-04-14 15:15:19 +00001440 switch (compose)
1441 {
1442 case ColorizeCompositeOp:
1443 case HueCompositeOp:
1444 case LuminizeCompositeOp:
1445 case ModulateCompositeOp:
1446 case SaturateCompositeOp:
1447 {
1448 GetPixelInfoPixel(composite_image,p,&source_pixel);
1449 GetPixelInfoPixel(image,q,&destination_pixel);
1450 break;
1451 }
1452 default:
1453 break;
1454 }
cristye4a40472011-12-22 02:56:19 +00001455 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1456 {
cristy7159f662012-10-28 17:32:43 +00001457 MagickRealType
1458 pixel,
cristy564a5692012-01-20 23:56:26 +00001459 sans;
1460
cristy5a23c552013-02-13 14:34:28 +00001461 PixelChannel channel=GetPixelChannelChannel(image,i);
1462 PixelTrait traits=GetPixelChannelTraits(image,channel);
1463 PixelTrait composite_traits=GetPixelChannelTraits(composite_image,
1464 channel);
cristy0cd1f212012-01-05 15:45:59 +00001465 if (traits == UndefinedPixelTrait)
1466 continue;
cristya7b07912012-01-11 20:01:32 +00001467 if ((compose != IntensityCompositeOp) &&
cristye4a40472011-12-22 02:56:19 +00001468 (composite_traits == UndefinedPixelTrait))
1469 continue;
1470 /*
1471 Sc: source color.
cristye4a40472011-12-22 02:56:19 +00001472 Dc: destination color.
cristye4a40472011-12-22 02:56:19 +00001473 */
cristy7159f662012-10-28 17:32:43 +00001474 Sc=(MagickRealType) GetPixelChannel(composite_image,channel,p);
1475 Dc=(MagickRealType) q[i];
cristye4a40472011-12-22 02:56:19 +00001476 if ((traits & CopyPixelTrait) != 0)
cristye10859a2011-12-18 22:28:59 +00001477 {
cristye4a40472011-12-22 02:56:19 +00001478 if (channel != AlphaPixelChannel)
1479 {
1480 /*
1481 Copy channel.
1482 */
1483 q[i]=ClampToQuantum(Sc);
cristye10859a2011-12-18 22:28:59 +00001484 continue;
cristye10859a2011-12-18 22:28:59 +00001485 }
cristye4a40472011-12-22 02:56:19 +00001486 /*
1487 Set alpha channel.
1488 */
cristye10859a2011-12-18 22:28:59 +00001489 switch (compose)
1490 {
cristyc8d63672012-01-11 13:03:13 +00001491 case AlphaCompositeOp:
1492 {
cristya7b07912012-01-11 20:01:32 +00001493 pixel=QuantumRange*Sa;
cristyc8d63672012-01-11 13:03:13 +00001494 break;
1495 }
cristye4a40472011-12-22 02:56:19 +00001496 case AtopCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001497 case CopyBlackCompositeOp:
1498 case CopyBlueCompositeOp:
1499 case CopyCyanCompositeOp:
1500 case CopyGreenCompositeOp:
1501 case CopyMagentaCompositeOp:
1502 case CopyRedCompositeOp:
1503 case CopyYellowCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001504 case SrcAtopCompositeOp:
1505 case DstCompositeOp:
1506 case NoCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001507 {
cristye4a40472011-12-22 02:56:19 +00001508 pixel=QuantumRange*Da;
cristye10859a2011-12-18 22:28:59 +00001509 break;
1510 }
cristye10859a2011-12-18 22:28:59 +00001511 case ChangeMaskCompositeOp:
1512 {
cristye4a40472011-12-22 02:56:19 +00001513 MagickBooleanType
1514 equivalent;
1515
cristy7159f662012-10-28 17:32:43 +00001516 if (Da > ((MagickRealType) QuantumRange/2.0))
cristy99abff32011-12-24 20:45:16 +00001517 {
cristy7159f662012-10-28 17:32:43 +00001518 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001519 break;
1520 }
cristye4a40472011-12-22 02:56:19 +00001521 equivalent=IsFuzzyEquivalencePixel(composite_image,p,image,q);
cristy99abff32011-12-24 20:45:16 +00001522 if (equivalent != MagickFalse)
cristye4a40472011-12-22 02:56:19 +00001523 {
cristy7159f662012-10-28 17:32:43 +00001524 pixel=(MagickRealType) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001525 break;
1526 }
cristy7159f662012-10-28 17:32:43 +00001527 pixel=(MagickRealType) OpaqueAlpha;
cristye4a40472011-12-22 02:56:19 +00001528 break;
1529 }
cristy99abff32011-12-24 20:45:16 +00001530 case ClearCompositeOp:
1531 {
cristy7159f662012-10-28 17:32:43 +00001532 pixel=(MagickRealType) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001533 break;
1534 }
1535 case ColorizeCompositeOp:
1536 case HueCompositeOp:
1537 case LuminizeCompositeOp:
1538 case SaturateCompositeOp:
1539 {
1540 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1541 {
1542 pixel=QuantumRange*Da;
1543 break;
1544 }
1545 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
1546 {
1547 pixel=QuantumRange*Sa;
1548 break;
1549 }
1550 if (Sa < Da)
1551 {
1552 pixel=QuantumRange*Da;
1553 break;
1554 }
1555 pixel=QuantumRange*Sa;
1556 break;
1557 }
cristy99abff32011-12-24 20:45:16 +00001558 case CopyAlphaCompositeOp:
cristy24d5d722012-05-17 12:27:27 +00001559 {
1560 pixel=QuantumRange*Sa;
cristy431cf8d2014-08-25 12:46:17 +00001561 if (composite_image->alpha_trait != BlendPixelTrait)
cristy24d5d722012-05-17 12:27:27 +00001562 pixel=GetPixelIntensity(composite_image,p);
1563 break;
1564 }
1565 case CopyCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001566 case DisplaceCompositeOp:
1567 case DistortCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001568 case DstAtopCompositeOp:
1569 case ReplaceCompositeOp:
1570 case SrcCompositeOp:
1571 {
1572 pixel=QuantumRange*Sa;
1573 break;
1574 }
1575 case DarkenIntensityCompositeOp:
1576 {
cristy99abff32011-12-24 20:45:16 +00001577 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1578 (1.0-Da)*GetPixelIntensity(image,q) ? Sa : Da;
cristye4a40472011-12-22 02:56:19 +00001579 break;
1580 }
cristy98621462011-12-31 22:31:11 +00001581 case IntensityCompositeOp:
1582 {
cristyf13c5942012-08-08 23:50:11 +00001583 pixel=GetPixelIntensity(composite_image,p);
cristy98621462011-12-31 22:31:11 +00001584 break;
1585 }
cristye4a40472011-12-22 02:56:19 +00001586 case LightenIntensityCompositeOp:
1587 {
1588 pixel=Sa*GetPixelIntensity(composite_image,p) >
1589 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye10859a2011-12-18 22:28:59 +00001590 break;
1591 }
cristy99abff32011-12-24 20:45:16 +00001592 case ModulateCompositeOp:
1593 {
1594 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1595 {
1596 pixel=QuantumRange*Da;
1597 break;
1598 }
1599 pixel=QuantumRange*Da;
1600 break;
1601 }
cristye10859a2011-12-18 22:28:59 +00001602 default:
1603 {
cristye4a40472011-12-22 02:56:19 +00001604 pixel=QuantumRange*alpha;
cristye10859a2011-12-18 22:28:59 +00001605 break;
1606 }
1607 }
cristye4a40472011-12-22 02:56:19 +00001608 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00001609 continue;
1610 }
1611 /*
cristy99abff32011-12-24 20:45:16 +00001612 Porter-Duff compositions:
1613 Sca: source normalized color multiplied by alpha.
1614 Dca: normalized destination color multiplied by alpha.
cristye10859a2011-12-18 22:28:59 +00001615 */
cristy99abff32011-12-24 20:45:16 +00001616 Sca=QuantumScale*Sa*Sc;
1617 Dca=QuantumScale*Da*Dc;
cristye10859a2011-12-18 22:28:59 +00001618 switch (compose)
1619 {
cristye10859a2011-12-18 22:28:59 +00001620 case DarkenCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001621 case LightenCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001622 case ModulusSubtractCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001623 {
cristy99abff32011-12-24 20:45:16 +00001624 gamma=1.0-alpha;
cristye10859a2011-12-18 22:28:59 +00001625 break;
1626 }
1627 default:
1628 break;
1629 }
cristy3e3ec3a2012-11-03 23:11:06 +00001630 gamma=PerceptibleReciprocal(alpha);
cristyd197cbb2012-01-13 02:14:12 +00001631 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001632 switch (compose)
1633 {
cristya7b07912012-01-11 20:01:32 +00001634 case AlphaCompositeOp:
1635 {
1636 pixel=QuantumRange*Sa;
1637 break;
1638 }
cristye4a40472011-12-22 02:56:19 +00001639 case AtopCompositeOp:
1640 case SrcAtopCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001641 {
cristye4a40472011-12-22 02:56:19 +00001642 pixel=Sc*Sa+Dc*(1.0-Sa);
1643 break;
cristye10859a2011-12-18 22:28:59 +00001644 }
cristye4a40472011-12-22 02:56:19 +00001645 case BlendCompositeOp:
1646 {
1647 pixel=gamma*(source_dissolve*Sa*Sc+destination_dissolve*Da*Dc);
1648 break;
1649 }
1650 case BlurCompositeOp:
1651 case DisplaceCompositeOp:
1652 case DistortCompositeOp:
1653 case CopyCompositeOp:
1654 case ReplaceCompositeOp:
1655 case SrcCompositeOp:
1656 {
1657 pixel=Sc;
1658 break;
1659 }
1660 case BumpmapCompositeOp:
1661 {
cristy99abff32011-12-24 20:45:16 +00001662 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001663 {
1664 pixel=Dc;
1665 break;
1666 }
1667 pixel=QuantumScale*GetPixelIntensity(composite_image,p)*Dc;
1668 break;
1669 }
cristy99abff32011-12-24 20:45:16 +00001670 case ChangeMaskCompositeOp:
1671 {
1672 pixel=Dc;
1673 break;
1674 }
1675 case ClearCompositeOp:
1676 {
1677 pixel=0.0;
1678 break;
1679 }
cristye4a40472011-12-22 02:56:19 +00001680 case ColorBurnCompositeOp:
1681 {
1682 /*
1683 Refer to the March 2009 SVG specification.
1684 */
1685 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
1686 {
cristy99abff32011-12-24 20:45:16 +00001687 pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001688 break;
1689 }
1690 if (Sca < MagickEpsilon)
1691 {
cristy99abff32011-12-24 20:45:16 +00001692 pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001693 break;
1694 }
cristy99abff32011-12-24 20:45:16 +00001695 pixel=QuantumRange*gamma*(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+
1696 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001697 break;
1698 }
1699 case ColorDodgeCompositeOp:
1700 {
cristy98a39202013-07-09 13:25:52 +00001701 if ((Sca*Da+Dca*Sa) >= Sa*Da)
cristye4a40472011-12-22 02:56:19 +00001702 {
cristy31784be2013-07-09 13:55:34 +00001703 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001704 break;
1705 }
cristy99abff32011-12-24 20:45:16 +00001706 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001707 (1.0-Sa));
1708 break;
1709 }
1710 case ColorizeCompositeOp:
1711 {
cristy99abff32011-12-24 20:45:16 +00001712 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001713 {
1714 pixel=Dc;
1715 break;
1716 }
cristy99abff32011-12-24 20:45:16 +00001717 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001718 {
1719 pixel=Sc;
1720 break;
1721 }
cristy7133e642012-08-14 11:04:11 +00001722 CompositeHCL(destination_pixel.red,destination_pixel.green,
1723 destination_pixel.blue,&sans,&sans,&luma);
1724 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1725 &hue,&chroma,&sans);
1726 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001727 switch (channel)
1728 {
1729 case RedPixelChannel: pixel=red; break;
1730 case GreenPixelChannel: pixel=green; break;
1731 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001732 default: pixel=Dc; break;
1733 }
1734 break;
1735 }
cristye4a40472011-12-22 02:56:19 +00001736 case CopyAlphaCompositeOp:
cristy98621462011-12-31 22:31:11 +00001737 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001738 {
cristy24d5d722012-05-17 12:27:27 +00001739 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001740 break;
1741 }
1742 case CopyBlackCompositeOp:
1743 {
cristyd197cbb2012-01-13 02:14:12 +00001744 if (channel == BlackPixelChannel)
cristy2fda00b2013-10-21 16:12:19 +00001745 pixel=(MagickRealType) (QuantumRange-
1746 GetPixelBlack(composite_image,p));
cristye4a40472011-12-22 02:56:19 +00001747 break;
1748 }
1749 case CopyBlueCompositeOp:
1750 case CopyYellowCompositeOp:
1751 {
cristyd197cbb2012-01-13 02:14:12 +00001752 if (channel == BluePixelChannel)
cristy7159f662012-10-28 17:32:43 +00001753 pixel=(MagickRealType) GetPixelBlue(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001754 break;
1755 }
1756 case CopyGreenCompositeOp:
1757 case CopyMagentaCompositeOp:
1758 {
cristyd197cbb2012-01-13 02:14:12 +00001759 if (channel == GreenPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001760 pixel=(MagickRealType) GetPixelGreen(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001761 break;
1762 }
1763 case CopyRedCompositeOp:
1764 case CopyCyanCompositeOp:
1765 {
cristyd197cbb2012-01-13 02:14:12 +00001766 if (channel == RedPixelChannel)
cristy7159f662012-10-28 17:32:43 +00001767 pixel=(MagickRealType) GetPixelRed(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001768 break;
1769 }
cristy99abff32011-12-24 20:45:16 +00001770 case DarkenCompositeOp:
1771 {
1772 /*
1773 Darken is equivalent to a 'Minimum' method
1774 OR a greyscale version of a binary 'Or'
1775 OR the 'Intersection' of pixel sets.
1776 */
1777 if (Sc < Dc)
1778 {
1779 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
1780 break;
1781 }
1782 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1783 break;
1784 }
cristye4a40472011-12-22 02:56:19 +00001785 case DarkenIntensityCompositeOp:
1786 {
cristy99abff32011-12-24 20:45:16 +00001787 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1788 (1.0-Da)*GetPixelIntensity(image,q) ? Sc : Dc;
cristye4a40472011-12-22 02:56:19 +00001789 break;
1790 }
1791 case DifferenceCompositeOp:
1792 {
1793 pixel=gamma*(Sa*Sc+Da*Dc-Sa*Da*2.0*MagickMin(Sc,Dc));
1794 break;
1795 }
1796 case DissolveCompositeOp:
1797 {
1798 pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
1799 destination_dissolve*Da*Dc+destination_dissolve*Da*Dc);
1800 break;
1801 }
1802 case DivideDstCompositeOp:
1803 {
1804 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1805 {
cristy99abff32011-12-24 20:45:16 +00001806 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001807 break;
1808 }
1809 if (fabs(Dca) < MagickEpsilon)
1810 {
cristy99abff32011-12-24 20:45:16 +00001811 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001812 break;
1813 }
cristy99abff32011-12-24 20:45:16 +00001814 pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001815 break;
1816 }
1817 case DivideSrcCompositeOp:
1818 {
1819 if ((fabs(Dca) < MagickEpsilon) && (fabs(Sca) < MagickEpsilon))
1820 {
cristy99abff32011-12-24 20:45:16 +00001821 pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001822 break;
1823 }
1824 if (fabs(Sca) < MagickEpsilon)
1825 {
cristy99abff32011-12-24 20:45:16 +00001826 pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001827 break;
1828 }
cristy99abff32011-12-24 20:45:16 +00001829 pixel=QuantumRange*gamma*(Dca*Sa*Sa/Sca+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001830 break;
1831 }
1832 case DstAtopCompositeOp:
1833 {
1834 pixel=Dc*Da+Sc*(1.0-Da);
1835 break;
1836 }
1837 case DstCompositeOp:
1838 case NoCompositeOp:
1839 {
1840 pixel=Dc;
1841 break;
1842 }
1843 case DstInCompositeOp:
1844 {
1845 pixel=gamma*(Sa*Dc*Sa);
1846 break;
1847 }
1848 case DstOutCompositeOp:
1849 {
1850 pixel=gamma*(Da*Dc*(1.0-Sa));
1851 break;
1852 }
1853 case DstOverCompositeOp:
1854 {
1855 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1856 break;
1857 }
1858 case ExclusionCompositeOp:
1859 {
cristy99abff32011-12-24 20:45:16 +00001860 pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
1861 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001862 break;
1863 }
1864 case HardLightCompositeOp:
1865 {
1866 if ((2.0*Sca) < Sa)
1867 {
cristy99abff32011-12-24 20:45:16 +00001868 pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001869 (1.0-Sa));
1870 break;
1871 }
cristy99abff32011-12-24 20:45:16 +00001872 pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
cristye4a40472011-12-22 02:56:19 +00001873 Dca*(1.0-Sa));
1874 break;
1875 }
cristy99fc2172014-06-26 10:30:53 +00001876 case HardMixCompositeOp:
1877 {
cristya9781452014-06-26 10:58:54 +00001878 double
1879 gamma;
1880
cristyaa19c352014-06-26 12:46:02 +00001881 if ((Sa+Da) < 1.0)
cristya9781452014-06-26 10:58:54 +00001882 gamma=0.0;
1883 else
1884 gamma=1.0;
cristyaa19c352014-06-26 12:46:02 +00001885 pixel=(gamma*(1.0-Sca)*(1.0-Dca))+Sa*(1.0-Sca)*Dca+Da*(1.0-Dca)*Sca;
cristy99fc2172014-06-26 10:30:53 +00001886 break;
1887 }
cristye4a40472011-12-22 02:56:19 +00001888 case HueCompositeOp:
1889 {
cristy99abff32011-12-24 20:45:16 +00001890 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001891 {
1892 pixel=Dc;
1893 break;
1894 }
cristy99abff32011-12-24 20:45:16 +00001895 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001896 {
1897 pixel=Sc;
1898 break;
1899 }
cristy7133e642012-08-14 11:04:11 +00001900 CompositeHCL(destination_pixel.red,destination_pixel.green,
1901 destination_pixel.blue,&hue,&chroma,&luma);
1902 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
cristye4a40472011-12-22 02:56:19 +00001903 &hue,&sans,&sans);
cristy7133e642012-08-14 11:04:11 +00001904 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001905 switch (channel)
1906 {
1907 case RedPixelChannel: pixel=red; break;
1908 case GreenPixelChannel: pixel=green; break;
1909 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001910 default: pixel=Dc; break;
1911 }
1912 break;
1913 }
1914 case InCompositeOp:
1915 case SrcInCompositeOp:
1916 {
1917 pixel=gamma*(Da*Sc*Da);
1918 break;
1919 }
cristy99abff32011-12-24 20:45:16 +00001920 case LinearBurnCompositeOp:
1921 {
1922 /*
1923 LinearBurn: as defined by Abode Photoshop, according to
1924 http://www.simplefilter.de/en/basics/mixmods.html is:
1925
1926 f(Sc,Dc) = Sc + Dc - 1
1927 */
1928 pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
1929 break;
1930 }
1931 case LinearDodgeCompositeOp:
1932 {
1933 pixel=QuantumRange*gamma*(Sa*Sc+Da*Dc);
1934 break;
1935 }
1936 case LinearLightCompositeOp:
1937 {
1938 /*
1939 LinearLight: as defined by Abode Photoshop, according to
1940 http://www.simplefilter.de/en/basics/mixmods.html is:
1941
1942 f(Sc,Dc) = Dc + 2*Sc - 1
1943 */
1944 pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
1945 break;
1946 }
1947 case LightenCompositeOp:
1948 {
1949 if (Sc > Dc)
1950 {
cristy24d5d722012-05-17 12:27:27 +00001951 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristy99abff32011-12-24 20:45:16 +00001952 break;
1953 }
cristy24d5d722012-05-17 12:27:27 +00001954 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
cristy99abff32011-12-24 20:45:16 +00001955 break;
1956 }
cristye4a40472011-12-22 02:56:19 +00001957 case LightenIntensityCompositeOp:
1958 {
1959 /*
1960 Lighten is equivalent to a 'Maximum' method
1961 OR a greyscale version of a binary 'And'
1962 OR the 'Union' of pixel sets.
1963 */
1964 pixel=Sa*GetPixelIntensity(composite_image,p) >
1965 Da*GetPixelIntensity(image,q) ? Sc : Dc;
1966 break;
1967 }
cristye4a40472011-12-22 02:56:19 +00001968 case LuminizeCompositeOp:
1969 {
cristy99abff32011-12-24 20:45:16 +00001970 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001971 {
1972 pixel=Dc;
1973 break;
1974 }
cristy99abff32011-12-24 20:45:16 +00001975 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001976 {
1977 pixel=Sc;
1978 break;
1979 }
cristy7133e642012-08-14 11:04:11 +00001980 CompositeHCL(destination_pixel.red,destination_pixel.green,
1981 destination_pixel.blue,&hue,&chroma,&luma);
1982 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1983 &sans,&sans,&luma);
1984 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001985 switch (channel)
1986 {
1987 case RedPixelChannel: pixel=red; break;
1988 case GreenPixelChannel: pixel=green; break;
1989 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001990 default: pixel=Dc; break;
1991 }
1992 break;
1993 }
1994 case MathematicsCompositeOp:
1995 {
1996 /*
1997 'Mathematics' a free form user control mathematical composition
1998 is defined as...
1999
2000 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
2001
2002 Where the arguments A,B,C,D are (currently) passed to composite
2003 as a command separated 'geometry' string in "compose:args" image
2004 artifact.
2005
2006 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
2007
2008 Applying the SVG transparency formula (see above), we get...
2009
2010 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
2011
2012 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
2013 Dca*(1.0-Sa)
2014 */
2015 pixel=gamma*geometry_info.rho*Sa*Sc*Da*Dc+geometry_info.sigma*
2016 Sa*Sc*Da+geometry_info.xi*Da*Dc*Sa+geometry_info.psi*Sa*Da+
2017 Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa);
2018 break;
2019 }
2020 case MinusDstCompositeOp:
2021 {
2022 pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
2023 break;
2024 }
2025 case MinusSrcCompositeOp:
2026 {
2027 /*
2028 Minus source from destination.
2029
2030 f(Sc,Dc) = Sc - Dc
2031 */
cristy99abff32011-12-24 20:45:16 +00002032 pixel=QuantumRange*gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
cristye4a40472011-12-22 02:56:19 +00002033 break;
2034 }
2035 case ModulateCompositeOp:
2036 {
2037 ssize_t
2038 offset;
2039
cristy99abff32011-12-24 20:45:16 +00002040 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002041 {
2042 pixel=Dc;
2043 break;
2044 }
2045 offset=(ssize_t) (GetPixelIntensity(composite_image,p)-midpoint);
2046 if (offset == 0)
2047 {
2048 pixel=Dc;
2049 break;
2050 }
cristy7133e642012-08-14 11:04:11 +00002051 CompositeHCL(destination_pixel.red,destination_pixel.green,
2052 destination_pixel.blue,&hue,&chroma,&luma);
2053 luma+=(0.01*percent_luma*offset)/midpoint;
2054 chroma*=0.01*percent_chroma;
2055 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002056 switch (channel)
2057 {
2058 case RedPixelChannel: pixel=red; break;
2059 case GreenPixelChannel: pixel=green; break;
2060 case BluePixelChannel: pixel=blue; break;
2061 default: pixel=Dc; break;
2062 }
2063 break;
2064 }
2065 case ModulusAddCompositeOp:
2066 {
2067 pixel=Sc+Dc;
2068 if (pixel > QuantumRange)
cristy516edc12013-10-27 10:53:42 +00002069 pixel-=QuantumRange;
cristy91de9ea2013-11-17 14:45:54 +00002070 pixel=gamma*(Sa*Da*pixel+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002071 break;
2072 }
2073 case ModulusSubtractCompositeOp:
2074 {
cristy99abff32011-12-24 20:45:16 +00002075 pixel=Sc-Dc;
cristye4a40472011-12-22 02:56:19 +00002076 if (pixel < 0.0)
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 MultiplyCompositeOp:
2082 {
cristy99abff32011-12-24 20:45:16 +00002083 pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002084 break;
2085 }
2086 case OutCompositeOp:
2087 case SrcOutCompositeOp:
2088 {
2089 pixel=gamma*(Sa*Sc*(1.0-Da));
2090 break;
2091 }
2092 case OverCompositeOp:
2093 case SrcOverCompositeOp:
2094 {
cristy99abff32011-12-24 20:45:16 +00002095 pixel=QuantumRange*gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002096 break;
2097 }
2098 case OverlayCompositeOp:
2099 {
2100 if ((2.0*Dca) < Da)
cristy99abff32011-12-24 20:45:16 +00002101 {
2102 pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*
2103 (1.0-Da));
2104 break;
2105 }
2106 pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+
2107 Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00002108 break;
2109 }
2110 case PegtopLightCompositeOp:
2111 {
2112 /*
2113 PegTop: A Soft-Light alternative: A continuous version of the
2114 Softlight function, producing very similar results.
2115
2116 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
2117
2118 http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
2119 */
2120 if (fabs(Da) < MagickEpsilon)
2121 {
cristy99abff32011-12-24 20:45:16 +00002122 pixel=QuantumRange*gamma*(Sca);
cristye4a40472011-12-22 02:56:19 +00002123 break;
2124 }
cristy99abff32011-12-24 20:45:16 +00002125 pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
2126 Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002127 break;
2128 }
2129 case PinLightCompositeOp:
2130 {
2131 /*
2132 PinLight: A Photoshop 7 composition method
2133 http://www.simplefilter.de/en/basics/mixmods.html
2134
2135 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
2136 */
2137 if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
2138 {
cristy99abff32011-12-24 20:45:16 +00002139 pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002140 break;
2141 }
2142 if ((Dca*Sa) > (2.0*Sca*Da))
2143 {
cristy99abff32011-12-24 20:45:16 +00002144 pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002145 break;
2146 }
cristy99abff32011-12-24 20:45:16 +00002147 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
cristye4a40472011-12-22 02:56:19 +00002148 break;
2149 }
2150 case PlusCompositeOp:
2151 {
cristy24d5d722012-05-17 12:27:27 +00002152 pixel=gamma*(Sa*Sc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002153 break;
2154 }
2155 case SaturateCompositeOp:
2156 {
cristy99abff32011-12-24 20:45:16 +00002157 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002158 {
2159 pixel=Dc;
2160 break;
2161 }
cristy99abff32011-12-24 20:45:16 +00002162 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002163 {
2164 pixel=Sc;
2165 break;
2166 }
cristy7133e642012-08-14 11:04:11 +00002167 CompositeHCL(destination_pixel.red,destination_pixel.green,
2168 destination_pixel.blue,&hue,&chroma,&luma);
2169 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2170 &sans,&chroma,&sans);
2171 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002172 switch (channel)
2173 {
2174 case RedPixelChannel: pixel=red; break;
2175 case GreenPixelChannel: pixel=green; break;
2176 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002177 default: pixel=Dc; break;
2178 }
2179 break;
2180 }
2181 case ScreenCompositeOp:
2182 {
2183 /*
2184 Screen: a negated multiply:
2185
2186 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
2187 */
cristy99abff32011-12-24 20:45:16 +00002188 pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
cristye4a40472011-12-22 02:56:19 +00002189 break;
2190 }
2191 case SoftLightCompositeOp:
2192 {
2193 /*
2194 Refer to the March 2009 SVG specification.
2195 */
2196 if ((2.0*Sca) < Sa)
2197 {
cristy99abff32011-12-24 20:45:16 +00002198 pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-(Dca/Da)))+
2199 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002200 break;
2201 }
2202 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
2203 {
cristy99abff32011-12-24 20:45:16 +00002204 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*(Dca/Da)*
2205 (4.0*(Dca/Da)+1.0)*((Dca/Da)-1.0)+7.0*(Dca/Da))+Sca*(1.0-Da)+
2206 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002207 break;
2208 }
cristy99abff32011-12-24 20:45:16 +00002209 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow((Dca/Da),0.5)-
2210 (Dca/Da))+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002211 break;
2212 }
2213 case ThresholdCompositeOp:
2214 {
cristy7159f662012-10-28 17:32:43 +00002215 MagickRealType
cristye4a40472011-12-22 02:56:19 +00002216 delta;
2217
2218 delta=Sc-Dc;
cristy7159f662012-10-28 17:32:43 +00002219 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
cristye4a40472011-12-22 02:56:19 +00002220 {
2221 pixel=gamma*Dc;
2222 break;
2223 }
2224 pixel=gamma*(Dc+delta*amount);
2225 break;
2226 }
2227 case VividLightCompositeOp:
2228 {
2229 /*
2230 VividLight: A Photoshop 7 composition method. See
2231 http://www.simplefilter.de/en/basics/mixmods.html.
2232
2233 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
2234 */
2235 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
2236 {
cristy99abff32011-12-24 20:45:16 +00002237 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002238 break;
2239 }
2240 if ((2.0*Sca) <= Sa)
2241 {
cristy99abff32011-12-24 20:45:16 +00002242 pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*
2243 (1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002244 break;
2245 }
cristy99abff32011-12-24 20:45:16 +00002246 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+
2247 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002248 break;
2249 }
2250 case XorCompositeOp:
2251 {
cristy99abff32011-12-24 20:45:16 +00002252 pixel=QuantumRange*gamma*(Sc*Sa*(1.0-Da)+Dc*Da*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002253 break;
2254 }
2255 default:
2256 {
2257 pixel=Sc;
2258 break;
2259 }
2260 }
2261 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00002262 }
cristyed231572011-07-14 02:18:59 +00002263 p+=GetPixelChannels(composite_image);
cristye4a40472011-12-22 02:56:19 +00002264 channels=GetPixelChannels(composite_image);
2265 if (p >= (pixels+channels*composite_image->columns))
cristy4c08aed2011-07-01 19:47:50 +00002266 p=pixels;
cristyed231572011-07-14 02:18:59 +00002267 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002268 }
2269 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2270 status=MagickFalse;
2271 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2272 {
2273 MagickBooleanType
2274 proceed;
2275
2276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002277 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002278#endif
2279 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2280 image->rows);
2281 if (proceed == MagickFalse)
2282 status=MagickFalse;
2283 }
2284 }
2285 composite_view=DestroyCacheView(composite_view);
2286 image_view=DestroyCacheView(image_view);
2287 if (destination_image != (Image * ) NULL)
2288 destination_image=DestroyImage(destination_image);
cristyf661c4d2012-07-28 12:57:17 +00002289 else
2290 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00002291 return(status);
2292}
2293
2294/*
2295%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2296% %
2297% %
2298% %
2299% T e x t u r e I m a g e %
2300% %
2301% %
2302% %
2303%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2304%
2305% TextureImage() repeatedly tiles the texture image across and down the image
2306% canvas.
2307%
2308% The format of the TextureImage method is:
2309%
cristy30d8c942012-02-07 13:44:59 +00002310% MagickBooleanType TextureImage(Image *image,const Image *texture,
cristye941a752011-10-15 01:52:48 +00002311% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002312%
2313% A description of each parameter follows:
2314%
2315% o image: the image.
2316%
cristye6178502011-12-23 17:02:29 +00002317% o texture_image: This image is the texture to layer on the background.
cristy4c08aed2011-07-01 19:47:50 +00002318%
2319*/
cristy30d8c942012-02-07 13:44:59 +00002320MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2321 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002322{
2323#define TextureImageTag "Texture/Image"
2324
2325 CacheView
2326 *image_view,
2327 *texture_view;
2328
cristy30d8c942012-02-07 13:44:59 +00002329 Image
2330 *texture_image;
2331
cristy4c08aed2011-07-01 19:47:50 +00002332 MagickBooleanType
2333 status;
2334
2335 ssize_t
2336 y;
2337
2338 assert(image != (Image *) NULL);
2339 if (image->debug != MagickFalse)
2340 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2341 assert(image->signature == MagickSignature);
cristy30d8c942012-02-07 13:44:59 +00002342 if (texture == (const Image *) NULL)
2343 return(MagickFalse);
2344 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2345 return(MagickFalse);
2346 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
cristye6178502011-12-23 17:02:29 +00002347 if (texture_image == (const Image *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002348 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +00002349 (void) TransformImageColorspace(texture_image,image->colorspace,exception);
cristy387430f2012-02-07 13:09:46 +00002350 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
2351 exception);
cristy4c08aed2011-07-01 19:47:50 +00002352 status=MagickTrue;
2353 if ((image->compose != CopyCompositeOp) &&
cristy974a2a82013-04-23 12:57:16 +00002354 ((image->compose != OverCompositeOp) ||
2355 (image->alpha_trait == BlendPixelTrait) ||
cristy8a46d822012-08-28 23:32:39 +00002356 (texture_image->alpha_trait == BlendPixelTrait)))
cristy4c08aed2011-07-01 19:47:50 +00002357 {
2358 /*
2359 Tile texture onto the image background.
2360 */
cristye6178502011-12-23 17:02:29 +00002361 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00002362 {
2363 register ssize_t
2364 x;
2365
2366 if (status == MagickFalse)
2367 continue;
cristye6178502011-12-23 17:02:29 +00002368 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002369 {
2370 MagickBooleanType
2371 thread_status;
2372
cristyfeb3e962012-03-29 17:25:55 +00002373 thread_status=CompositeImage(image,texture_image,image->compose,
2374 MagickFalse,x+texture_image->tile_offset.x,y+
2375 texture_image->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002376 if (thread_status == MagickFalse)
2377 {
2378 status=thread_status;
2379 break;
2380 }
2381 }
2382 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2383 {
2384 MagickBooleanType
2385 proceed;
2386
cristy4c08aed2011-07-01 19:47:50 +00002387 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2388 y,image->rows);
2389 if (proceed == MagickFalse)
2390 status=MagickFalse;
2391 }
2392 }
2393 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2394 image->rows,image->rows);
cristy30d8c942012-02-07 13:44:59 +00002395 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002396 return(status);
2397 }
2398 /*
2399 Tile texture onto the image background (optimized).
2400 */
2401 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00002402 texture_view=AcquireVirtualCacheView(texture_image,exception);
2403 image_view=AcquireAuthenticCacheView(image,exception);
cristye8914392012-12-16 21:16:11 +00002404#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyd6432472013-01-06 16:56:13 +00002405 #pragma omp parallel for schedule(static,4) shared(status) \
2406 magick_threads(texture_image,image,1,1)
cristye8914392012-12-16 21:16:11 +00002407#endif
cristy4c08aed2011-07-01 19:47:50 +00002408 for (y=0; y < (ssize_t) image->rows; y++)
2409 {
2410 MagickBooleanType
2411 sync;
2412
2413 register const Quantum
2414 *p,
2415 *pixels;
2416
2417 register ssize_t
2418 x;
2419
2420 register Quantum
2421 *q;
2422
2423 size_t
2424 width;
2425
2426 if (status == MagickFalse)
2427 continue;
cristye6178502011-12-23 17:02:29 +00002428 pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
2429 (y+texture_image->tile_offset.y) % texture_image->rows,
2430 texture_image->columns,1,exception);
2431 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002432 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2433 {
2434 status=MagickFalse;
2435 continue;
2436 }
cristye6178502011-12-23 17:02:29 +00002437 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002438 {
2439 register ssize_t
cristye6178502011-12-23 17:02:29 +00002440 j;
cristy4c08aed2011-07-01 19:47:50 +00002441
2442 p=pixels;
cristye6178502011-12-23 17:02:29 +00002443 width=texture_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002444 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2445 width=image->columns-x;
cristye6178502011-12-23 17:02:29 +00002446 for (j=0; j < (ssize_t) width; j++)
cristy4c08aed2011-07-01 19:47:50 +00002447 {
cristye6178502011-12-23 17:02:29 +00002448 register ssize_t
2449 i;
2450
cristy883fde12013-04-08 00:50:13 +00002451 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002452 {
2453 p+=GetPixelChannels(texture_image);
2454 q+=GetPixelChannels(image);
2455 continue;
2456 }
cristye6178502011-12-23 17:02:29 +00002457 for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
2458 {
cristy5a23c552013-02-13 14:34:28 +00002459 PixelChannel channel=GetPixelChannelChannel(texture_image,i);
2460 PixelTrait traits=GetPixelChannelTraits(image,channel);
2461 PixelTrait texture_traits=GetPixelChannelTraits(texture_image,
2462 channel);
cristye6178502011-12-23 17:02:29 +00002463 if ((traits == UndefinedPixelTrait) ||
2464 (texture_traits == UndefinedPixelTrait))
2465 continue;
2466 SetPixelChannel(image,channel,p[i],q);
2467 }
2468 p+=GetPixelChannels(texture_image);
cristyed231572011-07-14 02:18:59 +00002469 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002470 }
2471 }
2472 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2473 if (sync == MagickFalse)
2474 status=MagickFalse;
2475 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2476 {
2477 MagickBooleanType
2478 proceed;
2479
cristy4c08aed2011-07-01 19:47:50 +00002480 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2481 image->rows);
2482 if (proceed == MagickFalse)
2483 status=MagickFalse;
2484 }
2485 }
2486 texture_view=DestroyCacheView(texture_view);
2487 image_view=DestroyCacheView(image_view);
cristy30d8c942012-02-07 13:44:59 +00002488 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002489 return(status);
2490}