blob: 99ad303bd6e5f815a6929465f16ce9e309944e55 [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 %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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"
48#include "MagickCore/client.h"
49#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/composite.h"
54#include "MagickCore/composite-private.h"
55#include "MagickCore/constitute.h"
56#include "MagickCore/draw.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/memory_.h"
67#include "MagickCore/option.h"
68#include "MagickCore/pixel-accessor.h"
69#include "MagickCore/property.h"
70#include "MagickCore/quantum.h"
71#include "MagickCore/resample.h"
72#include "MagickCore/resource_.h"
73#include "MagickCore/string_.h"
74#include "MagickCore/thread-private.h"
cristy191c0b72012-08-12 16:29:52 +000075#include "MagickCore/threshold.h"
cristy63a81872012-03-22 15:52:52 +000076#include "MagickCore/token.h"
cristy4c08aed2011-07-01 19:47:50 +000077#include "MagickCore/utility.h"
cristyd1dd6e42011-09-04 01:46:08 +000078#include "MagickCore/utility-private.h"
cristy4c08aed2011-07-01 19:47:50 +000079#include "MagickCore/version.h"
80
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83% %
84% %
85% %
cristyf4ad9df2011-07-08 16:49:03 +000086% C o m p o s i t e I m a g e %
cristy4c08aed2011-07-01 19:47:50 +000087% %
88% %
89% %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
cristyf4ad9df2011-07-08 16:49:03 +000092% CompositeImage() returns the second image composited onto the first
cristy4c08aed2011-07-01 19:47:50 +000093% at the specified offset, using the specified composite method.
94%
cristyf4ad9df2011-07-08 16:49:03 +000095% The format of the CompositeImage method is:
cristy4c08aed2011-07-01 19:47:50 +000096%
97% MagickBooleanType CompositeImage(Image *image,
cristyfeb3e962012-03-29 17:25:55 +000098% const Image *composite_image,const CompositeOperator compose,
99% const MagickBooleanType clip_to_self,const ssize_t x_offset,
100% const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +0000101%
102% A description of each parameter follows:
103%
104% o image: the destination image, modified by he composition
105%
cristyfeb3e962012-03-29 17:25:55 +0000106% o composite_image: the composite (source) image.
107%
cristy4c08aed2011-07-01 19:47:50 +0000108% o compose: This operator affects how the composite is applied to
109% the image. The operators and how they are utilized are listed here
110% http://www.w3.org/TR/SVG12/#compositing.
111%
cristyfeb3e962012-03-29 17:25:55 +0000112% o clip_to_self: set to MagickTrue to limit composition to area composed.
cristy4c08aed2011-07-01 19:47:50 +0000113%
114% o x_offset: the column offset of the composited image.
115%
116% o y_offset: the row offset of the composited image.
117%
118% Extra Controls from Image meta-data in 'composite_image' (artifacts)
119%
120% o "compose:args"
121% A string containing extra numerical arguments for specific compose
122% methods, generally expressed as a 'geometry' or a comma separated list
123% of numbers.
124%
125% Compose methods needing such arguments include "BlendCompositeOp" and
126% "DisplaceCompositeOp".
127%
cristye941a752011-10-15 01:52:48 +0000128% o exception: return any errors or warnings in this structure.
129%
cristy4c08aed2011-07-01 19:47:50 +0000130*/
131
anthonyea068a52012-04-09 05:46:25 +0000132/*
133 Composition based on the SVG specification:
134
135 A Composition is defined by...
136 Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
137 Blending areas : X = 1 for area of overlap, ie: f(Sc,Dc)
138 Y = 1 for source preserved
139 Z = 1 for destination preserved
140
141 Conversion to transparency (then optimized)
142 Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
143 Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
144
145 Where...
146 Sca = Sc*Sa normalized Source color divided by Source alpha
147 Dca = Dc*Da normalized Dest color divided by Dest alpha
148 Dc' = Dca'/Da' the desired color value for this channel.
149
150 Da' in in the follow formula as 'gamma' The resulting alpla value.
151
152 Most functions use a blending mode of over (X=1,Y=1,Z=1) this results in
153 the following optimizations...
154 gamma = Sa+Da-Sa*Da;
155 gamma = 1 - QuantiumScale*alpha * QuantiumScale*beta;
156 opacity = QuantiumScale*alpha*beta; // over blend, optimized 1-Gamma
157
158 The above SVG definitions also definate that Mathematical Composition
159 methods should use a 'Over' blending mode for Alpha Channel.
160 It however was not applied for composition modes of 'Plus', 'Minus',
161 the modulus versions of 'Add' and 'Subtract'.
162
163 Mathematical operator changes to be applied from IM v6.7...
164
165 1) Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
166 'ModulusAdd' and 'ModulusSubtract' for clarity.
167
168 2) All mathematical compositions work as per the SVG specification
169 with regard to blending. This now includes 'ModulusAdd' and
170 'ModulusSubtract'.
171
172 3) When the special channel flag 'sync' (syncronize channel updates)
173 is turned off (enabled by default) then mathematical compositions are
174 only performed on the channels specified, and are applied
175 independantally of each other. In other words the mathematics is
176 performed as 'pure' mathematical operations, rather than as image
177 operations.
178*/
cristy4c08aed2011-07-01 19:47:50 +0000179
cristye4a40472011-12-22 02:56:19 +0000180static inline double MagickMin(const double x,const double y)
181{
182 if (x < y)
183 return(x);
184 return(y);
185}
cristyddeeea22012-04-12 01:33:09 +0000186
cristye4a40472011-12-22 02:56:19 +0000187static inline double MagickMax(const double x,const double y)
188{
189 if (x > y)
190 return(x);
191 return(y);
192}
193
cristyd8f16f72012-08-13 12:49:50 +0000194static inline double ConvertHueToRGB(double m1,
195 double m2,double hue)
196{
197 if (hue < 0.0)
198 hue+=1.0;
199 if (hue > 1.0)
200 hue-=1.0;
201 if ((6.0*hue) < 1.0)
202 return(m1+6.0*(m2-m1)*hue);
203 if ((2.0*hue) < 1.0)
204 return(m2);
205 if ((3.0*hue) < 2.0)
206 return(m1+6.0*(m2-m1)*(2.0/3.0-hue));
207 return(m1);
208}
209
cristy7133e642012-08-14 11:04:11 +0000210static void HCLComposite(const double hue,const double chroma,const double luma,
211 double *red,double *green,double *blue)
cristyd8f16f72012-08-13 12:49:50 +0000212{
213 double
214 b,
cristy7133e642012-08-14 11:04:11 +0000215 c,
cristyd8f16f72012-08-13 12:49:50 +0000216 g,
cristy7133e642012-08-14 11:04:11 +0000217 h,
218 m,
cristyd8f16f72012-08-13 12:49:50 +0000219 r,
cristye1715282012-08-15 13:10:55 +0000220 x,
221 z;
cristyd8f16f72012-08-13 12:49:50 +0000222
223 /*
cristy7133e642012-08-14 11:04:11 +0000224 Convert HCL to RGB colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000225 */
226 assert(red != (double *) NULL);
227 assert(green != (double *) NULL);
228 assert(blue != (double *) NULL);
cristy7133e642012-08-14 11:04:11 +0000229 h=6.0*hue;
230 c=chroma;
231 x=c*(1.0-fabs(fmod(h,2.0)-1.0));
232 r=0.0;
233 g=0.0;
234 b=0.0;
235 if ((0.0 <= h) && (h < 1.0))
cristyd8f16f72012-08-13 12:49:50 +0000236 {
cristye1715282012-08-15 13:10:55 +0000237 r=c;
238 g=x;
cristyd8f16f72012-08-13 12:49:50 +0000239 }
cristyd8f16f72012-08-13 12:49:50 +0000240 else
cristy7133e642012-08-14 11:04:11 +0000241 if ((1.0 <= h) && (h < 2.0))
242 {
cristye1715282012-08-15 13:10:55 +0000243 r=x;
244 g=c;
cristy7133e642012-08-14 11:04:11 +0000245 }
246 else
247 if ((2.0 <= h) && (h < 3.0))
248 {
cristye1715282012-08-15 13:10:55 +0000249 g=c;
250 b=x;
cristy7133e642012-08-14 11:04:11 +0000251 }
252 else
253 if ((3.0 <= h) && (h < 4.0))
254 {
cristye1715282012-08-15 13:10:55 +0000255 g=x;
256 b=c;
cristy7133e642012-08-14 11:04:11 +0000257 }
258 else
259 if ((4.0 <= h) && (h < 5.0))
260 {
cristye1715282012-08-15 13:10:55 +0000261 r=x;
262 b=c;
cristy7133e642012-08-14 11:04:11 +0000263 }
264 else
265 if ((5.0 <= h) && (h < 6.0))
266 {
cristye1715282012-08-15 13:10:55 +0000267 r=c;
268 b=x;
cristy7133e642012-08-14 11:04:11 +0000269 }
cristyf2c5f4a2012-08-14 23:22:31 +0000270 m=luma-(0.298839*r+0.586811*g+0.114350*b);
cristye1715282012-08-15 13:10:55 +0000271 /*
272 Choose saturation strategy to clip it into the RGB cube; hue and luma are
273 preserved and chroma may be changed.
274 */
275 z=1.0;
276 if (m < 0.0)
277 {
278 z=luma/(luma-m);
279 m=0.0;
280 }
281 else
282 if (m+c > 1.0)
283 {
284 z=(1.0-luma)/(m+c-luma);
285 m=1.0-z*c;
286 }
287 *red=QuantumRange*(z*r+m);
288 *green=QuantumRange*(z*g+m);
289 *blue=QuantumRange*(z*b+m);
cristyd8f16f72012-08-13 12:49:50 +0000290}
291
cristy7133e642012-08-14 11:04:11 +0000292static void CompositeHCL(const double red,const double green,const double blue,
293 double *hue,double *chroma,double *luma)
cristyd8f16f72012-08-13 12:49:50 +0000294{
295 double
296 b,
cristy7133e642012-08-14 11:04:11 +0000297 c,
cristyd8f16f72012-08-13 12:49:50 +0000298 g,
cristy7133e642012-08-14 11:04:11 +0000299 h,
cristyd8f16f72012-08-13 12:49:50 +0000300 max,
cristyd8f16f72012-08-13 12:49:50 +0000301 r;
302
303 /*
cristy7133e642012-08-14 11:04:11 +0000304 Convert RGB to HCL colorspace.
cristyd8f16f72012-08-13 12:49:50 +0000305 */
306 assert(hue != (double *) NULL);
cristy7133e642012-08-14 11:04:11 +0000307 assert(chroma != (double *) NULL);
308 assert(luma != (double *) NULL);
309 r=red;
310 g=green;
311 b=blue;
cristyd8f16f72012-08-13 12:49:50 +0000312 max=MagickMax(r,MagickMax(g,b));
cristy7133e642012-08-14 11:04:11 +0000313 c=max-(double) MagickMin(r,MagickMin(g,b));
314 h=0.0;
315 if (c == 0)
316 h=0.0;
cristyd8f16f72012-08-13 12:49:50 +0000317 else
cristy7133e642012-08-14 11:04:11 +0000318 if (red == max)
cristyf2c5f4a2012-08-14 23:22:31 +0000319 h=fmod(6.0+(g-b)/c,6.0);
cristyd8f16f72012-08-13 12:49:50 +0000320 else
cristy7133e642012-08-14 11:04:11 +0000321 if (green == max)
322 h=((b-r)/c)+2.0;
323 else
324 if (blue == max)
325 h=((r-g)/c)+4.0;
326 *hue=(h/6.0);
327 *chroma=QuantumScale*c;
328 *luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b);
cristyd8f16f72012-08-13 12:49:50 +0000329}
330
cristye4a40472011-12-22 02:56:19 +0000331static MagickBooleanType CompositeOverImage(Image *image,
cristyfeb3e962012-03-29 17:25:55 +0000332 const Image *composite_image,const MagickBooleanType clip_to_self,
333 const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristye4a40472011-12-22 02:56:19 +0000334{
335#define CompositeImageTag "Composite/Image"
336
337 CacheView
338 *composite_view,
339 *image_view;
340
cristye4a40472011-12-22 02:56:19 +0000341 MagickBooleanType
cristye4a40472011-12-22 02:56:19 +0000342 status;
343
344 MagickOffsetType
345 progress;
346
347 ssize_t
348 y;
349
cristye4a40472011-12-22 02:56:19 +0000350 /*
cristye4a40472011-12-22 02:56:19 +0000351 Composite image.
352 */
353 status=MagickTrue;
354 progress=0;
cristydb070952012-04-20 14:33:00 +0000355 composite_view=AcquireVirtualCacheView(composite_image,exception);
356 image_view=AcquireAuthenticCacheView(image,exception);
cristye4a40472011-12-22 02:56:19 +0000357#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000358 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000359 dynamic_number_threads(image,image->columns,image->rows,1)
cristye4a40472011-12-22 02:56:19 +0000360#endif
361 for (y=0; y < (ssize_t) image->rows; y++)
362 {
363 const Quantum
364 *pixels;
365
366 register const Quantum
367 *restrict p;
368
369 register Quantum
370 *restrict q;
371
372 register ssize_t
373 x;
374
cristy564a5692012-01-20 23:56:26 +0000375 size_t
376 channels;
377
cristye4a40472011-12-22 02:56:19 +0000378 if (status == MagickFalse)
379 continue;
cristyfeb3e962012-03-29 17:25:55 +0000380 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000381 {
382 if (y < y_offset)
383 continue;
384 if ((y-y_offset) >= (ssize_t) composite_image->rows)
385 continue;
386 }
387 /*
388 If pixels is NULL, y is outside overlay region.
389 */
390 pixels=(Quantum *) NULL;
391 p=(Quantum *) NULL;
392 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
393 {
394 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
395 composite_image->columns,1,exception);
396 if (p == (const Quantum *) NULL)
397 {
398 status=MagickFalse;
399 continue;
400 }
401 pixels=p;
402 if (x_offset < 0)
403 p-=x_offset*GetPixelChannels(composite_image);
404 }
405 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
406 if (q == (Quantum *) NULL)
407 {
408 status=MagickFalse;
409 continue;
410 }
411 for (x=0; x < (ssize_t) image->columns; x++)
412 {
cristya19f1d72012-08-07 18:24:38 +0000413 double
cristye4a40472011-12-22 02:56:19 +0000414 alpha,
415 Da,
416 Dc,
417 gamma,
418 Sa,
419 Sc;
420
421 register ssize_t
422 i;
423
cristyfeb3e962012-03-29 17:25:55 +0000424 if (clip_to_self != MagickFalse)
cristye4a40472011-12-22 02:56:19 +0000425 {
426 if (x < x_offset)
427 {
428 q+=GetPixelChannels(image);
429 continue;
430 }
431 if ((x-x_offset) >= (ssize_t) composite_image->columns)
432 break;
433 }
434 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
435 ((x-x_offset) >= (ssize_t) composite_image->columns))
436 {
437 Quantum
438 source[MaxPixelChannels];
439
440 /*
441 Virtual composite:
442 Sc: source color.
443 Dc: destination color.
444 */
cristy10a6c612012-01-29 21:41:05 +0000445 if (GetPixelMask(image,q) != 0)
446 {
447 q+=GetPixelChannels(image);
448 continue;
449 }
cristyc94ba6f2012-01-29 23:19:58 +0000450 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
451 source,exception);
cristye4a40472011-12-22 02:56:19 +0000452 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
453 {
454 PixelChannel
455 channel;
456
457 PixelTrait
458 composite_traits,
459 traits;
460
cristycf1296e2012-08-26 23:40:49 +0000461 channel=GetPixelChannelChannel(image,i);
462 traits=GetPixelChannelTraits(image,channel);
463 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristye4a40472011-12-22 02:56:19 +0000464 if ((traits == UndefinedPixelTrait) ||
465 (composite_traits == UndefinedPixelTrait))
466 continue;
467 q[i]=source[channel];
468 }
469 q+=GetPixelChannels(image);
470 continue;
471 }
472 /*
473 Authentic composite:
474 Sa: normalized source alpha.
475 Da: normalized destination alpha.
476 */
cristyc94ba6f2012-01-29 23:19:58 +0000477 if (GetPixelMask(composite_image,p) != 0)
478 {
479 p+=GetPixelChannels(composite_image);
480 channels=GetPixelChannels(composite_image);
481 if (p >= (pixels+channels*composite_image->columns))
482 p=pixels;
483 q+=GetPixelChannels(image);
484 continue;
485 }
cristye4a40472011-12-22 02:56:19 +0000486 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
487 Da=QuantumScale*GetPixelAlpha(image,q);
488 alpha=Sa*(-Da)+Sa+Da;
489 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
490 {
491 PixelChannel
492 channel;
493
494 PixelTrait
495 composite_traits,
496 traits;
497
cristycf1296e2012-08-26 23:40:49 +0000498 channel=GetPixelChannelChannel(image,i);
499 traits=GetPixelChannelTraits(image,channel);
500 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristye4a40472011-12-22 02:56:19 +0000501 if ((traits == UndefinedPixelTrait) ||
502 (composite_traits == UndefinedPixelTrait))
503 continue;
504 if ((traits & CopyPixelTrait) != 0)
505 {
506 if (channel != AlphaPixelChannel)
507 {
508 /*
509 Copy channel.
510 */
511 q[i]=GetPixelChannel(composite_image,channel,p);
512 continue;
513 }
514 /*
515 Set alpha channel.
516 */
517 q[i]=ClampToQuantum(QuantumRange*alpha);
518 continue;
519 }
520 /*
521 Sc: source color.
522 Dc: destination color.
523 */
cristya19f1d72012-08-07 18:24:38 +0000524 Sc=(double) GetPixelChannel(composite_image,channel,p);
525 Dc=(double) q[i];
cristyc58380a2012-06-03 15:12:30 +0000526 gamma=MagickEpsilonReciprocal(alpha);
cristye4a40472011-12-22 02:56:19 +0000527 q[i]=ClampToQuantum(gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc));
528 }
529 p+=GetPixelChannels(composite_image);
530 channels=GetPixelChannels(composite_image);
531 if (p >= (pixels+channels*composite_image->columns))
532 p=pixels;
533 q+=GetPixelChannels(image);
534 }
535 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
536 status=MagickFalse;
537 if (image->progress_monitor != (MagickProgressMonitor) NULL)
538 {
539 MagickBooleanType
540 proceed;
541
542#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000543 #pragma omp critical (MagickCore_CompositeImage)
cristye4a40472011-12-22 02:56:19 +0000544#endif
545 proceed=SetImageProgress(image,CompositeImageTag,progress++,
546 image->rows);
547 if (proceed == MagickFalse)
548 status=MagickFalse;
549 }
550 }
551 composite_view=DestroyCacheView(composite_view);
552 image_view=DestroyCacheView(image_view);
553 return(status);
554}
555
cristy4c08aed2011-07-01 19:47:50 +0000556MagickExport MagickBooleanType CompositeImage(Image *image,
cristya865ccd2012-07-28 00:33:10 +0000557 const Image *composite,const CompositeOperator compose,
cristy44c98412012-03-31 18:25:40 +0000558 const MagickBooleanType clip_to_self,const ssize_t x_offset,
559 const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +0000560{
cristy4c08aed2011-07-01 19:47:50 +0000561#define CompositeImageTag "Composite/Image"
562
563 CacheView
564 *composite_view,
565 *image_view;
566
cristy4c08aed2011-07-01 19:47:50 +0000567 GeometryInfo
568 geometry_info;
569
570 Image
cristya865ccd2012-07-28 00:33:10 +0000571 *composite_image,
cristy4c08aed2011-07-01 19:47:50 +0000572 *destination_image;
573
574 MagickBooleanType
cristy4c08aed2011-07-01 19:47:50 +0000575 status;
576
577 MagickOffsetType
578 progress;
579
cristya19f1d72012-08-07 18:24:38 +0000580 double
cristy4c08aed2011-07-01 19:47:50 +0000581 amount,
582 destination_dissolve,
583 midpoint,
cristy7133e642012-08-14 11:04:11 +0000584 percent_luma,
585 percent_chroma,
cristy4c08aed2011-07-01 19:47:50 +0000586 source_dissolve,
587 threshold;
588
589 MagickStatusType
590 flags;
591
cristyd197cbb2012-01-13 02:14:12 +0000592 ssize_t
593 y;
594
cristy4c08aed2011-07-01 19:47:50 +0000595 assert(image != (Image *) NULL);
596 assert(image->signature == MagickSignature);
597 if (image->debug != MagickFalse)
598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristya865ccd2012-07-28 00:33:10 +0000599 assert(composite!= (Image *) NULL);
600 assert(composite->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +0000601 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000602 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +0000603 composite_image=CloneImage(composite,0,0,MagickTrue,exception);
604 if (composite_image == (const Image *) NULL)
605 return(MagickFalse);
cristyeb16d5e2012-09-12 20:11:53 +0000606 if (IsGrayColorspace(image->colorspace) != MagickFalse)
607 (void) SetImageColorspace(image,RGBColorspace,exception);
cristya4a22a22012-08-01 23:16:38 +0000608 (void) SetImageColorspace(composite_image,image->colorspace,exception);
cristye4a40472011-12-22 02:56:19 +0000609 if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp))
610 {
cristyfeb3e962012-03-29 17:25:55 +0000611 status=CompositeOverImage(image,composite_image,clip_to_self,x_offset,
612 y_offset,exception);
cristya865ccd2012-07-28 00:33:10 +0000613 composite_image=DestroyImage(composite_image);
cristye4a40472011-12-22 02:56:19 +0000614 return(status);
615 }
cristy4c08aed2011-07-01 19:47:50 +0000616 destination_image=(Image *) NULL;
617 amount=0.5;
618 destination_dissolve=1.0;
cristy7133e642012-08-14 11:04:11 +0000619 percent_luma=100.0;
620 percent_chroma=100.0;
cristy4c08aed2011-07-01 19:47:50 +0000621 source_dissolve=1.0;
622 threshold=0.05f;
623 switch (compose)
624 {
cristy4c08aed2011-07-01 19:47:50 +0000625 case CopyCompositeOp:
626 {
627 if ((x_offset < 0) || (y_offset < 0))
628 break;
629 if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns)
630 break;
631 if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows)
632 break;
633 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +0000634 composite_view=AcquireVirtualCacheView(composite_image,exception);
635 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000636#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000637 #pragma omp parallel for schedule(static,4) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000638 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +0000639#endif
640 for (y=0; y < (ssize_t) composite_image->rows; y++)
641 {
642 MagickBooleanType
643 sync;
644
645 register const Quantum
646 *p;
647
648 register Quantum
649 *q;
650
651 register ssize_t
652 x;
653
654 if (status == MagickFalse)
655 continue;
656 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
657 1,exception);
658 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
659 composite_image->columns,1,exception);
660 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
661 {
662 status=MagickFalse;
663 continue;
664 }
665 for (x=0; x < (ssize_t) composite_image->columns; x++)
666 {
cristybdecccc2011-12-24 22:52:16 +0000667 register ssize_t
668 i;
669
cristy665e18f2012-05-17 12:39:54 +0000670 if (GetPixelMask(composite_image,p) != 0)
cristy10a6c612012-01-29 21:41:05 +0000671 {
672 p+=GetPixelChannels(composite_image);
673 q+=GetPixelChannels(image);
674 continue;
675 }
cristybdecccc2011-12-24 22:52:16 +0000676 for (i=0; i < (ssize_t) GetPixelChannels(composite_image); i++)
677 {
678 PixelChannel
679 channel;
680
681 PixelTrait
682 composite_traits,
683 traits;
684
cristycf1296e2012-08-26 23:40:49 +0000685 channel=GetPixelChannelChannel(composite_image,i);
686 composite_traits=GetPixelChannelTraits(composite_image,channel);
687 traits=GetPixelChannelTraits(image,channel);
cristybdecccc2011-12-24 22:52:16 +0000688 if ((traits == UndefinedPixelTrait) ||
689 (composite_traits == UndefinedPixelTrait))
690 continue;
691 SetPixelChannel(image,channel,p[i],q);
692 }
cristyed231572011-07-14 02:18:59 +0000693 p+=GetPixelChannels(composite_image);
694 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +0000695 }
696 sync=SyncCacheViewAuthenticPixels(image_view,exception);
697 if (sync == MagickFalse)
698 status=MagickFalse;
699 if (image->progress_monitor != (MagickProgressMonitor) NULL)
700 {
701 MagickBooleanType
702 proceed;
703
704#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +0000705 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +0000706#endif
707 proceed=SetImageProgress(image,CompositeImageTag,
708 (MagickOffsetType) y,image->rows);
709 if (proceed == MagickFalse)
710 status=MagickFalse;
711 }
712 }
713 composite_view=DestroyCacheView(composite_view);
714 image_view=DestroyCacheView(image_view);
cristy44886b92012-07-28 13:07:09 +0000715 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000716 return(status);
717 }
cristye4a40472011-12-22 02:56:19 +0000718 case CopyAlphaCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000719 case ChangeMaskCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +0000720 case IntensityCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000721 {
722 /*
723 Modify destination outside the overlaid region and require an alpha
724 channel to exist, to add transparency.
725 */
cristy8a46d822012-08-28 23:32:39 +0000726 if (image->alpha_trait != BlendPixelTrait)
cristy42c41de2012-05-05 18:36:31 +0000727 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy4c08aed2011-07-01 19:47:50 +0000728 break;
729 }
730 case BlurCompositeOp:
731 {
732 CacheView
733 *composite_view,
734 *destination_view;
735
cristyfeb3e962012-03-29 17:25:55 +0000736 const char
737 *value;
738
cristy4c08aed2011-07-01 19:47:50 +0000739 PixelInfo
740 pixel;
741
cristya19f1d72012-08-07 18:24:38 +0000742 double
cristy4c08aed2011-07-01 19:47:50 +0000743 angle_range,
744 angle_start,
745 height,
746 width;
747
748 ResampleFilter
749 *resample_filter;
750
751 SegmentInfo
752 blur;
753
754 /*
anthony9cb63cc2012-04-25 06:10:49 +0000755 Blur Image by resampling.
756
cristy4c08aed2011-07-01 19:47:50 +0000757 Blur Image dictated by an overlay gradient map: X = red_channel;
758 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
759 */
760 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000761 exception);
cristy4c08aed2011-07-01 19:47:50 +0000762 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000763 {
764 composite_image=DestroyImage(composite_image);
765 return(MagickFalse);
766 }
cristy4c08aed2011-07-01 19:47:50 +0000767 /*
anthony9cb63cc2012-04-25 06:10:49 +0000768 Gather the maximum blur sigma values from user.
cristy4c08aed2011-07-01 19:47:50 +0000769 */
770 SetGeometryInfo(&geometry_info);
771 flags=NoValue;
772 value=GetImageArtifact(composite_image,"compose:args");
773 if (value != (char *) NULL)
774 flags=ParseGeometry(value,&geometry_info);
anthonyd2923912012-04-23 13:06:53 +0000775 if ((flags & WidthValue) == 0 ) {
776 (void) ThrowMagickException(exception,GetMagickModule(),
777 OptionWarning,"InvalidSetting","'%s' '%s'",
anthony9cb63cc2012-04-25 06:10:49 +0000778 "compose:args",value);
cristy44886b92012-07-28 13:07:09 +0000779 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000780 destination_image=DestroyImage(destination_image);
781 return(MagickFalse);
782 }
anthony9cb63cc2012-04-25 06:10:49 +0000783 /*
784 Users input sigma now needs to be converted to the EWA ellipse size.
785 The filter defaults to a sigma of 0.5 so to make this match the
786 users input the ellipse size needs to be doubled.
anthonyd2923912012-04-23 13:06:53 +0000787 */
788 width=height=geometry_info.rho*2.0;
789 if ((flags & HeightValue) != 0 )
790 height=geometry_info.sigma*2.0;
791
anthony9cb63cc2012-04-25 06:10:49 +0000792 /* default the unrotated ellipse width and height axis vectors */
anthonyd2923912012-04-23 13:06:53 +0000793 blur.x1=width;
cristy4c08aed2011-07-01 19:47:50 +0000794 blur.x2=0.0;
795 blur.y1=0.0;
anthonyd2923912012-04-23 13:06:53 +0000796 blur.y2=height;
797 /* rotate vectors if a rotation angle is given */
cristy4c08aed2011-07-01 19:47:50 +0000798 if ((flags & XValue) != 0 )
799 {
cristya19f1d72012-08-07 18:24:38 +0000800 double
cristy4c08aed2011-07-01 19:47:50 +0000801 angle;
802
803 angle=DegreesToRadians(geometry_info.xi);
804 blur.x1=width*cos(angle);
805 blur.x2=width*sin(angle);
806 blur.y1=(-height*sin(angle));
807 blur.y2=height*cos(angle);
808 }
anthonyd2923912012-04-23 13:06:53 +0000809 /* Otherwise lets set a angle range and calculate in the loop */
810 angle_start=0.0;
811 angle_range=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000812 if ((flags & YValue) != 0 )
813 {
814 angle_start=DegreesToRadians(geometry_info.xi);
815 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
816 }
817 /*
anthony9cb63cc2012-04-25 06:10:49 +0000818 Set up a gaussian cylindrical filter for EWA Bluring.
anthonyd2923912012-04-23 13:06:53 +0000819
anthony9cb63cc2012-04-25 06:10:49 +0000820 As the minimum ellipse radius of support*1.0 the EWA algorithm
821 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
822 This means that even 'No Blur' will be still a little blurry!
anthonyd2923912012-04-23 13:06:53 +0000823
anthony9cb63cc2012-04-25 06:10:49 +0000824 The solution (as well as the problem of preventing any user
825 expert filter settings, is to set our own user settings, then
826 restore them afterwards.
cristy4c08aed2011-07-01 19:47:50 +0000827 */
cristy8a11cb12011-10-19 23:53:34 +0000828 resample_filter=AcquireResampleFilter(image,exception);
anthonyd2923912012-04-23 13:06:53 +0000829 SetResampleFilter(resample_filter,GaussianFilter);
anthony9cb63cc2012-04-25 06:10:49 +0000830
831 /* do the variable blurring of each pixel in image */
832 GetPixelInfo(image,&pixel);
cristydb070952012-04-20 14:33:00 +0000833 composite_view=AcquireVirtualCacheView(composite_image,exception);
834 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000835 for (y=0; y < (ssize_t) composite_image->rows; y++)
836 {
837 MagickBooleanType
838 sync;
839
840 register const Quantum
841 *restrict p;
842
843 register Quantum
844 *restrict q;
845
846 register ssize_t
847 x;
848
849 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
850 continue;
851 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
852 1,exception);
853 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +0000854 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000855 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
856 break;
857 for (x=0; x < (ssize_t) composite_image->columns; x++)
858 {
859 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
860 {
cristyed231572011-07-14 02:18:59 +0000861 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000862 continue;
863 }
864 if (fabs(angle_range) > MagickEpsilon)
865 {
cristya19f1d72012-08-07 18:24:38 +0000866 double
cristy4c08aed2011-07-01 19:47:50 +0000867 angle;
868
869 angle=angle_start+angle_range*QuantumScale*
870 GetPixelBlue(composite_image,p);
871 blur.x1=width*cos(angle);
872 blur.x2=width*sin(angle);
873 blur.y1=(-height*sin(angle));
874 blur.y2=height*cos(angle);
875 }
anthonyd2923912012-04-23 13:06:53 +0000876#if 0
877 if ( x == 10 && y == 60 ) {
878 fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",
879 blur.x1, blur.x2, blur.y1, blur.y2);
880 fprintf(stderr, "scaled by=%lf,%lf\n",
881 QuantumScale*GetPixelRed(p), QuantumScale*GetPixelGreen(p));
882#endif
883 ScaleResampleFilter(resample_filter,
884 blur.x1*QuantumScale*GetPixelRed(composite_image,p),
885 blur.y1*QuantumScale*GetPixelGreen(composite_image,p),
886 blur.x2*QuantumScale*GetPixelRed(composite_image,p),
887 blur.y2*QuantumScale*GetPixelGreen(composite_image,p) );
cristy4c08aed2011-07-01 19:47:50 +0000888 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
cristydb070952012-04-20 14:33:00 +0000889 (double) y_offset+y,&pixel,exception);
cristy803640d2011-11-17 02:11:32 +0000890 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +0000891 p+=GetPixelChannels(composite_image);
892 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +0000893 }
894 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
895 if (sync == MagickFalse)
896 break;
897 }
898 resample_filter=DestroyResampleFilter(resample_filter);
899 composite_view=DestroyCacheView(composite_view);
900 destination_view=DestroyCacheView(destination_view);
cristyf661c4d2012-07-28 12:57:17 +0000901 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000902 composite_image=destination_image;
903 break;
904 }
905 case DisplaceCompositeOp:
906 case DistortCompositeOp:
907 {
908 CacheView
909 *composite_view,
910 *destination_view,
911 *image_view;
912
cristyfeb3e962012-03-29 17:25:55 +0000913 const char
914 *value;
915
cristy4c08aed2011-07-01 19:47:50 +0000916 PixelInfo
917 pixel;
918
cristya19f1d72012-08-07 18:24:38 +0000919 double
cristy4c08aed2011-07-01 19:47:50 +0000920 horizontal_scale,
921 vertical_scale;
922
923 PointInfo
924 center,
925 offset;
926
927 /*
928 Displace/Distort based on overlay gradient map:
929 X = red_channel; Y = green_channel;
930 compose:args = x_scale[,y_scale[,center.x,center.y]]
931 */
932 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000933 exception);
cristy4c08aed2011-07-01 19:47:50 +0000934 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000935 {
936 composite_image=DestroyImage(composite_image);
937 return(MagickFalse);
938 }
cristy4c08aed2011-07-01 19:47:50 +0000939 SetGeometryInfo(&geometry_info);
940 flags=NoValue;
941 value=GetImageArtifact(composite_image,"compose:args");
942 if (value != (char *) NULL)
943 flags=ParseGeometry(value,&geometry_info);
944 if ((flags & (WidthValue|HeightValue)) == 0 )
945 {
946 if ((flags & AspectValue) == 0)
947 {
cristya19f1d72012-08-07 18:24:38 +0000948 horizontal_scale=(double) (composite_image->columns-1.0)/
cristy4c08aed2011-07-01 19:47:50 +0000949 2.0;
cristya19f1d72012-08-07 18:24:38 +0000950 vertical_scale=(double) (composite_image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000951 }
952 else
953 {
cristya19f1d72012-08-07 18:24:38 +0000954 horizontal_scale=(double) (image->columns-1.0)/2.0;
955 vertical_scale=(double) (image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000956 }
957 }
958 else
959 {
960 horizontal_scale=geometry_info.rho;
961 vertical_scale=geometry_info.sigma;
962 if ((flags & PercentValue) != 0)
963 {
964 if ((flags & AspectValue) == 0)
965 {
966 horizontal_scale*=(composite_image->columns-1.0)/200.0;
967 vertical_scale*=(composite_image->rows-1.0)/200.0;
968 }
969 else
970 {
971 horizontal_scale*=(image->columns-1.0)/200.0;
972 vertical_scale*=(image->rows-1.0)/200.0;
973 }
974 }
975 if ((flags & HeightValue) == 0)
976 vertical_scale=horizontal_scale;
977 }
978 /*
979 Determine fixed center point for absolute distortion map
980 Absolute distort ==
981 Displace offset relative to a fixed absolute point
982 Select that point according to +X+Y user inputs.
983 default = center of overlay image
984 arg flag '!' = locations/percentage relative to background image
985 */
cristya19f1d72012-08-07 18:24:38 +0000986 center.x=(double) x_offset;
987 center.y=(double) y_offset;
cristy4c08aed2011-07-01 19:47:50 +0000988 if (compose == DistortCompositeOp)
989 {
990 if ((flags & XValue) == 0)
991 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +0000992 center.x=(double) x_offset+(composite_image->columns-1)/
cristy4c08aed2011-07-01 19:47:50 +0000993 2.0;
994 else
cristya19f1d72012-08-07 18:24:38 +0000995 center.x=((double) image->columns-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000996 else
997 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +0000998 center.x=(double) x_offset+geometry_info.xi;
cristy4c08aed2011-07-01 19:47:50 +0000999 else
1000 center.x=geometry_info.xi;
1001 if ((flags & YValue) == 0)
1002 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +00001003 center.y=(double) y_offset+(composite_image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +00001004 else
cristya19f1d72012-08-07 18:24:38 +00001005 center.y=((double) image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +00001006 else
1007 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +00001008 center.y=(double) y_offset+geometry_info.psi;
cristy4c08aed2011-07-01 19:47:50 +00001009 else
1010 center.y=geometry_info.psi;
1011 }
1012 /*
1013 Shift the pixel offset point as defined by the provided,
1014 displacement/distortion map. -- Like a lens...
1015 */
cristye10859a2011-12-18 22:28:59 +00001016 GetPixelInfo(image,&pixel);
cristydb070952012-04-20 14:33:00 +00001017 image_view=AcquireVirtualCacheView(image,exception);
1018 composite_view=AcquireVirtualCacheView(composite_image,exception);
1019 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001020 for (y=0; y < (ssize_t) composite_image->rows; y++)
1021 {
1022 MagickBooleanType
1023 sync;
1024
1025 register const Quantum
1026 *restrict p;
1027
1028 register Quantum
1029 *restrict q;
1030
1031 register ssize_t
1032 x;
1033
1034 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1035 continue;
1036 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1037 1,exception);
1038 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +00001039 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001040 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1041 break;
1042 for (x=0; x < (ssize_t) composite_image->columns; x++)
1043 {
1044 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1045 {
cristyed231572011-07-14 02:18:59 +00001046 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001047 continue;
1048 }
1049 /*
1050 Displace the offset.
1051 */
1052 offset.x=(horizontal_scale*(GetPixelRed(composite_image,p)-
cristya19f1d72012-08-07 18:24:38 +00001053 (((double) QuantumRange+1.0)/2.0)))/(((double)
cristy4c08aed2011-07-01 19:47:50 +00001054 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
1055 x : 0);
1056 offset.y=(vertical_scale*(GetPixelGreen(composite_image,p)-
cristya19f1d72012-08-07 18:24:38 +00001057 (((double) QuantumRange+1.0)/2.0)))/(((double)
cristy4c08aed2011-07-01 19:47:50 +00001058 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
1059 y : 0);
1060 (void) InterpolatePixelInfo(image,image_view,
1061 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
1062 &pixel,exception);
1063 /*
1064 Mask with the 'invalid pixel mask' in alpha channel.
1065 */
cristya19f1d72012-08-07 18:24:38 +00001066 pixel.alpha=(double) QuantumRange*(1.0-(1.0-QuantumScale*
cristy99abff32011-12-24 20:45:16 +00001067 pixel.alpha)*(1.0-QuantumScale*GetPixelAlpha(composite_image,p)));
cristy803640d2011-11-17 02:11:32 +00001068 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00001069 p+=GetPixelChannels(composite_image);
1070 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00001071 }
1072 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
1073 if (sync == MagickFalse)
1074 break;
1075 }
1076 destination_view=DestroyCacheView(destination_view);
1077 composite_view=DestroyCacheView(composite_view);
1078 image_view=DestroyCacheView(image_view);
cristyf661c4d2012-07-28 12:57:17 +00001079 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001080 composite_image=destination_image;
1081 break;
1082 }
1083 case DissolveCompositeOp:
1084 {
cristyfeb3e962012-03-29 17:25:55 +00001085 const char
1086 *value;
1087
cristy4c08aed2011-07-01 19:47:50 +00001088 /*
1089 Geometry arguments to dissolve factors.
1090 */
1091 value=GetImageArtifact(composite_image,"compose:args");
1092 if (value != (char *) NULL)
1093 {
1094 flags=ParseGeometry(value,&geometry_info);
1095 source_dissolve=geometry_info.rho/100.0;
1096 destination_dissolve=1.0;
1097 if ((source_dissolve-MagickEpsilon) < 0.0)
1098 source_dissolve=0.0;
1099 if ((source_dissolve+MagickEpsilon) > 1.0)
1100 {
1101 destination_dissolve=2.0-source_dissolve;
1102 source_dissolve=1.0;
1103 }
1104 if ((flags & SigmaValue) != 0)
1105 destination_dissolve=geometry_info.sigma/100.0;
1106 if ((destination_dissolve-MagickEpsilon) < 0.0)
1107 destination_dissolve=0.0;
anthony9cb63cc2012-04-25 06:10:49 +00001108 /* posible speed up? -- from IMv6 update
1109 clip_to_self=MagickFalse;
1110 if ((destination_dissolve+MagickEpsilon) > 1.0 )
1111 {
1112 destination_dissolve=1.0;
1113 clip_to_self=MagickTrue;
1114 }
1115 */
cristy4c08aed2011-07-01 19:47:50 +00001116 }
1117 break;
1118 }
1119 case BlendCompositeOp:
1120 {
cristyfeb3e962012-03-29 17:25:55 +00001121 const char
1122 *value;
1123
cristy4c08aed2011-07-01 19:47:50 +00001124 value=GetImageArtifact(composite_image,"compose:args");
1125 if (value != (char *) NULL)
1126 {
1127 flags=ParseGeometry(value,&geometry_info);
1128 source_dissolve=geometry_info.rho/100.0;
1129 destination_dissolve=1.0-source_dissolve;
1130 if ((flags & SigmaValue) != 0)
1131 destination_dissolve=geometry_info.sigma/100.0;
cristy4c08aed2011-07-01 19:47:50 +00001132 }
1133 break;
1134 }
1135 case MathematicsCompositeOp:
1136 {
cristyfeb3e962012-03-29 17:25:55 +00001137 const char
1138 *value;
1139
cristy4c08aed2011-07-01 19:47:50 +00001140 /*
1141 Just collect the values from "compose:args", setting.
1142 Unused values are set to zero automagically.
1143
1144 Arguments are normally a comma separated list, so this probably should
1145 be changed to some 'general comma list' parser, (with a minimum
1146 number of values)
1147 */
1148 SetGeometryInfo(&geometry_info);
1149 value=GetImageArtifact(composite_image,"compose:args");
1150 if (value != (char *) NULL)
1151 (void) ParseGeometry(value,&geometry_info);
1152 break;
1153 }
1154 case ModulateCompositeOp:
1155 {
cristyfeb3e962012-03-29 17:25:55 +00001156 const char
1157 *value;
1158
cristy4c08aed2011-07-01 19:47:50 +00001159 /*
cristy7133e642012-08-14 11:04:11 +00001160 Determine the luma and chroma scale.
cristy4c08aed2011-07-01 19:47:50 +00001161 */
1162 value=GetImageArtifact(composite_image,"compose:args");
1163 if (value != (char *) NULL)
1164 {
1165 flags=ParseGeometry(value,&geometry_info);
cristy7133e642012-08-14 11:04:11 +00001166 percent_luma=geometry_info.rho;
cristy4c08aed2011-07-01 19:47:50 +00001167 if ((flags & SigmaValue) != 0)
cristy7133e642012-08-14 11:04:11 +00001168 percent_chroma=geometry_info.sigma;
cristy4c08aed2011-07-01 19:47:50 +00001169 }
1170 break;
1171 }
1172 case ThresholdCompositeOp:
1173 {
cristyfeb3e962012-03-29 17:25:55 +00001174 const char
1175 *value;
1176
cristy4c08aed2011-07-01 19:47:50 +00001177 /*
1178 Determine the amount and threshold.
1179 */
1180 value=GetImageArtifact(composite_image,"compose:args");
1181 if (value != (char *) NULL)
1182 {
1183 flags=ParseGeometry(value,&geometry_info);
1184 amount=geometry_info.rho;
1185 threshold=geometry_info.sigma;
1186 if ((flags & SigmaValue) == 0)
1187 threshold=0.05f;
1188 }
1189 threshold*=QuantumRange;
1190 break;
1191 }
1192 default:
1193 break;
1194 }
cristy4c08aed2011-07-01 19:47:50 +00001195 /*
1196 Composite image.
1197 */
1198 status=MagickTrue;
1199 progress=0;
cristya19f1d72012-08-07 18:24:38 +00001200 midpoint=((double) QuantumRange+1.0)/2;
cristydb070952012-04-20 14:33:00 +00001201 composite_view=AcquireVirtualCacheView(composite_image,exception);
1202 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001203#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001204 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001205 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00001206#endif
1207 for (y=0; y < (ssize_t) image->rows; y++)
1208 {
1209 const Quantum
1210 *pixels;
1211
1212 double
cristye4a40472011-12-22 02:56:19 +00001213 blue,
cristy7133e642012-08-14 11:04:11 +00001214 luma,
cristye4a40472011-12-22 02:56:19 +00001215 green,
cristy4c08aed2011-07-01 19:47:50 +00001216 hue,
cristye4a40472011-12-22 02:56:19 +00001217 red,
cristy7133e642012-08-14 11:04:11 +00001218 chroma;
cristy4c08aed2011-07-01 19:47:50 +00001219
cristyddeeea22012-04-12 01:33:09 +00001220 PixelInfo
1221 destination_pixel,
1222 source_pixel;
1223
cristy4c08aed2011-07-01 19:47:50 +00001224 register const Quantum
1225 *restrict p;
1226
1227 register Quantum
1228 *restrict q;
1229
1230 register ssize_t
1231 x;
1232
1233 if (status == MagickFalse)
1234 continue;
cristyfeb3e962012-03-29 17:25:55 +00001235 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001236 {
1237 if (y < y_offset)
1238 continue;
1239 if ((y-y_offset) >= (ssize_t) composite_image->rows)
1240 continue;
1241 }
1242 /*
1243 If pixels is NULL, y is outside overlay region.
1244 */
1245 pixels=(Quantum *) NULL;
1246 p=(Quantum *) NULL;
1247 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
1248 {
1249 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
1250 composite_image->columns,1,exception);
1251 if (p == (const Quantum *) NULL)
1252 {
1253 status=MagickFalse;
1254 continue;
1255 }
1256 pixels=p;
1257 if (x_offset < 0)
cristyed231572011-07-14 02:18:59 +00001258 p-=x_offset*GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001259 }
1260 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001261 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00001262 {
1263 status=MagickFalse;
1264 continue;
1265 }
cristy4c08aed2011-07-01 19:47:50 +00001266 hue=0.0;
cristy7133e642012-08-14 11:04:11 +00001267 chroma=0.0;
1268 luma=0.0;
cristyddeeea22012-04-12 01:33:09 +00001269 GetPixelInfo(image,&destination_pixel);
1270 GetPixelInfo(composite_image,&source_pixel);
cristy4c08aed2011-07-01 19:47:50 +00001271 for (x=0; x < (ssize_t) image->columns; x++)
1272 {
cristya19f1d72012-08-07 18:24:38 +00001273 double
cristye4a40472011-12-22 02:56:19 +00001274 alpha,
1275 Da,
1276 Dc,
1277 Dca,
1278 gamma,
1279 Sa,
1280 Sc,
1281 Sca;
1282
1283 register ssize_t
1284 i;
1285
cristy564a5692012-01-20 23:56:26 +00001286 size_t
1287 channels;
1288
cristyfeb3e962012-03-29 17:25:55 +00001289 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001290 {
1291 if (x < x_offset)
1292 {
cristyed231572011-07-14 02:18:59 +00001293 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001294 continue;
1295 }
1296 if ((x-x_offset) >= (ssize_t) composite_image->columns)
1297 break;
1298 }
cristye4a40472011-12-22 02:56:19 +00001299 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
1300 ((x-x_offset) >= (ssize_t) composite_image->columns))
1301 {
1302 Quantum
1303 source[MaxPixelChannels];
1304
1305 /*
1306 Virtual composite:
1307 Sc: source color.
1308 Dc: destination color.
1309 */
1310 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
1311 source,exception);
cristy10a6c612012-01-29 21:41:05 +00001312 if (GetPixelMask(image,q) != 0)
1313 {
1314 q+=GetPixelChannels(image);
1315 continue;
1316 }
cristye4a40472011-12-22 02:56:19 +00001317 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1318 {
cristya19f1d72012-08-07 18:24:38 +00001319 double
cristye4a40472011-12-22 02:56:19 +00001320 pixel;
1321
1322 PixelChannel
1323 channel;
1324
1325 PixelTrait
1326 composite_traits,
1327 traits;
1328
cristycf1296e2012-08-26 23:40:49 +00001329 channel=GetPixelChannelChannel(image,i);
1330 traits=GetPixelChannelTraits(image,channel);
1331 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristye4a40472011-12-22 02:56:19 +00001332 if ((traits == UndefinedPixelTrait) ||
1333 (composite_traits == UndefinedPixelTrait))
1334 continue;
1335 switch (compose)
1336 {
cristyc8d63672012-01-11 13:03:13 +00001337 case AlphaCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001338 case ChangeMaskCompositeOp:
1339 case CopyAlphaCompositeOp:
1340 case DstAtopCompositeOp:
1341 case DstInCompositeOp:
1342 case InCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +00001343 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001344 case OutCompositeOp:
1345 case SrcInCompositeOp:
1346 case SrcOutCompositeOp:
1347 {
cristya19f1d72012-08-07 18:24:38 +00001348 pixel=(double) q[i];
cristye4a40472011-12-22 02:56:19 +00001349 if (channel == AlphaPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001350 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001351 break;
1352 }
1353 case ClearCompositeOp:
1354 case CopyCompositeOp:
1355 case ReplaceCompositeOp:
1356 case SrcCompositeOp:
1357 {
1358 if (channel == AlphaPixelChannel)
1359 {
cristya19f1d72012-08-07 18:24:38 +00001360 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001361 break;
1362 }
1363 pixel=0.0;
1364 break;
1365 }
cristy99abff32011-12-24 20:45:16 +00001366 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001367 case DissolveCompositeOp:
1368 {
1369 if (channel == AlphaPixelChannel)
1370 {
1371 pixel=destination_dissolve*GetPixelAlpha(composite_image,
1372 source);
1373 break;
1374 }
cristya19f1d72012-08-07 18:24:38 +00001375 pixel=(double) source[channel];
cristye4a40472011-12-22 02:56:19 +00001376 break;
1377 }
1378 default:
1379 {
cristya19f1d72012-08-07 18:24:38 +00001380 pixel=(double) source[channel];
cristye4a40472011-12-22 02:56:19 +00001381 break;
1382 }
1383 }
1384 q[i]=ClampToQuantum(pixel);
1385 }
1386 q+=GetPixelChannels(image);
1387 continue;
1388 }
1389 /*
1390 Authentic composite:
1391 Sa: normalized source alpha.
1392 Da: normalized destination alpha.
1393 */
1394 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
1395 Da=QuantumScale*GetPixelAlpha(image,q);
cristy4c08aed2011-07-01 19:47:50 +00001396 switch (compose)
1397 {
cristye4a40472011-12-22 02:56:19 +00001398 case BumpmapCompositeOp:
1399 {
1400 alpha=GetPixelIntensity(composite_image,p)*Sa;
1401 break;
1402 }
cristycdc168f2011-12-21 15:24:39 +00001403 case ColorBurnCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001404 case ColorDodgeCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001405 case DifferenceCompositeOp:
1406 case DivideDstCompositeOp:
1407 case DivideSrcCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001408 case ExclusionCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001409 case HardLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001410 case LinearBurnCompositeOp:
1411 case LinearDodgeCompositeOp:
1412 case LinearLightCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001413 case MathematicsCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001414 case MinusDstCompositeOp:
1415 case MinusSrcCompositeOp:
1416 case ModulusAddCompositeOp:
1417 case ModulusSubtractCompositeOp:
1418 case MultiplyCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001419 case OverlayCompositeOp:
1420 case PegtopLightCompositeOp:
1421 case PinLightCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001422 case ScreenCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001423 case SoftLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001424 case VividLightCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001425 {
1426 alpha=RoundToUnity(Sa+Da-Sa*Da);
1427 break;
1428 }
1429 case DarkenCompositeOp:
1430 case DstAtopCompositeOp:
1431 case DstInCompositeOp:
1432 case InCompositeOp:
1433 case LightenCompositeOp:
1434 case SrcInCompositeOp:
1435 {
1436 alpha=Sa*Da;
1437 break;
1438 }
1439 case DissolveCompositeOp:
1440 {
1441 alpha=source_dissolve*Sa*(-destination_dissolve*Da)+source_dissolve*
1442 Sa+destination_dissolve*Da;
1443 break;
1444 }
1445 case DstOverCompositeOp:
1446 {
1447 alpha=Da*(-Sa)+Da+Sa;
1448 break;
1449 }
1450 case DstOutCompositeOp:
1451 {
1452 alpha=Da*(1.0-Sa);
1453 break;
1454 }
1455 case OutCompositeOp:
1456 case SrcOutCompositeOp:
1457 {
1458 alpha=Sa*(1.0-Da);
1459 break;
1460 }
1461 case OverCompositeOp:
1462 case SrcOverCompositeOp:
1463 {
1464 alpha=Sa*(-Da)+Sa+Da;
1465 break;
1466 }
cristy99abff32011-12-24 20:45:16 +00001467 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001468 case PlusCompositeOp:
1469 {
1470 alpha=RoundToUnity(Sa+Da);
1471 break;
1472 }
cristy4c08aed2011-07-01 19:47:50 +00001473 case XorCompositeOp:
1474 {
cristye4a40472011-12-22 02:56:19 +00001475 alpha=Sa+Da-2.0*Sa*Da;
cristy4c08aed2011-07-01 19:47:50 +00001476 break;
1477 }
1478 default:
cristye4a40472011-12-22 02:56:19 +00001479 {
1480 alpha=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001481 break;
cristye4a40472011-12-22 02:56:19 +00001482 }
cristy4c08aed2011-07-01 19:47:50 +00001483 }
cristy10a6c612012-01-29 21:41:05 +00001484 if (GetPixelMask(image,p) != 0)
1485 {
1486 p+=GetPixelChannels(composite_image);
1487 q+=GetPixelChannels(image);
1488 continue;
1489 }
cristy9d3d2792012-04-14 15:15:19 +00001490 switch (compose)
1491 {
1492 case ColorizeCompositeOp:
1493 case HueCompositeOp:
1494 case LuminizeCompositeOp:
1495 case ModulateCompositeOp:
1496 case SaturateCompositeOp:
1497 {
1498 GetPixelInfoPixel(composite_image,p,&source_pixel);
1499 GetPixelInfoPixel(image,q,&destination_pixel);
1500 break;
1501 }
1502 default:
1503 break;
1504 }
cristye4a40472011-12-22 02:56:19 +00001505 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1506 {
cristy564a5692012-01-20 23:56:26 +00001507 double
1508 sans;
1509
cristya19f1d72012-08-07 18:24:38 +00001510 double
cristye4a40472011-12-22 02:56:19 +00001511 pixel;
cristye10859a2011-12-18 22:28:59 +00001512
cristye4a40472011-12-22 02:56:19 +00001513 PixelChannel
1514 channel;
cristye10859a2011-12-18 22:28:59 +00001515
cristye4a40472011-12-22 02:56:19 +00001516 PixelTrait
1517 composite_traits,
1518 traits;
1519
cristycf1296e2012-08-26 23:40:49 +00001520 channel=GetPixelChannelChannel(image,i);
1521 traits=GetPixelChannelTraits(image,channel);
1522 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristy0cd1f212012-01-05 15:45:59 +00001523 if (traits == UndefinedPixelTrait)
1524 continue;
cristya7b07912012-01-11 20:01:32 +00001525 if ((compose != IntensityCompositeOp) &&
cristye4a40472011-12-22 02:56:19 +00001526 (composite_traits == UndefinedPixelTrait))
1527 continue;
1528 /*
1529 Sc: source color.
cristye4a40472011-12-22 02:56:19 +00001530 Dc: destination color.
cristye4a40472011-12-22 02:56:19 +00001531 */
cristya19f1d72012-08-07 18:24:38 +00001532 Sc=(double) GetPixelChannel(composite_image,channel,p);
1533 Dc=(double) q[i];
cristye4a40472011-12-22 02:56:19 +00001534 if ((traits & CopyPixelTrait) != 0)
cristye10859a2011-12-18 22:28:59 +00001535 {
cristye4a40472011-12-22 02:56:19 +00001536 if (channel != AlphaPixelChannel)
1537 {
1538 /*
1539 Copy channel.
1540 */
1541 q[i]=ClampToQuantum(Sc);
cristye10859a2011-12-18 22:28:59 +00001542 continue;
cristye10859a2011-12-18 22:28:59 +00001543 }
cristye4a40472011-12-22 02:56:19 +00001544 /*
1545 Set alpha channel.
1546 */
cristye10859a2011-12-18 22:28:59 +00001547 switch (compose)
1548 {
cristyc8d63672012-01-11 13:03:13 +00001549 case AlphaCompositeOp:
1550 {
cristya7b07912012-01-11 20:01:32 +00001551 pixel=QuantumRange*Sa;
cristyc8d63672012-01-11 13:03:13 +00001552 break;
1553 }
cristye4a40472011-12-22 02:56:19 +00001554 case AtopCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001555 case CopyBlackCompositeOp:
1556 case CopyBlueCompositeOp:
1557 case CopyCyanCompositeOp:
1558 case CopyGreenCompositeOp:
1559 case CopyMagentaCompositeOp:
1560 case CopyRedCompositeOp:
1561 case CopyYellowCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001562 case SrcAtopCompositeOp:
1563 case DstCompositeOp:
1564 case NoCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001565 {
cristye4a40472011-12-22 02:56:19 +00001566 pixel=QuantumRange*Da;
cristye10859a2011-12-18 22:28:59 +00001567 break;
1568 }
cristye10859a2011-12-18 22:28:59 +00001569 case ChangeMaskCompositeOp:
1570 {
cristye4a40472011-12-22 02:56:19 +00001571 MagickBooleanType
1572 equivalent;
1573
cristya19f1d72012-08-07 18:24:38 +00001574 if (Da > ((double) QuantumRange/2.0))
cristy99abff32011-12-24 20:45:16 +00001575 {
cristya19f1d72012-08-07 18:24:38 +00001576 pixel=(double) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001577 break;
1578 }
cristye4a40472011-12-22 02:56:19 +00001579 equivalent=IsFuzzyEquivalencePixel(composite_image,p,image,q);
cristy99abff32011-12-24 20:45:16 +00001580 if (equivalent != MagickFalse)
cristye4a40472011-12-22 02:56:19 +00001581 {
cristya19f1d72012-08-07 18:24:38 +00001582 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001583 break;
1584 }
cristya19f1d72012-08-07 18:24:38 +00001585 pixel=(double) OpaqueAlpha;
cristye4a40472011-12-22 02:56:19 +00001586 break;
1587 }
cristy99abff32011-12-24 20:45:16 +00001588 case ClearCompositeOp:
1589 {
cristya19f1d72012-08-07 18:24:38 +00001590 pixel=(double) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001591 break;
1592 }
1593 case ColorizeCompositeOp:
1594 case HueCompositeOp:
1595 case LuminizeCompositeOp:
1596 case SaturateCompositeOp:
1597 {
1598 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1599 {
1600 pixel=QuantumRange*Da;
1601 break;
1602 }
1603 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
1604 {
1605 pixel=QuantumRange*Sa;
1606 break;
1607 }
1608 if (Sa < Da)
1609 {
1610 pixel=QuantumRange*Da;
1611 break;
1612 }
1613 pixel=QuantumRange*Sa;
1614 break;
1615 }
cristy99abff32011-12-24 20:45:16 +00001616 case CopyAlphaCompositeOp:
cristy24d5d722012-05-17 12:27:27 +00001617 {
1618 pixel=QuantumRange*Sa;
cristy8a46d822012-08-28 23:32:39 +00001619 if (composite_image->alpha_trait != BlendPixelTrait)
cristy24d5d722012-05-17 12:27:27 +00001620 pixel=GetPixelIntensity(composite_image,p);
1621 break;
1622 }
1623 case CopyCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001624 case DisplaceCompositeOp:
1625 case DistortCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001626 case DstAtopCompositeOp:
1627 case ReplaceCompositeOp:
1628 case SrcCompositeOp:
1629 {
1630 pixel=QuantumRange*Sa;
1631 break;
1632 }
1633 case DarkenIntensityCompositeOp:
1634 {
cristy99abff32011-12-24 20:45:16 +00001635 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1636 (1.0-Da)*GetPixelIntensity(image,q) ? Sa : Da;
cristye4a40472011-12-22 02:56:19 +00001637 break;
1638 }
cristy98621462011-12-31 22:31:11 +00001639 case IntensityCompositeOp:
1640 {
cristyf13c5942012-08-08 23:50:11 +00001641 pixel=GetPixelIntensity(composite_image,p);
cristy98621462011-12-31 22:31:11 +00001642 break;
1643 }
cristye4a40472011-12-22 02:56:19 +00001644 case LightenIntensityCompositeOp:
1645 {
1646 pixel=Sa*GetPixelIntensity(composite_image,p) >
1647 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye10859a2011-12-18 22:28:59 +00001648 break;
1649 }
cristy99abff32011-12-24 20:45:16 +00001650 case ModulateCompositeOp:
1651 {
1652 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1653 {
1654 pixel=QuantumRange*Da;
1655 break;
1656 }
1657 pixel=QuantumRange*Da;
1658 break;
1659 }
cristye10859a2011-12-18 22:28:59 +00001660 default:
1661 {
cristye4a40472011-12-22 02:56:19 +00001662 pixel=QuantumRange*alpha;
cristye10859a2011-12-18 22:28:59 +00001663 break;
1664 }
1665 }
cristye4a40472011-12-22 02:56:19 +00001666 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00001667 continue;
1668 }
1669 /*
cristy99abff32011-12-24 20:45:16 +00001670 Porter-Duff compositions:
1671 Sca: source normalized color multiplied by alpha.
1672 Dca: normalized destination color multiplied by alpha.
cristye10859a2011-12-18 22:28:59 +00001673 */
cristy99abff32011-12-24 20:45:16 +00001674 Sca=QuantumScale*Sa*Sc;
1675 Dca=QuantumScale*Da*Dc;
cristye10859a2011-12-18 22:28:59 +00001676 switch (compose)
1677 {
cristye10859a2011-12-18 22:28:59 +00001678 case DarkenCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001679 case LightenCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001680 case ModulusSubtractCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001681 {
cristy99abff32011-12-24 20:45:16 +00001682 gamma=1.0-alpha;
cristye10859a2011-12-18 22:28:59 +00001683 break;
1684 }
1685 default:
1686 break;
1687 }
cristyc58380a2012-06-03 15:12:30 +00001688 gamma=MagickEpsilonReciprocal(alpha);
cristyd197cbb2012-01-13 02:14:12 +00001689 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001690 switch (compose)
1691 {
cristya7b07912012-01-11 20:01:32 +00001692 case AlphaCompositeOp:
1693 {
1694 pixel=QuantumRange*Sa;
1695 break;
1696 }
cristye4a40472011-12-22 02:56:19 +00001697 case AtopCompositeOp:
1698 case SrcAtopCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001699 {
cristye4a40472011-12-22 02:56:19 +00001700 pixel=Sc*Sa+Dc*(1.0-Sa);
1701 break;
cristye10859a2011-12-18 22:28:59 +00001702 }
cristye4a40472011-12-22 02:56:19 +00001703 case BlendCompositeOp:
1704 {
1705 pixel=gamma*(source_dissolve*Sa*Sc+destination_dissolve*Da*Dc);
1706 break;
1707 }
1708 case BlurCompositeOp:
1709 case DisplaceCompositeOp:
1710 case DistortCompositeOp:
1711 case CopyCompositeOp:
1712 case ReplaceCompositeOp:
1713 case SrcCompositeOp:
1714 {
1715 pixel=Sc;
1716 break;
1717 }
1718 case BumpmapCompositeOp:
1719 {
cristy99abff32011-12-24 20:45:16 +00001720 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001721 {
1722 pixel=Dc;
1723 break;
1724 }
1725 pixel=QuantumScale*GetPixelIntensity(composite_image,p)*Dc;
1726 break;
1727 }
cristy99abff32011-12-24 20:45:16 +00001728 case ChangeMaskCompositeOp:
1729 {
1730 pixel=Dc;
1731 break;
1732 }
1733 case ClearCompositeOp:
1734 {
1735 pixel=0.0;
1736 break;
1737 }
cristye4a40472011-12-22 02:56:19 +00001738 case ColorBurnCompositeOp:
1739 {
1740 /*
1741 Refer to the March 2009 SVG specification.
1742 */
1743 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
1744 {
cristy99abff32011-12-24 20:45:16 +00001745 pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001746 break;
1747 }
1748 if (Sca < MagickEpsilon)
1749 {
cristy99abff32011-12-24 20:45:16 +00001750 pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001751 break;
1752 }
cristy99abff32011-12-24 20:45:16 +00001753 pixel=QuantumRange*gamma*(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+
1754 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001755 break;
1756 }
1757 case ColorDodgeCompositeOp:
1758 {
1759 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1760 {
cristy99abff32011-12-24 20:45:16 +00001761 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001762 break;
1763 }
1764 if (fabs(Sca-Sa) < MagickEpsilon)
1765 {
cristy99abff32011-12-24 20:45:16 +00001766 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001767 break;
1768 }
cristy99abff32011-12-24 20:45:16 +00001769 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001770 (1.0-Sa));
1771 break;
1772 }
1773 case ColorizeCompositeOp:
1774 {
cristy99abff32011-12-24 20:45:16 +00001775 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001776 {
1777 pixel=Dc;
1778 break;
1779 }
cristy99abff32011-12-24 20:45:16 +00001780 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001781 {
1782 pixel=Sc;
1783 break;
1784 }
cristy7133e642012-08-14 11:04:11 +00001785 CompositeHCL(destination_pixel.red,destination_pixel.green,
1786 destination_pixel.blue,&sans,&sans,&luma);
1787 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1788 &hue,&chroma,&sans);
1789 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001790 switch (channel)
1791 {
1792 case RedPixelChannel: pixel=red; break;
1793 case GreenPixelChannel: pixel=green; break;
1794 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001795 default: pixel=Dc; break;
1796 }
1797 break;
1798 }
cristye4a40472011-12-22 02:56:19 +00001799 case CopyAlphaCompositeOp:
cristy98621462011-12-31 22:31:11 +00001800 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001801 {
cristy24d5d722012-05-17 12:27:27 +00001802 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001803 break;
1804 }
1805 case CopyBlackCompositeOp:
1806 {
cristyd197cbb2012-01-13 02:14:12 +00001807 if (channel == BlackPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001808 pixel=(double) GetPixelBlack(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001809 break;
1810 }
1811 case CopyBlueCompositeOp:
1812 case CopyYellowCompositeOp:
1813 {
cristyd197cbb2012-01-13 02:14:12 +00001814 if (channel == BluePixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001815 pixel=(double) GetPixelBlue(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001816 break;
1817 }
1818 case CopyGreenCompositeOp:
1819 case CopyMagentaCompositeOp:
1820 {
cristyd197cbb2012-01-13 02:14:12 +00001821 if (channel == GreenPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001822 pixel=(double) GetPixelGreen(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001823 break;
1824 }
1825 case CopyRedCompositeOp:
1826 case CopyCyanCompositeOp:
1827 {
cristyd197cbb2012-01-13 02:14:12 +00001828 if (channel == RedPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001829 pixel=(double) GetPixelRed(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001830 break;
1831 }
cristy99abff32011-12-24 20:45:16 +00001832 case DarkenCompositeOp:
1833 {
1834 /*
1835 Darken is equivalent to a 'Minimum' method
1836 OR a greyscale version of a binary 'Or'
1837 OR the 'Intersection' of pixel sets.
1838 */
1839 if (Sc < Dc)
1840 {
1841 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
1842 break;
1843 }
1844 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1845 break;
1846 }
cristye4a40472011-12-22 02:56:19 +00001847 case DarkenIntensityCompositeOp:
1848 {
cristy99abff32011-12-24 20:45:16 +00001849 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1850 (1.0-Da)*GetPixelIntensity(image,q) ? Sc : Dc;
cristye4a40472011-12-22 02:56:19 +00001851 break;
1852 }
1853 case DifferenceCompositeOp:
1854 {
1855 pixel=gamma*(Sa*Sc+Da*Dc-Sa*Da*2.0*MagickMin(Sc,Dc));
1856 break;
1857 }
1858 case DissolveCompositeOp:
1859 {
1860 pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
1861 destination_dissolve*Da*Dc+destination_dissolve*Da*Dc);
1862 break;
1863 }
1864 case DivideDstCompositeOp:
1865 {
1866 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1867 {
cristy99abff32011-12-24 20:45:16 +00001868 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001869 break;
1870 }
1871 if (fabs(Dca) < MagickEpsilon)
1872 {
cristy99abff32011-12-24 20:45:16 +00001873 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001874 break;
1875 }
cristy99abff32011-12-24 20:45:16 +00001876 pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001877 break;
1878 }
1879 case DivideSrcCompositeOp:
1880 {
1881 if ((fabs(Dca) < MagickEpsilon) && (fabs(Sca) < MagickEpsilon))
1882 {
cristy99abff32011-12-24 20:45:16 +00001883 pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001884 break;
1885 }
1886 if (fabs(Sca) < MagickEpsilon)
1887 {
cristy99abff32011-12-24 20:45:16 +00001888 pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001889 break;
1890 }
cristy99abff32011-12-24 20:45:16 +00001891 pixel=QuantumRange*gamma*(Dca*Sa*Sa/Sca+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001892 break;
1893 }
1894 case DstAtopCompositeOp:
1895 {
1896 pixel=Dc*Da+Sc*(1.0-Da);
1897 break;
1898 }
1899 case DstCompositeOp:
1900 case NoCompositeOp:
1901 {
1902 pixel=Dc;
1903 break;
1904 }
1905 case DstInCompositeOp:
1906 {
1907 pixel=gamma*(Sa*Dc*Sa);
1908 break;
1909 }
1910 case DstOutCompositeOp:
1911 {
1912 pixel=gamma*(Da*Dc*(1.0-Sa));
1913 break;
1914 }
1915 case DstOverCompositeOp:
1916 {
1917 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1918 break;
1919 }
1920 case ExclusionCompositeOp:
1921 {
cristy99abff32011-12-24 20:45:16 +00001922 pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
1923 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001924 break;
1925 }
1926 case HardLightCompositeOp:
1927 {
1928 if ((2.0*Sca) < Sa)
1929 {
cristy99abff32011-12-24 20:45:16 +00001930 pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001931 (1.0-Sa));
1932 break;
1933 }
cristy99abff32011-12-24 20:45:16 +00001934 pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
cristye4a40472011-12-22 02:56:19 +00001935 Dca*(1.0-Sa));
1936 break;
1937 }
1938 case HueCompositeOp:
1939 {
cristy99abff32011-12-24 20:45:16 +00001940 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001941 {
1942 pixel=Dc;
1943 break;
1944 }
cristy99abff32011-12-24 20:45:16 +00001945 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001946 {
1947 pixel=Sc;
1948 break;
1949 }
cristy7133e642012-08-14 11:04:11 +00001950 CompositeHCL(destination_pixel.red,destination_pixel.green,
1951 destination_pixel.blue,&hue,&chroma,&luma);
1952 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
cristye4a40472011-12-22 02:56:19 +00001953 &hue,&sans,&sans);
cristy7133e642012-08-14 11:04:11 +00001954 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001955 switch (channel)
1956 {
1957 case RedPixelChannel: pixel=red; break;
1958 case GreenPixelChannel: pixel=green; break;
1959 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001960 default: pixel=Dc; break;
1961 }
1962 break;
1963 }
1964 case InCompositeOp:
1965 case SrcInCompositeOp:
1966 {
1967 pixel=gamma*(Da*Sc*Da);
1968 break;
1969 }
cristy99abff32011-12-24 20:45:16 +00001970 case LinearBurnCompositeOp:
1971 {
1972 /*
1973 LinearBurn: as defined by Abode Photoshop, according to
1974 http://www.simplefilter.de/en/basics/mixmods.html is:
1975
1976 f(Sc,Dc) = Sc + Dc - 1
1977 */
1978 pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
1979 break;
1980 }
1981 case LinearDodgeCompositeOp:
1982 {
1983 pixel=QuantumRange*gamma*(Sa*Sc+Da*Dc);
1984 break;
1985 }
1986 case LinearLightCompositeOp:
1987 {
1988 /*
1989 LinearLight: as defined by Abode Photoshop, according to
1990 http://www.simplefilter.de/en/basics/mixmods.html is:
1991
1992 f(Sc,Dc) = Dc + 2*Sc - 1
1993 */
1994 pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
1995 break;
1996 }
1997 case LightenCompositeOp:
1998 {
1999 if (Sc > Dc)
2000 {
cristy24d5d722012-05-17 12:27:27 +00002001 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristy99abff32011-12-24 20:45:16 +00002002 break;
2003 }
cristy24d5d722012-05-17 12:27:27 +00002004 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
cristy99abff32011-12-24 20:45:16 +00002005 break;
2006 }
cristye4a40472011-12-22 02:56:19 +00002007 case LightenIntensityCompositeOp:
2008 {
2009 /*
2010 Lighten is equivalent to a 'Maximum' method
2011 OR a greyscale version of a binary 'And'
2012 OR the 'Union' of pixel sets.
2013 */
2014 pixel=Sa*GetPixelIntensity(composite_image,p) >
2015 Da*GetPixelIntensity(image,q) ? Sc : Dc;
2016 break;
2017 }
cristye4a40472011-12-22 02:56:19 +00002018 case LuminizeCompositeOp:
2019 {
cristy99abff32011-12-24 20:45:16 +00002020 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002021 {
2022 pixel=Dc;
2023 break;
2024 }
cristy99abff32011-12-24 20:45:16 +00002025 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002026 {
2027 pixel=Sc;
2028 break;
2029 }
cristy7133e642012-08-14 11:04:11 +00002030 CompositeHCL(destination_pixel.red,destination_pixel.green,
2031 destination_pixel.blue,&hue,&chroma,&luma);
2032 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2033 &sans,&sans,&luma);
2034 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002035 switch (channel)
2036 {
2037 case RedPixelChannel: pixel=red; break;
2038 case GreenPixelChannel: pixel=green; break;
2039 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002040 default: pixel=Dc; break;
2041 }
2042 break;
2043 }
2044 case MathematicsCompositeOp:
2045 {
2046 /*
2047 'Mathematics' a free form user control mathematical composition
2048 is defined as...
2049
2050 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
2051
2052 Where the arguments A,B,C,D are (currently) passed to composite
2053 as a command separated 'geometry' string in "compose:args" image
2054 artifact.
2055
2056 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
2057
2058 Applying the SVG transparency formula (see above), we get...
2059
2060 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
2061
2062 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
2063 Dca*(1.0-Sa)
2064 */
2065 pixel=gamma*geometry_info.rho*Sa*Sc*Da*Dc+geometry_info.sigma*
2066 Sa*Sc*Da+geometry_info.xi*Da*Dc*Sa+geometry_info.psi*Sa*Da+
2067 Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa);
2068 break;
2069 }
2070 case MinusDstCompositeOp:
2071 {
2072 pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
2073 break;
2074 }
2075 case MinusSrcCompositeOp:
2076 {
2077 /*
2078 Minus source from destination.
2079
2080 f(Sc,Dc) = Sc - Dc
2081 */
cristy99abff32011-12-24 20:45:16 +00002082 pixel=QuantumRange*gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
cristye4a40472011-12-22 02:56:19 +00002083 break;
2084 }
2085 case ModulateCompositeOp:
2086 {
2087 ssize_t
2088 offset;
2089
cristy99abff32011-12-24 20:45:16 +00002090 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002091 {
2092 pixel=Dc;
2093 break;
2094 }
2095 offset=(ssize_t) (GetPixelIntensity(composite_image,p)-midpoint);
2096 if (offset == 0)
2097 {
2098 pixel=Dc;
2099 break;
2100 }
cristy7133e642012-08-14 11:04:11 +00002101 CompositeHCL(destination_pixel.red,destination_pixel.green,
2102 destination_pixel.blue,&hue,&chroma,&luma);
2103 luma+=(0.01*percent_luma*offset)/midpoint;
2104 chroma*=0.01*percent_chroma;
2105 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002106 switch (channel)
2107 {
2108 case RedPixelChannel: pixel=red; break;
2109 case GreenPixelChannel: pixel=green; break;
2110 case BluePixelChannel: pixel=blue; break;
2111 default: pixel=Dc; break;
2112 }
2113 break;
2114 }
2115 case ModulusAddCompositeOp:
2116 {
2117 pixel=Sc+Dc;
2118 if (pixel > QuantumRange)
2119 pixel-=(QuantumRange+1.0);
2120 pixel=gamma*(pixel*Sa*Da+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
2121 break;
2122 }
2123 case ModulusSubtractCompositeOp:
2124 {
cristy99abff32011-12-24 20:45:16 +00002125 pixel=Sc-Dc;
cristye4a40472011-12-22 02:56:19 +00002126 if (pixel < 0.0)
2127 pixel+=(QuantumRange+1.0);
2128 pixel=gamma*(pixel*Sa*Da+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
2129 break;
2130 }
2131 case MultiplyCompositeOp:
2132 {
cristy99abff32011-12-24 20:45:16 +00002133 pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002134 break;
2135 }
2136 case OutCompositeOp:
2137 case SrcOutCompositeOp:
2138 {
2139 pixel=gamma*(Sa*Sc*(1.0-Da));
2140 break;
2141 }
2142 case OverCompositeOp:
2143 case SrcOverCompositeOp:
2144 {
cristy99abff32011-12-24 20:45:16 +00002145 pixel=QuantumRange*gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002146 break;
2147 }
2148 case OverlayCompositeOp:
2149 {
2150 if ((2.0*Dca) < Da)
cristy99abff32011-12-24 20:45:16 +00002151 {
2152 pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*
2153 (1.0-Da));
2154 break;
2155 }
2156 pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+
2157 Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00002158 break;
2159 }
2160 case PegtopLightCompositeOp:
2161 {
2162 /*
2163 PegTop: A Soft-Light alternative: A continuous version of the
2164 Softlight function, producing very similar results.
2165
2166 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
2167
2168 http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
2169 */
2170 if (fabs(Da) < MagickEpsilon)
2171 {
cristy99abff32011-12-24 20:45:16 +00002172 pixel=QuantumRange*gamma*(Sca);
cristye4a40472011-12-22 02:56:19 +00002173 break;
2174 }
cristy99abff32011-12-24 20:45:16 +00002175 pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
2176 Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002177 break;
2178 }
2179 case PinLightCompositeOp:
2180 {
2181 /*
2182 PinLight: A Photoshop 7 composition method
2183 http://www.simplefilter.de/en/basics/mixmods.html
2184
2185 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
2186 */
2187 if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
2188 {
cristy99abff32011-12-24 20:45:16 +00002189 pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002190 break;
2191 }
2192 if ((Dca*Sa) > (2.0*Sca*Da))
2193 {
cristy99abff32011-12-24 20:45:16 +00002194 pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002195 break;
2196 }
cristy99abff32011-12-24 20:45:16 +00002197 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
cristye4a40472011-12-22 02:56:19 +00002198 break;
2199 }
2200 case PlusCompositeOp:
2201 {
cristy24d5d722012-05-17 12:27:27 +00002202 pixel=gamma*(Sa*Sc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002203 break;
2204 }
2205 case SaturateCompositeOp:
2206 {
cristy99abff32011-12-24 20:45:16 +00002207 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002208 {
2209 pixel=Dc;
2210 break;
2211 }
cristy99abff32011-12-24 20:45:16 +00002212 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002213 {
2214 pixel=Sc;
2215 break;
2216 }
cristy7133e642012-08-14 11:04:11 +00002217 CompositeHCL(destination_pixel.red,destination_pixel.green,
2218 destination_pixel.blue,&hue,&chroma,&luma);
2219 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2220 &sans,&chroma,&sans);
2221 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002222 switch (channel)
2223 {
2224 case RedPixelChannel: pixel=red; break;
2225 case GreenPixelChannel: pixel=green; break;
2226 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002227 default: pixel=Dc; break;
2228 }
2229 break;
2230 }
2231 case ScreenCompositeOp:
2232 {
2233 /*
2234 Screen: a negated multiply:
2235
2236 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
2237 */
cristy99abff32011-12-24 20:45:16 +00002238 pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
cristye4a40472011-12-22 02:56:19 +00002239 break;
2240 }
2241 case SoftLightCompositeOp:
2242 {
2243 /*
2244 Refer to the March 2009 SVG specification.
2245 */
2246 if ((2.0*Sca) < Sa)
2247 {
cristy99abff32011-12-24 20:45:16 +00002248 pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-(Dca/Da)))+
2249 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002250 break;
2251 }
2252 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
2253 {
cristy99abff32011-12-24 20:45:16 +00002254 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*(Dca/Da)*
2255 (4.0*(Dca/Da)+1.0)*((Dca/Da)-1.0)+7.0*(Dca/Da))+Sca*(1.0-Da)+
2256 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002257 break;
2258 }
cristy99abff32011-12-24 20:45:16 +00002259 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow((Dca/Da),0.5)-
2260 (Dca/Da))+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002261 break;
2262 }
2263 case ThresholdCompositeOp:
2264 {
cristya19f1d72012-08-07 18:24:38 +00002265 double
cristye4a40472011-12-22 02:56:19 +00002266 delta;
2267
2268 delta=Sc-Dc;
cristya19f1d72012-08-07 18:24:38 +00002269 if ((double) fabs((double) (2.0*delta)) < threshold)
cristye4a40472011-12-22 02:56:19 +00002270 {
2271 pixel=gamma*Dc;
2272 break;
2273 }
2274 pixel=gamma*(Dc+delta*amount);
2275 break;
2276 }
2277 case VividLightCompositeOp:
2278 {
2279 /*
2280 VividLight: A Photoshop 7 composition method. See
2281 http://www.simplefilter.de/en/basics/mixmods.html.
2282
2283 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
2284 */
2285 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
2286 {
cristy99abff32011-12-24 20:45:16 +00002287 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002288 break;
2289 }
2290 if ((2.0*Sca) <= Sa)
2291 {
cristy99abff32011-12-24 20:45:16 +00002292 pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*
2293 (1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002294 break;
2295 }
cristy99abff32011-12-24 20:45:16 +00002296 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+
2297 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002298 break;
2299 }
2300 case XorCompositeOp:
2301 {
cristy99abff32011-12-24 20:45:16 +00002302 pixel=QuantumRange*gamma*(Sc*Sa*(1.0-Da)+Dc*Da*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002303 break;
2304 }
2305 default:
2306 {
2307 pixel=Sc;
2308 break;
2309 }
2310 }
2311 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00002312 }
cristyed231572011-07-14 02:18:59 +00002313 p+=GetPixelChannels(composite_image);
cristye4a40472011-12-22 02:56:19 +00002314 channels=GetPixelChannels(composite_image);
2315 if (p >= (pixels+channels*composite_image->columns))
cristy4c08aed2011-07-01 19:47:50 +00002316 p=pixels;
cristyed231572011-07-14 02:18:59 +00002317 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002318 }
2319 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2320 status=MagickFalse;
2321 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2322 {
2323 MagickBooleanType
2324 proceed;
2325
2326#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002327 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002328#endif
2329 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2330 image->rows);
2331 if (proceed == MagickFalse)
2332 status=MagickFalse;
2333 }
2334 }
2335 composite_view=DestroyCacheView(composite_view);
2336 image_view=DestroyCacheView(image_view);
2337 if (destination_image != (Image * ) NULL)
2338 destination_image=DestroyImage(destination_image);
cristyf661c4d2012-07-28 12:57:17 +00002339 else
2340 composite_image=DestroyImage(composite_image);
cristy191c0b72012-08-12 16:29:52 +00002341 if (status != MagickFalse)
2342 (void) ClampImage(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00002343 return(status);
2344}
2345
2346/*
2347%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2348% %
2349% %
2350% %
2351% T e x t u r e I m a g e %
2352% %
2353% %
2354% %
2355%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2356%
2357% TextureImage() repeatedly tiles the texture image across and down the image
2358% canvas.
2359%
2360% The format of the TextureImage method is:
2361%
cristy30d8c942012-02-07 13:44:59 +00002362% MagickBooleanType TextureImage(Image *image,const Image *texture,
cristye941a752011-10-15 01:52:48 +00002363% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002364%
2365% A description of each parameter follows:
2366%
2367% o image: the image.
2368%
cristye6178502011-12-23 17:02:29 +00002369% o texture_image: This image is the texture to layer on the background.
cristy4c08aed2011-07-01 19:47:50 +00002370%
2371*/
cristy30d8c942012-02-07 13:44:59 +00002372MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2373 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002374{
2375#define TextureImageTag "Texture/Image"
2376
2377 CacheView
2378 *image_view,
2379 *texture_view;
2380
cristy30d8c942012-02-07 13:44:59 +00002381 Image
2382 *texture_image;
2383
cristy4c08aed2011-07-01 19:47:50 +00002384 MagickBooleanType
2385 status;
2386
2387 ssize_t
2388 y;
2389
2390 assert(image != (Image *) NULL);
2391 if (image->debug != MagickFalse)
2392 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2393 assert(image->signature == MagickSignature);
cristy30d8c942012-02-07 13:44:59 +00002394 if (texture == (const Image *) NULL)
2395 return(MagickFalse);
2396 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2397 return(MagickFalse);
2398 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
cristye6178502011-12-23 17:02:29 +00002399 if (texture_image == (const Image *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002400 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +00002401 (void) TransformImageColorspace(texture_image,image->colorspace,exception);
cristy387430f2012-02-07 13:09:46 +00002402 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
2403 exception);
cristy4c08aed2011-07-01 19:47:50 +00002404 status=MagickTrue;
2405 if ((image->compose != CopyCompositeOp) &&
cristy8a46d822012-08-28 23:32:39 +00002406 ((image->compose != OverCompositeOp) || (image->alpha_trait == BlendPixelTrait) ||
2407 (texture_image->alpha_trait == BlendPixelTrait)))
cristy4c08aed2011-07-01 19:47:50 +00002408 {
2409 /*
2410 Tile texture onto the image background.
2411 */
cristye8ac1d02012-10-09 14:46:57 +00002412#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(NoBenefitFromParallelism)
cristy9a5a52f2012-10-09 14:40:31 +00002413 #pragma omp parallel for schedule(static,4) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002414 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00002415#endif
cristye6178502011-12-23 17:02:29 +00002416 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00002417 {
2418 register ssize_t
2419 x;
2420
2421 if (status == MagickFalse)
2422 continue;
cristye6178502011-12-23 17:02:29 +00002423 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002424 {
2425 MagickBooleanType
2426 thread_status;
2427
cristyfeb3e962012-03-29 17:25:55 +00002428 thread_status=CompositeImage(image,texture_image,image->compose,
2429 MagickFalse,x+texture_image->tile_offset.x,y+
2430 texture_image->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002431 if (thread_status == MagickFalse)
2432 {
2433 status=thread_status;
2434 break;
2435 }
2436 }
2437 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2438 {
2439 MagickBooleanType
2440 proceed;
2441
cristye8ac1d02012-10-09 14:46:57 +00002442#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(NoBenefitFromParallelism)
cristybd0435b2012-01-05 16:20:47 +00002443 #pragma omp critical (MagickCore_TextureImage)
cristy4c08aed2011-07-01 19:47:50 +00002444#endif
2445 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2446 y,image->rows);
2447 if (proceed == MagickFalse)
2448 status=MagickFalse;
2449 }
2450 }
2451 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2452 image->rows,image->rows);
cristy30d8c942012-02-07 13:44:59 +00002453 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002454 return(status);
2455 }
2456 /*
2457 Tile texture onto the image background (optimized).
2458 */
2459 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +00002460 texture_view=AcquireVirtualCacheView(texture_image,exception);
2461 image_view=AcquireAuthenticCacheView(image,exception);
cristye8ac1d02012-10-09 14:46:57 +00002462#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(NoBenefitFromParallelism)
cristy9a5a52f2012-10-09 14:40:31 +00002463 #pragma omp parallel for schedule(static,4) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002464 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00002465#endif
2466 for (y=0; y < (ssize_t) image->rows; y++)
2467 {
2468 MagickBooleanType
2469 sync;
2470
2471 register const Quantum
2472 *p,
2473 *pixels;
2474
2475 register ssize_t
2476 x;
2477
2478 register Quantum
2479 *q;
2480
2481 size_t
2482 width;
2483
2484 if (status == MagickFalse)
2485 continue;
cristye6178502011-12-23 17:02:29 +00002486 pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
2487 (y+texture_image->tile_offset.y) % texture_image->rows,
2488 texture_image->columns,1,exception);
2489 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002490 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2491 {
2492 status=MagickFalse;
2493 continue;
2494 }
cristye6178502011-12-23 17:02:29 +00002495 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002496 {
2497 register ssize_t
cristye6178502011-12-23 17:02:29 +00002498 j;
cristy4c08aed2011-07-01 19:47:50 +00002499
2500 p=pixels;
cristye6178502011-12-23 17:02:29 +00002501 width=texture_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002502 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2503 width=image->columns-x;
cristye6178502011-12-23 17:02:29 +00002504 for (j=0; j < (ssize_t) width; j++)
cristy4c08aed2011-07-01 19:47:50 +00002505 {
cristye6178502011-12-23 17:02:29 +00002506 register ssize_t
2507 i;
2508
cristy10a6c612012-01-29 21:41:05 +00002509 if (GetPixelMask(image,p) != 0)
2510 {
2511 p+=GetPixelChannels(texture_image);
2512 q+=GetPixelChannels(image);
2513 continue;
2514 }
cristye6178502011-12-23 17:02:29 +00002515 for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
2516 {
2517 PixelChannel
2518 channel;
2519
2520 PixelTrait
2521 texture_traits,
2522 traits;
2523
cristycf1296e2012-08-26 23:40:49 +00002524 channel=GetPixelChannelChannel(texture_image,i);
2525 texture_traits=GetPixelChannelTraits(texture_image,channel);
2526 traits=GetPixelChannelTraits(image,channel);
cristye6178502011-12-23 17:02:29 +00002527 if ((traits == UndefinedPixelTrait) ||
2528 (texture_traits == UndefinedPixelTrait))
2529 continue;
2530 SetPixelChannel(image,channel,p[i],q);
2531 }
2532 p+=GetPixelChannels(texture_image);
cristyed231572011-07-14 02:18:59 +00002533 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002534 }
2535 }
2536 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2537 if (sync == MagickFalse)
2538 status=MagickFalse;
2539 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2540 {
2541 MagickBooleanType
2542 proceed;
2543
cristye8ac1d02012-10-09 14:46:57 +00002544#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(NoBenefitFromParallelism)
cristy4c08aed2011-07-01 19:47:50 +00002545 #pragma omp critical (MagickCore_TextureImage)
2546#endif
2547 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2548 image->rows);
2549 if (proceed == MagickFalse)
2550 status=MagickFalse;
2551 }
2552 }
2553 texture_view=DestroyCacheView(texture_view);
2554 image_view=DestroyCacheView(image_view);
cristy30d8c942012-02-07 13:44:59 +00002555 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002556 return(status);
2557}