MagickCore 6.9.12-98
Convert, Edit, Or Compose Bitmap Images
Loading...
Searching...
No Matches
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,q->opacity,
1520 threshold,amount);
1521 if (q->colorspace == CMYKColorspace)
1522 composite->index=Threshold(p->index,q->index,threshold,amount);
1523}
1524
1525
1526static MagickRealType VividLight(const MagickRealType Sca,
1527 const MagickRealType Sa, const MagickRealType Dca, const MagickRealType Da)
1528{
1529 /*
1530 VividLight: A Photoshop 7 composition method. See
1531 http://www.simplefilter.de/en/basics/mixmods.html.
1532
1533 f(Sc,Dc) = (2*Sc < 1) ? 1-(1-Dc)/(2*Sc) : Dc/(2*(1-Sc))
1534 */
1535 if ((fabs((double) Sa) < MagickEpsilon) ||
1536 (fabs((double) (Sca-Sa)) < MagickEpsilon))
1537 return(Sa*Da+Sca*(1.0-Da)+Dca*(1.0-Sa));
1538 if ((2*Sca) <= Sa)
1539 return(Sa*(Da+Sa*(Dca-Da)*PerceptibleReciprocal(2.0*Sca))+Sca*(1.0-Da)+
1540 Dca*(1.0-Sa));
1541 return(Dca*Sa*Sa*PerceptibleReciprocal(2.0*(Sa-Sca))+Sca*(1.0-Da)+Dca*
1542 (1.0-Sa));
1543}
1544
1545static inline void CompositeVividLight(const MagickPixelPacket *p,
1546 const MagickPixelPacket *q,MagickPixelPacket *composite)
1547{
1548 MagickRealType
1549 Da,
1550 gamma,
1551 Sa;
1552
1553 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1554 Da=1.0-QuantumScale*q->opacity;
1555 gamma=RoundToUnity(Sa+Da-Sa*Da); /* over blend, as per SVG doc */
1556 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1557 gamma=(MagickRealType) QuantumRange/(fabs((double) gamma) < MagickEpsilon ?
1558 MagickEpsilon : gamma);
1559 composite->red=gamma*VividLight(QuantumScale*p->red*Sa,Sa,QuantumScale*
1560 q->red*Da,Da);
1561 composite->green=gamma*VividLight(QuantumScale*p->green*Sa,Sa,QuantumScale*
1562 q->green*Da,Da);
1563 composite->blue=gamma*VividLight(QuantumScale*p->blue*Sa,Sa,QuantumScale*
1564 q->blue*Da,Da);
1565 if (q->colorspace == CMYKColorspace)
1566 composite->index=gamma*VividLight(QuantumScale*p->index*Sa,Sa,QuantumScale*
1567 q->index*Da,Da);
1568}
1569
1570static MagickRealType Xor(const MagickRealType Sca,const MagickRealType Sa,
1571 const MagickRealType Dca,const MagickRealType Da)
1572{
1573 return(Sca*(1.0-Da)+Dca*(1.0-Sa));
1574}
1575
1576static inline void CompositeXor(const MagickPixelPacket *p,
1577 const MagickPixelPacket *q,MagickPixelPacket *composite)
1578{
1579 MagickRealType
1580 Da,
1581 gamma,
1582 Sa;
1583
1584 Sa=1.0-QuantumScale*p->opacity; /* simplify and speed up equations */
1585 Da=1.0-QuantumScale*q->opacity;
1586 gamma=Sa+Da-2*Sa*Da; /* Xor blend mode X=0,Y=1,Z=1 */
1587 composite->opacity=(MagickRealType) QuantumRange*(1.0-gamma);
1588 gamma=PerceptibleReciprocal(gamma);
1589 composite->red=gamma*Xor(p->red*Sa,Sa,q->red*Da,Da);
1590 composite->green=gamma*Xor(p->green*Sa,Sa,q->green*Da,Da);
1591 composite->blue=gamma*Xor(p->blue*Sa,Sa,q->blue*Da,Da);
1592 if (q->colorspace == CMYKColorspace)
1593 composite->index=gamma*Xor(p->index*Sa,Sa,q->index*Da,Da);
1594}
1595
1596MagickExport MagickBooleanType CompositeImage(Image *image,
1597 const CompositeOperator compose,const Image *source_image,
1598 const ssize_t x_offset,const ssize_t y_offset)
1599{
1600 MagickBooleanType
1601 status;
1602
1603 status=CompositeImageChannel(image,DefaultChannels,compose,source_image,
1604 x_offset,y_offset);
1605 return(status);
1606}
1607
1608MagickExport MagickBooleanType CompositeImageChannel(Image *image,
1609 const ChannelType channel,const CompositeOperator compose,
1610 const Image *composite,const ssize_t x_offset,const ssize_t y_offset)
1611{
1612#define CompositeImageTag "Composite/Image"
1613
1614 CacheView
1615 *source_view,
1616 *image_view;
1617
1618 const char
1619 *value;
1620
1622 *exception;
1623
1625 geometry_info;
1626
1627 Image
1628 *canvas_image,
1629 *source_image;
1630
1631 MagickBooleanType
1632 clamp,
1633 clip_to_self,
1634 status;
1635
1636 MagickOffsetType
1637 progress;
1638
1640 zero;
1641
1642 MagickRealType
1643 amount,
1644 canvas_dissolve,
1645 midpoint,
1646 percent_luma,
1647 percent_chroma,
1648 source_dissolve,
1649 threshold;
1650
1651 MagickStatusType
1652 flags;
1653
1654 ssize_t
1655 y;
1656
1657 /*
1658 Prepare composite image.
1659 */
1660 assert(image != (Image *) NULL);
1661 assert(image->signature == MagickCoreSignature);
1662 assert(composite != (Image *) NULL);
1663 assert(composite->signature == MagickCoreSignature);
1664 if (IsEventLogging() != MagickFalse)
1665 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1666 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
1667 return(MagickFalse);
1668 exception=(&image->exception);
1669 source_image=CloneImage(composite,0,0,MagickTrue,exception);
1670 if (source_image == (const Image *) NULL)
1671 return(MagickFalse);
1672 (void) SetImageColorspace(source_image,image->colorspace);
1673 GetMagickPixelPacket(image,&zero);
1674 canvas_image=(Image *) NULL;
1675 amount=0.5;
1676 canvas_dissolve=1.0;
1677 clip_to_self=MagickTrue;
1678 percent_luma=100.0;
1679 percent_chroma=100.0;
1680 source_dissolve=1.0;
1681 threshold=0.05f;
1682 switch (compose)
1683 {
1684 case ClearCompositeOp:
1685 case SrcCompositeOp:
1686 case InCompositeOp:
1687 case SrcInCompositeOp:
1688 case OutCompositeOp:
1689 case SrcOutCompositeOp:
1690 case DstInCompositeOp:
1691 case DstAtopCompositeOp:
1692 {
1693 /*
1694 Modify canvas outside the overlaid region.
1695 */
1696 clip_to_self=MagickFalse;
1697 break;
1698 }
1699 case OverCompositeOp:
1700 {
1701 if (image->matte != MagickFalse)
1702 break;
1703 if (source_image->matte != MagickFalse)
1704 break;
1705 magick_fallthrough;
1706 }
1707 case CopyCompositeOp:
1708 {
1709 if ((x_offset < 0) || (y_offset < 0))
1710 break;
1711 if ((x_offset+(ssize_t) source_image->columns) >= (ssize_t) image->columns)
1712 break;
1713 if ((y_offset+(ssize_t) source_image->rows) >= (ssize_t) image->rows)
1714 break;
1715 status=MagickTrue;
1716 source_view=AcquireVirtualCacheView(source_image,exception);
1717 image_view=AcquireAuthenticCacheView(image,exception);
1718#if defined(MAGICKCORE_OPENMP_SUPPORT)
1719 #pragma omp parallel for schedule(static) shared(status) \
1720 magick_number_threads(source_image,image,source_image->rows,1)
1721#endif
1722 for (y=0; y < (ssize_t) source_image->rows; y++)
1723 {
1724 MagickBooleanType
1725 sync;
1726
1727 const IndexPacket
1728 *source_indexes;
1729
1730 const PixelPacket
1731 *p;
1732
1733 IndexPacket
1734 *indexes;
1735
1737 *q;
1738
1739 if (status == MagickFalse)
1740 continue;
1741 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
1742 1,exception);
1743 q=GetCacheViewAuthenticPixels(image_view,x_offset,y+y_offset,
1744 source_image->columns,1,exception);
1745 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1746 {
1747 status=MagickFalse;
1748 continue;
1749 }
1750 source_indexes=GetCacheViewVirtualIndexQueue(source_view);
1751 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1752 (void) memcpy(q,p,source_image->columns*sizeof(*p));
1753 if ((indexes != (IndexPacket *) NULL) &&
1754 (source_indexes != (const IndexPacket *) NULL))
1755 (void) memcpy(indexes,source_indexes,
1756 source_image->columns*sizeof(*indexes));
1757 sync=SyncCacheViewAuthenticPixels(image_view,exception);
1758 if (sync == MagickFalse)
1759 status=MagickFalse;
1760 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1761 {
1762 MagickBooleanType
1763 proceed;
1764
1765 proceed=SetImageProgress(image,CompositeImageTag,(MagickOffsetType)
1766 y,image->rows);
1767 if (proceed == MagickFalse)
1768 status=MagickFalse;
1769 }
1770 }
1771 source_view=DestroyCacheView(source_view);
1772 image_view=DestroyCacheView(image_view);
1773 source_image=DestroyImage(source_image);
1774 return(status);
1775 }
1776 case CopyOpacityCompositeOp:
1777 case ChangeMaskCompositeOp:
1778 {
1779 /*
1780 Modify canvas outside the overlaid region and require an alpha
1781 channel to exist, to add transparency.
1782 */
1783 if (image->matte == MagickFalse)
1784 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
1785 clip_to_self=MagickFalse;
1786 break;
1787 }
1788 case BlurCompositeOp:
1789 {
1790 CacheView
1791 *canvas_view,
1792 *source_view;
1793
1795 pixel;
1796
1797 MagickRealType
1798 angle_range,
1799 angle_start,
1800 height,
1801 width;
1802
1804 *resample_filter;
1805
1807 blur;
1808
1809 /*
1810 Blur Image by resampling.
1811
1812 Blur Image dictated by an overlay gradient map: X = red_channel;
1813 Y = green_channel; compose:args = x_scale[,y_scale[,angle]].
1814 */
1815 canvas_image=CloneImage(image,0,0,MagickTrue,exception);
1816 if (canvas_image == (Image *) NULL)
1817 {
1818 source_image=DestroyImage(source_image);
1819 return(MagickFalse);
1820 }
1821 /*
1822 Gather the maximum blur sigma values from user.
1823 */
1824 SetGeometryInfo(&geometry_info);
1825 flags=NoValue;
1826 value=GetImageArtifact(image,"compose:args");
1827 if (value != (char *) NULL)
1828 flags=ParseGeometry(value,&geometry_info);
1829 if ((flags & WidthValue) == 0)
1830 {
1831 (void) ThrowMagickException(exception,GetMagickModule(),
1832 OptionWarning,"InvalidGeometry","'%s' '%s'","compose:args",value);
1833 source_image=DestroyImage(source_image);
1834 canvas_image=DestroyImage(canvas_image);
1835 return(MagickFalse);
1836 }
1837 /*
1838 Users input sigma now needs to be converted to the EWA ellipse size.
1839 The filter defaults to a sigma of 0.5 so to make this match the
1840 users input the ellipse size needs to be doubled.
1841 */
1842 width=height=geometry_info.rho*2.0;
1843 if ((flags & HeightValue) != 0 )
1844 height=geometry_info.sigma*2.0;
1845
1846 /* default the unrotated ellipse width and height axis vectors */
1847 blur.x1=width;
1848 blur.x2=0.0;
1849 blur.y1=0.0;
1850 blur.y2=height;
1851 /* rotate vectors if a rotation angle is given */
1852 if ((flags & XValue) != 0 )
1853 {
1854 MagickRealType
1855 angle;
1856
1857 angle=DegreesToRadians(geometry_info.xi);
1858 blur.x1=width*cos(angle);
1859 blur.x2=width*sin(angle);
1860 blur.y1=(-height*sin(angle));
1861 blur.y2=height*cos(angle);
1862 }
1863 /* Otherwise lets set a angle range and calculate in the loop */
1864 angle_start=0.0;
1865 angle_range=0.0;
1866 if ((flags & YValue) != 0 )
1867 {
1868 angle_start=DegreesToRadians(geometry_info.xi);
1869 angle_range=DegreesToRadians(geometry_info.psi)-angle_start;
1870 }
1871 /*
1872 Set up a gaussian cylindrical filter for EWA Blurring.
1873
1874 As the minimum ellipse radius of support*1.0 the EWA algorithm
1875 can only produce a minimum blur of 0.5 for Gaussian (support=2.0)
1876 This means that even 'No Blur' will be still a little blurry!
1877
1878 The solution (as well as the problem of preventing any user
1879 expert filter settings, is to set our own user settings, then
1880 restore them afterwards.
1881 */
1882 resample_filter=AcquireResampleFilter(image,exception);
1883 SetResampleFilter(resample_filter,GaussianFilter,1.0);
1884
1885 /* do the variable blurring of each pixel in image */
1886 pixel=zero;
1887 source_view=AcquireVirtualCacheView(source_image,exception);
1888 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
1889 for (y=0; y < (ssize_t) source_image->rows; y++)
1890 {
1891 MagickBooleanType
1892 sync;
1893
1894 const PixelPacket
1895 *magick_restrict p;
1896
1898 *magick_restrict r;
1899
1900 IndexPacket
1901 *magick_restrict canvas_indexes;
1902
1903 ssize_t
1904 x;
1905
1906 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
1907 continue;
1908 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
1909 1,exception);
1910 r=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,
1911 1,exception);
1912 if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
1913 break;
1914 canvas_indexes=GetCacheViewAuthenticIndexQueue(canvas_view);
1915 for (x=0; x < (ssize_t) source_image->columns; x++)
1916 {
1917 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
1918 {
1919 p++;
1920 continue;
1921 }
1922 if (fabs((double) angle_range) > MagickEpsilon)
1923 {
1924 MagickRealType
1925 angle;
1926
1927 angle=angle_start+angle_range*QuantumScale*(double)
1928 GetPixelBlue(p);
1929 blur.x1=width*cos(angle);
1930 blur.x2=width*sin(angle);
1931 blur.y1=(-height*sin(angle));
1932 blur.y2=height*cos(angle);
1933 }
1934#if 0
1935 if ( x == 10 && y == 60 ) {
1936 fprintf(stderr, "blur.x=%lf,%lf, blur.y=%lf,%lf\n",
1937 blur.x1, blur.x2, blur.y1, blur.y2);
1938 fprintf(stderr, "scaled by=%lf,%lf\n",
1939 QuantumScale*GetPixelRed(p), QuantumScale*GetPixelGreen(p));
1940 }
1941#endif
1942 ScaleResampleFilter(resample_filter,
1943 blur.x1*QuantumScale*(double) GetPixelRed(p),
1944 blur.y1*QuantumScale*(double) GetPixelGreen(p),
1945 blur.x2*QuantumScale*(double) GetPixelRed(p),
1946 blur.y2*QuantumScale*(double) GetPixelGreen(p));
1947 (void) ResamplePixelColor(resample_filter,(double) x_offset+x,(double)
1948 y_offset+y,&pixel);
1949 SetPixelPacket(canvas_image,&pixel,r,canvas_indexes+x);
1950 p++;
1951 r++;
1952 }
1953 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
1954 if (sync == MagickFalse)
1955 break;
1956 }
1957 resample_filter=DestroyResampleFilter(resample_filter);
1958 source_view=DestroyCacheView(source_view);
1959 canvas_view=DestroyCacheView(canvas_view);
1960 source_image=DestroyImage(source_image);
1961 source_image=canvas_image;
1962 break;
1963 }
1964 case DisplaceCompositeOp:
1965 case DistortCompositeOp:
1966 {
1967 CacheView
1968 *canvas_view,
1969 *source_view,
1970 *image_view;
1971
1973 pixel;
1974
1975 MagickRealType
1976 horizontal_scale,
1977 vertical_scale;
1978
1979 PointInfo
1980 center,
1981 offset;
1982
1983 IndexPacket
1984 *magick_restrict canvas_indexes;
1985
1987 *magick_restrict r;
1988
1989 /*
1990 Displace/Distort based on overlay gradient map:
1991 X = red_channel; Y = green_channel;
1992 compose:args = x_scale[,y_scale[,center.x,center.y]]
1993 */
1994 canvas_image=CloneImage(image,0,0,MagickTrue,exception);
1995 if (canvas_image == (Image *) NULL)
1996 {
1997 source_image=DestroyImage(source_image);
1998 return(MagickFalse);
1999 }
2000 SetGeometryInfo(&geometry_info);
2001 flags=NoValue;
2002 value=GetImageArtifact(image,"compose:args");
2003 if (value != (char *) NULL)
2004 flags=ParseGeometry(value,&geometry_info);
2005 if ((flags & (WidthValue | HeightValue)) == 0 )
2006 {
2007 if ((flags & AspectValue) == 0)
2008 {
2009 horizontal_scale=(MagickRealType) (source_image->columns-1)/2.0;
2010 vertical_scale=(MagickRealType) (source_image->rows-1)/2.0;
2011 }
2012 else
2013 {
2014 horizontal_scale=(MagickRealType) (image->columns-1)/2.0;
2015 vertical_scale=(MagickRealType) (image->rows-1)/2.0;
2016 }
2017 }
2018 else
2019 {
2020 horizontal_scale=geometry_info.rho;
2021 vertical_scale=geometry_info.sigma;
2022 if ((flags & PercentValue) != 0)
2023 {
2024 if ((flags & AspectValue) == 0)
2025 {
2026 horizontal_scale*=(source_image->columns-1)/200.0;
2027 vertical_scale*=(source_image->rows-1)/200.0;
2028 }
2029 else
2030 {
2031 horizontal_scale*=(image->columns-1)/200.0;
2032 vertical_scale*=(image->rows-1)/200.0;
2033 }
2034 }
2035 if ((flags & HeightValue) == 0)
2036 vertical_scale=horizontal_scale;
2037 }
2038 /*
2039 Determine fixed center point for absolute distortion map
2040 Absolute distort ==
2041 Displace offset relative to a fixed absolute point
2042 Select that point according to +X+Y user inputs.
2043 default = center of overlay image
2044 arg flag '!' = locations/percentage relative to background image
2045 */
2046 center.x=(MagickRealType) x_offset;
2047 center.y=(MagickRealType) y_offset;
2048 if (compose == DistortCompositeOp)
2049 {
2050 if ((flags & XValue) == 0)
2051 if ((flags & AspectValue) != 0)
2052 center.x=((MagickRealType) image->columns-1)/2.0;
2053 else
2054 center.x=(MagickRealType) (x_offset+(source_image->columns-1)/
2055 2.0);
2056 else
2057 if ((flags & AspectValue) == 0)
2058 center.x=(MagickRealType) (x_offset+geometry_info.xi);
2059 else
2060 center.x=geometry_info.xi;
2061 if ((flags & YValue) == 0)
2062 if ((flags & AspectValue) != 0)
2063 center.y=((MagickRealType) image->rows-1)/2.0;
2064 else
2065 center.y=(MagickRealType) (y_offset+(source_image->rows-1)/2.0);
2066 else
2067 if ((flags & AspectValue) != 0)
2068 center.y=geometry_info.psi;
2069 else
2070 center.y=(MagickRealType) (y_offset+geometry_info.psi);
2071 }
2072 /*
2073 Shift the pixel offset point as defined by the provided,
2074 displacement/distortion map. -- Like a lens...
2075 */
2076 pixel=zero;
2077 image_view=AcquireVirtualCacheView(image,exception);
2078 source_view=AcquireVirtualCacheView(source_image,exception);
2079 canvas_view=AcquireAuthenticCacheView(canvas_image,exception);
2080 for (y=0; y < (ssize_t) source_image->rows; y++)
2081 {
2082 MagickBooleanType
2083 sync;
2084
2085 const PixelPacket
2086 *magick_restrict p;
2087
2088 ssize_t
2089 x;
2090
2091 if (((y+y_offset) < 0) || ((y+y_offset) >= (ssize_t) image->rows))
2092 continue;
2093 p=GetCacheViewVirtualPixels(source_view,0,y,source_image->columns,
2094 1,exception);
2095 r=QueueCacheViewAuthenticPixels(canvas_view,0,y,canvas_image->columns,
2096 1,exception);
2097 if ((p == (const PixelPacket *) NULL) || (r == (PixelPacket *) NULL))
2098 break;
2099 canvas_indexes=GetCacheViewAuthenticIndexQueue(canvas_view);
2100 for (x=0; x < (ssize_t) source_image->columns; x++)
2101 {
2102 if (((x_offset+x) < 0) || ((x_offset+x) >= (ssize_t) image->columns))
2103 {
2104 p++;
2105 continue;
2106 }
2107 /*
2108 Displace the offset.
2109 */
2110 offset.x=(double) ((horizontal_scale*((MagickRealType) GetPixelRed(p)-
2111 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2112 QuantumRange+1.0)/2.0)+center.x+((compose == DisplaceCompositeOp) ?
2113 x : 0));
2114 offset.y=(double) ((vertical_scale*((MagickRealType) GetPixelGreen(p)-
2115 (((MagickRealType) QuantumRange+1.0)/2.0)))/(((MagickRealType)
2116 QuantumRange+1.0)/2.0)+center.y+((compose == DisplaceCompositeOp) ?
2117 y : 0));
2118 status=InterpolateMagickPixelPacket(image,image_view,
2119 UndefinedInterpolatePixel,(double) offset.x,(double) offset.y,
2120 &pixel,exception);
2121 if (status == MagickFalse)
2122 break;
2123 /*
2124 Mask with the 'invalid pixel mask' in alpha channel.
2125 */
2126 pixel.opacity=(MagickRealType) QuantumRange*(1.0-(1.0-QuantumScale*
2127 pixel.opacity)*(1.0-QuantumScale*(double) GetPixelOpacity(p)));
2128 SetPixelPacket(canvas_image,&pixel,r,canvas_indexes+x);
2129 p++;
2130 r++;
2131 }
2132 if (x < (ssize_t) source_image->columns)
2133 break;
2134 sync=SyncCacheViewAuthenticPixels(canvas_view,exception);
2135 if (sync == MagickFalse)
2136 break;
2137 }
2138 canvas_view=DestroyCacheView(canvas_view);
2139 source_view=DestroyCacheView(source_view);
2140 image_view=DestroyCacheView(image_view);
2141 source_image=DestroyImage(source_image);
2142 source_image=canvas_image;
2143 break;
2144 }
2145 case DissolveCompositeOp:
2146 {
2147 /*
2148 Geometry arguments to dissolve factors.
2149 */
2150 value=GetImageArtifact(image,"compose:args");
2151 if (value != (char *) NULL)
2152 {
2153 flags=ParseGeometry(value,&geometry_info);
2154 source_dissolve=geometry_info.rho/100.0;
2155 canvas_dissolve=1.0;
2156 if ((source_dissolve-MagickEpsilon) < 0.0)
2157 source_dissolve=0.0;
2158 if ((source_dissolve+MagickEpsilon) > 1.0)
2159 {
2160 canvas_dissolve=2.0-source_dissolve;
2161 source_dissolve=1.0;
2162 }
2163 if ((flags & SigmaValue) != 0)
2164 canvas_dissolve=geometry_info.sigma/100.0;
2165 if ((canvas_dissolve-MagickEpsilon) < 0.0)
2166 canvas_dissolve=0.0;
2167 clip_to_self=MagickFalse;
2168 if ((canvas_dissolve+MagickEpsilon) > 1.0 )
2169 {
2170 canvas_dissolve=1.0;
2171 clip_to_self=MagickTrue;
2172 }
2173 }
2174 break;
2175 }
2176 case BlendCompositeOp:
2177 {
2178 value=GetImageArtifact(image,"compose:args");
2179 if (value != (char *) NULL)
2180 {
2181 flags=ParseGeometry(value,&geometry_info);
2182 source_dissolve=geometry_info.rho/100.0;
2183 canvas_dissolve=1.0-source_dissolve;
2184 if ((flags & SigmaValue) != 0)
2185 canvas_dissolve=geometry_info.sigma/100.0;
2186 clip_to_self=MagickFalse;
2187 if ((canvas_dissolve+MagickEpsilon) > 1.0)
2188 clip_to_self=MagickTrue;
2189 }
2190 break;
2191 }
2192 case MathematicsCompositeOp:
2193 {
2194 /*
2195 Just collect the values from "compose:args", setting.
2196 Unused values are set to zero automagically.
2197
2198 Arguments are normally a comma separated list, so this probably should
2199 be changed to some 'general comma list' parser, (with a minimum
2200 number of values)
2201 */
2202 SetGeometryInfo(&geometry_info);
2203 value=GetImageArtifact(image,"compose:args");
2204 if (value != (char *) NULL)
2205 {
2206 flags=ParseGeometry(value,&geometry_info);
2207 if (flags == NoValue)
2208 (void) ThrowMagickException(exception,GetMagickModule(),OptionError,
2209 "InvalidGeometry","`%s'",value);
2210 }
2211 break;
2212 }
2213 case ModulateCompositeOp:
2214 {
2215 /*
2216 Determine the luma and chroma scale.
2217 */
2218 value=GetImageArtifact(image,"compose:args");
2219 if (value != (char *) NULL)
2220 {
2221 flags=ParseGeometry(value,&geometry_info);
2222 percent_luma=geometry_info.rho;
2223 if ((flags & SigmaValue) != 0)
2224 percent_chroma=geometry_info.sigma;
2225 }
2226 break;
2227 }
2228 case ThresholdCompositeOp:
2229 {
2230 /*
2231 Determine the amount and threshold.
2232 This Composition method is deprecated
2233 */
2234 value=GetImageArtifact(image,"compose:args");
2235 if (value != (char *) NULL)
2236 {
2237 flags=ParseGeometry(value,&geometry_info);
2238 amount=geometry_info.rho;
2239 threshold=geometry_info.sigma;
2240 if ((flags & SigmaValue) == 0)
2241 threshold=0.05f;
2242 }
2243 threshold*=(double) QuantumRange;
2244 break;
2245 }
2246 default:
2247 break;
2248 }
2249 value=GetImageArtifact(image,"compose:outside-overlay");
2250 if (value != (const char *) NULL)
2251 clip_to_self=IsMagickTrue(value) == MagickFalse ? MagickTrue : MagickFalse;
2252 value=GetImageArtifact(image,"compose:clip-to-self");
2253 if (value != (const char *) NULL)
2254 clip_to_self=IsMagickTrue(value) != MagickFalse ? MagickTrue : MagickFalse;
2255 clamp=MagickTrue;
2256 value=GetImageArtifact(image,"compose:clamp");
2257 if (value != (const char *) NULL)
2258 clamp=IsMagickTrue(value);
2259 /*
2260 Composite image.
2261 */
2262#if defined(MAGICKCORE_OPENCL_SUPPORT)
2263 status=AccelerateCompositeImage(image,channel,compose,source_image,
2264 x_offset,y_offset,canvas_dissolve,source_dissolve,exception);
2265 if (status != MagickFalse)
2266 return(status);
2267#endif
2268 status=MagickTrue;
2269 progress=0;
2270 midpoint=((MagickRealType) QuantumRange+1.0)/2;
2271 GetMagickPixelPacket(source_image,&zero);
2272 source_view=AcquireVirtualCacheView(source_image,exception);
2273 image_view=AcquireAuthenticCacheView(image,exception);
2274#if defined(MAGICKCORE_OPENMP_SUPPORT)
2275 #pragma omp parallel for schedule(static) shared(progress,status) \
2276 magick_number_threads(source_image,image,image->rows,1)
2277#endif
2278 for (y=0; y < (ssize_t) image->rows; y++)
2279 {
2280 const PixelPacket
2281 *pixels;
2282
2283 double
2284 luma,
2285 hue,
2286 chroma,
2287 sans;
2288
2290 composite,
2291 canvas,
2292 source;
2293
2294 const IndexPacket
2295 *magick_restrict source_indexes;
2296
2297 const PixelPacket
2298 *magick_restrict p;
2299
2300 IndexPacket
2301 *magick_restrict indexes;
2302
2303 ssize_t
2304 x;
2305
2307 *magick_restrict q;
2308
2309 if (status == MagickFalse)
2310 continue;
2311 if (clip_to_self != MagickFalse)
2312 {
2313 if (y < y_offset)
2314 continue;
2315 if ((y-y_offset) >= (ssize_t) source_image->rows)
2316 continue;
2317 }
2318 /*
2319 If pixels is NULL, y is outside overlay region.
2320 */
2321 pixels=(PixelPacket *) NULL;
2322 p=(PixelPacket *) NULL;
2323 if ((y >= y_offset) && ((y-y_offset) < (ssize_t) source_image->rows))
2324 {
2325 p=GetCacheViewVirtualPixels(source_view,0,y-y_offset,
2326 source_image->columns,1,exception);
2327 if (p == (const PixelPacket *) NULL)
2328 {
2329 status=MagickFalse;
2330 continue;
2331 }
2332 pixels=p;
2333 if (x_offset < 0)
2334 p-=x_offset;
2335 }
2336 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2337 if (q == (PixelPacket *) NULL)
2338 {
2339 status=MagickFalse;
2340 continue;
2341 }
2342 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2343 source_indexes=GetCacheViewVirtualIndexQueue(source_view);
2344 GetMagickPixelPacket(source_image,&source);
2345 GetMagickPixelPacket(image,&canvas);
2346 hue=0.0;
2347 chroma=0.0;
2348 luma=0.0;
2349 for (x=0; x < (ssize_t) image->columns; x++)
2350 {
2351 if (clip_to_self != MagickFalse)
2352 {
2353 if (x < x_offset)
2354 {
2355 q++;
2356 continue;
2357 }
2358 if ((x-x_offset) >= (ssize_t) source_image->columns)
2359 break;
2360 }
2361 canvas.red=(MagickRealType) GetPixelRed(q);
2362 canvas.green=(MagickRealType) GetPixelGreen(q);
2363 canvas.blue=(MagickRealType) GetPixelBlue(q);
2364 if (image->matte != MagickFalse)
2365 canvas.opacity=(MagickRealType) GetPixelOpacity(q);
2366 if (image->colorspace == CMYKColorspace)
2367 canvas.index=(MagickRealType) GetPixelIndex(indexes+x);
2368 if (image->colorspace == CMYKColorspace)
2369 {
2370 canvas.red=(MagickRealType) QuantumRange-canvas.red;
2371 canvas.green=(MagickRealType) QuantumRange-canvas.green;
2372 canvas.blue=(MagickRealType) QuantumRange-canvas.blue;
2373 canvas.index=(MagickRealType) QuantumRange-canvas.index;
2374 }
2375 /*
2376 Handle canvas modifications outside overlaid region.
2377 */
2378 composite=canvas;
2379 if ((pixels == (PixelPacket *) NULL) || (x < x_offset) ||
2380 ((x-x_offset) >= (ssize_t) source_image->columns))
2381 {
2382 switch (compose)
2383 {
2384 case DissolveCompositeOp:
2385 case BlendCompositeOp:
2386 {
2387 composite.opacity=(MagickRealType) ((MagickRealType) QuantumRange-
2388 canvas_dissolve*((MagickRealType) QuantumRange-
2389 composite.opacity));
2390 break;
2391 }
2392 case ClearCompositeOp:
2393 case SrcCompositeOp:
2394 {
2395 CompositeClear(&canvas,&composite);
2396 break;
2397 }
2398 case InCompositeOp:
2399 case SrcInCompositeOp:
2400 case OutCompositeOp:
2401 case SrcOutCompositeOp:
2402 case DstInCompositeOp:
2403 case DstAtopCompositeOp:
2404 case CopyOpacityCompositeOp:
2405 case ChangeMaskCompositeOp:
2406 {
2407 composite.opacity=(MagickRealType) TransparentOpacity;
2408 break;
2409 }
2410 default:
2411 {
2412 (void) GetOneVirtualMagickPixel(source_image,x-x_offset,
2413 y-y_offset,&composite,exception);
2414 break;
2415 }
2416 }
2417 if (image->colorspace == CMYKColorspace)
2418 {
2419 composite.red=(MagickRealType) QuantumRange-composite.red;
2420 composite.green=(MagickRealType) QuantumRange-composite.green;
2421 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2422 composite.index=(MagickRealType) QuantumRange-composite.index;
2423 }
2424 SetPixelRed(q,clamp != MagickFalse ?
2425 ClampPixel(composite.red) : ClampToQuantum(composite.red));
2426 SetPixelGreen(q,clamp != MagickFalse ?
2427 ClampPixel(composite.green) : ClampToQuantum(composite.green));
2428 SetPixelBlue(q,clamp != MagickFalse ?
2429 ClampPixel(composite.blue) : ClampToQuantum(composite.blue));
2430 if (image->matte != MagickFalse)
2431 SetPixelOpacity(q,clamp != MagickFalse ?
2432 ClampPixel(composite.opacity) :
2433 ClampToQuantum(composite.opacity));
2434 if (image->colorspace == CMYKColorspace)
2435 SetPixelIndex(indexes+x,clamp != MagickFalse ?
2436 ClampPixel(composite.index) : ClampToQuantum(composite.index));
2437 q++;
2438 continue;
2439 }
2440 /*
2441 Handle normal overlay of source onto canvas.
2442 */
2443 source.red=(MagickRealType) GetPixelRed(p);
2444 source.green=(MagickRealType) GetPixelGreen(p);
2445 source.blue=(MagickRealType) GetPixelBlue(p);
2446 if (source_image->matte != MagickFalse)
2447 source.opacity=(MagickRealType) GetPixelOpacity(p);
2448 if (source_image->colorspace == CMYKColorspace)
2449 source.index=(MagickRealType) GetPixelIndex(source_indexes+
2450 x-x_offset);
2451 if (source_image->colorspace == CMYKColorspace)
2452 {
2453 source.red=(MagickRealType) QuantumRange-source.red;
2454 source.green=(MagickRealType) QuantumRange-source.green;
2455 source.blue=(MagickRealType) QuantumRange-source.blue;
2456 source.index=(MagickRealType) QuantumRange-source.index;
2457 }
2458 switch (compose)
2459 {
2460 /* Duff-Porter Compositions */
2461 case ClearCompositeOp:
2462 {
2463 CompositeClear(&canvas,&composite);
2464 break;
2465 }
2466 case SrcCompositeOp:
2467 case CopyCompositeOp:
2468 case ReplaceCompositeOp:
2469 {
2470 composite=source;
2471 break;
2472 }
2473 case NoCompositeOp:
2474 case DstCompositeOp:
2475 break;
2476 case OverCompositeOp:
2477 case SrcOverCompositeOp:
2478 {
2479 MagickPixelCompositeOver(&source,source.opacity,&canvas,
2480 canvas.opacity,&composite);
2481 break;
2482 }
2483 case DstOverCompositeOp:
2484 {
2485 MagickPixelCompositeOver(&canvas,canvas.opacity,&source,
2486 source.opacity,&composite);
2487 break;
2488 }
2489 case SrcInCompositeOp:
2490 case InCompositeOp:
2491 {
2492 CompositeIn(&source,&canvas,&composite);
2493 break;
2494 }
2495 case DstInCompositeOp:
2496 {
2497 CompositeIn(&canvas,&source,&composite);
2498 break;
2499 }
2500 case OutCompositeOp:
2501 case SrcOutCompositeOp:
2502 {
2503 CompositeOut(&source,&canvas,&composite);
2504 break;
2505 }
2506 case DstOutCompositeOp:
2507 {
2508 CompositeOut(&canvas,&source,&composite);
2509 break;
2510 }
2511 case AtopCompositeOp:
2512 case SrcAtopCompositeOp:
2513 {
2514 CompositeAtop(&source,&canvas,&composite);
2515 break;
2516 }
2517 case DstAtopCompositeOp:
2518 {
2519 CompositeAtop(&canvas,&source,&composite);
2520 break;
2521 }
2522 case XorCompositeOp:
2523 {
2524 CompositeXor(&source,&canvas,&composite);
2525 break;
2526 }
2527 /* Mathematical Compositions */
2528 case PlusCompositeOp:
2529 {
2530 CompositePlus(&source,&canvas,channel,&composite);
2531 break;
2532 }
2533 case MinusDstCompositeOp:
2534 {
2535 CompositeMinus(&source,&canvas,channel,&composite);
2536 break;
2537 }
2538 case MinusSrcCompositeOp:
2539 {
2540 CompositeMinus(&canvas,&source,channel,&composite);
2541 break;
2542 }
2543 case ModulusAddCompositeOp:
2544 {
2545 CompositeModulusAdd(&source,&canvas,channel,&composite);
2546 break;
2547 }
2548 case ModulusSubtractCompositeOp:
2549 {
2550 CompositeModulusSubtract(&source,&canvas,channel,&composite);
2551 break;
2552 }
2553 case DifferenceCompositeOp:
2554 {
2555 CompositeDifference(&source,&canvas,channel,&composite);
2556 break;
2557 }
2558 case ExclusionCompositeOp:
2559 {
2560 CompositeExclusion(&source,&canvas,channel,&composite);
2561 break;
2562 }
2563 case MultiplyCompositeOp:
2564 {
2565 CompositeMultiply(&source,&canvas,channel,&composite);
2566 break;
2567 }
2568 case ScreenCompositeOp:
2569 {
2570 CompositeScreen(&source,&canvas,channel,&composite);
2571 break;
2572 }
2573 case DivideDstCompositeOp:
2574 {
2575 CompositeDivide(&source,&canvas,channel,&composite);
2576 break;
2577 }
2578 case DivideSrcCompositeOp:
2579 {
2580 CompositeDivide(&canvas,&source,channel,&composite);
2581 break;
2582 }
2583 case DarkenCompositeOp:
2584 {
2585 CompositeDarken(&source,&canvas,channel,&composite);
2586 break;
2587 }
2588 case LightenCompositeOp:
2589 {
2590 CompositeLighten(&source,&canvas,channel,&composite);
2591 break;
2592 }
2593 case DarkenIntensityCompositeOp:
2594 {
2595 CompositeDarkenIntensity(&source,&canvas,channel,&composite);
2596 break;
2597 }
2598 case LightenIntensityCompositeOp:
2599 {
2600 CompositeLightenIntensity(&source,&canvas,channel,&composite);
2601 break;
2602 }
2603 case MathematicsCompositeOp:
2604 {
2605 CompositeMathematics(&source,&canvas,channel,&geometry_info,
2606 &composite);
2607 break;
2608 }
2609 /* Lighting Compositions */
2610 case ColorDodgeCompositeOp:
2611 {
2612 CompositeColorDodge(&source,&canvas,&composite);
2613 break;
2614 }
2615 case ColorBurnCompositeOp:
2616 {
2617 CompositeColorBurn(&source,&canvas,&composite);
2618 break;
2619 }
2620 case LinearDodgeCompositeOp:
2621 {
2622 CompositeLinearDodge(&source,&canvas,&composite);
2623 break;
2624 }
2625 case LinearBurnCompositeOp:
2626 {
2627 CompositeLinearBurn(&source,&canvas,&composite);
2628 break;
2629 }
2630 case HardLightCompositeOp:
2631 {
2632 CompositeHardLight(&source,&canvas,&composite);
2633 break;
2634 }
2635 case HardMixCompositeOp:
2636 {
2637 CompositeHardMix(&source,&canvas,&composite);
2638 break;
2639 }
2640 case OverlayCompositeOp:
2641 {
2642 /* Overlay = Reversed HardLight. */
2643 CompositeHardLight(&canvas,&source,&composite);
2644 break;
2645 }
2646 case SoftLightCompositeOp:
2647 {
2648 CompositeSoftLight(&source,&canvas,&composite);
2649 break;
2650 }
2651 case LinearLightCompositeOp:
2652 {
2653 CompositeLinearLight(&source,&canvas,&composite);
2654 break;
2655 }
2656 case PegtopLightCompositeOp:
2657 {
2658 CompositePegtopLight(&source,&canvas,&composite);
2659 break;
2660 }
2661 case VividLightCompositeOp:
2662 {
2663 CompositeVividLight(&source,&canvas,&composite);
2664 break;
2665 }
2666 case PinLightCompositeOp:
2667 {
2668 CompositePinLight(&source,&canvas,&composite);
2669 break;
2670 }
2671 /* Other Composition */
2672 case ChangeMaskCompositeOp:
2673 {
2674 if ((composite.opacity > ((MagickRealType) QuantumRange/2.0)) ||
2675 (IsMagickColorSimilar(&source,&canvas) != MagickFalse))
2676 composite.opacity=(MagickRealType) TransparentOpacity;
2677 else
2678 composite.opacity=(MagickRealType) OpaqueOpacity;
2679 break;
2680 }
2681 case BumpmapCompositeOp:
2682 {
2683 if (source.opacity == (MagickRealType) TransparentOpacity)
2684 break;
2685 CompositeBumpmap(&source,&canvas,&composite);
2686 break;
2687 }
2688 case DissolveCompositeOp:
2689 {
2690 MagickPixelCompositeOver(&source,(MagickRealType) QuantumRange-
2691 source_dissolve*((MagickRealType) QuantumRange-source.opacity),
2692 &canvas,(MagickRealType) QuantumRange-canvas_dissolve*
2693 ((MagickRealType) QuantumRange-canvas.opacity),&composite);
2694 break;
2695 }
2696 case BlendCompositeOp:
2697 {
2698 MagickPixelCompositeBlend(&source,source_dissolve,&canvas,
2699 canvas_dissolve,&composite);
2700 break;
2701 }
2702 case StereoCompositeOp:
2703 {
2704 composite.red=(MagickRealType) GetPixelRed(p);
2705 composite.opacity=(composite.opacity+canvas.opacity/2);
2706 break;
2707 }
2708 case ThresholdCompositeOp:
2709 {
2710 CompositeThreshold(&source,&canvas,threshold,amount,&composite);
2711 break;
2712 }
2713 case ModulateCompositeOp:
2714 {
2715 ssize_t
2716 offset;
2717
2718 if (source.opacity == (MagickRealType) TransparentOpacity)
2719 break;
2720 offset=(ssize_t) ((MagickRealType) MagickPixelIntensityToQuantum(
2721 &source)-(MagickRealType) midpoint);
2722 if (offset == 0)
2723 break;
2724 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2725 &chroma,&luma);
2726 luma+=(0.01*percent_luma*offset)/midpoint;
2727 chroma*=0.01*percent_chroma;
2728 HCLComposite(hue,chroma,luma,&composite.red,&composite.green,
2729 &composite.blue);
2730 break;
2731 }
2732 case HueCompositeOp:
2733 {
2734 if (source.opacity == (MagickRealType) TransparentOpacity)
2735 break;
2736 if (canvas.opacity == (MagickRealType) TransparentOpacity)
2737 {
2738 composite=source;
2739 break;
2740 }
2741 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2742 &chroma,&luma);
2743 CompositeHCL(source.red,source.green,source.blue,&hue,&sans,&sans);
2744 HCLComposite(hue,chroma,luma,&composite.red,
2745 &composite.green,&composite.blue);
2746 if (source.opacity < canvas.opacity)
2747 composite.opacity=source.opacity;
2748 break;
2749 }
2750 case SaturateCompositeOp:
2751 {
2752 if (source.opacity == (MagickRealType) TransparentOpacity)
2753 break;
2754 if (canvas.opacity == (MagickRealType) TransparentOpacity)
2755 {
2756 composite=source;
2757 break;
2758 }
2759 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2760 &chroma,&luma);
2761 CompositeHCL(source.red,source.green,source.blue,&sans,&chroma,
2762 &sans);
2763 HCLComposite(hue,chroma,luma,&composite.red,
2764 &composite.green,&composite.blue);
2765 if (source.opacity < canvas.opacity)
2766 composite.opacity=source.opacity;
2767 break;
2768 }
2769 case LuminizeCompositeOp:
2770 {
2771 if (source.opacity == (MagickRealType) TransparentOpacity)
2772 break;
2773 if (canvas.opacity == (MagickRealType) TransparentOpacity)
2774 {
2775 composite=source;
2776 break;
2777 }
2778 CompositeHCL(canvas.red,canvas.green,canvas.blue,&hue,
2779 &chroma,&luma);
2780 CompositeHCL(source.red,source.green,source.blue,&sans,&sans,
2781 &luma);
2782 HCLComposite(hue,chroma,luma,&composite.red,
2783 &composite.green,&composite.blue);
2784 if (source.opacity < canvas.opacity)
2785 composite.opacity=source.opacity;
2786 break;
2787 }
2788 case ColorizeCompositeOp:
2789 {
2790 if (source.opacity == (MagickRealType) TransparentOpacity)
2791 break;
2792 if (canvas.opacity == (MagickRealType) TransparentOpacity)
2793 {
2794 composite=source;
2795 break;
2796 }
2797 CompositeHCL(canvas.red,canvas.green,canvas.blue,&sans,
2798 &sans,&luma);
2799 CompositeHCL(source.red,source.green,source.blue,&hue,&chroma,&sans);
2800 HCLComposite(hue,chroma,luma,&composite.red,
2801 &composite.green,&composite.blue);
2802 if (source.opacity < canvas.opacity)
2803 composite.opacity=source.opacity;
2804 break;
2805 }
2806 case CopyRedCompositeOp:
2807 case CopyCyanCompositeOp:
2808 {
2809 composite.red=source.red;
2810 break;
2811 }
2812 case CopyGreenCompositeOp:
2813 case CopyMagentaCompositeOp:
2814 {
2815 composite.green=source.green;
2816 break;
2817 }
2818 case CopyBlueCompositeOp:
2819 case CopyYellowCompositeOp:
2820 {
2821 composite.blue=source.blue;
2822 break;
2823 }
2824 case CopyOpacityCompositeOp:
2825 {
2826 if (source.matte == MagickFalse)
2827 composite.opacity=(MagickRealType) (QuantumRange-
2828 MagickPixelIntensityToQuantum(&source));
2829 else
2830 composite.opacity=source.opacity;
2831 break;
2832 }
2833 case CopyBlackCompositeOp:
2834 {
2835 if (source.colorspace != CMYKColorspace)
2836 ConvertRGBToCMYK(&source);
2837 composite.index=source.index;
2838 break;
2839 }
2840 /* compose methods that are already handled */
2841 case BlurCompositeOp:
2842 case DisplaceCompositeOp:
2843 case DistortCompositeOp:
2844 {
2845 composite=source;
2846 break;
2847 }
2848 default:
2849 break;
2850 }
2851 if (image->colorspace == CMYKColorspace)
2852 {
2853 composite.red=(MagickRealType) QuantumRange-composite.red;
2854 composite.green=(MagickRealType) QuantumRange-composite.green;
2855 composite.blue=(MagickRealType) QuantumRange-composite.blue;
2856 composite.index=(MagickRealType) QuantumRange-composite.index;
2857 }
2858 SetPixelRed(q,clamp != MagickFalse ?
2859 ClampPixel(composite.red) : ClampToQuantum(composite.red));
2860 SetPixelGreen(q,clamp != MagickFalse ?
2861 ClampPixel(composite.green) : ClampToQuantum(composite.green));
2862 SetPixelBlue(q,clamp != MagickFalse ?
2863 ClampPixel(composite.blue) : ClampToQuantum(composite.blue));
2864 SetPixelOpacity(q,clamp != MagickFalse ?
2865 ClampPixel(composite.opacity) : ClampToQuantum(composite.opacity));
2866 if (image->colorspace == CMYKColorspace)
2867 SetPixelIndex(indexes+x,clamp != MagickFalse ?
2868 ClampPixel(composite.index) : ClampToQuantum(composite.index));
2869 p++;
2870 if (p >= (pixels+source_image->columns))
2871 p=pixels;
2872 q++;
2873 }
2874 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2875 status=MagickFalse;
2876 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2877 {
2878 MagickBooleanType
2879 proceed;
2880
2881#if defined(MAGICKCORE_OPENMP_SUPPORT)
2882 #pragma omp atomic
2883#endif
2884 progress++;
2885 proceed=SetImageProgress(image,CompositeImageTag,progress,image->rows);
2886 if (proceed == MagickFalse)
2887 status=MagickFalse;
2888 }
2889 }
2890 source_view=DestroyCacheView(source_view);
2891 image_view=DestroyCacheView(image_view);
2892 if (canvas_image != (Image * ) NULL)
2893 canvas_image=DestroyImage(canvas_image);
2894 else
2895 source_image=DestroyImage(source_image);
2896 return(status);
2897}
2898
2899/*
2900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2901% %
2902% %
2903% %
2904% T e x t u r e I m a g e %
2905% %
2906% %
2907% %
2908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2909%
2910% TextureImage() repeatedly tiles the texture image across and down the image
2911% canvas.
2912%
2913% The format of the TextureImage method is:
2914%
2915% MagickBooleanType TextureImage(Image *image,const Image *texture)
2916%
2917% A description of each parameter follows:
2918%
2919% o image: the image.
2920%
2921% o texture: This image is the texture to layer on the background.
2922%
2923*/
2924MagickExport MagickBooleanType TextureImage(Image *image,const Image *texture)
2925{
2926#define TextureImageTag "Texture/Image"
2927
2928 CacheView
2929 *image_view,
2930 *texture_view;
2931
2933 *exception;
2934
2935 Image
2936 *texture_image;
2937
2938 MagickBooleanType
2939 status;
2940
2941 ssize_t
2942 y;
2943
2944 assert(image != (Image *) NULL);
2945 assert(image->signature == MagickCoreSignature);
2946 if (IsEventLogging() != MagickFalse)
2947 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2948 if (texture == (const Image *) NULL)
2949 return(MagickFalse);
2950 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2951 return(MagickFalse);
2952 exception=(&image->exception);
2953 texture_image=CloneImage(texture,0,0,MagickTrue,exception);
2954 if (texture_image == (const Image *) NULL)
2955 return(MagickFalse);
2956 (void) TransformImageColorspace(texture_image,image->colorspace);
2957 (void) SetImageVirtualPixelMethod(texture_image,TileVirtualPixelMethod);
2958 status=MagickTrue;
2959 if ((image->compose != CopyCompositeOp) &&
2960 ((image->compose != OverCompositeOp) || (image->matte != MagickFalse) ||
2961 (texture_image->matte != MagickFalse)))
2962 {
2963 /*
2964 Tile texture onto the image background.
2965 */
2966 for (y=0; y < (ssize_t) image->rows; y+=(ssize_t) texture_image->rows)
2967 {
2968 ssize_t
2969 x;
2970
2971 if (status == MagickFalse)
2972 continue;
2973 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
2974 {
2975 MagickBooleanType
2976 thread_status;
2977
2978 thread_status=CompositeImage(image,image->compose,texture_image,x+
2979 texture_image->tile_offset.x,y+texture_image->tile_offset.y);
2980 if (thread_status == MagickFalse)
2981 {
2982 status=thread_status;
2983 break;
2984 }
2985 }
2986 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2987 {
2988 MagickBooleanType
2989 proceed;
2990
2991 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2992 y,image->rows);
2993 if (proceed == MagickFalse)
2994 status=MagickFalse;
2995 }
2996 }
2997 (void) SetImageProgress(image,TextureImageTag,(MagickOffsetType)
2998 image->rows,image->rows);
2999 texture_image=DestroyImage(texture_image);
3000 return(status);
3001 }
3002 /*
3003 Tile texture onto the image background (optimized).
3004 */
3005 status=MagickTrue;
3006 texture_view=AcquireVirtualCacheView(texture_image,exception);
3007 image_view=AcquireAuthenticCacheView(image,exception);
3008#if defined(MAGICKCORE_OPENMP_SUPPORT)
3009 #pragma omp parallel for schedule(static) shared(status) \
3010 magick_number_threads(image,texture_image,image->rows,1)
3011#endif
3012 for (y=0; y < (ssize_t) image->rows; y++)
3013 {
3014 MagickBooleanType
3015 sync;
3016
3017 const IndexPacket
3018 *texture_indexes;
3019
3020 const PixelPacket
3021 *p;
3022
3023 IndexPacket
3024 *indexes;
3025
3026 ssize_t
3027 x;
3028
3030 *q;
3031
3032 size_t
3033 width;
3034
3035 if (status == MagickFalse)
3036 continue;
3037 p=GetCacheViewVirtualPixels(texture_view,texture_image->tile_offset.x,(y+
3038 texture_image->tile_offset.y) % texture_image->rows,
3039 texture_image->columns,1,exception);
3040 q=QueueCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3041 exception);
3042 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3043 {
3044 status=MagickFalse;
3045 continue;
3046 }
3047 texture_indexes=GetCacheViewVirtualIndexQueue(texture_view);
3048 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3049 for (x=0; x < (ssize_t) image->columns; x+=(ssize_t) texture_image->columns)
3050 {
3051 width=texture_image->columns;
3052 if ((x+(ssize_t) width) > (ssize_t) image->columns)
3053 width=image->columns-x;
3054 (void) memcpy(q,p,width*sizeof(*p));
3055 if ((image->colorspace == CMYKColorspace) &&
3056 (texture_image->colorspace == CMYKColorspace))
3057 {
3058 (void) memcpy(indexes,texture_indexes,width*
3059 sizeof(*indexes));
3060 indexes+=width;
3061 }
3062 q+=width;
3063 }
3064 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3065 if (sync == MagickFalse)
3066 status=MagickFalse;
3067 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3068 {
3069 MagickBooleanType
3070 proceed;
3071
3072 proceed=SetImageProgress(image,TextureImageTag,(MagickOffsetType) y,
3073 image->rows);
3074 if (proceed == MagickFalse)
3075 status=MagickFalse;
3076 }
3077 }
3078 texture_view=DestroyCacheView(texture_view);
3079 image_view=DestroyCacheView(image_view);
3080 texture_image=DestroyImage(texture_image);
3081 return(status);
3082}