blob: f54ad4e560c045b35bf7dd64f6f8e4c29b6dc047 [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"
cristy52010022011-10-21 18:07:37 +000046#include "MagickCore/cache-private.h"
cristy4c08aed2011-07-01 19:47:50 +000047#include "MagickCore/cache-view.h"
48#include "MagickCore/client.h"
49#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
52#include "MagickCore/colorspace-private.h"
53#include "MagickCore/composite.h"
54#include "MagickCore/composite-private.h"
55#include "MagickCore/constitute.h"
56#include "MagickCore/draw.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/memory_.h"
67#include "MagickCore/option.h"
68#include "MagickCore/pixel-accessor.h"
69#include "MagickCore/property.h"
70#include "MagickCore/quantum.h"
71#include "MagickCore/resample.h"
72#include "MagickCore/resource_.h"
73#include "MagickCore/string_.h"
74#include "MagickCore/thread-private.h"
75#include "MagickCore/utility.h"
cristyd1dd6e42011-09-04 01:46:08 +000076#include "MagickCore/utility-private.h"
cristy4c08aed2011-07-01 19:47:50 +000077#include "MagickCore/version.h"
78
79/*
80%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81% %
82% %
83% %
cristyf4ad9df2011-07-08 16:49:03 +000084% C o m p o s i t e I m a g e %
cristy4c08aed2011-07-01 19:47:50 +000085% %
86% %
87% %
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89%
cristyf4ad9df2011-07-08 16:49:03 +000090% CompositeImage() returns the second image composited onto the first
cristy4c08aed2011-07-01 19:47:50 +000091% at the specified offset, using the specified composite method.
92%
cristyf4ad9df2011-07-08 16:49:03 +000093% The format of the CompositeImage method is:
cristy4c08aed2011-07-01 19:47:50 +000094%
95% MagickBooleanType CompositeImage(Image *image,
96% const CompositeOperator compose,Image *composite_image,
cristye941a752011-10-15 01:52:48 +000097% const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +000098%
99% A description of each parameter follows:
100%
101% o image: the destination image, modified by he composition
102%
cristy4c08aed2011-07-01 19:47:50 +0000103% o compose: This operator affects how the composite is applied to
104% the image. The operators and how they are utilized are listed here
105% http://www.w3.org/TR/SVG12/#compositing.
106%
107% o composite_image: the composite (source) image.
108%
109% o x_offset: the column offset of the composited image.
110%
111% o y_offset: the row offset of the composited image.
112%
113% Extra Controls from Image meta-data in 'composite_image' (artifacts)
114%
115% o "compose:args"
116% A string containing extra numerical arguments for specific compose
117% methods, generally expressed as a 'geometry' or a comma separated list
118% of numbers.
119%
120% Compose methods needing such arguments include "BlendCompositeOp" and
121% "DisplaceCompositeOp".
122%
123% o "compose:outside-overlay"
124% Modify how the composition is to effect areas not directly covered
125% by the 'composite_image' at the offset given. Normally this is
126% dependant on the 'compose' method, especially Duff-Porter methods.
127%
128% If set to "false" then disable all normal handling of pixels not
129% covered by the composite_image. Typically used for repeated tiling
130% of the composite_image by the calling API.
131%
132% Previous to IM v6.5.3-3 this was called "modify-outside-overlay"
133%
cristye941a752011-10-15 01:52:48 +0000134% o exception: return any errors or warnings in this structure.
135%
cristy4c08aed2011-07-01 19:47:50 +0000136*/
137
138static inline double MagickMin(const double x,const double y)
139{
140 if (x < y)
141 return(x);
142 return(y);
143}
144static inline double MagickMax(const double x,const double y)
145{
146 if (x > y)
147 return(x);
148 return(y);
149}
150
151/*
152 Programmers notes on SVG specification.
153
154 A Composition is defined by...
155 Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
156 Blending areas : X = 1 for area of overlap ie: f(Sc,Dc)
157 Y = 1 for source preserved
158 Z = 1 for destination preserved
159
160 Conversion to transparency (then optimized)
161 Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
162 Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
163
164 Where...
165 Sca = Sc*Sa normalized Source color divided by Source alpha
166 Dca = Dc*Da normalized Dest color divided by Dest alpha
167 Dc' = Dca'/Da' the desired color value for this channel.
168
anthonydc248f82011-10-23 07:58:53 +0000169 Da' (alpha result) is stored as 'gamma' in the functions.
cristy4c08aed2011-07-01 19:47:50 +0000170
anthonydc248f82011-10-23 07:58:53 +0000171 The compose functions defined is just simplifications of the above
172 formula on a case by case bases.
173
174
175
176 The above SVG definitions also defines that Mathematical Composition
cristy4c08aed2011-07-01 19:47:50 +0000177 methods should use a 'Over' blending mode for Alpha Channel.
178 It however was not applied for composition modes of 'Plus', 'Minus',
179 the modulus versions of 'Add' and 'Subtract'.
180
181 Mathematical operator changes to be applied from IM v6.7...
182
183 1/ Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
184 'ModulusAdd' and 'ModulusSubtract' for clarity.
185
186 2/ All mathematical compositions work as per the SVG specification
187 with regard to blending. This now includes 'ModulusAdd' and
188 'ModulusSubtract'.
189
190 3/ When the special channel flag 'sync' (syncronize channel updates)
191 is turned off (enabled by default) then mathematical compositions are
192 only performed on the channels specified, and are applied
193 independantally of each other. In other words the mathematics is
194 performed as 'pure' mathematical operations, rather than as image
195 operations.
196*/
197
198static inline MagickRealType Atop(const MagickRealType p,
199 const MagickRealType Sa,const MagickRealType q,
200 const MagickRealType magick_unused(Da))
201{
202 return(p*Sa+q*(1.0-Sa)); /* Da optimized out, Da/gamma => 1.0 */
203}
204
205static inline void CompositeAtop(const PixelInfo *p,const PixelInfo *q,
206 PixelInfo *composite)
207{
208 MagickRealType
209 Sa;
210
211 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
212 composite->alpha=q->alpha; /* optimized Da = 1.0-Gamma */
213 composite->red=Atop(p->red,Sa,q->red,1.0);
214 composite->green=Atop(p->green,Sa,q->green,1.0);
215 composite->blue=Atop(p->blue,Sa,q->blue,1.0);
216 if (q->colorspace == CMYKColorspace)
217 composite->black=Atop(p->black,Sa,q->black,1.0);
218}
219
220/*
anthony88149d72011-10-23 07:29:23 +0000221 Bumpmap: Multiply by overlay intensity
222 What is this Composition actually method for? Can't find any specification!
223
224 I think this was meant to be a 'HardLight effect' using a Shaded Image!
225 That is a Embossing, using a height map! Better to do it piecemeal.
cristy4c08aed2011-07-01 19:47:50 +0000226*/
227static inline void CompositeBumpmap(const PixelInfo *p,const PixelInfo *q,
228 PixelInfo *composite)
229{
230 MagickRealType
231 intensity;
232
233 intensity=(MagickRealType) GetPixelInfoIntensity(p);
234 composite->red=QuantumScale*intensity*q->red;
235 composite->green=QuantumScale*intensity*q->green;
236 composite->blue=QuantumScale*intensity*q->blue;
237 composite->alpha=(MagickRealType) QuantumScale*intensity*p->alpha;
238 if (q->colorspace == CMYKColorspace)
239 composite->black=QuantumScale*intensity*q->black;
240}
241
242static inline void CompositeClear(const PixelInfo *q,PixelInfo *composite)
243{
244 composite->alpha=(MagickRealType) TransparentAlpha;
245 composite->red=0.0;
246 composite->green=0.0;
247 composite->blue=0.0;
anthonydc248f82011-10-23 07:58:53 +0000248 composite->black=1.0;
cristy4c08aed2011-07-01 19:47:50 +0000249}
250
251static MagickRealType ColorBurn(const MagickRealType Sca,
252 const MagickRealType Sa, const MagickRealType Dca,const MagickRealType Da)
253{
254 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca-Da) < MagickEpsilon))
255 return(Sa*Da+Dca*(1.0-Sa));
256 if (Sca < MagickEpsilon)
257 return(Dca*(1.0-Sa));
258 return(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*Sa/Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
259}
260
261static inline void CompositeColorBurn(const PixelInfo *p,const PixelInfo *q,
262 PixelInfo *composite)
263{
264 MagickRealType
265 Da,
266 gamma,
267 Sa;
268
269 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
270 Da=QuantumScale*q->alpha;
271 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
272 composite->alpha=(MagickRealType) QuantumRange*gamma;
273 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
274 composite->red=gamma*ColorBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
275 q->red*Da,Da);
276 composite->green=gamma*ColorBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
277 q->green*Da,Da);
278 composite->blue=gamma*ColorBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
279 q->blue*Da,Da);
280 if (q->colorspace == CMYKColorspace)
281 composite->black=gamma*ColorBurn(QuantumScale*p->black*Sa,Sa,QuantumScale*
282 q->black*Da,Da);
283}
284
285
286static MagickRealType ColorDodge(const MagickRealType Sca,
287 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
288{
289 /*
290 Working from first principles using the original formula:
291
292 f(Sc,Dc) = Dc/(1-Sc)
293
294 This works correctly! Looks like the 2004 SVG model was right but just
295 required a extra condition for correct handling.
296 */
297 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
298 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
299 if (fabs(Sca-Sa) < MagickEpsilon)
300 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
301 return(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
302}
303
304static inline void CompositeColorDodge(const PixelInfo *p,const PixelInfo *q,
305 PixelInfo *composite)
306{
307 MagickRealType
308 Da,
309 gamma,
310 Sa;
311
312 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
313 Da=QuantumScale*q->alpha;
314 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
315 composite->alpha=(MagickRealType) QuantumRange*gamma;
316 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
317 composite->red=gamma*ColorDodge(QuantumScale*p->red*Sa,Sa,QuantumScale*
318 q->red*Da,Da);
319 composite->green=gamma*ColorDodge(QuantumScale*p->green*Sa,Sa,QuantumScale*
320 q->green*Da,Da);
321 composite->blue=gamma*ColorDodge(QuantumScale*p->blue*Sa,Sa,QuantumScale*
322 q->blue*Da,Da);
323 if (q->colorspace == CMYKColorspace)
324 composite->black=gamma*ColorDodge(QuantumScale*p->black*Sa,Sa,QuantumScale*
325 q->black*Da,Da);
326}
327
328static inline MagickRealType Darken(const MagickRealType p,
329 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
330{
331 if (p < q)
332 return(MagickOver_(p,alpha,q,beta)); /* src-over */
333 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
334}
335
cristy2b9582a2011-07-04 17:38:56 +0000336static inline void CompositeDarken(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000337 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000338{
339 MagickRealType
340 gamma;
341
342 /*
343 Darken is equivalent to a 'Minimum' method OR a greyscale version of a
344 binary 'Or' OR the 'Intersection' of pixel sets.
345 */
cristy5f95f4f2011-10-23 01:01:01 +0000346 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000347 {
348 /*
349 Handle channels as separate grayscale channels.
350 */
cristyed231572011-07-14 02:18:59 +0000351 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000352 composite->red=MagickMin(p->red,q->red);
cristyed231572011-07-14 02:18:59 +0000353 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000354 composite->green=MagickMin(p->green,q->green);
cristyed231572011-07-14 02:18:59 +0000355 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000356 composite->blue=MagickMin(p->blue,q->blue);
cristyed231572011-07-14 02:18:59 +0000357 if ((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0 &&
cristy4c08aed2011-07-01 19:47:50 +0000358 (q->colorspace == CMYKColorspace))
359 composite->black=MagickMin(p->black,q->black);
cristyed231572011-07-14 02:18:59 +0000360 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000361 composite->alpha=MagickMax(p->alpha,q->alpha);
362 return;
363 }
364 composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */
365 gamma=1.0-QuantumScale*composite->alpha;
366 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
367 composite->red=gamma*Darken(p->red,p->alpha,q->red,q->alpha);
368 composite->green=gamma*Darken(p->green,p->alpha,q->green,q->alpha);
369 composite->blue=gamma*Darken(p->blue,p->alpha,q->blue,q->alpha);
370 if (q->colorspace == CMYKColorspace)
371 composite->black=gamma*Darken(p->black,p->alpha,q->black,q->alpha);
372}
373
cristy2b9582a2011-07-04 17:38:56 +0000374static inline void CompositeDarkenIntensity(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +0000375 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000376{
377 MagickRealType
378 Da,
379 Sa;
380
381 /*
382 Select the pixel based on the intensity level.
383 If 'Sync' flag select whole pixel based on alpha weighted intensity.
384 Otherwise use intensity only, but restrict copy according to channel.
385 */
cristy5f95f4f2011-10-23 01:01:01 +0000386 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000387 {
388 MagickBooleanType
389 from_p;
390
391 from_p=GetPixelInfoIntensity(p) < GetPixelInfoIntensity(q) ? MagickTrue :
392 MagickFalse;
cristyed231572011-07-14 02:18:59 +0000393 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000394 composite->red=from_p != MagickFalse ? p->red : q->red;
cristyed231572011-07-14 02:18:59 +0000395 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000396 composite->green=from_p != MagickFalse ? p->green : q->green;
cristyed231572011-07-14 02:18:59 +0000397 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000398 composite->blue=from_p != MagickFalse ? p->blue : q->blue;
cristyed231572011-07-14 02:18:59 +0000399 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000400 (q->colorspace == CMYKColorspace))
401 composite->black=from_p != MagickFalse ? p->black : q->black;
cristyed231572011-07-14 02:18:59 +0000402 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000403 composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha;
404 return;
405 }
406 Sa=QuantumScale*p->alpha;
407 Da=QuantumScale*q->alpha;
408 *composite=(Sa*GetPixelInfoIntensity(p) < Da*GetPixelInfoIntensity(q)) ?
409 *p : *q;
410}
411
412static inline MagickRealType Difference(const MagickRealType p,
413 const MagickRealType Sa,const MagickRealType q,const MagickRealType Da)
414{
415 /*
416 Optimized by Multipling by QuantumRange (taken from gamma).
417 */
418 return(Sa*p+Da*q-Sa*Da*2.0*MagickMin(p,q));
419}
420
cristy2b9582a2011-07-04 17:38:56 +0000421static inline void CompositeDifference(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000422 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000423{
424 MagickRealType
425 Da,
426 gamma,
427 Sa;
428
429 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
430 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +0000431 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000432 {
433 /*
434 Handle channels as separate grayscale channels.
435 */
cristyed231572011-07-14 02:18:59 +0000436 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000437 composite->red=fabs((double) (p->red-q->red));
cristyed231572011-07-14 02:18:59 +0000438 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000439 composite->green=fabs((double) (p->green-q->green));
cristyed231572011-07-14 02:18:59 +0000440 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000441 composite->blue=fabs((double) (p->blue-q->blue));
cristyed231572011-07-14 02:18:59 +0000442 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000443 (q->colorspace == CMYKColorspace))
444 composite->black=fabs((double) (p->black-q->black));
cristyed231572011-07-14 02:18:59 +0000445 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000446 composite->alpha=fabs((double) (p->alpha-q->alpha));
447 return;
448 }
449 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
450 composite->alpha=(MagickRealType) QuantumRange*gamma;
451 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
452 composite->red=gamma*Difference(p->red,Sa,q->red,Da);
453 composite->green=gamma*Difference(p->green,Sa,q->green,Da);
454 composite->blue=gamma*Difference(p->blue,Sa,q->blue,Da);
455 if (q->colorspace == CMYKColorspace)
456 composite->black=gamma*Difference(p->black,Sa,q->black,Da);
457}
458
459static MagickRealType Divide(const MagickRealType Sca,const MagickRealType Sa,
460 const MagickRealType Dca,const MagickRealType Da)
461{
462 /*
463 Divide Source by Destination
464
465 f(Sc,Dc) = Sc / Dc
466
467 But with appropriate handling for special case of Dc == 0 specifically
468 so that f(Black,Black)=Black and f(non-Black,Black)=White.
469 It is however also important to correctly do 'over' alpha blending which
470 is why the formula becomes so complex.
471 */
472 if ((fabs(Sca) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
473 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
474 if (fabs(Dca) < MagickEpsilon)
475 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
476 return(Sca*Da*Da/Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
477}
478
cristy2b9582a2011-07-04 17:38:56 +0000479static inline void CompositeDivide(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000480 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000481{
482 MagickRealType
483 Da,
484 gamma,
485 Sa;
486
487 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
488 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +0000489 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000490 {
491 /*
492 Handle channels as separate grayscale channels.
493 */
cristyed231572011-07-14 02:18:59 +0000494 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000495 composite->red=QuantumRange*Divide(QuantumScale*p->red,1.0,
496 QuantumScale*q->red,1.0);
cristyed231572011-07-14 02:18:59 +0000497 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000498 composite->green=QuantumRange*Divide(QuantumScale*p->green,1.0,
499 QuantumScale*q->green,1.0);
cristyed231572011-07-14 02:18:59 +0000500 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000501 composite->blue=QuantumRange*Divide(QuantumScale*p->blue,1.0,
502 QuantumScale*q->blue,1.0);
cristyed231572011-07-14 02:18:59 +0000503 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000504 (q->colorspace == CMYKColorspace))
505 composite->black=QuantumRange*Divide(QuantumScale*p->black,1.0,
506 QuantumScale*q->black,1.0);
cristyed231572011-07-14 02:18:59 +0000507 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000508 composite->alpha=QuantumRange*(1.0-Divide(Sa,1.0,Da,1.0));
509 return;
510 }
511 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
512 composite->alpha=(MagickRealType) QuantumRange*gamma;
513 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
514 composite->red=gamma*Divide(QuantumScale*p->red*Sa,Sa,QuantumScale*
515 q->red*Da,Da);
516 composite->green=gamma*Divide(QuantumScale*p->green*Sa,Sa,QuantumScale*
517 q->green*Da,Da);
518 composite->blue=gamma*Divide(QuantumScale*p->blue*Sa,Sa,QuantumScale*
519 q->blue*Da,Da);
520 if (q->colorspace == CMYKColorspace)
521 composite->black=gamma*Divide(QuantumScale*p->black*Sa,Sa,QuantumScale*
522 q->black*Da,Da);
523}
524
525static MagickRealType Exclusion(const MagickRealType Sca,
526 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
527{
528 return(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
529}
530
cristy2b9582a2011-07-04 17:38:56 +0000531static inline void CompositeExclusion(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000532 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000533{
534 MagickRealType
535 gamma,
536 Sa,
537 Da;
538
539 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
540 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +0000541 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000542 {
543 /*
544 Handle channels as separate grayscale channels.
545 */
cristyed231572011-07-14 02:18:59 +0000546 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000547 composite->red=QuantumRange*Exclusion(QuantumScale*p->red,1.0,
548 QuantumScale*q->red,1.0);
cristyed231572011-07-14 02:18:59 +0000549 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000550 composite->green=QuantumRange*Exclusion(QuantumScale*p->green,1.0,
551 QuantumScale*q->green,1.0);
cristyed231572011-07-14 02:18:59 +0000552 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000553 composite->blue=QuantumRange*Exclusion(QuantumScale*p->blue,1.0,
554 QuantumScale*q->blue,1.0);
cristyed231572011-07-14 02:18:59 +0000555 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000556 (q->colorspace == CMYKColorspace))
557 composite->black=QuantumRange*Exclusion(QuantumScale*p->black,1.0,
558 QuantumScale*q->black,1.0);
cristyed231572011-07-14 02:18:59 +0000559 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000560 composite->alpha=QuantumRange*(1.0-Exclusion(Sa,1.0,Da,1.0));
561 return;
562 }
563 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
564 composite->alpha=(MagickRealType) QuantumRange*gamma;
565 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
566 composite->red=gamma*Exclusion(QuantumScale*p->red*Sa,Sa,QuantumScale*
567 q->red*Da,Da);
568 composite->green=gamma*Exclusion(QuantumScale*p->green*Sa,Sa,QuantumScale*
569 q->green*Da,Da);
570 composite->blue=gamma*Exclusion(QuantumScale*p->blue*Sa,Sa,QuantumScale*
571 q->blue*Da,Da);
572 if (q->colorspace == CMYKColorspace)
573 composite->black=gamma*Exclusion(QuantumScale*p->black*Sa,Sa,
574 QuantumScale*q->black*Da,Da);
575}
576
577static MagickRealType HardLight(const MagickRealType Sca,
578 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
579{
580 if ((2.0*Sca) < Sa)
581 return(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
582 return(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
583}
584
585static inline void CompositeHardLight(const PixelInfo *p,const PixelInfo *q,
586 PixelInfo *composite)
587{
588 MagickRealType
589 Da,
590 gamma,
591 Sa;
592
593 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
594 Da=QuantumScale*q->alpha;
595 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
596 composite->alpha=(MagickRealType) QuantumRange*gamma;
597 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
598 composite->red=gamma*HardLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
599 q->red*Da,Da);
600 composite->green=gamma*HardLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
601 q->green*Da,Da);
602 composite->blue=gamma*HardLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
603 q->blue*Da,Da);
604 if (q->colorspace == CMYKColorspace)
605 composite->black=gamma*HardLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
606 q->black*Da,Da);
607}
608
609static void CompositeHSB(const MagickRealType red,const MagickRealType green,
610 const MagickRealType blue,double *hue,double *saturation,double *brightness)
611{
612 MagickRealType
613 delta,
614 max,
615 min;
616
617 /*
618 Convert RGB to HSB colorspace.
619 */
620 assert(hue != (double *) NULL);
621 assert(saturation != (double *) NULL);
622 assert(brightness != (double *) NULL);
623 max=(red > green ? red : green);
624 if (blue > max)
625 max=blue;
626 min=(red < green ? red : green);
627 if (blue < min)
628 min=blue;
629 *hue=0.0;
630 *saturation=0.0;
631 *brightness=(double) (QuantumScale*max);
cristyaa83c2c2011-09-21 13:36:25 +0000632 if (fabs(max) < MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000633 return;
634 *saturation=(double) (1.0-min/max);
635 delta=max-min;
cristyaa83c2c2011-09-21 13:36:25 +0000636 if (fabs(delta) < MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000637 return;
cristyaa83c2c2011-09-21 13:36:25 +0000638 if (fabs(red-max) < MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000639 *hue=(double) ((green-blue)/delta);
640 else
cristyaa83c2c2011-09-21 13:36:25 +0000641 if (fabs(green-max) < MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000642 *hue=(double) (2.0+(blue-red)/delta);
643 else
cristyaa83c2c2011-09-21 13:36:25 +0000644 if (fabs(blue-max) < MagickEpsilon)
cristy4c08aed2011-07-01 19:47:50 +0000645 *hue=(double) (4.0+(red-green)/delta);
646 *hue/=6.0;
647 if (*hue < 0.0)
648 *hue+=1.0;
649}
650
anthony88149d72011-10-23 07:29:23 +0000651#if 0
cristy4c08aed2011-07-01 19:47:50 +0000652static inline MagickRealType In(const MagickRealType p,const MagickRealType Sa,
653 const MagickRealType magick_unused(q),const MagickRealType Da)
654{
655 return(Sa*p*Da);
656}
anthony88149d72011-10-23 07:29:23 +0000657#endif
cristy4c08aed2011-07-01 19:47:50 +0000658
659static inline void CompositeIn(const PixelInfo *p,const PixelInfo *q,
660 PixelInfo *composite)
661{
anthony88149d72011-10-23 07:29:23 +0000662#if 0
cristy4c08aed2011-07-01 19:47:50 +0000663 MagickRealType
664 gamma,
665 Sa,
666 Da;
667
668 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
669 Da=QuantumScale*q->alpha;
670 gamma=Sa*Da;
671 composite->alpha=(MagickRealType) QuantumRange*gamma;
672 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony88149d72011-10-23 07:29:23 +0000673 /* really this just preserves the src or p color as is! */
cristy4c08aed2011-07-01 19:47:50 +0000674 composite->red=gamma*In(p->red,Sa,q->red,Da);
675 composite->green=gamma*In(p->green,Sa,q->green,Da);
676 composite->blue=gamma*In(p->blue,Sa,q->blue,Da);
677 if (q->colorspace == CMYKColorspace)
678 composite->black=gamma*In(p->black,Sa,q->black,Da);
anthony88149d72011-10-23 07:29:23 +0000679#else
680 /* Simplified to a multiply of the Alpha Channel */
anthonyfe381212011-10-23 07:41:00 +0000681 *composite=*p; /* structure copy */
anthony88149d72011-10-23 07:29:23 +0000682 composite->alpha=QuantumScale*p->alpha*q->alpha;
683#endif
cristy4c08aed2011-07-01 19:47:50 +0000684}
685
686static inline MagickRealType Lighten(const MagickRealType p,
687 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
688{
689 if (p > q)
690 return(MagickOver_(p,alpha,q,beta)); /* src-over */
691 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
692}
693
cristy2b9582a2011-07-04 17:38:56 +0000694static inline void CompositeLighten(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000695 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000696{
697 MagickRealType
698 gamma;
699
700 /*
701 Lighten is also equvalent to a 'Maximum' method OR a greyscale version of a
702 binary 'And' OR the 'Union' of pixel sets.
703 */
cristy5f95f4f2011-10-23 01:01:01 +0000704 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000705 {
706 /*
707 Handle channels as separate grayscale channels
708 */
cristyed231572011-07-14 02:18:59 +0000709 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000710 composite->red=MagickMax(p->red,q->red);
cristyed231572011-07-14 02:18:59 +0000711 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000712 composite->green=MagickMax(p->green,q->green);
cristyed231572011-07-14 02:18:59 +0000713 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000714 composite->blue=MagickMax(p->blue,q->blue);
cristyed231572011-07-14 02:18:59 +0000715 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000716 (q->colorspace == CMYKColorspace))
717 composite->black=MagickMax(p->black,q->black);
cristyed231572011-07-14 02:18:59 +0000718 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000719 composite->alpha=MagickMin(p->alpha,q->alpha);
720 return;
721 }
722 composite->alpha=QuantumScale*p->alpha*q->alpha; /* Over Blend */
723 gamma=1.0-QuantumScale*composite->alpha;
724 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
725 composite->red=gamma*Lighten(p->red,p->alpha,q->red,q->alpha);
726 composite->green=gamma*Lighten(p->green,p->alpha,q->green,q->alpha);
727 composite->blue=gamma*Lighten(p->blue,p->alpha,q->blue,q->alpha);
728 if (q->colorspace == CMYKColorspace)
729 composite->black=gamma*Lighten(p->black,p->alpha,q->black,q->alpha);
730}
731
cristy2b9582a2011-07-04 17:38:56 +0000732static inline void CompositeLightenIntensity(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +0000733 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000734{
735 MagickRealType
736 Da,
737 Sa;
738
739 /*
740 Select the pixel based on the intensity level.
741 If 'Sync' flag select whole pixel based on alpha weighted intensity.
742 Otherwise use Intenisty only, but restrict copy according to channel.
743 */
cristy5f95f4f2011-10-23 01:01:01 +0000744 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000745 {
746 MagickBooleanType
747 from_p;
748
749 from_p=GetPixelInfoIntensity(p) > GetPixelInfoIntensity(q) ? MagickTrue :
750 MagickFalse;
cristyed231572011-07-14 02:18:59 +0000751 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000752 composite->red=from_p != MagickFalse ? p->red : q->red;
cristyed231572011-07-14 02:18:59 +0000753 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000754 composite->green=from_p != MagickFalse ? p->green : q->green;
cristyed231572011-07-14 02:18:59 +0000755 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000756 composite->blue=from_p != MagickFalse ? p->blue : q->blue;
cristyed231572011-07-14 02:18:59 +0000757 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000758 (q->colorspace == CMYKColorspace))
759 composite->black=from_p != MagickFalse ? p->black : q->black;
cristyed231572011-07-14 02:18:59 +0000760 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000761 composite->alpha=from_p != MagickFalse ? p->alpha : q->alpha;
762 return;
763 }
764 Sa=QuantumScale*p->alpha;
765 Da=QuantumScale*q->alpha;
766 *composite=(Sa*GetPixelInfoIntensity(p) > Da*GetPixelInfoIntensity(q)) ?
767 *p : *q;
768}
769
770static inline void CompositeLinearDodge(const PixelInfo *p,const PixelInfo *q,
771 PixelInfo *composite)
772{
773 MagickRealType
774 Da,
775 gamma,
776 Sa;
777
778 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
779 Da=QuantumScale*q->alpha;
780 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
781 composite->alpha=(MagickRealType) QuantumRange*gamma;
782 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
783 composite->red=gamma*(p->red*Sa+q->red*Da);
784 composite->green=gamma*(p->green*Sa+q->green*Da);
785 composite->blue=gamma*(p->blue*Sa+q->blue*Da);
786 if (q->colorspace == CMYKColorspace)
787 composite->black=gamma*(p->black*Sa+q->black*Da);
788}
789
790
791static inline MagickRealType LinearBurn(const MagickRealType Sca,
792 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
793{
794 /*
795 LinearBurn: as defined by Abode Photoshop, according to
796 http://www.simplefilter.de/en/basics/mixmods.html is:
797
798 f(Sc,Dc) = Sc + Dc - 1
799 */
800 return(Sca+Dca-Sa*Da);
801}
802
803static inline void CompositeLinearBurn(const PixelInfo *p,const PixelInfo *q,
804 PixelInfo *composite)
805{
806 MagickRealType
807 Da,
808 gamma,
809 Sa;
810
811 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
812 Da=QuantumScale*q->alpha;
813 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
814 composite->alpha=(MagickRealType) QuantumRange*gamma;
815 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
816 composite->red=gamma*LinearBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
817 q->red*Da,Da);
818 composite->green=gamma*LinearBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
819 q->green*Da,Da);
820 composite->blue=gamma*LinearBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
821 q->blue*Da,Da);
822 if (q->colorspace == CMYKColorspace)
823 composite->black=gamma*LinearBurn(QuantumScale*p->black*Sa,Sa,QuantumScale*
824 q->black*Da,Da);
825}
826
827static inline MagickRealType LinearLight(const MagickRealType Sca,
828 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
829{
830 /*
831 LinearLight: as defined by Abode Photoshop, according to
832 http://www.simplefilter.de/en/basics/mixmods.html is:
833
834 f(Sc,Dc) = Dc + 2*Sc - 1
835 */
836 return((Sca-Sa)*Da+Sca+Dca);
837}
838
839static inline void CompositeLinearLight(const PixelInfo *p,const PixelInfo *q,
840 PixelInfo *composite)
841{
842 MagickRealType
843 Da,
844 gamma,
845 Sa;
846
847 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
848 Da=QuantumScale*q->alpha;
849 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
850 composite->alpha=(MagickRealType) QuantumRange*gamma;
851 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
852 composite->red=gamma*LinearLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
853 q->red*Da,Da);
854 composite->green=gamma*LinearLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
855 q->green*Da,Da);
856 composite->blue=gamma*LinearLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
857 q->blue*Da,Da);
858 if (q->colorspace == CMYKColorspace)
859 composite->black=gamma*LinearLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
860 q->black*Da,Da);
861}
862
863static inline MagickRealType Mathematics(const MagickRealType Sca,
864 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da,
865 const GeometryInfo *geometry_info)
866{
867 MagickRealType
868 gamma;
869
870 /*
871 'Mathematics' a free form user control mathematical composition is defined
872 as...
873
874 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
875
876 Where the arguments A,B,C,D are (currently) passed to composite as
877 a command separated 'geometry' string in "compose:args" image artifact.
878
879 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
880
881 Applying the SVG transparency formula (see above), we get...
882
883 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
884
885 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
886 Dca*(1.0-Sa)
887 */
888 gamma=geometry_info->rho*Sca*Dca+geometry_info->sigma*Sca*Da+
889 geometry_info->xi*Dca*Sa+geometry_info->psi*Sa*Da+Sca*(1.0-Da)+
890 Dca*(1.0-Sa);
891 return(gamma);
892}
893
cristy2b9582a2011-07-04 17:38:56 +0000894static inline void CompositeMathematics(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000895 const PixelInfo *q,const GeometryInfo *args,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000896{
897 MagickRealType
898 Da,
899 gamma,
900 Sa;
901
902 Sa=QuantumScale*p->alpha; /* ??? - AT */
903 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +0000904 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000905 {
906 /*
907 Handle channels as separate grayscale channels.
908 */
cristyed231572011-07-14 02:18:59 +0000909 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000910 composite->red=QuantumRange*Mathematics(QuantumScale*p->red,1.0,
911 QuantumScale*q->red,1.0,args);
cristyed231572011-07-14 02:18:59 +0000912 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000913 composite->green=QuantumRange*Mathematics(QuantumScale*p->green,1.0,
914 QuantumScale*q->green,1.0,args);
cristyed231572011-07-14 02:18:59 +0000915 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000916 composite->blue=QuantumRange*Mathematics(QuantumScale*p->blue,1.0,
917 QuantumScale*q->blue,1.0,args);
cristyed231572011-07-14 02:18:59 +0000918 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000919 (q->colorspace == CMYKColorspace))
920 composite->black=QuantumRange*Mathematics(QuantumScale*p->black,1.0,
921 QuantumScale*q->black,1.0,args);
cristyed231572011-07-14 02:18:59 +0000922 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000923 composite->alpha=QuantumRange*(1.0-Mathematics(Sa,1.0,Da,1.0,args));
924 return;
925 }
926 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
927 composite->alpha=(MagickRealType) QuantumRange*gamma;
928 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
929 composite->red=gamma*Mathematics(QuantumScale*p->red*Sa,Sa,QuantumScale*
930 q->red*Da,Da,args);
931 composite->green=gamma*Mathematics(QuantumScale*p->green*Sa,Sa,
932 QuantumScale*q->green*Da,Da,args);
933 composite->blue=gamma*Mathematics(QuantumScale*p->blue*Sa,Sa,QuantumScale*
934 q->blue*Da,Da,args);
935 if (q->colorspace == CMYKColorspace)
936 composite->black=gamma*Mathematics(QuantumScale*p->black*Sa,Sa,
937 QuantumScale*q->black*Da,Da,args);
938}
939
cristy2b9582a2011-07-04 17:38:56 +0000940static inline void CompositePlus(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000941 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000942{
943 /*
944 NOTE: "Plus" does not use 'over' alpha-blending but uses a special
945 'plus' form of alph-blending. It is the ONLY mathematical operator to
946 do this. this is what makes it different to the otherwise equivalent
947 "LinearDodge" composition method.
948
949 Note however that color channels are still effected by the alpha channel
950 as a result of the blending, making it just as useless for independant
951 channel maths, just like all other mathematical composition methods.
952
953 As such the removal of the 'sync' flag, is still a usful convention.
954
955 The CompositePixelInfoPlus() function is defined in
956 "composite-private.h" so it can also be used for Image Blending.
957 */
cristy5f95f4f2011-10-23 01:01:01 +0000958 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +0000959 {
960 /*
961 Handle channels as separate grayscale channels.
962 */
cristyed231572011-07-14 02:18:59 +0000963 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000964 composite->red=p->red+q->red;
cristyed231572011-07-14 02:18:59 +0000965 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000966 composite->green=p->green+q->green;
cristyed231572011-07-14 02:18:59 +0000967 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000968 composite->blue=p->blue+q->blue;
cristyed231572011-07-14 02:18:59 +0000969 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000970 (q->colorspace == CMYKColorspace))
971 composite->black=p->black+q->black;
cristyed231572011-07-14 02:18:59 +0000972 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000973 composite->alpha=p->alpha+q->alpha-QuantumRange;
974 return;
975 }
976 CompositePixelInfoPlus(p,p->alpha,q,q->alpha,composite);
977}
978
979static inline MagickRealType Minus(const MagickRealType Sca,
980 const MagickRealType Sa,const MagickRealType Dca,
981 const MagickRealType magick_unused(Da))
982{
983 /*
984 Minus Source from Destination
985
986 f(Sc,Dc) = Sc - Dc
987 */
988 return(Sca+Dca-2.0*Dca*Sa);
989}
990
cristy2b9582a2011-07-04 17:38:56 +0000991static inline void CompositeMinus(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +0000992 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +0000993{
994 MagickRealType
995 Da,
996 gamma,
997 Sa;
998
999 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1000 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +00001001 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +00001002 {
1003 /*
1004 Handle channels as separate grayscale channels.
1005 */
cristyed231572011-07-14 02:18:59 +00001006 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001007 composite->red=p->red-q->red;
cristyed231572011-07-14 02:18:59 +00001008 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001009 composite->green=p->green-q->green;
cristyed231572011-07-14 02:18:59 +00001010 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001011 composite->blue=p->blue-q->blue;
cristyed231572011-07-14 02:18:59 +00001012 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001013 (q->colorspace == CMYKColorspace))
1014 composite->black=p->black-q->black;
cristyed231572011-07-14 02:18:59 +00001015 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001016 composite->alpha=QuantumRange*(1.0-(Sa-Da));
1017 return;
1018 }
1019 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1020 composite->alpha=(MagickRealType) QuantumRange*gamma;
1021 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1022 composite->red=gamma*Minus(p->red*Sa,Sa,q->red*Da,Da);
1023 composite->green=gamma*Minus(p->green*Sa,Sa,q->green*Da,Da);
1024 composite->blue=gamma*Minus(p->blue*Sa,Sa,q->blue*Da,Da);
1025 if (q->colorspace == CMYKColorspace)
1026 composite->black=gamma*Minus(p->black*Sa,Sa,q->black*Da,Da);
1027}
1028
1029static inline MagickRealType ModulusAdd(const MagickRealType p,
1030 const MagickRealType Sa,const MagickRealType q, const MagickRealType Da)
1031{
1032 MagickRealType
1033 pixel;
1034
1035 pixel=p+q;
1036 if (pixel > QuantumRange)
1037 pixel-=(QuantumRange+1.0);
1038 return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa));
1039}
1040
cristy2b9582a2011-07-04 17:38:56 +00001041static inline void CompositeModulusAdd(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001042 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001043{
1044 MagickRealType
1045 Da,
1046 gamma,
1047 Sa;
1048
cristy5f95f4f2011-10-23 01:01:01 +00001049 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +00001050 {
1051 /*
1052 Handle channels as separate grayscale channels.
1053 */
cristyed231572011-07-14 02:18:59 +00001054 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001055 composite->red=ModulusAdd(p->red,1.0,q->red,1.0);
cristyed231572011-07-14 02:18:59 +00001056 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001057 composite->green=ModulusAdd(p->green,1.0,q->green,1.0);
cristyed231572011-07-14 02:18:59 +00001058 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001059 composite->blue=ModulusAdd(p->blue,1.0,q->blue,1.0);
cristyed231572011-07-14 02:18:59 +00001060 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001061 (q->colorspace == CMYKColorspace))
1062 composite->black=ModulusAdd(p->black,1.0,q->black,1.0);
cristyed231572011-07-14 02:18:59 +00001063 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001064 composite->alpha=ModulusAdd(p->alpha,1.0,q->alpha,1.0);
1065 return;
1066 }
1067 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1068 Da=QuantumScale*q->alpha;
1069 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1070 composite->alpha=(MagickRealType) QuantumRange*gamma;
1071 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1072 composite->red=ModulusAdd(p->red,Sa,q->red,Da);
1073 composite->green=ModulusAdd(p->green,Sa,q->green,Da);
1074 composite->blue=ModulusAdd(p->blue,Sa,q->blue,Da);
1075 if (q->colorspace == CMYKColorspace)
1076 composite->black=ModulusAdd(p->black,Sa,q->black,Da);
1077}
1078
1079static inline MagickRealType ModulusSubtract(const MagickRealType p,
1080 const MagickRealType Sa,const MagickRealType q, const MagickRealType Da)
1081{
1082 MagickRealType
1083 pixel;
1084
1085 pixel=p-q;
1086 if (pixel < 0.0)
1087 pixel+=(QuantumRange+1.0);
1088 return(pixel*Sa*Da+p*Sa*(1.0-Da)+q*Da*(1.0-Sa));
1089}
1090
cristy2b9582a2011-07-04 17:38:56 +00001091static inline void CompositeModulusSubtract(const Image *image,
cristyf4ad9df2011-07-08 16:49:03 +00001092 const PixelInfo *p,const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001093{
1094 MagickRealType
1095 Da,
1096 gamma,
1097 Sa;
1098
cristy5f95f4f2011-10-23 01:01:01 +00001099 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +00001100 {
1101 /*
1102 Handle channels as separate grayscale channels,
1103 */
cristyed231572011-07-14 02:18:59 +00001104 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001105 composite->red=ModulusSubtract(p->red,1.0,q->red,1.0);
cristyed231572011-07-14 02:18:59 +00001106 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001107 composite->green=ModulusSubtract(p->green,1.0,q->green,1.0);
cristyed231572011-07-14 02:18:59 +00001108 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001109 composite->blue=ModulusSubtract(p->blue,1.0,q->blue,1.0);
cristyed231572011-07-14 02:18:59 +00001110 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001111 (q->colorspace == CMYKColorspace))
1112 composite->black=ModulusSubtract(p->black,1.0,q->black,1.0);
cristyed231572011-07-14 02:18:59 +00001113 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001114 composite->alpha=ModulusSubtract(p->alpha,1.0,q->alpha,1.0);
1115 return;
1116 }
1117 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1118 Da=QuantumScale*q->alpha;
1119 gamma = RoundToUnity(Sa+Da-Sa*Da);
1120 composite->alpha=(MagickRealType) QuantumRange*gamma;
1121 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1122 composite->red=ModulusSubtract(p->red,Sa,q->red,Da);
1123 composite->green=ModulusSubtract(p->green,Sa,q->green,Da);
1124 composite->blue=ModulusSubtract(p->blue,Sa,q->blue,Da);
1125 if (q->colorspace == CMYKColorspace)
1126 composite->black=ModulusSubtract(p->black,Sa,q->black,Da);
1127}
1128
1129static inline MagickRealType Multiply(const MagickRealType Sca,
1130 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1131{
1132 return(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
1133}
1134
cristy2b9582a2011-07-04 17:38:56 +00001135static inline void CompositeMultiply(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001136 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001137{
1138 MagickRealType
1139 Da,
1140 gamma,
1141 Sa;
1142
1143 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1144 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +00001145 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +00001146 {
1147 /*
1148 Handle channels as separate grayscale channels.
1149 */
cristyed231572011-07-14 02:18:59 +00001150 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001151 composite->red=QuantumScale*p->red*q->red;
cristyed231572011-07-14 02:18:59 +00001152 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001153 composite->green=QuantumScale*p->green*q->green;
cristyed231572011-07-14 02:18:59 +00001154 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001155 composite->blue=QuantumScale*p->blue*q->blue;
cristyed231572011-07-14 02:18:59 +00001156 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001157 (q->colorspace == CMYKColorspace))
1158 composite->black=QuantumScale*p->black*q->black;
cristyed231572011-07-14 02:18:59 +00001159 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001160 composite->alpha=QuantumRange*(1.0-Sa*Da);
1161 return;
1162 }
1163 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1164 composite->alpha=(MagickRealType) QuantumRange*gamma;
1165 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1166 composite->red=gamma*Multiply(QuantumScale*p->red*Sa,Sa,QuantumScale*
1167 q->red*Da,Da);
1168 composite->green=gamma*Multiply(QuantumScale*p->green*Sa,Sa,QuantumScale*
1169 q->green*Da,Da);
1170 composite->blue=gamma*Multiply(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1171 q->blue*Da,Da);
1172 if (q->colorspace == CMYKColorspace)
1173 composite->black=gamma*Multiply(QuantumScale*p->black*Sa,Sa,
1174 QuantumScale*q->black*Da,Da);
1175}
1176
anthony88149d72011-10-23 07:29:23 +00001177#if 0
cristy4c08aed2011-07-01 19:47:50 +00001178static inline MagickRealType Out(const MagickRealType p,const MagickRealType Sa,
1179 const MagickRealType magick_unused(q),const MagickRealType Da)
1180{
1181 return(Sa*p*(1.0-Da));
1182}
anthony88149d72011-10-23 07:29:23 +00001183#endif
cristy4c08aed2011-07-01 19:47:50 +00001184
1185static inline void CompositeOut(const PixelInfo *p,const PixelInfo *q,
1186 PixelInfo *composite)
1187{
anthony88149d72011-10-23 07:29:23 +00001188#if 0
cristy4c08aed2011-07-01 19:47:50 +00001189 MagickRealType
1190 Sa,
1191 Da,
1192 gamma;
1193
1194 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1195 Da=QuantumScale*q->alpha;
1196 gamma=Sa*(1.0-Da);
1197 composite->alpha=(MagickRealType) QuantumRange*gamma;
1198 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1199 composite->red=gamma*Out(p->red,Sa,q->red,Da);
1200 composite->green=gamma*Out(p->green,Sa,q->green,Da);
1201 composite->blue=gamma*Out(p->blue,Sa,q->blue,Da);
1202 if (q->colorspace == CMYKColorspace)
1203 composite->black=gamma*Out(p->black,Sa,q->black,Da);
anthony88149d72011-10-23 07:29:23 +00001204#else
1205 /* Simplified to a negated multiply of the Alpha Channel */
anthonyfe381212011-10-23 07:41:00 +00001206 *composite=*p; /* structure copy */
anthony88149d72011-10-23 07:29:23 +00001207 composite->alpha=p->alpha*(1.0-QuantumScale*q->alpha);
1208#endif
cristy4c08aed2011-07-01 19:47:50 +00001209}
1210
1211static MagickRealType PegtopLight(const MagickRealType Sca,
1212 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1213{
1214 /*
1215 PegTop: A Soft-Light alternative: A continuous version of the Softlight
1216 function, producing very similar results.
1217
1218 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
1219
1220 See http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
1221 */
1222 if (fabs(Da) < MagickEpsilon)
1223 return(Sca);
1224 return(Dca*Dca*(Sa-2.0*Sca)/Da+Sca*(2.0*Dca+1.0-Da)+Dca*(1.0-Sa));
1225}
1226
1227static inline void CompositePegtopLight(const PixelInfo *p,const PixelInfo *q,
1228 PixelInfo *composite)
1229{
1230 MagickRealType
1231 Da,
1232 gamma,
1233 Sa;
1234
1235 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1236 Da=QuantumScale*q->alpha;
1237 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1238 composite->alpha=(MagickRealType) QuantumRange*gamma;
1239 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1240 composite->red=gamma*PegtopLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1241 q->red*Da,Da);
1242 composite->green=gamma*PegtopLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1243 q->green*Da,Da);
1244 composite->blue=gamma*PegtopLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1245 q->blue*Da,Da);
1246 if (q->colorspace == CMYKColorspace)
1247 composite->black=gamma*PegtopLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1248 q->black*Da,Da);
1249}
1250
1251static MagickRealType PinLight(const MagickRealType Sca,const MagickRealType Sa,
1252 const MagickRealType Dca,const MagickRealType Da)
1253{
1254 /*
1255 PinLight: A Photoshop 7 composition method
1256 http://www.simplefilter.de/en/basics/mixmods.html
1257
1258 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
1259 */
1260 if (Dca*Sa < Da*(2.0*Sca-Sa))
1261 return(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
1262 if ((Dca*Sa) > (2.0*Sca*Da))
1263 return(Sca*Da+Sca+Dca*(1.0-Sa));
1264 return(Sca*(1.0-Da)+Dca);
1265}
1266
1267static inline void CompositePinLight(const PixelInfo *p,const PixelInfo *q,
1268 PixelInfo *composite)
1269{
1270 MagickRealType
1271 Da,
1272 gamma,
1273 Sa;
1274
1275 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1276 Da=QuantumScale*q->alpha;
1277 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1278 composite->alpha=(MagickRealType) QuantumRange*gamma;
1279 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1280 composite->red=gamma*PinLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1281 q->red*Da,Da);
1282 composite->green=gamma*PinLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1283 q->green*Da,Da);
1284 composite->blue=gamma*PinLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1285 q->blue*Da,Da);
1286 if (q->colorspace == CMYKColorspace)
1287 composite->black=gamma*PinLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1288 q->black*Da,Da);
1289}
1290
1291static inline MagickRealType Screen(const MagickRealType Sca,
1292 const MagickRealType Dca)
1293{
1294 /*
1295 Screen: A negated multiply
1296 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
1297 */
1298 return(Sca+Dca-Sca*Dca);
1299}
1300
cristy2b9582a2011-07-04 17:38:56 +00001301static inline void CompositeScreen(const Image *image,const PixelInfo *p,
cristyf4ad9df2011-07-08 16:49:03 +00001302 const PixelInfo *q,PixelInfo *composite)
cristy4c08aed2011-07-01 19:47:50 +00001303{
1304 MagickRealType
1305 Da,
1306 gamma,
1307 Sa;
1308
1309 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1310 Da=QuantumScale*q->alpha;
cristy5f95f4f2011-10-23 01:01:01 +00001311 if (image->channel_mask != DefaultChannels)
cristy4c08aed2011-07-01 19:47:50 +00001312 {
1313 /*
1314 Handle channels as separate grayscale channels.
1315 */
cristyed231572011-07-14 02:18:59 +00001316 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001317 composite->red=QuantumRange*Screen(QuantumScale*p->red,
1318 QuantumScale*q->red);
cristyed231572011-07-14 02:18:59 +00001319 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001320 composite->green=QuantumRange*Screen(QuantumScale*p->green,
1321 QuantumScale*q->green);
cristyed231572011-07-14 02:18:59 +00001322 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001323 composite->blue=QuantumRange*Screen(QuantumScale*p->blue,
1324 QuantumScale*q->blue);
cristyed231572011-07-14 02:18:59 +00001325 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001326 (q->colorspace == CMYKColorspace))
1327 composite->black=QuantumRange*Screen(QuantumScale*p->black,
1328 QuantumScale*q->black);
cristyed231572011-07-14 02:18:59 +00001329 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001330 composite->alpha=QuantumRange*(1.0-Screen(Sa,Da));
1331 return;
1332 }
1333 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1334 composite->alpha=(MagickRealType) QuantumRange*gamma;
1335 Sa*=QuantumScale; Da*=QuantumScale; /* optimization */
1336 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1337 composite->red=gamma*Screen(p->red*Sa,q->red*Da);
1338 composite->green=gamma*Screen(p->green*Sa,q->green*Da);
1339 composite->blue=gamma*Screen(p->blue*Sa,q->blue*Da);
1340 if (q->colorspace == CMYKColorspace)
1341 composite->black=gamma*Screen(p->black*Sa,q->black*Da);
1342}
1343
1344static MagickRealType SoftLight(const MagickRealType Sca,
1345 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1346{
1347 MagickRealType
1348 alpha,
1349 beta;
1350
1351 /*
1352 New specification: March 2009 SVG specification.
1353 */
1354 alpha=Dca/Da;
1355 if ((2.0*Sca) < Sa)
1356 return(Dca*(Sa+(2.0*Sca-Sa)*(1.0-alpha))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1357 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
1358 {
1359 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*alpha*(4.0*alpha+1.0)*(alpha-1.0)+7.0*
1360 alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1361 return(beta);
1362 }
1363 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(pow(alpha,0.5)-alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1364 return(beta);
1365}
1366
1367static inline void CompositeSoftLight(const PixelInfo *p,const PixelInfo *q,
1368 PixelInfo *composite)
1369{
1370 MagickRealType
1371 Da,
1372 gamma,
1373 Sa;
1374
1375 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1376 Da=QuantumScale*q->alpha;
1377 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1378 composite->alpha=(MagickRealType) QuantumRange*gamma;
1379 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1380 composite->red=gamma*SoftLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1381 q->red*Da,Da);
1382 composite->green=gamma*SoftLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1383 q->green*Da,Da);
1384 composite->blue=gamma*SoftLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1385 q->blue*Da,Da);
1386 if (q->colorspace == CMYKColorspace)
1387 composite->black=gamma*SoftLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1388 q->black*Da,Da);
1389}
1390
1391static inline MagickRealType Threshold(const MagickRealType p,
1392 const MagickRealType q,const MagickRealType threshold,
1393 const MagickRealType amount)
1394{
1395 MagickRealType
1396 delta;
1397
1398 /*
1399 Multiply difference by amount, if differance larger than threshold???
1400 What use this is is completely unknown. The Opacity calculation appears to
1401 be inverted -- Anthony Thyssen
1402
1403 Deprecated.
1404 */
1405 delta=p-q;
1406 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
1407 return(q);
1408 return(q+delta*amount);
1409}
1410
1411static inline void CompositeThreshold(const PixelInfo *p,const PixelInfo *q,
1412 const MagickRealType threshold,const MagickRealType amount,
1413 PixelInfo *composite)
1414{
1415 composite->red=Threshold(p->red,q->red,threshold,amount);
1416 composite->green=Threshold(p->green,q->green,threshold,amount);
1417 composite->blue=Threshold(p->blue,q->blue,threshold,amount);
1418 composite->alpha=Threshold(p->alpha,q->alpha,threshold,amount);
1419 if (q->colorspace == CMYKColorspace)
1420 composite->black=Threshold(p->black,q->black,threshold,amount);
1421}
1422
1423
1424static MagickRealType VividLight(const MagickRealType Sca,
1425 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1426{
1427 /*
1428 VividLight: A Photoshop 7 composition method. See
1429 http://www.simplefilter.de/en/basics/mixmods.html.
1430
1431 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
1432 */
1433 if ((fabs(Sa) < MagickEpsilon) || (fabs(Sca-Sa) < MagickEpsilon))
1434 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
1435 if ((2.0*Sca) <= Sa)
1436 return(Sa*(Da+Sa*(Dca-Da)/(2.0*Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1437 return(Dca*Sa*Sa/(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1438}
1439
1440static inline void CompositeVividLight(const PixelInfo *p,const PixelInfo *q,
1441 PixelInfo *composite)
1442{
1443 MagickRealType
1444 Da,
1445 gamma,
1446 Sa;
1447
1448 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1449 Da=QuantumScale*q->alpha;
1450 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1451 composite->alpha=(MagickRealType) QuantumRange*gamma;
1452 gamma=QuantumRange/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1453 composite->red=gamma*VividLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1454 q->red*Da,Da);
1455 composite->green=gamma*VividLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1456 q->green*Da,Da);
1457 composite->blue=gamma*VividLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1458 q->blue*Da,Da);
1459 if (q->colorspace == CMYKColorspace)
1460 composite->black=gamma*VividLight(QuantumScale*p->black*Sa,Sa,QuantumScale*
1461 q->black*Da,Da);
1462}
1463
1464static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa,
1465 const MagickRealType Dca,const MagickRealType Da)
1466{
1467 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
1468}
1469
1470static inline void CompositeXor(const PixelInfo *p,const PixelInfo *q,
1471 PixelInfo *composite)
1472{
1473 MagickRealType
1474 Da,
1475 gamma,
1476 Sa;
1477
1478 Sa=QuantumScale*p->alpha; /* simplify and speed up equations */
1479 Da=QuantumScale*q->alpha;
1480 gamma=Sa+Da-2.0*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */
1481 composite->alpha=(MagickRealType) QuantumRange*gamma;
1482 gamma=1.0/(fabs(gamma) <= MagickEpsilon ? 1.0 : gamma);
1483 composite->red=gamma*Xor(p->red*Sa,Sa,q->red*Da,Da);
1484 composite->green=gamma*Xor(p->green*Sa,Sa,q->green*Da,Da);
1485 composite->blue=gamma*Xor(p->blue*Sa,Sa,q->blue*Da,Da);
1486 if (q->colorspace == CMYKColorspace)
1487 composite->black=gamma*Xor(p->black*Sa,Sa,q->black*Da,Da);
1488}
1489
1490static void HSBComposite(const double hue,const double saturation,
1491 const double brightness,MagickRealType *red,MagickRealType *green,
1492 MagickRealType *blue)
1493{
1494 MagickRealType
1495 f,
1496 h,
1497 p,
1498 q,
1499 t;
1500
1501 /*
1502 Convert HSB to RGB colorspace.
1503 */
1504 assert(red != (MagickRealType *) NULL);
1505 assert(green != (MagickRealType *) NULL);
1506 assert(blue != (MagickRealType *) NULL);
1507 if (saturation == 0.0)
1508 {
1509 *red=(MagickRealType) QuantumRange*brightness;
1510 *green=(*red);
1511 *blue=(*red);
1512 return;
1513 }
1514 h=6.0*(hue-floor(hue));
1515 f=h-floor((double) h);
1516 p=brightness*(1.0-saturation);
1517 q=brightness*(1.0-saturation*f);
1518 t=brightness*(1.0-saturation*(1.0-f));
1519 switch ((int) h)
1520 {
1521 case 0:
1522 default:
1523 {
1524 *red=(MagickRealType) QuantumRange*brightness;
1525 *green=(MagickRealType) QuantumRange*t;
1526 *blue=(MagickRealType) QuantumRange*p;
1527 break;
1528 }
1529 case 1:
1530 {
1531 *red=(MagickRealType) QuantumRange*q;
1532 *green=(MagickRealType) QuantumRange*brightness;
1533 *blue=(MagickRealType) QuantumRange*p;
1534 break;
1535 }
1536 case 2:
1537 {
1538 *red=(MagickRealType) QuantumRange*p;
1539 *green=(MagickRealType) QuantumRange*brightness;
1540 *blue=(MagickRealType) QuantumRange*t;
1541 break;
1542 }
1543 case 3:
1544 {
1545 *red=(MagickRealType) QuantumRange*p;
1546 *green=(MagickRealType) QuantumRange*q;
1547 *blue=(MagickRealType) QuantumRange*brightness;
1548 break;
1549 }
1550 case 4:
1551 {
1552 *red=(MagickRealType) QuantumRange*t;
1553 *green=(MagickRealType) QuantumRange*p;
1554 *blue=(MagickRealType) QuantumRange*brightness;
1555 break;
1556 }
1557 case 5:
1558 {
1559 *red=(MagickRealType) QuantumRange*brightness;
1560 *green=(MagickRealType) QuantumRange*p;
1561 *blue=(MagickRealType) QuantumRange*q;
1562 break;
1563 }
1564 }
1565}
1566
1567MagickExport MagickBooleanType CompositeImage(Image *image,
1568 const CompositeOperator compose,const Image *composite_image,
cristye941a752011-10-15 01:52:48 +00001569 const ssize_t x_offset,const ssize_t y_offset,ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00001570{
cristy4c08aed2011-07-01 19:47:50 +00001571#define CompositeImageTag "Composite/Image"
1572
1573 CacheView
1574 *composite_view,
1575 *image_view;
1576
1577 const char
1578 *value;
1579
1580 double
1581 sans;
1582
cristy4c08aed2011-07-01 19:47:50 +00001583 GeometryInfo
1584 geometry_info;
1585
1586 Image
1587 *destination_image;
1588
1589 MagickBooleanType
1590 modify_outside_overlay,
1591 status;
1592
1593 MagickOffsetType
1594 progress;
1595
cristy4c08aed2011-07-01 19:47:50 +00001596 MagickRealType
1597 amount,
1598 destination_dissolve,
1599 midpoint,
1600 percent_brightness,
1601 percent_saturation,
1602 source_dissolve,
1603 threshold;
1604
1605 MagickStatusType
1606 flags;
1607
cristycd01fae2011-08-06 23:52:42 +00001608 PixelInfo
1609 zero;
1610
cristy4c08aed2011-07-01 19:47:50 +00001611 ssize_t
1612 y;
1613
1614 /*
1615 Prepare composite image.
1616 */
1617 assert(image != (Image *) NULL);
1618 assert(image->signature == MagickSignature);
1619 if (image->debug != MagickFalse)
1620 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1621 assert(composite_image != (Image *) NULL);
1622 assert(composite_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001623 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00001624 return(MagickFalse);
1625 GetPixelInfo(image,&zero);
1626 destination_image=(Image *) NULL;
1627 amount=0.5;
1628 destination_dissolve=1.0;
1629 modify_outside_overlay=MagickFalse;
1630 percent_brightness=100.0;
1631 percent_saturation=100.0;
1632 source_dissolve=1.0;
1633 threshold=0.05f;
1634 switch (compose)
1635 {
1636 case ClearCompositeOp:
1637 case SrcCompositeOp:
1638 case InCompositeOp:
1639 case SrcInCompositeOp:
1640 case OutCompositeOp:
1641 case SrcOutCompositeOp:
1642 case DstInCompositeOp:
1643 case DstAtopCompositeOp:
1644 {
1645 /*
1646 Modify destination outside the overlaid region.
1647 */
1648 modify_outside_overlay=MagickTrue;
1649 break;
1650 }
1651 case CopyCompositeOp:
1652 {
1653 if ((x_offset < 0) || (y_offset < 0))
1654 break;
1655 if ((x_offset+(ssize_t) composite_image->columns) >= (ssize_t) image->columns)
1656 break;
1657 if ((y_offset+(ssize_t) composite_image->rows) >= (ssize_t) image->rows)
1658 break;
1659 status=MagickTrue;
cristy4c08aed2011-07-01 19:47:50 +00001660 image_view=AcquireCacheView(image);
1661 composite_view=AcquireCacheView(composite_image);
1662#if defined(MAGICKCORE_OPENMP_SUPPORT)
1663#pragma omp parallel for schedule(dynamic,4) shared(status)
1664#endif
1665 for (y=0; y < (ssize_t) composite_image->rows; y++)
1666 {
1667 MagickBooleanType
1668 sync;
1669
1670 register const Quantum
1671 *p;
1672
1673 register Quantum
1674 *q;
1675
1676 register ssize_t
1677 x;
1678
1679 if (status == MagickFalse)
1680 continue;
1681 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1682 1,exception);
1683 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
1684 composite_image->columns,1,exception);
1685 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1686 {
1687 status=MagickFalse;
1688 continue;
1689 }
1690 for (x=0; x < (ssize_t) composite_image->columns; x++)
1691 {
1692 SetPixelRed(image,GetPixelRed(composite_image,p),q);
1693 SetPixelGreen(image,GetPixelGreen(composite_image,p),q);
1694 SetPixelBlue(image,GetPixelBlue(composite_image,p),q);
1695 SetPixelAlpha(image,GetPixelAlpha(composite_image,p),q);
1696 if (image->colorspace == CMYKColorspace)
1697 SetPixelBlack(image,GetPixelBlack(composite_image,p),q);
cristyed231572011-07-14 02:18:59 +00001698 p+=GetPixelChannels(composite_image);
1699 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00001700 }
1701 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1702 if (sync == MagickFalse)
1703 status=MagickFalse;
1704 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1705 {
1706 MagickBooleanType
1707 proceed;
1708
1709#if defined(MAGICKCORE_OPENMP_SUPPORT)
1710#pragma omp critical (MagickCore_CompositeImage)
1711#endif
1712 proceed=SetImageProgress(image,CompositeImageTag,
1713 (MagickOffsetType) y,image->rows);
1714 if (proceed == MagickFalse)
1715 status=MagickFalse;
1716 }
1717 }
1718 composite_view=DestroyCacheView(composite_view);
1719 image_view=DestroyCacheView(image_view);
1720 return(status);
1721 }
1722 case CopyOpacityCompositeOp:
1723 case ChangeMaskCompositeOp:
1724 {
1725 /*
1726 Modify destination outside the overlaid region and require an alpha
1727 channel to exist, to add transparency.
1728 */
1729 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00001730 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy4c08aed2011-07-01 19:47:50 +00001731 modify_outside_overlay=MagickTrue;
1732 break;
1733 }
1734 case BlurCompositeOp:
1735 {
1736 CacheView
1737 *composite_view,
1738 *destination_view;
1739
1740 PixelInfo
1741 pixel;
1742
1743 MagickRealType
1744 angle_range,
1745 angle_start,
1746 height,
1747 width;
1748
1749 ResampleFilter
1750 *resample_filter;
1751
1752 SegmentInfo
1753 blur;
1754
1755 /*
1756 Blur Image dictated by an overlay gradient map: X = red_channel;
1757 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
1758 */
1759 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +00001760 exception);
cristy4c08aed2011-07-01 19:47:50 +00001761 if (destination_image == (Image *) NULL)
1762 return(MagickFalse);
1763 /*
1764 Determine the horizontal and vertical maximim blur.
1765 */
1766 SetGeometryInfo(&geometry_info);
1767 flags=NoValue;
1768 value=GetImageArtifact(composite_image,"compose:args");
1769 if (value != (char *) NULL)
1770 flags=ParseGeometry(value,&geometry_info);
1771 if ((flags & WidthValue) == 0 )
1772 {
1773 destination_image=DestroyImage(destination_image);
1774 return(MagickFalse);
1775 }
1776 width=geometry_info.rho;
1777 height=geometry_info.sigma;
1778 blur.x1=geometry_info.rho;
1779 blur.x2=0.0;
1780 blur.y1=0.0;
1781 blur.y2=geometry_info.sigma;
1782 angle_start=0.0;
1783 angle_range=0.0;
1784 if ((flags & HeightValue) == 0)
1785 blur.y2=blur.x1;
1786 if ((flags & XValue) != 0 )
1787 {
1788 MagickRealType
1789 angle;
1790
1791 angle=DegreesToRadians(geometry_info.xi);
1792 blur.x1=width*cos(angle);
1793 blur.x2=width*sin(angle);
1794 blur.y1=(-height*sin(angle));
1795 blur.y2=height*cos(angle);
1796 }
1797 if ((flags & YValue) != 0 )
1798 {
1799 angle_start=DegreesToRadians(geometry_info.xi);
1800 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
1801 }
1802 /*
1803 Blur Image by resampling.
1804 */
1805 pixel=zero;
cristy8a11cb12011-10-19 23:53:34 +00001806 resample_filter=AcquireResampleFilter(image,exception);
cristy4c08aed2011-07-01 19:47:50 +00001807 SetResampleFilter(resample_filter,CubicFilter,2.0);
1808 destination_view=AcquireCacheView(destination_image);
1809 composite_view=AcquireCacheView(composite_image);
1810 for (y=0; y < (ssize_t) composite_image->rows; y++)
1811 {
1812 MagickBooleanType
1813 sync;
1814
1815 register const Quantum
1816 *restrict p;
1817
1818 register Quantum
1819 *restrict q;
1820
1821 register ssize_t
1822 x;
1823
1824 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1825 continue;
1826 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1827 1,exception);
1828 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +00001829 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001830 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1831 break;
1832 for (x=0; x < (ssize_t) composite_image->columns; x++)
1833 {
1834 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1835 {
cristyed231572011-07-14 02:18:59 +00001836 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00001837 continue;
1838 }
1839 if (fabs(angle_range) > MagickEpsilon)
1840 {
1841 MagickRealType
1842 angle;
1843
1844 angle=angle_start+angle_range*QuantumScale*
1845 GetPixelBlue(composite_image,p);
1846 blur.x1=width*cos(angle);
1847 blur.x2=width*sin(angle);
1848 blur.y1=(-height*sin(angle));
1849 blur.y2=height*cos(angle);
1850 }
1851 ScaleResampleFilter(resample_filter,blur.x1*QuantumScale*
1852 GetPixelRed(composite_image,p),blur.y1*QuantumScale*
1853 GetPixelGreen(composite_image,p),blur.x2*QuantumScale*
1854 GetPixelRed(composite_image,p),blur.y2*QuantumScale*
1855 GetPixelGreen(composite_image,p));
1856 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,
1857 (double) y_offset+y,&pixel);
1858 SetPixelPixelInfo(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00001859 p+=GetPixelChannels(composite_image);
1860 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00001861 }
1862 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
1863 if (sync == MagickFalse)
1864 break;
1865 }
1866 resample_filter=DestroyResampleFilter(resample_filter);
1867 composite_view=DestroyCacheView(composite_view);
1868 destination_view=DestroyCacheView(destination_view);
1869 composite_image=destination_image;
1870 break;
1871 }
1872 case DisplaceCompositeOp:
1873 case DistortCompositeOp:
1874 {
1875 CacheView
1876 *composite_view,
1877 *destination_view,
1878 *image_view;
1879
1880 PixelInfo
1881 pixel;
1882
1883 MagickRealType
1884 horizontal_scale,
1885 vertical_scale;
1886
1887 PointInfo
1888 center,
1889 offset;
1890
1891 /*
1892 Displace/Distort based on overlay gradient map:
1893 X = red_channel; Y = green_channel;
1894 compose:args = x_scale[,y_scale[,center.x,center.y]]
1895 */
1896 destination_image=CloneImage(image,image->columns,image->rows,MagickTrue,
cristy8a11cb12011-10-19 23:53:34 +00001897 exception);
cristy4c08aed2011-07-01 19:47:50 +00001898 if (destination_image == (Image *) NULL)
1899 return(MagickFalse);
1900 SetGeometryInfo(&geometry_info);
1901 flags=NoValue;
1902 value=GetImageArtifact(composite_image,"compose:args");
1903 if (value != (char *) NULL)
1904 flags=ParseGeometry(value,&geometry_info);
1905 if ((flags & (WidthValue|HeightValue)) == 0 )
1906 {
1907 if ((flags & AspectValue) == 0)
1908 {
1909 horizontal_scale=(MagickRealType) (composite_image->columns-1.0)/
1910 2.0;
1911 vertical_scale=(MagickRealType) (composite_image->rows-1.0)/2.0;
1912 }
1913 else
1914 {
1915 horizontal_scale=(MagickRealType) (image->columns-1.0)/2.0;
1916 vertical_scale=(MagickRealType) (image->rows-1.0)/2.0;
1917 }
1918 }
1919 else
1920 {
1921 horizontal_scale=geometry_info.rho;
1922 vertical_scale=geometry_info.sigma;
1923 if ((flags & PercentValue) != 0)
1924 {
1925 if ((flags & AspectValue) == 0)
1926 {
1927 horizontal_scale*=(composite_image->columns-1.0)/200.0;
1928 vertical_scale*=(composite_image->rows-1.0)/200.0;
1929 }
1930 else
1931 {
1932 horizontal_scale*=(image->columns-1.0)/200.0;
1933 vertical_scale*=(image->rows-1.0)/200.0;
1934 }
1935 }
1936 if ((flags & HeightValue) == 0)
1937 vertical_scale=horizontal_scale;
1938 }
1939 /*
1940 Determine fixed center point for absolute distortion map
1941 Absolute distort ==
1942 Displace offset relative to a fixed absolute point
1943 Select that point according to +X+Y user inputs.
1944 default = center of overlay image
1945 arg flag '!' = locations/percentage relative to background image
1946 */
1947 center.x=(MagickRealType) x_offset;
1948 center.y=(MagickRealType) y_offset;
1949 if (compose == DistortCompositeOp)
1950 {
1951 if ((flags & XValue) == 0)
1952 if ((flags & AspectValue) == 0)
1953 center.x=(MagickRealType) x_offset+(composite_image->columns-1)/
1954 2.0;
1955 else
1956 center.x=((MagickRealType) image->columns-1)/2.0;
1957 else
1958 if ((flags & AspectValue) == 0)
1959 center.x=(MagickRealType) x_offset+geometry_info.xi;
1960 else
1961 center.x=geometry_info.xi;
1962 if ((flags & YValue) == 0)
1963 if ((flags & AspectValue) == 0)
1964 center.y=(MagickRealType) y_offset+(composite_image->rows-1)/2.0;
1965 else
1966 center.y=((MagickRealType) image->rows-1)/2.0;
1967 else
1968 if ((flags & AspectValue) == 0)
1969 center.y=(MagickRealType) y_offset+geometry_info.psi;
1970 else
1971 center.y=geometry_info.psi;
1972 }
1973 /*
1974 Shift the pixel offset point as defined by the provided,
1975 displacement/distortion map. -- Like a lens...
1976 */
1977 pixel=zero;
cristy4c08aed2011-07-01 19:47:50 +00001978 image_view=AcquireCacheView(image);
1979 destination_view=AcquireCacheView(destination_image);
1980 composite_view=AcquireCacheView(composite_image);
1981 for (y=0; y < (ssize_t) composite_image->rows; y++)
1982 {
1983 MagickBooleanType
1984 sync;
1985
1986 register const Quantum
1987 *restrict p;
1988
1989 register Quantum
1990 *restrict q;
1991
1992 register ssize_t
1993 x;
1994
1995 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1996 continue;
1997 p=GetCacheViewVirtualPixels(composite_view,0,y,composite_image->columns,
1998 1,exception);
1999 q=QueueCacheViewAuthenticPixels(destination_view,0,y,
cristy8a11cb12011-10-19 23:53:34 +00002000 destination_image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002001 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2002 break;
2003 for (x=0; x < (ssize_t) composite_image->columns; x++)
2004 {
2005 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
2006 {
cristyed231572011-07-14 02:18:59 +00002007 p+=GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00002008 continue;
2009 }
2010 /*
2011 Displace the offset.
2012 */
2013 offset.x=(horizontal_scale*(GetPixelRed(composite_image,p)-
2014 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2015 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
2016 x : 0);
2017 offset.y=(vertical_scale*(GetPixelGreen(composite_image,p)-
2018 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2019 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
2020 y : 0);
2021 (void) InterpolatePixelInfo(image,image_view,
2022 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
2023 &pixel,exception);
2024 /*
2025 Mask with the 'invalid pixel mask' in alpha channel.
2026 */
2027 pixel.alpha=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale*
2028 pixel.alpha)*(1.0-QuantumScale*
2029 GetPixelAlpha(composite_image,p)));
2030 SetPixelPixelInfo(destination_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00002031 p+=GetPixelChannels(composite_image);
2032 q+=GetPixelChannels(destination_image);
cristy4c08aed2011-07-01 19:47:50 +00002033 }
2034 sync=SyncCacheViewAuthenticPixels(destination_view,exception);
2035 if (sync == MagickFalse)
2036 break;
2037 }
2038 destination_view=DestroyCacheView(destination_view);
2039 composite_view=DestroyCacheView(composite_view);
2040 image_view=DestroyCacheView(image_view);
2041 composite_image=destination_image;
2042 break;
2043 }
2044 case DissolveCompositeOp:
2045 {
2046 /*
2047 Geometry arguments to dissolve factors.
2048 */
2049 value=GetImageArtifact(composite_image,"compose:args");
2050 if (value != (char *) NULL)
2051 {
2052 flags=ParseGeometry(value,&geometry_info);
2053 source_dissolve=geometry_info.rho/100.0;
2054 destination_dissolve=1.0;
2055 if ((source_dissolve-MagickEpsilon) < 0.0)
2056 source_dissolve=0.0;
2057 if ((source_dissolve+MagickEpsilon) > 1.0)
2058 {
2059 destination_dissolve=2.0-source_dissolve;
2060 source_dissolve=1.0;
2061 }
2062 if ((flags & SigmaValue) != 0)
2063 destination_dissolve=geometry_info.sigma/100.0;
2064 if ((destination_dissolve-MagickEpsilon) < 0.0)
2065 destination_dissolve=0.0;
2066 modify_outside_overlay=MagickTrue;
2067 if ((destination_dissolve+MagickEpsilon) > 1.0 )
2068 {
2069 destination_dissolve=1.0;
2070 modify_outside_overlay=MagickFalse;
2071 }
2072 }
2073 break;
2074 }
2075 case BlendCompositeOp:
2076 {
2077 value=GetImageArtifact(composite_image,"compose:args");
2078 if (value != (char *) NULL)
2079 {
2080 flags=ParseGeometry(value,&geometry_info);
2081 source_dissolve=geometry_info.rho/100.0;
2082 destination_dissolve=1.0-source_dissolve;
2083 if ((flags & SigmaValue) != 0)
2084 destination_dissolve=geometry_info.sigma/100.0;
2085 modify_outside_overlay=MagickTrue;
2086 if ((destination_dissolve+MagickEpsilon) > 1.0)
2087 modify_outside_overlay=MagickFalse;
2088 }
2089 break;
2090 }
2091 case MathematicsCompositeOp:
2092 {
2093 /*
2094 Just collect the values from "compose:args", setting.
2095 Unused values are set to zero automagically.
2096
2097 Arguments are normally a comma separated list, so this probably should
2098 be changed to some 'general comma list' parser, (with a minimum
2099 number of values)
2100 */
2101 SetGeometryInfo(&geometry_info);
2102 value=GetImageArtifact(composite_image,"compose:args");
2103 if (value != (char *) NULL)
2104 (void) ParseGeometry(value,&geometry_info);
2105 break;
2106 }
2107 case ModulateCompositeOp:
2108 {
2109 /*
2110 Determine the brightness and saturation scale.
2111 */
2112 value=GetImageArtifact(composite_image,"compose:args");
2113 if (value != (char *) NULL)
2114 {
2115 flags=ParseGeometry(value,&geometry_info);
2116 percent_brightness=geometry_info.rho;
2117 if ((flags & SigmaValue) != 0)
2118 percent_saturation=geometry_info.sigma;
2119 }
2120 break;
2121 }
2122 case ThresholdCompositeOp:
2123 {
2124 /*
2125 Determine the amount and threshold.
2126 */
2127 value=GetImageArtifact(composite_image,"compose:args");
2128 if (value != (char *) NULL)
2129 {
2130 flags=ParseGeometry(value,&geometry_info);
2131 amount=geometry_info.rho;
2132 threshold=geometry_info.sigma;
2133 if ((flags & SigmaValue) == 0)
2134 threshold=0.05f;
2135 }
2136 threshold*=QuantumRange;
2137 break;
2138 }
2139 default:
2140 break;
2141 }
2142 value=GetImageArtifact(composite_image,"compose:outside-overlay");
2143 if (value != (const char *) NULL)
2144 modify_outside_overlay=IsMagickTrue(value);
2145 /*
2146 Composite image.
2147 */
2148 status=MagickTrue;
2149 progress=0;
2150 midpoint=((MagickRealType) QuantumRange+1.0)/2;
2151 GetPixelInfo(composite_image,&zero);
cristy4c08aed2011-07-01 19:47:50 +00002152 image_view=AcquireCacheView(image);
2153 composite_view=AcquireCacheView(composite_image);
2154#if defined(MAGICKCORE_OPENMP_SUPPORT)
2155 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2156#endif
2157 for (y=0; y < (ssize_t) image->rows; y++)
2158 {
2159 const Quantum
2160 *pixels;
2161
2162 double
2163 brightness,
2164 hue,
2165 saturation;
2166
2167 PixelInfo
2168 composite,
2169 destination,
2170 source;
2171
2172 register const Quantum
2173 *restrict p;
2174
2175 register Quantum
2176 *restrict q;
2177
2178 register ssize_t
2179 x;
2180
2181 if (status == MagickFalse)
2182 continue;
2183 if (modify_outside_overlay == MagickFalse)
2184 {
2185 if (y < y_offset)
2186 continue;
2187 if ((y-y_offset) >= (ssize_t) composite_image->rows)
2188 continue;
2189 }
2190 /*
2191 If pixels is NULL, y is outside overlay region.
2192 */
2193 pixels=(Quantum *) NULL;
2194 p=(Quantum *) NULL;
2195 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) composite_image->rows))
2196 {
2197 p=GetCacheViewVirtualPixels(composite_view,0,y-y_offset,
2198 composite_image->columns,1,exception);
2199 if (p == (const Quantum *) NULL)
2200 {
2201 status=MagickFalse;
2202 continue;
2203 }
2204 pixels=p;
2205 if (x_offset < 0)
cristyed231572011-07-14 02:18:59 +00002206 p-=x_offset*GetPixelChannels(composite_image);
cristy4c08aed2011-07-01 19:47:50 +00002207 }
2208 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002209 if (q == (Quantum *) NULL)
cristy4c08aed2011-07-01 19:47:50 +00002210 {
2211 status=MagickFalse;
2212 continue;
2213 }
2214 source=zero;
2215 destination=zero;
2216 hue=0.0;
2217 saturation=0.0;
2218 brightness=0.0;
2219 for (x=0; x < (ssize_t) image->columns; x++)
2220 {
2221 if (modify_outside_overlay == MagickFalse)
2222 {
2223 if (x < x_offset)
2224 {
cristyed231572011-07-14 02:18:59 +00002225 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002226 continue;
2227 }
2228 if ((x-x_offset) >= (ssize_t) composite_image->columns)
2229 break;
2230 }
2231 destination.red=(MagickRealType) GetPixelRed(image,q);
2232 destination.green=(MagickRealType) GetPixelGreen(image,q);
2233 destination.blue=(MagickRealType) GetPixelBlue(image,q);
2234 if (image->colorspace == CMYKColorspace)
2235 destination.black=(MagickRealType) GetPixelBlack(image,q);
2236 if (image->colorspace == CMYKColorspace)
2237 {
2238 destination.red=(MagickRealType) QuantumRange-destination.red;
2239 destination.green=(MagickRealType) QuantumRange-destination.green;
2240 destination.blue=(MagickRealType) QuantumRange-destination.blue;
2241 destination.black=(MagickRealType) QuantumRange-destination.black;
2242 }
2243 if (image->matte != MagickFalse)
2244 destination.alpha=(MagickRealType) GetPixelAlpha(image,q);
2245 /*
2246 Handle destination modifications outside overlaid region.
2247 */
2248 composite=destination;
2249 if ((pixels == (Quantum *) NULL) || (x < x_offset) ||
2250 ((x-x_offset) >= (ssize_t) composite_image->columns))
2251 {
2252 switch (compose)
2253 {
2254 case DissolveCompositeOp:
2255 case BlendCompositeOp:
2256 {
2257 composite.alpha=destination_dissolve*(composite.alpha);
2258 break;
2259 }
2260 case ClearCompositeOp:
2261 case SrcCompositeOp:
2262 {
2263 CompositeClear(&destination,&composite);
2264 break;
2265 }
2266 case InCompositeOp:
2267 case SrcInCompositeOp:
2268 case OutCompositeOp:
2269 case SrcOutCompositeOp:
2270 case DstInCompositeOp:
2271 case DstAtopCompositeOp:
2272 case CopyOpacityCompositeOp:
2273 case ChangeMaskCompositeOp:
2274 {
2275 composite.alpha=(MagickRealType) TransparentAlpha;
2276 break;
2277 }
2278 default:
2279 {
cristy52010022011-10-21 18:07:37 +00002280 (void) GetOneVirtualMagickPixel(composite_image,
2281 GetPixelCacheVirtualMethod(composite_image),x-x_offset,y-
cristy4c08aed2011-07-01 19:47:50 +00002282 y_offset,&composite,exception);
2283 break;
2284 }
2285 }
2286 if (image->colorspace == CMYKColorspace)
2287 {
2288 composite.red=(MagickRealType) QuantumRange-composite.red;
2289 composite.green=(MagickRealType) QuantumRange-composite.green;
2290 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2291 composite.black=(MagickRealType) QuantumRange-composite.black;
2292 }
2293 SetPixelRed(image,ClampToQuantum(composite.red),q);
2294 SetPixelGreen(image,ClampToQuantum(composite.green),q);
2295 SetPixelBlue(image,ClampToQuantum(composite.blue),q);
2296 if (image->matte != MagickFalse)
2297 SetPixelAlpha(image,ClampToQuantum(composite.alpha),q);
2298 if (image->colorspace == CMYKColorspace)
2299 SetPixelBlack(image,ClampToQuantum(composite.black),q);
cristyed231572011-07-14 02:18:59 +00002300 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002301 continue;
2302 }
2303 /*
2304 Handle normal overlay of source onto destination.
2305 */
2306 source.red=(MagickRealType) GetPixelRed(composite_image,p);
2307 source.green=(MagickRealType) GetPixelGreen(composite_image,p);
2308 source.blue=(MagickRealType) GetPixelBlue(composite_image,p);
2309 if (composite_image->colorspace == CMYKColorspace)
2310 source.black=(MagickRealType) GetPixelBlack(composite_image,p);
2311 if (composite_image->colorspace == CMYKColorspace)
2312 {
2313 source.red=(MagickRealType) QuantumRange-source.red;
2314 source.green=(MagickRealType) QuantumRange-source.green;
2315 source.blue=(MagickRealType) QuantumRange-source.blue;
2316 source.black=(MagickRealType) QuantumRange-source.black;
2317 }
2318 if (composite_image->matte != MagickFalse)
2319 source.alpha=(MagickRealType) GetPixelAlpha(composite_image,p);
2320 /*
2321 Porter-Duff compositions.
2322 */
2323 switch (compose)
2324 {
2325 case ClearCompositeOp:
2326 {
2327 CompositeClear(&destination,&composite);
2328 break;
2329 }
2330 case SrcCompositeOp:
2331 case CopyCompositeOp:
2332 case ReplaceCompositeOp:
2333 {
2334 composite=source;
2335 break;
2336 }
2337 case NoCompositeOp:
2338 case DstCompositeOp:
2339 break;
2340 case OverCompositeOp:
2341 case SrcOverCompositeOp:
2342 {
2343 CompositePixelInfoOver(&source,source.alpha,&destination,
2344 destination.alpha,&composite);
2345 break;
2346 }
2347 case DstOverCompositeOp:
2348 {
2349 CompositePixelInfoOver(&destination,destination.alpha,&source,
2350 source.alpha,&composite);
2351 break;
2352 }
2353 case SrcInCompositeOp:
2354 case InCompositeOp:
2355 {
2356 CompositeIn(&source,&destination,&composite);
2357 break;
2358 }
2359 case DstInCompositeOp:
2360 {
2361 CompositeIn(&destination,&source,&composite);
2362 break;
2363 }
2364 case OutCompositeOp:
2365 case SrcOutCompositeOp:
2366 {
2367 CompositeOut(&source,&destination,&composite);
2368 break;
2369 }
2370 case DstOutCompositeOp:
2371 {
2372 CompositeOut(&destination,&source,&composite);
2373 break;
2374 }
2375 case AtopCompositeOp:
2376 case SrcAtopCompositeOp:
2377 {
2378 CompositeAtop(&source,&destination,&composite);
2379 break;
2380 }
2381 case DstAtopCompositeOp:
2382 {
2383 CompositeAtop(&destination,&source,&composite);
2384 break;
2385 }
2386 case XorCompositeOp:
2387 {
2388 CompositeXor(&source,&destination,&composite);
2389 break;
2390 }
2391 case PlusCompositeOp:
2392 {
cristyf4ad9df2011-07-08 16:49:03 +00002393 CompositePlus(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002394 break;
2395 }
2396 case MinusDstCompositeOp:
2397 {
cristyf4ad9df2011-07-08 16:49:03 +00002398 CompositeMinus(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002399 break;
2400 }
2401 case MinusSrcCompositeOp:
2402 {
cristyf4ad9df2011-07-08 16:49:03 +00002403 CompositeMinus(image,&destination,&source,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002404 break;
2405 }
2406 case ModulusAddCompositeOp:
2407 {
cristyf4ad9df2011-07-08 16:49:03 +00002408 CompositeModulusAdd(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002409 break;
2410 }
2411 case ModulusSubtractCompositeOp:
2412 {
cristyf4ad9df2011-07-08 16:49:03 +00002413 CompositeModulusSubtract(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002414 break;
2415 }
2416 case DifferenceCompositeOp:
2417 {
cristyf4ad9df2011-07-08 16:49:03 +00002418 CompositeDifference(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002419 break;
2420 }
2421 case ExclusionCompositeOp:
2422 {
cristyf4ad9df2011-07-08 16:49:03 +00002423 CompositeExclusion(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002424 break;
2425 }
2426 case MultiplyCompositeOp:
2427 {
cristyf4ad9df2011-07-08 16:49:03 +00002428 CompositeMultiply(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002429 break;
2430 }
2431 case ScreenCompositeOp:
2432 {
cristyf4ad9df2011-07-08 16:49:03 +00002433 CompositeScreen(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002434 break;
2435 }
2436 case DivideDstCompositeOp:
2437 {
cristyf4ad9df2011-07-08 16:49:03 +00002438 CompositeDivide(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002439 break;
2440 }
2441 case DivideSrcCompositeOp:
2442 {
cristyf4ad9df2011-07-08 16:49:03 +00002443 CompositeDivide(image,&destination,&source,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002444 break;
2445 }
2446 case DarkenCompositeOp:
2447 {
cristyf4ad9df2011-07-08 16:49:03 +00002448 CompositeDarken(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002449 break;
2450 }
2451 case LightenCompositeOp:
2452 {
cristyf4ad9df2011-07-08 16:49:03 +00002453 CompositeLighten(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002454 break;
2455 }
2456 case DarkenIntensityCompositeOp:
2457 {
cristyf4ad9df2011-07-08 16:49:03 +00002458 CompositeDarkenIntensity(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002459 break;
2460 }
2461 case LightenIntensityCompositeOp:
2462 {
cristyf4ad9df2011-07-08 16:49:03 +00002463 CompositeLightenIntensity(image,&source,&destination,&composite);
cristy4c08aed2011-07-01 19:47:50 +00002464 break;
2465 }
2466 case MathematicsCompositeOp:
2467 {
cristyf4ad9df2011-07-08 16:49:03 +00002468 CompositeMathematics(image,&source,&destination,&geometry_info,
2469 &composite);
cristy4c08aed2011-07-01 19:47:50 +00002470 break;
2471 }
2472 case ColorDodgeCompositeOp:
2473 {
2474 CompositeColorDodge(&source,&destination,&composite);
2475 break;
2476 }
2477 case ColorBurnCompositeOp:
2478 {
2479 CompositeColorBurn(&source,&destination,&composite);
2480 break;
2481 }
2482 case LinearDodgeCompositeOp:
2483 {
2484 CompositeLinearDodge(&source,&destination,&composite);
2485 break;
2486 }
2487 case LinearBurnCompositeOp:
2488 {
2489 CompositeLinearBurn(&source,&destination,&composite);
2490 break;
2491 }
2492 case HardLightCompositeOp:
2493 {
2494 CompositeHardLight(&source,&destination,&composite);
2495 break;
2496 }
2497 case OverlayCompositeOp:
2498 {
2499 CompositeHardLight(&destination,&source,&composite);
2500 break;
2501 }
2502 case SoftLightCompositeOp:
2503 {
2504 CompositeSoftLight(&source,&destination,&composite);
2505 break;
2506 }
2507 case LinearLightCompositeOp:
2508 {
2509 CompositeLinearLight(&source,&destination,&composite);
2510 break;
2511 }
2512 case PegtopLightCompositeOp:
2513 {
2514 CompositePegtopLight(&source,&destination,&composite);
2515 break;
2516 }
2517 case VividLightCompositeOp:
2518 {
2519 CompositeVividLight(&source,&destination,&composite);
2520 break;
2521 }
2522 case PinLightCompositeOp:
2523 {
2524 CompositePinLight(&source,&destination,&composite);
2525 break;
2526 }
2527 case ChangeMaskCompositeOp:
2528 {
2529 if ((composite.alpha > ((MagickRealType) QuantumRange/2.0)) ||
2530 (IsFuzzyEquivalencePixelInfo(&source,&destination) != MagickFalse))
2531 composite.alpha=(MagickRealType) TransparentAlpha;
2532 else
2533 composite.alpha=(MagickRealType) OpaqueAlpha;
2534 break;
2535 }
2536 case BumpmapCompositeOp:
2537 {
2538 if (source.alpha == TransparentAlpha)
2539 break;
2540 CompositeBumpmap(&source,&destination,&composite);
2541 break;
2542 }
2543 case DissolveCompositeOp:
2544 {
2545 CompositePixelInfoOver(&source,source_dissolve*source.alpha,
2546 &destination,(MagickRealType) (destination_dissolve*
2547 destination.alpha),&composite);
2548 break;
2549 }
2550 case BlendCompositeOp:
2551 {
2552 CompositePixelInfoBlend(&source,source_dissolve,&destination,
2553 destination_dissolve,&composite);
2554 break;
2555 }
2556 case ThresholdCompositeOp:
2557 {
2558 CompositeThreshold(&source,&destination,threshold,amount,&composite);
2559 break;
2560 }
2561 case ModulateCompositeOp:
2562 {
2563 ssize_t
2564 offset;
2565
2566 if (source.alpha == TransparentAlpha)
2567 break;
2568 offset=(ssize_t) (GetPixelInfoIntensity(&source)-midpoint);
2569 if (offset == 0)
2570 break;
2571 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2572 &saturation,&brightness);
2573 brightness+=(0.01*percent_brightness*offset)/midpoint;
2574 saturation*=0.01*percent_saturation;
2575 HSBComposite(hue,saturation,brightness,&composite.red,
2576 &composite.green,&composite.blue);
2577 break;
2578 }
2579 case HueCompositeOp:
2580 {
2581 if (source.alpha == TransparentAlpha)
2582 break;
2583 if (destination.alpha == TransparentAlpha)
2584 {
2585 composite=source;
2586 break;
2587 }
2588 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2589 &saturation,&brightness);
2590 CompositeHSB(source.red,source.green,source.blue,&hue,&sans,&sans);
2591 HSBComposite(hue,saturation,brightness,&composite.red,
2592 &composite.green,&composite.blue);
2593 if (source.alpha < destination.alpha)
2594 composite.alpha=source.alpha;
2595 break;
2596 }
2597 case SaturateCompositeOp:
2598 {
2599 if (source.alpha == TransparentAlpha)
2600 break;
2601 if (destination.alpha == TransparentAlpha)
2602 {
2603 composite=source;
2604 break;
2605 }
2606 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2607 &saturation,&brightness);
2608 CompositeHSB(source.red,source.green,source.blue,&sans,&saturation,
2609 &sans);
2610 HSBComposite(hue,saturation,brightness,&composite.red,
2611 &composite.green,&composite.blue);
2612 if (source.alpha < destination.alpha)
2613 composite.alpha=source.alpha;
2614 break;
2615 }
2616 case LuminizeCompositeOp:
2617 {
2618 if (source.alpha == TransparentAlpha)
2619 break;
2620 if (destination.alpha == TransparentAlpha)
2621 {
2622 composite=source;
2623 break;
2624 }
2625 CompositeHSB(destination.red,destination.green,destination.blue,&hue,
2626 &saturation,&brightness);
2627 CompositeHSB(source.red,source.green,source.blue,&sans,&sans,
2628 &brightness);
2629 HSBComposite(hue,saturation,brightness,&composite.red,
2630 &composite.green,&composite.blue);
2631 if (source.alpha < destination.alpha)
2632 composite.alpha=source.alpha;
2633 break;
2634 }
2635 case ColorizeCompositeOp:
2636 {
2637 if (source.alpha == TransparentAlpha)
2638 break;
2639 if (destination.alpha == TransparentAlpha)
2640 {
2641 composite=source;
2642 break;
2643 }
2644 CompositeHSB(destination.red,destination.green,destination.blue,&sans,
2645 &sans,&brightness);
2646 CompositeHSB(source.red,source.green,source.blue,&hue,&saturation,
2647 &sans);
2648 HSBComposite(hue,saturation,brightness,&composite.red,
2649 &composite.green,&composite.blue);
2650 if (source.alpha < destination.alpha)
2651 composite.alpha=source.alpha;
2652 break;
2653 }
2654 case CopyRedCompositeOp:
2655 case CopyCyanCompositeOp:
2656 {
2657 composite.red=source.red;
2658 break;
2659 }
2660 case CopyGreenCompositeOp:
2661 case CopyMagentaCompositeOp:
2662 {
2663 composite.green=source.green;
2664 break;
2665 }
2666 case CopyBlueCompositeOp:
2667 case CopyYellowCompositeOp:
2668 {
2669 composite.blue=source.blue;
2670 break;
2671 }
2672 case CopyOpacityCompositeOp:
2673 {
2674 if (source.matte == MagickFalse)
2675 {
2676 composite.alpha=(MagickRealType) GetPixelInfoIntensity(&source);
2677 break;
2678 }
2679 composite.alpha=source.alpha;
2680 break;
2681 }
2682 case CopyBlackCompositeOp:
2683 {
2684 if (source.colorspace != CMYKColorspace)
2685 ConvertRGBToCMYK(&source);
2686 composite.black=source.black;
2687 break;
2688 }
2689 case BlurCompositeOp:
2690 case DisplaceCompositeOp:
2691 case DistortCompositeOp:
2692 {
2693 composite=source;
2694 break;
2695 }
2696 default:
2697 break;
2698 }
2699 if (image->colorspace == CMYKColorspace)
2700 {
2701 composite.red=(MagickRealType) QuantumRange-composite.red;
2702 composite.green=(MagickRealType) QuantumRange-composite.green;
2703 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2704 composite.black=(MagickRealType) QuantumRange-composite.black;
2705 }
2706 SetPixelRed(image,ClampToQuantum(composite.red),q);
2707 SetPixelGreen(image,ClampToQuantum(composite.green),q);
2708 SetPixelBlue(image,ClampToQuantum(composite.blue),q);
2709 if (image->colorspace == CMYKColorspace)
2710 SetPixelBlack(image,ClampToQuantum(composite.black),q);
2711 SetPixelAlpha(image,ClampToQuantum(composite.alpha),q);
cristyed231572011-07-14 02:18:59 +00002712 p+=GetPixelChannels(composite_image);
2713 if (p >= (pixels+composite_image->columns*GetPixelChannels(composite_image)))
cristy4c08aed2011-07-01 19:47:50 +00002714 p=pixels;
cristyed231572011-07-14 02:18:59 +00002715 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002716 }
2717 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2718 status=MagickFalse;
2719 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2720 {
2721 MagickBooleanType
2722 proceed;
2723
2724#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002725 #pragma omp critical (MagickCore_CompositeImage)
cristy4c08aed2011-07-01 19:47:50 +00002726#endif
2727 proceed=SetImageProgress(image,CompositeImageTag,progress++,
2728 image->rows);
2729 if (proceed == MagickFalse)
2730 status=MagickFalse;
2731 }
2732 }
2733 composite_view=DestroyCacheView(composite_view);
2734 image_view=DestroyCacheView(image_view);
2735 if (destination_image != (Image * ) NULL)
2736 destination_image=DestroyImage(destination_image);
2737 return(status);
2738}
2739
2740/*
2741%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2742% %
2743% %
2744% %
2745% T e x t u r e I m a g e %
2746% %
2747% %
2748% %
2749%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2750%
2751% TextureImage() repeatedly tiles the texture image across and down the image
2752% canvas.
2753%
2754% The format of the TextureImage method is:
2755%
cristye941a752011-10-15 01:52:48 +00002756% MagickBooleanType TextureImage(Image *image,const Image *texture,
2757% ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002758%
2759% A description of each parameter follows:
2760%
2761% o image: the image.
2762%
2763% o texture: This image is the texture to layer on the background.
2764%
2765*/
cristye941a752011-10-15 01:52:48 +00002766MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture,
2767 ExceptionInfo *exception)
cristy4c08aed2011-07-01 19:47:50 +00002768{
2769#define TextureImageTag "Texture/Image"
2770
2771 CacheView
2772 *image_view,
2773 *texture_view;
2774
cristy4c08aed2011-07-01 19:47:50 +00002775 MagickBooleanType
2776 status;
2777
2778 ssize_t
2779 y;
2780
2781 assert(image != (Image *) NULL);
2782 if (image->debug != MagickFalse)
2783 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2784 assert(image->signature == MagickSignature);
2785 if (texture == (const Image *) NULL)
2786 return(MagickFalse);
2787 (void) SetImageVirtualPixelMethod(texture,TileVirtualPixelMethod);
cristy574cc262011-08-05 01:23:58 +00002788 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy4c08aed2011-07-01 19:47:50 +00002789 return(MagickFalse);
2790 status=MagickTrue;
2791 if ((image->compose != CopyCompositeOp) &&
2792 ((image->compose != OverCompositeOp) || (image->matte != MagickFalse) ||
2793 (texture->matte != MagickFalse)))
2794 {
2795 /*
2796 Tile texture onto the image background.
2797 */
2798#if defined(MAGICKCORE_OPENMP_SUPPORT)
2799 #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1)
2800#endif
2801 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture->rows)
2802 {
2803 register ssize_t
2804 x;
2805
2806 if (status == MagickFalse)
2807 continue;
2808 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture->columns)
2809 {
2810 MagickBooleanType
2811 thread_status;
2812
2813 thread_status=CompositeImage(image,image->compose,texture,x+
cristye941a752011-10-15 01:52:48 +00002814 texture->tile_offset.x,y+texture->tile_offset.y,exception);
cristy4c08aed2011-07-01 19:47:50 +00002815 if (thread_status == MagickFalse)
2816 {
2817 status=thread_status;
2818 break;
2819 }
2820 }
2821 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2822 {
2823 MagickBooleanType
2824 proceed;
2825
2826#if defined(MAGICKCORE_OPENMP_SUPPORT)
2827 #pragma omp critical (MagickCore_TextureImage)
2828#endif
2829 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2830 y,image->rows);
2831 if (proceed == MagickFalse)
2832 status=MagickFalse;
2833 }
2834 }
2835 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2836 image->rows,image->rows);
2837 return(status);
2838 }
2839 /*
2840 Tile texture onto the image background (optimized).
2841 */
2842 status=MagickTrue;
cristy4c08aed2011-07-01 19:47:50 +00002843 image_view=AcquireCacheView(image);
2844 texture_view=AcquireCacheView(texture);
2845#if defined(MAGICKCORE_OPENMP_SUPPORT)
2846 #pragma omp parallel for schedule(dynamic,4) shared(status) omp_throttle(1)
2847#endif
2848 for (y=0; y < (ssize_t) image->rows; y++)
2849 {
2850 MagickBooleanType
2851 sync;
2852
2853 register const Quantum
2854 *p,
2855 *pixels;
2856
2857 register ssize_t
2858 x;
2859
2860 register Quantum
2861 *q;
2862
2863 size_t
2864 width;
2865
2866 if (status == MagickFalse)
2867 continue;
2868 pixels=GetCacheViewVirtualPixels(texture_view,texture->tile_offset.x,(y+
2869 texture->tile_offset.y) % texture->rows,texture->columns,1,exception);
2870 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
2871 exception);
2872 if ((pixels == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2873 {
2874 status=MagickFalse;
2875 continue;
2876 }
2877 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture->columns)
2878 {
2879 register ssize_t
2880 i;
2881
2882 p=pixels;
2883 width=texture->columns;
2884 if ((x+(ssize_t) width) > (ssize_t) image->columns)
2885 width=image->columns-x;
2886 for (i=0; i < (ssize_t) width; i++)
2887 {
2888 SetPixelRed(image,GetPixelRed(texture,p),q);
2889 SetPixelGreen(image,GetPixelGreen(texture,p),q);
2890 SetPixelBlue(image,GetPixelBlue(texture,p),q);
2891 SetPixelAlpha(image,GetPixelAlpha(texture,p),q);
2892 if ((image->colorspace == CMYKColorspace) &&
2893 (texture->colorspace == CMYKColorspace))
2894 SetPixelBlack(image,GetPixelBlack(texture,p),q);
cristyed231572011-07-14 02:18:59 +00002895 p+=GetPixelChannels(texture);
2896 q+=GetPixelChannels(image);
cristy4c08aed2011-07-01 19:47:50 +00002897 }
2898 }
2899 sync=SyncCacheViewAuthenticPixels(image_view,exception);
2900 if (sync == MagickFalse)
2901 status=MagickFalse;
2902 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2903 {
2904 MagickBooleanType
2905 proceed;
2906
2907#if defined(MAGICKCORE_OPENMP_SUPPORT)
2908 #pragma omp critical (MagickCore_TextureImage)
2909#endif
2910 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
2911 image->rows);
2912 if (proceed == MagickFalse)
2913 status=MagickFalse;
2914 }
2915 }
2916 texture_view=DestroyCacheView(texture_view);
2917 image_view=DestroyCacheView(image_view);
2918 return(status);
2919}