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