blob: 788bae850f048999970b5d918d906bc6e725938f [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);
cristya4a22a22012-08-01 23:16:38 +0000606 (void) SetImageColorspace(composite_image,image->colorspace,exception);
cristye4a40472011-12-22 02:56:19 +0000607 if ((compose == OverCompositeOp) || (compose == SrcOverCompositeOp))
608 {
cristyfeb3e962012-03-29 17:25:55 +0000609 status=CompositeOverImage(image,composite_image,clip_to_self,x_offset,
610 y_offset,exception);
cristya865ccd2012-07-28 00:33:10 +0000611 composite_image=DestroyImage(composite_image);
cristye4a40472011-12-22 02:56:19 +0000612 return(status);
613 }
cristy4c08aed2011-07-01 19:47:50 +0000614 destination_image=(Image *) NULL;
615 amount=0.5;
616 destination_dissolve=1.0;
cristy7133e642012-08-14 11:04:11 +0000617 percent_luma=100.0;
618 percent_chroma=100.0;
cristy4c08aed2011-07-01 19:47:50 +0000619 source_dissolve=1.0;
620 threshold=0.05f;
621 switch (compose)
622 {
cristy4c08aed2011-07-01 19:47:50 +0000623 case CopyCompositeOp:
624 {
625 if ((x_offset < 0) || (y_offset < 0))
626 break;
627 if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns)
628 break;
629 if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows)
630 break;
631 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +0000632 composite_view=AcquireVirtualCacheView(composite_image,exception);
633 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000634#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000635 #pragma omp parallel for schedule(static,4) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000636 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +0000637#endif
638 for (y=0; y < (ssize_t) composite_image->rows; y++)
639 {
640 MagickBooleanType
641 sync;
642
643 register const Quantum
644 *p;
645
646 register Quantum
647 *q;
648
649 register ssize_t
650 x;
651
652 if (status == MagickFalse)
653 continue;
654 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
655 1,exception);
656 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
657 composite_image->columns,1,exception);
658 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
659 {
660 status=MagickFalse;
661 continue;
662 }
663 for (x=0; x < (ssize_t) composite_image->columns; x++)
664 {
cristybdecccc2011-12-24 22:52:16 +0000665 register ssize_t
666 i;
667
cristy665e18f2012-05-17 12:39:54 +0000668 if (GetPixelMask(composite_image,p) != 0)
cristy10a6c612012-01-29 21:41:05 +0000669 {
670 p+=GetPixelChannels(composite_image);
671 q+=GetPixelChannels(image);
672 continue;
673 }
cristybdecccc2011-12-24 22:52:16 +0000674 for (i=0; i < (ssize_t) GetPixelChannels(composite_image); i++)
675 {
676 PixelChannel
677 channel;
678
679 PixelTrait
680 composite_traits,
681 traits;
682
cristycf1296e2012-08-26 23:40:49 +0000683 channel=GetPixelChannelChannel(composite_image,i);
684 composite_traits=GetPixelChannelTraits(composite_image,channel);
685 traits=GetPixelChannelTraits(image,channel);
cristybdecccc2011-12-24 22:52:16 +0000686 if ((traits == UndefinedPixelTrait) ||
687 (composite_traits == UndefinedPixelTrait))
688 continue;
689 SetPixelChannel(image,channel,p[i],q);
690 }
cristyed231572011-07-14 02:18:59 +0000691 p+=GetPixelChannels(composite_image);
692 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +0000693 }
694 sync=SyncCacheViewAuthenticPixels(image_view,exception);
695 if (sync == MagickFalse)
696 status=MagickFalse;
697 if (image->progress_monitor != (MagickProgressMonitor) NULL)
698 {
699 MagickBooleanType
700 proceed;
701
702#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +0000703 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +0000704#endif
705 proceed=SetImageProgress(image,CompositeImageTag,
706 (MagickOffsetType) y,image->rows);
707 if (proceed == MagickFalse)
708 status=MagickFalse;
709 }
710 }
711 composite_view=DestroyCacheView(composite_view);
712 image_view=DestroyCacheView(image_view);
cristy44886b92012-07-28 13:07:09 +0000713 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000714 return(status);
715 }
cristye4a40472011-12-22 02:56:19 +0000716 case CopyAlphaCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000717 case ChangeMaskCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +0000718 case IntensityCompositeOp:
cristy4c08aed2011-07-01 19:47:50 +0000719 {
720 /*
721 Modify destination outside the overlaid region and require an alpha
722 channel to exist, to add transparency.
723 */
cristy8a46d822012-08-28 23:32:39 +0000724 if (image->alpha_trait != BlendPixelTrait)
cristy42c41de2012-05-05 18:36:31 +0000725 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy4c08aed2011-07-01 19:47:50 +0000726 break;
727 }
728 case BlurCompositeOp:
729 {
730 CacheView
731 *composite_view,
732 *destination_view;
733
cristyfeb3e962012-03-29 17:25:55 +0000734 const char
735 *value;
736
cristy4c08aed2011-07-01 19:47:50 +0000737 PixelInfo
738 pixel;
739
cristya19f1d72012-08-07 18:24:38 +0000740 double
cristy4c08aed2011-07-01 19:47:50 +0000741 angle_range,
742 angle_start,
743 height,
744 width;
745
746 ResampleFilter
747 *resample_filter;
748
749 SegmentInfo
750 blur;
751
752 /*
anthony9cb63cc2012-04-25 06:10:49 +0000753 Blur Image by resampling.
754
cristy4c08aed2011-07-01 19:47:50 +0000755 Blur Image dictated by an overlay gradient map: X = red_channel;
756 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
757 */
758 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000759 exception);
cristy4c08aed2011-07-01 19:47:50 +0000760 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000761 {
762 composite_image=DestroyImage(composite_image);
763 return(MagickFalse);
764 }
cristy4c08aed2011-07-01 19:47:50 +0000765 /*
anthony9cb63cc2012-04-25 06:10:49 +0000766 Gather the maximum blur sigma values from user.
cristy4c08aed2011-07-01 19:47:50 +0000767 */
768 SetGeometryInfo(&geometry_info);
769 flags=NoValue;
770 value=GetImageArtifact(composite_image,"compose:args");
771 if (value != (char *) NULL)
772 flags=ParseGeometry(value,&geometry_info);
anthonyd2923912012-04-23 13:06:53 +0000773 if ((flags & WidthValue) == 0 ) {
774 (void) ThrowMagickException(exception,GetMagickModule(),
775 OptionWarning,"InvalidSetting","'%s' '%s'",
anthony9cb63cc2012-04-25 06:10:49 +0000776 "compose:args",value);
cristy44886b92012-07-28 13:07:09 +0000777 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000778 destination_image=DestroyImage(destination_image);
779 return(MagickFalse);
780 }
anthony9cb63cc2012-04-25 06:10:49 +0000781 /*
782 Users input sigma now needs to be converted to the EWA ellipse size.
783 The filter defaults to a sigma of 0.5 so to make this match the
784 users input the ellipse size needs to be doubled.
anthonyd2923912012-04-23 13:06:53 +0000785 */
786 width=height=geometry_info.rho*2.0;
787 if ((flags & HeightValue) != 0 )
788 height=geometry_info.sigma*2.0;
789
anthony9cb63cc2012-04-25 06:10:49 +0000790 /* default the unrotated ellipse width and height axis vectors */
anthonyd2923912012-04-23 13:06:53 +0000791 blur.x1=width;
cristy4c08aed2011-07-01 19:47:50 +0000792 blur.x2=0.0;
793 blur.y1=0.0;
anthonyd2923912012-04-23 13:06:53 +0000794 blur.y2=height;
795 /* rotate vectors if a rotation angle is given */
cristy4c08aed2011-07-01 19:47:50 +0000796 if ((flags & XValue) != 0 )
797 {
cristya19f1d72012-08-07 18:24:38 +0000798 double
cristy4c08aed2011-07-01 19:47:50 +0000799 angle;
800
801 angle=DegreesToRadians(geometry_info.xi);
802 blur.x1=width*cos(angle);
803 blur.x2=width*sin(angle);
804 blur.y1=(-height*sin(angle));
805 blur.y2=height*cos(angle);
806 }
anthonyd2923912012-04-23 13:06:53 +0000807 /* Otherwise lets set a angle range and calculate in the loop */
808 angle_start=0.0;
809 angle_range=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000810 if ((flags & YValue) != 0 )
811 {
812 angle_start=DegreesToRadians(geometry_info.xi);
813 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
814 }
815 /*
anthony9cb63cc2012-04-25 06:10:49 +0000816 Set up a gaussian cylindrical filter for EWA Bluring.
anthonyd2923912012-04-23 13:06:53 +0000817
anthony9cb63cc2012-04-25 06:10:49 +0000818 As the minimum ellipse radius of support*1.0 the EWA algorithm
819 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
820 This means that even 'No Blur' will be still a little blurry!
anthonyd2923912012-04-23 13:06:53 +0000821
anthony9cb63cc2012-04-25 06:10:49 +0000822 The solution (as well as the problem of preventing any user
823 expert filter settings, is to set our own user settings, then
824 restore them afterwards.
cristy4c08aed2011-07-01 19:47:50 +0000825 */
cristy8a11cb12011-10-19 23:53:34 +0000826 resample_filter=AcquireResampleFilter(image,exception);
anthonyd2923912012-04-23 13:06:53 +0000827 SetResampleFilter(resample_filter,GaussianFilter);
anthony9cb63cc2012-04-25 06:10:49 +0000828
829 /* do the variable blurring of each pixel in image */
830 GetPixelInfo(image,&pixel);
cristydb070952012-04-20 14:33:00 +0000831 composite_view=AcquireVirtualCacheView(composite_image,exception);
832 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +0000833 for (y=0; y < (ssize_t) composite_image->rows; y++)
834 {
835 MagickBooleanType
836 sync;
837
838 register const Quantum
839 *restrict p;
840
841 register Quantum
842 *restrict q;
843
844 register ssize_t
845 x;
846
847 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
848 continue;
849 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
850 1,exception);
851 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +0000852 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000853 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
854 break;
855 for (x=0; x < (ssize_t) composite_image->columns; x++)
856 {
857 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
858 {
cristyed231572011-07-14 02:18:59 +0000859 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000860 continue;
861 }
862 if (fabs(angle_range) > MagickEpsilon)
863 {
cristya19f1d72012-08-07 18:24:38 +0000864 double
cristy4c08aed2011-07-01 19:47:50 +0000865 angle;
866
867 angle=angle_start+angle_range*QuantumScale*
868 GetPixelBlue(composite_image,p);
869 blur.x1=width*cos(angle);
870 blur.x2=width*sin(angle);
871 blur.y1=(-height*sin(angle));
872 blur.y2=height*cos(angle);
873 }
anthonyd2923912012-04-23 13:06:53 +0000874#if 0
875 if ( x == 10 && y == 60 ) {
876 fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",
877 blur.x1, blur.x2, blur.y1, blur.y2);
878 fprintf(stderr, "scaled by=%lf,%lf\n",
879 QuantumScale*GetPixelRed(p), QuantumScale*GetPixelGreen(p));
880#endif
881 ScaleResampleFilter(resample_filter,
882 blur.x1*QuantumScale*GetPixelRed(composite_image,p),
883 blur.y1*QuantumScale*GetPixelGreen(composite_image,p),
884 blur.x2*QuantumScale*GetPixelRed(composite_image,p),
885 blur.y2*QuantumScale*GetPixelGreen(composite_image,p) );
cristy4c08aed2011-07-01 19:47:50 +0000886 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
cristydb070952012-04-20 14:33:00 +0000887 (double) y_offset+y,&pixel,exception);
cristy803640d2011-11-17 02:11:32 +0000888 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +0000889 p+=GetPixelChannels(composite_image);
890 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +0000891 }
892 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
893 if (sync == MagickFalse)
894 break;
895 }
896 resample_filter=DestroyResampleFilter(resample_filter);
897 composite_view=DestroyCacheView(composite_view);
898 destination_view=DestroyCacheView(destination_view);
cristyf661c4d2012-07-28 12:57:17 +0000899 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +0000900 composite_image=destination_image;
901 break;
902 }
903 case DisplaceCompositeOp:
904 case DistortCompositeOp:
905 {
906 CacheView
907 *composite_view,
908 *destination_view,
909 *image_view;
910
cristyfeb3e962012-03-29 17:25:55 +0000911 const char
912 *value;
913
cristy4c08aed2011-07-01 19:47:50 +0000914 PixelInfo
915 pixel;
916
cristya19f1d72012-08-07 18:24:38 +0000917 double
cristy4c08aed2011-07-01 19:47:50 +0000918 horizontal_scale,
919 vertical_scale;
920
921 PointInfo
922 center,
923 offset;
924
925 /*
926 Displace/Distort based on overlay gradient map:
927 X = red_channel; Y = green_channel;
928 compose:args = x_scale[,y_scale[,center.x,center.y]]
929 */
930 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +0000931 exception);
cristy4c08aed2011-07-01 19:47:50 +0000932 if (destination_image == (Image *) NULL)
cristy44886b92012-07-28 13:07:09 +0000933 {
934 composite_image=DestroyImage(composite_image);
935 return(MagickFalse);
936 }
cristy4c08aed2011-07-01 19:47:50 +0000937 SetGeometryInfo(&geometry_info);
938 flags=NoValue;
939 value=GetImageArtifact(composite_image,"compose:args");
940 if (value != (char *) NULL)
941 flags=ParseGeometry(value,&geometry_info);
942 if ((flags & (WidthValue|HeightValue)) == 0 )
943 {
944 if ((flags & AspectValue) == 0)
945 {
cristya19f1d72012-08-07 18:24:38 +0000946 horizontal_scale=(double) (composite_image->columns-1.0)/
cristy4c08aed2011-07-01 19:47:50 +0000947 2.0;
cristya19f1d72012-08-07 18:24:38 +0000948 vertical_scale=(double) (composite_image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000949 }
950 else
951 {
cristya19f1d72012-08-07 18:24:38 +0000952 horizontal_scale=(double) (image->columns-1.0)/2.0;
953 vertical_scale=(double) (image->rows-1.0)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000954 }
955 }
956 else
957 {
958 horizontal_scale=geometry_info.rho;
959 vertical_scale=geometry_info.sigma;
960 if ((flags & PercentValue) != 0)
961 {
962 if ((flags & AspectValue) == 0)
963 {
964 horizontal_scale*=(composite_image->columns-1.0)/200.0;
965 vertical_scale*=(composite_image->rows-1.0)/200.0;
966 }
967 else
968 {
969 horizontal_scale*=(image->columns-1.0)/200.0;
970 vertical_scale*=(image->rows-1.0)/200.0;
971 }
972 }
973 if ((flags & HeightValue) == 0)
974 vertical_scale=horizontal_scale;
975 }
976 /*
977 Determine fixed center point for absolute distortion map
978 Absolute distort ==
979 Displace offset relative to a fixed absolute point
980 Select that point according to +X+Y user inputs.
981 default = center of overlay image
982 arg flag '!' = locations/percentage relative to background image
983 */
cristya19f1d72012-08-07 18:24:38 +0000984 center.x=(double) x_offset;
985 center.y=(double) y_offset;
cristy4c08aed2011-07-01 19:47:50 +0000986 if (compose == DistortCompositeOp)
987 {
988 if ((flags & XValue) == 0)
989 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +0000990 center.x=(double) x_offset+(composite_image->columns-1)/
cristy4c08aed2011-07-01 19:47:50 +0000991 2.0;
992 else
cristya19f1d72012-08-07 18:24:38 +0000993 center.x=((double) image->columns-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +0000994 else
995 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +0000996 center.x=(double) x_offset+geometry_info.xi;
cristy4c08aed2011-07-01 19:47:50 +0000997 else
998 center.x=geometry_info.xi;
999 if ((flags & YValue) == 0)
1000 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +00001001 center.y=(double) y_offset+(composite_image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +00001002 else
cristya19f1d72012-08-07 18:24:38 +00001003 center.y=((double) image->rows-1)/2.0;
cristy4c08aed2011-07-01 19:47:50 +00001004 else
1005 if ((flags & AspectValue) == 0)
cristya19f1d72012-08-07 18:24:38 +00001006 center.y=(double) y_offset+geometry_info.psi;
cristy4c08aed2011-07-01 19:47:50 +00001007 else
1008 center.y=geometry_info.psi;
1009 }
1010 /*
1011 Shift the pixel offset point as defined by the provided,
1012 displacement/distortion map. -- Like a lens...
1013 */
cristye10859a2011-12-18 22:28:59 +00001014 GetPixelInfo(image,&pixel);
cristydb070952012-04-20 14:33:00 +00001015 image_view=AcquireVirtualCacheView(image,exception);
1016 composite_view=AcquireVirtualCacheView(composite_image,exception);
1017 destination_view=AcquireAuthenticCacheView(destination_image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001018 for (y=0; y < (ssize_t) composite_image->rows; y++)
1019 {
1020 MagickBooleanType
1021 sync;
1022
1023 register const Quantum
1024 *restrict p;
1025
1026 register Quantum
1027 *restrict q;
1028
1029 register ssize_t
1030 x;
1031
1032 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1033 continue;
1034 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1035 1,exception);
1036 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +00001037 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001038 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1039 break;
1040 for (x=0; x < (ssize_t) composite_image->columns; x++)
1041 {
1042 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1043 {
cristyed231572011-07-14 02:18:59 +00001044 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001045 continue;
1046 }
1047 /*
1048 Displace the offset.
1049 */
1050 offset.x=(horizontal_scale*(GetPixelRed(composite_image,p)-
cristya19f1d72012-08-07 18:24:38 +00001051 (((double) QuantumRange+1.0)/2.0)))/(((double)
cristy4c08aed2011-07-01 19:47:50 +00001052 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
1053 x : 0);
1054 offset.y=(vertical_scale*(GetPixelGreen(composite_image,p)-
cristya19f1d72012-08-07 18:24:38 +00001055 (((double) QuantumRange+1.0)/2.0)))/(((double)
cristy4c08aed2011-07-01 19:47:50 +00001056 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
1057 y : 0);
1058 (void) InterpolatePixelInfo(image,image_view,
1059 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
1060 &pixel,exception);
1061 /*
1062 Mask with the 'invalid pixel mask' in alpha channel.
1063 */
cristya19f1d72012-08-07 18:24:38 +00001064 pixel.alpha=(double) QuantumRange*(1.0-(1.0-QuantumScale*
cristy99abff32011-12-24 20:45:16 +00001065 pixel.alpha)*(1.0-QuantumScale*GetPixelAlpha(composite_image,p)));
cristy803640d2011-11-17 02:11:32 +00001066 SetPixelInfoPixel(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00001067 p+=GetPixelChannels(composite_image);
1068 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00001069 }
1070 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
1071 if (sync == MagickFalse)
1072 break;
1073 }
1074 destination_view=DestroyCacheView(destination_view);
1075 composite_view=DestroyCacheView(composite_view);
1076 image_view=DestroyCacheView(image_view);
cristyf661c4d2012-07-28 12:57:17 +00001077 composite_image=DestroyImage(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001078 composite_image=destination_image;
1079 break;
1080 }
1081 case DissolveCompositeOp:
1082 {
cristyfeb3e962012-03-29 17:25:55 +00001083 const char
1084 *value;
1085
cristy4c08aed2011-07-01 19:47:50 +00001086 /*
1087 Geometry arguments to dissolve factors.
1088 */
1089 value=GetImageArtifact(composite_image,"compose:args");
1090 if (value != (char *) NULL)
1091 {
1092 flags=ParseGeometry(value,&geometry_info);
1093 source_dissolve=geometry_info.rho/100.0;
1094 destination_dissolve=1.0;
1095 if ((source_dissolve-MagickEpsilon) < 0.0)
1096 source_dissolve=0.0;
1097 if ((source_dissolve+MagickEpsilon) > 1.0)
1098 {
1099 destination_dissolve=2.0-source_dissolve;
1100 source_dissolve=1.0;
1101 }
1102 if ((flags & SigmaValue) != 0)
1103 destination_dissolve=geometry_info.sigma/100.0;
1104 if ((destination_dissolve-MagickEpsilon) < 0.0)
1105 destination_dissolve=0.0;
anthony9cb63cc2012-04-25 06:10:49 +00001106 /* posible speed up? -- from IMv6 update
1107 clip_to_self=MagickFalse;
1108 if ((destination_dissolve+MagickEpsilon) > 1.0 )
1109 {
1110 destination_dissolve=1.0;
1111 clip_to_self=MagickTrue;
1112 }
1113 */
cristy4c08aed2011-07-01 19:47:50 +00001114 }
1115 break;
1116 }
1117 case BlendCompositeOp:
1118 {
cristyfeb3e962012-03-29 17:25:55 +00001119 const char
1120 *value;
1121
cristy4c08aed2011-07-01 19:47:50 +00001122 value=GetImageArtifact(composite_image,"compose:args");
1123 if (value != (char *) NULL)
1124 {
1125 flags=ParseGeometry(value,&geometry_info);
1126 source_dissolve=geometry_info.rho/100.0;
1127 destination_dissolve=1.0-source_dissolve;
1128 if ((flags & SigmaValue) != 0)
1129 destination_dissolve=geometry_info.sigma/100.0;
cristy4c08aed2011-07-01 19:47:50 +00001130 }
1131 break;
1132 }
1133 case MathematicsCompositeOp:
1134 {
cristyfeb3e962012-03-29 17:25:55 +00001135 const char
1136 *value;
1137
cristy4c08aed2011-07-01 19:47:50 +00001138 /*
1139 Just collect the values from "compose:args", setting.
1140 Unused values are set to zero automagically.
1141
1142 Arguments are normally a comma separated list, so this probably should
1143 be changed to some 'general comma list' parser, (with a minimum
1144 number of values)
1145 */
1146 SetGeometryInfo(&geometry_info);
1147 value=GetImageArtifact(composite_image,"compose:args");
1148 if (value != (char *) NULL)
1149 (void) ParseGeometry(value,&geometry_info);
1150 break;
1151 }
1152 case ModulateCompositeOp:
1153 {
cristyfeb3e962012-03-29 17:25:55 +00001154 const char
1155 *value;
1156
cristy4c08aed2011-07-01 19:47:50 +00001157 /*
cristy7133e642012-08-14 11:04:11 +00001158 Determine the luma and chroma scale.
cristy4c08aed2011-07-01 19:47:50 +00001159 */
1160 value=GetImageArtifact(composite_image,"compose:args");
1161 if (value != (char *) NULL)
1162 {
1163 flags=ParseGeometry(value,&geometry_info);
cristy7133e642012-08-14 11:04:11 +00001164 percent_luma=geometry_info.rho;
cristy4c08aed2011-07-01 19:47:50 +00001165 if ((flags & SigmaValue) != 0)
cristy7133e642012-08-14 11:04:11 +00001166 percent_chroma=geometry_info.sigma;
cristy4c08aed2011-07-01 19:47:50 +00001167 }
1168 break;
1169 }
1170 case ThresholdCompositeOp:
1171 {
cristyfeb3e962012-03-29 17:25:55 +00001172 const char
1173 *value;
1174
cristy4c08aed2011-07-01 19:47:50 +00001175 /*
1176 Determine the amount and threshold.
1177 */
1178 value=GetImageArtifact(composite_image,"compose:args");
1179 if (value != (char *) NULL)
1180 {
1181 flags=ParseGeometry(value,&geometry_info);
1182 amount=geometry_info.rho;
1183 threshold=geometry_info.sigma;
1184 if ((flags & SigmaValue) == 0)
1185 threshold=0.05f;
1186 }
1187 threshold*=QuantumRange;
1188 break;
1189 }
1190 default:
1191 break;
1192 }
cristy4c08aed2011-07-01 19:47:50 +00001193 /*
1194 Composite image.
1195 */
1196 status=MagickTrue;
1197 progress=0;
cristya19f1d72012-08-07 18:24:38 +00001198 midpoint=((double) QuantumRange+1.0)/2;
cristydb070952012-04-20 14:33:00 +00001199 composite_view=AcquireVirtualCacheView(composite_image,exception);
1200 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001201#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001202 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001203 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00001204#endif
1205 for (y=0; y < (ssize_t) image->rows; y++)
1206 {
1207 const Quantum
1208 *pixels;
1209
1210 double
cristye4a40472011-12-22 02:56:19 +00001211 blue,
cristy7133e642012-08-14 11:04:11 +00001212 luma,
cristye4a40472011-12-22 02:56:19 +00001213 green,
cristy4c08aed2011-07-01 19:47:50 +00001214 hue,
cristye4a40472011-12-22 02:56:19 +00001215 red,
cristy7133e642012-08-14 11:04:11 +00001216 chroma;
cristy4c08aed2011-07-01 19:47:50 +00001217
cristyddeeea22012-04-12 01:33:09 +00001218 PixelInfo
1219 destination_pixel,
1220 source_pixel;
1221
cristy4c08aed2011-07-01 19:47:50 +00001222 register const Quantum
1223 *restrict p;
1224
1225 register Quantum
1226 *restrict q;
1227
1228 register ssize_t
1229 x;
1230
1231 if (status == MagickFalse)
1232 continue;
cristyfeb3e962012-03-29 17:25:55 +00001233 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001234 {
1235 if (y < y_offset)
1236 continue;
1237 if ((y-y_offset) >= (ssize_t) composite_image->rows)
1238 continue;
1239 }
1240 /*
1241 If pixels is NULL, y is outside overlay region.
1242 */
1243 pixels=(Quantum *) NULL;
1244 p=(Quantum *) NULL;
1245 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
1246 {
1247 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
1248 composite_image->columns,1,exception);
1249 if (p == (const Quantum *) NULL)
1250 {
1251 status=MagickFalse;
1252 continue;
1253 }
1254 pixels=p;
1255 if (x_offset < 0)
cristyed231572011-07-14 02:18:59 +00001256 p-=x_offset*GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001257 }
1258 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001259 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00001260 {
1261 status=MagickFalse;
1262 continue;
1263 }
cristy4c08aed2011-07-01 19:47:50 +00001264 hue=0.0;
cristy7133e642012-08-14 11:04:11 +00001265 chroma=0.0;
1266 luma=0.0;
cristyddeeea22012-04-12 01:33:09 +00001267 GetPixelInfo(image,&destination_pixel);
1268 GetPixelInfo(composite_image,&source_pixel);
cristy4c08aed2011-07-01 19:47:50 +00001269 for (x=0; x < (ssize_t) image->columns; x++)
1270 {
cristya19f1d72012-08-07 18:24:38 +00001271 double
cristye4a40472011-12-22 02:56:19 +00001272 alpha,
1273 Da,
1274 Dc,
1275 Dca,
1276 gamma,
1277 Sa,
1278 Sc,
1279 Sca;
1280
1281 register ssize_t
1282 i;
1283
cristy564a5692012-01-20 23:56:26 +00001284 size_t
1285 channels;
1286
cristyfeb3e962012-03-29 17:25:55 +00001287 if (clip_to_self != MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001288 {
1289 if (x < x_offset)
1290 {
cristyed231572011-07-14 02:18:59 +00001291 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001292 continue;
1293 }
1294 if ((x-x_offset) >= (ssize_t) composite_image->columns)
1295 break;
1296 }
cristye4a40472011-12-22 02:56:19 +00001297 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
1298 ((x-x_offset) >= (ssize_t) composite_image->columns))
1299 {
1300 Quantum
1301 source[MaxPixelChannels];
1302
1303 /*
1304 Virtual composite:
1305 Sc: source color.
1306 Dc: destination color.
1307 */
1308 (void) GetOneVirtualPixel(composite_image,x-x_offset,y-y_offset,
1309 source,exception);
cristy10a6c612012-01-29 21:41:05 +00001310 if (GetPixelMask(image,q) != 0)
1311 {
1312 q+=GetPixelChannels(image);
1313 continue;
1314 }
cristye4a40472011-12-22 02:56:19 +00001315 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1316 {
cristya19f1d72012-08-07 18:24:38 +00001317 double
cristye4a40472011-12-22 02:56:19 +00001318 pixel;
1319
1320 PixelChannel
1321 channel;
1322
1323 PixelTrait
1324 composite_traits,
1325 traits;
1326
cristycf1296e2012-08-26 23:40:49 +00001327 channel=GetPixelChannelChannel(image,i);
1328 traits=GetPixelChannelTraits(image,channel);
1329 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristye4a40472011-12-22 02:56:19 +00001330 if ((traits == UndefinedPixelTrait) ||
1331 (composite_traits == UndefinedPixelTrait))
1332 continue;
1333 switch (compose)
1334 {
cristyc8d63672012-01-11 13:03:13 +00001335 case AlphaCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001336 case ChangeMaskCompositeOp:
1337 case CopyAlphaCompositeOp:
1338 case DstAtopCompositeOp:
1339 case DstInCompositeOp:
1340 case InCompositeOp:
cristy0cd1f212012-01-05 15:45:59 +00001341 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001342 case OutCompositeOp:
1343 case SrcInCompositeOp:
1344 case SrcOutCompositeOp:
1345 {
cristya19f1d72012-08-07 18:24:38 +00001346 pixel=(double) q[i];
cristye4a40472011-12-22 02:56:19 +00001347 if (channel == AlphaPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001348 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001349 break;
1350 }
1351 case ClearCompositeOp:
1352 case CopyCompositeOp:
1353 case ReplaceCompositeOp:
1354 case SrcCompositeOp:
1355 {
1356 if (channel == AlphaPixelChannel)
1357 {
cristya19f1d72012-08-07 18:24:38 +00001358 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001359 break;
1360 }
1361 pixel=0.0;
1362 break;
1363 }
cristy99abff32011-12-24 20:45:16 +00001364 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001365 case DissolveCompositeOp:
1366 {
1367 if (channel == AlphaPixelChannel)
1368 {
1369 pixel=destination_dissolve*GetPixelAlpha(composite_image,
1370 source);
1371 break;
1372 }
cristya19f1d72012-08-07 18:24:38 +00001373 pixel=(double) source[channel];
cristye4a40472011-12-22 02:56:19 +00001374 break;
1375 }
1376 default:
1377 {
cristya19f1d72012-08-07 18:24:38 +00001378 pixel=(double) source[channel];
cristye4a40472011-12-22 02:56:19 +00001379 break;
1380 }
1381 }
1382 q[i]=ClampToQuantum(pixel);
1383 }
1384 q+=GetPixelChannels(image);
1385 continue;
1386 }
1387 /*
1388 Authentic composite:
1389 Sa: normalized source alpha.
1390 Da: normalized destination alpha.
1391 */
1392 Sa=QuantumScale*GetPixelAlpha(composite_image,p);
1393 Da=QuantumScale*GetPixelAlpha(image,q);
cristy4c08aed2011-07-01 19:47:50 +00001394 switch (compose)
1395 {
cristye4a40472011-12-22 02:56:19 +00001396 case BumpmapCompositeOp:
1397 {
1398 alpha=GetPixelIntensity(composite_image,p)*Sa;
1399 break;
1400 }
cristycdc168f2011-12-21 15:24:39 +00001401 case ColorBurnCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001402 case ColorDodgeCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001403 case DifferenceCompositeOp:
1404 case DivideDstCompositeOp:
1405 case DivideSrcCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001406 case ExclusionCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001407 case HardLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001408 case LinearBurnCompositeOp:
1409 case LinearDodgeCompositeOp:
1410 case LinearLightCompositeOp:
cristyd06bf862011-12-21 02:18:45 +00001411 case MathematicsCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001412 case MinusDstCompositeOp:
1413 case MinusSrcCompositeOp:
1414 case ModulusAddCompositeOp:
1415 case ModulusSubtractCompositeOp:
1416 case MultiplyCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001417 case OverlayCompositeOp:
1418 case PegtopLightCompositeOp:
1419 case PinLightCompositeOp:
cristye0984c82011-12-20 02:35:37 +00001420 case ScreenCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001421 case SoftLightCompositeOp:
cristycdc168f2011-12-21 15:24:39 +00001422 case VividLightCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001423 {
1424 alpha=RoundToUnity(Sa+Da-Sa*Da);
1425 break;
1426 }
1427 case DarkenCompositeOp:
1428 case DstAtopCompositeOp:
1429 case DstInCompositeOp:
1430 case InCompositeOp:
1431 case LightenCompositeOp:
1432 case SrcInCompositeOp:
1433 {
1434 alpha=Sa*Da;
1435 break;
1436 }
1437 case DissolveCompositeOp:
1438 {
1439 alpha=source_dissolve*Sa*(-destination_dissolve*Da)+source_dissolve*
1440 Sa+destination_dissolve*Da;
1441 break;
1442 }
1443 case DstOverCompositeOp:
1444 {
1445 alpha=Da*(-Sa)+Da+Sa;
1446 break;
1447 }
1448 case DstOutCompositeOp:
1449 {
1450 alpha=Da*(1.0-Sa);
1451 break;
1452 }
1453 case OutCompositeOp:
1454 case SrcOutCompositeOp:
1455 {
1456 alpha=Sa*(1.0-Da);
1457 break;
1458 }
1459 case OverCompositeOp:
1460 case SrcOverCompositeOp:
1461 {
1462 alpha=Sa*(-Da)+Sa+Da;
1463 break;
1464 }
cristy99abff32011-12-24 20:45:16 +00001465 case BlendCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001466 case PlusCompositeOp:
1467 {
1468 alpha=RoundToUnity(Sa+Da);
1469 break;
1470 }
cristy4c08aed2011-07-01 19:47:50 +00001471 case XorCompositeOp:
1472 {
cristye4a40472011-12-22 02:56:19 +00001473 alpha=Sa+Da-2.0*Sa*Da;
cristy4c08aed2011-07-01 19:47:50 +00001474 break;
1475 }
1476 default:
cristye4a40472011-12-22 02:56:19 +00001477 {
1478 alpha=1.0;
cristy4c08aed2011-07-01 19:47:50 +00001479 break;
cristye4a40472011-12-22 02:56:19 +00001480 }
cristy4c08aed2011-07-01 19:47:50 +00001481 }
cristy10a6c612012-01-29 21:41:05 +00001482 if (GetPixelMask(image,p) != 0)
1483 {
1484 p+=GetPixelChannels(composite_image);
1485 q+=GetPixelChannels(image);
1486 continue;
1487 }
cristy9d3d2792012-04-14 15:15:19 +00001488 switch (compose)
1489 {
1490 case ColorizeCompositeOp:
1491 case HueCompositeOp:
1492 case LuminizeCompositeOp:
1493 case ModulateCompositeOp:
1494 case SaturateCompositeOp:
1495 {
1496 GetPixelInfoPixel(composite_image,p,&source_pixel);
1497 GetPixelInfoPixel(image,q,&destination_pixel);
1498 break;
1499 }
1500 default:
1501 break;
1502 }
cristye4a40472011-12-22 02:56:19 +00001503 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1504 {
cristy564a5692012-01-20 23:56:26 +00001505 double
1506 sans;
1507
cristya19f1d72012-08-07 18:24:38 +00001508 double
cristye4a40472011-12-22 02:56:19 +00001509 pixel;
cristye10859a2011-12-18 22:28:59 +00001510
cristye4a40472011-12-22 02:56:19 +00001511 PixelChannel
1512 channel;
cristye10859a2011-12-18 22:28:59 +00001513
cristye4a40472011-12-22 02:56:19 +00001514 PixelTrait
1515 composite_traits,
1516 traits;
1517
cristycf1296e2012-08-26 23:40:49 +00001518 channel=GetPixelChannelChannel(image,i);
1519 traits=GetPixelChannelTraits(image,channel);
1520 composite_traits=GetPixelChannelTraits(composite_image,channel);
cristy0cd1f212012-01-05 15:45:59 +00001521 if (traits == UndefinedPixelTrait)
1522 continue;
cristya7b07912012-01-11 20:01:32 +00001523 if ((compose != IntensityCompositeOp) &&
cristye4a40472011-12-22 02:56:19 +00001524 (composite_traits == UndefinedPixelTrait))
1525 continue;
1526 /*
1527 Sc: source color.
cristye4a40472011-12-22 02:56:19 +00001528 Dc: destination color.
cristye4a40472011-12-22 02:56:19 +00001529 */
cristya19f1d72012-08-07 18:24:38 +00001530 Sc=(double) GetPixelChannel(composite_image,channel,p);
1531 Dc=(double) q[i];
cristye4a40472011-12-22 02:56:19 +00001532 if ((traits & CopyPixelTrait) != 0)
cristye10859a2011-12-18 22:28:59 +00001533 {
cristye4a40472011-12-22 02:56:19 +00001534 if (channel != AlphaPixelChannel)
1535 {
1536 /*
1537 Copy channel.
1538 */
1539 q[i]=ClampToQuantum(Sc);
cristye10859a2011-12-18 22:28:59 +00001540 continue;
cristye10859a2011-12-18 22:28:59 +00001541 }
cristye4a40472011-12-22 02:56:19 +00001542 /*
1543 Set alpha channel.
1544 */
cristye10859a2011-12-18 22:28:59 +00001545 switch (compose)
1546 {
cristyc8d63672012-01-11 13:03:13 +00001547 case AlphaCompositeOp:
1548 {
cristya7b07912012-01-11 20:01:32 +00001549 pixel=QuantumRange*Sa;
cristyc8d63672012-01-11 13:03:13 +00001550 break;
1551 }
cristye4a40472011-12-22 02:56:19 +00001552 case AtopCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001553 case CopyBlackCompositeOp:
1554 case CopyBlueCompositeOp:
1555 case CopyCyanCompositeOp:
1556 case CopyGreenCompositeOp:
1557 case CopyMagentaCompositeOp:
1558 case CopyRedCompositeOp:
1559 case CopyYellowCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001560 case SrcAtopCompositeOp:
1561 case DstCompositeOp:
1562 case NoCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001563 {
cristye4a40472011-12-22 02:56:19 +00001564 pixel=QuantumRange*Da;
cristye10859a2011-12-18 22:28:59 +00001565 break;
1566 }
cristye10859a2011-12-18 22:28:59 +00001567 case ChangeMaskCompositeOp:
1568 {
cristye4a40472011-12-22 02:56:19 +00001569 MagickBooleanType
1570 equivalent;
1571
cristya19f1d72012-08-07 18:24:38 +00001572 if (Da > ((double) QuantumRange/2.0))
cristy99abff32011-12-24 20:45:16 +00001573 {
cristya19f1d72012-08-07 18:24:38 +00001574 pixel=(double) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001575 break;
1576 }
cristye4a40472011-12-22 02:56:19 +00001577 equivalent=IsFuzzyEquivalencePixel(composite_image,p,image,q);
cristy99abff32011-12-24 20:45:16 +00001578 if (equivalent != MagickFalse)
cristye4a40472011-12-22 02:56:19 +00001579 {
cristya19f1d72012-08-07 18:24:38 +00001580 pixel=(double) TransparentAlpha;
cristye4a40472011-12-22 02:56:19 +00001581 break;
1582 }
cristya19f1d72012-08-07 18:24:38 +00001583 pixel=(double) OpaqueAlpha;
cristye4a40472011-12-22 02:56:19 +00001584 break;
1585 }
cristy99abff32011-12-24 20:45:16 +00001586 case ClearCompositeOp:
1587 {
cristya19f1d72012-08-07 18:24:38 +00001588 pixel=(double) TransparentAlpha;
cristy99abff32011-12-24 20:45:16 +00001589 break;
1590 }
1591 case ColorizeCompositeOp:
1592 case HueCompositeOp:
1593 case LuminizeCompositeOp:
1594 case SaturateCompositeOp:
1595 {
1596 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1597 {
1598 pixel=QuantumRange*Da;
1599 break;
1600 }
1601 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
1602 {
1603 pixel=QuantumRange*Sa;
1604 break;
1605 }
1606 if (Sa < Da)
1607 {
1608 pixel=QuantumRange*Da;
1609 break;
1610 }
1611 pixel=QuantumRange*Sa;
1612 break;
1613 }
cristy99abff32011-12-24 20:45:16 +00001614 case CopyAlphaCompositeOp:
cristy24d5d722012-05-17 12:27:27 +00001615 {
1616 pixel=QuantumRange*Sa;
cristy8a46d822012-08-28 23:32:39 +00001617 if (composite_image->alpha_trait != BlendPixelTrait)
cristy24d5d722012-05-17 12:27:27 +00001618 pixel=GetPixelIntensity(composite_image,p);
1619 break;
1620 }
1621 case CopyCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001622 case DisplaceCompositeOp:
1623 case DistortCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001624 case DstAtopCompositeOp:
1625 case ReplaceCompositeOp:
1626 case SrcCompositeOp:
1627 {
1628 pixel=QuantumRange*Sa;
1629 break;
1630 }
1631 case DarkenIntensityCompositeOp:
1632 {
cristy99abff32011-12-24 20:45:16 +00001633 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1634 (1.0-Da)*GetPixelIntensity(image,q) ? Sa : Da;
cristye4a40472011-12-22 02:56:19 +00001635 break;
1636 }
cristy98621462011-12-31 22:31:11 +00001637 case IntensityCompositeOp:
1638 {
cristyf13c5942012-08-08 23:50:11 +00001639 pixel=GetPixelIntensity(composite_image,p);
cristy98621462011-12-31 22:31:11 +00001640 break;
1641 }
cristye4a40472011-12-22 02:56:19 +00001642 case LightenIntensityCompositeOp:
1643 {
1644 pixel=Sa*GetPixelIntensity(composite_image,p) >
1645 Da*GetPixelIntensity(image,q) ? Sa : Da;
cristye10859a2011-12-18 22:28:59 +00001646 break;
1647 }
cristy99abff32011-12-24 20:45:16 +00001648 case ModulateCompositeOp:
1649 {
1650 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
1651 {
1652 pixel=QuantumRange*Da;
1653 break;
1654 }
1655 pixel=QuantumRange*Da;
1656 break;
1657 }
cristye10859a2011-12-18 22:28:59 +00001658 default:
1659 {
cristye4a40472011-12-22 02:56:19 +00001660 pixel=QuantumRange*alpha;
cristye10859a2011-12-18 22:28:59 +00001661 break;
1662 }
1663 }
cristye4a40472011-12-22 02:56:19 +00001664 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00001665 continue;
1666 }
1667 /*
cristy99abff32011-12-24 20:45:16 +00001668 Porter-Duff compositions:
1669 Sca: source normalized color multiplied by alpha.
1670 Dca: normalized destination color multiplied by alpha.
cristye10859a2011-12-18 22:28:59 +00001671 */
cristy99abff32011-12-24 20:45:16 +00001672 Sca=QuantumScale*Sa*Sc;
1673 Dca=QuantumScale*Da*Dc;
cristye10859a2011-12-18 22:28:59 +00001674 switch (compose)
1675 {
cristye10859a2011-12-18 22:28:59 +00001676 case DarkenCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001677 case LightenCompositeOp:
cristy99abff32011-12-24 20:45:16 +00001678 case ModulusSubtractCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001679 {
cristy99abff32011-12-24 20:45:16 +00001680 gamma=1.0-alpha;
cristye10859a2011-12-18 22:28:59 +00001681 break;
1682 }
1683 default:
1684 break;
1685 }
cristyc58380a2012-06-03 15:12:30 +00001686 gamma=MagickEpsilonReciprocal(alpha);
cristyd197cbb2012-01-13 02:14:12 +00001687 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001688 switch (compose)
1689 {
cristya7b07912012-01-11 20:01:32 +00001690 case AlphaCompositeOp:
1691 {
1692 pixel=QuantumRange*Sa;
1693 break;
1694 }
cristye4a40472011-12-22 02:56:19 +00001695 case AtopCompositeOp:
1696 case SrcAtopCompositeOp:
cristye10859a2011-12-18 22:28:59 +00001697 {
cristye4a40472011-12-22 02:56:19 +00001698 pixel=Sc*Sa+Dc*(1.0-Sa);
1699 break;
cristye10859a2011-12-18 22:28:59 +00001700 }
cristye4a40472011-12-22 02:56:19 +00001701 case BlendCompositeOp:
1702 {
1703 pixel=gamma*(source_dissolve*Sa*Sc+destination_dissolve*Da*Dc);
1704 break;
1705 }
1706 case BlurCompositeOp:
1707 case DisplaceCompositeOp:
1708 case DistortCompositeOp:
1709 case CopyCompositeOp:
1710 case ReplaceCompositeOp:
1711 case SrcCompositeOp:
1712 {
1713 pixel=Sc;
1714 break;
1715 }
1716 case BumpmapCompositeOp:
1717 {
cristy99abff32011-12-24 20:45:16 +00001718 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001719 {
1720 pixel=Dc;
1721 break;
1722 }
1723 pixel=QuantumScale*GetPixelIntensity(composite_image,p)*Dc;
1724 break;
1725 }
cristy99abff32011-12-24 20:45:16 +00001726 case ChangeMaskCompositeOp:
1727 {
1728 pixel=Dc;
1729 break;
1730 }
1731 case ClearCompositeOp:
1732 {
1733 pixel=0.0;
1734 break;
1735 }
cristye4a40472011-12-22 02:56:19 +00001736 case ColorBurnCompositeOp:
1737 {
1738 /*
1739 Refer to the March 2009 SVG specification.
1740 */
1741 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
1742 {
cristy99abff32011-12-24 20:45:16 +00001743 pixel=QuantumRange*gamma*(Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001744 break;
1745 }
1746 if (Sca < MagickEpsilon)
1747 {
cristy99abff32011-12-24 20:45:16 +00001748 pixel=QuantumRange*gamma*(Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001749 break;
1750 }
cristy99abff32011-12-24 20:45:16 +00001751 pixel=QuantumRange*gamma*(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+
1752 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001753 break;
1754 }
1755 case ColorDodgeCompositeOp:
1756 {
1757 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1758 {
cristy99abff32011-12-24 20:45:16 +00001759 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001760 break;
1761 }
1762 if (fabs(Sca-Sa) < MagickEpsilon)
1763 {
cristy99abff32011-12-24 20:45:16 +00001764 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001765 break;
1766 }
cristy99abff32011-12-24 20:45:16 +00001767 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001768 (1.0-Sa));
1769 break;
1770 }
1771 case ColorizeCompositeOp:
1772 {
cristy99abff32011-12-24 20:45:16 +00001773 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001774 {
1775 pixel=Dc;
1776 break;
1777 }
cristy99abff32011-12-24 20:45:16 +00001778 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001779 {
1780 pixel=Sc;
1781 break;
1782 }
cristy7133e642012-08-14 11:04:11 +00001783 CompositeHCL(destination_pixel.red,destination_pixel.green,
1784 destination_pixel.blue,&sans,&sans,&luma);
1785 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
1786 &hue,&chroma,&sans);
1787 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001788 switch (channel)
1789 {
1790 case RedPixelChannel: pixel=red; break;
1791 case GreenPixelChannel: pixel=green; break;
1792 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001793 default: pixel=Dc; break;
1794 }
1795 break;
1796 }
cristye4a40472011-12-22 02:56:19 +00001797 case CopyAlphaCompositeOp:
cristy98621462011-12-31 22:31:11 +00001798 case IntensityCompositeOp:
cristye4a40472011-12-22 02:56:19 +00001799 {
cristy24d5d722012-05-17 12:27:27 +00001800 pixel=Dc;
cristye4a40472011-12-22 02:56:19 +00001801 break;
1802 }
1803 case CopyBlackCompositeOp:
1804 {
cristyd197cbb2012-01-13 02:14:12 +00001805 if (channel == BlackPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001806 pixel=(double) GetPixelBlack(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001807 break;
1808 }
1809 case CopyBlueCompositeOp:
1810 case CopyYellowCompositeOp:
1811 {
cristyd197cbb2012-01-13 02:14:12 +00001812 if (channel == BluePixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001813 pixel=(double) GetPixelBlue(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001814 break;
1815 }
1816 case CopyGreenCompositeOp:
1817 case CopyMagentaCompositeOp:
1818 {
cristyd197cbb2012-01-13 02:14:12 +00001819 if (channel == GreenPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001820 pixel=(double) GetPixelGreen(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001821 break;
1822 }
1823 case CopyRedCompositeOp:
1824 case CopyCyanCompositeOp:
1825 {
cristyd197cbb2012-01-13 02:14:12 +00001826 if (channel == RedPixelChannel)
cristya19f1d72012-08-07 18:24:38 +00001827 pixel=(double) GetPixelRed(composite_image,p);
cristye4a40472011-12-22 02:56:19 +00001828 break;
1829 }
cristy99abff32011-12-24 20:45:16 +00001830 case DarkenCompositeOp:
1831 {
1832 /*
1833 Darken is equivalent to a 'Minimum' method
1834 OR a greyscale version of a binary 'Or'
1835 OR the 'Intersection' of pixel sets.
1836 */
1837 if (Sc < Dc)
1838 {
1839 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
1840 break;
1841 }
1842 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1843 break;
1844 }
cristye4a40472011-12-22 02:56:19 +00001845 case DarkenIntensityCompositeOp:
1846 {
cristy99abff32011-12-24 20:45:16 +00001847 pixel=(1.0-Sa)*GetPixelIntensity(composite_image,p) <
1848 (1.0-Da)*GetPixelIntensity(image,q) ? Sc : Dc;
cristye4a40472011-12-22 02:56:19 +00001849 break;
1850 }
1851 case DifferenceCompositeOp:
1852 {
1853 pixel=gamma*(Sa*Sc+Da*Dc-Sa*Da*2.0*MagickMin(Sc,Dc));
1854 break;
1855 }
1856 case DissolveCompositeOp:
1857 {
1858 pixel=gamma*(source_dissolve*Sa*Sc-source_dissolve*Sa*
1859 destination_dissolve*Da*Dc+destination_dissolve*Da*Dc);
1860 break;
1861 }
1862 case DivideDstCompositeOp:
1863 {
1864 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
1865 {
cristy99abff32011-12-24 20:45:16 +00001866 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001867 break;
1868 }
1869 if (fabs(Dca) < MagickEpsilon)
1870 {
cristy99abff32011-12-24 20:45:16 +00001871 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001872 break;
1873 }
cristy99abff32011-12-24 20:45:16 +00001874 pixel=QuantumRange*gamma*(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001875 break;
1876 }
1877 case DivideSrcCompositeOp:
1878 {
1879 if ((fabs(Dca) < MagickEpsilon) && (fabs(Sca) < MagickEpsilon))
1880 {
cristy99abff32011-12-24 20:45:16 +00001881 pixel=QuantumRange*gamma*(Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001882 break;
1883 }
1884 if (fabs(Sca) < MagickEpsilon)
1885 {
cristy99abff32011-12-24 20:45:16 +00001886 pixel=QuantumRange*gamma*(Da*Sa+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001887 break;
1888 }
cristy99abff32011-12-24 20:45:16 +00001889 pixel=QuantumRange*gamma*(Dca*Sa*Sa/Sca+Dca*(1.0-Sa)+Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00001890 break;
1891 }
1892 case DstAtopCompositeOp:
1893 {
1894 pixel=Dc*Da+Sc*(1.0-Da);
1895 break;
1896 }
1897 case DstCompositeOp:
1898 case NoCompositeOp:
1899 {
1900 pixel=Dc;
1901 break;
1902 }
1903 case DstInCompositeOp:
1904 {
1905 pixel=gamma*(Sa*Dc*Sa);
1906 break;
1907 }
1908 case DstOutCompositeOp:
1909 {
1910 pixel=gamma*(Da*Dc*(1.0-Sa));
1911 break;
1912 }
1913 case DstOverCompositeOp:
1914 {
1915 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
1916 break;
1917 }
1918 case ExclusionCompositeOp:
1919 {
cristy99abff32011-12-24 20:45:16 +00001920 pixel=QuantumRange*gamma*(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+
1921 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00001922 break;
1923 }
1924 case HardLightCompositeOp:
1925 {
1926 if ((2.0*Sca) < Sa)
1927 {
cristy99abff32011-12-24 20:45:16 +00001928 pixel=QuantumRange*gamma*(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*
cristye4a40472011-12-22 02:56:19 +00001929 (1.0-Sa));
1930 break;
1931 }
cristy99abff32011-12-24 20:45:16 +00001932 pixel=QuantumRange*gamma*(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+
cristye4a40472011-12-22 02:56:19 +00001933 Dca*(1.0-Sa));
1934 break;
1935 }
1936 case HueCompositeOp:
1937 {
cristy99abff32011-12-24 20:45:16 +00001938 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001939 {
1940 pixel=Dc;
1941 break;
1942 }
cristy99abff32011-12-24 20:45:16 +00001943 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00001944 {
1945 pixel=Sc;
1946 break;
1947 }
cristy7133e642012-08-14 11:04:11 +00001948 CompositeHCL(destination_pixel.red,destination_pixel.green,
1949 destination_pixel.blue,&hue,&chroma,&luma);
1950 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
cristye4a40472011-12-22 02:56:19 +00001951 &hue,&sans,&sans);
cristy7133e642012-08-14 11:04:11 +00001952 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00001953 switch (channel)
1954 {
1955 case RedPixelChannel: pixel=red; break;
1956 case GreenPixelChannel: pixel=green; break;
1957 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00001958 default: pixel=Dc; break;
1959 }
1960 break;
1961 }
1962 case InCompositeOp:
1963 case SrcInCompositeOp:
1964 {
1965 pixel=gamma*(Da*Sc*Da);
1966 break;
1967 }
cristy99abff32011-12-24 20:45:16 +00001968 case LinearBurnCompositeOp:
1969 {
1970 /*
1971 LinearBurn: as defined by Abode Photoshop, according to
1972 http://www.simplefilter.de/en/basics/mixmods.html is:
1973
1974 f(Sc,Dc) = Sc + Dc - 1
1975 */
1976 pixel=QuantumRange*gamma*(Sca+Dca-Sa*Da);
1977 break;
1978 }
1979 case LinearDodgeCompositeOp:
1980 {
1981 pixel=QuantumRange*gamma*(Sa*Sc+Da*Dc);
1982 break;
1983 }
1984 case LinearLightCompositeOp:
1985 {
1986 /*
1987 LinearLight: as defined by Abode Photoshop, according to
1988 http://www.simplefilter.de/en/basics/mixmods.html is:
1989
1990 f(Sc,Dc) = Dc + 2*Sc - 1
1991 */
1992 pixel=QuantumRange*gamma*((Sca-Sa)*Da+Sca+Dca);
1993 break;
1994 }
1995 case LightenCompositeOp:
1996 {
1997 if (Sc > Dc)
1998 {
cristy24d5d722012-05-17 12:27:27 +00001999 pixel=gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristy99abff32011-12-24 20:45:16 +00002000 break;
2001 }
cristy24d5d722012-05-17 12:27:27 +00002002 pixel=gamma*(Da*Dc-Da*Sa*Sc+Sa*Sc);
cristy99abff32011-12-24 20:45:16 +00002003 break;
2004 }
cristye4a40472011-12-22 02:56:19 +00002005 case LightenIntensityCompositeOp:
2006 {
2007 /*
2008 Lighten is equivalent to a 'Maximum' method
2009 OR a greyscale version of a binary 'And'
2010 OR the 'Union' of pixel sets.
2011 */
2012 pixel=Sa*GetPixelIntensity(composite_image,p) >
2013 Da*GetPixelIntensity(image,q) ? Sc : Dc;
2014 break;
2015 }
cristye4a40472011-12-22 02:56:19 +00002016 case LuminizeCompositeOp:
2017 {
cristy99abff32011-12-24 20:45:16 +00002018 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002019 {
2020 pixel=Dc;
2021 break;
2022 }
cristy99abff32011-12-24 20:45:16 +00002023 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002024 {
2025 pixel=Sc;
2026 break;
2027 }
cristy7133e642012-08-14 11:04:11 +00002028 CompositeHCL(destination_pixel.red,destination_pixel.green,
2029 destination_pixel.blue,&hue,&chroma,&luma);
2030 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2031 &sans,&sans,&luma);
2032 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002033 switch (channel)
2034 {
2035 case RedPixelChannel: pixel=red; break;
2036 case GreenPixelChannel: pixel=green; break;
2037 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002038 default: pixel=Dc; break;
2039 }
2040 break;
2041 }
2042 case MathematicsCompositeOp:
2043 {
2044 /*
2045 'Mathematics' a free form user control mathematical composition
2046 is defined as...
2047
2048 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
2049
2050 Where the arguments A,B,C,D are (currently) passed to composite
2051 as a command separated 'geometry' string in "compose:args" image
2052 artifact.
2053
2054 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
2055
2056 Applying the SVG transparency formula (see above), we get...
2057
2058 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
2059
2060 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
2061 Dca*(1.0-Sa)
2062 */
2063 pixel=gamma*geometry_info.rho*Sa*Sc*Da*Dc+geometry_info.sigma*
2064 Sa*Sc*Da+geometry_info.xi*Da*Dc*Sa+geometry_info.psi*Sa*Da+
2065 Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa);
2066 break;
2067 }
2068 case MinusDstCompositeOp:
2069 {
2070 pixel=gamma*(Sa*Sc+Da*Dc-2.0*Da*Dc*Sa);
2071 break;
2072 }
2073 case MinusSrcCompositeOp:
2074 {
2075 /*
2076 Minus source from destination.
2077
2078 f(Sc,Dc) = Sc - Dc
2079 */
cristy99abff32011-12-24 20:45:16 +00002080 pixel=QuantumRange*gamma*(Da*Dc+Sa*Sc-2.0*Sa*Sc*Da);
cristye4a40472011-12-22 02:56:19 +00002081 break;
2082 }
2083 case ModulateCompositeOp:
2084 {
2085 ssize_t
2086 offset;
2087
cristy99abff32011-12-24 20:45:16 +00002088 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002089 {
2090 pixel=Dc;
2091 break;
2092 }
2093 offset=(ssize_t) (GetPixelIntensity(composite_image,p)-midpoint);
2094 if (offset == 0)
2095 {
2096 pixel=Dc;
2097 break;
2098 }
cristy7133e642012-08-14 11:04:11 +00002099 CompositeHCL(destination_pixel.red,destination_pixel.green,
2100 destination_pixel.blue,&hue,&chroma,&luma);
2101 luma+=(0.01*percent_luma*offset)/midpoint;
2102 chroma*=0.01*percent_chroma;
2103 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002104 switch (channel)
2105 {
2106 case RedPixelChannel: pixel=red; break;
2107 case GreenPixelChannel: pixel=green; break;
2108 case BluePixelChannel: pixel=blue; break;
2109 default: pixel=Dc; break;
2110 }
2111 break;
2112 }
2113 case ModulusAddCompositeOp:
2114 {
2115 pixel=Sc+Dc;
2116 if (pixel > QuantumRange)
2117 pixel-=(QuantumRange+1.0);
2118 pixel=gamma*(pixel*Sa*Da+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
2119 break;
2120 }
2121 case ModulusSubtractCompositeOp:
2122 {
cristy99abff32011-12-24 20:45:16 +00002123 pixel=Sc-Dc;
cristye4a40472011-12-22 02:56:19 +00002124 if (pixel < 0.0)
2125 pixel+=(QuantumRange+1.0);
2126 pixel=gamma*(pixel*Sa*Da+Sa*Sc*(1.0-Da)+Da*Dc*(1.0-Sa));
2127 break;
2128 }
2129 case MultiplyCompositeOp:
2130 {
cristy99abff32011-12-24 20:45:16 +00002131 pixel=QuantumRange*gamma*(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002132 break;
2133 }
2134 case OutCompositeOp:
2135 case SrcOutCompositeOp:
2136 {
2137 pixel=gamma*(Sa*Sc*(1.0-Da));
2138 break;
2139 }
2140 case OverCompositeOp:
2141 case SrcOverCompositeOp:
2142 {
cristy99abff32011-12-24 20:45:16 +00002143 pixel=QuantumRange*gamma*(Sa*Sc-Sa*Da*Dc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002144 break;
2145 }
2146 case OverlayCompositeOp:
2147 {
2148 if ((2.0*Dca) < Da)
cristy99abff32011-12-24 20:45:16 +00002149 {
2150 pixel=QuantumRange*gamma*(2.0*Dca*Sca+Dca*(1.0-Sa)+Sca*
2151 (1.0-Da));
2152 break;
2153 }
2154 pixel=QuantumRange*gamma*(Da*Sa-2.0*(Sa-Sca)*(Da-Dca)+Dca*(1.0-Sa)+
2155 Sca*(1.0-Da));
cristye4a40472011-12-22 02:56:19 +00002156 break;
2157 }
2158 case PegtopLightCompositeOp:
2159 {
2160 /*
2161 PegTop: A Soft-Light alternative: A continuous version of the
2162 Softlight function, producing very similar results.
2163
2164 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
2165
2166 http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
2167 */
2168 if (fabs(Da) < MagickEpsilon)
2169 {
cristy99abff32011-12-24 20:45:16 +00002170 pixel=QuantumRange*gamma*(Sca);
cristye4a40472011-12-22 02:56:19 +00002171 break;
2172 }
cristy99abff32011-12-24 20:45:16 +00002173 pixel=QuantumRange*gamma*(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-
2174 Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002175 break;
2176 }
2177 case PinLightCompositeOp:
2178 {
2179 /*
2180 PinLight: A Photoshop 7 composition method
2181 http://www.simplefilter.de/en/basics/mixmods.html
2182
2183 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
2184 */
2185 if ((Dca*Sa) < (Da*(2.0*Sca-Sa)))
2186 {
cristy99abff32011-12-24 20:45:16 +00002187 pixel=QuantumRange*gamma*(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002188 break;
2189 }
2190 if ((Dca*Sa) > (2.0*Sca*Da))
2191 {
cristy99abff32011-12-24 20:45:16 +00002192 pixel=QuantumRange*gamma*(Sca*Da+Sca+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002193 break;
2194 }
cristy99abff32011-12-24 20:45:16 +00002195 pixel=QuantumRange*gamma*(Sca*(1.0-Da)+Dca);
cristye4a40472011-12-22 02:56:19 +00002196 break;
2197 }
2198 case PlusCompositeOp:
2199 {
cristy24d5d722012-05-17 12:27:27 +00002200 pixel=gamma*(Sa*Sc+Da*Dc);
cristye4a40472011-12-22 02:56:19 +00002201 break;
2202 }
2203 case SaturateCompositeOp:
2204 {
cristy99abff32011-12-24 20:45:16 +00002205 if (fabs(QuantumRange*Sa-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002206 {
2207 pixel=Dc;
2208 break;
2209 }
cristy99abff32011-12-24 20:45:16 +00002210 if (fabs(QuantumRange*Da-TransparentAlpha) < MagickEpsilon)
cristye4a40472011-12-22 02:56:19 +00002211 {
2212 pixel=Sc;
2213 break;
2214 }
cristy7133e642012-08-14 11:04:11 +00002215 CompositeHCL(destination_pixel.red,destination_pixel.green,
2216 destination_pixel.blue,&hue,&chroma,&luma);
2217 CompositeHCL(source_pixel.red,source_pixel.green,source_pixel.blue,
2218 &sans,&chroma,&sans);
2219 HCLComposite(hue,chroma,luma,&red,&green,&blue);
cristye4a40472011-12-22 02:56:19 +00002220 switch (channel)
2221 {
2222 case RedPixelChannel: pixel=red; break;
2223 case GreenPixelChannel: pixel=green; break;
2224 case BluePixelChannel: pixel=blue; break;
cristye4a40472011-12-22 02:56:19 +00002225 default: pixel=Dc; break;
2226 }
2227 break;
2228 }
2229 case ScreenCompositeOp:
2230 {
2231 /*
2232 Screen: a negated multiply:
2233
2234 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
2235 */
cristy99abff32011-12-24 20:45:16 +00002236 pixel=QuantumRange*gamma*(Sca+Dca-Sca*Dca);
cristye4a40472011-12-22 02:56:19 +00002237 break;
2238 }
2239 case SoftLightCompositeOp:
2240 {
2241 /*
2242 Refer to the March 2009 SVG specification.
2243 */
2244 if ((2.0*Sca) < Sa)
2245 {
cristy99abff32011-12-24 20:45:16 +00002246 pixel=QuantumRange*gamma*(Dca*(Sa+(2.0*Sca-Sa)*(1.0-(Dca/Da)))+
2247 Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002248 break;
2249 }
2250 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
2251 {
cristy99abff32011-12-24 20:45:16 +00002252 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*(Dca/Da)*
2253 (4.0*(Dca/Da)+1.0)*((Dca/Da)-1.0)+7.0*(Dca/Da))+Sca*(1.0-Da)+
2254 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002255 break;
2256 }
cristy99abff32011-12-24 20:45:16 +00002257 pixel=QuantumRange*gamma*(Dca*Sa+Da*(2.0*Sca-Sa)*(pow((Dca/Da),0.5)-
2258 (Dca/Da))+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002259 break;
2260 }
2261 case ThresholdCompositeOp:
2262 {
cristya19f1d72012-08-07 18:24:38 +00002263 double
cristye4a40472011-12-22 02:56:19 +00002264 delta;
2265
2266 delta=Sc-Dc;
cristya19f1d72012-08-07 18:24:38 +00002267 if ((double) fabs((double) (2.0*delta)) < threshold)
cristye4a40472011-12-22 02:56:19 +00002268 {
2269 pixel=gamma*Dc;
2270 break;
2271 }
2272 pixel=gamma*(Dc+delta*amount);
2273 break;
2274 }
2275 case VividLightCompositeOp:
2276 {
2277 /*
2278 VividLight: A Photoshop 7 composition method. See
2279 http://www.simplefilter.de/en/basics/mixmods.html.
2280
2281 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
2282 */
2283 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
2284 {
cristy99abff32011-12-24 20:45:16 +00002285 pixel=QuantumRange*gamma*(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002286 break;
2287 }
2288 if ((2.0*Sca) <= Sa)
2289 {
cristy99abff32011-12-24 20:45:16 +00002290 pixel=QuantumRange*gamma*(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*
2291 (1.0-Da)+Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002292 break;
2293 }
cristy99abff32011-12-24 20:45:16 +00002294 pixel=QuantumRange*gamma*(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+
2295 Dca*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002296 break;
2297 }
2298 case XorCompositeOp:
2299 {
cristy99abff32011-12-24 20:45:16 +00002300 pixel=QuantumRange*gamma*(Sc*Sa*(1.0-Da)+Dc*Da*(1.0-Sa));
cristye4a40472011-12-22 02:56:19 +00002301 break;
2302 }
2303 default:
2304 {
2305 pixel=Sc;
2306 break;
2307 }
2308 }
2309 q[i]=ClampToQuantum(pixel);
cristye10859a2011-12-18 22:28:59 +00002310 }
cristyed231572011-07-14 02:18:59 +00002311 p+=GetPixelChannels(composite_image);
cristye4a40472011-12-22 02:56:19 +00002312 channels=GetPixelChannels(composite_image);
2313 if (p >= (pixels+channels*composite_image->columns))
cristy4c08aed2011-07-01 19:47:50 +00002314 p=pixels;
cristyed231572011-07-14 02:18:59 +00002315 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002316 }
2317 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2318 status=MagickFalse;
2319 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2320 {
2321 MagickBooleanType
2322 proceed;
2323
2324#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002325 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002326#endif
2327 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2328 image->rows);
2329 if (proceed == MagickFalse)
2330 status=MagickFalse;
2331 }
2332 }
2333 composite_view=DestroyCacheView(composite_view);
2334 image_view=DestroyCacheView(image_view);
2335 if (destination_image != (Image * ) NULL)
2336 destination_image=DestroyImage(destination_image);
cristyf661c4d2012-07-28 12:57:17 +00002337 else
2338 composite_image=DestroyImage(composite_image);
cristy191c0b72012-08-12 16:29:52 +00002339 if (status != MagickFalse)
2340 (void) ClampImage(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00002341 return(status);
2342}
2343
2344/*
2345%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2346% %
2347% %
2348% %
2349% T e x t u r e I m a g e %
2350% %
2351% %
2352% %
2353%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2354%
2355% TextureImage() repeatedly tiles the texture image across and down the image
2356% canvas.
2357%
2358% The format of the TextureImage method is:
2359%
cristy30d8c942012-02-07 13:44:59 +00002360% MagickBooleanType TextureImage(Image *image,const Image *texture,
cristye941a752011-10-15 01:52:48 +00002361% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002362%
2363% A description of each parameter follows:
2364%
2365% o image: the image.
2366%
cristye6178502011-12-23 17:02:29 +00002367% o texture_image: This image is the texture to layer on the background.
cristy4c08aed2011-07-01 19:47:50 +00002368%
2369*/
cristy30d8c942012-02-07 13:44:59 +00002370MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2371 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002372{
2373#define TextureImageTag "Texture/Image"
2374
2375 CacheView
2376 *image_view,
2377 *texture_view;
2378
cristy30d8c942012-02-07 13:44:59 +00002379 Image
2380 *texture_image;
2381
cristy4c08aed2011-07-01 19:47:50 +00002382 MagickBooleanType
2383 status;
2384
2385 ssize_t
2386 y;
2387
2388 assert(image != (Image *) NULL);
2389 if (image->debug != MagickFalse)
2390 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2391 assert(image->signature == MagickSignature);
cristy30d8c942012-02-07 13:44:59 +00002392 if (texture == (const Image *) NULL)
2393 return(MagickFalse);
2394 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2395 return(MagickFalse);
2396 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
cristye6178502011-12-23 17:02:29 +00002397 if (texture_image == (const Image *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002398 return(MagickFalse);
cristya865ccd2012-07-28 00:33:10 +00002399 (void) TransformImageColorspace(texture_image,image->colorspace,exception);
cristy387430f2012-02-07 13:09:46 +00002400 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod,
2401 exception);
cristy4c08aed2011-07-01 19:47:50 +00002402 status=MagickTrue;
2403 if ((image->compose != CopyCompositeOp) &&
cristy8a46d822012-08-28 23:32:39 +00002404 ((image->compose != OverCompositeOp) || (image->alpha_trait == BlendPixelTrait) ||
2405 (texture_image->alpha_trait == BlendPixelTrait)))
cristy4c08aed2011-07-01 19:47:50 +00002406 {
2407 /*
2408 Tile texture onto the image background.
2409 */
2410#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002411 #pragma omp parallel for schedule(static) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002412 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00002413#endif
cristye6178502011-12-23 17:02:29 +00002414 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
cristy4c08aed2011-07-01 19:47:50 +00002415 {
2416 register ssize_t
2417 x;
2418
2419 if (status == MagickFalse)
2420 continue;
cristye6178502011-12-23 17:02:29 +00002421 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002422 {
2423 MagickBooleanType
2424 thread_status;
2425
cristyfeb3e962012-03-29 17:25:55 +00002426 thread_status=CompositeImage(image,texture_image,image->compose,
2427 MagickFalse,x+texture_image->tile_offset.x,y+
2428 texture_image->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002429 if (thread_status == MagickFalse)
2430 {
2431 status=thread_status;
2432 break;
2433 }
2434 }
2435 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2436 {
2437 MagickBooleanType
2438 proceed;
2439
2440#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd0435b2012-01-05 16:20:47 +00002441 #pragma omp critical (MagickCore_TextureImage)
cristy4c08aed2011-07-01 19:47:50 +00002442#endif
2443 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2444 y,image->rows);
2445 if (proceed == MagickFalse)
2446 status=MagickFalse;
2447 }
2448 }
2449 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2450 image->rows,image->rows);
cristy30d8c942012-02-07 13:44:59 +00002451 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002452 return(status);
2453 }
2454 /*
2455 Tile texture onto the image background (optimized).
2456 */
2457 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +00002458 texture_view=AcquireVirtualCacheView(texture_image,exception);
2459 image_view=AcquireAuthenticCacheView(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00002460#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002461 #pragma omp parallel for schedule(static) shared(status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002462 dynamic_number_threads(image,image->columns,image->rows,1)
cristy4c08aed2011-07-01 19:47:50 +00002463#endif
2464 for (y=0; y < (ssize_t) image->rows; y++)
2465 {
2466 MagickBooleanType
2467 sync;
2468
2469 register const Quantum
2470 *p,
2471 *pixels;
2472
2473 register ssize_t
2474 x;
2475
2476 register Quantum
2477 *q;
2478
2479 size_t
2480 width;
2481
2482 if (status == MagickFalse)
2483 continue;
cristye6178502011-12-23 17:02:29 +00002484 pixels=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,
2485 (y+texture_image->tile_offset.y) % texture_image->rows,
2486 texture_image->columns,1,exception);
2487 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002488 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2489 {
2490 status=MagickFalse;
2491 continue;
2492 }
cristye6178502011-12-23 17:02:29 +00002493 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
cristy4c08aed2011-07-01 19:47:50 +00002494 {
2495 register ssize_t
cristye6178502011-12-23 17:02:29 +00002496 j;
cristy4c08aed2011-07-01 19:47:50 +00002497
2498 p=pixels;
cristye6178502011-12-23 17:02:29 +00002499 width=texture_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002500 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2501 width=image->columns-x;
cristye6178502011-12-23 17:02:29 +00002502 for (j=0; j < (ssize_t) width; j++)
cristy4c08aed2011-07-01 19:47:50 +00002503 {
cristye6178502011-12-23 17:02:29 +00002504 register ssize_t
2505 i;
2506
cristy10a6c612012-01-29 21:41:05 +00002507 if (GetPixelMask(image,p) != 0)
2508 {
2509 p+=GetPixelChannels(texture_image);
2510 q+=GetPixelChannels(image);
2511 continue;
2512 }
cristye6178502011-12-23 17:02:29 +00002513 for (i=0; i < (ssize_t) GetPixelChannels(texture_image); i++)
2514 {
2515 PixelChannel
2516 channel;
2517
2518 PixelTrait
2519 texture_traits,
2520 traits;
2521
cristycf1296e2012-08-26 23:40:49 +00002522 channel=GetPixelChannelChannel(texture_image,i);
2523 texture_traits=GetPixelChannelTraits(texture_image,channel);
2524 traits=GetPixelChannelTraits(image,channel);
cristye6178502011-12-23 17:02:29 +00002525 if ((traits == UndefinedPixelTrait) ||
2526 (texture_traits == UndefinedPixelTrait))
2527 continue;
2528 SetPixelChannel(image,channel,p[i],q);
2529 }
2530 p+=GetPixelChannels(texture_image);
cristyed231572011-07-14 02:18:59 +00002531 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002532 }
2533 }
2534 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2535 if (sync == MagickFalse)
2536 status=MagickFalse;
2537 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2538 {
2539 MagickBooleanType
2540 proceed;
2541
2542#if defined(MAGICKCORE_OPENMP_SUPPORT)
2543 #pragma omp critical (MagickCore_TextureImage)
2544#endif
2545 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2546 image->rows);
2547 if (proceed == MagickFalse)
2548 status=MagickFalse;
2549 }
2550 }
2551 texture_view=DestroyCacheView(texture_view);
2552 image_view=DestroyCacheView(image_view);
cristy30d8c942012-02-07 13:44:59 +00002553 texture_image=DestroyImage(texture_image);
cristy4c08aed2011-07-01 19:47:50 +00002554 return(status);
2555}