blob: eea733b16602e8170cce708bf9753a3422f2b64c [file] [log] [blame]
cristy4c08aed2011-07-01 19:47:50 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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% %
20% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
21% 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"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/client.h"
48#include "MagickCore/color.h"
49#include "MagickCore/color-private.h"
50#include "MagickCore/colorspace.h"
51#include "MagickCore/colorspace-private.h"
52#include "MagickCore/composite.h"
53#include "MagickCore/composite-private.h"
54#include "MagickCore/constitute.h"
55#include "MagickCore/draw.h"
56#include "MagickCore/fx.h"
57#include "MagickCore/gem.h"
58#include "MagickCore/geometry.h"
59#include "MagickCore/image.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/monitor.h"
64#include "MagickCore/monitor-private.h"
65#include "MagickCore/memory_.h"
66#include "MagickCore/option.h"
67#include "MagickCore/pixel-accessor.h"
68#include "MagickCore/property.h"
69#include "MagickCore/quantum.h"
70#include "MagickCore/resample.h"
71#include "MagickCore/resource_.h"
72#include "MagickCore/string_.h"
73#include "MagickCore/thread-private.h"
74#include "MagickCore/utility.h"
75#include "MagickCore/version.h"
76
77/*
78%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79% %
80% %
81% %
cristyf4ad9df2011-07-08 16:49:03 +000082% C o m p o s i t e I m a g e %
cristy4c08aed2011-07-01 19:47:50 +000083% %
84% %
85% %
86%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87%
cristyf4ad9df2011-07-08 16:49:03 +000088% CompositeImage() returns the second image composited onto the first
cristy4c08aed2011-07-01 19:47:50 +000089% at the specified offset, using the specified composite method.
90%
cristyf4ad9df2011-07-08 16:49:03 +000091% The format of the CompositeImage method is:
cristy4c08aed2011-07-01 19:47:50 +000092%
93% MagickBooleanType CompositeImage(Image *image,
94% const CompositeOperator compose,Image *composite_image,
95% const ssize_t x_offset,const ssize_t y_offset)
cristy4c08aed2011-07-01 19:47:50 +000096%
97% A description of each parameter follows:
98%
99% o image: the destination image, modified by he composition
100%
cristy4c08aed2011-07-01 19:47:50 +0000101% o compose: This operator affects how the composite is applied to
102% the image. The operators and how they are utilized are listed here
103% http://www.w3.org/TR/SVG12/#compositing.
104%
105% o composite_image: the composite (source) image.
106%
107% o x_offset: the column offset of the composited image.
108%
109% o y_offset: the row offset of the composited image.
110%
111% Extra Controls from Image meta-data in 'composite_image' (artifacts)
112%
113% o "compose:args"
114% A string containing extra numerical arguments for specific compose
115% methods, generally expressed as a 'geometry' or a comma separated list
116% of numbers.
117%
118% Compose methods needing such arguments include "BlendCompositeOp" and
119% "DisplaceCompositeOp".
120%
121% o "compose:outside-overlay"
122% Modify how the composition is to effect areas not directly covered
123% by the 'composite_image' at the offset given. Normally this is
124% dependant on the 'compose' method, especially Duff-Porter methods.
125%
126% If set to "false" then disable all normal handling of pixels not
127% covered by the composite_image. Typically used for repeated tiling
128% of the composite_image by the calling API.
129%
130% Previous to IM v6.5.3-3 this was called "modify-outside-overlay"
131%
132*/
133
134static inline double MagickMin(const double x,const double y)
135{
136 if (x < y)
137 return(x);
138 return(y);
139}
140static inline double MagickMax(const double x,const double y)
141{
142 if (x > y)
143 return(x);
144 return(y);
145}
146
147/*
148 Programmers notes on SVG specification.
149
150 A Composition is defined by...
151 Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
152 Blending areas : X = 1 for area of overlap ie: f(Sc,Dc)
153 Y = 1 for source preserved
154 Z = 1 for destination preserved
155
156 Conversion to transparency (then optimized)
157 Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
158 Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
159
160 Where...
161 Sca = Sc*Sa normalized Source color divided by Source alpha
162 Dca = Dc*Da normalized Dest color divided by Dest alpha
163 Dc' = Dca'/Da' the desired color value for this channel.
164
165 Da' in in the follow formula as 'gamma' The resulting alpla value.
166
167 Most functions use a blending mode of over (X=1,Y=1,Z=1)
168 this results in the following optimizations...
169 gamma = Sa+Da-Sa*Da;
170 gamma = 1 - QuantiumScale*alpha * QuantiumScale*beta;
171 opacity = QuantiumScale*alpha*beta; // over blend, optimized 1-Gamma
172
173 The above SVG definitions also definate that Mathematical Composition
174 methods should use a 'Over' blending mode for Alpha Channel.
175 It however was not applied for composition modes of 'Plus', 'Minus',
176 the modulus versions of 'Add' and 'Subtract'.
177
178 Mathematical operator changes to be applied from IM v6.7...
179
180 1/ Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
181 'ModulusAdd' and 'ModulusSubtract' for clarity.
182
183 2/ All mathematical compositions work as per the SVG specification
184 with regard to blending. This now includes 'ModulusAdd' and
185 'ModulusSubtract'.
186
187 3/ When the special channel flag 'sync' (syncronize channel updates)
188 is turned off (enabled by default) then mathematical compositions are
189 only performed on the channels specified, and are applied
190 independantally of each other. In other words the mathematics is
191 performed as 'pure' mathematical operations, rather than as image
192 operations.
193*/
194
195static inline MagickRealType Atop(const MagickRealType p,
196 const MagickRealType Sa,const MagickRealType q,
197 const MagickRealType magick_unused(Da))
198{
199 return(p*Sa+q*(1.0-Sa)); /* Da optimized out, Da/gamma => 1.0 */
200}
201
202static inline void CompositeAtop(const PixelInfo *p,const PixelInfo *q,
203 PixelInfo *composite)
204{
205 MagickRealType
206 Sa;
207
208 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
209 composite->alpha=q->alpha; /* optimized Da = 1.0-Gamma */
210 composite->red=Atop(p->red,Sa,q->red,1.0);
211 composite->green=Atop(p->green,Sa,q->green,1.0);
212 composite->blue=Atop(p->blue,Sa,q->blue,1.0);
213 if (q->colorspace == CMYKColorspace)
214 composite->black=Atop(p->black,Sa,q->black,1.0);
215}
216
217/*
218 What is this Composition method for? Can't find any specification!
219 WARNING this is not doing correct 'over' blend handling (Anthony Thyssen).
220*/
221static inline void CompositeBumpmap(const PixelInfo *p,const PixelInfo *q,
222 PixelInfo *composite)
223{
224 MagickRealType
225 intensity;
226
227 intensity=(MagickRealType) GetPixelInfoIntensity(p);
228 composite->red=QuantumScale*intensity*q->red;
229 composite->green=QuantumScale*intensity*q->green;
230 composite->blue=QuantumScale*intensity*q->blue;
231 composite->alpha=(MagickRealType) QuantumScale*intensity*p->alpha;
232 if (q->colorspace == CMYKColorspace)
233 composite->black=QuantumScale*intensity*q->black;
234}
235
236static inline void CompositeClear(const PixelInfo *q,PixelInfo *composite)
237{
238 composite->alpha=(MagickRealType) TransparentAlpha;
239 composite->red=0.0;
240 composite->green=0.0;
241 composite->blue=0.0;
242 if (q->colorspace == CMYKColorspace)
243 composite->black=0.0;
244}
245
246static MagickRealType ColorBurn(const MagickRealType Sca,
247 const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da)
248{
249 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
250 return(Sa*Da+Dca*(1.0-Sa));
251 if (Sca < MagickEpsilon)
252 return(Dca*(1.0-Sa));
253 return(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
254}
255
256static inline void CompositeColorBurn(const PixelInfo *p,const PixelInfo *q,
257 PixelInfo *composite)
258{
259 MagickRealType
260 Da,
261 gamma,
262 Sa;
263
264 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
265 Da=QuantumScale*q->alpha;
266 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
267 composite->alpha=(MagickRealType) QuantumRange*gamma;
268 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
269 composite->red=gamma*ColorBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
270 q->red*Da,Da);
271 composite->green=gamma*ColorBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
272 q->green*Da,Da);
273 composite->blue=gamma*ColorBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
274 q->blue*Da,Da);
275 if (q->colorspace == CMYKColorspace)
276 composite->black=gamma*ColorBurn(QuantumScale*p->black*Sa,Sa,QuantumScale*
277 q->black*Da,Da);
278}
279
280
281static MagickRealType ColorDodge(const MagickRealType Sca,
282 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
283{
284 /*
285 Working from first principles using the original formula:
286
287 f(Sc,Dc) = Dc/(1-Sc)
288
289 This works correctly! Looks like the 2004 SVG model was right but just
290 required a extra condition for correct handling.
291 */
292 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
293 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
294 if (fabs(Sca-Sa) < MagickEpsilon)
295 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
296 return(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
297}
298
299static inline void CompositeColorDodge(const PixelInfo *p,const PixelInfo *q,
300 PixelInfo *composite)
301{
302 MagickRealType
303 Da,
304 gamma,
305 Sa;
306
307 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
308 Da=QuantumScale*q->alpha;
309 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
310 composite->alpha=(MagickRealType) QuantumRange*gamma;
311 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
312 composite->red=gamma*ColorDodge(QuantumScale*p->red*Sa,Sa,QuantumScale*
313 q->red*Da,Da);
314 composite->green=gamma*ColorDodge(QuantumScale*p->green*Sa,Sa,QuantumScale*
315 q->green*Da,Da);
316 composite->blue=gamma*ColorDodge(QuantumScale*p->blue*Sa,Sa,QuantumScale*
317 q->blue*Da,Da);
318 if (q->colorspace == CMYKColorspace)
319 composite->black=gamma*ColorDodge(QuantumScale*p->black*Sa,Sa,QuantumScale*
320 q->black*Da,Da);
321}
322
323static inline MagickRealType Darken(const MagickRealType p,
324 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
325{
326 if (p < q)
327 return(MagickOver_(p,alpha,q,beta)); /* src-over */
328 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
329}
330
cristy2b9582a2011-07-04 17:38:56 +0000331static inline void CompositeDarken(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000332 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000333{
334 MagickRealType
335 gamma;
336
337 /*
338 Darken is equivalent to a 'Minimum' method OR a greyscale version of a
339 binary 'Or' OR the 'Intersection' of pixel sets.
340 */
cristyfefab1b2011-07-05 00:33:22 +0000341 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000342 {
343 /*
344 Handle channels as separate grayscale channels.
345 */
cristy2b9582a2011-07-04 17:38:56 +0000346 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000347 composite->red=MagickMin(p->red,q->red);
cristy2b9582a2011-07-04 17:38:56 +0000348 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000349 composite->green=MagickMin(p->green,q->green);
cristy2b9582a2011-07-04 17:38:56 +0000350 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000351 composite->blue=MagickMin(p->blue,q->blue);
cristy2b9582a2011-07-04 17:38:56 +0000352 if ((GetPixelBlackTraits(image) & ActivePixelTrait) != 0 &&
cristy4c08aed2011-07-01 19:47:50 +0000353 (q->colorspace == CMYKColorspace))
354 composite->black=MagickMin(p->black,q->black);
cristy2b9582a2011-07-04 17:38:56 +0000355 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000356 composite->alpha=MagickMax(p->alpha,q->alpha);
357 return;
358 }
359 composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */
360 gamma=1.0-QuantumScale*composite->alpha;
361 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
362 composite->red=gamma*Darken(p->red,p->alpha,q->red,q->alpha);
363 composite->green=gamma*Darken(p->green,p->alpha,q->green,q->alpha);
364 composite->blue=gamma*Darken(p->blue,p->alpha,q->blue,q->alpha);
365 if (q->colorspace == CMYKColorspace)
366 composite->black=gamma*Darken(p->black,p->alpha,q->black,q->alpha);
367}
368
cristy2b9582a2011-07-04 17:38:56 +0000369static inline void CompositeDarkenIntensity(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +0000370 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000371{
372 MagickRealType
373 Da,
374 Sa;
375
376 /*
377 Select the pixel based on the intensity level.
378 If 'Sync' flag select whole pixel based on alpha weighted intensity.
379 Otherwise use intensity only, but restrict copy according to channel.
380 */
cristyfefab1b2011-07-05 00:33:22 +0000381 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000382 {
383 MagickBooleanType
384 from_p;
385
386 from_p=GetPixelInfoIntensity(p) < GetPixelInfoIntensity(q) ? MagickTrue :
387 MagickFalse;
cristy2b9582a2011-07-04 17:38:56 +0000388 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000389 composite->red=from_p != MagickFalse ? p->red : q->red;
cristy2b9582a2011-07-04 17:38:56 +0000390 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000391 composite->green=from_p != MagickFalse ? p->green : q->green;
cristy2b9582a2011-07-04 17:38:56 +0000392 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000393 composite->blue=from_p != MagickFalse ? p->blue : q->blue;
cristy2b9582a2011-07-04 17:38:56 +0000394 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000395 (q->colorspace == CMYKColorspace))
396 composite->black=from_p != MagickFalse ? p->black : q->black;
cristy2b9582a2011-07-04 17:38:56 +0000397 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000398 composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha;
399 return;
400 }
401 Sa=QuantumScale*p->alpha;
402 Da=QuantumScale*q->alpha;
403 *composite=(Sa*GetPixelInfoIntensity(p) < Da*GetPixelInfoIntensity(q)) ?
404 *p : *q;
405}
406
407static inline MagickRealType Difference(const MagickRealType p,
408 const MagickRealType Sa,const MagickRealType q,const MagickRealType Da)
409{
410 /*
411 Optimized by Multipling by QuantumRange (taken from gamma).
412 */
413 return(Sa*p+Da*q-Sa*Da*2.0*MagickMin(p,q));
414}
415
cristy2b9582a2011-07-04 17:38:56 +0000416static inline void CompositeDifference(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000417 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000418{
419 MagickRealType
420 Da,
421 gamma,
422 Sa;
423
424 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
425 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +0000426 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000427 {
428 /*
429 Handle channels as separate grayscale channels.
430 */
cristy2b9582a2011-07-04 17:38:56 +0000431 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000432 composite->red=fabs((double) (p->red-q->red));
cristy2b9582a2011-07-04 17:38:56 +0000433 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000434 composite->green=fabs((double) (p->green-q->green));
cristy2b9582a2011-07-04 17:38:56 +0000435 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000436 composite->blue=fabs((double) (p->blue-q->blue));
cristy2b9582a2011-07-04 17:38:56 +0000437 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000438 (q->colorspace == CMYKColorspace))
439 composite->black=fabs((double) (p->black-q->black));
cristy2b9582a2011-07-04 17:38:56 +0000440 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000441 composite->alpha=fabs((double) (p->alpha-q->alpha));
442 return;
443 }
444 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
445 composite->alpha=(MagickRealType) QuantumRange*gamma;
446 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
447 composite->red=gamma*Difference(p->red,Sa,q->red,Da);
448 composite->green=gamma*Difference(p->green,Sa,q->green,Da);
449 composite->blue=gamma*Difference(p->blue,Sa,q->blue,Da);
450 if (q->colorspace == CMYKColorspace)
451 composite->black=gamma*Difference(p->black,Sa,q->black,Da);
452}
453
454static MagickRealType Divide(const MagickRealType Sca,const MagickRealType Sa,
455 const MagickRealType Dca,const MagickRealType Da)
456{
457 /*
458 Divide Source by Destination
459
460 f(Sc,Dc) = Sc / Dc
461
462 But with appropriate handling for special case of Dc == 0 specifically
463 so that f(Black,Black)=Black and f(non-Black,Black)=White.
464 It is however also important to correctly do 'over' alpha blending which
465 is why the formula becomes so complex.
466 */
467 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
468 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
469 if (fabs(Dca) < MagickEpsilon)
470 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
471 return(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
472}
473
cristy2b9582a2011-07-04 17:38:56 +0000474static inline void CompositeDivide(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000475 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000476{
477 MagickRealType
478 Da,
479 gamma,
480 Sa;
481
482 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
483 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +0000484 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000485 {
486 /*
487 Handle channels as separate grayscale channels.
488 */
cristy2b9582a2011-07-04 17:38:56 +0000489 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000490 composite->red=QuantumRange*Divide(QuantumScale*p->red,1.0,
491 QuantumScale*q->red,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000492 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000493 composite->green=QuantumRange*Divide(QuantumScale*p->green,1.0,
494 QuantumScale*q->green,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000495 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000496 composite->blue=QuantumRange*Divide(QuantumScale*p->blue,1.0,
497 QuantumScale*q->blue,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000498 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000499 (q->colorspace == CMYKColorspace))
500 composite->black=QuantumRange*Divide(QuantumScale*p->black,1.0,
501 QuantumScale*q->black,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000502 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000503 composite->alpha=QuantumRange*(1.0-Divide(Sa,1.0,Da,1.0));
504 return;
505 }
506 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
507 composite->alpha=(MagickRealType) QuantumRange*gamma;
508 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
509 composite->red=gamma*Divide(QuantumScale*p->red*Sa,Sa,QuantumScale*
510 q->red*Da,Da);
511 composite->green=gamma*Divide(QuantumScale*p->green*Sa,Sa,QuantumScale*
512 q->green*Da,Da);
513 composite->blue=gamma*Divide(QuantumScale*p->blue*Sa,Sa,QuantumScale*
514 q->blue*Da,Da);
515 if (q->colorspace == CMYKColorspace)
516 composite->black=gamma*Divide(QuantumScale*p->black*Sa,Sa,QuantumScale*
517 q->black*Da,Da);
518}
519
520static MagickRealType Exclusion(const MagickRealType Sca,
521 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
522{
523 return(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
524}
525
cristy2b9582a2011-07-04 17:38:56 +0000526static inline void CompositeExclusion(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000527 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000528{
529 MagickRealType
530 gamma,
531 Sa,
532 Da;
533
534 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
535 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +0000536 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000537 {
538 /*
539 Handle channels as separate grayscale channels.
540 */
cristy2b9582a2011-07-04 17:38:56 +0000541 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000542 composite->red=QuantumRange*Exclusion(QuantumScale*p->red,1.0,
543 QuantumScale*q->red,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000544 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000545 composite->green=QuantumRange*Exclusion(QuantumScale*p->green,1.0,
546 QuantumScale*q->green,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000547 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000548 composite->blue=QuantumRange*Exclusion(QuantumScale*p->blue,1.0,
549 QuantumScale*q->blue,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000550 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000551 (q->colorspace == CMYKColorspace))
552 composite->black=QuantumRange*Exclusion(QuantumScale*p->black,1.0,
553 QuantumScale*q->black,1.0);
cristy2b9582a2011-07-04 17:38:56 +0000554 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000555 composite->alpha=QuantumRange*(1.0-Exclusion(Sa,1.0,Da,1.0));
556 return;
557 }
558 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
559 composite->alpha=(MagickRealType) QuantumRange*gamma;
560 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
561 composite->red=gamma*Exclusion(QuantumScale*p->red*Sa,Sa,QuantumScale*
562 q->red*Da,Da);
563 composite->green=gamma*Exclusion(QuantumScale*p->green*Sa,Sa,QuantumScale*
564 q->green*Da,Da);
565 composite->blue=gamma*Exclusion(QuantumScale*p->blue*Sa,Sa,QuantumScale*
566 q->blue*Da,Da);
567 if (q->colorspace == CMYKColorspace)
568 composite->black=gamma*Exclusion(QuantumScale*p->black*Sa,Sa,
569 QuantumScale*q->black*Da,Da);
570}
571
572static MagickRealType HardLight(const MagickRealType Sca,
573 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
574{
575 if ((2.0*Sca) < Sa)
576 return(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
577 return(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
578}
579
580static inline void CompositeHardLight(const PixelInfo *p,const PixelInfo *q,
581 PixelInfo *composite)
582{
583 MagickRealType
584 Da,
585 gamma,
586 Sa;
587
588 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
589 Da=QuantumScale*q->alpha;
590 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
591 composite->alpha=(MagickRealType) QuantumRange*gamma;
592 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
593 composite->red=gamma*HardLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
594 q->red*Da,Da);
595 composite->green=gamma*HardLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
596 q->green*Da,Da);
597 composite->blue=gamma*HardLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
598 q->blue*Da,Da);
599 if (q->colorspace == CMYKColorspace)
600 composite->black=gamma*HardLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
601 q->black*Da,Da);
602}
603
604static void CompositeHSB(const MagickRealType red,const MagickRealType green,
605 const MagickRealType blue,double *hue,double *saturation,double *brightness)
606{
607 MagickRealType
608 delta,
609 max,
610 min;
611
612 /*
613 Convert RGB to HSB colorspace.
614 */
615 assert(hue != (double *) NULL);
616 assert(saturation != (double *) NULL);
617 assert(brightness != (double *) NULL);
618 max=(red > green ? red : green);
619 if (blue > max)
620 max=blue;
621 min=(red < green ? red : green);
622 if (blue < min)
623 min=blue;
624 *hue=0.0;
625 *saturation=0.0;
626 *brightness=(double) (QuantumScale*max);
627 if (max == 0.0)
628 return;
629 *saturation=(double) (1.0-min/max);
630 delta=max-min;
631 if (delta == 0.0)
632 return;
633 if (red == max)
634 *hue=(double) ((green-blue)/delta);
635 else
636 if (green == max)
637 *hue=(double) (2.0+(blue-red)/delta);
638 else
639 if (blue == max)
640 *hue=(double) (4.0+(red-green)/delta);
641 *hue/=6.0;
642 if (*hue < 0.0)
643 *hue+=1.0;
644}
645
646static inline MagickRealType In(const MagickRealType p,const MagickRealType Sa,
647 const MagickRealType magick_unused(q),const MagickRealType Da)
648{
649 return(Sa*p*Da);
650}
651
652static inline void CompositeIn(const PixelInfo *p,const PixelInfo *q,
653 PixelInfo *composite)
654{
655 MagickRealType
656 gamma,
657 Sa,
658 Da;
659
660 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
661 Da=QuantumScale*q->alpha;
662 gamma=Sa*Da;
663 composite->alpha=(MagickRealType) QuantumRange*gamma;
664 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
665 composite->red=gamma*In(p->red,Sa,q->red,Da);
666 composite->green=gamma*In(p->green,Sa,q->green,Da);
667 composite->blue=gamma*In(p->blue,Sa,q->blue,Da);
668 if (q->colorspace == CMYKColorspace)
669 composite->black=gamma*In(p->black,Sa,q->black,Da);
670}
671
672static inline MagickRealType Lighten(const MagickRealType p,
673 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
674{
675 if (p > q)
676 return(MagickOver_(p,alpha,q,beta)); /* src-over */
677 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
678}
679
cristy2b9582a2011-07-04 17:38:56 +0000680static inline void CompositeLighten(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000681 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000682{
683 MagickRealType
684 gamma;
685
686 /*
687 Lighten is also equvalent to a 'Maximum' method OR a greyscale version of a
688 binary 'And' OR the 'Union' of pixel sets.
689 */
cristyfefab1b2011-07-05 00:33:22 +0000690 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000691 {
692 /*
693 Handle channels as separate grayscale channels
694 */
cristy2b9582a2011-07-04 17:38:56 +0000695 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000696 composite->red=MagickMax(p->red,q->red);
cristy2b9582a2011-07-04 17:38:56 +0000697 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000698 composite->green=MagickMax(p->green,q->green);
cristy2b9582a2011-07-04 17:38:56 +0000699 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000700 composite->blue=MagickMax(p->blue,q->blue);
cristy2b9582a2011-07-04 17:38:56 +0000701 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000702 (q->colorspace == CMYKColorspace))
703 composite->black=MagickMax(p->black,q->black);
cristy2b9582a2011-07-04 17:38:56 +0000704 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000705 composite->alpha=MagickMin(p->alpha,q->alpha);
706 return;
707 }
708 composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */
709 gamma=1.0-QuantumScale*composite->alpha;
710 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
711 composite->red=gamma*Lighten(p->red,p->alpha,q->red,q->alpha);
712 composite->green=gamma*Lighten(p->green,p->alpha,q->green,q->alpha);
713 composite->blue=gamma*Lighten(p->blue,p->alpha,q->blue,q->alpha);
714 if (q->colorspace == CMYKColorspace)
715 composite->black=gamma*Lighten(p->black,p->alpha,q->black,q->alpha);
716}
717
cristy2b9582a2011-07-04 17:38:56 +0000718static inline void CompositeLightenIntensity(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +0000719 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000720{
721 MagickRealType
722 Da,
723 Sa;
724
725 /*
726 Select the pixel based on the intensity level.
727 If 'Sync' flag select whole pixel based on alpha weighted intensity.
728 Otherwise use Intenisty only, but restrict copy according to channel.
729 */
cristyfefab1b2011-07-05 00:33:22 +0000730 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000731 {
732 MagickBooleanType
733 from_p;
734
735 from_p=GetPixelInfoIntensity(p) > GetPixelInfoIntensity(q) ? MagickTrue :
736 MagickFalse;
cristy2b9582a2011-07-04 17:38:56 +0000737 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000738 composite->red=from_p != MagickFalse ? p->red : q->red;
cristy2b9582a2011-07-04 17:38:56 +0000739 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000740 composite->green=from_p != MagickFalse ? p->green : q->green;
cristy2b9582a2011-07-04 17:38:56 +0000741 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000742 composite->blue=from_p != MagickFalse ? p->blue : q->blue;
cristy2b9582a2011-07-04 17:38:56 +0000743 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000744 (q->colorspace == CMYKColorspace))
745 composite->black=from_p != MagickFalse ? p->black : q->black;
cristy2b9582a2011-07-04 17:38:56 +0000746 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000747 composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha;
748 return;
749 }
750 Sa=QuantumScale*p->alpha;
751 Da=QuantumScale*q->alpha;
752 *composite=(Sa*GetPixelInfoIntensity(p) > Da*GetPixelInfoIntensity(q)) ?
753 *p : *q;
754}
755
756static inline void CompositeLinearDodge(const PixelInfo *p,const PixelInfo *q,
757 PixelInfo *composite)
758{
759 MagickRealType
760 Da,
761 gamma,
762 Sa;
763
764 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
765 Da=QuantumScale*q->alpha;
766 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
767 composite->alpha=(MagickRealType) QuantumRange*gamma;
768 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
769 composite->red=gamma*(p->red*Sa+q->red*Da);
770 composite->green=gamma*(p->green*Sa+q->green*Da);
771 composite->blue=gamma*(p->blue*Sa+q->blue*Da);
772 if (q->colorspace == CMYKColorspace)
773 composite->black=gamma*(p->black*Sa+q->black*Da);
774}
775
776
777static inline MagickRealType LinearBurn(const MagickRealType Sca,
778 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
779{
780 /*
781 LinearBurn: as defined by Abode Photoshop, according to
782 http://www.simplefilter.de/en/basics/mixmods.html is:
783
784 f(Sc,Dc) = Sc + Dc - 1
785 */
786 return(Sca+Dca-Sa*Da);
787}
788
789static inline void CompositeLinearBurn(const PixelInfo *p,const PixelInfo *q,
790 PixelInfo *composite)
791{
792 MagickRealType
793 Da,
794 gamma,
795 Sa;
796
797 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
798 Da=QuantumScale*q->alpha;
799 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
800 composite->alpha=(MagickRealType) QuantumRange*gamma;
801 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
802 composite->red=gamma*LinearBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
803 q->red*Da,Da);
804 composite->green=gamma*LinearBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
805 q->green*Da,Da);
806 composite->blue=gamma*LinearBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
807 q->blue*Da,Da);
808 if (q->colorspace == CMYKColorspace)
809 composite->black=gamma*LinearBurn(QuantumScale*p->black*Sa,Sa,QuantumScale*
810 q->black*Da,Da);
811}
812
813static inline MagickRealType LinearLight(const MagickRealType Sca,
814 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
815{
816 /*
817 LinearLight: as defined by Abode Photoshop, according to
818 http://www.simplefilter.de/en/basics/mixmods.html is:
819
820 f(Sc,Dc) = Dc + 2*Sc - 1
821 */
822 return((Sca-Sa)*Da+Sca+Dca);
823}
824
825static inline void CompositeLinearLight(const PixelInfo *p,const PixelInfo *q,
826 PixelInfo *composite)
827{
828 MagickRealType
829 Da,
830 gamma,
831 Sa;
832
833 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
834 Da=QuantumScale*q->alpha;
835 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
836 composite->alpha=(MagickRealType) QuantumRange*gamma;
837 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
838 composite->red=gamma*LinearLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
839 q->red*Da,Da);
840 composite->green=gamma*LinearLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
841 q->green*Da,Da);
842 composite->blue=gamma*LinearLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
843 q->blue*Da,Da);
844 if (q->colorspace == CMYKColorspace)
845 composite->black=gamma*LinearLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
846 q->black*Da,Da);
847}
848
849static inline MagickRealType Mathematics(const MagickRealType Sca,
850 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da,
851 const GeometryInfo *geometry_info)
852{
853 MagickRealType
854 gamma;
855
856 /*
857 'Mathematics' a free form user control mathematical composition is defined
858 as...
859
860 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
861
862 Where the arguments A,B,C,D are (currently) passed to composite as
863 a command separated 'geometry' string in "compose:args" image artifact.
864
865 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
866
867 Applying the SVG transparency formula (see above), we get...
868
869 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
870
871 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
872 Dca*(1.0-Sa)
873 */
874 gamma=geometry_info->rho*Sca*Dca+geometry_info->sigma*Sca*Da+
875 geometry_info->xi*Dca*Sa+geometry_info->psi*Sa*Da+Sca*(1.0-Da)+
876 Dca*(1.0-Sa);
877 return(gamma);
878}
879
cristy2b9582a2011-07-04 17:38:56 +0000880static inline void CompositeMathematics(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000881 const PixelInfo *q,const GeometryInfo *args,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000882{
883 MagickRealType
884 Da,
885 gamma,
886 Sa;
887
888 Sa=QuantumScale*p->alpha; /* ??? - AT */
889 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +0000890 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000891 {
892 /*
893 Handle channels as separate grayscale channels.
894 */
cristy2b9582a2011-07-04 17:38:56 +0000895 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000896 composite->red=QuantumRange*Mathematics(QuantumScale*p->red,1.0,
897 QuantumScale*q->red,1.0,args);
cristy2b9582a2011-07-04 17:38:56 +0000898 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000899 composite->green=QuantumRange*Mathematics(QuantumScale*p->green,1.0,
900 QuantumScale*q->green,1.0,args);
cristy2b9582a2011-07-04 17:38:56 +0000901 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000902 composite->blue=QuantumRange*Mathematics(QuantumScale*p->blue,1.0,
903 QuantumScale*q->blue,1.0,args);
cristy2b9582a2011-07-04 17:38:56 +0000904 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000905 (q->colorspace == CMYKColorspace))
906 composite->black=QuantumRange*Mathematics(QuantumScale*p->black,1.0,
907 QuantumScale*q->black,1.0,args);
cristy2b9582a2011-07-04 17:38:56 +0000908 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000909 composite->alpha=QuantumRange*(1.0-Mathematics(Sa,1.0,Da,1.0,args));
910 return;
911 }
912 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
913 composite->alpha=(MagickRealType) QuantumRange*gamma;
914 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
915 composite->red=gamma*Mathematics(QuantumScale*p->red*Sa,Sa,QuantumScale*
916 q->red*Da,Da,args);
917 composite->green=gamma*Mathematics(QuantumScale*p->green*Sa,Sa,
918 QuantumScale*q->green*Da,Da,args);
919 composite->blue=gamma*Mathematics(QuantumScale*p->blue*Sa,Sa,QuantumScale*
920 q->blue*Da,Da,args);
921 if (q->colorspace == CMYKColorspace)
922 composite->black=gamma*Mathematics(QuantumScale*p->black*Sa,Sa,
923 QuantumScale*q->black*Da,Da,args);
924}
925
cristy2b9582a2011-07-04 17:38:56 +0000926static inline void CompositePlus(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000927 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000928{
929 /*
930 NOTE: "Plus" does not use 'over' alpha-blending but uses a special
931 'plus' form of alph-blending. It is the ONLY mathematical operator to
932 do this. this is what makes it different to the otherwise equivalent
933 "LinearDodge" composition method.
934
935 Note however that color channels are still effected by the alpha channel
936 as a result of the blending, making it just as useless for independant
937 channel maths, just like all other mathematical composition methods.
938
939 As such the removal of the 'sync' flag, is still a usful convention.
940
941 The CompositePixelInfoPlus() function is defined in
942 "composite-private.h" so it can also be used for Image Blending.
943 */
cristyfefab1b2011-07-05 00:33:22 +0000944 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000945 {
946 /*
947 Handle channels as separate grayscale channels.
948 */
cristy2b9582a2011-07-04 17:38:56 +0000949 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000950 composite->red=p->red+q->red;
cristy2b9582a2011-07-04 17:38:56 +0000951 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000952 composite->green=p->green+q->green;
cristy2b9582a2011-07-04 17:38:56 +0000953 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000954 composite->blue=p->blue+q->blue;
cristy2b9582a2011-07-04 17:38:56 +0000955 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000956 (q->colorspace == CMYKColorspace))
957 composite->black=p->black+q->black;
cristy2b9582a2011-07-04 17:38:56 +0000958 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000959 composite->alpha=p->alpha+q->alpha-QuantumRange;
960 return;
961 }
962 CompositePixelInfoPlus(p,p->alpha,q,q->alpha,composite);
963}
964
965static inline MagickRealType Minus(const MagickRealType Sca,
966 const MagickRealType Sa,const MagickRealType Dca,
967 const MagickRealType magick_unused(Da))
968{
969 /*
970 Minus Source from Destination
971
972 f(Sc,Dc) = Sc - Dc
973 */
974 return(Sca+Dca-2.0*Dca*Sa);
975}
976
cristy2b9582a2011-07-04 17:38:56 +0000977static inline void CompositeMinus(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000978 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000979{
980 MagickRealType
981 Da,
982 gamma,
983 Sa;
984
985 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
986 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +0000987 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +0000988 {
989 /*
990 Handle channels as separate grayscale channels.
991 */
cristy2b9582a2011-07-04 17:38:56 +0000992 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000993 composite->red=p->red-q->red;
cristy2b9582a2011-07-04 17:38:56 +0000994 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000995 composite->green=p->green-q->green;
cristy2b9582a2011-07-04 17:38:56 +0000996 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000997 composite->blue=p->blue-q->blue;
cristy2b9582a2011-07-04 17:38:56 +0000998 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000999 (q->colorspace == CMYKColorspace))
1000 composite->black=p->black-q->black;
cristy2b9582a2011-07-04 17:38:56 +00001001 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001002 composite->alpha=QuantumRange*(1.0-(Sa-Da));
1003 return;
1004 }
1005 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1006 composite->alpha=(MagickRealType) QuantumRange*gamma;
1007 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1008 composite->red=gamma*Minus(p->red*Sa,Sa,q->red*Da,Da);
1009 composite->green=gamma*Minus(p->green*Sa,Sa,q->green*Da,Da);
1010 composite->blue=gamma*Minus(p->blue*Sa,Sa,q->blue*Da,Da);
1011 if (q->colorspace == CMYKColorspace)
1012 composite->black=gamma*Minus(p->black*Sa,Sa,q->black*Da,Da);
1013}
1014
1015static inline MagickRealType ModulusAdd(const MagickRealType p,
1016 const MagickRealType Sa,const MagickRealType q, const MagickRealType Da)
1017{
1018 MagickRealType
1019 pixel;
1020
1021 pixel=p+q;
1022 if (pixel > QuantumRange)
1023 pixel-=(QuantumRange+1.0);
1024 return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa));
1025}
1026
cristy2b9582a2011-07-04 17:38:56 +00001027static inline void CompositeModulusAdd(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001028 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001029{
1030 MagickRealType
1031 Da,
1032 gamma,
1033 Sa;
1034
cristyfefab1b2011-07-05 00:33:22 +00001035 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001036 {
1037 /*
1038 Handle channels as separate grayscale channels.
1039 */
cristy2b9582a2011-07-04 17:38:56 +00001040 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001041 composite->red=ModulusAdd(p->red,1.0,q->red,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001042 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001043 composite->green=ModulusAdd(p->green,1.0,q->green,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001044 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001045 composite->blue=ModulusAdd(p->blue,1.0,q->blue,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001046 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001047 (q->colorspace == CMYKColorspace))
1048 composite->black=ModulusAdd(p->black,1.0,q->black,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001049 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001050 composite->alpha=ModulusAdd(p->alpha,1.0,q->alpha,1.0);
1051 return;
1052 }
1053 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1054 Da=QuantumScale*q->alpha;
1055 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1056 composite->alpha=(MagickRealType) QuantumRange*gamma;
1057 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1058 composite->red=ModulusAdd(p->red,Sa,q->red,Da);
1059 composite->green=ModulusAdd(p->green,Sa,q->green,Da);
1060 composite->blue=ModulusAdd(p->blue,Sa,q->blue,Da);
1061 if (q->colorspace == CMYKColorspace)
1062 composite->black=ModulusAdd(p->black,Sa,q->black,Da);
1063}
1064
1065static inline MagickRealType ModulusSubtract(const MagickRealType p,
1066 const MagickRealType Sa,const MagickRealType q, const MagickRealType Da)
1067{
1068 MagickRealType
1069 pixel;
1070
1071 pixel=p-q;
1072 if (pixel < 0.0)
1073 pixel+=(QuantumRange+1.0);
1074 return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa));
1075}
1076
cristy2b9582a2011-07-04 17:38:56 +00001077static inline void CompositeModulusSubtract(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +00001078 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001079{
1080 MagickRealType
1081 Da,
1082 gamma,
1083 Sa;
1084
cristyfefab1b2011-07-05 00:33:22 +00001085 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001086 {
1087 /*
1088 Handle channels as separate grayscale channels,
1089 */
cristy2b9582a2011-07-04 17:38:56 +00001090 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001091 composite->red=ModulusSubtract(p->red,1.0,q->red,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001092 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001093 composite->green=ModulusSubtract(p->green,1.0,q->green,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001094 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001095 composite->blue=ModulusSubtract(p->blue,1.0,q->blue,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001096 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001097 (q->colorspace == CMYKColorspace))
1098 composite->black=ModulusSubtract(p->black,1.0,q->black,1.0);
cristy2b9582a2011-07-04 17:38:56 +00001099 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001100 composite->alpha=ModulusSubtract(p->alpha,1.0,q->alpha,1.0);
1101 return;
1102 }
1103 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1104 Da=QuantumScale*q->alpha;
1105 gamma = RoundToUnity(Sa+Da-Sa*Da);
1106 composite->alpha=(MagickRealType) QuantumRange*gamma;
1107 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1108 composite->red=ModulusSubtract(p->red,Sa,q->red,Da);
1109 composite->green=ModulusSubtract(p->green,Sa,q->green,Da);
1110 composite->blue=ModulusSubtract(p->blue,Sa,q->blue,Da);
1111 if (q->colorspace == CMYKColorspace)
1112 composite->black=ModulusSubtract(p->black,Sa,q->black,Da);
1113}
1114
1115static inline MagickRealType Multiply(const MagickRealType Sca,
1116 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1117{
1118 return(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
1119}
1120
cristy2b9582a2011-07-04 17:38:56 +00001121static inline void CompositeMultiply(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001122 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001123{
1124 MagickRealType
1125 Da,
1126 gamma,
1127 Sa;
1128
1129 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1130 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +00001131 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001132 {
1133 /*
1134 Handle channels as separate grayscale channels.
1135 */
cristy2b9582a2011-07-04 17:38:56 +00001136 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001137 composite->red=QuantumScale*p->red*q->red;
cristy2b9582a2011-07-04 17:38:56 +00001138 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001139 composite->green=QuantumScale*p->green*q->green;
cristy2b9582a2011-07-04 17:38:56 +00001140 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001141 composite->blue=QuantumScale*p->blue*q->blue;
cristy2b9582a2011-07-04 17:38:56 +00001142 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001143 (q->colorspace == CMYKColorspace))
1144 composite->black=QuantumScale*p->black*q->black;
cristy2b9582a2011-07-04 17:38:56 +00001145 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001146 composite->alpha=QuantumRange*(1.0-Sa*Da);
1147 return;
1148 }
1149 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1150 composite->alpha=(MagickRealType) QuantumRange*gamma;
1151 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1152 composite->red=gamma*Multiply(QuantumScale*p->red*Sa,Sa,QuantumScale*
1153 q->red*Da,Da);
1154 composite->green=gamma*Multiply(QuantumScale*p->green*Sa,Sa,QuantumScale*
1155 q->green*Da,Da);
1156 composite->blue=gamma*Multiply(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1157 q->blue*Da,Da);
1158 if (q->colorspace == CMYKColorspace)
1159 composite->black=gamma*Multiply(QuantumScale*p->black*Sa,Sa,
1160 QuantumScale*q->black*Da,Da);
1161}
1162
1163static inline MagickRealType Out(const MagickRealType p,const MagickRealType Sa,
1164 const MagickRealType magick_unused(q),const MagickRealType Da)
1165{
1166 return(Sa*p*(1.0-Da));
1167}
1168
1169static inline void CompositeOut(const PixelInfo *p,const PixelInfo *q,
1170 PixelInfo *composite)
1171{
1172 MagickRealType
1173 Sa,
1174 Da,
1175 gamma;
1176
1177 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1178 Da=QuantumScale*q->alpha;
1179 gamma=Sa*(1.0-Da);
1180 composite->alpha=(MagickRealType) QuantumRange*gamma;
1181 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1182 composite->red=gamma*Out(p->red,Sa,q->red,Da);
1183 composite->green=gamma*Out(p->green,Sa,q->green,Da);
1184 composite->blue=gamma*Out(p->blue,Sa,q->blue,Da);
1185 if (q->colorspace == CMYKColorspace)
1186 composite->black=gamma*Out(p->black,Sa,q->black,Da);
1187}
1188
1189static MagickRealType PegtopLight(const MagickRealType Sca,
1190 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1191{
1192 /*
1193 PegTop: A Soft-Light alternative: A continuous version of the Softlight
1194 function, producing very similar results.
1195
1196 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
1197
1198 See http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
1199 */
1200 if (fabs(Da) < MagickEpsilon)
1201 return(Sca);
1202 return(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-Da)+Dca*(1.0-Sa));
1203}
1204
1205static inline void CompositePegtopLight(const PixelInfo *p,const PixelInfo *q,
1206 PixelInfo *composite)
1207{
1208 MagickRealType
1209 Da,
1210 gamma,
1211 Sa;
1212
1213 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1214 Da=QuantumScale*q->alpha;
1215 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1216 composite->alpha=(MagickRealType) QuantumRange*gamma;
1217 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1218 composite->red=gamma*PegtopLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1219 q->red*Da,Da);
1220 composite->green=gamma*PegtopLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1221 q->green*Da,Da);
1222 composite->blue=gamma*PegtopLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1223 q->blue*Da,Da);
1224 if (q->colorspace == CMYKColorspace)
1225 composite->black=gamma*PegtopLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1226 q->black*Da,Da);
1227}
1228
1229static MagickRealType PinLight(const MagickRealType Sca,const MagickRealType Sa,
1230 const MagickRealType Dca,const MagickRealType Da)
1231{
1232 /*
1233 PinLight: A Photoshop 7 composition method
1234 http://www.simplefilter.de/en/basics/mixmods.html
1235
1236 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
1237 */
1238 if (Dca*Sa < Da*(2.0*Sca-Sa))
1239 return(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
1240 if ((Dca*Sa) > (2.0*Sca*Da))
1241 return(Sca*Da+Sca+Dca*(1.0-Sa));
1242 return(Sca*(1.0-Da)+Dca);
1243}
1244
1245static inline void CompositePinLight(const PixelInfo *p,const PixelInfo *q,
1246 PixelInfo *composite)
1247{
1248 MagickRealType
1249 Da,
1250 gamma,
1251 Sa;
1252
1253 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1254 Da=QuantumScale*q->alpha;
1255 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1256 composite->alpha=(MagickRealType) QuantumRange*gamma;
1257 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1258 composite->red=gamma*PinLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1259 q->red*Da,Da);
1260 composite->green=gamma*PinLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1261 q->green*Da,Da);
1262 composite->blue=gamma*PinLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1263 q->blue*Da,Da);
1264 if (q->colorspace == CMYKColorspace)
1265 composite->black=gamma*PinLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1266 q->black*Da,Da);
1267}
1268
1269static inline MagickRealType Screen(const MagickRealType Sca,
1270 const MagickRealType Dca)
1271{
1272 /*
1273 Screen: A negated multiply
1274 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
1275 */
1276 return(Sca+Dca-Sca*Dca);
1277}
1278
cristy2b9582a2011-07-04 17:38:56 +00001279static inline void CompositeScreen(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001280 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001281{
1282 MagickRealType
1283 Da,
1284 gamma,
1285 Sa;
1286
1287 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1288 Da=QuantumScale*q->alpha;
cristyfefab1b2011-07-05 00:33:22 +00001289 if (image->sync == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001290 {
1291 /*
1292 Handle channels as separate grayscale channels.
1293 */
cristy2b9582a2011-07-04 17:38:56 +00001294 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001295 composite->red=QuantumRange*Screen(QuantumScale*p->red,
1296 QuantumScale*q->red);
cristy2b9582a2011-07-04 17:38:56 +00001297 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001298 composite->green=QuantumRange*Screen(QuantumScale*p->green,
1299 QuantumScale*q->green);
cristy2b9582a2011-07-04 17:38:56 +00001300 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001301 composite->blue=QuantumRange*Screen(QuantumScale*p->blue,
1302 QuantumScale*q->blue);
cristy2b9582a2011-07-04 17:38:56 +00001303 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001304 (q->colorspace == CMYKColorspace))
1305 composite->black=QuantumRange*Screen(QuantumScale*p->black,
1306 QuantumScale*q->black);
cristy2b9582a2011-07-04 17:38:56 +00001307 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001308 composite->alpha=QuantumRange*(1.0-Screen(Sa,Da));
1309 return;
1310 }
1311 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1312 composite->alpha=(MagickRealType) QuantumRange*gamma;
1313 Sa*=QuantumScale; Da*=QuantumScale; /* optimization */
1314 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1315 composite->red=gamma*Screen(p->red*Sa,q->red*Da);
1316 composite->green=gamma*Screen(p->green*Sa,q->green*Da);
1317 composite->blue=gamma*Screen(p->blue*Sa,q->blue*Da);
1318 if (q->colorspace == CMYKColorspace)
1319 composite->black=gamma*Screen(p->black*Sa,q->black*Da);
1320}
1321
1322static MagickRealType SoftLight(const MagickRealType Sca,
1323 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1324{
1325 MagickRealType
1326 alpha,
1327 beta;
1328
1329 /*
1330 New specification: March 2009 SVG specification.
1331 */
1332 alpha=Dca/Da;
1333 if ((2.0*Sca) < Sa)
1334 return(Dca*(Sa+(2.0*Sca-Sa)*(1.0-alpha))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1335 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
1336 {
1337 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*alpha*(4.0*alpha+1.0)*(alpha-1.0)+7.0*
1338 alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1339 return(beta);
1340 }
1341 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(pow(alpha,0.5)-alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1342 return(beta);
1343}
1344
1345static inline void CompositeSoftLight(const PixelInfo *p,const PixelInfo *q,
1346 PixelInfo *composite)
1347{
1348 MagickRealType
1349 Da,
1350 gamma,
1351 Sa;
1352
1353 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1354 Da=QuantumScale*q->alpha;
1355 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1356 composite->alpha=(MagickRealType) QuantumRange*gamma;
1357 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1358 composite->red=gamma*SoftLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1359 q->red*Da,Da);
1360 composite->green=gamma*SoftLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1361 q->green*Da,Da);
1362 composite->blue=gamma*SoftLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1363 q->blue*Da,Da);
1364 if (q->colorspace == CMYKColorspace)
1365 composite->black=gamma*SoftLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1366 q->black*Da,Da);
1367}
1368
1369static inline MagickRealType Threshold(const MagickRealType p,
1370 const MagickRealType q,const MagickRealType threshold,
1371 const MagickRealType amount)
1372{
1373 MagickRealType
1374 delta;
1375
1376 /*
1377 Multiply difference by amount, if differance larger than threshold???
1378 What use this is is completely unknown. The Opacity calculation appears to
1379 be inverted -- Anthony Thyssen
1380
1381 Deprecated.
1382 */
1383 delta=p-q;
1384 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
1385 return(q);
1386 return(q+delta*amount);
1387}
1388
1389static inline void CompositeThreshold(const PixelInfo *p,const PixelInfo *q,
1390 const MagickRealType threshold,const MagickRealType amount,
1391 PixelInfo *composite)
1392{
1393 composite->red=Threshold(p->red,q->red,threshold,amount);
1394 composite->green=Threshold(p->green,q->green,threshold,amount);
1395 composite->blue=Threshold(p->blue,q->blue,threshold,amount);
1396 composite->alpha=Threshold(p->alpha,q->alpha,threshold,amount);
1397 if (q->colorspace == CMYKColorspace)
1398 composite->black=Threshold(p->black,q->black,threshold,amount);
1399}
1400
1401
1402static MagickRealType VividLight(const MagickRealType Sca,
1403 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1404{
1405 /*
1406 VividLight: A Photoshop 7 composition method. See
1407 http://www.simplefilter.de/en/basics/mixmods.html.
1408
1409 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
1410 */
1411 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
1412 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
1413 if ((2.0*Sca) <= Sa)
1414 return(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1415 return(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1416}
1417
1418static inline void CompositeVividLight(const PixelInfo *p,const PixelInfo *q,
1419 PixelInfo *composite)
1420{
1421 MagickRealType
1422 Da,
1423 gamma,
1424 Sa;
1425
1426 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1427 Da=QuantumScale*q->alpha;
1428 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1429 composite->alpha=(MagickRealType) QuantumRange*gamma;
1430 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1431 composite->red=gamma*VividLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1432 q->red*Da,Da);
1433 composite->green=gamma*VividLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1434 q->green*Da,Da);
1435 composite->blue=gamma*VividLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1436 q->blue*Da,Da);
1437 if (q->colorspace == CMYKColorspace)
1438 composite->black=gamma*VividLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1439 q->black*Da,Da);
1440}
1441
1442static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa,
1443 const MagickRealType Dca,const MagickRealType Da)
1444{
1445 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
1446}
1447
1448static inline void CompositeXor(const PixelInfo *p,const PixelInfo *q,
1449 PixelInfo *composite)
1450{
1451 MagickRealType
1452 Da,
1453 gamma,
1454 Sa;
1455
1456 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1457 Da=QuantumScale*q->alpha;
1458 gamma=Sa+Da-2.0*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */
1459 composite->alpha=(MagickRealType) QuantumRange*gamma;
1460 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1461 composite->red=gamma*Xor(p->red*Sa,Sa,q->red*Da,Da);
1462 composite->green=gamma*Xor(p->green*Sa,Sa,q->green*Da,Da);
1463 composite->blue=gamma*Xor(p->blue*Sa,Sa,q->blue*Da,Da);
1464 if (q->colorspace == CMYKColorspace)
1465 composite->black=gamma*Xor(p->black*Sa,Sa,q->black*Da,Da);
1466}
1467
1468static void HSBComposite(const double hue,const double saturation,
1469 const double brightness,MagickRealType *red,MagickRealType *green,
1470 MagickRealType *blue)
1471{
1472 MagickRealType
1473 f,
1474 h,
1475 p,
1476 q,
1477 t;
1478
1479 /*
1480 Convert HSB to RGB colorspace.
1481 */
1482 assert(red != (MagickRealType *) NULL);
1483 assert(green != (MagickRealType *) NULL);
1484 assert(blue != (MagickRealType *) NULL);
1485 if (saturation == 0.0)
1486 {
1487 *red=(MagickRealType) QuantumRange*brightness;
1488 *green=(*red);
1489 *blue=(*red);
1490 return;
1491 }
1492 h=6.0*(hue-floor(hue));
1493 f=h-floor((double) h);
1494 p=brightness*(1.0-saturation);
1495 q=brightness*(1.0-saturation*f);
1496 t=brightness*(1.0-saturation*(1.0-f));
1497 switch ((int) h)
1498 {
1499 case 0:
1500 default:
1501 {
1502 *red=(MagickRealType) QuantumRange*brightness;
1503 *green=(MagickRealType) QuantumRange*t;
1504 *blue=(MagickRealType) QuantumRange*p;
1505 break;
1506 }
1507 case 1:
1508 {
1509 *red=(MagickRealType) QuantumRange*q;
1510 *green=(MagickRealType) QuantumRange*brightness;
1511 *blue=(MagickRealType) QuantumRange*p;
1512 break;
1513 }
1514 case 2:
1515 {
1516 *red=(MagickRealType) QuantumRange*p;
1517 *green=(MagickRealType) QuantumRange*brightness;
1518 *blue=(MagickRealType) QuantumRange*t;
1519 break;
1520 }
1521 case 3:
1522 {
1523 *red=(MagickRealType) QuantumRange*p;
1524 *green=(MagickRealType) QuantumRange*q;
1525 *blue=(MagickRealType) QuantumRange*brightness;
1526 break;
1527 }
1528 case 4:
1529 {
1530 *red=(MagickRealType) QuantumRange*t;
1531 *green=(MagickRealType) QuantumRange*p;
1532 *blue=(MagickRealType) QuantumRange*brightness;
1533 break;
1534 }
1535 case 5:
1536 {
1537 *red=(MagickRealType) QuantumRange*brightness;
1538 *green=(MagickRealType) QuantumRange*p;
1539 *blue=(MagickRealType) QuantumRange*q;
1540 break;
1541 }
1542 }
1543}
1544
1545MagickExport MagickBooleanType CompositeImage(Image *image,
1546 const CompositeOperator compose,const Image *composite_image,
1547 const ssize_t x_offset,const ssize_t y_offset)
1548{
cristy4c08aed2011-07-01 19:47:50 +00001549#define CompositeImageTag "Composite/Image"
1550
1551 CacheView
1552 *composite_view,
1553 *image_view;
1554
1555 const char
1556 *value;
1557
1558 double
1559 sans;
1560
1561 ExceptionInfo
1562 *exception;
1563
1564 GeometryInfo
1565 geometry_info;
1566
1567 Image
1568 *destination_image;
1569
1570 MagickBooleanType
1571 modify_outside_overlay,
1572 status;
1573
1574 MagickOffsetType
1575 progress;
1576
1577 PixelInfo
1578 zero;
1579
1580 MagickRealType
1581 amount,
1582 destination_dissolve,
1583 midpoint,
1584 percent_brightness,
1585 percent_saturation,
1586 source_dissolve,
1587 threshold;
1588
1589 MagickStatusType
1590 flags;
1591
1592 ssize_t
1593 y;
1594
1595 /*
1596 Prepare composite image.
1597 */
1598 assert(image != (Image *) NULL);
1599 assert(image->signature == MagickSignature);
1600 if (image->debug != MagickFalse)
1601 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1602 assert(composite_image != (Image *) NULL);
1603 assert(composite_image->signature == MagickSignature);
1604 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1605 return(MagickFalse);
1606 GetPixelInfo(image,&zero);
1607 destination_image=(Image *) NULL;
1608 amount=0.5;
1609 destination_dissolve=1.0;
1610 modify_outside_overlay=MagickFalse;
1611 percent_brightness=100.0;
1612 percent_saturation=100.0;
1613 source_dissolve=1.0;
1614 threshold=0.05f;
1615 switch (compose)
1616 {
1617 case ClearCompositeOp:
1618 case SrcCompositeOp:
1619 case InCompositeOp:
1620 case SrcInCompositeOp:
1621 case OutCompositeOp:
1622 case SrcOutCompositeOp:
1623 case DstInCompositeOp:
1624 case DstAtopCompositeOp:
1625 {
1626 /*
1627 Modify destination outside the overlaid region.
1628 */
1629 modify_outside_overlay=MagickTrue;
1630 break;
1631 }
1632 case CopyCompositeOp:
1633 {
1634 if ((x_offset < 0) || (y_offset < 0))
1635 break;
1636 if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns)
1637 break;
1638 if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows)
1639 break;
1640 status=MagickTrue;
1641 exception=(&image->exception);
1642 image_view=AcquireCacheView(image);
1643 composite_view=AcquireCacheView(composite_image);
1644#if defined(MAGICKCORE_OPENMP_SUPPORT)
1645#pragma omp parallel for schedule(dynamic,4) shared(status)
1646#endif
1647 for (y=0; y < (ssize_t) composite_image->rows; y++)
1648 {
1649 MagickBooleanType
1650 sync;
1651
1652 register const Quantum
1653 *p;
1654
1655 register Quantum
1656 *q;
1657
1658 register ssize_t
1659 x;
1660
1661 if (status == MagickFalse)
1662 continue;
1663 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1664 1,exception);
1665 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
1666 composite_image->columns,1,exception);
1667 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1668 {
1669 status=MagickFalse;
1670 continue;
1671 }
1672 for (x=0; x < (ssize_t) composite_image->columns; x++)
1673 {
1674 SetPixelRed(image,GetPixelRed(composite_image,p),q);
1675 SetPixelGreen(image,GetPixelGreen(composite_image,p),q);
1676 SetPixelBlue(image,GetPixelBlue(composite_image,p),q);
1677 SetPixelAlpha(image,GetPixelAlpha(composite_image,p),q);
1678 if (image->colorspace == CMYKColorspace)
1679 SetPixelBlack(image,GetPixelBlack(composite_image,p),q);
cristydcfc1ad2011-07-07 16:25:41 +00001680 p+=GetPixelComponents(composite_image);
1681 q+=GetPixelComponents(image);
cristy4c08aed2011-07-01 19:47:50 +00001682 }
1683 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1684 if (sync == MagickFalse)
1685 status=MagickFalse;
1686 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1687 {
1688 MagickBooleanType
1689 proceed;
1690
1691#if defined(MAGICKCORE_OPENMP_SUPPORT)
1692#pragma omp critical (MagickCore_CompositeImage)
1693#endif
1694 proceed=SetImageProgress(image,CompositeImageTag,
1695 (MagickOffsetType) y,image->rows);
1696 if (proceed == MagickFalse)
1697 status=MagickFalse;
1698 }
1699 }
1700 composite_view=DestroyCacheView(composite_view);
1701 image_view=DestroyCacheView(image_view);
1702 return(status);
1703 }
1704 case CopyOpacityCompositeOp:
1705 case ChangeMaskCompositeOp:
1706 {
1707 /*
1708 Modify destination outside the overlaid region and require an alpha
1709 channel to exist, to add transparency.
1710 */
1711 if (image->matte == MagickFalse)
1712 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
1713 modify_outside_overlay=MagickTrue;
1714 break;
1715 }
1716 case BlurCompositeOp:
1717 {
1718 CacheView
1719 *composite_view,
1720 *destination_view;
1721
1722 PixelInfo
1723 pixel;
1724
1725 MagickRealType
1726 angle_range,
1727 angle_start,
1728 height,
1729 width;
1730
1731 ResampleFilter
1732 *resample_filter;
1733
1734 SegmentInfo
1735 blur;
1736
1737 /*
1738 Blur Image dictated by an overlay gradient map: X = red_channel;
1739 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
1740 */
1741 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1742 &image->exception);
1743 if (destination_image == (Image *) NULL)
1744 return(MagickFalse);
1745 /*
1746 Determine the horizontal and vertical maximim blur.
1747 */
1748 SetGeometryInfo(&geometry_info);
1749 flags=NoValue;
1750 value=GetImageArtifact(composite_image,"compose:args");
1751 if (value != (char *) NULL)
1752 flags=ParseGeometry(value,&geometry_info);
1753 if ((flags & WidthValue) == 0 )
1754 {
1755 destination_image=DestroyImage(destination_image);
1756 return(MagickFalse);
1757 }
1758 width=geometry_info.rho;
1759 height=geometry_info.sigma;
1760 blur.x1=geometry_info.rho;
1761 blur.x2=0.0;
1762 blur.y1=0.0;
1763 blur.y2=geometry_info.sigma;
1764 angle_start=0.0;
1765 angle_range=0.0;
1766 if ((flags & HeightValue) == 0)
1767 blur.y2=blur.x1;
1768 if ((flags & XValue) != 0 )
1769 {
1770 MagickRealType
1771 angle;
1772
1773 angle=DegreesToRadians(geometry_info.xi);
1774 blur.x1=width*cos(angle);
1775 blur.x2=width*sin(angle);
1776 blur.y1=(-height*sin(angle));
1777 blur.y2=height*cos(angle);
1778 }
1779 if ((flags & YValue) != 0 )
1780 {
1781 angle_start=DegreesToRadians(geometry_info.xi);
1782 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
1783 }
1784 /*
1785 Blur Image by resampling.
1786 */
1787 pixel=zero;
1788 exception=(&image->exception);
1789 resample_filter=AcquireResampleFilter(image,&image->exception);
1790 SetResampleFilter(resample_filter,CubicFilter,2.0);
1791 destination_view=AcquireCacheView(destination_image);
1792 composite_view=AcquireCacheView(composite_image);
1793 for (y=0; y < (ssize_t) composite_image->rows; y++)
1794 {
1795 MagickBooleanType
1796 sync;
1797
1798 register const Quantum
1799 *restrict p;
1800
1801 register Quantum
1802 *restrict q;
1803
1804 register ssize_t
1805 x;
1806
1807 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1808 continue;
1809 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1810 1,exception);
1811 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
1812 destination_image->columns,1,&image->exception);
1813 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1814 break;
1815 for (x=0; x < (ssize_t) composite_image->columns; x++)
1816 {
1817 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1818 {
cristydcfc1ad2011-07-07 16:25:41 +00001819 p+=GetPixelComponents(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001820 continue;
1821 }
1822 if (fabs(angle_range) > MagickEpsilon)
1823 {
1824 MagickRealType
1825 angle;
1826
1827 angle=angle_start+angle_range*QuantumScale*
1828 GetPixelBlue(composite_image,p);
1829 blur.x1=width*cos(angle);
1830 blur.x2=width*sin(angle);
1831 blur.y1=(-height*sin(angle));
1832 blur.y2=height*cos(angle);
1833 }
1834 ScaleResampleFilter(resample_filter,blur.x1*QuantumScale*
1835 GetPixelRed(composite_image,p),blur.y1*QuantumScale*
1836 GetPixelGreen(composite_image,p),blur.x2*QuantumScale*
1837 GetPixelRed(composite_image,p),blur.y2*QuantumScale*
1838 GetPixelGreen(composite_image,p));
1839 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
1840 (double) y_offset+y,&pixel);
1841 SetPixelPixelInfo(destination_image,&pixel,q);
cristydcfc1ad2011-07-07 16:25:41 +00001842 p+=GetPixelComponents(composite_image);
1843 q+=GetPixelComponents(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00001844 }
1845 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
1846 if (sync == MagickFalse)
1847 break;
1848 }
1849 resample_filter=DestroyResampleFilter(resample_filter);
1850 composite_view=DestroyCacheView(composite_view);
1851 destination_view=DestroyCacheView(destination_view);
1852 composite_image=destination_image;
1853 break;
1854 }
1855 case DisplaceCompositeOp:
1856 case DistortCompositeOp:
1857 {
1858 CacheView
1859 *composite_view,
1860 *destination_view,
1861 *image_view;
1862
1863 PixelInfo
1864 pixel;
1865
1866 MagickRealType
1867 horizontal_scale,
1868 vertical_scale;
1869
1870 PointInfo
1871 center,
1872 offset;
1873
1874 /*
1875 Displace/Distort based on overlay gradient map:
1876 X = red_channel; Y = green_channel;
1877 compose:args = x_scale[,y_scale[,center.x,center.y]]
1878 */
1879 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1880 &image->exception);
1881 if (destination_image == (Image *) NULL)
1882 return(MagickFalse);
1883 SetGeometryInfo(&geometry_info);
1884 flags=NoValue;
1885 value=GetImageArtifact(composite_image,"compose:args");
1886 if (value != (char *) NULL)
1887 flags=ParseGeometry(value,&geometry_info);
1888 if ((flags & (WidthValue|HeightValue)) == 0 )
1889 {
1890 if ((flags & AspectValue) == 0)
1891 {
1892 horizontal_scale=(MagickRealType) (composite_image->columns-1.0)/
1893 2.0;
1894 vertical_scale=(MagickRealType) (composite_image->rows-1.0)/2.0;
1895 }
1896 else
1897 {
1898 horizontal_scale=(MagickRealType) (image->columns-1.0)/2.0;
1899 vertical_scale=(MagickRealType) (image->rows-1.0)/2.0;
1900 }
1901 }
1902 else
1903 {
1904 horizontal_scale=geometry_info.rho;
1905 vertical_scale=geometry_info.sigma;
1906 if ((flags & PercentValue) != 0)
1907 {
1908 if ((flags & AspectValue) == 0)
1909 {
1910 horizontal_scale*=(composite_image->columns-1.0)/200.0;
1911 vertical_scale*=(composite_image->rows-1.0)/200.0;
1912 }
1913 else
1914 {
1915 horizontal_scale*=(image->columns-1.0)/200.0;
1916 vertical_scale*=(image->rows-1.0)/200.0;
1917 }
1918 }
1919 if ((flags & HeightValue) == 0)
1920 vertical_scale=horizontal_scale;
1921 }
1922 /*
1923 Determine fixed center point for absolute distortion map
1924 Absolute distort ==
1925 Displace offset relative to a fixed absolute point
1926 Select that point according to +X+Y user inputs.
1927 default = center of overlay image
1928 arg flag '!' = locations/percentage relative to background image
1929 */
1930 center.x=(MagickRealType) x_offset;
1931 center.y=(MagickRealType) y_offset;
1932 if (compose == DistortCompositeOp)
1933 {
1934 if ((flags & XValue) == 0)
1935 if ((flags & AspectValue) == 0)
1936 center.x=(MagickRealType) x_offset+(composite_image->columns-1)/
1937 2.0;
1938 else
1939 center.x=((MagickRealType) image->columns-1)/2.0;
1940 else
1941 if ((flags & AspectValue) == 0)
1942 center.x=(MagickRealType) x_offset+geometry_info.xi;
1943 else
1944 center.x=geometry_info.xi;
1945 if ((flags & YValue) == 0)
1946 if ((flags & AspectValue) == 0)
1947 center.y=(MagickRealType) y_offset+(composite_image->rows-1)/2.0;
1948 else
1949 center.y=((MagickRealType) image->rows-1)/2.0;
1950 else
1951 if ((flags & AspectValue) == 0)
1952 center.y=(MagickRealType) y_offset+geometry_info.psi;
1953 else
1954 center.y=geometry_info.psi;
1955 }
1956 /*
1957 Shift the pixel offset point as defined by the provided,
1958 displacement/distortion map. -- Like a lens...
1959 */
1960 pixel=zero;
1961 exception=(&image->exception);
1962 image_view=AcquireCacheView(image);
1963 destination_view=AcquireCacheView(destination_image);
1964 composite_view=AcquireCacheView(composite_image);
1965 for (y=0; y < (ssize_t) composite_image->rows; y++)
1966 {
1967 MagickBooleanType
1968 sync;
1969
1970 register const Quantum
1971 *restrict p;
1972
1973 register Quantum
1974 *restrict q;
1975
1976 register ssize_t
1977 x;
1978
1979 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1980 continue;
1981 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1982 1,exception);
1983 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
1984 destination_image->columns,1,&image->exception);
1985 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1986 break;
1987 for (x=0; x < (ssize_t) composite_image->columns; x++)
1988 {
1989 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1990 {
cristydcfc1ad2011-07-07 16:25:41 +00001991 p+=GetPixelComponents(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001992 continue;
1993 }
1994 /*
1995 Displace the offset.
1996 */
1997 offset.x=(horizontal_scale*(GetPixelRed(composite_image,p)-
1998 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
1999 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
2000 x : 0);
2001 offset.y=(vertical_scale*(GetPixelGreen(composite_image,p)-
2002 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2003 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
2004 y : 0);
2005 (void) InterpolatePixelInfo(image,image_view,
2006 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
2007 &pixel,exception);
2008 /*
2009 Mask with the 'invalid pixel mask' in alpha channel.
2010 */
2011 pixel.alpha=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale*
2012 pixel.alpha)*(1.0-QuantumScale*
2013 GetPixelAlpha(composite_image,p)));
2014 SetPixelPixelInfo(destination_image,&pixel,q);
cristydcfc1ad2011-07-07 16:25:41 +00002015 p+=GetPixelComponents(composite_image);
2016 q+=GetPixelComponents(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00002017 }
2018 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
2019 if (sync == MagickFalse)
2020 break;
2021 }
2022 destination_view=DestroyCacheView(destination_view);
2023 composite_view=DestroyCacheView(composite_view);
2024 image_view=DestroyCacheView(image_view);
2025 composite_image=destination_image;
2026 break;
2027 }
2028 case DissolveCompositeOp:
2029 {
2030 /*
2031 Geometry arguments to dissolve factors.
2032 */
2033 value=GetImageArtifact(composite_image,"compose:args");
2034 if (value != (char *) NULL)
2035 {
2036 flags=ParseGeometry(value,&geometry_info);
2037 source_dissolve=geometry_info.rho/100.0;
2038 destination_dissolve=1.0;
2039 if ((source_dissolve-MagickEpsilon) < 0.0)
2040 source_dissolve=0.0;
2041 if ((source_dissolve+MagickEpsilon) > 1.0)
2042 {
2043 destination_dissolve=2.0-source_dissolve;
2044 source_dissolve=1.0;
2045 }
2046 if ((flags & SigmaValue) != 0)
2047 destination_dissolve=geometry_info.sigma/100.0;
2048 if ((destination_dissolve-MagickEpsilon) < 0.0)
2049 destination_dissolve=0.0;
2050 modify_outside_overlay=MagickTrue;
2051 if ((destination_dissolve+MagickEpsilon) > 1.0 )
2052 {
2053 destination_dissolve=1.0;
2054 modify_outside_overlay=MagickFalse;
2055 }
2056 }
2057 break;
2058 }
2059 case BlendCompositeOp:
2060 {
2061 value=GetImageArtifact(composite_image,"compose:args");
2062 if (value != (char *) NULL)
2063 {
2064 flags=ParseGeometry(value,&geometry_info);
2065 source_dissolve=geometry_info.rho/100.0;
2066 destination_dissolve=1.0-source_dissolve;
2067 if ((flags & SigmaValue) != 0)
2068 destination_dissolve=geometry_info.sigma/100.0;
2069 modify_outside_overlay=MagickTrue;
2070 if ((destination_dissolve+MagickEpsilon) > 1.0)
2071 modify_outside_overlay=MagickFalse;
2072 }
2073 break;
2074 }
2075 case MathematicsCompositeOp:
2076 {
2077 /*
2078 Just collect the values from "compose:args", setting.
2079 Unused values are set to zero automagically.
2080
2081 Arguments are normally a comma separated list, so this probably should
2082 be changed to some 'general comma list' parser, (with a minimum
2083 number of values)
2084 */
2085 SetGeometryInfo(&geometry_info);
2086 value=GetImageArtifact(composite_image,"compose:args");
2087 if (value != (char *) NULL)
2088 (void) ParseGeometry(value,&geometry_info);
2089 break;
2090 }
2091 case ModulateCompositeOp:
2092 {
2093 /*
2094 Determine the brightness and saturation scale.
2095 */
2096 value=GetImageArtifact(composite_image,"compose:args");
2097 if (value != (char *) NULL)
2098 {
2099 flags=ParseGeometry(value,&geometry_info);
2100 percent_brightness=geometry_info.rho;
2101 if ((flags & SigmaValue) != 0)
2102 percent_saturation=geometry_info.sigma;
2103 }
2104 break;
2105 }
2106 case ThresholdCompositeOp:
2107 {
2108 /*
2109 Determine the amount and threshold.
2110 */
2111 value=GetImageArtifact(composite_image,"compose:args");
2112 if (value != (char *) NULL)
2113 {
2114 flags=ParseGeometry(value,&geometry_info);
2115 amount=geometry_info.rho;
2116 threshold=geometry_info.sigma;
2117 if ((flags & SigmaValue) == 0)
2118 threshold=0.05f;
2119 }
2120 threshold*=QuantumRange;
2121 break;
2122 }
2123 default:
2124 break;
2125 }
2126 value=GetImageArtifact(composite_image,"compose:outside-overlay");
2127 if (value != (const char *) NULL)
2128 modify_outside_overlay=IsMagickTrue(value);
2129 /*
2130 Composite image.
2131 */
2132 status=MagickTrue;
2133 progress=0;
2134 midpoint=((MagickRealType) QuantumRange+1.0)/2;
2135 GetPixelInfo(composite_image,&zero);
2136 exception=(&image->exception);
2137 image_view=AcquireCacheView(image);
2138 composite_view=AcquireCacheView(composite_image);
2139#if defined(MAGICKCORE_OPENMP_SUPPORT)
2140 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2141#endif
2142 for (y=0; y < (ssize_t) image->rows; y++)
2143 {
2144 const Quantum
2145 *pixels;
2146
2147 double
2148 brightness,
2149 hue,
2150 saturation;
2151
2152 PixelInfo
2153 composite,
2154 destination,
2155 source;
2156
2157 register const Quantum
2158 *restrict p;
2159
2160 register Quantum
2161 *restrict q;
2162
2163 register ssize_t
2164 x;
2165
2166 if (status == MagickFalse)
2167 continue;
2168 if (modify_outside_overlay == MagickFalse)
2169 {
2170 if (y < y_offset)
2171 continue;
2172 if ((y-y_offset) >= (ssize_t) composite_image->rows)
2173 continue;
2174 }
2175 /*
2176 If pixels is NULL, y is outside overlay region.
2177 */
2178 pixels=(Quantum *) NULL;
2179 p=(Quantum *) NULL;
2180 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
2181 {
2182 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
2183 composite_image->columns,1,exception);
2184 if (p == (const Quantum *) NULL)
2185 {
2186 status=MagickFalse;
2187 continue;
2188 }
2189 pixels=p;
2190 if (x_offset < 0)
cristydcfc1ad2011-07-07 16:25:41 +00002191 p-=x_offset*GetPixelComponents(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00002192 }
2193 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2194 if (q == (const Quantum *) NULL)
2195 {
2196 status=MagickFalse;
2197 continue;
2198 }
2199 source=zero;
2200 destination=zero;
2201 hue=0.0;
2202 saturation=0.0;
2203 brightness=0.0;
2204 for (x=0; x < (ssize_t) image->columns; x++)
2205 {
2206 if (modify_outside_overlay == MagickFalse)
2207 {
2208 if (x < x_offset)
2209 {
cristydcfc1ad2011-07-07 16:25:41 +00002210 q+=GetPixelComponents(image);
cristy4c08aed2011-07-01 19:47:50 +00002211 continue;
2212 }
2213 if ((x-x_offset) >= (ssize_t) composite_image->columns)
2214 break;
2215 }
2216 destination.red=(MagickRealType) GetPixelRed(image,q);
2217 destination.green=(MagickRealType) GetPixelGreen(image,q);
2218 destination.blue=(MagickRealType) GetPixelBlue(image,q);
2219 if (image->colorspace == CMYKColorspace)
2220 destination.black=(MagickRealType) GetPixelBlack(image,q);
2221 if (image->colorspace == CMYKColorspace)
2222 {
2223 destination.red=(MagickRealType) QuantumRange-destination.red;
2224 destination.green=(MagickRealType) QuantumRange-destination.green;
2225 destination.blue=(MagickRealType) QuantumRange-destination.blue;
2226 destination.black=(MagickRealType) QuantumRange-destination.black;
2227 }
2228 if (image->matte != MagickFalse)
2229 destination.alpha=(MagickRealType) GetPixelAlpha(image,q);
2230 /*
2231 Handle destination modifications outside overlaid region.
2232 */
2233 composite=destination;
2234 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
2235 ((x-x_offset) >= (ssize_t) composite_image->columns))
2236 {
2237 switch (compose)
2238 {
2239 case DissolveCompositeOp:
2240 case BlendCompositeOp:
2241 {
2242 composite.alpha=destination_dissolve*(composite.alpha);
2243 break;
2244 }
2245 case ClearCompositeOp:
2246 case SrcCompositeOp:
2247 {
2248 CompositeClear(&destination,&composite);
2249 break;
2250 }
2251 case InCompositeOp:
2252 case SrcInCompositeOp:
2253 case OutCompositeOp:
2254 case SrcOutCompositeOp:
2255 case DstInCompositeOp:
2256 case DstAtopCompositeOp:
2257 case CopyOpacityCompositeOp:
2258 case ChangeMaskCompositeOp:
2259 {
2260 composite.alpha=(MagickRealType) TransparentAlpha;
2261 break;
2262 }
2263 default:
2264 {
2265 (void) GetOneVirtualMagickPixel(composite_image,x-x_offset,y-
2266 y_offset,&composite,exception);
2267 break;
2268 }
2269 }
2270 if (image->colorspace == CMYKColorspace)
2271 {
2272 composite.red=(MagickRealType) QuantumRange-composite.red;
2273 composite.green=(MagickRealType) QuantumRange-composite.green;
2274 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2275 composite.black=(MagickRealType) QuantumRange-composite.black;
2276 }
2277 SetPixelRed(image,ClampToQuantum(composite.red),q);
2278 SetPixelGreen(image,ClampToQuantum(composite.green),q);
2279 SetPixelBlue(image,ClampToQuantum(composite.blue),q);
2280 if (image->matte != MagickFalse)
2281 SetPixelAlpha(image,ClampToQuantum(composite.alpha),q);
2282 if (image->colorspace == CMYKColorspace)
2283 SetPixelBlack(image,ClampToQuantum(composite.black),q);
cristydcfc1ad2011-07-07 16:25:41 +00002284 q+=GetPixelComponents(image);
cristy4c08aed2011-07-01 19:47:50 +00002285 continue;
2286 }
2287 /*
2288 Handle normal overlay of source onto destination.
2289 */
2290 source.red=(MagickRealType) GetPixelRed(composite_image,p);
2291 source.green=(MagickRealType) GetPixelGreen(composite_image,p);
2292 source.blue=(MagickRealType) GetPixelBlue(composite_image,p);
2293 if (composite_image->colorspace == CMYKColorspace)
2294 source.black=(MagickRealType) GetPixelBlack(composite_image,p);
2295 if (composite_image->colorspace == CMYKColorspace)
2296 {
2297 source.red=(MagickRealType) QuantumRange-source.red;
2298 source.green=(MagickRealType) QuantumRange-source.green;
2299 source.blue=(MagickRealType) QuantumRange-source.blue;
2300 source.black=(MagickRealType) QuantumRange-source.black;
2301 }
2302 if (composite_image->matte != MagickFalse)
2303 source.alpha=(MagickRealType) GetPixelAlpha(composite_image,p);
2304 /*
2305 Porter-Duff compositions.
2306 */
2307 switch (compose)
2308 {
2309 case ClearCompositeOp:
2310 {
2311 CompositeClear(&destination,&composite);
2312 break;
2313 }
2314 case SrcCompositeOp:
2315 case CopyCompositeOp:
2316 case ReplaceCompositeOp:
2317 {
2318 composite=source;
2319 break;
2320 }
2321 case NoCompositeOp:
2322 case DstCompositeOp:
2323 break;
2324 case OverCompositeOp:
2325 case SrcOverCompositeOp:
2326 {
2327 CompositePixelInfoOver(&source,source.alpha,&destination,
2328 destination.alpha,&composite);
2329 break;
2330 }
2331 case DstOverCompositeOp:
2332 {
2333 CompositePixelInfoOver(&destination,destination.alpha,&source,
2334 source.alpha,&composite);
2335 break;
2336 }
2337 case SrcInCompositeOp:
2338 case InCompositeOp:
2339 {
2340 CompositeIn(&source,&destination,&composite);
2341 break;
2342 }
2343 case DstInCompositeOp:
2344 {
2345 CompositeIn(&destination,&source,&composite);
2346 break;
2347 }
2348 case OutCompositeOp:
2349 case SrcOutCompositeOp:
2350 {
2351 CompositeOut(&source,&destination,&composite);
2352 break;
2353 }
2354 case DstOutCompositeOp:
2355 {
2356 CompositeOut(&destination,&source,&composite);
2357 break;
2358 }
2359 case AtopCompositeOp:
2360 case SrcAtopCompositeOp:
2361 {
2362 CompositeAtop(&source,&destination,&composite);
2363 break;
2364 }
2365 case DstAtopCompositeOp:
2366 {
2367 CompositeAtop(&destination,&source,&composite);
2368 break;
2369 }
2370 case XorCompositeOp:
2371 {
2372 CompositeXor(&source,&destination,&composite);
2373 break;
2374 }
2375 case PlusCompositeOp:
2376 {
cristyf4ad9df2011-07-08 16:49:03 +00002377 CompositePlus(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002378 break;
2379 }
2380 case MinusDstCompositeOp:
2381 {
cristyf4ad9df2011-07-08 16:49:03 +00002382 CompositeMinus(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002383 break;
2384 }
2385 case MinusSrcCompositeOp:
2386 {
cristyf4ad9df2011-07-08 16:49:03 +00002387 CompositeMinus(image,&destination,&source,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002388 break;
2389 }
2390 case ModulusAddCompositeOp:
2391 {
cristyf4ad9df2011-07-08 16:49:03 +00002392 CompositeModulusAdd(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002393 break;
2394 }
2395 case ModulusSubtractCompositeOp:
2396 {
cristyf4ad9df2011-07-08 16:49:03 +00002397 CompositeModulusSubtract(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002398 break;
2399 }
2400 case DifferenceCompositeOp:
2401 {
cristyf4ad9df2011-07-08 16:49:03 +00002402 CompositeDifference(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002403 break;
2404 }
2405 case ExclusionCompositeOp:
2406 {
cristyf4ad9df2011-07-08 16:49:03 +00002407 CompositeExclusion(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002408 break;
2409 }
2410 case MultiplyCompositeOp:
2411 {
cristyf4ad9df2011-07-08 16:49:03 +00002412 CompositeMultiply(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002413 break;
2414 }
2415 case ScreenCompositeOp:
2416 {
cristyf4ad9df2011-07-08 16:49:03 +00002417 CompositeScreen(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002418 break;
2419 }
2420 case DivideDstCompositeOp:
2421 {
cristyf4ad9df2011-07-08 16:49:03 +00002422 CompositeDivide(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002423 break;
2424 }
2425 case DivideSrcCompositeOp:
2426 {
cristyf4ad9df2011-07-08 16:49:03 +00002427 CompositeDivide(image,&destination,&source,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002428 break;
2429 }
2430 case DarkenCompositeOp:
2431 {
cristyf4ad9df2011-07-08 16:49:03 +00002432 CompositeDarken(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002433 break;
2434 }
2435 case LightenCompositeOp:
2436 {
cristyf4ad9df2011-07-08 16:49:03 +00002437 CompositeLighten(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002438 break;
2439 }
2440 case DarkenIntensityCompositeOp:
2441 {
cristyf4ad9df2011-07-08 16:49:03 +00002442 CompositeDarkenIntensity(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002443 break;
2444 }
2445 case LightenIntensityCompositeOp:
2446 {
cristyf4ad9df2011-07-08 16:49:03 +00002447 CompositeLightenIntensity(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002448 break;
2449 }
2450 case MathematicsCompositeOp:
2451 {
cristyf4ad9df2011-07-08 16:49:03 +00002452 CompositeMathematics(image,&source,&destination,&geometry_info,
2453 &composite);
cristy4c08aed2011-07-01 19:47:50 +00002454 break;
2455 }
2456 case ColorDodgeCompositeOp:
2457 {
2458 CompositeColorDodge(&source,&destination,&composite);
2459 break;
2460 }
2461 case ColorBurnCompositeOp:
2462 {
2463 CompositeColorBurn(&source,&destination,&composite);
2464 break;
2465 }
2466 case LinearDodgeCompositeOp:
2467 {
2468 CompositeLinearDodge(&source,&destination,&composite);
2469 break;
2470 }
2471 case LinearBurnCompositeOp:
2472 {
2473 CompositeLinearBurn(&source,&destination,&composite);
2474 break;
2475 }
2476 case HardLightCompositeOp:
2477 {
2478 CompositeHardLight(&source,&destination,&composite);
2479 break;
2480 }
2481 case OverlayCompositeOp:
2482 {
2483 CompositeHardLight(&destination,&source,&composite);
2484 break;
2485 }
2486 case SoftLightCompositeOp:
2487 {
2488 CompositeSoftLight(&source,&destination,&composite);
2489 break;
2490 }
2491 case LinearLightCompositeOp:
2492 {
2493 CompositeLinearLight(&source,&destination,&composite);
2494 break;
2495 }
2496 case PegtopLightCompositeOp:
2497 {
2498 CompositePegtopLight(&source,&destination,&composite);
2499 break;
2500 }
2501 case VividLightCompositeOp:
2502 {
2503 CompositeVividLight(&source,&destination,&composite);
2504 break;
2505 }
2506 case PinLightCompositeOp:
2507 {
2508 CompositePinLight(&source,&destination,&composite);
2509 break;
2510 }
2511 case ChangeMaskCompositeOp:
2512 {
2513 if ((composite.alpha > ((MagickRealType) QuantumRange/2.0)) ||
2514 (IsFuzzyEquivalencePixelInfo(&source,&destination) != MagickFalse))
2515 composite.alpha=(MagickRealType) TransparentAlpha;
2516 else
2517 composite.alpha=(MagickRealType) OpaqueAlpha;
2518 break;
2519 }
2520 case BumpmapCompositeOp:
2521 {
2522 if (source.alpha == TransparentAlpha)
2523 break;
2524 CompositeBumpmap(&source,&destination,&composite);
2525 break;
2526 }
2527 case DissolveCompositeOp:
2528 {
2529 CompositePixelInfoOver(&source,source_dissolve*source.alpha,
2530 &destination,(MagickRealType) (destination_dissolve*
2531 destination.alpha),&composite);
2532 break;
2533 }
2534 case BlendCompositeOp:
2535 {
2536 CompositePixelInfoBlend(&source,source_dissolve,&destination,
2537 destination_dissolve,&composite);
2538 break;
2539 }
2540 case ThresholdCompositeOp:
2541 {
2542 CompositeThreshold(&source,&destination,threshold,amount,&composite);
2543 break;
2544 }
2545 case ModulateCompositeOp:
2546 {
2547 ssize_t
2548 offset;
2549
2550 if (source.alpha == TransparentAlpha)
2551 break;
2552 offset=(ssize_t) (GetPixelInfoIntensity(&source)-midpoint);
2553 if (offset == 0)
2554 break;
2555 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2556 &saturation,&brightness);
2557 brightness+=(0.01*percent_brightness*offset)/midpoint;
2558 saturation*=0.01*percent_saturation;
2559 HSBComposite(hue,saturation,brightness,&composite.red,
2560 &composite.green,&composite.blue);
2561 break;
2562 }
2563 case HueCompositeOp:
2564 {
2565 if (source.alpha == TransparentAlpha)
2566 break;
2567 if (destination.alpha == TransparentAlpha)
2568 {
2569 composite=source;
2570 break;
2571 }
2572 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2573 &saturation,&brightness);
2574 CompositeHSB(source.red,source.green,source.blue,&hue,&sans,&sans);
2575 HSBComposite(hue,saturation,brightness,&composite.red,
2576 &composite.green,&composite.blue);
2577 if (source.alpha < destination.alpha)
2578 composite.alpha=source.alpha;
2579 break;
2580 }
2581 case SaturateCompositeOp:
2582 {
2583 if (source.alpha == TransparentAlpha)
2584 break;
2585 if (destination.alpha == TransparentAlpha)
2586 {
2587 composite=source;
2588 break;
2589 }
2590 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2591 &saturation,&brightness);
2592 CompositeHSB(source.red,source.green,source.blue,&sans,&saturation,
2593 &sans);
2594 HSBComposite(hue,saturation,brightness,&composite.red,
2595 &composite.green,&composite.blue);
2596 if (source.alpha < destination.alpha)
2597 composite.alpha=source.alpha;
2598 break;
2599 }
2600 case LuminizeCompositeOp:
2601 {
2602 if (source.alpha == TransparentAlpha)
2603 break;
2604 if (destination.alpha == TransparentAlpha)
2605 {
2606 composite=source;
2607 break;
2608 }
2609 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2610 &saturation,&brightness);
2611 CompositeHSB(source.red,source.green,source.blue,&sans,&sans,
2612 &brightness);
2613 HSBComposite(hue,saturation,brightness,&composite.red,
2614 &composite.green,&composite.blue);
2615 if (source.alpha < destination.alpha)
2616 composite.alpha=source.alpha;
2617 break;
2618 }
2619 case ColorizeCompositeOp:
2620 {
2621 if (source.alpha == TransparentAlpha)
2622 break;
2623 if (destination.alpha == TransparentAlpha)
2624 {
2625 composite=source;
2626 break;
2627 }
2628 CompositeHSB(destination.red,destination.green,destination.blue,&sans,
2629 &sans,&brightness);
2630 CompositeHSB(source.red,source.green,source.blue,&hue,&saturation,
2631 &sans);
2632 HSBComposite(hue,saturation,brightness,&composite.red,
2633 &composite.green,&composite.blue);
2634 if (source.alpha < destination.alpha)
2635 composite.alpha=source.alpha;
2636 break;
2637 }
2638 case CopyRedCompositeOp:
2639 case CopyCyanCompositeOp:
2640 {
2641 composite.red=source.red;
2642 break;
2643 }
2644 case CopyGreenCompositeOp:
2645 case CopyMagentaCompositeOp:
2646 {
2647 composite.green=source.green;
2648 break;
2649 }
2650 case CopyBlueCompositeOp:
2651 case CopyYellowCompositeOp:
2652 {
2653 composite.blue=source.blue;
2654 break;
2655 }
2656 case CopyOpacityCompositeOp:
2657 {
2658 if (source.matte == MagickFalse)
2659 {
2660 composite.alpha=(MagickRealType) GetPixelInfoIntensity(&source);
2661 break;
2662 }
2663 composite.alpha=source.alpha;
2664 break;
2665 }
2666 case CopyBlackCompositeOp:
2667 {
2668 if (source.colorspace != CMYKColorspace)
2669 ConvertRGBToCMYK(&source);
2670 composite.black=source.black;
2671 break;
2672 }
2673 case BlurCompositeOp:
2674 case DisplaceCompositeOp:
2675 case DistortCompositeOp:
2676 {
2677 composite=source;
2678 break;
2679 }
2680 default:
2681 break;
2682 }
2683 if (image->colorspace == CMYKColorspace)
2684 {
2685 composite.red=(MagickRealType) QuantumRange-composite.red;
2686 composite.green=(MagickRealType) QuantumRange-composite.green;
2687 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2688 composite.black=(MagickRealType) QuantumRange-composite.black;
2689 }
2690 SetPixelRed(image,ClampToQuantum(composite.red),q);
2691 SetPixelGreen(image,ClampToQuantum(composite.green),q);
2692 SetPixelBlue(image,ClampToQuantum(composite.blue),q);
2693 if (image->colorspace == CMYKColorspace)
2694 SetPixelBlack(image,ClampToQuantum(composite.black),q);
2695 SetPixelAlpha(image,ClampToQuantum(composite.alpha),q);
cristydcfc1ad2011-07-07 16:25:41 +00002696 p+=GetPixelComponents(composite_image);
2697 if (p >= (pixels+composite_image->columns*GetPixelComponents(composite_image)))
cristy4c08aed2011-07-01 19:47:50 +00002698 p=pixels;
cristydcfc1ad2011-07-07 16:25:41 +00002699 q+=GetPixelComponents(image);
cristy4c08aed2011-07-01 19:47:50 +00002700 }
2701 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2702 status=MagickFalse;
2703 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2704 {
2705 MagickBooleanType
2706 proceed;
2707
2708#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002709 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002710#endif
2711 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2712 image->rows);
2713 if (proceed == MagickFalse)
2714 status=MagickFalse;
2715 }
2716 }
2717 composite_view=DestroyCacheView(composite_view);
2718 image_view=DestroyCacheView(image_view);
2719 if (destination_image != (Image * ) NULL)
2720 destination_image=DestroyImage(destination_image);
2721 return(status);
2722}
2723
2724/*
2725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2726% %
2727% %
2728% %
2729% T e x t u r e I m a g e %
2730% %
2731% %
2732% %
2733%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2734%
2735% TextureImage() repeatedly tiles the texture image across and down the image
2736% canvas.
2737%
2738% The format of the TextureImage method is:
2739%
2740% MagickBooleanType TextureImage(Image *image,const Image *texture)
2741%
2742% A description of each parameter follows:
2743%
2744% o image: the image.
2745%
2746% o texture: This image is the texture to layer on the background.
2747%
2748*/
2749MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture)
2750{
2751#define TextureImageTag "Texture/Image"
2752
2753 CacheView
2754 *image_view,
2755 *texture_view;
2756
2757 ExceptionInfo
2758 *exception;
2759
2760 MagickBooleanType
2761 status;
2762
2763 ssize_t
2764 y;
2765
2766 assert(image != (Image *) NULL);
2767 if (image->debug != MagickFalse)
2768 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2769 assert(image->signature == MagickSignature);
2770 if (texture == (const Image *) NULL)
2771 return(MagickFalse);
2772 (void) SetImageVirtualPixelMethod(texture,TileVirtualPixelMethod);
2773 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2774 return(MagickFalse);
2775 status=MagickTrue;
2776 if ((image->compose != CopyCompositeOp) &&
2777 ((image->compose != OverCompositeOp) || (image->matte != MagickFalse) ||
2778 (texture->matte != MagickFalse)))
2779 {
2780 /*
2781 Tile texture onto the image background.
2782 */
2783#if defined(MAGICKCORE_OPENMP_SUPPORT)
2784 #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1)
2785#endif
2786 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture->rows)
2787 {
2788 register ssize_t
2789 x;
2790
2791 if (status == MagickFalse)
2792 continue;
2793 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture->columns)
2794 {
2795 MagickBooleanType
2796 thread_status;
2797
2798 thread_status=CompositeImage(image,image->compose,texture,x+
2799 texture->tile_offset.x,y+texture->tile_offset.y);
2800 if (thread_status == MagickFalse)
2801 {
2802 status=thread_status;
2803 break;
2804 }
2805 }
2806 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2807 {
2808 MagickBooleanType
2809 proceed;
2810
2811#if defined(MAGICKCORE_OPENMP_SUPPORT)
2812 #pragma omp critical (MagickCore_TextureImage)
2813#endif
2814 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2815 y,image->rows);
2816 if (proceed == MagickFalse)
2817 status=MagickFalse;
2818 }
2819 }
2820 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2821 image->rows,image->rows);
2822 return(status);
2823 }
2824 /*
2825 Tile texture onto the image background (optimized).
2826 */
2827 status=MagickTrue;
2828 exception=(&image->exception);
2829 image_view=AcquireCacheView(image);
2830 texture_view=AcquireCacheView(texture);
2831#if defined(MAGICKCORE_OPENMP_SUPPORT)
2832 #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1)
2833#endif
2834 for (y=0; y < (ssize_t) image->rows; y++)
2835 {
2836 MagickBooleanType
2837 sync;
2838
2839 register const Quantum
2840 *p,
2841 *pixels;
2842
2843 register ssize_t
2844 x;
2845
2846 register Quantum
2847 *q;
2848
2849 size_t
2850 width;
2851
2852 if (status == MagickFalse)
2853 continue;
2854 pixels=GetCacheViewVirtualPixels(texture_view,texture->tile_offset.x,(y+
2855 texture->tile_offset.y) % texture->rows,texture->columns,1,exception);
2856 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2857 exception);
2858 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2859 {
2860 status=MagickFalse;
2861 continue;
2862 }
2863 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture->columns)
2864 {
2865 register ssize_t
2866 i;
2867
2868 p=pixels;
2869 width=texture->columns;
2870 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2871 width=image->columns-x;
2872 for (i=0; i < (ssize_t) width; i++)
2873 {
2874 SetPixelRed(image,GetPixelRed(texture,p),q);
2875 SetPixelGreen(image,GetPixelGreen(texture,p),q);
2876 SetPixelBlue(image,GetPixelBlue(texture,p),q);
2877 SetPixelAlpha(image,GetPixelAlpha(texture,p),q);
2878 if ((image->colorspace == CMYKColorspace) &&
2879 (texture->colorspace == CMYKColorspace))
2880 SetPixelBlack(image,GetPixelBlack(texture,p),q);
cristydcfc1ad2011-07-07 16:25:41 +00002881 p+=GetPixelComponents(texture);
2882 q+=GetPixelComponents(image);
cristy4c08aed2011-07-01 19:47:50 +00002883 }
2884 }
2885 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2886 if (sync == MagickFalse)
2887 status=MagickFalse;
2888 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2889 {
2890 MagickBooleanType
2891 proceed;
2892
2893#if defined(MAGICKCORE_OPENMP_SUPPORT)
2894 #pragma omp critical (MagickCore_TextureImage)
2895#endif
2896 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2897 image->rows);
2898 if (proceed == MagickFalse)
2899 status=MagickFalse;
2900 }
2901 }
2902 texture_view=DestroyCacheView(texture_view);
2903 image_view=DestroyCacheView(image_view);
2904 return(status);
2905}