1 /*
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 % Cristy %
17 % July 1992 %
18 % %
19 % %
20 % Copyright 1999-2021 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 % https://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 "magick/studio.h"
44 #include "magick/accelerate-private.h"
45 #include "magick/artifact.h"
46 #include "magick/cache-view.h"
47 #include "magick/channel.h"
48 #include "magick/client.h"
49 #include "magick/color.h"
50 #include "magick/color-private.h"
51 #include "magick/colorspace.h"
52 #include "magick/colorspace-private.h"
53 #include "magick/composite.h"
54 #include "magick/composite-private.h"
55 #include "magick/constitute.h"
56 #include "magick/draw.h"
57 #include "magick/fx.h"
58 #include "magick/gem.h"
59 #include "magick/geometry.h"
60 #include "magick/image.h"
61 #include "magick/image-private.h"
62 #include "magick/list.h"
63 #include "magick/log.h"
64 #include "magick/monitor.h"
65 #include "magick/monitor-private.h"
66 #include "magick/memory_.h"
67 #include "magick/option.h"
68 #include "magick/pixel-private.h"
69 #include "magick/property.h"
70 #include "magick/quantum.h"
71 #include "magick/resample.h"
72 #include "magick/resource_.h"
73 #include "magick/string_.h"
74 #include "magick/thread-private.h"
75 #include "magick/threshold.h"
76 #include "magick/token.h"
77 #include "magick/utility.h"
78 #include "magick/version.h"
79
80 /*
81 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 % %
83 % %
84 % %
85 % C o m p o s i t e I m a g e C h a n n e l %
86 % %
87 % %
88 % %
89 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90 %
91 % CompositeImageChannel() returns the second image composited onto the first
92 % at the specified offset, using the specified composite method.
93 %
94 % The format of the CompositeImageChannel method is:
95 %
96 % MagickBooleanType CompositeImage(Image *image,
97 % const CompositeOperator compose,Image *source_image,
98 % const ssize_t x_offset,const ssize_t y_offset)
99 % MagickBooleanType CompositeImageChannel(Image *image,
100 % const ChannelType channel,const CompositeOperator compose,
101 % Image *source_image,const ssize_t x_offset,const ssize_t y_offset)
102 %
103 % A description of each parameter follows:
104 %
105 % o image: the canvas image, modified by he composition
106 %
107 % o channel: the channel.
108 %
109 % o compose: This operator affects how the composite is applied to
110 % the image. The operators and how they are utilized are listed here
111 % http://www.w3.org/TR/SVG12/#compositing.
112 %
113 % o source_image: the composite (source) image.
114 %
115 % o x_offset: the column offset of the composited image.
116 %
117 % o y_offset: the row offset of the composited image.
118 %
119 % Extra Controls from Image meta-data in 'source_image' (artifacts)
120 %
121 % o "compose:args"
122 % A string containing extra numerical arguments for specific compose
123 % methods, generally expressed as a 'geometry' or a comma separated list
124 % of numbers.
125 %
126 % Compose methods needing such arguments include "BlendCompositeOp" and
127 % "DisplaceCompositeOp".
128 %
129 % o "compose:outside-overlay"
130 % Modify how the composition is to effect areas not directly covered
131 % by the 'source_image' at the offset given. Normally this is
132 % dependant on the 'compose' method, especially Duff-Porter methods.
133 %
134 % If set to "false" then disable all normal handling of pixels not
135 % covered by the source_image. Typically used for repeated tiling
136 % of the source_image by the calling API.
137 %
138 % Previous to IM v6.5.3-3 this was called "modify-outside-overlay"
139 %
140 */
141
142 /*
143 ** Programmers notes on SVG specification.
144 **
145 ** A Composition is defined by...
146 ** Color Function : f(Sc,Dc) where Sc and Dc are the normizalized colors
147 ** Blending areas : X = 1 for area of overlap ie: f(Sc,Dc)
148 ** Y = 1 for source preserved
149 ** Z = 1 for canvas preserved
150 **
151 ** Conversion to transparency (then optimized)
152 ** Dca' = f(Sc, Dc)*Sa*Da + Y*Sca*(1-Da) + Z*Dca*(1-Sa)
153 ** Da' = X*Sa*Da + Y*Sa*(1-Da) + Z*Da*(1-Sa)
154 **
155 ** Where...
156 ** Sca = Sc*Sa normalized Source color divided by Source alpha
157 ** Dca = Dc*Da normalized Dest color divided by Dest alpha
158 ** Dc' = Dca'/Da' the desired color value for this channel.
159 **
160 ** Da' in in the follow formula as 'gamma' The resulting alpla value.
161 **
162 **
163 ** Most functions use a blending mode of over (X=1,Y=1,Z=1)
164 ** this results in the following optimizations...
165 ** gamma = Sa+Da-Sa*Da;
166 ** gamma = 1 - QuantumScale*alpha * QuantumScale*beta;
167 ** opacity = QuantumScale*alpha*beta; // over blend, optimized 1-Gamma
168 **
169 ** The above SVG definitions also define that Mathematical Composition
170 ** methods should use a 'Over' blending mode for Alpha Channel.
171 ** It however was not applied for composition modes of 'Plus', 'Minus',
172 ** the modulus versions of 'Add' and 'Subtract'.
173 **
174 **
175 ** Mathematical operator changes to be applied from IM v6.7...
176 **
177 ** 1/ Modulus modes 'Add' and 'Subtract' are obsoleted and renamed
178 ** 'ModulusAdd' and 'ModulusSubtract' for clarity.
179 **
180 ** 2/ All mathematical compositions work as per the SVG specification
181 ** with regard to blending. This now includes 'ModulusAdd' and
182 ** 'ModulusSubtract'.
183 **
184 ** 3/ When the special channel flag 'sync' (syncronize channel updates)
185 ** is turned off (enabled by default) then mathematical compositions are
186 ** only performed on the channels specified, and are applied
187 ** independantally of each other. In other words the mathematics is
188 ** performed as 'pure' mathematical operations, rather than as image
189 ** operations.
190 */
191
Atop(const MagickRealType p,const MagickRealType Sa,const MagickRealType q,const MagickRealType magick_unused (Da))192 static inline MagickRealType Atop(const MagickRealType p,
193 const MagickRealType Sa,const MagickRealType q,
194 const MagickRealType magick_unused(Da))
195 {
196 magick_unreferenced(Da);
197
198 return(p*Sa+q*(1.0-Sa)); /* Da optimized out, Da/gamma => 1.0 */
199 }
200
CompositeAtop(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)201 static inline void CompositeAtop(const MagickPixelPacket *p,
202 const MagickPixelPacket *q,MagickPixelPacket *composite)
203 {
204 MagickRealType
205 Sa;
206
207 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
208 composite->opacity=q->opacity; /* optimized Da = 1.0-Gamma */
209 composite->red=Atop(p->red,Sa,q->red,1.0);
210 composite->green=Atop(p->green,Sa,q->green,1.0);
211 composite->blue=Atop(p->blue,Sa,q->blue,1.0);
212 if (q->colorspace == CMYKColorspace)
213 composite->index=Atop(p->index,Sa,q->index,1.0);
214 }
215
216 /*
217 What is this Composition method for? Can't find any specification!
218 WARNING this is not doing correct 'over' blend handling (Anthony Thyssen).
219 */
CompositeBumpmap(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)220 static inline void CompositeBumpmap(const MagickPixelPacket *p,
221 const MagickPixelPacket *q,MagickPixelPacket *composite)
222 {
223 MagickRealType
224 intensity;
225
226 intensity=MagickPixelIntensity(p);
227 composite->red=QuantumScale*intensity*q->red;
228 composite->green=QuantumScale*intensity*q->green;
229 composite->blue=QuantumScale*intensity*q->blue;
230 composite->opacity=(MagickRealType) QuantumScale*intensity*p->opacity;
231 if (q->colorspace == CMYKColorspace)
232 composite->index=QuantumScale*intensity*q->index;
233 }
234
CompositeClear(const MagickPixelPacket * q,MagickPixelPacket * composite)235 static inline void CompositeClear(const MagickPixelPacket *q,
236 MagickPixelPacket *composite)
237 {
238 composite->opacity=(MagickRealType) TransparentOpacity;
239 composite->red=0.0;
240 composite->green=0.0;
241 composite->blue=0.0;
242 if (q->colorspace == CMYKColorspace)
243 composite->index=0.0;
244 }
245
ColorBurn(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)246 static MagickRealType ColorBurn(const MagickRealType Sca,
247 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
248 {
249 double
250 SaSca;
251
252 if ((fabs((double) Sca) < MagickEpsilon) &&
253 (fabs((double) (Dca-Da)) < MagickEpsilon))
254 return(Sa*Da+Dca*(1.0-Sa));
255 if (Sca < MagickEpsilon)
256 return(Dca*(1.0-Sa));
257 SaSca=Sa*PerceptibleReciprocal(Sca);
258 return(Sa*Da-Sa*MagickMin(Da,(Da-Dca)*SaSca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
259 }
260
CompositeColorBurn(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)261 static inline void CompositeColorBurn(const MagickPixelPacket *p,
262 const MagickPixelPacket *q,MagickPixelPacket *composite)
263 {
264 MagickRealType
265 Da,
266 gamma,
267 Sa;
268
269 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
270 Da=1.0-QuantumScale*q->opacity;
271 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
272 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
273 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
274 gamma);
275 composite->red=gamma*ColorBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
276 q->red*Da,Da);
277 composite->green=gamma*ColorBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
278 q->green*Da,Da);
279 composite->blue=gamma*ColorBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
280 q->blue*Da,Da);
281 if (q->colorspace == CMYKColorspace)
282 composite->index=gamma*ColorBurn(QuantumScale*p->index*Sa,Sa,QuantumScale*
283 q->index*Da,Da);
284 }
285
286
ColorDodge(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)287 static MagickRealType ColorDodge(const MagickRealType Sca,
288 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
289 {
290 /*
291 Oct 2004 SVG specification.
292 */
293 if ((Sca*Da+Dca*Sa) >= Sa*Da)
294 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
295 return(Dca*Sa*Sa*PerceptibleReciprocal(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
296 #if 0
297 /*
298 New specification, March 2009 SVG specification. This specification was
299 also wrong of non-overlap cases.
300 */
301 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
302 return(Sca*(1.0-Da));
303 if (fabs(Sca-Sa) < MagickEpsilon)
304 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
305 return(Sa*MagickMin(Da,Dca*Sa/(Sa-Sca)));
306 #endif
307 #if 0
308 /*
309 Working from first principles using the original formula:
310
311 f(Sc,Dc) = Dc/(1-Sc)
312
313 This works correctly! Looks like the 2004 model was right but just
314 required a extra condition for correct handling.
315 */
316 if ((fabs(Sca-Sa) < MagickEpsilon) && (fabs(Dca) < MagickEpsilon))
317 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
318 if (fabs(Sca-Sa) < MagickEpsilon)
319 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
320 return(Dca*Sa*Sa/(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
321 #endif
322 }
323
CompositeColorDodge(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)324 static inline void CompositeColorDodge(const MagickPixelPacket *p,
325 const MagickPixelPacket *q,MagickPixelPacket *composite)
326 {
327 MagickRealType
328 Da,
329 gamma,
330 Sa;
331
332 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
333 Da=1.0-QuantumScale*q->opacity;
334 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
335 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
336 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
337 gamma);
338 composite->red=gamma*ColorDodge(QuantumScale*p->red*Sa,Sa,QuantumScale*
339 q->red*Da,Da);
340 composite->green=gamma*ColorDodge(QuantumScale*p->green*Sa,Sa,QuantumScale*
341 q->green*Da,Da);
342 composite->blue=gamma*ColorDodge(QuantumScale*p->blue*Sa,Sa,QuantumScale*
343 q->blue*Da,Da);
344 if (q->colorspace == CMYKColorspace)
345 composite->index=gamma*ColorDodge(QuantumScale*p->index*Sa,Sa,QuantumScale*
346 q->index*Da,Da);
347 }
348
Darken(const MagickRealType p,const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)349 static inline MagickRealType Darken(const MagickRealType p,
350 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
351 {
352 if (p < q)
353 return(MagickOver_(p,alpha,q,beta)); /* src-over */
354 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
355 }
356
CompositeDarken(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)357 static inline void CompositeDarken(const MagickPixelPacket *p,
358 const MagickPixelPacket *q,const ChannelType channel,
359 MagickPixelPacket *composite)
360 {
361 /*
362 Darken is equivalent to a 'Minimum' method
363 OR a greyscale version of a binary 'Or'
364 OR the 'Intersection' of pixel sets.
365 */
366 double
367 gamma;
368
369 if ( (channel & SyncChannels) != 0 ) {
370 composite->opacity=QuantumScale*p->opacity*q->opacity; /* Over Blend */
371 gamma=1.0-QuantumScale*composite->opacity;
372 gamma=PerceptibleReciprocal(gamma);
373 composite->red=gamma*Darken(p->red,p->opacity,q->red,q->opacity);
374 composite->green=gamma*Darken(p->green,p->opacity,q->green,q->opacity);
375 composite->blue=gamma*Darken(p->blue,p->opacity,q->blue,q->opacity);
376 if (q->colorspace == CMYKColorspace)
377 composite->index=gamma*Darken(p->index,p->opacity,q->index,q->opacity);
378 }
379 else { /* handle channels as separate grayscale channels */
380 if ( (channel & AlphaChannel) != 0 )
381 composite->opacity=MagickMax(p->opacity,q->opacity);
382 if ( (channel & RedChannel) != 0 )
383 composite->red=MagickMin(p->red,q->red);
384 if ( (channel & GreenChannel) != 0 )
385 composite->green=MagickMin(p->green,q->green);
386 if ( (channel & BlueChannel) != 0 )
387 composite->blue=MagickMin(p->blue,q->blue);
388 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
389 composite->index=MagickMin(p->index,q->index);
390 }
391 }
392
CompositeDarkenIntensity(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)393 static inline void CompositeDarkenIntensity(const MagickPixelPacket *p,
394 const MagickPixelPacket *q,const ChannelType channel,
395 MagickPixelPacket *composite)
396 {
397 /*
398 Select the pixel based on the intensity level.
399 If 'Sync' flag select whole pixel based on alpha weighted intensity.
400 Otherwise use intensity only, but restrict copy according to channel.
401 */
402 if ( (channel & SyncChannels) != 0 ) {
403 MagickRealType
404 Da,
405 Sa;
406
407 Sa=1.0-QuantumScale*p->opacity;
408 Da=1.0-QuantumScale*q->opacity;
409 *composite = (Sa*MagickPixelIntensity(p) < Da*MagickPixelIntensity(q))
410 ? *p : *q;
411 }
412 else {
413 int from_p = (MagickPixelIntensity(p) < MagickPixelIntensity(q));
414 if ( (channel & AlphaChannel) != 0 )
415 composite->opacity = from_p ? p->opacity : q->opacity;
416 if ( (channel & RedChannel) != 0 )
417 composite->red = from_p ? p->red : q->red;
418 if ( (channel & GreenChannel) != 0 )
419 composite->green = from_p ? p->green : q->green;
420 if ( (channel & BlueChannel) != 0 )
421 composite->blue = from_p ? p->blue : q->blue;
422 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
423 composite->index = from_p ? p->index : q->index;
424 }
425 }
426
Difference(const MagickRealType p,const MagickRealType Sa,const MagickRealType q,const MagickRealType Da)427 static inline MagickRealType Difference(const MagickRealType p,
428 const MagickRealType Sa,const MagickRealType q,const MagickRealType Da)
429 {
430 /* Optimized by Multipling by QuantumRange (taken from gamma). */
431 return(Sa*p+Da*q-Sa*Da*2.0*MagickMin(p,q));
432 }
433
CompositeDifference(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)434 static inline void CompositeDifference(const MagickPixelPacket *p,
435 const MagickPixelPacket *q,const ChannelType channel,
436 MagickPixelPacket *composite)
437 {
438 double
439 gamma;
440
441 MagickRealType
442 Da,
443 Sa;
444
445 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
446 Da=1.0-QuantumScale*q->opacity;
447 if ( (channel & SyncChannels) != 0 ) {
448 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
449 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
450 gamma=PerceptibleReciprocal(gamma);
451 /* Values are not normalized as an optimization. */
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->index=gamma*Difference(p->index,Sa,q->index,Da);
457 }
458 else { /* handle channels as separate grayscale channels */
459 if ( (channel & AlphaChannel) != 0 )
460 composite->opacity=QuantumRange-fabs((double) (p->opacity-q->opacity));
461 if ( (channel & RedChannel) != 0 )
462 composite->red=fabs((double) (p->red-q->red));
463 if ( (channel & GreenChannel) != 0 )
464 composite->green=fabs((double) (p->green-q->green));
465 if ( (channel & BlueChannel) != 0 )
466 composite->blue=fabs((double) (p->blue-q->blue));
467 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
468 composite->index=fabs((double) (p->index-q->index));
469 }
470 }
471
Divide(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)472 static MagickRealType Divide(const MagickRealType Sca,const MagickRealType Sa,
473 const MagickRealType Dca,const MagickRealType Da)
474 {
475 /*
476 Divide Source by Destination
477
478 f(Sc,Dc) = Sc / Dc
479
480 But with appropriate handling for special case of Dc == 0 specifically
481 so that f(Black,Black)=Black and f(non-Black,Black)=White.
482 It is however also important to correctly do 'over' alpha blending which
483 is why the formula becomes so complex.
484 */
485 if ((fabs((double) Sca) < MagickEpsilon) &&
486 (fabs((double) Dca) < MagickEpsilon))
487 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
488 if (fabs((double) Dca) < MagickEpsilon)
489 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
490 return(Sca*Da*Da*PerceptibleReciprocal(Dca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
491 }
492
CompositeDivide(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)493 static inline void CompositeDivide(const MagickPixelPacket *p,
494 const MagickPixelPacket *q,const ChannelType channel,
495 MagickPixelPacket *composite)
496 {
497 MagickRealType
498 Da,
499 gamma,
500 Sa;
501
502 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
503 Da=1.0-QuantumScale*q->opacity;
504 if ( (channel & SyncChannels) != 0 ) {
505 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
506 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
507 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
508 gamma);
509 composite->red=gamma*Divide(QuantumScale*p->red*Sa,Sa,QuantumScale*
510 q->red*Da,Da);
511 composite->green=gamma*Divide(QuantumScale*p->green*Sa,Sa,QuantumScale*
512 q->green*Da,Da);
513 composite->blue=gamma*Divide(QuantumScale*p->blue*Sa,Sa,QuantumScale*
514 q->blue*Da,Da);
515 if (q->colorspace == CMYKColorspace)
516 composite->index=gamma*Divide(QuantumScale*p->index*Sa,Sa,QuantumScale*
517 q->index*Da,Da);
518 }
519 else { /* handle channels as separate grayscale channels */
520 if ( (channel & AlphaChannel) != 0 )
521 composite->opacity=QuantumRange*(1.0-Divide(Sa,1.0,Da,1.0));
522 if ( (channel & RedChannel) != 0 )
523 composite->red=QuantumRange*
524 Divide(QuantumScale*p->red,1.0,QuantumScale*q->red,1.0);
525 if ( (channel & GreenChannel) != 0 )
526 composite->green=QuantumRange*
527 Divide(QuantumScale*p->green,1.0,QuantumScale*q->green,1.0);
528 if ( (channel & BlueChannel) != 0 )
529 composite->blue=QuantumRange*
530 Divide(QuantumScale*p->blue,1.0,QuantumScale*q->blue,1.0);
531 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
532 composite->index=QuantumRange*
533 Divide(QuantumScale*p->index,1.0,QuantumScale*q->index,1.0);
534 }
535 }
536
Exclusion(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)537 static MagickRealType Exclusion(const MagickRealType Sca,
538 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
539 {
540 return(Sca*Da+Dca*Sa-2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
541 }
542
CompositeExclusion(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)543 static inline void CompositeExclusion(const MagickPixelPacket *p,
544 const MagickPixelPacket *q,const ChannelType channel,
545 MagickPixelPacket *composite)
546 {
547 MagickRealType
548 gamma,
549 Sa,
550 Da;
551
552 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
553 Da=1.0-QuantumScale*q->opacity;
554 if ( (channel & SyncChannels) != 0 ) {
555 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
556 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
557 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
558 gamma);
559 composite->red=gamma*Exclusion(QuantumScale*p->red*Sa,Sa,QuantumScale*
560 q->red*Da,Da);
561 composite->green=gamma*Exclusion(QuantumScale*p->green*Sa,Sa,QuantumScale*
562 q->green*Da,Da);
563 composite->blue=gamma*Exclusion(QuantumScale*p->blue*Sa,Sa,QuantumScale*
564 q->blue*Da,Da);
565 if (q->colorspace == CMYKColorspace)
566 composite->index=gamma*Exclusion(QuantumScale*p->index*Sa,Sa,QuantumScale*
567 q->index*Da,Da);
568 }
569 else { /* handle channels as separate grayscale channels */
570 if ((channel & AlphaChannel) != 0)
571 composite->opacity=QuantumRange*(1.0-Exclusion(Sa,1.0,Da,1.0));
572 if ((channel & RedChannel) != 0)
573 composite->red=QuantumRange*Exclusion(QuantumScale*p->red,1.0,
574 QuantumScale*q->red,1.0);
575 if ((channel & GreenChannel) != 0)
576 composite->green=QuantumRange*Exclusion(QuantumScale*p->green,1.0,
577 QuantumScale*q->green,1.0);
578 if ((channel & BlueChannel) != 0)
579 composite->blue=QuantumRange*Exclusion(QuantumScale*p->blue,1.0,
580 QuantumScale*q->blue,1.0);
581 if (((channel & IndexChannel) != 0) && (q->colorspace == CMYKColorspace))
582 composite->index=QuantumRange*Exclusion(QuantumScale*p->index,1.0,
583 QuantumScale*q->index,1.0);
584 }
585 }
586
HardLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)587 static MagickRealType HardLight(const MagickRealType Sca,
588 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
589 {
590 if ((2.0*Sca) < Sa)
591 return(2.0*Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
592 return(Sa*Da-2.0*(Da-Dca)*(Sa-Sca)+Sca*(1.0-Da)+Dca*(1.0-Sa));
593 }
594
CompositeHardLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)595 static inline void CompositeHardLight(const MagickPixelPacket *p,
596 const MagickPixelPacket *q,MagickPixelPacket *composite)
597 {
598 MagickRealType
599 Da,
600 gamma,
601 Sa;
602
603 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
604 Da=1.0-QuantumScale*q->opacity;
605 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
606 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
607 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
608 gamma);
609 composite->red=gamma*HardLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
610 q->red*Da,Da);
611 composite->green=gamma*HardLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
612 q->green*Da,Da);
613 composite->blue=gamma*HardLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
614 q->blue*Da,Da);
615 if (q->colorspace == CMYKColorspace)
616 composite->index=gamma*HardLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
617 q->index*Da,Da);
618 }
619
HardMix(const MagickRealType Sca,const MagickRealType Dca)620 static MagickRealType HardMix(const MagickRealType Sca,
621 const MagickRealType Dca)
622 {
623 if ((Sca+Dca) < QuantumRange)
624 return(0.0);
625 else
626 return(1.0);
627 }
628
CompositeHardMix(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)629 static inline void CompositeHardMix(const MagickPixelPacket *p,
630 const MagickPixelPacket *q,MagickPixelPacket *composite)
631 {
632 MagickRealType
633 Da,
634 gamma,
635 Sa;
636
637 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
638 Da=1.0-QuantumScale*q->opacity;
639 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
640 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
641 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
642 gamma);
643 composite->red=gamma*HardMix(p->red*Sa,q->red*Da);
644 composite->green=gamma*HardMix(p->green*Sa,q->green*Da);
645 composite->blue=gamma*HardMix(p->blue*Sa,q->blue*Da);
646 if (q->colorspace == CMYKColorspace)
647 composite->index=gamma*HardMix(p->index*Sa,q->index*Da);
648 }
649
HCLComposite(const double hue,const double chroma,const double luma,MagickRealType * red,MagickRealType * green,MagickRealType * blue)650 static void HCLComposite(const double hue,const double chroma,const double luma,
651 MagickRealType *red,MagickRealType *green,MagickRealType *blue)
652 {
653 double
654 b,
655 c,
656 g,
657 h,
658 m,
659 r,
660 x;
661
662 /*
663 Convert HCL to RGB colorspace.
664 */
665 assert(red != (MagickRealType *) NULL);
666 assert(green != (MagickRealType *) NULL);
667 assert(blue != (MagickRealType *) NULL);
668 h=6.0*hue;
669 c=chroma;
670 x=c*(1.0-fabs(fmod(h,2.0)-1.0));
671 r=0.0;
672 g=0.0;
673 b=0.0;
674 if ((0.0 <= h) && (h < 1.0))
675 {
676 r=c;
677 g=x;
678 }
679 else
680 if ((1.0 <= h) && (h < 2.0))
681 {
682 r=x;
683 g=c;
684 }
685 else
686 if ((2.0 <= h) && (h < 3.0))
687 {
688 g=c;
689 b=x;
690 }
691 else
692 if ((3.0 <= h) && (h < 4.0))
693 {
694 g=x;
695 b=c;
696 }
697 else
698 if ((4.0 <= h) && (h < 5.0))
699 {
700 r=x;
701 b=c;
702 }
703 else
704 if ((5.0 <= h) && (h < 6.0))
705 {
706 r=c;
707 b=x;
708 }
709 m=luma-(0.298839*r+0.586811*g+0.114350*b);
710 *red=QuantumRange*(r+m);
711 *green=QuantumRange*(g+m);
712 *blue=QuantumRange*(b+m);
713 }
714
CompositeHCL(const MagickRealType red,const MagickRealType green,const MagickRealType blue,double * hue,double * chroma,double * luma)715 static void CompositeHCL(const MagickRealType red,const MagickRealType green,
716 const MagickRealType blue,double *hue,double *chroma,double *luma)
717 {
718 double
719 b,
720 c,
721 g,
722 h,
723 max,
724 r;
725
726 /*
727 Convert RGB to HCL colorspace.
728 */
729 assert(hue != (double *) NULL);
730 assert(chroma != (double *) NULL);
731 assert(luma != (double *) NULL);
732 r=(double) red;
733 g=(double) green;
734 b=(double) blue;
735 max=MagickMax(r,MagickMax(g,b));
736 c=max-(double) MagickMin(r,MagickMin(g,b));
737 h=0.0;
738 if (c == 0)
739 h=0.0;
740 else
741 if (red == (MagickRealType) max)
742 h=fmod((g-b)/c+6.0,6.0);
743 else
744 if (green == (MagickRealType) max)
745 h=((b-r)/c)+2.0;
746 else
747 if (blue == (MagickRealType) max)
748 h=((r-g)/c)+4.0;
749 *hue=(h/6.0);
750 *chroma=QuantumScale*c;
751 *luma=QuantumScale*(0.298839*r+0.586811*g+0.114350*b);
752 }
753
In(const MagickRealType p,const MagickRealType Sa,const MagickRealType magick_unused (q),const MagickRealType Da)754 static inline MagickRealType In(const MagickRealType p,const MagickRealType Sa,
755 const MagickRealType magick_unused(q),const MagickRealType Da)
756 {
757 magick_unreferenced(q);
758
759 return(Sa*p*Da);
760 }
761
CompositeIn(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)762 static inline void CompositeIn(const MagickPixelPacket *p,
763 const MagickPixelPacket *q,MagickPixelPacket *composite)
764 {
765 double
766 gamma;
767
768 MagickRealType
769 Sa,
770 Da;
771
772 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
773 Da=1.0-QuantumScale*q->opacity;
774 gamma=Sa*Da;
775 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
776 gamma=PerceptibleReciprocal(gamma);
777 composite->red=gamma*In(p->red,Sa,q->red,Da);
778 composite->green=gamma*In(p->green,Sa,q->green,Da);
779 composite->blue=gamma*In(p->blue,Sa,q->blue,Da);
780 if (q->colorspace == CMYKColorspace)
781 composite->index=gamma*In(p->index,Sa,q->index,Da);
782 }
783
Lighten(const MagickRealType p,const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)784 static inline MagickRealType Lighten(const MagickRealType p,
785 const MagickRealType alpha,const MagickRealType q,const MagickRealType beta)
786 {
787 if (p > q)
788 return(MagickOver_(p,alpha,q,beta)); /* src-over */
789 return(MagickOver_(q,beta,p,alpha)); /* dst-over */
790 }
791
CompositeLighten(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)792 static inline void CompositeLighten(const MagickPixelPacket *p,
793 const MagickPixelPacket *q,const ChannelType channel,
794 MagickPixelPacket *composite)
795 {
796 /*
797 Lighten is also equvalent to a 'Maximum' method
798 OR a greyscale version of a binary 'And'
799 OR the 'Union' of pixel sets.
800 */
801 double
802 gamma;
803
804 if ( (channel & SyncChannels) != 0 ) {
805 composite->opacity=QuantumScale*p->opacity*q->opacity; /* Over Blend */
806 gamma=1.0-QuantumScale*composite->opacity;
807 gamma=PerceptibleReciprocal(gamma);
808 composite->red=gamma*Lighten(p->red,p->opacity,q->red,q->opacity);
809 composite->green=gamma*Lighten(p->green,p->opacity,q->green,q->opacity);
810 composite->blue=gamma*Lighten(p->blue,p->opacity,q->blue,q->opacity);
811 if (q->colorspace == CMYKColorspace)
812 composite->index=gamma*Lighten(p->index,p->opacity,q->index,q->opacity);
813 }
814 else { /* handle channels as separate grayscale channels */
815 if ( (channel & AlphaChannel) != 0 )
816 composite->opacity=MagickMin(p->opacity,q->opacity);
817 if ( (channel & RedChannel) != 0 )
818 composite->red=MagickMax(p->red,q->red);
819 if ( (channel & GreenChannel) != 0 )
820 composite->green=MagickMax(p->green,q->green);
821 if ( (channel & BlueChannel) != 0 )
822 composite->blue=MagickMax(p->blue,q->blue);
823 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
824 composite->index=MagickMax(p->index,q->index);
825 }
826 }
827
CompositeLightenIntensity(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)828 static inline void CompositeLightenIntensity(const MagickPixelPacket *p,
829 const MagickPixelPacket *q,const ChannelType channel,
830 MagickPixelPacket *composite)
831 {
832 /*
833 Select the pixel based on the intensity level.
834 If 'Sync' flag select whole pixel based on alpha weighted intensity.
835 Otherwise use Intenisty only, but restrict copy according to channel.
836 */
837 if ( (channel & SyncChannels) != 0 ) {
838 MagickRealType
839 Da,
840 Sa;
841
842 Sa=1.0-QuantumScale*p->opacity;
843 Da=1.0-QuantumScale*q->opacity;
844 *composite = (Sa*MagickPixelIntensity(p) > Da*MagickPixelIntensity(q))
845 ? *p : *q;
846 }
847 else {
848 int from_p = (MagickPixelIntensity(p) > MagickPixelIntensity(q));
849 if ( (channel & AlphaChannel) != 0 )
850 composite->opacity = from_p ? p->opacity : q->opacity;
851 if ( (channel & RedChannel) != 0 )
852 composite->red = from_p ? p->red : q->red;
853 if ( (channel & GreenChannel) != 0 )
854 composite->green = from_p ? p->green : q->green;
855 if ( (channel & BlueChannel) != 0 )
856 composite->blue = from_p ? p->blue : q->blue;
857 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
858 composite->index = from_p ? p->index : q->index;
859 }
860 }
861
862 #if 0
863 static inline MagickRealType LinearDodge(const MagickRealType Sca,
864 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
865 {
866 /*
867 LinearDodge: simplifies to a trivial formula
868 f(Sc,Dc) = Sc + Dc
869 Dca' = Sca + Dca
870 */
871 return(Sca+Dca);
872 }
873 #endif
874
CompositeLinearDodge(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)875 static inline void CompositeLinearDodge(const MagickPixelPacket *p,
876 const MagickPixelPacket *q,MagickPixelPacket *composite)
877 {
878 double
879 gamma;
880
881 MagickRealType
882 Da,
883 Sa;
884
885 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
886 Da=1.0-QuantumScale*q->opacity;
887 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
888 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
889 gamma=PerceptibleReciprocal(gamma);
890 composite->red=gamma*(p->red*Sa+q->red*Da);
891 composite->green=gamma*(p->green*Sa+q->green*Da);
892 composite->blue=gamma*(p->blue*Sa+q->blue*Da);
893 if (q->colorspace == CMYKColorspace)
894 composite->index=gamma*(p->index*Sa+q->index*Da);
895 }
896
897
LinearBurn(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)898 static inline MagickRealType LinearBurn(const MagickRealType Sca,
899 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
900 {
901 /*
902 LinearBurn: as defined by Abode Photoshop, according to
903 http://www.simplefilter.de/en/basics/mixmods.html is:
904
905 f(Sc,Dc) = Sc + Dc - 1
906 */
907 return(Sca+Dca-Sa*Da);
908 }
909
CompositeLinearBurn(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)910 static inline void CompositeLinearBurn(const MagickPixelPacket *p,
911 const MagickPixelPacket *q,MagickPixelPacket *composite)
912 {
913 MagickRealType
914 Da,
915 gamma,
916 Sa;
917
918 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
919 Da=1.0-QuantumScale*q->opacity;
920 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
921 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
922 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
923 gamma);
924 composite->red=gamma*LinearBurn(QuantumScale*p->red*Sa,Sa,QuantumScale*
925 q->red*Da,Da);
926 composite->green=gamma*LinearBurn(QuantumScale*p->green*Sa,Sa,QuantumScale*
927 q->green*Da,Da);
928 composite->blue=gamma*LinearBurn(QuantumScale*p->blue*Sa,Sa,QuantumScale*
929 q->blue*Da,Da);
930 if (q->colorspace == CMYKColorspace)
931 composite->index=gamma*LinearBurn(QuantumScale*p->index*Sa,Sa,QuantumScale*
932 q->index*Da,Da);
933 }
934
LinearLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)935 static inline MagickRealType LinearLight(const MagickRealType Sca,
936 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
937 {
938 #if 0
939 /*
940 Previous formula, was only valid for fully-opaque images.
941 */
942 return(Dca+2*Sca-1.0);
943 #else
944 /*
945 LinearLight: as defined by Abode Photoshop, according to
946 http://www.simplefilter.de/en/basics/mixmods.html is:
947
948 f(Sc,Dc) = Dc + 2*Sc - 1
949 */
950 return((Sca-Sa)*Da+Sca+Dca);
951 #endif
952 }
953
CompositeLinearLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)954 static inline void CompositeLinearLight(const MagickPixelPacket *p,
955 const MagickPixelPacket *q,MagickPixelPacket *composite)
956 {
957 MagickRealType
958 Da,
959 gamma,
960 Sa;
961
962 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
963 Da=1.0-QuantumScale*q->opacity;
964 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
965 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
966 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
967 gamma);
968 composite->red=gamma*LinearLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
969 q->red*Da,Da);
970 composite->green=gamma*LinearLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
971 q->green*Da,Da);
972 composite->blue=gamma*LinearLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
973 q->blue*Da,Da);
974 if (q->colorspace == CMYKColorspace)
975 composite->index=gamma*LinearLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
976 q->index*Da,Da);
977 }
978
Mathematics(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da,const GeometryInfo * geometry_info)979 static inline MagickRealType Mathematics(const MagickRealType Sca,
980 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da,
981 const GeometryInfo *geometry_info)
982 {
983 /*
984 'Mathematics' a free form user control mathematical composition is defined
985 as...
986
987 f(Sc,Dc) = A*Sc*Dc + B*Sc + C*Dc + D
988
989 Where the arguments A,B,C,D are (currently) passed to composite as
990 a command separated 'geometry' string in "compose:args" image artifact.
991
992 A = a->rho, B = a->sigma, C = a->xi, D = a->psi
993
994 Applying the SVG transparency formula (see above), we get...
995
996 Dca' = Sa*Da*f(Sc,Dc) + Sca*(1.0-Da) + Dca*(1.0-Sa)
997
998 Dca' = A*Sca*Dca + B*Sca*Da + C*Dca*Sa + D*Sa*Da + Sca*(1.0-Da) +
999 Dca*(1.0-Sa)
1000 */
1001 return(geometry_info->rho*Sca*Dca+geometry_info->sigma*Sca*Da+
1002 geometry_info->xi*Dca*Sa+geometry_info->psi*Sa*Da+Sca*(1.0-Da)+
1003 Dca*(1.0-Sa));
1004 }
1005
CompositeMathematics(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,const GeometryInfo * args,MagickPixelPacket * composite)1006 static inline void CompositeMathematics(const MagickPixelPacket *p,
1007 const MagickPixelPacket *q,const ChannelType channel, const GeometryInfo
1008 *args, MagickPixelPacket *composite)
1009 {
1010 double
1011 gamma;
1012
1013 MagickRealType
1014 Da,
1015 Sa;
1016
1017 Sa=1.0-QuantumScale*p->opacity; /* ??? - AT */
1018 Da=1.0-QuantumScale*q->opacity;
1019 if ( (channel & SyncChannels) != 0 ) {
1020 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1021 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1022 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1023 gamma);
1024 composite->red=gamma*Mathematics(QuantumScale*p->red*Sa,Sa,QuantumScale*
1025 q->red*Da,Da,args);
1026 composite->green=gamma*Mathematics(QuantumScale*p->green*Sa,Sa,QuantumScale*
1027 q->green*Da,Da,args);
1028 composite->blue=gamma*Mathematics(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1029 q->blue*Da,Da,args);
1030 if (q->colorspace == CMYKColorspace)
1031 composite->index=gamma*Mathematics(QuantumScale*p->index*Sa,Sa,
1032 QuantumScale*q->index*Da,Da,args);
1033 }
1034 else { /* handle channels as separate grayscale channels */
1035 if ( (channel & AlphaChannel) != 0 )
1036 composite->opacity=QuantumRange*(1.0-Mathematics(Sa,1.0,Da,1.0,args));
1037 if ( (channel & RedChannel) != 0 )
1038 composite->red=QuantumRange*
1039 Mathematics(QuantumScale*p->red,1.0,QuantumScale*q->red,1.0,args);
1040 if ( (channel & GreenChannel) != 0 )
1041 composite->green=QuantumRange*
1042 Mathematics(QuantumScale*p->green,1.0,QuantumScale*q->green,1.0,args);
1043 if ( (channel & BlueChannel) != 0 )
1044 composite->blue=QuantumRange*
1045 Mathematics(QuantumScale*p->blue,1.0,QuantumScale*q->blue,1.0,args);
1046 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1047 composite->index=QuantumRange*
1048 Mathematics(QuantumScale*p->index,1.0,QuantumScale*q->index,1.0,args);
1049 }
1050
1051 }
1052
CompositePlus(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1053 static inline void CompositePlus(const MagickPixelPacket *p,
1054 const MagickPixelPacket *q,const ChannelType channel,
1055 MagickPixelPacket *composite)
1056 {
1057 if ( (channel & SyncChannels) != 0 ) {
1058 /*
1059 NOTE: "Plus" does not use 'over' alpha-blending but uses a
1060 special 'plus' form of alph-blending. It is the ONLY mathematical
1061 operator to do this. this is what makes it different to the
1062 otherwise equivalent "LinearDodge" composition method.
1063
1064 Note however that color channels are still effected by the alpha channel
1065 as a result of the blending, making it just as useless for independant
1066 channel maths, just like all other mathematical composition methods.
1067
1068 As such the removal of the 'sync' flag, is still a usful convention.
1069
1070 The MagickPixelCompositePlus() function is defined in
1071 "composite-private.h" so it can also be used for Image Blending.
1072 */
1073 MagickPixelCompositePlus(p,p->opacity,q,q->opacity,composite);
1074 }
1075 else { /* handle channels as separate grayscale channels */
1076 if ( (channel & AlphaChannel) != 0 )
1077 composite->opacity=p->opacity+q->opacity-QuantumRange;
1078 if ( (channel & RedChannel) != 0 )
1079 composite->red=p->red+q->red;
1080 if ( (channel & GreenChannel) != 0 )
1081 composite->green=p->green+q->green;
1082 if ( (channel & BlueChannel) != 0 )
1083 composite->blue=p->blue+q->blue;
1084 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1085 composite->index=p->index+q->index;
1086 }
1087 }
1088
Minus(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType magick_unused (Da))1089 static inline MagickRealType Minus(const MagickRealType Sca,
1090 const MagickRealType Sa,const MagickRealType Dca,
1091 const MagickRealType magick_unused(Da))
1092 {
1093 /*
1094 Minus Source from Destination
1095
1096 f(Sc,Dc) = Sc - Dc
1097
1098 */
1099 magick_unreferenced(Da);
1100
1101 return(Sca+Dca-2*Dca*Sa);
1102 }
1103
CompositeMinus(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1104 static inline void CompositeMinus(const MagickPixelPacket *p,
1105 const MagickPixelPacket *q,const ChannelType channel,
1106 MagickPixelPacket *composite)
1107 {
1108 double
1109 gamma;
1110
1111 MagickRealType
1112 Da,
1113 Sa;
1114
1115 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1116 Da=1.0-QuantumScale*q->opacity;
1117 if ( (channel & SyncChannels) != 0 ) {
1118 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1119 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1120 gamma=PerceptibleReciprocal(gamma);
1121 composite->red=gamma*Minus(p->red*Sa,Sa,q->red*Da,Da);
1122 composite->green=gamma*Minus(p->green*Sa,Sa,q->green*Da,Da);
1123 composite->blue=gamma*Minus(p->blue*Sa,Sa,q->blue*Da,Da);
1124 if (q->colorspace == CMYKColorspace)
1125 composite->index=gamma*Minus(p->index*Sa,Sa,q->index*Da,Da);
1126 }
1127 else { /* handle channels as separate grayscale channels */
1128 if ( (channel & AlphaChannel) != 0 )
1129 composite->opacity=QuantumRange*(1.0-(Sa-Da));
1130 if ( (channel & RedChannel) != 0 )
1131 composite->red=p->red-q->red;
1132 if ( (channel & GreenChannel) != 0 )
1133 composite->green=p->green-q->green;
1134 if ( (channel & BlueChannel) != 0 )
1135 composite->blue=p->blue-q->blue;
1136 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1137 composite->index=p->index-q->index;
1138 }
1139 }
1140
ModulusAdd(const MagickRealType Sc,const MagickRealType Sa,const MagickRealType Dc,const MagickRealType Da)1141 static inline MagickRealType ModulusAdd(const MagickRealType Sc,
1142 const MagickRealType Sa,const MagickRealType Dc,const MagickRealType Da)
1143 {
1144 if (((Sc*Sa)+(Dc*Da)) <= QuantumRange)
1145 return((Sc*Sa)+Dc*Da);
1146 return(((Sc*Sa)+Dc*Da)-QuantumRange);
1147 }
1148
CompositeModulusAdd(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1149 static inline void CompositeModulusAdd(const MagickPixelPacket *p,
1150 const MagickPixelPacket *q,const ChannelType channel,
1151 MagickPixelPacket *composite)
1152 {
1153 if ( (channel & SyncChannels) != 0 ) {
1154 double
1155 gamma;
1156
1157 MagickRealType
1158 Sa,
1159 Da;
1160
1161 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1162 Da=1.0-QuantumScale*q->opacity;
1163 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1164 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1165 gamma=PerceptibleReciprocal(gamma);
1166 composite->red=ModulusAdd(p->red,Sa,q->red,Da);
1167 composite->green=ModulusAdd(p->green,Sa,q->green,Da);
1168 composite->blue=ModulusAdd(p->blue,Sa,q->blue,Da);
1169 if (q->colorspace == CMYKColorspace)
1170 composite->index=ModulusAdd(p->index,Sa,q->index,Da);
1171 }
1172 else { /* handle channels as separate grayscale channels */
1173 if ( (channel & AlphaChannel) != 0 )
1174 composite->opacity=QuantumRange-ModulusAdd(QuantumRange-p->opacity,
1175 1.0,QuantumRange-q->opacity,1.0);
1176 if ( (channel & RedChannel) != 0 )
1177 composite->red=ModulusAdd(p->red,1.0,q->red,1.0);
1178 if ( (channel & GreenChannel) != 0 )
1179 composite->green=ModulusAdd(p->green,1.0,q->green,1.0);
1180 if ( (channel & BlueChannel) != 0 )
1181 composite->blue=ModulusAdd(p->blue,1.0,q->blue,1.0);
1182 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1183 composite->index=ModulusAdd(p->index,1.0,q->index,1.0);
1184 }
1185 }
1186
ModulusSubtract(const MagickRealType Sc,const MagickRealType Sa,const MagickRealType Dc,const MagickRealType Da)1187 static inline MagickRealType ModulusSubtract(const MagickRealType Sc,
1188 const MagickRealType Sa,const MagickRealType Dc,const MagickRealType Da)
1189 {
1190 if (((Sc*Sa)-(Dc*Da)) <= 0.0)
1191 return((Sc*Sa)-Dc*Da);
1192 return(((Sc*Sa)-Dc*Da)+QuantumRange);
1193 }
1194
CompositeModulusSubtract(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1195 static inline void CompositeModulusSubtract(const MagickPixelPacket *p,
1196 const MagickPixelPacket *q,const ChannelType channel,
1197 MagickPixelPacket *composite)
1198 {
1199 if ( (channel & SyncChannels) != 0 ) {
1200 double
1201 gamma;
1202
1203 MagickRealType
1204 Da,
1205 Sa;
1206
1207 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1208 Da=1.0-QuantumScale*q->opacity;
1209 gamma = RoundToUnity(Sa+Da-Sa*Da);
1210 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1211 gamma=PerceptibleReciprocal(gamma);
1212 composite->red=ModulusSubtract(p->red,Sa,q->red,Da);
1213 composite->green=ModulusSubtract(p->green,Sa,q->green,Da);
1214 composite->blue=ModulusSubtract(p->blue,Sa,q->blue,Da);
1215 if (q->colorspace == CMYKColorspace)
1216 composite->index=ModulusSubtract(p->index,Sa,q->index,Da);
1217 }
1218 else { /* handle channels as separate grayscale channels */
1219 if ( (channel & AlphaChannel) != 0 )
1220 composite->opacity=QuantumRange-ModulusSubtract(QuantumRange-p->opacity,
1221 1.0,QuantumRange-q->opacity,1.0);
1222 if ( (channel & RedChannel) != 0 )
1223 composite->red=ModulusSubtract(p->red,1.0,q->red,1.0);
1224 if ( (channel & GreenChannel) != 0 )
1225 composite->green=ModulusSubtract(p->green,1.0,q->green,1.0);
1226 if ( (channel & BlueChannel) != 0 )
1227 composite->blue=ModulusSubtract(p->blue,1.0,q->blue,1.0);
1228 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1229 composite->index=ModulusSubtract(p->index,1.0,q->index,1.0);
1230 }
1231 }
1232
Multiply(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1233 static inline MagickRealType Multiply(const MagickRealType Sca,
1234 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1235 {
1236 return(Sca*Dca+Sca*(1.0-Da)+Dca*(1.0-Sa));
1237 }
1238
CompositeMultiply(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1239 static inline void CompositeMultiply(const MagickPixelPacket *p,
1240 const MagickPixelPacket *q,const ChannelType channel,
1241 MagickPixelPacket *composite)
1242 {
1243 MagickRealType
1244 Da,
1245 gamma,
1246 Sa;
1247
1248 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1249 Da=1.0-QuantumScale*q->opacity;
1250 if ( (channel & SyncChannels) != 0 ) {
1251 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1252 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1253 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1254 gamma);
1255 composite->red=gamma*Multiply(QuantumScale*p->red*Sa,Sa,QuantumScale*
1256 q->red*Da,Da);
1257 composite->green=gamma*Multiply(QuantumScale*p->green*Sa,Sa,QuantumScale*
1258 q->green*Da,Da);
1259 composite->blue=gamma*Multiply(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1260 q->blue*Da,Da);
1261 if (q->colorspace == CMYKColorspace)
1262 composite->index=gamma*Multiply(QuantumScale*p->index*Sa,Sa,QuantumScale*
1263 q->index*Da,Da);
1264 }
1265 else { /* handle channels as separate grayscale channels */
1266 if ( (channel & AlphaChannel) != 0 )
1267 composite->opacity=QuantumRange*(1.0-Sa*Da);
1268 if ( (channel & RedChannel) != 0 )
1269 composite->red=QuantumScale*p->red*q->red;
1270 if ( (channel & GreenChannel) != 0 )
1271 composite->green=QuantumScale*p->green*q->green;
1272 if ( (channel & BlueChannel) != 0 )
1273 composite->blue=QuantumScale*p->blue*q->blue;
1274 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1275 composite->index=QuantumScale*p->index*q->index;
1276 }
1277 }
1278
Out(const MagickRealType p,const MagickRealType Sa,const MagickRealType magick_unused (q),const MagickRealType Da)1279 static inline MagickRealType Out(const MagickRealType p,
1280 const MagickRealType Sa,const MagickRealType magick_unused(q),
1281 const MagickRealType Da)
1282 {
1283 magick_unreferenced(q);
1284
1285 return(Sa*p*(1.0-Da));
1286 }
1287
CompositeOut(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1288 static inline void CompositeOut(const MagickPixelPacket *p,
1289 const MagickPixelPacket *q,MagickPixelPacket *composite)
1290 {
1291 double
1292 gamma;
1293
1294 MagickRealType
1295 Da,
1296 Sa;
1297
1298 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1299 Da=1.0-QuantumScale*q->opacity;
1300 gamma=Sa*(1.0-Da);
1301 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1302 gamma=PerceptibleReciprocal(gamma);
1303 composite->red=gamma*Out(p->red,Sa,q->red,Da);
1304 composite->green=gamma*Out(p->green,Sa,q->green,Da);
1305 composite->blue=gamma*Out(p->blue,Sa,q->blue,Da);
1306 if (q->colorspace == CMYKColorspace)
1307 composite->index=gamma*Out(p->index,Sa,q->index,Da);
1308 }
1309
PegtopLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1310 static MagickRealType PegtopLight(const MagickRealType Sca,
1311 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1312 {
1313 /*
1314 PegTop: A Soft-Light alternative: A continuous version of the Softlight
1315 function, producing very similar results.
1316
1317 f(Sc,Dc) = Dc^2*(1-2*Sc) + 2*Sc*Dc
1318
1319 See http://www.pegtop.net/delphi/articles/blendmodes/softlight.htm.
1320 */
1321 if (fabs((double) Da) < MagickEpsilon)
1322 return(Sca);
1323 return(Dca*Dca*(Sa-2.0*Sca)*PerceptibleReciprocal(Da)+Sca*(2.0*Dca+1.0-Da)+Dca*(1.0-Sa));
1324 }
1325
CompositePegtopLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1326 static inline void CompositePegtopLight(const MagickPixelPacket *p,
1327 const MagickPixelPacket *q,MagickPixelPacket *composite)
1328 {
1329 MagickRealType
1330 Da,
1331 gamma,
1332 Sa;
1333
1334 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1335 Da=1.0-QuantumScale*q->opacity;
1336 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1337 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1338 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1339 gamma);
1340 composite->red=gamma*PegtopLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1341 q->red*Da,Da);
1342 composite->green=gamma*PegtopLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1343 q->green*Da,Da);
1344 composite->blue=gamma*PegtopLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1345 q->blue*Da,Da);
1346 if (q->colorspace == CMYKColorspace)
1347 composite->index=gamma*PegtopLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
1348 q->index*Da,Da);
1349 }
1350
PinLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1351 static MagickRealType PinLight(const MagickRealType Sca,
1352 const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)
1353 {
1354 /*
1355 PinLight: A Photoshop 7 composition method
1356 http://www.simplefilter.de/en/basics/mixmods.html
1357
1358 f(Sc,Dc) = Dc<2*Sc-1 ? 2*Sc-1 : Dc>2*Sc ? 2*Sc : Dc
1359 */
1360 if (Dca*Sa < Da*(2*Sca-Sa))
1361 return(Sca*(Da+1.0)-Sa*Da+Dca*(1.0-Sa));
1362 if ((Dca*Sa) > (2*Sca*Da))
1363 return(Sca*Da+Sca+Dca*(1.0-Sa));
1364 return(Sca*(1.0-Da)+Dca);
1365 }
1366
CompositePinLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1367 static inline void CompositePinLight(const MagickPixelPacket *p,
1368 const MagickPixelPacket *q,MagickPixelPacket *composite)
1369 {
1370 MagickRealType
1371 Da,
1372 gamma,
1373 Sa;
1374
1375 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1376 Da=1.0-QuantumScale*q->opacity;
1377 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1378 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1379 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1380 gamma);
1381 composite->red=gamma*PinLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1382 q->red*Da,Da);
1383 composite->green=gamma*PinLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1384 q->green*Da,Da);
1385 composite->blue=gamma*PinLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1386 q->blue*Da,Da);
1387 if (q->colorspace == CMYKColorspace)
1388 composite->index=gamma*PinLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
1389 q->index*Da,Da);
1390 }
1391
Screen(const MagickRealType Sca,const MagickRealType Dca)1392 static inline MagickRealType Screen(const MagickRealType Sca,
1393 const MagickRealType Dca)
1394 {
1395 /* Screen: A negated multiply
1396 f(Sc,Dc) = 1.0-(1.0-Sc)*(1.0-Dc)
1397 */
1398 return(Sca+Dca-Sca*Dca);
1399 }
1400
CompositeScreen(const MagickPixelPacket * p,const MagickPixelPacket * q,const ChannelType channel,MagickPixelPacket * composite)1401 static inline void CompositeScreen(const MagickPixelPacket *p,
1402 const MagickPixelPacket *q,const ChannelType channel,
1403 MagickPixelPacket *composite)
1404 {
1405 double
1406 gamma;
1407
1408 MagickRealType
1409 Da,
1410 Sa;
1411
1412 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1413 Da=1.0-QuantumScale*q->opacity;
1414 if ( (channel & SyncChannels) != 0 ) {
1415 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1416 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1417 Sa*=(MagickRealType) QuantumScale;
1418 Da*=(MagickRealType) QuantumScale; /* optimization */
1419 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1420 gamma);
1421 composite->red=gamma*Screen(p->red*Sa,q->red*Da);
1422 composite->green=gamma*Screen(p->green*Sa,q->green*Da);
1423 composite->blue=gamma*Screen(p->blue*Sa,q->blue*Da);
1424 if (q->colorspace == CMYKColorspace)
1425 composite->index=gamma*Screen(p->index*Sa,q->index*Da);
1426 }
1427 else { /* handle channels as separate grayscale channels */
1428 if ( (channel & AlphaChannel) != 0 )
1429 composite->opacity=QuantumRange*(1.0-Screen(Sa,Da));
1430 if ( (channel & RedChannel) != 0 )
1431 composite->red=QuantumRange*Screen(QuantumScale*p->red,
1432 QuantumScale*q->red);
1433 if ( (channel & GreenChannel) != 0 )
1434 composite->green=QuantumRange*Screen(QuantumScale*p->green,
1435 QuantumScale*q->green);
1436 if ( (channel & BlueChannel) != 0 )
1437 composite->blue=QuantumRange*Screen(QuantumScale*p->blue,
1438 QuantumScale*q->blue);
1439 if ( (channel & IndexChannel) != 0 && q->colorspace == CMYKColorspace)
1440 composite->index=QuantumRange*Screen(QuantumScale*p->index,
1441 QuantumScale*q->index);
1442 }
1443 }
1444
SoftLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1445 static MagickRealType SoftLight(const MagickRealType Sca,
1446 const MagickRealType Sa, const MagickRealType Dca, const MagickRealType Da)
1447 {
1448 MagickRealType
1449 alpha,
1450 beta;
1451
1452 alpha=Dca*PerceptibleReciprocal(Da);
1453 if ((2.0*Sca) < Sa)
1454 return(Dca*(Sa+(2.0*Sca-Sa)*(1.0-alpha))+Sca*(1.0-Da)+Dca*(1.0-Sa));
1455 if (((2.0*Sca) > Sa) && ((4.0*Dca) <= Da))
1456 {
1457 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(4.0*alpha*(4.0*alpha+1.0)*(alpha-1.0)+7.0*
1458 alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1459 return(beta);
1460 }
1461 beta=Dca*Sa+Da*(2.0*Sca-Sa)*(pow(alpha,0.5)-alpha)+Sca*(1.0-Da)+Dca*(1.0-Sa);
1462 return(beta);
1463 }
1464
CompositeSoftLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1465 static inline void CompositeSoftLight(const MagickPixelPacket *p,
1466 const MagickPixelPacket *q,MagickPixelPacket *composite)
1467 {
1468 MagickRealType
1469 Da,
1470 gamma,
1471 Sa;
1472
1473 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1474 Da=1.0-QuantumScale*q->opacity;
1475 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1476 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1477 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1478 gamma);
1479 composite->red=gamma*SoftLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1480 q->red*Da,Da);
1481 composite->green=gamma*SoftLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1482 q->green*Da,Da);
1483 composite->blue=gamma*SoftLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1484 q->blue*Da,Da);
1485 if (q->colorspace == CMYKColorspace)
1486 composite->index=gamma*SoftLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
1487 q->index*Da,Da);
1488 }
1489
1490 /*
1491 Deprecated
1492 Multiply difference by amount, if differance larger than threshold???
1493 What use this is is completely unknown
1494 The Opacity calculation appears to be inverted -- Anthony Thyssen
1495 */
Threshold(const MagickRealType p,const MagickRealType q,const MagickRealType threshold,const MagickRealType amount)1496 static inline MagickRealType Threshold(const MagickRealType p,
1497 const MagickRealType q,const MagickRealType threshold,
1498 const MagickRealType amount)
1499 {
1500 MagickRealType
1501 delta;
1502
1503 delta=p-q;
1504 if ((MagickRealType) fabs((double) (2.0*delta)) < threshold)
1505 return(q);
1506 return(q+delta*amount);
1507 }
1508
CompositeThreshold(const MagickPixelPacket * p,const MagickPixelPacket * q,const MagickRealType threshold,const MagickRealType amount,MagickPixelPacket * composite)1509 static inline void CompositeThreshold(const MagickPixelPacket *p,
1510 const MagickPixelPacket *q,const MagickRealType threshold,
1511 const MagickRealType amount,MagickPixelPacket *composite)
1512 {
1513 composite->red=Threshold(p->red,q->red,threshold,amount);
1514 composite->green=Threshold(p->green,q->green,threshold,amount);
1515 composite->blue=Threshold(p->blue,q->blue,threshold,amount);
1516 composite->opacity=QuantumRange-Threshold(p->opacity,q->opacity,
1517 threshold,amount);
1518 if (q->colorspace == CMYKColorspace)
1519 composite->index=Threshold(p->index,q->index,threshold,amount);
1520 }
1521
1522
VividLight(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1523 static MagickRealType VividLight(const MagickRealType Sca,
1524 const MagickRealType Sa, const MagickRealType Dca, const MagickRealType Da)
1525 {
1526 /*
1527 VividLight: A Photoshop 7 composition method. See
1528 http://www.simplefilter.de/en/basics/mixmods.html.
1529
1530 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
1531 */
1532 if ((fabs((double) Sa) < MagickEpsilon) ||
1533 (fabs((double) (Sca-Sa)) < MagickEpsilon))
1534 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
1535 if ((2*Sca) <= Sa)
1536 return(Sa*(Da+Sa*(Dca-Da)*PerceptibleReciprocal(2.0*Sca))+Sca*(1.0-Da)+
1537 Dca*(1.0-Sa));
1538 return(Dca*Sa*Sa*PerceptibleReciprocal(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*
1539 (1.0-Sa));
1540 }
1541
CompositeVividLight(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1542 static inline void CompositeVividLight(const MagickPixelPacket *p,
1543 const MagickPixelPacket *q,MagickPixelPacket *composite)
1544 {
1545 MagickRealType
1546 Da,
1547 gamma,
1548 Sa;
1549
1550 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1551 Da=1.0-QuantumScale*q->opacity;
1552 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1553 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1554 gamma=QuantumRange/(fabs((double) gamma) < MagickEpsilon ? MagickEpsilon :
1555 gamma);
1556 composite->red=gamma*VividLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1557 q->red*Da,Da);
1558 composite->green=gamma*VividLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1559 q->green*Da,Da);
1560 composite->blue=gamma*VividLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1561 q->blue*Da,Da);
1562 if (q->colorspace == CMYKColorspace)
1563 composite->index=gamma*VividLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
1564 q->index*Da,Da);
1565 }
1566
Xor(const MagickRealType Sca,const MagickRealType Sa,const MagickRealType Dca,const MagickRealType Da)1567 static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa,
1568 const MagickRealType Dca,const MagickRealType Da)
1569 {
1570 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
1571 }
1572
CompositeXor(const MagickPixelPacket * p,const MagickPixelPacket * q,MagickPixelPacket * composite)1573 static inline void CompositeXor(const MagickPixelPacket *p,
1574 const MagickPixelPacket *q,MagickPixelPacket *composite)
1575 {
1576 MagickRealType
1577 Da,
1578 gamma,
1579 Sa;
1580
1581 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1582 Da=1.0-QuantumScale*q->opacity;
1583 gamma=Sa+Da-2*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */
1584 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1585 gamma=PerceptibleReciprocal(gamma);
1586 composite->red=gamma*Xor(p->red*Sa,Sa,q->red*Da,Da);
1587 composite->green=gamma*Xor(p->green*Sa,Sa,q->green*Da,Da);
1588 composite->blue=gamma*Xor(p->blue*Sa,Sa,q->blue*Da,Da);
1589 if (q->colorspace == CMYKColorspace)
1590 composite->index=gamma*Xor(p->index*Sa,Sa,q->index*Da,Da);
1591 }
1592
CompositeImage(Image * image,const CompositeOperator compose,const Image * source_image,const ssize_t x_offset,const ssize_t y_offset)1593 MagickExport MagickBooleanType CompositeImage(Image *image,
1594 const CompositeOperator compose,const Image *source_image,
1595 const ssize_t x_offset,const ssize_t y_offset)
1596 {
1597 MagickBooleanType
1598 status;
1599
1600 status=CompositeImageChannel(image,DefaultChannels,compose,source_image,
1601 x_offset,y_offset);
1602 return(status);
1603 }
1604
CompositeImageChannel(Image * image,const ChannelType channel,const CompositeOperator compose,const Image * composite,const ssize_t x_offset,const ssize_t y_offset)1605 MagickExport MagickBooleanType CompositeImageChannel(Image *image,
1606 const ChannelType channel,const CompositeOperator compose,
1607 const Image *composite,const ssize_t x_offset,const ssize_t y_offset)
1608 {
1609 #define CompositeImageTag "Composite/Image"
1610
1611 CacheView
1612 *source_view,
1613 *image_view;
1614
1615 const char
1616 *value;
1617
1618 ExceptionInfo
1619 *exception;
1620
1621 GeometryInfo
1622 geometry_info;
1623
1624 Image
1625 *canvas_image,
1626 *source_image;
1627
1628 MagickBooleanType
1629 clamp,
1630 clip_to_self,
1631 status;
1632
1633 MagickOffsetType
1634 progress;
1635
1636 MagickPixelPacket
1637 zero;
1638
1639 MagickRealType
1640 amount,
1641 canvas_dissolve,
1642 midpoint,
1643 percent_luma,
1644 percent_chroma,
1645 source_dissolve,
1646 threshold;
1647
1648 MagickStatusType
1649 flags;
1650
1651 ssize_t
1652 y;
1653
1654 /*
1655 Prepare composite image.
1656 */
1657 assert(image != (Image *) NULL);
1658 assert(image->signature == MagickCoreSignature);
1659 if (image->debug != MagickFalse)
1660 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1661 assert(composite != (Image *) NULL);
1662 assert(composite->signature == MagickCoreSignature);
1663 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1664 return(MagickFalse);
1665 exception=(&image->exception);
1666 source_image=CloneImage(composite,0,0,MagickTrue,exception);
1667 if (source_image == (const Image *) NULL)
1668 return(MagickFalse);
1669 (void) SetImageColorspace(source_image,image->colorspace);
1670 GetMagickPixelPacket(image,&zero);
1671 canvas_image=(Image *) NULL;
1672 amount=0.5;
1673 canvas_dissolve=1.0;
1674 clip_to_self=MagickTrue;
1675 percent_luma=100.0;
1676 percent_chroma=100.0;
1677 source_dissolve=1.0;
1678 threshold=0.05f;
1679 switch (compose)
1680 {
1681 case ClearCompositeOp:
1682 case SrcCompositeOp:
1683 case InCompositeOp:
1684 case SrcInCompositeOp:
1685 case OutCompositeOp:
1686 case SrcOutCompositeOp:
1687 case DstInCompositeOp:
1688 case DstAtopCompositeOp:
1689 {
1690 /*
1691 Modify canvas outside the overlaid region.
1692 */
1693 clip_to_self=MagickFalse;
1694 break;
1695 }
1696 case OverCompositeOp:
1697 {
1698 if (image->matte != MagickFalse)
1699 break;
1700 if (source_image->matte != MagickFalse)
1701 break;
1702 }
1703 case CopyCompositeOp:
1704 {
1705 if ((x_offset < 0) || (y_offset < 0))
1706 break;
1707 if ((x_offset+(ssize_t) source_image->columns) >= (ssize_t) image->columns)
1708 break;
1709 if ((y_offset+(ssize_t) source_image->rows) >= (ssize_t) image->rows)
1710 break;
1711 status=MagickTrue;
1712 source_view=AcquireVirtualCacheView(source_image,exception);
1713 image_view=AcquireAuthenticCacheView(image,exception);
1714 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1715 #pragma omp parallel for schedule(static) shared(status) \
1716 magick_number_threads(source_image,image,source_image->rows,1)
1717 #endif
1718 for (y=0; y < (ssize_t) source_image->rows; y++)
1719 {
1720 MagickBooleanType
1721 sync;
1722
1723 const IndexPacket
1724 *source_indexes;
1725
1726 const PixelPacket
1727 *p;
1728
1729 IndexPacket
1730 *indexes;
1731
1732 PixelPacket
1733 *q;
1734
1735 if (status == MagickFalse)
1736 continue;
1737 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
1738 1,exception);
1739 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
1740 source_image->columns,1,exception);
1741 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1742 {
1743 status=MagickFalse;
1744 continue;
1745 }
1746 source_indexes=GetCacheViewVirtualIndexQueue(source_view);
1747 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1748 (void) memcpy(q,p,source_image->columns*sizeof(*p));
1749 if ((indexes != (IndexPacket *) NULL) &&
1750 (source_indexes != (const IndexPacket *) NULL))
1751 (void) memcpy(indexes,source_indexes,
1752 source_image->columns*sizeof(*indexes));
1753 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1754 if (sync == MagickFalse)
1755 status=MagickFalse;
1756 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1757 {
1758 MagickBooleanType
1759 proceed;
1760
1761 proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType)
1762 y,image->rows);
1763 if (proceed == MagickFalse)
1764 status=MagickFalse;
1765 }
1766 }
1767 source_view=DestroyCacheView(source_view);
1768 image_view=DestroyCacheView(image_view);
1769 source_image=DestroyImage(source_image);
1770 return(status);
1771 }
1772 case CopyOpacityCompositeOp:
1773 case ChangeMaskCompositeOp:
1774 {
1775 /*
1776 Modify canvas outside the overlaid region and require an alpha
1777 channel to exist, to add transparency.
1778 */
1779 if (image->matte == MagickFalse)
1780 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
1781 clip_to_self=MagickFalse;
1782 break;
1783 }
1784 case BlurCompositeOp:
1785 {
1786 CacheView
1787 *canvas_view,
1788 *source_view;
1789
1790 MagickPixelPacket
1791 pixel;
1792
1793 MagickRealType
1794 angle_range,
1795 angle_start,
1796 height,
1797 width;
1798
1799 ResampleFilter
1800 *resample_filter;
1801
1802 SegmentInfo
1803 blur;
1804
1805 /*
1806 Blur Image by resampling.
1807
1808 Blur Image dictated by an overlay gradient map: X = red_channel;
1809 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
1810 */
1811 canvas_image=CloneImage(image,0,0,MagickTrue,exception);
1812 if (canvas_image == (Image *) NULL)
1813 {
1814 source_image=DestroyImage(source_image);
1815 return(MagickFalse);
1816 }
1817 /*
1818 Gather the maximum blur sigma values from user.
1819 */
1820 SetGeometryInfo(&geometry_info);
1821 flags=NoValue;
1822 value=GetImageArtifact(image,"compose:args");
1823 if (value != (char *) NULL)
1824 flags=ParseGeometry(value,&geometry_info);
1825 if ((flags & WidthValue) == 0)
1826 {
1827 (void) ThrowMagickException(exception,GetMagickModule(),
1828 OptionWarning,"InvalidGeometry","'%s' '%s'","compose:args",value);
1829 source_image=DestroyImage(source_image);
1830 canvas_image=DestroyImage(canvas_image);
1831 return(MagickFalse);
1832 }
1833 /*
1834 Users input sigma now needs to be converted to the EWA ellipse size.
1835 The filter defaults to a sigma of 0.5 so to make this match the
1836 users input the ellipse size needs to be doubled.
1837 */
1838 width=height=geometry_info.rho*2.0;
1839 if ((flags & HeightValue) != 0 )
1840 height=geometry_info.sigma*2.0;
1841
1842 /* default the unrotated ellipse width and height axis vectors */
1843 blur.x1=width;
1844 blur.x2=0.0;
1845 blur.y1=0.0;
1846 blur.y2=height;
1847 /* rotate vectors if a rotation angle is given */
1848 if ((flags & XValue) != 0 )
1849 {
1850 MagickRealType
1851 angle;
1852
1853 angle=DegreesToRadians(geometry_info.xi);
1854 blur.x1=width*cos(angle);
1855 blur.x2=width*sin(angle);
1856 blur.y1=(-height*sin(angle));
1857 blur.y2=height*cos(angle);
1858 }
1859 /* Otherwise lets set a angle range and calculate in the loop */
1860 angle_start=0.0;
1861 angle_range=0.0;
1862 if ((flags & YValue) != 0 )
1863 {
1864 angle_start=DegreesToRadians(geometry_info.xi);
1865 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
1866 }
1867 /*
1868 Set up a gaussian cylindrical filter for EWA Bluring.
1869
1870 As the minimum ellipse radius of support*1.0 the EWA algorithm
1871 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
1872 This means that even 'No Blur' will be still a little blurry!
1873
1874 The solution (as well as the problem of preventing any user
1875 expert filter settings, is to set our own user settings, then
1876 restore them afterwards.
1877 */
1878 resample_filter=AcquireResampleFilter(image,exception);
1879 SetResampleFilter(resample_filter,GaussianFilter,1.0);
1880
1881 /* do the variable blurring of each pixel in image */
1882 pixel=zero;
1883 source_view=AcquireVirtualCacheView(source_image,exception);
1884 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
1885 for (y=0; y < (ssize_t) source_image->rows; y++)
1886 {
1887 MagickBooleanType
1888 sync;
1889
1890 const PixelPacket
1891 *magick_restrict p;
1892
1893 PixelPacket
1894 *magick_restrict r;
1895
1896 IndexPacket
1897 *magick_restrict canvas_indexes;
1898
1899 ssize_t
1900 x;
1901
1902 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1903 continue;
1904 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
1905 1,exception);
1906 r=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,
1907 1,exception);
1908 if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
1909 break;
1910 canvas_indexes=GetCacheViewAuthenticIndexQueue(canvas_view);
1911 for (x=0; x < (ssize_t) source_image->columns; x++)
1912 {
1913 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1914 {
1915 p++;
1916 continue;
1917 }
1918 if (fabs((double) angle_range) > MagickEpsilon)
1919 {
1920 MagickRealType
1921 angle;
1922
1923 angle=angle_start+angle_range*QuantumScale*GetPixelBlue(p);
1924 blur.x1=width*cos(angle);
1925 blur.x2=width*sin(angle);
1926 blur.y1=(-height*sin(angle));
1927 blur.y2=height*cos(angle);
1928 }
1929 #if 0
1930 if ( x == 10 && y == 60 ) {
1931 fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",
1932 blur.x1, blur.x2, blur.y1, blur.y2);
1933 fprintf(stderr, "scaled by=%lf,%lf\n",
1934 QuantumScale*GetPixelRed(p), QuantumScale*GetPixelGreen(p));
1935 }
1936 #endif
1937 ScaleResampleFilter(resample_filter,
1938 blur.x1*QuantumScale*GetPixelRed(p),
1939 blur.y1*QuantumScale*GetPixelGreen(p),
1940 blur.x2*QuantumScale*GetPixelRed(p),
1941 blur.y2*QuantumScale*GetPixelGreen(p));
1942 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,(double)
1943 y_offset+y,&pixel);
1944 SetPixelPacket(canvas_image,&pixel,r,canvas_indexes+x);
1945 p++;
1946 r++;
1947 }
1948 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
1949 if (sync == MagickFalse)
1950 break;
1951 }
1952 resample_filter=DestroyResampleFilter(resample_filter);
1953 source_view=DestroyCacheView(source_view);
1954 canvas_view=DestroyCacheView(canvas_view);
1955 source_image=DestroyImage(source_image);
1956 source_image=canvas_image;
1957 break;
1958 }
1959 case DisplaceCompositeOp:
1960 case DistortCompositeOp:
1961 {
1962 CacheView
1963 *canvas_view,
1964 *source_view,
1965 *image_view;
1966
1967 MagickPixelPacket
1968 pixel;
1969
1970 MagickRealType
1971 horizontal_scale,
1972 vertical_scale;
1973
1974 PointInfo
1975 center,
1976 offset;
1977
1978 IndexPacket
1979 *magick_restrict canvas_indexes;
1980
1981 PixelPacket
1982 *magick_restrict r;
1983
1984 /*
1985 Displace/Distort based on overlay gradient map:
1986 X = red_channel; Y = green_channel;
1987 compose:args = x_scale[,y_scale[,center.x,center.y]]
1988 */
1989 canvas_image=CloneImage(image,0,0,MagickTrue,exception);
1990 if (canvas_image == (Image *) NULL)
1991 {
1992 source_image=DestroyImage(source_image);
1993 return(MagickFalse);
1994 }
1995 SetGeometryInfo(&geometry_info);
1996 flags=NoValue;
1997 value=GetImageArtifact(image,"compose:args");
1998 if (value != (char *) NULL)
1999 flags=ParseGeometry(value,&geometry_info);
2000 if ((flags & (WidthValue | HeightValue)) == 0 )
2001 {
2002 if ((flags & AspectValue) == 0)
2003 {
2004 horizontal_scale=(MagickRealType) (source_image->columns-1)/2.0;
2005 vertical_scale=(MagickRealType) (source_image->rows-1)/2.0;
2006 }
2007 else
2008 {
2009 horizontal_scale=(MagickRealType) (image->columns-1)/2.0;
2010 vertical_scale=(MagickRealType) (image->rows-1)/2.0;
2011 }
2012 }
2013 else
2014 {
2015 horizontal_scale=geometry_info.rho;
2016 vertical_scale=geometry_info.sigma;
2017 if ((flags & PercentValue) != 0)
2018 {
2019 if ((flags & AspectValue) == 0)
2020 {
2021 horizontal_scale*=(source_image->columns-1)/200.0;
2022 vertical_scale*=(source_image->rows-1)/200.0;
2023 }
2024 else
2025 {
2026 horizontal_scale*=(image->columns-1)/200.0;
2027 vertical_scale*=(image->rows-1)/200.0;
2028 }
2029 }
2030 if ((flags & HeightValue) == 0)
2031 vertical_scale=horizontal_scale;
2032 }
2033 /*
2034 Determine fixed center point for absolute distortion map
2035 Absolute distort ==
2036 Displace offset relative to a fixed absolute point
2037 Select that point according to +X+Y user inputs.
2038 default = center of overlay image
2039 arg flag '!' = locations/percentage relative to background image
2040 */
2041 center.x=(MagickRealType) x_offset;
2042 center.y=(MagickRealType) y_offset;
2043 if (compose == DistortCompositeOp)
2044 {
2045 if ((flags & XValue) == 0)
2046 if ((flags & AspectValue) != 0)
2047 center.x=((MagickRealType) image->columns-1)/2.0;
2048 else
2049 center.x=(MagickRealType) (x_offset+(source_image->columns-1)/
2050 2.0);
2051 else
2052 if ((flags & AspectValue) == 0)
2053 center.x=(MagickRealType) (x_offset+geometry_info.xi);
2054 else
2055 center.x=geometry_info.xi;
2056 if ((flags & YValue) == 0)
2057 if ((flags & AspectValue) != 0)
2058 center.y=((MagickRealType) image->rows-1)/2.0;
2059 else
2060 center.y=(MagickRealType) (y_offset+(source_image->rows-1)/2.0);
2061 else
2062 if ((flags & AspectValue) != 0)
2063 center.y=geometry_info.psi;
2064 else
2065 center.y=(MagickRealType) (y_offset+geometry_info.psi);
2066 }
2067 /*
2068 Shift the pixel offset point as defined by the provided,
2069 displacement/distortion map. -- Like a lens...
2070 */
2071 pixel=zero;
2072 image_view=AcquireVirtualCacheView(image,exception);
2073 source_view=AcquireVirtualCacheView(source_image,exception);
2074 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
2075 for (y=0; y < (ssize_t) source_image->rows; y++)
2076 {
2077 MagickBooleanType
2078 sync;
2079
2080 const PixelPacket
2081 *magick_restrict p;
2082
2083 ssize_t
2084 x;
2085
2086 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
2087 continue;
2088 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
2089 1,exception);
2090 r=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,
2091 1,exception);
2092 if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
2093 break;
2094 canvas_indexes=GetCacheViewAuthenticIndexQueue(canvas_view);
2095 for (x=0; x < (ssize_t) source_image->columns; x++)
2096 {
2097 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
2098 {
2099 p++;
2100 continue;
2101 }
2102 /*
2103 Displace the offset.
2104 */
2105 offset.x=(double) ((horizontal_scale*(GetPixelRed(p)-
2106 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2107 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
2108 x : 0));
2109 offset.y=(double) ((vertical_scale*(GetPixelGreen(p)-
2110 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2111 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
2112 y : 0));
2113 status=InterpolateMagickPixelPacket(image,image_view,
2114 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
2115 &pixel,exception);
2116 if (status == MagickFalse)
2117 break;
2118 /*
2119 Mask with the 'invalid pixel mask' in alpha channel.
2120 */
2121 pixel.opacity=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale*
2122 pixel.opacity)*(1.0-QuantumScale*GetPixelOpacity(p)));
2123 SetPixelPacket(canvas_image,&pixel,r,canvas_indexes+x);
2124 p++;
2125 r++;
2126 }
2127 if (x < (ssize_t) source_image->columns)
2128 break;
2129 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
2130 if (sync == MagickFalse)
2131 break;
2132 }
2133 canvas_view=DestroyCacheView(canvas_view);
2134 source_view=DestroyCacheView(source_view);
2135 image_view=DestroyCacheView(image_view);
2136 source_image=DestroyImage(source_image);
2137 source_image=canvas_image;
2138 break;
2139 }
2140 case DissolveCompositeOp:
2141 {
2142 /*
2143 Geometry arguments to dissolve factors.
2144 */
2145 value=GetImageArtifact(image,"compose:args");
2146 if (value != (char *) NULL)
2147 {
2148 flags=ParseGeometry(value,&geometry_info);
2149 source_dissolve=geometry_info.rho/100.0;
2150 canvas_dissolve=1.0;
2151 if ((source_dissolve-MagickEpsilon) < 0.0)
2152 source_dissolve=0.0;
2153 if ((source_dissolve+MagickEpsilon) > 1.0)
2154 {
2155 canvas_dissolve=2.0-source_dissolve;
2156 source_dissolve=1.0;
2157 }
2158 if ((flags & SigmaValue) != 0)
2159 canvas_dissolve=geometry_info.sigma/100.0;
2160 if ((canvas_dissolve-MagickEpsilon) < 0.0)
2161 canvas_dissolve=0.0;
2162 clip_to_self=MagickFalse;
2163 if ((canvas_dissolve+MagickEpsilon) > 1.0 )
2164 {
2165 canvas_dissolve=1.0;
2166 clip_to_self=MagickTrue;
2167 }
2168 }
2169 break;
2170 }
2171 case BlendCompositeOp:
2172 {
2173 value=GetImageArtifact(image,"compose:args");
2174 if (value != (char *) NULL)
2175 {
2176 flags=ParseGeometry(value,&geometry_info);
2177 source_dissolve=geometry_info.rho/100.0;
2178 canvas_dissolve=1.0-source_dissolve;
2179 if ((flags & SigmaValue) != 0)
2180 canvas_dissolve=geometry_info.sigma/100.0;
2181 clip_to_self=MagickFalse;
2182 if ((canvas_dissolve+MagickEpsilon) > 1.0)
2183 clip_to_self=MagickTrue;
2184 }
2185 break;
2186 }
2187 case MathematicsCompositeOp:
2188 {
2189 /*
2190 Just collect the values from "compose:args", setting.
2191 Unused values are set to zero automagically.
2192
2193 Arguments are normally a comma separated list, so this probably should
2194 be changed to some 'general comma list' parser, (with a minimum
2195 number of values)
2196 */
2197 SetGeometryInfo(&geometry_info);
2198 value=GetImageArtifact(image,"compose:args");
2199 if (value != (char *) NULL)
2200 {
2201 flags=ParseGeometry(value,&geometry_info);
2202 if (flags == NoValue)
2203 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
2204 "InvalidGeometry","`%s'",value);
2205 }
2206 break;
2207 }
2208 case ModulateCompositeOp:
2209 {
2210 /*
2211 Determine the luma and chroma scale.
2212 */
2213 value=GetImageArtifact(image,"compose:args");
2214 if (value != (char *) NULL)
2215 {
2216 flags=ParseGeometry(value,&geometry_info);
2217 percent_luma=geometry_info.rho;
2218 if ((flags & SigmaValue) != 0)
2219 percent_chroma=geometry_info.sigma;
2220 }
2221 break;
2222 }
2223 case ThresholdCompositeOp:
2224 {
2225 /*
2226 Determine the amount and threshold.
2227 This Composition method is deprecated
2228 */
2229 value=GetImageArtifact(image,"compose:args");
2230 if (value != (char *) NULL)
2231 {
2232 flags=ParseGeometry(value,&geometry_info);
2233 amount=geometry_info.rho;
2234 threshold=geometry_info.sigma;
2235 if ((flags & SigmaValue) == 0)
2236 threshold=0.05f;
2237 }
2238 threshold*=QuantumRange;
2239 break;
2240 }
2241 default:
2242 break;
2243 }
2244 value=GetImageArtifact(image,"compose:outside-overlay");
2245 if (value != (const char *) NULL)
2246 clip_to_self=IsMagickTrue(value) == MagickFalse ? MagickTrue : MagickFalse;
2247 value=GetImageArtifact(image,"compose:clip-to-self");
2248 if (value != (const char *) NULL)
2249 clip_to_self=IsMagickTrue(value) != MagickFalse ? MagickTrue : MagickFalse;
2250 clamp=MagickTrue;
2251 value=GetImageArtifact(image,"compose:clamp");
2252 if (value != (const char *) NULL)
2253 clamp=IsMagickTrue(value);
2254 /*
2255 Composite image.
2256 */
2257 #if defined(MAGICKCORE_OPENCL_SUPPORT)
2258 status=AccelerateCompositeImage(image,channel,compose,source_image,
2259 x_offset,y_offset,canvas_dissolve,source_dissolve,exception);
2260 if (status != MagickFalse)
2261 return(status);
2262 #endif
2263 status=MagickTrue;
2264 progress=0;
2265 midpoint=((MagickRealType) QuantumRange+1.0)/2;
2266 GetMagickPixelPacket(source_image,&zero);
2267 source_view=AcquireVirtualCacheView(source_image,exception);
2268 image_view=AcquireAuthenticCacheView(image,exception);
2269 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2270 #pragma omp parallel for schedule(static) shared(progress,status) \
2271 magick_number_threads(source_image,image,image->rows,1)
2272 #endif
2273 for (y=0; y < (ssize_t) image->rows; y++)
2274 {
2275 const PixelPacket
2276 *pixels;
2277
2278 double
2279 luma,
2280 hue,
2281 chroma,
2282 sans;
2283
2284 MagickPixelPacket
2285 composite,
2286 canvas,
2287 source;
2288
2289 const IndexPacket
2290 *magick_restrict source_indexes;
2291
2292 const PixelPacket
2293 *magick_restrict p;
2294
2295 IndexPacket
2296 *magick_restrict indexes;
2297
2298 ssize_t
2299 x;
2300
2301 PixelPacket
2302 *magick_restrict q;
2303
2304 if (status == MagickFalse)
2305 continue;
2306 if (clip_to_self != MagickFalse)
2307 {
2308 if (y < y_offset)
2309 continue;
2310 if ((y-y_offset) >= (ssize_t) source_image->rows)
2311 continue;
2312 }
2313 /*
2314 If pixels is NULL, y is outside overlay region.
2315 */
2316 pixels=(PixelPacket *) NULL;
2317 p=(PixelPacket *) NULL;
2318 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
2319 {
2320 p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
2321 source_image->columns,1,exception);
2322 if (p == (const PixelPacket *) NULL)
2323 {
2324 status=MagickFalse;
2325 continue;
2326 }
2327 pixels=p;
2328 if (x_offset < 0)
2329 p-=x_offset;
2330 }
2331 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2332 if (q == (PixelPacket *) NULL)
2333 {
2334 status=MagickFalse;
2335 continue;
2336 }
2337 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2338 source_indexes=GetCacheViewVirtualIndexQueue(source_view);
2339 GetMagickPixelPacket(source_image,&source);
2340 GetMagickPixelPacket(image,&canvas);
2341 hue=0.0;
2342 chroma=0.0;
2343 luma=0.0;
2344 for (x=0; x < (ssize_t) image->columns; x++)
2345 {
2346 if (clip_to_self != MagickFalse)
2347 {
2348 if (x < x_offset)
2349 {
2350 q++;
2351 continue;
2352 }
2353 if ((x-x_offset) >= (ssize_t) source_image->columns)
2354 break;
2355 }
2356 canvas.red=(MagickRealType) GetPixelRed(q);
2357 canvas.green=(MagickRealType) GetPixelGreen(q);
2358 canvas.blue=(MagickRealType) GetPixelBlue(q);
2359 if (image->matte != MagickFalse)
2360 canvas.opacity=(MagickRealType) GetPixelOpacity(q);
2361 if (image->colorspace == CMYKColorspace)
2362 canvas.index=(MagickRealType) GetPixelIndex(indexes+x);
2363 if (image->colorspace == CMYKColorspace)
2364 {
2365 canvas.red=(MagickRealType) QuantumRange-canvas.red;
2366 canvas.green=(MagickRealType) QuantumRange-canvas.green;
2367 canvas.blue=(MagickRealType) QuantumRange-canvas.blue;
2368 canvas.index=(MagickRealType) QuantumRange-canvas.index;
2369 }
2370 /*
2371 Handle canvas modifications outside overlaid region.
2372 */
2373 composite=canvas;
2374 if ((pixels == (PixelPacket *) NULL) || (x < x_offset) ||
2375 ((x-x_offset) >= (ssize_t) source_image->columns))
2376 {
2377 switch (compose)
2378 {
2379 case DissolveCompositeOp:
2380 case BlendCompositeOp:
2381 {
2382 composite.opacity=(MagickRealType) (QuantumRange-canvas_dissolve*
2383 (QuantumRange-composite.opacity));
2384 break;
2385 }
2386 case ClearCompositeOp:
2387 case SrcCompositeOp:
2388 {
2389 CompositeClear(&canvas,&composite);
2390 break;
2391 }
2392 case InCompositeOp:
2393 case SrcInCompositeOp:
2394 case OutCompositeOp:
2395 case SrcOutCompositeOp:
2396 case DstInCompositeOp:
2397 case DstAtopCompositeOp:
2398 case CopyOpacityCompositeOp:
2399 case ChangeMaskCompositeOp:
2400 {
2401 composite.opacity=(MagickRealType) TransparentOpacity;
2402 break;
2403 }
2404 default:
2405 {
2406 (void) GetOneVirtualMagickPixel(source_image,x-x_offset,
2407 y-y_offset,&composite,exception);
2408 break;
2409 }
2410 }
2411 if (image->colorspace == CMYKColorspace)
2412 {
2413 composite.red=(MagickRealType) QuantumRange-composite.red;
2414 composite.green=(MagickRealType) QuantumRange-composite.green;
2415 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2416 composite.index=(MagickRealType) QuantumRange-composite.index;
2417 }
2418 SetPixelRed(q,clamp != MagickFalse ?
2419 ClampPixel(composite.red) : ClampToQuantum(composite.red));
2420 SetPixelGreen(q,clamp != MagickFalse ?
2421 ClampPixel(composite.green) : ClampToQuantum(composite.green));
2422 SetPixelBlue(q,clamp != MagickFalse ?
2423 ClampPixel(composite.blue) : ClampToQuantum(composite.blue));
2424 if (image->matte != MagickFalse)
2425 SetPixelOpacity(q,clamp != MagickFalse ?
2426 ClampPixel(composite.opacity) :
2427 ClampToQuantum(composite.opacity));
2428 if (image->colorspace == CMYKColorspace)
2429 SetPixelIndex(indexes+x,clamp != MagickFalse ?
2430 ClampPixel(composite.index) : ClampToQuantum(composite.index));
2431 q++;
2432 continue;
2433 }
2434 /*
2435 Handle normal overlay of source onto canvas.
2436 */
2437 source.red=(MagickRealType) GetPixelRed(p);
2438 source.green=(MagickRealType) GetPixelGreen(p);
2439 source.blue=(MagickRealType) GetPixelBlue(p);
2440 if (source_image->matte != MagickFalse)
2441 source.opacity=(MagickRealType) GetPixelOpacity(p);
2442 if (source_image->colorspace == CMYKColorspace)
2443 source.index=(MagickRealType) GetPixelIndex(source_indexes+
2444 x-x_offset);
2445 if (source_image->colorspace == CMYKColorspace)
2446 {
2447 source.red=(MagickRealType) QuantumRange-source.red;
2448 source.green=(MagickRealType) QuantumRange-source.green;
2449 source.blue=(MagickRealType) QuantumRange-source.blue;
2450 source.index=(MagickRealType) QuantumRange-source.index;
2451 }
2452 switch (compose)
2453 {
2454 /* Duff-Porter Compositions */
2455 case ClearCompositeOp:
2456 {
2457 CompositeClear(&canvas,&composite);
2458 break;
2459 }
2460 case SrcCompositeOp:
2461 case CopyCompositeOp:
2462 case ReplaceCompositeOp:
2463 {
2464 composite=source;
2465 break;
2466 }
2467 case NoCompositeOp:
2468 case DstCompositeOp:
2469 break;
2470 case OverCompositeOp:
2471 case SrcOverCompositeOp:
2472 {
2473 MagickPixelCompositeOver(&source,source.opacity,&canvas,
2474 canvas.opacity,&composite);
2475 break;
2476 }
2477 case DstOverCompositeOp:
2478 {
2479 MagickPixelCompositeOver(&canvas,canvas.opacity,&source,
2480 source.opacity,&composite);
2481 break;
2482 }
2483 case SrcInCompositeOp:
2484 case InCompositeOp:
2485 {
2486 CompositeIn(&source,&canvas,&composite);
2487 break;
2488 }
2489 case DstInCompositeOp:
2490 {
2491 CompositeIn(&canvas,&source,&composite);
2492 break;
2493 }
2494 case OutCompositeOp:
2495 case SrcOutCompositeOp:
2496 {
2497 CompositeOut(&source,&canvas,&composite);
2498 break;
2499 }
2500 case DstOutCompositeOp:
2501 {
2502 CompositeOut(&canvas,&source,&composite);
2503 break;
2504 }
2505 case AtopCompositeOp:
2506 case SrcAtopCompositeOp:
2507 {
2508 CompositeAtop(&source,&canvas,&composite);
2509 break;
2510 }
2511 case DstAtopCompositeOp:
2512 {
2513 CompositeAtop(&canvas,&source,&composite);
2514 break;
2515 }
2516 case XorCompositeOp:
2517 {
2518 CompositeXor(&source,&canvas,&composite);
2519 break;
2520 }
2521 /* Mathematical Compositions */
2522 case PlusCompositeOp:
2523 {
2524 CompositePlus(&source,&canvas,channel,&composite);
2525 break;
2526 }
2527 case MinusDstCompositeOp:
2528 {
2529 CompositeMinus(&source,&canvas,channel,&composite);
2530 break;
2531 }
2532 case MinusSrcCompositeOp:
2533 {
2534 CompositeMinus(&canvas,&source,channel,&composite);
2535 break;
2536 }
2537 case ModulusAddCompositeOp:
2538 {
2539 CompositeModulusAdd(&source,&canvas,channel,&composite);
2540 break;
2541 }
2542 case ModulusSubtractCompositeOp:
2543 {
2544 CompositeModulusSubtract(&source,&canvas,channel,&composite);
2545 break;
2546 }
2547 case DifferenceCompositeOp:
2548 {
2549 CompositeDifference(&source,&canvas,channel,&composite);
2550 break;
2551 }
2552 case ExclusionCompositeOp:
2553 {
2554 CompositeExclusion(&source,&canvas,channel,&composite);
2555 break;
2556 }
2557 case MultiplyCompositeOp:
2558 {
2559 CompositeMultiply(&source,&canvas,channel,&composite);
2560 break;
2561 }
2562 case ScreenCompositeOp:
2563 {
2564 CompositeScreen(&source,&canvas,channel,&composite);
2565 break;
2566 }
2567 case DivideDstCompositeOp:
2568 {
2569 CompositeDivide(&source,&canvas,channel,&composite);
2570 break;
2571 }
2572 case DivideSrcCompositeOp:
2573 {
2574 CompositeDivide(&canvas,&source,channel,&composite);
2575 break;
2576 }
2577 case DarkenCompositeOp:
2578 {
2579 CompositeDarken(&source,&canvas,channel,&composite);
2580 break;
2581 }
2582 case LightenCompositeOp:
2583 {
2584 CompositeLighten(&source,&canvas,channel,&composite);
2585 break;
2586 }
2587 case DarkenIntensityCompositeOp:
2588 {
2589 CompositeDarkenIntensity(&source,&canvas,channel,&composite);
2590 break;
2591 }
2592 case LightenIntensityCompositeOp:
2593 {
2594 CompositeLightenIntensity(&source,&canvas,channel,&composite);
2595 break;
2596 }
2597 case MathematicsCompositeOp:
2598 {
2599 CompositeMathematics(&source,&canvas,channel,&geometry_info,
2600 &composite);
2601 break;
2602 }
2603 /* Lighting Compositions */
2604 case ColorDodgeCompositeOp:
2605 {
2606 CompositeColorDodge(&source,&canvas,&composite);
2607 break;
2608 }
2609 case ColorBurnCompositeOp:
2610 {
2611 CompositeColorBurn(&source,&canvas,&composite);
2612 break;
2613 }
2614 case LinearDodgeCompositeOp:
2615 {
2616 CompositeLinearDodge(&source,&canvas,&composite);
2617 break;
2618 }
2619 case LinearBurnCompositeOp:
2620 {
2621 CompositeLinearBurn(&source,&canvas,&composite);
2622 break;
2623 }
2624 case HardLightCompositeOp:
2625 {
2626 CompositeHardLight(&source,&canvas,&composite);
2627 break;
2628 }
2629 case HardMixCompositeOp:
2630 {
2631 CompositeHardMix(&source,&canvas,&composite);
2632 break;
2633 }
2634 case OverlayCompositeOp:
2635 {
2636 /* Overlay = Reversed HardLight. */
2637 CompositeHardLight(&canvas,&source,&composite);
2638 break;
2639 }
2640 case SoftLightCompositeOp:
2641 {
2642 CompositeSoftLight(&source,&canvas,&composite);
2643 break;
2644 }
2645 case LinearLightCompositeOp:
2646 {
2647 CompositeLinearLight(&source,&canvas,&composite);
2648 break;
2649 }
2650 case PegtopLightCompositeOp:
2651 {
2652 CompositePegtopLight(&source,&canvas,&composite);
2653 break;
2654 }
2655 case VividLightCompositeOp:
2656 {
2657 CompositeVividLight(&source,&canvas,&composite);
2658 break;
2659 }
2660 case PinLightCompositeOp:
2661 {
2662 CompositePinLight(&source,&canvas,&composite);
2663 break;
2664 }
2665 /* Other Composition */
2666 case ChangeMaskCompositeOp:
2667 {
2668 if ((composite.opacity > ((MagickRealType) QuantumRange/2.0)) ||
2669 (IsMagickColorSimilar(&source,&canvas) != MagickFalse))
2670 composite.opacity=(MagickRealType) TransparentOpacity;
2671 else
2672 composite.opacity=(MagickRealType) OpaqueOpacity;
2673 break;
2674 }
2675 case BumpmapCompositeOp:
2676 {
2677 if (source.opacity == TransparentOpacity)
2678 break;
2679 CompositeBumpmap(&source,&canvas,&composite);
2680 break;
2681 }
2682 case DissolveCompositeOp:
2683 {
2684 MagickPixelCompositeOver(&source,(MagickRealType) (QuantumRange-
2685 source_dissolve*(QuantumRange-source.opacity)),&canvas,
2686 (MagickRealType) (QuantumRange-canvas_dissolve*(QuantumRange-
2687 canvas.opacity)),&composite);
2688 break;
2689 }
2690 case BlendCompositeOp:
2691 {
2692 MagickPixelCompositeBlend(&source,source_dissolve,&canvas,
2693 canvas_dissolve,&composite);
2694 break;
2695 }
2696 case StereoCompositeOp:
2697 {
2698 composite.red=(MagickRealType) GetPixelRed(p);
2699 composite.opacity=(composite.opacity+canvas.opacity/2);
2700 break;
2701 }
2702 case ThresholdCompositeOp:
2703 {
2704 CompositeThreshold(&source,&canvas,threshold,amount,&composite);
2705 break;
2706 }
2707 case ModulateCompositeOp:
2708 {
2709 ssize_t
2710 offset;
2711
2712 if (source.opacity == TransparentOpacity)
2713 break;
2714 offset=(ssize_t) (MagickPixelIntensityToQuantum(&source)-midpoint);
2715 if (offset == 0)
2716 break;
2717 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2718 &chroma,&luma);
2719 luma+=(0.01*percent_luma*offset)/midpoint;
2720 chroma*=0.01*percent_chroma;
2721 HCLComposite(hue,chroma,luma,&composite.red,&composite.green,
2722 &composite.blue);
2723 break;
2724 }
2725 case HueCompositeOp:
2726 {
2727 if (source.opacity == TransparentOpacity)
2728 break;
2729 if (canvas.opacity == TransparentOpacity)
2730 {
2731 composite=source;
2732 break;
2733 }
2734 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2735 &chroma,&luma);
2736 CompositeHCL(source.red,source.green,source.blue,&hue,&sans,&sans);
2737 HCLComposite(hue,chroma,luma,&composite.red,
2738 &composite.green,&composite.blue);
2739 if (source.opacity < canvas.opacity)
2740 composite.opacity=source.opacity;
2741 break;
2742 }
2743 case SaturateCompositeOp:
2744 {
2745 if (source.opacity == TransparentOpacity)
2746 break;
2747 if (canvas.opacity == TransparentOpacity)
2748 {
2749 composite=source;
2750 break;
2751 }
2752 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2753 &chroma,&luma);
2754 CompositeHCL(source.red,source.green,source.blue,&sans,&chroma,
2755 &sans);
2756 HCLComposite(hue,chroma,luma,&composite.red,
2757 &composite.green,&composite.blue);
2758 if (source.opacity < canvas.opacity)
2759 composite.opacity=source.opacity;
2760 break;
2761 }
2762 case LuminizeCompositeOp:
2763 {
2764 if (source.opacity == TransparentOpacity)
2765 break;
2766 if (canvas.opacity == TransparentOpacity)
2767 {
2768 composite=source;
2769 break;
2770 }
2771 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2772 &chroma,&luma);
2773 CompositeHCL(source.red,source.green,source.blue,&sans,&sans,
2774 &luma);
2775 HCLComposite(hue,chroma,luma,&composite.red,
2776 &composite.green,&composite.blue);
2777 if (source.opacity < canvas.opacity)
2778 composite.opacity=source.opacity;
2779 break;
2780 }
2781 case ColorizeCompositeOp:
2782 {
2783 if (source.opacity == TransparentOpacity)
2784 break;
2785 if (canvas.opacity == TransparentOpacity)
2786 {
2787 composite=source;
2788 break;
2789 }
2790 CompositeHCL(canvas.red,canvas.green,canvas.blue,&sans,
2791 &sans,&luma);
2792 CompositeHCL(source.red,source.green,source.blue,&hue,&chroma,&sans);
2793 HCLComposite(hue,chroma,luma,&composite.red,
2794 &composite.green,&composite.blue);
2795 if (source.opacity < canvas.opacity)
2796 composite.opacity=source.opacity;
2797 break;
2798 }
2799 case CopyRedCompositeOp:
2800 case CopyCyanCompositeOp:
2801 {
2802 composite.red=source.red;
2803 break;
2804 }
2805 case CopyGreenCompositeOp:
2806 case CopyMagentaCompositeOp:
2807 {
2808 composite.green=source.green;
2809 break;
2810 }
2811 case CopyBlueCompositeOp:
2812 case CopyYellowCompositeOp:
2813 {
2814 composite.blue=source.blue;
2815 break;
2816 }
2817 case CopyOpacityCompositeOp:
2818 {
2819 if (source.matte == MagickFalse)
2820 composite.opacity=(MagickRealType) (QuantumRange-
2821 MagickPixelIntensityToQuantum(&source));
2822 else
2823 composite.opacity=source.opacity;
2824 break;
2825 }
2826 case CopyBlackCompositeOp:
2827 {
2828 if (source.colorspace != CMYKColorspace)
2829 ConvertRGBToCMYK(&source);
2830 composite.index=source.index;
2831 break;
2832 }
2833 /* compose methods that are already handled */
2834 case BlurCompositeOp:
2835 case DisplaceCompositeOp:
2836 case DistortCompositeOp:
2837 {
2838 composite=source;
2839 break;
2840 }
2841 default:
2842 break;
2843 }
2844 if (image->colorspace == CMYKColorspace)
2845 {
2846 composite.red=(MagickRealType) QuantumRange-composite.red;
2847 composite.green=(MagickRealType) QuantumRange-composite.green;
2848 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2849 composite.index=(MagickRealType) QuantumRange-composite.index;
2850 }
2851 SetPixelRed(q,clamp != MagickFalse ?
2852 ClampPixel(composite.red) : ClampToQuantum(composite.red));
2853 SetPixelGreen(q,clamp != MagickFalse ?
2854 ClampPixel(composite.green) : ClampToQuantum(composite.green));
2855 SetPixelBlue(q,clamp != MagickFalse ?
2856 ClampPixel(composite.blue) : ClampToQuantum(composite.blue));
2857 SetPixelOpacity(q,clamp != MagickFalse ?
2858 ClampPixel(composite.opacity) : ClampToQuantum(composite.opacity));
2859 if (image->colorspace == CMYKColorspace)
2860 SetPixelIndex(indexes+x,clamp != MagickFalse ?
2861 ClampPixel(composite.index) : ClampToQuantum(composite.index));
2862 p++;
2863 if (p >= (pixels+source_image->columns))
2864 p=pixels;
2865 q++;
2866 }
2867 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2868 status=MagickFalse;
2869 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2870 {
2871 MagickBooleanType
2872 proceed;
2873
2874 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2875 #pragma omp atomic
2876 #endif
2877 progress++;
2878 proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows);
2879 if (proceed == MagickFalse)
2880 status=MagickFalse;
2881 }
2882 }
2883 source_view=DestroyCacheView(source_view);
2884 image_view=DestroyCacheView(image_view);
2885 if (canvas_image != (Image * ) NULL)
2886 canvas_image=DestroyImage(canvas_image);
2887 else
2888 source_image=DestroyImage(source_image);
2889 return(status);
2890 }
2891
2892 /*
2893 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2894 % %
2895 % %
2896 % %
2897 % T e x t u r e I m a g e %
2898 % %
2899 % %
2900 % %
2901 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2902 %
2903 % TextureImage() repeatedly tiles the texture image across and down the image
2904 % canvas.
2905 %
2906 % The format of the TextureImage method is:
2907 %
2908 % MagickBooleanType TextureImage(Image *image,const Image *texture)
2909 %
2910 % A description of each parameter follows:
2911 %
2912 % o image: the image.
2913 %
2914 % o texture: This image is the texture to layer on the background.
2915 %
2916 */
TextureImage(Image * image,const Image * texture)2917 MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture)
2918 {
2919 #define TextureImageTag "Texture/Image"
2920
2921 CacheView
2922 *image_view,
2923 *texture_view;
2924
2925 ExceptionInfo
2926 *exception;
2927
2928 Image
2929 *texture_image;
2930
2931 MagickBooleanType
2932 status;
2933
2934 ssize_t
2935 y;
2936
2937 assert(image != (Image *) NULL);
2938 if (image->debug != MagickFalse)
2939 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2940 assert(image->signature == MagickCoreSignature);
2941 if (texture == (const Image *) NULL)
2942 return(MagickFalse);
2943 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2944 return(MagickFalse);
2945 exception=(&image->exception);
2946 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
2947 if (texture_image == (const Image *) NULL)
2948 return(MagickFalse);
2949 (void) TransformImageColorspace(texture_image,image->colorspace);
2950 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod);
2951 status=MagickTrue;
2952 if ((image->compose != CopyCompositeOp) &&
2953 ((image->compose != OverCompositeOp) || (image->matte != MagickFalse) ||
2954 (texture_image->matte != MagickFalse)))
2955 {
2956 /*
2957 Tile texture onto the image background.
2958 */
2959 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
2960 {
2961 ssize_t
2962 x;
2963
2964 if (status == MagickFalse)
2965 continue;
2966 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
2967 {
2968 MagickBooleanType
2969 thread_status;
2970
2971 thread_status=CompositeImage(image,image->compose,texture_image,x+
2972 texture_image->tile_offset.x,y+texture_image->tile_offset.y);
2973 if (thread_status == MagickFalse)
2974 {
2975 status=thread_status;
2976 break;
2977 }
2978 }
2979 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2980 {
2981 MagickBooleanType
2982 proceed;
2983
2984 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2985 y,image->rows);
2986 if (proceed == MagickFalse)
2987 status=MagickFalse;
2988 }
2989 }
2990 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2991 image->rows,image->rows);
2992 texture_image=DestroyImage(texture_image);
2993 return(status);
2994 }
2995 /*
2996 Tile texture onto the image background (optimized).
2997 */
2998 status=MagickTrue;
2999 texture_view=AcquireVirtualCacheView(texture_image,exception);
3000 image_view=AcquireAuthenticCacheView(image,exception);
3001 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3002 #pragma omp parallel for schedule(static) shared(status) \
3003 magick_number_threads(image,texture_image,image->rows,1)
3004 #endif
3005 for (y=0; y < (ssize_t) image->rows; y++)
3006 {
3007 MagickBooleanType
3008 sync;
3009
3010 const IndexPacket
3011 *texture_indexes;
3012
3013 const PixelPacket
3014 *p;
3015
3016 IndexPacket
3017 *indexes;
3018
3019 ssize_t
3020 x;
3021
3022 PixelPacket
3023 *q;
3024
3025 size_t
3026 width;
3027
3028 if (status == MagickFalse)
3029 continue;
3030 p=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,(y+
3031 texture_image->tile_offset.y) % texture_image->rows,
3032 texture_image->columns,1,exception);
3033 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3034 exception);
3035 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3036 {
3037 status=MagickFalse;
3038 continue;
3039 }
3040 texture_indexes=GetCacheViewVirtualIndexQueue(texture_view);
3041 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3042 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
3043 {
3044 width=texture_image->columns;
3045 if ((x+(ssize_t) width) > (ssize_t) image->columns)
3046 width=image->columns-x;
3047 (void) memcpy(q,p,width*sizeof(*p));
3048 if ((image->colorspace == CMYKColorspace) &&
3049 (texture_image->colorspace == CMYKColorspace))
3050 {
3051 (void) memcpy(indexes,texture_indexes,width*
3052 sizeof(*indexes));
3053 indexes+=width;
3054 }
3055 q+=width;
3056 }
3057 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3058 if (sync == MagickFalse)
3059 status=MagickFalse;
3060 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3061 {
3062 MagickBooleanType
3063 proceed;
3064
3065 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
3066 image->rows);
3067 if (proceed == MagickFalse)
3068 status=MagickFalse;
3069 }
3070 }
3071 texture_view=DestroyCacheView(texture_view);
3072 image_view=DestroyCacheView(image_view);
3073 texture_image=DestroyImage(texture_image);
3074 return(status);
3075 }
3076