]> granicus.if.org Git - imagemagick/blob - MagickCore/enhance.c
https://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=29708
[imagemagick] / MagickCore / enhance.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %              EEEEE  N   N  H   H   AAA   N   N   CCCC  EEEEE                %
7 %              E      NN  N  H   H  A   A  NN  N  C      E                    %
8 %              EEE    N N N  HHHHH  AAAAA  N N N  C      EEE                  %
9 %              E      N  NN  H   H  A   A  N  NN  C      E                    %
10 %              EEEEE  N   N  H   H  A   A  N   N   CCCC  EEEEE                %
11 %                                                                             %
12 %                                                                             %
13 %                    MagickCore Image Enhancement Methods                     %
14 %                                                                             %
15 %                              Software Design                                %
16 %                                   Cristy                                    %
17 %                                 July 1992                                   %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2016 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 \f
40 /*
41   Include declarations.
42 */
43 #include "MagickCore/studio.h"
44 #include "MagickCore/accelerate.h"
45 #include "MagickCore/artifact.h"
46 #include "MagickCore/attribute.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/cache-view.h"
49 #include "MagickCore/channel.h"
50 #include "MagickCore/color.h"
51 #include "MagickCore/color-private.h"
52 #include "MagickCore/colorspace.h"
53 #include "MagickCore/colorspace-private.h"
54 #include "MagickCore/composite-private.h"
55 #include "MagickCore/enhance.h"
56 #include "MagickCore/exception.h"
57 #include "MagickCore/exception-private.h"
58 #include "MagickCore/fx.h"
59 #include "MagickCore/gem.h"
60 #include "MagickCore/gem-private.h"
61 #include "MagickCore/geometry.h"
62 #include "MagickCore/histogram.h"
63 #include "MagickCore/image.h"
64 #include "MagickCore/image-private.h"
65 #include "MagickCore/memory_.h"
66 #include "MagickCore/monitor.h"
67 #include "MagickCore/monitor-private.h"
68 #include "MagickCore/option.h"
69 #include "MagickCore/pixel.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/quantum.h"
72 #include "MagickCore/quantum-private.h"
73 #include "MagickCore/resample.h"
74 #include "MagickCore/resample-private.h"
75 #include "MagickCore/resource_.h"
76 #include "MagickCore/statistic.h"
77 #include "MagickCore/string_.h"
78 #include "MagickCore/string-private.h"
79 #include "MagickCore/thread-private.h"
80 #include "MagickCore/threshold.h"
81 #include "MagickCore/token.h"
82 #include "MagickCore/xml-tree.h"
83 #include "MagickCore/xml-tree-private.h"
84 \f
85 /*
86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 %                                                                             %
88 %                                                                             %
89 %                                                                             %
90 %     A u t o G a m m a I m a g e                                             %
91 %                                                                             %
92 %                                                                             %
93 %                                                                             %
94 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 %
96 %  AutoGammaImage() extract the 'mean' from the image and adjust the image
97 %  to try make set its gamma appropriatally.
98 %
99 %  The format of the AutoGammaImage method is:
100 %
101 %      MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
102 %
103 %  A description of each parameter follows:
104 %
105 %    o image: The image to auto-level
106 %
107 %    o exception: return any errors or warnings in this structure.
108 %
109 */
110 MagickExport MagickBooleanType AutoGammaImage(Image *image,
111   ExceptionInfo *exception)
112 {
113   double
114     gamma,
115     log_mean,
116     mean,
117     sans;
118
119   MagickStatusType
120     status;
121
122   register ssize_t
123     i;
124
125   log_mean=log(0.5);
126   if (image->channel_mask == DefaultChannels)
127     {
128       /*
129         Apply gamma correction equally across all given channels.
130       */
131       (void) GetImageMean(image,&mean,&sans,exception);
132       gamma=log(mean*QuantumScale)/log_mean;
133       return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
134     }
135   /*
136     Auto-gamma each channel separately.
137   */
138   status=MagickTrue;
139   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
140   {
141     ChannelType
142       channel_mask;
143
144     PixelChannel channel=GetPixelChannelChannel(image,i);
145     PixelTrait traits=GetPixelChannelTraits(image,channel);
146     if ((traits & UpdatePixelTrait) == 0)
147       continue;
148     channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
149     status=GetImageMean(image,&mean,&sans,exception);
150     gamma=log(mean*QuantumScale)/log_mean;
151     status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
152     (void) SetImageChannelMask(image,channel_mask);
153     if (status == MagickFalse)
154       break;
155   }
156   return(status != 0 ? MagickTrue : MagickFalse);
157 }
158 \f
159 /*
160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
161 %                                                                             %
162 %                                                                             %
163 %                                                                             %
164 %     A u t o L e v e l I m a g e                                             %
165 %                                                                             %
166 %                                                                             %
167 %                                                                             %
168 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169 %
170 %  AutoLevelImage() adjusts the levels of a particular image channel by
171 %  scaling the minimum and maximum values to the full quantum range.
172 %
173 %  The format of the LevelImage method is:
174 %
175 %      MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
176 %
177 %  A description of each parameter follows:
178 %
179 %    o image: The image to auto-level
180 %
181 %    o exception: return any errors or warnings in this structure.
182 %
183 */
184 MagickExport MagickBooleanType AutoLevelImage(Image *image,
185   ExceptionInfo *exception)
186 {
187   return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
188 }
189 \f
190 /*
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
192 %                                                                             %
193 %                                                                             %
194 %                                                                             %
195 %     B r i g h t n e s s C o n t r a s t I m a g e                           %
196 %                                                                             %
197 %                                                                             %
198 %                                                                             %
199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200 %
201 %  BrightnessContrastImage() changes the brightness and/or contrast of an
202 %  image.  It converts the brightness and contrast parameters into slope and
203 %  intercept and calls a polynomical function to apply to the image.
204 %
205 %  The format of the BrightnessContrastImage method is:
206 %
207 %      MagickBooleanType BrightnessContrastImage(Image *image,
208 %        const double brightness,const double contrast,ExceptionInfo *exception)
209 %
210 %  A description of each parameter follows:
211 %
212 %    o image: the image.
213 %
214 %    o brightness: the brightness percent (-100 .. 100).
215 %
216 %    o contrast: the contrast percent (-100 .. 100).
217 %
218 %    o exception: return any errors or warnings in this structure.
219 %
220 */
221 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
222   const double brightness,const double contrast,ExceptionInfo *exception)
223 {
224 #define BrightnessContastImageTag  "BrightnessContast/Image"
225
226   double
227     alpha,
228     coefficients[2],
229     intercept,
230     slope;
231
232   MagickBooleanType
233     status;
234
235   /*
236     Compute slope and intercept.
237   */
238   assert(image != (Image *) NULL);
239   assert(image->signature == MagickCoreSignature);
240   if (image->debug != MagickFalse)
241     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
242   alpha=contrast;
243   slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
244   if (slope < 0.0)
245     slope=0.0;
246   intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
247   coefficients[0]=slope;
248   coefficients[1]=intercept;
249   status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
250   return(status);
251 }
252 \f
253 /*
254 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
255 %                                                                             %
256 %                                                                             %
257 %                                                                             %
258 %     C l u t I m a g e                                                       %
259 %                                                                             %
260 %                                                                             %
261 %                                                                             %
262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 %
264 %  ClutImage() replaces each color value in the given image, by using it as an
265 %  index to lookup a replacement color value in a Color Look UP Table in the
266 %  form of an image.  The values are extracted along a diagonal of the CLUT
267 %  image so either a horizontal or vertial gradient image can be used.
268 %
269 %  Typically this is used to either re-color a gray-scale image according to a
270 %  color gradient in the CLUT image, or to perform a freeform histogram
271 %  (level) adjustment according to the (typically gray-scale) gradient in the
272 %  CLUT image.
273 %
274 %  When the 'channel' mask includes the matte/alpha transparency channel but
275 %  one image has no such channel it is assumed that that image is a simple
276 %  gray-scale image that will effect the alpha channel values, either for
277 %  gray-scale coloring (with transparent or semi-transparent colors), or
278 %  a histogram adjustment of existing alpha channel values.   If both images
279 %  have matte channels, direct and normal indexing is applied, which is rarely
280 %  used.
281 %
282 %  The format of the ClutImage method is:
283 %
284 %      MagickBooleanType ClutImage(Image *image,Image *clut_image,
285 %        const PixelInterpolateMethod method,ExceptionInfo *exception)
286 %
287 %  A description of each parameter follows:
288 %
289 %    o image: the image, which is replaced by indexed CLUT values
290 %
291 %    o clut_image: the color lookup table image for replacement color values.
292 %
293 %    o method: the pixel interpolation method.
294 %
295 %    o exception: return any errors or warnings in this structure.
296 %
297 */
298 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
299   const PixelInterpolateMethod method,ExceptionInfo *exception)
300 {
301 #define ClutImageTag  "Clut/Image"
302
303   CacheView
304     *clut_view,
305     *image_view;
306
307   MagickBooleanType
308     status;
309
310   MagickOffsetType
311     progress;
312
313   PixelInfo
314     *clut_map;
315
316   register ssize_t
317     i;
318
319   ssize_t adjust,
320     y;
321
322   assert(image != (Image *) NULL);
323   assert(image->signature == MagickCoreSignature);
324   if (image->debug != MagickFalse)
325     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
326   assert(clut_image != (Image *) NULL);
327   assert(clut_image->signature == MagickCoreSignature);
328   if( SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
329     return(MagickFalse);
330   if( (IsGrayColorspace(image->colorspace) != MagickFalse) &&
331       (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
332     (void) SetImageColorspace(image,sRGBColorspace,exception);
333   clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
334   if (clut_map == (PixelInfo *) NULL)
335     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
336       image->filename);
337   /*
338     Clut image.
339   */
340   status=MagickTrue;
341   progress=0;
342   adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
343   clut_view=AcquireVirtualCacheView(clut_image,exception);
344   for (i=0; i <= (ssize_t) MaxMap; i++)
345   {
346     GetPixelInfo(clut_image,clut_map+i);
347     (void) InterpolatePixelInfo(clut_image,clut_view,method,
348       (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
349       (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
350   }
351   clut_view=DestroyCacheView(clut_view);
352   image_view=AcquireAuthenticCacheView(image,exception);
353 #if defined(MAGICKCORE_OPENMP_SUPPORT)
354   #pragma omp parallel for schedule(static,4) shared(progress,status) \
355     magick_threads(image,image,image->rows,1)
356 #endif
357   for (y=0; y < (ssize_t) image->rows; y++)
358   {
359     PixelInfo
360       pixel;
361
362     register Quantum
363       *magick_restrict q;
364
365     register ssize_t
366       x;
367
368     if (status == MagickFalse)
369       continue;
370     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
371     if (q == (Quantum *) NULL)
372       {
373         status=MagickFalse;
374         continue;
375       }
376     GetPixelInfo(image,&pixel);
377     for (x=0; x < (ssize_t) image->columns; x++)
378     {
379       PixelTrait
380         traits;
381
382       if (GetPixelReadMask(image,q) == 0)
383         {
384           q+=GetPixelChannels(image);
385           continue;
386         }
387       GetPixelInfoPixel(image,q,&pixel);
388       traits=GetPixelChannelTraits(image,RedPixelChannel);
389       if ((traits & UpdatePixelTrait) != 0)
390         pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
391           pixel.red))].red;
392       traits=GetPixelChannelTraits(image,GreenPixelChannel);
393       if ((traits & UpdatePixelTrait) != 0)
394         pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
395           pixel.green))].green;
396       traits=GetPixelChannelTraits(image,BluePixelChannel);
397       if ((traits & UpdatePixelTrait) != 0)
398         pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
399           pixel.blue))].blue;
400       traits=GetPixelChannelTraits(image,BlackPixelChannel);
401       if ((traits & UpdatePixelTrait) != 0)
402         pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
403           pixel.black))].black;
404       traits=GetPixelChannelTraits(image,AlphaPixelChannel);
405       if ((traits & UpdatePixelTrait) != 0)
406         pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
407           pixel.alpha))].alpha;
408       SetPixelViaPixelInfo(image,&pixel,q);
409       q+=GetPixelChannels(image);
410     }
411     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
412       status=MagickFalse;
413     if (image->progress_monitor != (MagickProgressMonitor) NULL)
414       {
415         MagickBooleanType
416           proceed;
417
418 #if defined(MAGICKCORE_OPENMP_SUPPORT)
419         #pragma omp critical (MagickCore_ClutImage)
420 #endif
421         proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
422         if (proceed == MagickFalse)
423           status=MagickFalse;
424       }
425   }
426   image_view=DestroyCacheView(image_view);
427   clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
428   if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
429       ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
430     (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
431   return(status);
432 }
433 \f
434 /*
435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
436 %                                                                             %
437 %                                                                             %
438 %                                                                             %
439 %     C o l o r D e c i s i o n L i s t I m a g e                             %
440 %                                                                             %
441 %                                                                             %
442 %                                                                             %
443 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444 %
445 %  ColorDecisionListImage() accepts a lightweight Color Correction Collection
446 %  (CCC) file which solely contains one or more color corrections and applies
447 %  the correction to the image.  Here is a sample CCC file:
448 %
449 %    <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
450 %          <ColorCorrection id="cc03345">
451 %                <SOPNode>
452 %                     <Slope> 0.9 1.2 0.5 </Slope>
453 %                     <Offset> 0.4 -0.5 0.6 </Offset>
454 %                     <Power> 1.0 0.8 1.5 </Power>
455 %                </SOPNode>
456 %                <SATNode>
457 %                     <Saturation> 0.85 </Saturation>
458 %                </SATNode>
459 %          </ColorCorrection>
460 %    </ColorCorrectionCollection>
461 %
462 %  which includes the slop, offset, and power for each of the RGB channels
463 %  as well as the saturation.
464 %
465 %  The format of the ColorDecisionListImage method is:
466 %
467 %      MagickBooleanType ColorDecisionListImage(Image *image,
468 %        const char *color_correction_collection,ExceptionInfo *exception)
469 %
470 %  A description of each parameter follows:
471 %
472 %    o image: the image.
473 %
474 %    o color_correction_collection: the color correction collection in XML.
475 %
476 %    o exception: return any errors or warnings in this structure.
477 %
478 */
479 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
480   const char *color_correction_collection,ExceptionInfo *exception)
481 {
482 #define ColorDecisionListCorrectImageTag  "ColorDecisionList/Image"
483
484   typedef struct _Correction
485   {
486     double
487       slope,
488       offset,
489       power;
490   } Correction;
491
492   typedef struct _ColorCorrection
493   {
494     Correction
495       red,
496       green,
497       blue;
498
499     double
500       saturation;
501   } ColorCorrection;
502
503   CacheView
504     *image_view;
505
506   char
507     token[MagickPathExtent];
508
509   ColorCorrection
510     color_correction;
511
512   const char
513     *content,
514     *p;
515
516   MagickBooleanType
517     status;
518
519   MagickOffsetType
520     progress;
521
522   PixelInfo
523     *cdl_map;
524
525   register ssize_t
526     i;
527
528   ssize_t
529     y;
530
531   XMLTreeInfo
532     *cc,
533     *ccc,
534     *sat,
535     *sop;
536
537   /*
538     Allocate and initialize cdl maps.
539   */
540   assert(image != (Image *) NULL);
541   assert(image->signature == MagickCoreSignature);
542   if (image->debug != MagickFalse)
543     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
544   if (color_correction_collection == (const char *) NULL)
545     return(MagickFalse);
546   ccc=NewXMLTree((const char *) color_correction_collection,exception);
547   if (ccc == (XMLTreeInfo *) NULL)
548     return(MagickFalse);
549   cc=GetXMLTreeChild(ccc,"ColorCorrection");
550   if (cc == (XMLTreeInfo *) NULL)
551     {
552       ccc=DestroyXMLTree(ccc);
553       return(MagickFalse);
554     }
555   color_correction.red.slope=1.0;
556   color_correction.red.offset=0.0;
557   color_correction.red.power=1.0;
558   color_correction.green.slope=1.0;
559   color_correction.green.offset=0.0;
560   color_correction.green.power=1.0;
561   color_correction.blue.slope=1.0;
562   color_correction.blue.offset=0.0;
563   color_correction.blue.power=1.0;
564   color_correction.saturation=0.0;
565   sop=GetXMLTreeChild(cc,"SOPNode");
566   if (sop != (XMLTreeInfo *) NULL)
567     {
568       XMLTreeInfo
569         *offset,
570         *power,
571         *slope;
572
573       slope=GetXMLTreeChild(sop,"Slope");
574       if (slope != (XMLTreeInfo *) NULL)
575         {
576           content=GetXMLTreeContent(slope);
577           p=(const char *) content;
578           for (i=0; (*p != '\0') && (i < 3); i++)
579           {
580             GetNextToken(p,&p,MagickPathExtent,token);
581             if (*token == ',')
582               GetNextToken(p,&p,MagickPathExtent,token);
583             switch (i)
584             {
585               case 0:
586               {
587                 color_correction.red.slope=StringToDouble(token,(char **) NULL);
588                 break;
589               }
590               case 1:
591               {
592                 color_correction.green.slope=StringToDouble(token,
593                   (char **) NULL);
594                 break;
595               }
596               case 2:
597               {
598                 color_correction.blue.slope=StringToDouble(token,
599                   (char **) NULL);
600                 break;
601               }
602             }
603           }
604         }
605       offset=GetXMLTreeChild(sop,"Offset");
606       if (offset != (XMLTreeInfo *) NULL)
607         {
608           content=GetXMLTreeContent(offset);
609           p=(const char *) content;
610           for (i=0; (*p != '\0') && (i < 3); i++)
611           {
612             GetNextToken(p,&p,MagickPathExtent,token);
613             if (*token == ',')
614               GetNextToken(p,&p,MagickPathExtent,token);
615             switch (i)
616             {
617               case 0:
618               {
619                 color_correction.red.offset=StringToDouble(token,
620                   (char **) NULL);
621                 break;
622               }
623               case 1:
624               {
625                 color_correction.green.offset=StringToDouble(token,
626                   (char **) NULL);
627                 break;
628               }
629               case 2:
630               {
631                 color_correction.blue.offset=StringToDouble(token,
632                   (char **) NULL);
633                 break;
634               }
635             }
636           }
637         }
638       power=GetXMLTreeChild(sop,"Power");
639       if (power != (XMLTreeInfo *) NULL)
640         {
641           content=GetXMLTreeContent(power);
642           p=(const char *) content;
643           for (i=0; (*p != '\0') && (i < 3); i++)
644           {
645             GetNextToken(p,&p,MagickPathExtent,token);
646             if (*token == ',')
647               GetNextToken(p,&p,MagickPathExtent,token);
648             switch (i)
649             {
650               case 0:
651               {
652                 color_correction.red.power=StringToDouble(token,(char **) NULL);
653                 break;
654               }
655               case 1:
656               {
657                 color_correction.green.power=StringToDouble(token,
658                   (char **) NULL);
659                 break;
660               }
661               case 2:
662               {
663                 color_correction.blue.power=StringToDouble(token,
664                   (char **) NULL);
665                 break;
666               }
667             }
668           }
669         }
670     }
671   sat=GetXMLTreeChild(cc,"SATNode");
672   if (sat != (XMLTreeInfo *) NULL)
673     {
674       XMLTreeInfo
675         *saturation;
676
677       saturation=GetXMLTreeChild(sat,"Saturation");
678       if (saturation != (XMLTreeInfo *) NULL)
679         {
680           content=GetXMLTreeContent(saturation);
681           p=(const char *) content;
682           GetNextToken(p,&p,MagickPathExtent,token);
683           color_correction.saturation=StringToDouble(token,(char **) NULL);
684         }
685     }
686   ccc=DestroyXMLTree(ccc);
687   if (image->debug != MagickFalse)
688     {
689       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
690         "  Color Correction Collection:");
691       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
692         "  color_correction.red.slope: %g",color_correction.red.slope);
693       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
694         "  color_correction.red.offset: %g",color_correction.red.offset);
695       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
696         "  color_correction.red.power: %g",color_correction.red.power);
697       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
698         "  color_correction.green.slope: %g",color_correction.green.slope);
699       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
700         "  color_correction.green.offset: %g",color_correction.green.offset);
701       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
702         "  color_correction.green.power: %g",color_correction.green.power);
703       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
704         "  color_correction.blue.slope: %g",color_correction.blue.slope);
705       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
706         "  color_correction.blue.offset: %g",color_correction.blue.offset);
707       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
708         "  color_correction.blue.power: %g",color_correction.blue.power);
709       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
710         "  color_correction.saturation: %g",color_correction.saturation);
711     }
712   cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
713   if (cdl_map == (PixelInfo *) NULL)
714     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
715       image->filename);
716   for (i=0; i <= (ssize_t) MaxMap; i++)
717   {
718     cdl_map[i].red=(double) ScaleMapToQuantum((double)
719       (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
720       color_correction.red.offset,color_correction.red.power))));
721     cdl_map[i].green=(double) ScaleMapToQuantum((double)
722       (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
723       color_correction.green.offset,color_correction.green.power))));
724     cdl_map[i].blue=(double) ScaleMapToQuantum((double)
725       (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
726       color_correction.blue.offset,color_correction.blue.power))));
727   }
728   if (image->storage_class == PseudoClass)
729     for (i=0; i < (ssize_t) image->colors; i++)
730     {
731       /*
732         Apply transfer function to colormap.
733       */
734       double
735         luma;
736
737       luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
738         0.07217f*image->colormap[i].blue;
739       image->colormap[i].red=luma+color_correction.saturation*cdl_map[
740         ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
741       image->colormap[i].green=luma+color_correction.saturation*cdl_map[
742         ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
743       image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
744         ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
745     }
746   /*
747     Apply transfer function to image.
748   */
749   status=MagickTrue;
750   progress=0;
751   image_view=AcquireAuthenticCacheView(image,exception);
752 #if defined(MAGICKCORE_OPENMP_SUPPORT)
753   #pragma omp parallel for schedule(static,4) shared(progress,status) \
754     magick_threads(image,image,image->rows,1)
755 #endif
756   for (y=0; y < (ssize_t) image->rows; y++)
757   {
758     double
759       luma;
760
761     register Quantum
762       *magick_restrict q;
763
764     register ssize_t
765       x;
766
767     if (status == MagickFalse)
768       continue;
769     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
770     if (q == (Quantum *) NULL)
771       {
772         status=MagickFalse;
773         continue;
774       }
775     for (x=0; x < (ssize_t) image->columns; x++)
776     {
777       luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
778         0.07217f*GetPixelBlue(image,q);
779       SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
780         (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
781       SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
782         (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
783       SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
784         (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
785       q+=GetPixelChannels(image);
786     }
787     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
788       status=MagickFalse;
789     if (image->progress_monitor != (MagickProgressMonitor) NULL)
790       {
791         MagickBooleanType
792           proceed;
793
794 #if defined(MAGICKCORE_OPENMP_SUPPORT)
795         #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
796 #endif
797         proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
798           progress++,image->rows);
799         if (proceed == MagickFalse)
800           status=MagickFalse;
801       }
802   }
803   image_view=DestroyCacheView(image_view);
804   cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
805   return(status);
806 }
807 \f
808 /*
809 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
810 %                                                                             %
811 %                                                                             %
812 %                                                                             %
813 %     C o n t r a s t I m a g e                                               %
814 %                                                                             %
815 %                                                                             %
816 %                                                                             %
817 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818 %
819 %  ContrastImage() enhances the intensity differences between the lighter and
820 %  darker elements of the image.  Set sharpen to a MagickTrue to increase the
821 %  image contrast otherwise the contrast is reduced.
822 %
823 %  The format of the ContrastImage method is:
824 %
825 %      MagickBooleanType ContrastImage(Image *image,
826 %        const MagickBooleanType sharpen,ExceptionInfo *exception)
827 %
828 %  A description of each parameter follows:
829 %
830 %    o image: the image.
831 %
832 %    o sharpen: Increase or decrease image contrast.
833 %
834 %    o exception: return any errors or warnings in this structure.
835 %
836 */
837
838 static void Contrast(const int sign,double *red,double *green,double *blue)
839 {
840   double
841     brightness,
842     hue,
843     saturation;
844
845   /*
846     Enhance contrast: dark color become darker, light color become lighter.
847   */
848   assert(red != (double *) NULL);
849   assert(green != (double *) NULL);
850   assert(blue != (double *) NULL);
851   hue=0.0;
852   saturation=0.0;
853   brightness=0.0;
854   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
855   brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
856     brightness);
857   if (brightness > 1.0)
858     brightness=1.0;
859   else
860     if (brightness < 0.0)
861       brightness=0.0;
862   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
863 }
864
865 MagickExport MagickBooleanType ContrastImage(Image *image,
866   const MagickBooleanType sharpen,ExceptionInfo *exception)
867 {
868 #define ContrastImageTag  "Contrast/Image"
869
870   CacheView
871     *image_view;
872
873   int
874     sign;
875
876   MagickBooleanType
877     status;
878
879   MagickOffsetType
880     progress;
881
882   register ssize_t
883     i;
884
885   ssize_t
886     y;
887
888   assert(image != (Image *) NULL);
889   assert(image->signature == MagickCoreSignature);
890   if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
891     return(MagickTrue);
892   if (image->debug != MagickFalse)
893     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
894   sign=sharpen != MagickFalse ? 1 : -1;
895   if (image->storage_class == PseudoClass)
896     {
897       /*
898         Contrast enhance colormap.
899       */
900       for (i=0; i < (ssize_t) image->colors; i++)
901       {
902         double
903           blue,
904           green,
905           red;
906
907         red=(double) image->colormap[i].red;
908         green=(double) image->colormap[i].green;
909         blue=(double) image->colormap[i].blue;
910         Contrast(sign,&red,&green,&blue);
911         image->colormap[i].red=(MagickRealType) red;
912         image->colormap[i].green=(MagickRealType) green;
913         image->colormap[i].blue=(MagickRealType) blue;
914       }
915     }
916   /*
917     Contrast enhance image.
918   */
919   status=MagickTrue;
920   progress=0;
921   image_view=AcquireAuthenticCacheView(image,exception);
922 #if defined(MAGICKCORE_OPENMP_SUPPORT)
923   #pragma omp parallel for schedule(static,4) shared(progress,status) \
924     magick_threads(image,image,image->rows,1)
925 #endif
926   for (y=0; y < (ssize_t) image->rows; y++)
927   {
928     double
929       blue,
930       green,
931       red;
932
933     register Quantum
934       *magick_restrict q;
935
936     register ssize_t
937       x;
938
939     if (status == MagickFalse)
940       continue;
941     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
942     if (q == (Quantum *) NULL)
943       {
944         status=MagickFalse;
945         continue;
946       }
947     for (x=0; x < (ssize_t) image->columns; x++)
948     {
949       red=(double) GetPixelRed(image,q);
950       green=(double) GetPixelGreen(image,q);
951       blue=(double) GetPixelBlue(image,q);
952       Contrast(sign,&red,&green,&blue);
953       SetPixelRed(image,ClampToQuantum(red),q);
954       SetPixelGreen(image,ClampToQuantum(green),q);
955       SetPixelBlue(image,ClampToQuantum(blue),q);
956       q+=GetPixelChannels(image);
957     }
958     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
959       status=MagickFalse;
960     if (image->progress_monitor != (MagickProgressMonitor) NULL)
961       {
962         MagickBooleanType
963           proceed;
964
965 #if defined(MAGICKCORE_OPENMP_SUPPORT)
966         #pragma omp critical (MagickCore_ContrastImage)
967 #endif
968         proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
969         if (proceed == MagickFalse)
970           status=MagickFalse;
971       }
972   }
973   image_view=DestroyCacheView(image_view);
974   return(status);
975 }
976 \f
977 /*
978 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
979 %                                                                             %
980 %                                                                             %
981 %                                                                             %
982 %     C o n t r a s t S t r e t c h I m a g e                                 %
983 %                                                                             %
984 %                                                                             %
985 %                                                                             %
986 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987 %
988 %  ContrastStretchImage() is a simple image enhancement technique that attempts
989 %  to improve the contrast in an image by 'stretching' the range of intensity
990 %  values it contains to span a desired range of values. It differs from the
991 %  more sophisticated histogram equalization in that it can only apply a
992 %  linear scaling function to the image pixel values.  As a result the
993 %  'enhancement' is less harsh.
994 %
995 %  The format of the ContrastStretchImage method is:
996 %
997 %      MagickBooleanType ContrastStretchImage(Image *image,
998 %        const char *levels,ExceptionInfo *exception)
999 %
1000 %  A description of each parameter follows:
1001 %
1002 %    o image: the image.
1003 %
1004 %    o black_point: the black point.
1005 %
1006 %    o white_point: the white point.
1007 %
1008 %    o levels: Specify the levels where the black and white points have the
1009 %      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1010 %
1011 %    o exception: return any errors or warnings in this structure.
1012 %
1013 */
1014 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1015   const double black_point,const double white_point,ExceptionInfo *exception)
1016 {
1017 #define MaxRange(color)  ((double) ScaleQuantumToMap((Quantum) (color)))
1018 #define ContrastStretchImageTag  "ContrastStretch/Image"
1019
1020   CacheView
1021     *image_view;
1022
1023   double
1024     *black,
1025     *histogram,
1026     *stretch_map,
1027     *white;
1028
1029   MagickBooleanType
1030     status;
1031
1032   MagickOffsetType
1033     progress;
1034
1035   register ssize_t
1036     i;
1037
1038   ssize_t
1039     y;
1040
1041   /*
1042     Allocate histogram and stretch map.
1043   */
1044   assert(image != (Image *) NULL);
1045   assert(image->signature == MagickCoreSignature);
1046   if (image->debug != MagickFalse)
1047     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1048   if (SetImageGray(image,exception) != MagickFalse)
1049     (void) SetImageColorspace(image,GRAYColorspace,exception);
1050   black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1051   white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1052   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1053     sizeof(*histogram));
1054   stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1055     GetPixelChannels(image)*sizeof(*stretch_map));
1056   if ((black == (double *) NULL) || (white == (double *) NULL) ||
1057       (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1058     {
1059       if (stretch_map != (double *) NULL)
1060         stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1061       if (histogram != (double *) NULL)
1062         histogram=(double *) RelinquishMagickMemory(histogram);
1063       if (white != (double *) NULL)
1064         white=(double *) RelinquishMagickMemory(white);
1065       if (black != (double *) NULL)
1066         black=(double *) RelinquishMagickMemory(black);
1067       ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1068         image->filename);
1069     }
1070   /*
1071     Form histogram.
1072   */
1073   status=MagickTrue;
1074   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1075     sizeof(*histogram));
1076   image_view=AcquireVirtualCacheView(image,exception);
1077   for (y=0; y < (ssize_t) image->rows; y++)
1078   {
1079     register const Quantum
1080       *magick_restrict p;
1081
1082     register ssize_t
1083       x;
1084
1085     if (status == MagickFalse)
1086       continue;
1087     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1088     if (p == (const Quantum *) NULL)
1089       {
1090         status=MagickFalse;
1091         continue;
1092       }
1093     for (x=0; x < (ssize_t) image->columns; x++)
1094     {
1095       double
1096         pixel;
1097
1098       pixel=GetPixelIntensity(image,p);
1099       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1100       {
1101         if (image->channel_mask != DefaultChannels)
1102           pixel=(double) p[i];
1103         histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1104           ClampToQuantum(pixel))+i]++;
1105       }
1106       p+=GetPixelChannels(image);
1107     }
1108   }
1109   image_view=DestroyCacheView(image_view);
1110   /*
1111     Find the histogram boundaries by locating the black/white levels.
1112   */
1113   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1114   {
1115     double
1116       intensity;
1117
1118     register ssize_t
1119       j;
1120
1121     black[i]=0.0;
1122     white[i]=MaxRange(QuantumRange);
1123     intensity=0.0;
1124     for (j=0; j <= (ssize_t) MaxMap; j++)
1125     {
1126       intensity+=histogram[GetPixelChannels(image)*j+i];
1127       if (intensity > black_point)
1128         break;
1129     }
1130     black[i]=(double) j;
1131     intensity=0.0;
1132     for (j=(ssize_t) MaxMap; j != 0; j--)
1133     {
1134       intensity+=histogram[GetPixelChannels(image)*j+i];
1135       if (intensity > ((double) image->columns*image->rows-white_point))
1136         break;
1137     }
1138     white[i]=(double) j;
1139   }
1140   histogram=(double *) RelinquishMagickMemory(histogram);
1141   /*
1142     Stretch the histogram to create the stretched image mapping.
1143   */
1144   (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1145     sizeof(*stretch_map));
1146   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1147   {
1148     register ssize_t
1149       j;
1150
1151     for (j=0; j <= (ssize_t) MaxMap; j++)
1152     {
1153       double
1154         gamma;
1155
1156       gamma=PerceptibleReciprocal(white[i]-black[i]);
1157       if (j < (ssize_t) black[i])
1158         stretch_map[GetPixelChannels(image)*j+i]=0.0;
1159       else
1160         if (j > (ssize_t) white[i])
1161           stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1162         else
1163           stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1164             (double) (MaxMap*gamma*(j-black[i])));
1165     }
1166   }
1167   if (image->storage_class == PseudoClass)
1168     {
1169       register ssize_t
1170         j;
1171
1172       /*
1173         Stretch-contrast colormap.
1174       */
1175       for (j=0; j < (ssize_t) image->colors; j++)
1176       {
1177         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1178           {
1179             i=GetPixelChannelOffset(image,RedPixelChannel);
1180             image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1181               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1182           }
1183         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1184           {
1185             i=GetPixelChannelOffset(image,GreenPixelChannel);
1186             image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1187               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1188           }
1189         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1190           {
1191             i=GetPixelChannelOffset(image,BluePixelChannel);
1192             image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1193               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1194           }
1195         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1196           {
1197             i=GetPixelChannelOffset(image,AlphaPixelChannel);
1198             image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1199               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1200           }
1201       }
1202     }
1203   /*
1204     Stretch-contrast image.
1205   */
1206   status=MagickTrue;
1207   progress=0;
1208   image_view=AcquireAuthenticCacheView(image,exception);
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1210   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1211     magick_threads(image,image,image->rows,1)
1212 #endif
1213   for (y=0; y < (ssize_t) image->rows; y++)
1214   {
1215     register Quantum
1216       *magick_restrict q;
1217
1218     register ssize_t
1219       x;
1220
1221     if (status == MagickFalse)
1222       continue;
1223     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1224     if (q == (Quantum *) NULL)
1225       {
1226         status=MagickFalse;
1227         continue;
1228       }
1229     for (x=0; x < (ssize_t) image->columns; x++)
1230     {
1231       register ssize_t
1232         j;
1233
1234       if (GetPixelReadMask(image,q) == 0)
1235         {
1236           q+=GetPixelChannels(image);
1237           continue;
1238         }
1239       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1240       {
1241         PixelChannel channel=GetPixelChannelChannel(image,j);
1242         PixelTrait traits=GetPixelChannelTraits(image,channel);
1243         if ((traits & UpdatePixelTrait) == 0)
1244           continue;
1245         q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1246           ScaleQuantumToMap(q[j])+j]);
1247       }
1248       q+=GetPixelChannels(image);
1249     }
1250     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1251       status=MagickFalse;
1252     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1253       {
1254         MagickBooleanType
1255           proceed;
1256
1257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1258         #pragma omp critical (MagickCore_ContrastStretchImage)
1259 #endif
1260         proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1261           image->rows);
1262         if (proceed == MagickFalse)
1263           status=MagickFalse;
1264       }
1265   }
1266   image_view=DestroyCacheView(image_view);
1267   stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1268   white=(double *) RelinquishMagickMemory(white);
1269   black=(double *) RelinquishMagickMemory(black);
1270   return(status);
1271 }
1272 \f
1273 /*
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1275 %                                                                             %
1276 %                                                                             %
1277 %                                                                             %
1278 %     E n h a n c e I m a g e                                                 %
1279 %                                                                             %
1280 %                                                                             %
1281 %                                                                             %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1283 %
1284 %  EnhanceImage() applies a digital filter that improves the quality of a
1285 %  noisy image.
1286 %
1287 %  The format of the EnhanceImage method is:
1288 %
1289 %      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1290 %
1291 %  A description of each parameter follows:
1292 %
1293 %    o image: the image.
1294 %
1295 %    o exception: return any errors or warnings in this structure.
1296 %
1297 */
1298 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1299 {
1300 #define EnhanceImageTag  "Enhance/Image"
1301 #define EnhancePixel(weight) \
1302   mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1303   distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1304   distance_squared=(4.0+mean)*distance*distance; \
1305   mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1306   distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1307   distance_squared+=(7.0-mean)*distance*distance; \
1308   mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1309   distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1310   distance_squared+=(5.0-mean)*distance*distance; \
1311   mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1312   distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1313   distance_squared+=(5.0-mean)*distance*distance; \
1314   mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1315   distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1316   distance_squared+=(5.0-mean)*distance*distance; \
1317   if (distance_squared < 0.069) \
1318     { \
1319       aggregate.red+=(weight)*GetPixelRed(image,r); \
1320       aggregate.green+=(weight)*GetPixelGreen(image,r); \
1321       aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1322       aggregate.black+=(weight)*GetPixelBlack(image,r); \
1323       aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1324       total_weight+=(weight); \
1325     } \
1326   r+=GetPixelChannels(image);
1327
1328   CacheView
1329     *enhance_view,
1330     *image_view;
1331
1332   Image
1333     *enhance_image;
1334
1335   MagickBooleanType
1336     status;
1337
1338   MagickOffsetType
1339     progress;
1340
1341   ssize_t
1342     y;
1343
1344   /*
1345     Initialize enhanced image attributes.
1346   */
1347   assert(image != (const Image *) NULL);
1348   assert(image->signature == MagickCoreSignature);
1349   if (image->debug != MagickFalse)
1350     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1351   assert(exception != (ExceptionInfo *) NULL);
1352   assert(exception->signature == MagickCoreSignature);
1353   enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1354     exception);
1355   if (enhance_image == (Image *) NULL)
1356     return((Image *) NULL);
1357   if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1358     {
1359       enhance_image=DestroyImage(enhance_image);
1360       return((Image *) NULL);
1361     }
1362   /*
1363     Enhance image.
1364   */
1365   status=MagickTrue;
1366   progress=0;
1367   image_view=AcquireVirtualCacheView(image,exception);
1368   enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1370   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1371     magick_threads(image,enhance_image,image->rows,1)
1372 #endif
1373   for (y=0; y < (ssize_t) image->rows; y++)
1374   {
1375     PixelInfo
1376       pixel;
1377
1378     register const Quantum
1379       *magick_restrict p;
1380
1381     register Quantum
1382       *magick_restrict q;
1383
1384     register ssize_t
1385       x;
1386
1387     ssize_t
1388       center;
1389
1390     if (status == MagickFalse)
1391       continue;
1392     p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1393     q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1394       exception);
1395     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1396       {
1397         status=MagickFalse;
1398         continue;
1399       }
1400     center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1401     GetPixelInfo(image,&pixel);
1402     for (x=0; x < (ssize_t) image->columns; x++)
1403     {
1404       double
1405         distance,
1406         distance_squared,
1407         mean,
1408         total_weight;
1409
1410       PixelInfo
1411         aggregate;
1412
1413       register const Quantum
1414         *magick_restrict r;
1415
1416       if (GetPixelReadMask(image,p) == 0)
1417         {
1418           SetPixelBackgoundColor(enhance_image,q);
1419           p+=GetPixelChannels(image);
1420           q+=GetPixelChannels(enhance_image);
1421           continue;
1422         }
1423       GetPixelInfo(image,&aggregate);
1424       total_weight=0.0;
1425       GetPixelInfoPixel(image,p+center,&pixel);
1426       r=p;
1427       EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1428         EnhancePixel(8.0); EnhancePixel(5.0);
1429       r=p+GetPixelChannels(image)*(image->columns+4);
1430       EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1431         EnhancePixel(20.0); EnhancePixel(8.0);
1432       r=p+2*GetPixelChannels(image)*(image->columns+4);
1433       EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1434         EnhancePixel(40.0); EnhancePixel(10.0);
1435       r=p+3*GetPixelChannels(image)*(image->columns+4);
1436       EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1437         EnhancePixel(20.0); EnhancePixel(8.0);
1438       r=p+4*GetPixelChannels(image)*(image->columns+4);
1439       EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1440         EnhancePixel(8.0); EnhancePixel(5.0);
1441       pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1442       pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1443       pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1444       pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1445       pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1446       SetPixelViaPixelInfo(image,&pixel,q);
1447       p+=GetPixelChannels(image);
1448       q+=GetPixelChannels(enhance_image);
1449     }
1450     if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1451       status=MagickFalse;
1452     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1453       {
1454         MagickBooleanType
1455           proceed;
1456
1457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1458         #pragma omp critical (MagickCore_EnhanceImage)
1459 #endif
1460         proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1461         if (proceed == MagickFalse)
1462           status=MagickFalse;
1463       }
1464   }
1465   enhance_view=DestroyCacheView(enhance_view);
1466   image_view=DestroyCacheView(image_view);
1467   if (status == MagickFalse)
1468     enhance_image=DestroyImage(enhance_image);
1469   return(enhance_image);
1470 }
1471 \f
1472 /*
1473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1474 %                                                                             %
1475 %                                                                             %
1476 %                                                                             %
1477 %     E q u a l i z e I m a g e                                               %
1478 %                                                                             %
1479 %                                                                             %
1480 %                                                                             %
1481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1482 %
1483 %  EqualizeImage() applies a histogram equalization to the image.
1484 %
1485 %  The format of the EqualizeImage method is:
1486 %
1487 %      MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1488 %
1489 %  A description of each parameter follows:
1490 %
1491 %    o image: the image.
1492 %
1493 %    o exception: return any errors or warnings in this structure.
1494 %
1495 */
1496 MagickExport MagickBooleanType EqualizeImage(Image *image,
1497   ExceptionInfo *exception)
1498 {
1499 #define EqualizeImageTag  "Equalize/Image"
1500
1501   CacheView
1502     *image_view;
1503
1504   double
1505     black[CompositePixelChannel+1],
1506     *equalize_map,
1507     *histogram,
1508     *map,
1509     white[CompositePixelChannel+1];
1510
1511   MagickBooleanType
1512     status;
1513
1514   MagickOffsetType
1515     progress;
1516
1517   register ssize_t
1518     i;
1519
1520   ssize_t
1521     y;
1522
1523   /*
1524     Allocate and initialize histogram arrays.
1525   */
1526   assert(image != (Image *) NULL);
1527   assert(image->signature == MagickCoreSignature);
1528   if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1529     return(MagickTrue);
1530   if (image->debug != MagickFalse)
1531     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1532   equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1533     GetPixelChannels(image)*sizeof(*equalize_map));
1534   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1535     sizeof(*histogram));
1536   map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1537     sizeof(*map));
1538   if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1539       (map == (double *) NULL))
1540     {
1541       if (map != (double *) NULL)
1542         map=(double *) RelinquishMagickMemory(map);
1543       if (histogram != (double *) NULL)
1544         histogram=(double *) RelinquishMagickMemory(histogram);
1545       if (equalize_map != (double *) NULL)
1546         equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1547       ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1548         image->filename);
1549     }
1550   /*
1551     Form histogram.
1552   */
1553   status=MagickTrue;
1554   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1555     sizeof(*histogram));
1556   image_view=AcquireVirtualCacheView(image,exception);
1557   for (y=0; y < (ssize_t) image->rows; y++)
1558   {
1559     register const Quantum
1560       *magick_restrict p;
1561
1562     register ssize_t
1563       x;
1564
1565     if (status == MagickFalse)
1566       continue;
1567     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1568     if (p == (const Quantum *) NULL)
1569       {
1570         status=MagickFalse;
1571         continue;
1572       }
1573     for (x=0; x < (ssize_t) image->columns; x++)
1574     {
1575       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1576       {
1577         double
1578           intensity;
1579
1580         intensity=p[i];
1581         if ((image->channel_mask & SyncChannels) != 0)
1582           intensity=GetPixelIntensity(image,p);
1583         histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1584       }
1585       p+=GetPixelChannels(image);
1586     }
1587   }
1588   image_view=DestroyCacheView(image_view);
1589   /*
1590     Integrate the histogram to get the equalization map.
1591   */
1592   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1593   {
1594     double
1595       intensity;
1596
1597     register ssize_t
1598       j;
1599
1600     intensity=0.0;
1601     for (j=0; j <= (ssize_t) MaxMap; j++)
1602     {
1603       intensity+=histogram[GetPixelChannels(image)*j+i];
1604       map[GetPixelChannels(image)*j+i]=intensity;
1605     }
1606   }
1607   (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1608     sizeof(*equalize_map));
1609   (void) ResetMagickMemory(black,0,sizeof(*black));
1610   (void) ResetMagickMemory(white,0,sizeof(*white));
1611   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1612   {
1613     register ssize_t
1614       j;
1615
1616     black[i]=map[i];
1617     white[i]=map[GetPixelChannels(image)*MaxMap+i];
1618     if (black[i] != white[i])
1619       for (j=0; j <= (ssize_t) MaxMap; j++)
1620         equalize_map[GetPixelChannels(image)*j+i]=(double)
1621           ScaleMapToQuantum((double) ((MaxMap*(map[
1622           GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1623   }
1624   histogram=(double *) RelinquishMagickMemory(histogram);
1625   map=(double *) RelinquishMagickMemory(map);
1626   if (image->storage_class == PseudoClass)
1627     {
1628       register ssize_t
1629         j;
1630
1631       /*
1632         Equalize colormap.
1633       */
1634       for (j=0; j < (ssize_t) image->colors; j++)
1635       {
1636         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1637           {
1638             PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1639             if (black[channel] != white[channel])
1640               image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1641                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1642                 channel];
1643           }
1644         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1645           {
1646             PixelChannel channel=GetPixelChannelChannel(image,
1647               GreenPixelChannel);
1648             if (black[channel] != white[channel])
1649               image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1650                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1651                 channel];
1652           }
1653         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1654           {
1655             PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1656             if (black[channel] != white[channel])
1657               image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1658                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1659                 channel];
1660           }
1661         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1662           {
1663             PixelChannel channel=GetPixelChannelChannel(image,
1664               AlphaPixelChannel);
1665             if (black[channel] != white[channel])
1666               image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1667                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1668                 channel];
1669           }
1670       }
1671     }
1672   /*
1673     Equalize image.
1674   */
1675   progress=0;
1676   image_view=AcquireAuthenticCacheView(image,exception);
1677 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1678   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1679     magick_threads(image,image,image->rows,1)
1680 #endif
1681   for (y=0; y < (ssize_t) image->rows; y++)
1682   {
1683     register Quantum
1684       *magick_restrict q;
1685
1686     register ssize_t
1687       x;
1688
1689     if (status == MagickFalse)
1690       continue;
1691     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1692     if (q == (Quantum *) NULL)
1693       {
1694         status=MagickFalse;
1695         continue;
1696       }
1697     for (x=0; x < (ssize_t) image->columns; x++)
1698     {
1699       register ssize_t
1700         j;
1701
1702       if (GetPixelReadMask(image,q) == 0)
1703         {
1704           q+=GetPixelChannels(image);
1705           continue;
1706         }
1707       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1708       {
1709         PixelChannel channel=GetPixelChannelChannel(image,j);
1710         PixelTrait traits=GetPixelChannelTraits(image,channel);
1711         if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1712           continue;
1713         q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1714           ScaleQuantumToMap(q[j])+j]);
1715       }
1716       q+=GetPixelChannels(image);
1717     }
1718     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1719       status=MagickFalse;
1720     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1721       {
1722         MagickBooleanType
1723           proceed;
1724
1725 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1726         #pragma omp critical (MagickCore_EqualizeImage)
1727 #endif
1728         proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1729         if (proceed == MagickFalse)
1730           status=MagickFalse;
1731       }
1732   }
1733   image_view=DestroyCacheView(image_view);
1734   equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1735   return(status);
1736 }
1737 \f
1738 /*
1739 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1740 %                                                                             %
1741 %                                                                             %
1742 %                                                                             %
1743 %     G a m m a I m a g e                                                     %
1744 %                                                                             %
1745 %                                                                             %
1746 %                                                                             %
1747 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1748 %
1749 %  GammaImage() gamma-corrects a particular image channel.  The same
1750 %  image viewed on different devices will have perceptual differences in the
1751 %  way the image's intensities are represented on the screen.  Specify
1752 %  individual gamma levels for the red, green, and blue channels, or adjust
1753 %  all three with the gamma parameter.  Values typically range from 0.8 to 2.3.
1754 %
1755 %  You can also reduce the influence of a particular channel with a gamma
1756 %  value of 0.
1757 %
1758 %  The format of the GammaImage method is:
1759 %
1760 %      MagickBooleanType GammaImage(Image *image,const double gamma,
1761 %        ExceptionInfo *exception)
1762 %
1763 %  A description of each parameter follows:
1764 %
1765 %    o image: the image.
1766 %
1767 %    o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1768 %
1769 %    o gamma: the image gamma.
1770 %
1771 */
1772
1773 static inline double gamma_pow(const double value,const double gamma)
1774 {
1775   return(value < 0.0 ? value : pow(value,gamma));
1776 }
1777
1778 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1779   ExceptionInfo *exception)
1780 {
1781 #define GammaCorrectImageTag  "GammaCorrect/Image"
1782
1783   CacheView
1784     *image_view;
1785
1786   MagickBooleanType
1787     status;
1788
1789   MagickOffsetType
1790     progress;
1791
1792   Quantum
1793     *gamma_map;
1794
1795   register ssize_t
1796     i;
1797
1798   ssize_t
1799     y;
1800
1801   /*
1802     Allocate and initialize gamma maps.
1803   */
1804   assert(image != (Image *) NULL);
1805   assert(image->signature == MagickCoreSignature);
1806   if (image->debug != MagickFalse)
1807     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1808   if (gamma == 1.0)
1809     return(MagickTrue);
1810   gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1811   if (gamma_map == (Quantum *) NULL)
1812     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1813       image->filename);
1814   (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1815   if (gamma != 0.0)
1816     for (i=0; i <= (ssize_t) MaxMap; i++)
1817       gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1818         MaxMap,1.0/gamma)));
1819   if (image->storage_class == PseudoClass)
1820     for (i=0; i < (ssize_t) image->colors; i++)
1821     {
1822       /*
1823         Gamma-correct colormap.
1824       */
1825 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1826       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1827         image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1828           ClampToQuantum(image->colormap[i].red))];
1829       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1830         image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1831           ClampToQuantum(image->colormap[i].green))];
1832       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1833         image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1834           ClampToQuantum(image->colormap[i].blue))];
1835       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1836         image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1837           ClampToQuantum(image->colormap[i].alpha))];
1838 #else
1839       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1840         image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1841           image->colormap[i].red,1.0/gamma);
1842       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1843         image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1844           image->colormap[i].green,1.0/gamma);
1845       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1846         image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1847           image->colormap[i].blue,1.0/gamma);
1848       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1849         image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1850           image->colormap[i].alpha,1.0/gamma);
1851 #endif
1852     }
1853   /*
1854     Gamma-correct image.
1855   */
1856   status=MagickTrue;
1857   progress=0;
1858   image_view=AcquireAuthenticCacheView(image,exception);
1859 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1860   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1861     magick_threads(image,image,image->rows,1)
1862 #endif
1863   for (y=0; y < (ssize_t) image->rows; y++)
1864   {
1865     register Quantum
1866       *magick_restrict q;
1867
1868     register ssize_t
1869       x;
1870
1871     if (status == MagickFalse)
1872       continue;
1873     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1874     if (q == (Quantum *) NULL)
1875       {
1876         status=MagickFalse;
1877         continue;
1878       }
1879     for (x=0; x < (ssize_t) image->columns; x++)
1880     {
1881       register ssize_t
1882         j;
1883
1884       if (GetPixelReadMask(image,q) == 0)
1885         {
1886           q+=GetPixelChannels(image);
1887           continue;
1888         }
1889       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1890       {
1891         PixelChannel channel=GetPixelChannelChannel(image,j);
1892         PixelTrait traits=GetPixelChannelTraits(image,channel);
1893         if ((traits & UpdatePixelTrait) == 0)
1894           continue;
1895 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1896         q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1897 #else
1898         q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1899 #endif
1900       }
1901       q+=GetPixelChannels(image);
1902     }
1903     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1904       status=MagickFalse;
1905     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1906       {
1907         MagickBooleanType
1908           proceed;
1909
1910 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1911         #pragma omp critical (MagickCore_GammaImage)
1912 #endif
1913         proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1914           image->rows);
1915         if (proceed == MagickFalse)
1916           status=MagickFalse;
1917       }
1918   }
1919   image_view=DestroyCacheView(image_view);
1920   gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1921   if (image->gamma != 0.0)
1922     image->gamma*=gamma;
1923   return(status);
1924 }
1925 \f
1926 /*
1927 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928 %                                                                             %
1929 %                                                                             %
1930 %                                                                             %
1931 %     G r a y s c a l e I m a g e                                             %
1932 %                                                                             %
1933 %                                                                             %
1934 %                                                                             %
1935 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936 %
1937 %  GrayscaleImage() converts the image to grayscale.
1938 %
1939 %  The format of the GrayscaleImage method is:
1940 %
1941 %      MagickBooleanType GrayscaleImage(Image *image,
1942 %        const PixelIntensityMethod method ,ExceptionInfo *exception)
1943 %
1944 %  A description of each parameter follows:
1945 %
1946 %    o image: the image.
1947 %
1948 %    o method: the pixel intensity method.
1949 %
1950 %    o exception: return any errors or warnings in this structure.
1951 %
1952 */
1953 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1954   const PixelIntensityMethod method,ExceptionInfo *exception)
1955 {
1956 #define GrayscaleImageTag  "Grayscale/Image"
1957
1958   CacheView
1959     *image_view;
1960
1961   MagickBooleanType
1962     status;
1963
1964   MagickOffsetType
1965     progress;
1966
1967   ssize_t
1968     y;
1969
1970   assert(image != (Image *) NULL);
1971   assert(image->signature == MagickCoreSignature);
1972   if (image->debug != MagickFalse)
1973     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1974   if (image->storage_class == PseudoClass)
1975     {
1976       if (SyncImage(image,exception) == MagickFalse)
1977         return(MagickFalse);
1978       if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1979         return(MagickFalse);
1980     }
1981   if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1982     {
1983       image->intensity=method;
1984       image->type=GrayscaleType;
1985       return(SetImageColorspace(image,GRAYColorspace,exception));
1986     }
1987   /*
1988     Grayscale image.
1989   */
1990   status=MagickTrue;
1991   progress=0;
1992   image_view=AcquireAuthenticCacheView(image,exception);
1993 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1994   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1995     magick_threads(image,image,image->rows,1)
1996 #endif
1997   for (y=0; y < (ssize_t) image->rows; y++)
1998   {
1999     register Quantum
2000       *magick_restrict q;
2001
2002     register ssize_t
2003       x;
2004
2005     if (status == MagickFalse)
2006       continue;
2007     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2008     if (q == (Quantum *) NULL)
2009       {
2010         status=MagickFalse;
2011         continue;
2012       }
2013     for (x=0; x < (ssize_t) image->columns; x++)
2014     {
2015       MagickRealType
2016         blue,
2017         green,
2018         red,
2019         intensity;
2020
2021       if (GetPixelReadMask(image,q) == 0)
2022         {
2023           q+=GetPixelChannels(image);
2024           continue;
2025         }
2026       red=(MagickRealType) GetPixelRed(image,q);
2027       green=(MagickRealType) GetPixelGreen(image,q);
2028       blue=(MagickRealType) GetPixelBlue(image,q);
2029       intensity=0.0;
2030       switch (method)
2031       {
2032         case AveragePixelIntensityMethod:
2033         {
2034           intensity=(red+green+blue)/3.0;
2035           break;
2036         }
2037         case BrightnessPixelIntensityMethod:
2038         {
2039           intensity=MagickMax(MagickMax(red,green),blue);
2040           break;
2041         }
2042         case LightnessPixelIntensityMethod:
2043         {
2044           intensity=(MagickMin(MagickMin(red,green),blue)+
2045             MagickMax(MagickMax(red,green),blue))/2.0;
2046           break;
2047         }
2048         case MSPixelIntensityMethod:
2049         {
2050           intensity=(MagickRealType) (((double) red*red+green*green+
2051             blue*blue)/3.0);
2052           break;
2053         }
2054         case Rec601LumaPixelIntensityMethod:
2055         {
2056           if (image->colorspace == RGBColorspace)
2057             {
2058               red=EncodePixelGamma(red);
2059               green=EncodePixelGamma(green);
2060               blue=EncodePixelGamma(blue);
2061             }
2062           intensity=0.298839*red+0.586811*green+0.114350*blue;
2063           break;
2064         }
2065         case Rec601LuminancePixelIntensityMethod:
2066         {
2067           if (image->colorspace == sRGBColorspace)
2068             {
2069               red=DecodePixelGamma(red);
2070               green=DecodePixelGamma(green);
2071               blue=DecodePixelGamma(blue);
2072             }
2073           intensity=0.298839*red+0.586811*green+0.114350*blue;
2074           break;
2075         }
2076         case Rec709LumaPixelIntensityMethod:
2077         default:
2078         {
2079           if (image->colorspace == RGBColorspace)
2080             {
2081               red=EncodePixelGamma(red);
2082               green=EncodePixelGamma(green);
2083               blue=EncodePixelGamma(blue);
2084             }
2085           intensity=0.212656*red+0.715158*green+0.072186*blue;
2086           break;
2087         }
2088         case Rec709LuminancePixelIntensityMethod:
2089         {
2090           if (image->colorspace == sRGBColorspace)
2091             {
2092               red=DecodePixelGamma(red);
2093               green=DecodePixelGamma(green);
2094               blue=DecodePixelGamma(blue);
2095             }
2096           intensity=0.212656*red+0.715158*green+0.072186*blue;
2097           break;
2098         }
2099         case RMSPixelIntensityMethod:
2100         {
2101           intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2102             blue*blue)/sqrt(3.0));
2103           break;
2104         }
2105       }
2106       SetPixelGray(image,ClampToQuantum(intensity),q);
2107       q+=GetPixelChannels(image);
2108     }
2109     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2110       status=MagickFalse;
2111     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2112       {
2113         MagickBooleanType
2114           proceed;
2115
2116 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2117         #pragma omp critical (MagickCore_GrayscaleImage)
2118 #endif
2119         proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2120            image->rows);
2121         if (proceed == MagickFalse)
2122           status=MagickFalse;
2123       }
2124   }
2125   image_view=DestroyCacheView(image_view);
2126   image->intensity=method;
2127   image->type=GrayscaleType;
2128   return(SetImageColorspace(image,GRAYColorspace,exception));
2129 }
2130 \f
2131 /*
2132 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2133 %                                                                             %
2134 %                                                                             %
2135 %                                                                             %
2136 %     H a l d C l u t I m a g e                                               %
2137 %                                                                             %
2138 %                                                                             %
2139 %                                                                             %
2140 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2141 %
2142 %  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
2143 %  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2144 %  Create it with the HALD coder.  You can apply any color transformation to
2145 %  the Hald image and then use this method to apply the transform to the
2146 %  image.
2147 %
2148 %  The format of the HaldClutImage method is:
2149 %
2150 %      MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2151 %        ExceptionInfo *exception)
2152 %
2153 %  A description of each parameter follows:
2154 %
2155 %    o image: the image, which is replaced by indexed CLUT values
2156 %
2157 %    o hald_image: the color lookup table image for replacement color values.
2158 %
2159 %    o exception: return any errors or warnings in this structure.
2160 %
2161 */
2162 MagickExport MagickBooleanType HaldClutImage(Image *image,
2163   const Image *hald_image,ExceptionInfo *exception)
2164 {
2165 #define HaldClutImageTag  "Clut/Image"
2166
2167   typedef struct _HaldInfo
2168   {
2169     double
2170       x,
2171       y,
2172       z;
2173   } HaldInfo;
2174
2175   CacheView
2176     *hald_view,
2177     *image_view;
2178
2179   double
2180     width;
2181
2182   MagickBooleanType
2183     status;
2184
2185   MagickOffsetType
2186     progress;
2187
2188   PixelInfo
2189     zero;
2190
2191   size_t
2192     cube_size,
2193     length,
2194     level;
2195
2196   ssize_t
2197     y;
2198
2199   assert(image != (Image *) NULL);
2200   assert(image->signature == MagickCoreSignature);
2201   if (image->debug != MagickFalse)
2202     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2203   assert(hald_image != (Image *) NULL);
2204   assert(hald_image->signature == MagickCoreSignature);
2205   if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2206     return(MagickFalse);
2207   if (image->alpha_trait == UndefinedPixelTrait)
2208     (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2209   /*
2210     Hald clut image.
2211   */
2212   status=MagickTrue;
2213   progress=0;
2214   length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2215     (MagickRealType) hald_image->rows);
2216   for (level=2; (level*level*level) < length; level++) ;
2217   level*=level;
2218   cube_size=level*level;
2219   width=(double) hald_image->columns;
2220   GetPixelInfo(hald_image,&zero);
2221   hald_view=AcquireVirtualCacheView(hald_image,exception);
2222   image_view=AcquireAuthenticCacheView(image,exception);
2223 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2224   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2225     magick_threads(image,image,image->rows,1)
2226 #endif
2227   for (y=0; y < (ssize_t) image->rows; y++)
2228   {
2229     register Quantum
2230       *magick_restrict q;
2231
2232     register ssize_t
2233       x;
2234
2235     if (status == MagickFalse)
2236       continue;
2237     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2238     if (q == (Quantum *) NULL)
2239       {
2240         status=MagickFalse;
2241         continue;
2242       }
2243     for (x=0; x < (ssize_t) image->columns; x++)
2244     {
2245       double
2246         offset;
2247
2248       HaldInfo
2249         point;
2250
2251       PixelInfo
2252         pixel,
2253         pixel1,
2254         pixel2,
2255         pixel3,
2256         pixel4;
2257
2258       point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2259       point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2260       point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2261       offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2262       point.x-=floor(point.x);
2263       point.y-=floor(point.y);
2264       point.z-=floor(point.z);
2265       pixel1=zero;
2266       (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2267         fmod(offset,width),floor(offset/width),&pixel1,exception);
2268       pixel2=zero;
2269       (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2270         fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2271       pixel3=zero;
2272       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2273         point.y,&pixel3);
2274       offset+=cube_size;
2275       (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2276         fmod(offset,width),floor(offset/width),&pixel1,exception);
2277       (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2278         fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2279       pixel4=zero;
2280       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2281         point.y,&pixel4);
2282       pixel=zero;
2283       CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2284         point.z,&pixel);
2285       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2286         SetPixelRed(image,ClampToQuantum(pixel.red),q);
2287       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2288         SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2289       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2290         SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2291       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2292           (image->colorspace == CMYKColorspace))
2293         SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2294       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2295           (image->alpha_trait != UndefinedPixelTrait))
2296         SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2297       q+=GetPixelChannels(image);
2298     }
2299     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2300       status=MagickFalse;
2301     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2302       {
2303         MagickBooleanType
2304           proceed;
2305
2306 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2307         #pragma omp critical (MagickCore_HaldClutImage)
2308 #endif
2309         proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2310         if (proceed == MagickFalse)
2311           status=MagickFalse;
2312       }
2313   }
2314   hald_view=DestroyCacheView(hald_view);
2315   image_view=DestroyCacheView(image_view);
2316   return(status);
2317 }
2318 \f
2319 /*
2320 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2321 %                                                                             %
2322 %                                                                             %
2323 %                                                                             %
2324 %     L e v e l I m a g e                                                     %
2325 %                                                                             %
2326 %                                                                             %
2327 %                                                                             %
2328 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2329 %
2330 %  LevelImage() adjusts the levels of a particular image channel by
2331 %  scaling the colors falling between specified white and black points to
2332 %  the full available quantum range.
2333 %
2334 %  The parameters provided represent the black, and white points.  The black
2335 %  point specifies the darkest color in the image. Colors darker than the
2336 %  black point are set to zero.  White point specifies the lightest color in
2337 %  the image.  Colors brighter than the white point are set to the maximum
2338 %  quantum value.
2339 %
2340 %  If a '!' flag is given, map black and white colors to the given levels
2341 %  rather than mapping those levels to black and white.  See
2342 %  LevelizeImage() below.
2343 %
2344 %  Gamma specifies a gamma correction to apply to the image.
2345 %
2346 %  The format of the LevelImage method is:
2347 %
2348 %      MagickBooleanType LevelImage(Image *image,const double black_point,
2349 %        const double white_point,const double gamma,ExceptionInfo *exception)
2350 %
2351 %  A description of each parameter follows:
2352 %
2353 %    o image: the image.
2354 %
2355 %    o black_point: The level to map zero (black) to.
2356 %
2357 %    o white_point: The level to map QuantumRange (white) to.
2358 %
2359 %    o exception: return any errors or warnings in this structure.
2360 %
2361 */
2362
2363 static inline double LevelPixel(const double black_point,
2364   const double white_point,const double gamma,const double pixel)
2365 {
2366   double
2367     level_pixel,
2368     scale;
2369
2370   scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2371   level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2372     1.0/gamma);
2373   return(level_pixel);
2374 }
2375
2376 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2377   const double white_point,const double gamma,ExceptionInfo *exception)
2378 {
2379 #define LevelImageTag  "Level/Image"
2380
2381   CacheView
2382     *image_view;
2383
2384   MagickBooleanType
2385     status;
2386
2387   MagickOffsetType
2388     progress;
2389
2390   register ssize_t
2391     i;
2392
2393   ssize_t
2394     y;
2395
2396   /*
2397     Allocate and initialize levels map.
2398   */
2399   assert(image != (Image *) NULL);
2400   assert(image->signature == MagickCoreSignature);
2401   if (image->debug != MagickFalse)
2402     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2403   if (image->storage_class == PseudoClass)
2404     for (i=0; i < (ssize_t) image->colors; i++)
2405     {
2406       /*
2407         Level colormap.
2408       */
2409       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2410         image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2411           white_point,gamma,image->colormap[i].red));
2412       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2413         image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2414           white_point,gamma,image->colormap[i].green));
2415       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2416         image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2417           white_point,gamma,image->colormap[i].blue));
2418       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2419         image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2420           white_point,gamma,image->colormap[i].alpha));
2421       }
2422   /*
2423     Level image.
2424   */
2425   status=MagickTrue;
2426   progress=0;
2427   image_view=AcquireAuthenticCacheView(image,exception);
2428 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2429   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2430     magick_threads(image,image,image->rows,1)
2431 #endif
2432   for (y=0; y < (ssize_t) image->rows; y++)
2433   {
2434     register Quantum
2435       *magick_restrict q;
2436
2437     register ssize_t
2438       x;
2439
2440     if (status == MagickFalse)
2441       continue;
2442     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2443     if (q == (Quantum *) NULL)
2444       {
2445         status=MagickFalse;
2446         continue;
2447       }
2448     for (x=0; x < (ssize_t) image->columns; x++)
2449     {
2450       register ssize_t
2451         j;
2452
2453       if (GetPixelReadMask(image,q) == 0)
2454         {
2455           q+=GetPixelChannels(image);
2456           continue;
2457         }
2458       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2459       {
2460         PixelChannel channel=GetPixelChannelChannel(image,j);
2461         PixelTrait traits=GetPixelChannelTraits(image,channel);
2462         if ((traits & UpdatePixelTrait) == 0)
2463           continue;
2464         q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2465           (double) q[j]));
2466       }
2467       q+=GetPixelChannels(image);
2468     }
2469     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2470       status=MagickFalse;
2471     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2472       {
2473         MagickBooleanType
2474           proceed;
2475
2476 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2477         #pragma omp critical (MagickCore_LevelImage)
2478 #endif
2479         proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2480         if (proceed == MagickFalse)
2481           status=MagickFalse;
2482       }
2483   }
2484   image_view=DestroyCacheView(image_view);
2485   (void) ClampImage(image,exception);
2486   return(status);
2487 }
2488 \f
2489 /*
2490 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2491 %                                                                             %
2492 %                                                                             %
2493 %                                                                             %
2494 %     L e v e l i z e I m a g e                                               %
2495 %                                                                             %
2496 %                                                                             %
2497 %                                                                             %
2498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499 %
2500 %  LevelizeImage() applies the reversed LevelImage() operation to just
2501 %  the specific channels specified.  It compresses the full range of color
2502 %  values, so that they lie between the given black and white points. Gamma is
2503 %  applied before the values are mapped.
2504 %
2505 %  LevelizeImage() can be called with by using a +level command line
2506 %  API option, or using a '!' on a -level or LevelImage() geometry string.
2507 %
2508 %  It can be used to de-contrast a greyscale image to the exact levels
2509 %  specified.  Or by using specific levels for each channel of an image you
2510 %  can convert a gray-scale image to any linear color gradient, according to
2511 %  those levels.
2512 %
2513 %  The format of the LevelizeImage method is:
2514 %
2515 %      MagickBooleanType LevelizeImage(Image *image,const double black_point,
2516 %        const double white_point,const double gamma,ExceptionInfo *exception)
2517 %
2518 %  A description of each parameter follows:
2519 %
2520 %    o image: the image.
2521 %
2522 %    o black_point: The level to map zero (black) to.
2523 %
2524 %    o white_point: The level to map QuantumRange (white) to.
2525 %
2526 %    o gamma: adjust gamma by this factor before mapping values.
2527 %
2528 %    o exception: return any errors or warnings in this structure.
2529 %
2530 */
2531 MagickExport MagickBooleanType LevelizeImage(Image *image,
2532   const double black_point,const double white_point,const double gamma,
2533   ExceptionInfo *exception)
2534 {
2535 #define LevelizeImageTag  "Levelize/Image"
2536 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2537   (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2538
2539   CacheView
2540     *image_view;
2541
2542   MagickBooleanType
2543     status;
2544
2545   MagickOffsetType
2546     progress;
2547
2548   register ssize_t
2549     i;
2550
2551   ssize_t
2552     y;
2553
2554   /*
2555     Allocate and initialize levels map.
2556   */
2557   assert(image != (Image *) NULL);
2558   assert(image->signature == MagickCoreSignature);
2559   if (image->debug != MagickFalse)
2560     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2561   if (image->storage_class == PseudoClass)
2562     for (i=0; i < (ssize_t) image->colors; i++)
2563     {
2564       /*
2565         Level colormap.
2566       */
2567       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2568         image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2569       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2570         image->colormap[i].green=(double) LevelizeValue(
2571           image->colormap[i].green);
2572       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2573         image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2574       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2575         image->colormap[i].alpha=(double) LevelizeValue(
2576           image->colormap[i].alpha);
2577     }
2578   /*
2579     Level image.
2580   */
2581   status=MagickTrue;
2582   progress=0;
2583   image_view=AcquireAuthenticCacheView(image,exception);
2584 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2585   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2586     magick_threads(image,image,image->rows,1)
2587 #endif
2588   for (y=0; y < (ssize_t) image->rows; y++)
2589   {
2590     register Quantum
2591       *magick_restrict q;
2592
2593     register ssize_t
2594       x;
2595
2596     if (status == MagickFalse)
2597       continue;
2598     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2599     if (q == (Quantum *) NULL)
2600       {
2601         status=MagickFalse;
2602         continue;
2603       }
2604     for (x=0; x < (ssize_t) image->columns; x++)
2605     {
2606       register ssize_t
2607         j;
2608
2609       if (GetPixelReadMask(image,q) == 0)
2610         {
2611           q+=GetPixelChannels(image);
2612           continue;
2613         }
2614       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2615       {
2616         PixelChannel channel=GetPixelChannelChannel(image,j);
2617         PixelTrait traits=GetPixelChannelTraits(image,channel);
2618         if ((traits & UpdatePixelTrait) == 0)
2619           continue;
2620         q[j]=LevelizeValue(q[j]);
2621       }
2622       q+=GetPixelChannels(image);
2623     }
2624     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2625       status=MagickFalse;
2626     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2627       {
2628         MagickBooleanType
2629           proceed;
2630
2631 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2632         #pragma omp critical (MagickCore_LevelizeImage)
2633 #endif
2634         proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2635         if (proceed == MagickFalse)
2636           status=MagickFalse;
2637       }
2638   }
2639   image_view=DestroyCacheView(image_view);
2640   return(status);
2641 }
2642 \f
2643 /*
2644 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2645 %                                                                             %
2646 %                                                                             %
2647 %                                                                             %
2648 %     L e v e l I m a g e C o l o r s                                         %
2649 %                                                                             %
2650 %                                                                             %
2651 %                                                                             %
2652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2653 %
2654 %  LevelImageColors() maps the given color to "black" and "white" values,
2655 %  linearly spreading out the colors, and level values on a channel by channel
2656 %  bases, as per LevelImage().  The given colors allows you to specify
2657 %  different level ranges for each of the color channels separately.
2658 %
2659 %  If the boolean 'invert' is set true the image values will modifyed in the
2660 %  reverse direction. That is any existing "black" and "white" colors in the
2661 %  image will become the color values given, with all other values compressed
2662 %  appropriatally.  This effectivally maps a greyscale gradient into the given
2663 %  color gradient.
2664 %
2665 %  The format of the LevelImageColors method is:
2666 %
2667 %    MagickBooleanType LevelImageColors(Image *image,
2668 %      const PixelInfo *black_color,const PixelInfo *white_color,
2669 %      const MagickBooleanType invert,ExceptionInfo *exception)
2670 %
2671 %  A description of each parameter follows:
2672 %
2673 %    o image: the image.
2674 %
2675 %    o black_color: The color to map black to/from
2676 %
2677 %    o white_point: The color to map white to/from
2678 %
2679 %    o invert: if true map the colors (levelize), rather than from (level)
2680 %
2681 %    o exception: return any errors or warnings in this structure.
2682 %
2683 */
2684 MagickExport MagickBooleanType LevelImageColors(Image *image,
2685   const PixelInfo *black_color,const PixelInfo *white_color,
2686   const MagickBooleanType invert,ExceptionInfo *exception)
2687 {
2688   ChannelType
2689     channel_mask;
2690
2691   MagickStatusType
2692     status;
2693
2694   /*
2695     Allocate and initialize levels map.
2696   */
2697   assert(image != (Image *) NULL);
2698   assert(image->signature == MagickCoreSignature);
2699   if (image->debug != MagickFalse)
2700     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2701   if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2702       ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2703        (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2704     (void) SetImageColorspace(image,sRGBColorspace,exception);
2705   status=MagickTrue;
2706   if (invert == MagickFalse)
2707     {
2708       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2709         {
2710           channel_mask=SetImageChannelMask(image,RedChannel);
2711           status&=LevelImage(image,black_color->red,white_color->red,1.0,
2712             exception);
2713           (void) SetImageChannelMask(image,channel_mask);
2714         }
2715       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2716         {
2717           channel_mask=SetImageChannelMask(image,GreenChannel);
2718           status&=LevelImage(image,black_color->green,white_color->green,1.0,
2719             exception);
2720           (void) SetImageChannelMask(image,channel_mask);
2721         }
2722       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2723         {
2724           channel_mask=SetImageChannelMask(image,BlueChannel);
2725           status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2726             exception);
2727           (void) SetImageChannelMask(image,channel_mask);
2728         }
2729       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2730           (image->colorspace == CMYKColorspace))
2731         {
2732           channel_mask=SetImageChannelMask(image,BlackChannel);
2733           status&=LevelImage(image,black_color->black,white_color->black,1.0,
2734             exception);
2735           (void) SetImageChannelMask(image,channel_mask);
2736         }
2737       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2738           (image->alpha_trait != UndefinedPixelTrait))
2739         {
2740           channel_mask=SetImageChannelMask(image,AlphaChannel);
2741           status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2742             exception);
2743           (void) SetImageChannelMask(image,channel_mask);
2744         }
2745     }
2746   else
2747     {
2748       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2749         {
2750           channel_mask=SetImageChannelMask(image,RedChannel);
2751           status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2752             exception);
2753           (void) SetImageChannelMask(image,channel_mask);
2754         }
2755       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2756         {
2757           channel_mask=SetImageChannelMask(image,GreenChannel);
2758           status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2759             exception);
2760           (void) SetImageChannelMask(image,channel_mask);
2761         }
2762       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2763         {
2764           channel_mask=SetImageChannelMask(image,BlueChannel);
2765           status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2766             exception);
2767           (void) SetImageChannelMask(image,channel_mask);
2768         }
2769       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2770           (image->colorspace == CMYKColorspace))
2771         {
2772           channel_mask=SetImageChannelMask(image,BlackChannel);
2773           status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2774             exception);
2775           (void) SetImageChannelMask(image,channel_mask);
2776         }
2777       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2778           (image->alpha_trait != UndefinedPixelTrait))
2779         {
2780           channel_mask=SetImageChannelMask(image,AlphaChannel);
2781           status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2782             exception);
2783           (void) SetImageChannelMask(image,channel_mask);
2784         }
2785     }
2786   return(status != 0 ? MagickTrue : MagickFalse);
2787 }
2788 \f
2789 /*
2790 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2791 %                                                                             %
2792 %                                                                             %
2793 %                                                                             %
2794 %     L i n e a r S t r e t c h I m a g e                                     %
2795 %                                                                             %
2796 %                                                                             %
2797 %                                                                             %
2798 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2799 %
2800 %  LinearStretchImage() discards any pixels below the black point and above
2801 %  the white point and levels the remaining pixels.
2802 %
2803 %  The format of the LinearStretchImage method is:
2804 %
2805 %      MagickBooleanType LinearStretchImage(Image *image,
2806 %        const double black_point,const double white_point,
2807 %        ExceptionInfo *exception)
2808 %
2809 %  A description of each parameter follows:
2810 %
2811 %    o image: the image.
2812 %
2813 %    o black_point: the black point.
2814 %
2815 %    o white_point: the white point.
2816 %
2817 %    o exception: return any errors or warnings in this structure.
2818 %
2819 */
2820 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2821   const double black_point,const double white_point,ExceptionInfo *exception)
2822 {
2823 #define LinearStretchImageTag  "LinearStretch/Image"
2824
2825   CacheView
2826     *image_view;
2827
2828   double
2829     *histogram,
2830     intensity;
2831
2832   MagickBooleanType
2833     status;
2834
2835   ssize_t
2836     black,
2837     white,
2838     y;
2839
2840   /*
2841     Allocate histogram and linear map.
2842   */
2843   assert(image != (Image *) NULL);
2844   assert(image->signature == MagickCoreSignature);
2845   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2846   if (histogram == (double *) NULL)
2847     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2848       image->filename);
2849   /*
2850     Form histogram.
2851   */
2852   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2853   image_view=AcquireVirtualCacheView(image,exception);
2854   for (y=0; y < (ssize_t) image->rows; y++)
2855   {
2856     register const Quantum
2857       *magick_restrict p;
2858
2859     register ssize_t
2860       x;
2861
2862     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2863     if (p == (const Quantum *) NULL)
2864       break;
2865     for (x=0; x < (ssize_t) image->columns; x++)
2866     {
2867       intensity=GetPixelIntensity(image,p);
2868       histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2869       p+=GetPixelChannels(image);
2870     }
2871   }
2872   image_view=DestroyCacheView(image_view);
2873   /*
2874     Find the histogram boundaries by locating the black and white point levels.
2875   */
2876   intensity=0.0;
2877   for (black=0; black < (ssize_t) MaxMap; black++)
2878   {
2879     intensity+=histogram[black];
2880     if (intensity >= black_point)
2881       break;
2882   }
2883   intensity=0.0;
2884   for (white=(ssize_t) MaxMap; white != 0; white--)
2885   {
2886     intensity+=histogram[white];
2887     if (intensity >= white_point)
2888       break;
2889   }
2890   histogram=(double *) RelinquishMagickMemory(histogram);
2891   status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2892     (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2893   return(status);
2894 }
2895
2896
2897 /*
2898 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2899 %                                                                             %
2900 %                                                                             %
2901 %                                                                             %
2902 %     M o d u l a t e I m a g e                                               %
2903 %                                                                             %
2904 %                                                                             %
2905 %                                                                             %
2906 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2907 %
2908 %  ModulateImage() lets you control the brightness, saturation, and hue
2909 %  of an image.  Modulate represents the brightness, saturation, and hue
2910 %  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
2911 %  modulation is lightness, saturation, and hue.  For HWB, use blackness,
2912 %  whiteness, and hue. And for HCL, use chrome, luma, and hue.
2913 %
2914 %  The format of the ModulateImage method is:
2915 %
2916 %      MagickBooleanType ModulateImage(Image *image,const char *modulate,
2917 %        ExceptionInfo *exception)
2918 %
2919 %  A description of each parameter follows:
2920 %
2921 %    o image: the image.
2922 %
2923 %    o modulate: Define the percent change in brightness, saturation, and hue.
2924 %
2925 %    o exception: return any errors or warnings in this structure.
2926 %
2927 */
2928
2929 static inline void ModulateHCL(const double percent_hue,
2930   const double percent_chroma,const double percent_luma,double *red,
2931   double *green,double *blue)
2932 {
2933   double
2934     hue,
2935     luma,
2936     chroma;
2937
2938   /*
2939     Increase or decrease color luma, chroma, or hue.
2940   */
2941   ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2942   hue+=0.5*(0.01*percent_hue-1.0);
2943   while (hue < 0.0)
2944     hue+=1.0;
2945   while (hue > 1.0)
2946     hue-=1.0;
2947   chroma*=0.01*percent_chroma;
2948   luma*=0.01*percent_luma;
2949   ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2950 }
2951
2952 static inline void ModulateHCLp(const double percent_hue,
2953   const double percent_chroma,const double percent_luma,double *red,
2954   double *green,double *blue)
2955 {
2956   double
2957     hue,
2958     luma,
2959     chroma;
2960
2961   /*
2962     Increase or decrease color luma, chroma, or hue.
2963   */
2964   ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2965   hue+=0.5*(0.01*percent_hue-1.0);
2966   while (hue < 0.0)
2967     hue+=1.0;
2968   while (hue > 1.0)
2969     hue-=1.0;
2970   chroma*=0.01*percent_chroma;
2971   luma*=0.01*percent_luma;
2972   ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2973 }
2974
2975 static inline void ModulateHSB(const double percent_hue,
2976   const double percent_saturation,const double percent_brightness,double *red,
2977   double *green,double *blue)
2978 {
2979   double
2980     brightness,
2981     hue,
2982     saturation;
2983
2984   /*
2985     Increase or decrease color brightness, saturation, or hue.
2986   */
2987   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2988   hue+=0.5*(0.01*percent_hue-1.0);
2989   while (hue < 0.0)
2990     hue+=1.0;
2991   while (hue > 1.0)
2992     hue-=1.0;
2993   saturation*=0.01*percent_saturation;
2994   brightness*=0.01*percent_brightness;
2995   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2996 }
2997
2998 static inline void ModulateHSI(const double percent_hue,
2999   const double percent_saturation,const double percent_intensity,double *red,
3000   double *green,double *blue)
3001 {
3002   double
3003     intensity,
3004     hue,
3005     saturation;
3006
3007   /*
3008     Increase or decrease color intensity, saturation, or hue.
3009   */
3010   ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3011   hue+=0.5*(0.01*percent_hue-1.0);
3012   while (hue < 0.0)
3013     hue+=1.0;
3014   while (hue > 1.0)
3015     hue-=1.0;
3016   saturation*=0.01*percent_saturation;
3017   intensity*=0.01*percent_intensity;
3018   ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3019 }
3020
3021 static inline void ModulateHSL(const double percent_hue,
3022   const double percent_saturation,const double percent_lightness,double *red,
3023   double *green,double *blue)
3024 {
3025   double
3026     hue,
3027     lightness,
3028     saturation;
3029
3030   /*
3031     Increase or decrease color lightness, saturation, or hue.
3032   */
3033   ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3034   hue+=0.5*(0.01*percent_hue-1.0);
3035   while (hue < 0.0)
3036     hue+=1.0;
3037   while (hue >= 1.0)
3038     hue-=1.0;
3039   saturation*=0.01*percent_saturation;
3040   lightness*=0.01*percent_lightness;
3041   ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3042 }
3043
3044 static inline void ModulateHSV(const double percent_hue,
3045   const double percent_saturation,const double percent_value,double *red,
3046   double *green,double *blue)
3047 {
3048   double
3049     hue,
3050     saturation,
3051     value;
3052
3053   /*
3054     Increase or decrease color value, saturation, or hue.
3055   */
3056   ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3057   hue+=0.5*(0.01*percent_hue-1.0);
3058   while (hue < 0.0)
3059     hue+=1.0;
3060   while (hue >= 1.0)
3061     hue-=1.0;
3062   saturation*=0.01*percent_saturation;
3063   value*=0.01*percent_value;
3064   ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3065 }
3066
3067 static inline void ModulateHWB(const double percent_hue,
3068   const double percent_whiteness,const double percent_blackness,double *red,
3069   double *green,double *blue)
3070 {
3071   double
3072     blackness,
3073     hue,
3074     whiteness;
3075
3076   /*
3077     Increase or decrease color blackness, whiteness, or hue.
3078   */
3079   ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3080   hue+=0.5*(0.01*percent_hue-1.0);
3081   while (hue < 0.0)
3082     hue+=1.0;
3083   while (hue >= 1.0)
3084     hue-=1.0;
3085   blackness*=0.01*percent_blackness;
3086   whiteness*=0.01*percent_whiteness;
3087   ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3088 }
3089
3090 static inline void ModulateLCHab(const double percent_luma,
3091   const double percent_chroma,const double percent_hue,double *red,
3092   double *green,double *blue)
3093 {
3094   double
3095     hue,
3096     luma,
3097     chroma;
3098
3099   /*
3100     Increase or decrease color luma, chroma, or hue.
3101   */
3102   ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3103   luma*=0.01*percent_luma;
3104   chroma*=0.01*percent_chroma;
3105   hue+=0.5*(0.01*percent_hue-1.0);
3106   while (hue < 0.0)
3107     hue+=1.0;
3108   while (hue >= 1.0)
3109     hue-=1.0;
3110   ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3111 }
3112
3113 static inline void ModulateLCHuv(const double percent_luma,
3114   const double percent_chroma,const double percent_hue,double *red,
3115   double *green,double *blue)
3116 {
3117   double
3118     hue,
3119     luma,
3120     chroma;
3121
3122   /*
3123     Increase or decrease color luma, chroma, or hue.
3124   */
3125   ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3126   luma*=0.01*percent_luma;
3127   chroma*=0.01*percent_chroma;
3128   hue+=0.5*(0.01*percent_hue-1.0);
3129   while (hue < 0.0)
3130     hue+=1.0;
3131   while (hue >= 1.0)
3132     hue-=1.0;
3133   ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3134 }
3135
3136 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3137   ExceptionInfo *exception)
3138 {
3139 #define ModulateImageTag  "Modulate/Image"
3140
3141   CacheView
3142     *image_view;
3143
3144   ColorspaceType
3145     colorspace;
3146
3147   const char
3148     *artifact;
3149
3150   double
3151     percent_brightness,
3152     percent_hue,
3153     percent_saturation;
3154
3155   GeometryInfo
3156     geometry_info;
3157
3158   MagickBooleanType
3159     status;
3160
3161   MagickOffsetType
3162     progress;
3163
3164   MagickStatusType
3165     flags;
3166
3167   register ssize_t
3168     i;
3169
3170   ssize_t
3171     y;
3172
3173   /*
3174     Initialize modulate table.
3175   */
3176   assert(image != (Image *) NULL);
3177   assert(image->signature == MagickCoreSignature);
3178   if (image->debug != MagickFalse)
3179     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3180   if (modulate == (char *) NULL)
3181     return(MagickFalse);
3182   if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3183     (void) SetImageColorspace(image,sRGBColorspace,exception);
3184   flags=ParseGeometry(modulate,&geometry_info);
3185   percent_brightness=geometry_info.rho;
3186   percent_saturation=geometry_info.sigma;
3187   if ((flags & SigmaValue) == 0)
3188     percent_saturation=100.0;
3189   percent_hue=geometry_info.xi;
3190   if ((flags & XiValue) == 0)
3191     percent_hue=100.0;
3192   colorspace=UndefinedColorspace;
3193   artifact=GetImageArtifact(image,"modulate:colorspace");
3194   if (artifact != (const char *) NULL)
3195     colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3196       MagickFalse,artifact);
3197   if (image->storage_class == PseudoClass)
3198     for (i=0; i < (ssize_t) image->colors; i++)
3199     {
3200       double
3201         blue,
3202         green,
3203         red;
3204
3205       /*
3206         Modulate image colormap.
3207       */
3208       red=(double) image->colormap[i].red;
3209       green=(double) image->colormap[i].green;
3210       blue=(double) image->colormap[i].blue;
3211       switch (colorspace)
3212       {
3213         case HCLColorspace:
3214         {
3215           ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3216             &red,&green,&blue);
3217           break;
3218         }
3219         case HCLpColorspace:
3220         {
3221           ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3222             &red,&green,&blue);
3223           break;
3224         }
3225         case HSBColorspace:
3226         {
3227           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3228             &red,&green,&blue);
3229           break;
3230         }
3231         case HSIColorspace:
3232         {
3233           ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3234             &red,&green,&blue);
3235           break;
3236         }
3237         case HSLColorspace:
3238         default:
3239         {
3240           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3241             &red,&green,&blue);
3242           break;
3243         }
3244         case HSVColorspace:
3245         {
3246           ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3247             &red,&green,&blue);
3248           break;
3249         }
3250         case HWBColorspace:
3251         {
3252           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3253             &red,&green,&blue);
3254           break;
3255         }
3256         case LCHColorspace:
3257         case LCHabColorspace:
3258         {
3259           ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3260             &red,&green,&blue);
3261           break;
3262         }
3263         case LCHuvColorspace:
3264         {
3265           ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3266             &red,&green,&blue);
3267           break;
3268         }
3269       }
3270       image->colormap[i].red=red;
3271       image->colormap[i].green=green;
3272       image->colormap[i].blue=blue;
3273     }
3274   /*
3275     Modulate image.
3276   */
3277   if(AccelerateModulateImage(image,percent_brightness,percent_hue,
3278      percent_saturation,colorspace,exception) != MagickFalse)
3279     return(MagickTrue);
3280   status=MagickTrue;
3281   progress=0;
3282   image_view=AcquireAuthenticCacheView(image,exception);
3283 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3284   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3285     magick_threads(image,image,image->rows,1)
3286 #endif
3287   for (y=0; y < (ssize_t) image->rows; y++)
3288   {
3289     register Quantum
3290       *magick_restrict q;
3291
3292     register ssize_t
3293       x;
3294
3295     if (status == MagickFalse)
3296       continue;
3297     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3298     if (q == (Quantum *) NULL)
3299       {
3300         status=MagickFalse;
3301         continue;
3302       }
3303     for (x=0; x < (ssize_t) image->columns; x++)
3304     {
3305       double
3306         blue,
3307         green,
3308         red;
3309
3310       red=(double) GetPixelRed(image,q);
3311       green=(double) GetPixelGreen(image,q);
3312       blue=(double) GetPixelBlue(image,q);
3313       switch (colorspace)
3314       {
3315         case HCLColorspace:
3316         {
3317           ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3318             &red,&green,&blue);
3319           break;
3320         }
3321         case HCLpColorspace:
3322         {
3323           ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3324             &red,&green,&blue);
3325           break;
3326         }
3327         case HSBColorspace:
3328         {
3329           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3330             &red,&green,&blue);
3331           break;
3332         }
3333         case HSLColorspace:
3334         default:
3335         {
3336           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3337             &red,&green,&blue);
3338           break;
3339         }
3340         case HSVColorspace:
3341         {
3342           ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3343             &red,&green,&blue);
3344           break;
3345         }
3346         case HWBColorspace:
3347         {
3348           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3349             &red,&green,&blue);
3350           break;
3351         }
3352         case LCHabColorspace:
3353         {
3354           ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3355             &red,&green,&blue);
3356           break;
3357         }
3358         case LCHColorspace:
3359         case LCHuvColorspace:
3360         {
3361           ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3362             &red,&green,&blue);
3363           break;
3364         }
3365       }
3366       SetPixelRed(image,ClampToQuantum(red),q);
3367       SetPixelGreen(image,ClampToQuantum(green),q);
3368       SetPixelBlue(image,ClampToQuantum(blue),q);
3369       q+=GetPixelChannels(image);
3370     }
3371     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3372       status=MagickFalse;
3373     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3374       {
3375         MagickBooleanType
3376           proceed;
3377
3378 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3379         #pragma omp critical (MagickCore_ModulateImage)
3380 #endif
3381         proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3382         if (proceed == MagickFalse)
3383           status=MagickFalse;
3384       }
3385   }
3386   image_view=DestroyCacheView(image_view);
3387   return(status);
3388 }
3389 \f
3390 /*
3391 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392 %                                                                             %
3393 %                                                                             %
3394 %                                                                             %
3395 %     N e g a t e I m a g e                                                   %
3396 %                                                                             %
3397 %                                                                             %
3398 %                                                                             %
3399 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3400 %
3401 %  NegateImage() negates the colors in the reference image.  The grayscale
3402 %  option means that only grayscale values within the image are negated.
3403 %
3404 %  The format of the NegateImage method is:
3405 %
3406 %      MagickBooleanType NegateImage(Image *image,
3407 %        const MagickBooleanType grayscale,ExceptionInfo *exception)
3408 %
3409 %  A description of each parameter follows:
3410 %
3411 %    o image: the image.
3412 %
3413 %    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3414 %
3415 %    o exception: return any errors or warnings in this structure.
3416 %
3417 */
3418 MagickExport MagickBooleanType NegateImage(Image *image,
3419   const MagickBooleanType grayscale,ExceptionInfo *exception)
3420 {
3421 #define NegateImageTag  "Negate/Image"
3422
3423   CacheView
3424     *image_view;
3425
3426   MagickBooleanType
3427     status;
3428
3429   MagickOffsetType
3430     progress;
3431
3432   register ssize_t
3433     i;
3434
3435   ssize_t
3436     y;
3437
3438   assert(image != (Image *) NULL);
3439   assert(image->signature == MagickCoreSignature);
3440   if (image->debug != MagickFalse)
3441     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3442   if (image->storage_class == PseudoClass)
3443     for (i=0; i < (ssize_t) image->colors; i++)
3444     {
3445       /*
3446         Negate colormap.
3447       */
3448       if( grayscale != MagickFalse )
3449         if ((image->colormap[i].red != image->colormap[i].green) ||
3450             (image->colormap[i].green != image->colormap[i].blue))
3451           continue;
3452       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3453         image->colormap[i].red=QuantumRange-image->colormap[i].red;
3454       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3455         image->colormap[i].green=QuantumRange-image->colormap[i].green;
3456       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3457         image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3458     }
3459   /*
3460     Negate image.
3461   */
3462   status=MagickTrue;
3463   progress=0;
3464   image_view=AcquireAuthenticCacheView(image,exception);
3465   if( grayscale != MagickFalse )
3466     {
3467       for (y=0; y < (ssize_t) image->rows; y++)
3468       {
3469         MagickBooleanType
3470           sync;
3471
3472         register Quantum
3473           *magick_restrict q;
3474
3475         register ssize_t
3476           x;
3477
3478         if (status == MagickFalse)
3479           continue;
3480         q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3481           exception);
3482         if (q == (Quantum *) NULL)
3483           {
3484             status=MagickFalse;
3485             continue;
3486           }
3487         for (x=0; x < (ssize_t) image->columns; x++)
3488         {
3489           register ssize_t
3490             j;
3491
3492           if ((GetPixelReadMask(image,q) == 0) ||
3493               IsPixelGray(image,q) != MagickFalse)
3494             {
3495               q+=GetPixelChannels(image);
3496               continue;
3497             }
3498           for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3499           {
3500             PixelChannel channel=GetPixelChannelChannel(image,j);
3501             PixelTrait traits=GetPixelChannelTraits(image,channel);
3502             if ((traits & UpdatePixelTrait) == 0)
3503               continue;
3504             q[j]=QuantumRange-q[j];
3505           }
3506           q+=GetPixelChannels(image);
3507         }
3508         sync=SyncCacheViewAuthenticPixels(image_view,exception);
3509         if (sync == MagickFalse)
3510           status=MagickFalse;
3511         if (image->progress_monitor != (MagickProgressMonitor) NULL)
3512           {
3513             MagickBooleanType
3514               proceed;
3515
3516 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3517             #pragma omp critical (MagickCore_NegateImage)
3518 #endif
3519             proceed=SetImageProgress(image,NegateImageTag,progress++,
3520               image->rows);
3521             if (proceed == MagickFalse)
3522               status=MagickFalse;
3523           }
3524       }
3525       image_view=DestroyCacheView(image_view);
3526       return(MagickTrue);
3527     }
3528   /*
3529     Negate image.
3530   */
3531 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3532   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3533     magick_threads(image,image,image->rows,1)
3534 #endif
3535   for (y=0; y < (ssize_t) image->rows; y++)
3536   {
3537     register Quantum
3538       *magick_restrict q;
3539
3540     register ssize_t
3541       x;
3542
3543     if (status == MagickFalse)
3544       continue;
3545     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3546     if (q == (Quantum *) NULL)
3547       {
3548         status=MagickFalse;
3549         continue;
3550       }
3551     for (x=0; x < (ssize_t) image->columns; x++)
3552     {
3553       register ssize_t
3554         j;
3555
3556       if (GetPixelReadMask(image,q) == 0)
3557         {
3558           q+=GetPixelChannels(image);
3559           continue;
3560         }
3561       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3562       {
3563         PixelChannel channel=GetPixelChannelChannel(image,j);
3564         PixelTrait traits=GetPixelChannelTraits(image,channel);
3565         if ((traits & UpdatePixelTrait) == 0)
3566           continue;
3567         q[j]=QuantumRange-q[j];
3568       }
3569       q+=GetPixelChannels(image);
3570     }
3571     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3572       status=MagickFalse;
3573     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3574       {
3575         MagickBooleanType
3576           proceed;
3577
3578 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3579         #pragma omp critical (MagickCore_NegateImage)
3580 #endif
3581         proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3582         if (proceed == MagickFalse)
3583           status=MagickFalse;
3584       }
3585   }
3586   image_view=DestroyCacheView(image_view);
3587   return(status);
3588 }
3589 \f
3590 /*
3591 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3592 %                                                                             %
3593 %                                                                             %
3594 %                                                                             %
3595 %     N o r m a l i z e I m a g e                                             %
3596 %                                                                             %
3597 %                                                                             %
3598 %                                                                             %
3599 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3600 %
3601 %  The NormalizeImage() method enhances the contrast of a color image by
3602 %  mapping the darkest 2 percent of all pixel to black and the brightest
3603 %  1 percent to white.
3604 %
3605 %  The format of the NormalizeImage method is:
3606 %
3607 %      MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3608 %
3609 %  A description of each parameter follows:
3610 %
3611 %    o image: the image.
3612 %
3613 %    o exception: return any errors or warnings in this structure.
3614 %
3615 */
3616 MagickExport MagickBooleanType NormalizeImage(Image *image,
3617   ExceptionInfo *exception)
3618 {
3619   double
3620     black_point,
3621     white_point;
3622
3623   black_point=(double) image->columns*image->rows*0.0015;
3624   white_point=(double) image->columns*image->rows*0.9995;
3625   return(ContrastStretchImage(image,black_point,white_point,exception));
3626 }
3627 \f
3628 /*
3629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3630 %                                                                             %
3631 %                                                                             %
3632 %                                                                             %
3633 %     S i g m o i d a l C o n t r a s t I m a g e                             %
3634 %                                                                             %
3635 %                                                                             %
3636 %                                                                             %
3637 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3638 %
3639 %  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3640 %  sigmoidal contrast algorithm.  Increase the contrast of the image using a
3641 %  sigmoidal transfer function without saturating highlights or shadows.
3642 %  Contrast indicates how much to increase the contrast (0 is none; 3 is
3643 %  typical; 20 is pushing it); mid-point indicates where midtones fall in the
3644 %  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
3645 %  sharpen to MagickTrue to increase the image contrast otherwise the contrast
3646 %  is reduced.
3647 %
3648 %  The format of the SigmoidalContrastImage method is:
3649 %
3650 %      MagickBooleanType SigmoidalContrastImage(Image *image,
3651 %        const MagickBooleanType sharpen,const char *levels,
3652 %        ExceptionInfo *exception)
3653 %
3654 %  A description of each parameter follows:
3655 %
3656 %    o image: the image.
3657 %
3658 %    o sharpen: Increase or decrease image contrast.
3659 %
3660 %    o contrast: strength of the contrast, the larger the number the more
3661 %      'threshold-like' it becomes.
3662 %
3663 %    o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3664 %
3665 %    o exception: return any errors or warnings in this structure.
3666 %
3667 */
3668
3669 /*
3670   ImageMagick 6 has a version of this function which uses LUTs.
3671 */
3672
3673 /*
3674   Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3675   constant" set to a.
3676
3677   The first version, based on the hyperbolic tangent tanh, when combined with
3678   the scaling step, is an exact arithmetic clone of the the sigmoid function
3679   based on the logistic curve. The equivalence is based on the identity
3680
3681     1/(1+exp(-t)) = (1+tanh(t/2))/2
3682
3683   (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3684   scaled sigmoidal derivation is invariant under affine transformations of
3685   the ordinate.
3686
3687   The tanh version is almost certainly more accurate and cheaper.  The 0.5
3688   factor in the argument is to clone the legacy ImageMagick behavior. The
3689   reason for making the define depend on atanh even though it only uses tanh
3690   has to do with the construction of the inverse of the scaled sigmoidal.
3691 */
3692 #if defined(MAGICKCORE_HAVE_ATANH)
3693 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3694 #else
3695 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3696 #endif
3697 /*
3698   Scaled sigmoidal function:
3699
3700     ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3701     ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3702
3703   See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3704   http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf.  The limit
3705   of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3706   zero. This is fixed below by exiting immediately when contrast is small,
3707   leaving the image (or colormap) unmodified. This appears to be safe because
3708   the series expansion of the logistic sigmoidal function around x=b is
3709
3710   1/2-a*(b-x)/4+...
3711
3712   so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3713 */
3714 #define ScaledSigmoidal(a,b,x) (                    \
3715   (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3716   (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3717 /*
3718   Inverse of ScaledSigmoidal, used for +sigmoidal-contrast.  Because b
3719   may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3720   sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3721   when creating a LUT from in gamut values, hence the branching.  In
3722   addition, HDRI may have out of gamut values.
3723   InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3724   It is only a right inverse. This is unavoidable.
3725 */
3726 static inline double InverseScaledSigmoidal(const double a,const double b,
3727   const double x)
3728 {
3729   const double sig0=Sigmoidal(a,b,0.0);
3730   const double sig1=Sigmoidal(a,b,1.0);
3731   const double argument=(sig1-sig0)*x+sig0;
3732   const double clamped=
3733     (
3734 #if defined(MAGICKCORE_HAVE_ATANH)
3735       argument < -1+MagickEpsilon
3736       ?
3737       -1+MagickEpsilon
3738       :
3739       ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3740     );
3741   return(b+(2.0/a)*atanh(clamped));
3742 #else
3743       argument < MagickEpsilon
3744       ?
3745       MagickEpsilon
3746       :
3747       ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3748     );
3749   return(b-log(1.0/clamped-1.0)/a);
3750 #endif
3751 }
3752
3753 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3754   const MagickBooleanType sharpen,const double contrast,const double midpoint,
3755   ExceptionInfo *exception)
3756 {
3757 #define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
3758 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3759   ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3760 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3761   InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3762
3763   CacheView
3764     *image_view;
3765
3766   MagickBooleanType
3767     status;
3768
3769   MagickOffsetType
3770     progress;
3771
3772   ssize_t
3773     y;
3774
3775   /*
3776     Convenience macros.
3777   */
3778   assert(image != (Image *) NULL);
3779   assert(image->signature == MagickCoreSignature);
3780   if (image->debug != MagickFalse)
3781     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3782   /*
3783     Side effect: may clamp values unless contrast<MagickEpsilon, in which
3784     case nothing is done.
3785   */
3786   if (contrast < MagickEpsilon)
3787     return(MagickTrue);
3788   /*
3789     Sigmoidal-contrast enhance colormap.
3790   */
3791   if (image->storage_class == PseudoClass)
3792     {
3793       register ssize_t
3794         i;
3795
3796       if( sharpen != MagickFalse )
3797         for (i=0; i < (ssize_t) image->colors; i++)
3798         {
3799           if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3800             image->colormap[i].red=(MagickRealType) ScaledSig(
3801               image->colormap[i].red);
3802           if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3803             image->colormap[i].green=(MagickRealType) ScaledSig(
3804               image->colormap[i].green);
3805           if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3806             image->colormap[i].blue=(MagickRealType) ScaledSig(
3807               image->colormap[i].blue);
3808           if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3809             image->colormap[i].alpha=(MagickRealType) ScaledSig(
3810               image->colormap[i].alpha);
3811         }
3812       else
3813         for (i=0; i < (ssize_t) image->colors; i++)
3814         {
3815           if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3816             image->colormap[i].red=(MagickRealType) InverseScaledSig(
3817               image->colormap[i].red);
3818           if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3819             image->colormap[i].green=(MagickRealType) InverseScaledSig(
3820               image->colormap[i].green);
3821           if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3822             image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3823               image->colormap[i].blue);
3824           if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3825             image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3826               image->colormap[i].alpha);
3827         }
3828     }
3829   /*
3830     Sigmoidal-contrast enhance image.
3831   */
3832   status=MagickTrue;
3833   progress=0;
3834   image_view=AcquireAuthenticCacheView(image,exception);
3835 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3836   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3837     magick_threads(image,image,image->rows,1)
3838 #endif
3839   for (y=0; y < (ssize_t) image->rows; y++)
3840   {
3841     register Quantum
3842       *magick_restrict q;
3843
3844     register ssize_t
3845       x;
3846
3847     if (status == MagickFalse)
3848       continue;
3849     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3850     if (q == (Quantum *) NULL)
3851       {
3852         status=MagickFalse;
3853         continue;
3854       }
3855     for (x=0; x < (ssize_t) image->columns; x++)
3856     {
3857       register ssize_t
3858         i;
3859
3860       if (GetPixelReadMask(image,q) == 0)
3861         {
3862           q+=GetPixelChannels(image);
3863           continue;
3864         }
3865       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3866       {
3867         PixelChannel channel=GetPixelChannelChannel(image,i);
3868         PixelTrait traits=GetPixelChannelTraits(image,channel);
3869         if ((traits & UpdatePixelTrait) == 0)
3870           continue;
3871         if( sharpen != MagickFalse )
3872           q[i]=ScaledSig(q[i]);
3873         else
3874           q[i]=InverseScaledSig(q[i]);
3875       }
3876       q+=GetPixelChannels(image);
3877     }
3878     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3879       status=MagickFalse;
3880     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3881       {
3882         MagickBooleanType
3883           proceed;
3884
3885 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3886         #pragma omp critical (MagickCore_SigmoidalContrastImage)
3887 #endif
3888         proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3889           image->rows);
3890         if (proceed == MagickFalse)
3891           status=MagickFalse;
3892       }
3893   }
3894   image_view=DestroyCacheView(image_view);
3895   return(status);
3896 }