source: ImageMagick/branches/ImageMagick-6/magick/composite.c @ 7625

Revision 7625, 99.6 KB checked in by anthony, 13 months ago (diff)

Fix Gaussian Filter which was causing problems with Variable Blur

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