]> granicus.if.org Git - imagemagick/blob - MagickCore/enhance.c
The -preview option is an image operator
[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-2017 ImageMagick Studio LLC, a non-profit organization      %
21 %  dedicated to making software imaging solutions freely available.           %
22 %                                                                             %
23 %  You may not use this file except in compliance with the License.  You may  %
24 %  obtain a copy of the License at                                            %
25 %                                                                             %
26 %    https://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-private.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 (GetPixelWriteMask(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 defined(MAGICKCORE_OPENCL_SUPPORT)
891   if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
892     return(MagickTrue);
893 #endif
894   if (image->debug != MagickFalse)
895     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
896   sign=sharpen != MagickFalse ? 1 : -1;
897   if (image->storage_class == PseudoClass)
898     {
899       /*
900         Contrast enhance colormap.
901       */
902       for (i=0; i < (ssize_t) image->colors; i++)
903       {
904         double
905           blue,
906           green,
907           red;
908
909         red=(double) image->colormap[i].red;
910         green=(double) image->colormap[i].green;
911         blue=(double) image->colormap[i].blue;
912         Contrast(sign,&red,&green,&blue);
913         image->colormap[i].red=(MagickRealType) red;
914         image->colormap[i].green=(MagickRealType) green;
915         image->colormap[i].blue=(MagickRealType) blue;
916       }
917     }
918   /*
919     Contrast enhance image.
920   */
921   status=MagickTrue;
922   progress=0;
923   image_view=AcquireAuthenticCacheView(image,exception);
924 #if defined(MAGICKCORE_OPENMP_SUPPORT)
925   #pragma omp parallel for schedule(static,4) shared(progress,status) \
926     magick_threads(image,image,image->rows,1)
927 #endif
928   for (y=0; y < (ssize_t) image->rows; y++)
929   {
930     double
931       blue,
932       green,
933       red;
934
935     register Quantum
936       *magick_restrict q;
937
938     register ssize_t
939       x;
940
941     if (status == MagickFalse)
942       continue;
943     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
944     if (q == (Quantum *) NULL)
945       {
946         status=MagickFalse;
947         continue;
948       }
949     for (x=0; x < (ssize_t) image->columns; x++)
950     {
951       red=(double) GetPixelRed(image,q);
952       green=(double) GetPixelGreen(image,q);
953       blue=(double) GetPixelBlue(image,q);
954       Contrast(sign,&red,&green,&blue);
955       SetPixelRed(image,ClampToQuantum(red),q);
956       SetPixelGreen(image,ClampToQuantum(green),q);
957       SetPixelBlue(image,ClampToQuantum(blue),q);
958       q+=GetPixelChannels(image);
959     }
960     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
961       status=MagickFalse;
962     if (image->progress_monitor != (MagickProgressMonitor) NULL)
963       {
964         MagickBooleanType
965           proceed;
966
967 #if defined(MAGICKCORE_OPENMP_SUPPORT)
968         #pragma omp critical (MagickCore_ContrastImage)
969 #endif
970         proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
971         if (proceed == MagickFalse)
972           status=MagickFalse;
973       }
974   }
975   image_view=DestroyCacheView(image_view);
976   return(status);
977 }
978 \f
979 /*
980 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
981 %                                                                             %
982 %                                                                             %
983 %                                                                             %
984 %     C o n t r a s t S t r e t c h I m a g e                                 %
985 %                                                                             %
986 %                                                                             %
987 %                                                                             %
988 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
989 %
990 %  ContrastStretchImage() is a simple image enhancement technique that attempts
991 %  to improve the contrast in an image by 'stretching' the range of intensity
992 %  values it contains to span a desired range of values. It differs from the
993 %  more sophisticated histogram equalization in that it can only apply a
994 %  linear scaling function to the image pixel values.  As a result the
995 %  'enhancement' is less harsh.
996 %
997 %  The format of the ContrastStretchImage method is:
998 %
999 %      MagickBooleanType ContrastStretchImage(Image *image,
1000 %        const char *levels,ExceptionInfo *exception)
1001 %
1002 %  A description of each parameter follows:
1003 %
1004 %    o image: the image.
1005 %
1006 %    o black_point: the black point.
1007 %
1008 %    o white_point: the white point.
1009 %
1010 %    o levels: Specify the levels where the black and white points have the
1011 %      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1012 %
1013 %    o exception: return any errors or warnings in this structure.
1014 %
1015 */
1016 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1017   const double black_point,const double white_point,ExceptionInfo *exception)
1018 {
1019 #define MaxRange(color)  ((double) ScaleQuantumToMap((Quantum) (color)))
1020 #define ContrastStretchImageTag  "ContrastStretch/Image"
1021
1022   CacheView
1023     *image_view;
1024
1025   double
1026     *black,
1027     *histogram,
1028     *stretch_map,
1029     *white;
1030
1031   MagickBooleanType
1032     status;
1033
1034   MagickOffsetType
1035     progress;
1036
1037   register ssize_t
1038     i;
1039
1040   ssize_t
1041     y;
1042
1043   /*
1044     Allocate histogram and stretch map.
1045   */
1046   assert(image != (Image *) NULL);
1047   assert(image->signature == MagickCoreSignature);
1048   if (image->debug != MagickFalse)
1049     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1050   if (SetImageGray(image,exception) != MagickFalse)
1051     (void) SetImageColorspace(image,GRAYColorspace,exception);
1052   black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1053   white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1054   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1055     sizeof(*histogram));
1056   stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1057     GetPixelChannels(image)*sizeof(*stretch_map));
1058   if ((black == (double *) NULL) || (white == (double *) NULL) ||
1059       (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1060     {
1061       if (stretch_map != (double *) NULL)
1062         stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1063       if (histogram != (double *) NULL)
1064         histogram=(double *) RelinquishMagickMemory(histogram);
1065       if (white != (double *) NULL)
1066         white=(double *) RelinquishMagickMemory(white);
1067       if (black != (double *) NULL)
1068         black=(double *) RelinquishMagickMemory(black);
1069       ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1070         image->filename);
1071     }
1072   /*
1073     Form histogram.
1074   */
1075   status=MagickTrue;
1076   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1077     sizeof(*histogram));
1078   image_view=AcquireVirtualCacheView(image,exception);
1079   for (y=0; y < (ssize_t) image->rows; y++)
1080   {
1081     register const Quantum
1082       *magick_restrict p;
1083
1084     register ssize_t
1085       x;
1086
1087     if (status == MagickFalse)
1088       continue;
1089     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1090     if (p == (const Quantum *) NULL)
1091       {
1092         status=MagickFalse;
1093         continue;
1094       }
1095     for (x=0; x < (ssize_t) image->columns; x++)
1096     {
1097       double
1098         pixel;
1099
1100       pixel=GetPixelIntensity(image,p);
1101       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1102       {
1103         if (image->channel_mask != DefaultChannels)
1104           pixel=(double) p[i];
1105         histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1106           ClampToQuantum(pixel))+i]++;
1107       }
1108       p+=GetPixelChannels(image);
1109     }
1110   }
1111   image_view=DestroyCacheView(image_view);
1112   /*
1113     Find the histogram boundaries by locating the black/white levels.
1114   */
1115   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1116   {
1117     double
1118       intensity;
1119
1120     register ssize_t
1121       j;
1122
1123     black[i]=0.0;
1124     white[i]=MaxRange(QuantumRange);
1125     intensity=0.0;
1126     for (j=0; j <= (ssize_t) MaxMap; j++)
1127     {
1128       intensity+=histogram[GetPixelChannels(image)*j+i];
1129       if (intensity > black_point)
1130         break;
1131     }
1132     black[i]=(double) j;
1133     intensity=0.0;
1134     for (j=(ssize_t) MaxMap; j != 0; j--)
1135     {
1136       intensity+=histogram[GetPixelChannels(image)*j+i];
1137       if (intensity > ((double) image->columns*image->rows-white_point))
1138         break;
1139     }
1140     white[i]=(double) j;
1141   }
1142   histogram=(double *) RelinquishMagickMemory(histogram);
1143   /*
1144     Stretch the histogram to create the stretched image mapping.
1145   */
1146   (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1147     sizeof(*stretch_map));
1148   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1149   {
1150     register ssize_t
1151       j;
1152
1153     for (j=0; j <= (ssize_t) MaxMap; j++)
1154     {
1155       double
1156         gamma;
1157
1158       gamma=PerceptibleReciprocal(white[i]-black[i]);
1159       if (j < (ssize_t) black[i])
1160         stretch_map[GetPixelChannels(image)*j+i]=0.0;
1161       else
1162         if (j > (ssize_t) white[i])
1163           stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1164         else
1165           stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1166             (double) (MaxMap*gamma*(j-black[i])));
1167     }
1168   }
1169   if (image->storage_class == PseudoClass)
1170     {
1171       register ssize_t
1172         j;
1173
1174       /*
1175         Stretch-contrast colormap.
1176       */
1177       for (j=0; j < (ssize_t) image->colors; j++)
1178       {
1179         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1180           {
1181             i=GetPixelChannelOffset(image,RedPixelChannel);
1182             image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1183               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1184           }
1185         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1186           {
1187             i=GetPixelChannelOffset(image,GreenPixelChannel);
1188             image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1189               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1190           }
1191         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1192           {
1193             i=GetPixelChannelOffset(image,BluePixelChannel);
1194             image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1195               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1196           }
1197         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1198           {
1199             i=GetPixelChannelOffset(image,AlphaPixelChannel);
1200             image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1201               ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1202           }
1203       }
1204     }
1205   /*
1206     Stretch-contrast image.
1207   */
1208   status=MagickTrue;
1209   progress=0;
1210   image_view=AcquireAuthenticCacheView(image,exception);
1211 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1212   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1213     magick_threads(image,image,image->rows,1)
1214 #endif
1215   for (y=0; y < (ssize_t) image->rows; y++)
1216   {
1217     register Quantum
1218       *magick_restrict q;
1219
1220     register ssize_t
1221       x;
1222
1223     if (status == MagickFalse)
1224       continue;
1225     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1226     if (q == (Quantum *) NULL)
1227       {
1228         status=MagickFalse;
1229         continue;
1230       }
1231     for (x=0; x < (ssize_t) image->columns; x++)
1232     {
1233       register ssize_t
1234         j;
1235
1236       if (GetPixelWriteMask(image,q) == 0)
1237         {
1238           q+=GetPixelChannels(image);
1239           continue;
1240         }
1241       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1242       {
1243         PixelChannel channel=GetPixelChannelChannel(image,j);
1244         PixelTrait traits=GetPixelChannelTraits(image,channel);
1245         if ((traits & UpdatePixelTrait) == 0)
1246           continue;
1247         q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1248           ScaleQuantumToMap(q[j])+j]);
1249       }
1250       q+=GetPixelChannels(image);
1251     }
1252     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1253       status=MagickFalse;
1254     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1255       {
1256         MagickBooleanType
1257           proceed;
1258
1259 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1260         #pragma omp critical (MagickCore_ContrastStretchImage)
1261 #endif
1262         proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1263           image->rows);
1264         if (proceed == MagickFalse)
1265           status=MagickFalse;
1266       }
1267   }
1268   image_view=DestroyCacheView(image_view);
1269   stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1270   white=(double *) RelinquishMagickMemory(white);
1271   black=(double *) RelinquishMagickMemory(black);
1272   return(status);
1273 }
1274 \f
1275 /*
1276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1277 %                                                                             %
1278 %                                                                             %
1279 %                                                                             %
1280 %     E n h a n c e I m a g e                                                 %
1281 %                                                                             %
1282 %                                                                             %
1283 %                                                                             %
1284 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1285 %
1286 %  EnhanceImage() applies a digital filter that improves the quality of a
1287 %  noisy image.
1288 %
1289 %  The format of the EnhanceImage method is:
1290 %
1291 %      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1292 %
1293 %  A description of each parameter follows:
1294 %
1295 %    o image: the image.
1296 %
1297 %    o exception: return any errors or warnings in this structure.
1298 %
1299 */
1300 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1301 {
1302 #define EnhanceImageTag  "Enhance/Image"
1303 #define EnhancePixel(weight) \
1304   mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1305   distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1306   distance_squared=(4.0+mean)*distance*distance; \
1307   mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1308   distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1309   distance_squared+=(7.0-mean)*distance*distance; \
1310   mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1311   distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1312   distance_squared+=(5.0-mean)*distance*distance; \
1313   mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1314   distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1315   distance_squared+=(5.0-mean)*distance*distance; \
1316   mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1317   distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1318   distance_squared+=(5.0-mean)*distance*distance; \
1319   if (distance_squared < 0.069) \
1320     { \
1321       aggregate.red+=(weight)*GetPixelRed(image,r); \
1322       aggregate.green+=(weight)*GetPixelGreen(image,r); \
1323       aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1324       aggregate.black+=(weight)*GetPixelBlack(image,r); \
1325       aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1326       total_weight+=(weight); \
1327     } \
1328   r+=GetPixelChannels(image);
1329
1330   CacheView
1331     *enhance_view,
1332     *image_view;
1333
1334   Image
1335     *enhance_image;
1336
1337   MagickBooleanType
1338     status;
1339
1340   MagickOffsetType
1341     progress;
1342
1343   ssize_t
1344     y;
1345
1346   /*
1347     Initialize enhanced image attributes.
1348   */
1349   assert(image != (const Image *) NULL);
1350   assert(image->signature == MagickCoreSignature);
1351   if (image->debug != MagickFalse)
1352     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1353   assert(exception != (ExceptionInfo *) NULL);
1354   assert(exception->signature == MagickCoreSignature);
1355   enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1356     exception);
1357   if (enhance_image == (Image *) NULL)
1358     return((Image *) NULL);
1359   if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1360     {
1361       enhance_image=DestroyImage(enhance_image);
1362       return((Image *) NULL);
1363     }
1364   /*
1365     Enhance image.
1366   */
1367   status=MagickTrue;
1368   progress=0;
1369   image_view=AcquireVirtualCacheView(image,exception);
1370   enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1371 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1372   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1373     magick_threads(image,enhance_image,image->rows,1)
1374 #endif
1375   for (y=0; y < (ssize_t) image->rows; y++)
1376   {
1377     PixelInfo
1378       pixel;
1379
1380     register const Quantum
1381       *magick_restrict p;
1382
1383     register Quantum
1384       *magick_restrict q;
1385
1386     register ssize_t
1387       x;
1388
1389     ssize_t
1390       center;
1391
1392     if (status == MagickFalse)
1393       continue;
1394     p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1395     q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1396       exception);
1397     if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1398       {
1399         status=MagickFalse;
1400         continue;
1401       }
1402     center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1403     GetPixelInfo(image,&pixel);
1404     for (x=0; x < (ssize_t) image->columns; x++)
1405     {
1406       double
1407         distance,
1408         distance_squared,
1409         mean,
1410         total_weight;
1411
1412       PixelInfo
1413         aggregate;
1414
1415       register const Quantum
1416         *magick_restrict r;
1417
1418       if (GetPixelWriteMask(image,p) == 0)
1419         {
1420           SetPixelBackgoundColor(enhance_image,q);
1421           p+=GetPixelChannels(image);
1422           q+=GetPixelChannels(enhance_image);
1423           continue;
1424         }
1425       GetPixelInfo(image,&aggregate);
1426       total_weight=0.0;
1427       GetPixelInfoPixel(image,p+center,&pixel);
1428       r=p;
1429       EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1430         EnhancePixel(8.0); EnhancePixel(5.0);
1431       r=p+GetPixelChannels(image)*(image->columns+4);
1432       EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1433         EnhancePixel(20.0); EnhancePixel(8.0);
1434       r=p+2*GetPixelChannels(image)*(image->columns+4);
1435       EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1436         EnhancePixel(40.0); EnhancePixel(10.0);
1437       r=p+3*GetPixelChannels(image)*(image->columns+4);
1438       EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1439         EnhancePixel(20.0); EnhancePixel(8.0);
1440       r=p+4*GetPixelChannels(image)*(image->columns+4);
1441       EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1442         EnhancePixel(8.0); EnhancePixel(5.0);
1443       pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1444       pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1445       pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1446       pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1447       pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1448       SetPixelViaPixelInfo(image,&pixel,q);
1449       p+=GetPixelChannels(image);
1450       q+=GetPixelChannels(enhance_image);
1451     }
1452     if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1453       status=MagickFalse;
1454     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1455       {
1456         MagickBooleanType
1457           proceed;
1458
1459 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1460         #pragma omp critical (MagickCore_EnhanceImage)
1461 #endif
1462         proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1463         if (proceed == MagickFalse)
1464           status=MagickFalse;
1465       }
1466   }
1467   enhance_view=DestroyCacheView(enhance_view);
1468   image_view=DestroyCacheView(image_view);
1469   if (status == MagickFalse)
1470     enhance_image=DestroyImage(enhance_image);
1471   return(enhance_image);
1472 }
1473 \f
1474 /*
1475 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1476 %                                                                             %
1477 %                                                                             %
1478 %                                                                             %
1479 %     E q u a l i z e I m a g e                                               %
1480 %                                                                             %
1481 %                                                                             %
1482 %                                                                             %
1483 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1484 %
1485 %  EqualizeImage() applies a histogram equalization to the image.
1486 %
1487 %  The format of the EqualizeImage method is:
1488 %
1489 %      MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1490 %
1491 %  A description of each parameter follows:
1492 %
1493 %    o image: the image.
1494 %
1495 %    o exception: return any errors or warnings in this structure.
1496 %
1497 */
1498 MagickExport MagickBooleanType EqualizeImage(Image *image,
1499   ExceptionInfo *exception)
1500 {
1501 #define EqualizeImageTag  "Equalize/Image"
1502
1503   CacheView
1504     *image_view;
1505
1506   double
1507     black[CompositePixelChannel+1],
1508     *equalize_map,
1509     *histogram,
1510     *map,
1511     white[CompositePixelChannel+1];
1512
1513   MagickBooleanType
1514     status;
1515
1516   MagickOffsetType
1517     progress;
1518
1519   register ssize_t
1520     i;
1521
1522   ssize_t
1523     y;
1524
1525   /*
1526     Allocate and initialize histogram arrays.
1527   */
1528   assert(image != (Image *) NULL);
1529   assert(image->signature == MagickCoreSignature);
1530 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1531   if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1532     return(MagickTrue);
1533 #endif
1534   if (image->debug != MagickFalse)
1535     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1536   equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1537     GetPixelChannels(image)*sizeof(*equalize_map));
1538   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1539     sizeof(*histogram));
1540   map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1541     sizeof(*map));
1542   if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1543       (map == (double *) NULL))
1544     {
1545       if (map != (double *) NULL)
1546         map=(double *) RelinquishMagickMemory(map);
1547       if (histogram != (double *) NULL)
1548         histogram=(double *) RelinquishMagickMemory(histogram);
1549       if (equalize_map != (double *) NULL)
1550         equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1551       ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1552         image->filename);
1553     }
1554   /*
1555     Form histogram.
1556   */
1557   status=MagickTrue;
1558   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1559     sizeof(*histogram));
1560   image_view=AcquireVirtualCacheView(image,exception);
1561   for (y=0; y < (ssize_t) image->rows; y++)
1562   {
1563     register const Quantum
1564       *magick_restrict p;
1565
1566     register ssize_t
1567       x;
1568
1569     if (status == MagickFalse)
1570       continue;
1571     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1572     if (p == (const Quantum *) NULL)
1573       {
1574         status=MagickFalse;
1575         continue;
1576       }
1577     for (x=0; x < (ssize_t) image->columns; x++)
1578     {
1579       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1580       {
1581         double
1582           intensity;
1583
1584         intensity=p[i];
1585         if ((image->channel_mask & SyncChannels) != 0)
1586           intensity=GetPixelIntensity(image,p);
1587         histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1588       }
1589       p+=GetPixelChannels(image);
1590     }
1591   }
1592   image_view=DestroyCacheView(image_view);
1593   /*
1594     Integrate the histogram to get the equalization map.
1595   */
1596   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1597   {
1598     double
1599       intensity;
1600
1601     register ssize_t
1602       j;
1603
1604     intensity=0.0;
1605     for (j=0; j <= (ssize_t) MaxMap; j++)
1606     {
1607       intensity+=histogram[GetPixelChannels(image)*j+i];
1608       map[GetPixelChannels(image)*j+i]=intensity;
1609     }
1610   }
1611   (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1612     sizeof(*equalize_map));
1613   (void) ResetMagickMemory(black,0,sizeof(*black));
1614   (void) ResetMagickMemory(white,0,sizeof(*white));
1615   for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1616   {
1617     register ssize_t
1618       j;
1619
1620     black[i]=map[i];
1621     white[i]=map[GetPixelChannels(image)*MaxMap+i];
1622     if (black[i] != white[i])
1623       for (j=0; j <= (ssize_t) MaxMap; j++)
1624         equalize_map[GetPixelChannels(image)*j+i]=(double)
1625           ScaleMapToQuantum((double) ((MaxMap*(map[
1626           GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1627   }
1628   histogram=(double *) RelinquishMagickMemory(histogram);
1629   map=(double *) RelinquishMagickMemory(map);
1630   if (image->storage_class == PseudoClass)
1631     {
1632       register ssize_t
1633         j;
1634
1635       /*
1636         Equalize colormap.
1637       */
1638       for (j=0; j < (ssize_t) image->colors; j++)
1639       {
1640         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1641           {
1642             PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1643             if (black[channel] != white[channel])
1644               image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1645                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1646                 channel];
1647           }
1648         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1649           {
1650             PixelChannel channel=GetPixelChannelChannel(image,
1651               GreenPixelChannel);
1652             if (black[channel] != white[channel])
1653               image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1654                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1655                 channel];
1656           }
1657         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1658           {
1659             PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1660             if (black[channel] != white[channel])
1661               image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1662                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1663                 channel];
1664           }
1665         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1666           {
1667             PixelChannel channel=GetPixelChannelChannel(image,
1668               AlphaPixelChannel);
1669             if (black[channel] != white[channel])
1670               image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1671                 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1672                 channel];
1673           }
1674       }
1675     }
1676   /*
1677     Equalize image.
1678   */
1679   progress=0;
1680   image_view=AcquireAuthenticCacheView(image,exception);
1681 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1682   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1683     magick_threads(image,image,image->rows,1)
1684 #endif
1685   for (y=0; y < (ssize_t) image->rows; y++)
1686   {
1687     register Quantum
1688       *magick_restrict q;
1689
1690     register ssize_t
1691       x;
1692
1693     if (status == MagickFalse)
1694       continue;
1695     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1696     if (q == (Quantum *) NULL)
1697       {
1698         status=MagickFalse;
1699         continue;
1700       }
1701     for (x=0; x < (ssize_t) image->columns; x++)
1702     {
1703       register ssize_t
1704         j;
1705
1706       if (GetPixelWriteMask(image,q) == 0)
1707         {
1708           q+=GetPixelChannels(image);
1709           continue;
1710         }
1711       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1712       {
1713         PixelChannel channel=GetPixelChannelChannel(image,j);
1714         PixelTrait traits=GetPixelChannelTraits(image,channel);
1715         if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1716           continue;
1717         q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1718           ScaleQuantumToMap(q[j])+j]);
1719       }
1720       q+=GetPixelChannels(image);
1721     }
1722     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1723       status=MagickFalse;
1724     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1725       {
1726         MagickBooleanType
1727           proceed;
1728
1729 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1730         #pragma omp critical (MagickCore_EqualizeImage)
1731 #endif
1732         proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1733         if (proceed == MagickFalse)
1734           status=MagickFalse;
1735       }
1736   }
1737   image_view=DestroyCacheView(image_view);
1738   equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1739   return(status);
1740 }
1741 \f
1742 /*
1743 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744 %                                                                             %
1745 %                                                                             %
1746 %                                                                             %
1747 %     G a m m a I m a g e                                                     %
1748 %                                                                             %
1749 %                                                                             %
1750 %                                                                             %
1751 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1752 %
1753 %  GammaImage() gamma-corrects a particular image channel.  The same
1754 %  image viewed on different devices will have perceptual differences in the
1755 %  way the image's intensities are represented on the screen.  Specify
1756 %  individual gamma levels for the red, green, and blue channels, or adjust
1757 %  all three with the gamma parameter.  Values typically range from 0.8 to 2.3.
1758 %
1759 %  You can also reduce the influence of a particular channel with a gamma
1760 %  value of 0.
1761 %
1762 %  The format of the GammaImage method is:
1763 %
1764 %      MagickBooleanType GammaImage(Image *image,const double gamma,
1765 %        ExceptionInfo *exception)
1766 %
1767 %  A description of each parameter follows:
1768 %
1769 %    o image: the image.
1770 %
1771 %    o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1772 %
1773 %    o gamma: the image gamma.
1774 %
1775 */
1776
1777 static inline double gamma_pow(const double value,const double gamma)
1778 {
1779   return(value < 0.0 ? value : pow(value,gamma));
1780 }
1781
1782 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1783   ExceptionInfo *exception)
1784 {
1785 #define GammaCorrectImageTag  "GammaCorrect/Image"
1786
1787   CacheView
1788     *image_view;
1789
1790   MagickBooleanType
1791     status;
1792
1793   MagickOffsetType
1794     progress;
1795
1796   Quantum
1797     *gamma_map;
1798
1799   register ssize_t
1800     i;
1801
1802   ssize_t
1803     y;
1804
1805   /*
1806     Allocate and initialize gamma maps.
1807   */
1808   assert(image != (Image *) NULL);
1809   assert(image->signature == MagickCoreSignature);
1810   if (image->debug != MagickFalse)
1811     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1812   if (gamma == 1.0)
1813     return(MagickTrue);
1814   gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1815   if (gamma_map == (Quantum *) NULL)
1816     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1817       image->filename);
1818   (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1819   if (gamma != 0.0)
1820     for (i=0; i <= (ssize_t) MaxMap; i++)
1821       gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1822         MaxMap,1.0/gamma)));
1823   if (image->storage_class == PseudoClass)
1824     for (i=0; i < (ssize_t) image->colors; i++)
1825     {
1826       /*
1827         Gamma-correct colormap.
1828       */
1829 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1830       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1831         image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1832           ClampToQuantum(image->colormap[i].red))];
1833       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1834         image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1835           ClampToQuantum(image->colormap[i].green))];
1836       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1837         image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1838           ClampToQuantum(image->colormap[i].blue))];
1839       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1840         image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1841           ClampToQuantum(image->colormap[i].alpha))];
1842 #else
1843       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1844         image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1845           image->colormap[i].red,1.0/gamma);
1846       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1847         image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1848           image->colormap[i].green,1.0/gamma);
1849       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1850         image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1851           image->colormap[i].blue,1.0/gamma);
1852       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1853         image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1854           image->colormap[i].alpha,1.0/gamma);
1855 #endif
1856     }
1857   /*
1858     Gamma-correct image.
1859   */
1860   status=MagickTrue;
1861   progress=0;
1862   image_view=AcquireAuthenticCacheView(image,exception);
1863 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1864   #pragma omp parallel for schedule(static,4) shared(progress,status) \
1865     magick_threads(image,image,image->rows,1)
1866 #endif
1867   for (y=0; y < (ssize_t) image->rows; y++)
1868   {
1869     register Quantum
1870       *magick_restrict q;
1871
1872     register ssize_t
1873       x;
1874
1875     if (status == MagickFalse)
1876       continue;
1877     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1878     if (q == (Quantum *) NULL)
1879       {
1880         status=MagickFalse;
1881         continue;
1882       }
1883     for (x=0; x < (ssize_t) image->columns; x++)
1884     {
1885       register ssize_t
1886         j;
1887
1888       if (GetPixelWriteMask(image,q) == 0)
1889         {
1890           q+=GetPixelChannels(image);
1891           continue;
1892         }
1893       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1894       {
1895         PixelChannel channel=GetPixelChannelChannel(image,j);
1896         PixelTrait traits=GetPixelChannelTraits(image,channel);
1897         if ((traits & UpdatePixelTrait) == 0)
1898           continue;
1899 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1900         q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1901 #else
1902         q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1903 #endif
1904       }
1905       q+=GetPixelChannels(image);
1906     }
1907     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1908       status=MagickFalse;
1909     if (image->progress_monitor != (MagickProgressMonitor) NULL)
1910       {
1911         MagickBooleanType
1912           proceed;
1913
1914 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1915         #pragma omp critical (MagickCore_GammaImage)
1916 #endif
1917         proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1918           image->rows);
1919         if (proceed == MagickFalse)
1920           status=MagickFalse;
1921       }
1922   }
1923   image_view=DestroyCacheView(image_view);
1924   gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1925   if (image->gamma != 0.0)
1926     image->gamma*=gamma;
1927   return(status);
1928 }
1929 \f
1930 /*
1931 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1932 %                                                                             %
1933 %                                                                             %
1934 %                                                                             %
1935 %     G r a y s c a l e I m a g e                                             %
1936 %                                                                             %
1937 %                                                                             %
1938 %                                                                             %
1939 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1940 %
1941 %  GrayscaleImage() converts the image to grayscale.
1942 %
1943 %  The format of the GrayscaleImage method is:
1944 %
1945 %      MagickBooleanType GrayscaleImage(Image *image,
1946 %        const PixelIntensityMethod method ,ExceptionInfo *exception)
1947 %
1948 %  A description of each parameter follows:
1949 %
1950 %    o image: the image.
1951 %
1952 %    o method: the pixel intensity method.
1953 %
1954 %    o exception: return any errors or warnings in this structure.
1955 %
1956 */
1957 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1958   const PixelIntensityMethod method,ExceptionInfo *exception)
1959 {
1960 #define GrayscaleImageTag  "Grayscale/Image"
1961
1962   CacheView
1963     *image_view;
1964
1965   MagickBooleanType
1966     status;
1967
1968   MagickOffsetType
1969     progress;
1970
1971   ssize_t
1972     y;
1973
1974   assert(image != (Image *) NULL);
1975   assert(image->signature == MagickCoreSignature);
1976   if (image->debug != MagickFalse)
1977     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1978   if (image->storage_class == PseudoClass)
1979     {
1980       if (SyncImage(image,exception) == MagickFalse)
1981         return(MagickFalse);
1982       if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1983         return(MagickFalse);
1984     }
1985 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1986   if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1987     {
1988       image->intensity=method;
1989       image->type=GrayscaleType;
1990       return(SetImageColorspace(image,GRAYColorspace,exception));
1991     }
1992 #endif
1993   /*
1994     Grayscale image.
1995   */
1996   status=MagickTrue;
1997   progress=0;
1998   image_view=AcquireAuthenticCacheView(image,exception);
1999 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2000   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2001     magick_threads(image,image,image->rows,1)
2002 #endif
2003   for (y=0; y < (ssize_t) image->rows; y++)
2004   {
2005     register Quantum
2006       *magick_restrict q;
2007
2008     register ssize_t
2009       x;
2010
2011     if (status == MagickFalse)
2012       continue;
2013     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2014     if (q == (Quantum *) NULL)
2015       {
2016         status=MagickFalse;
2017         continue;
2018       }
2019     for (x=0; x < (ssize_t) image->columns; x++)
2020     {
2021       MagickRealType
2022         blue,
2023         green,
2024         red,
2025         intensity;
2026
2027       if (GetPixelWriteMask(image,q) == 0)
2028         {
2029           q+=GetPixelChannels(image);
2030           continue;
2031         }
2032       red=(MagickRealType) GetPixelRed(image,q);
2033       green=(MagickRealType) GetPixelGreen(image,q);
2034       blue=(MagickRealType) GetPixelBlue(image,q);
2035       intensity=0.0;
2036       switch (method)
2037       {
2038         case AveragePixelIntensityMethod:
2039         {
2040           intensity=(red+green+blue)/3.0;
2041           break;
2042         }
2043         case BrightnessPixelIntensityMethod:
2044         {
2045           intensity=MagickMax(MagickMax(red,green),blue);
2046           break;
2047         }
2048         case LightnessPixelIntensityMethod:
2049         {
2050           intensity=(MagickMin(MagickMin(red,green),blue)+
2051             MagickMax(MagickMax(red,green),blue))/2.0;
2052           break;
2053         }
2054         case MSPixelIntensityMethod:
2055         {
2056           intensity=(MagickRealType) (((double) red*red+green*green+
2057             blue*blue)/3.0);
2058           break;
2059         }
2060         case Rec601LumaPixelIntensityMethod:
2061         {
2062           if (image->colorspace == RGBColorspace)
2063             {
2064               red=EncodePixelGamma(red);
2065               green=EncodePixelGamma(green);
2066               blue=EncodePixelGamma(blue);
2067             }
2068           intensity=0.298839*red+0.586811*green+0.114350*blue;
2069           break;
2070         }
2071         case Rec601LuminancePixelIntensityMethod:
2072         {
2073           if (image->colorspace == sRGBColorspace)
2074             {
2075               red=DecodePixelGamma(red);
2076               green=DecodePixelGamma(green);
2077               blue=DecodePixelGamma(blue);
2078             }
2079           intensity=0.298839*red+0.586811*green+0.114350*blue;
2080           break;
2081         }
2082         case Rec709LumaPixelIntensityMethod:
2083         default:
2084         {
2085           if (image->colorspace == RGBColorspace)
2086             {
2087               red=EncodePixelGamma(red);
2088               green=EncodePixelGamma(green);
2089               blue=EncodePixelGamma(blue);
2090             }
2091           intensity=0.212656*red+0.715158*green+0.072186*blue;
2092           break;
2093         }
2094         case Rec709LuminancePixelIntensityMethod:
2095         {
2096           if (image->colorspace == sRGBColorspace)
2097             {
2098               red=DecodePixelGamma(red);
2099               green=DecodePixelGamma(green);
2100               blue=DecodePixelGamma(blue);
2101             }
2102           intensity=0.212656*red+0.715158*green+0.072186*blue;
2103           break;
2104         }
2105         case RMSPixelIntensityMethod:
2106         {
2107           intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2108             blue*blue)/sqrt(3.0));
2109           break;
2110         }
2111       }
2112       SetPixelGray(image,ClampToQuantum(intensity),q);
2113       q+=GetPixelChannels(image);
2114     }
2115     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2116       status=MagickFalse;
2117     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2118       {
2119         MagickBooleanType
2120           proceed;
2121
2122 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2123         #pragma omp critical (MagickCore_GrayscaleImage)
2124 #endif
2125         proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2126            image->rows);
2127         if (proceed == MagickFalse)
2128           status=MagickFalse;
2129       }
2130   }
2131   image_view=DestroyCacheView(image_view);
2132   image->intensity=method;
2133   image->type=GrayscaleType;
2134   return(SetImageColorspace(image,GRAYColorspace,exception));
2135 }
2136 \f
2137 /*
2138 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2139 %                                                                             %
2140 %                                                                             %
2141 %                                                                             %
2142 %     H a l d C l u t I m a g e                                               %
2143 %                                                                             %
2144 %                                                                             %
2145 %                                                                             %
2146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2147 %
2148 %  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
2149 %  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2150 %  Create it with the HALD coder.  You can apply any color transformation to
2151 %  the Hald image and then use this method to apply the transform to the
2152 %  image.
2153 %
2154 %  The format of the HaldClutImage method is:
2155 %
2156 %      MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2157 %        ExceptionInfo *exception)
2158 %
2159 %  A description of each parameter follows:
2160 %
2161 %    o image: the image, which is replaced by indexed CLUT values
2162 %
2163 %    o hald_image: the color lookup table image for replacement color values.
2164 %
2165 %    o exception: return any errors or warnings in this structure.
2166 %
2167 */
2168 MagickExport MagickBooleanType HaldClutImage(Image *image,
2169   const Image *hald_image,ExceptionInfo *exception)
2170 {
2171 #define HaldClutImageTag  "Clut/Image"
2172
2173   typedef struct _HaldInfo
2174   {
2175     double
2176       x,
2177       y,
2178       z;
2179   } HaldInfo;
2180
2181   CacheView
2182     *hald_view,
2183     *image_view;
2184
2185   double
2186     width;
2187
2188   MagickBooleanType
2189     status;
2190
2191   MagickOffsetType
2192     progress;
2193
2194   PixelInfo
2195     zero;
2196
2197   size_t
2198     cube_size,
2199     length,
2200     level;
2201
2202   ssize_t
2203     y;
2204
2205   assert(image != (Image *) NULL);
2206   assert(image->signature == MagickCoreSignature);
2207   if (image->debug != MagickFalse)
2208     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2209   assert(hald_image != (Image *) NULL);
2210   assert(hald_image->signature == MagickCoreSignature);
2211   if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2212     return(MagickFalse);
2213   if (image->alpha_trait == UndefinedPixelTrait)
2214     (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2215   /*
2216     Hald clut image.
2217   */
2218   status=MagickTrue;
2219   progress=0;
2220   length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2221     (MagickRealType) hald_image->rows);
2222   for (level=2; (level*level*level) < length; level++) ;
2223   level*=level;
2224   cube_size=level*level;
2225   width=(double) hald_image->columns;
2226   GetPixelInfo(hald_image,&zero);
2227   hald_view=AcquireVirtualCacheView(hald_image,exception);
2228   image_view=AcquireAuthenticCacheView(image,exception);
2229 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2230   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2231     magick_threads(image,image,image->rows,1)
2232 #endif
2233   for (y=0; y < (ssize_t) image->rows; y++)
2234   {
2235     register Quantum
2236       *magick_restrict q;
2237
2238     register ssize_t
2239       x;
2240
2241     if (status == MagickFalse)
2242       continue;
2243     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2244     if (q == (Quantum *) NULL)
2245       {
2246         status=MagickFalse;
2247         continue;
2248       }
2249     for (x=0; x < (ssize_t) image->columns; x++)
2250     {
2251       double
2252         offset;
2253
2254       HaldInfo
2255         point;
2256
2257       PixelInfo
2258         pixel,
2259         pixel1,
2260         pixel2,
2261         pixel3,
2262         pixel4;
2263
2264       point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2265       point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2266       point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2267       offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2268       point.x-=floor(point.x);
2269       point.y-=floor(point.y);
2270       point.z-=floor(point.z);
2271       pixel1=zero;
2272       (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2273         fmod(offset,width),floor(offset/width),&pixel1,exception);
2274       pixel2=zero;
2275       (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2276         fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2277       pixel3=zero;
2278       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2279         point.y,&pixel3);
2280       offset+=cube_size;
2281       (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2282         fmod(offset,width),floor(offset/width),&pixel1,exception);
2283       (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2284         fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2285       pixel4=zero;
2286       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2287         point.y,&pixel4);
2288       pixel=zero;
2289       CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2290         point.z,&pixel);
2291       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2292         SetPixelRed(image,ClampToQuantum(pixel.red),q);
2293       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2294         SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2295       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2296         SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2297       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2298           (image->colorspace == CMYKColorspace))
2299         SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2300       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2301           (image->alpha_trait != UndefinedPixelTrait))
2302         SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2303       q+=GetPixelChannels(image);
2304     }
2305     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2306       status=MagickFalse;
2307     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2308       {
2309         MagickBooleanType
2310           proceed;
2311
2312 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2313         #pragma omp critical (MagickCore_HaldClutImage)
2314 #endif
2315         proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2316         if (proceed == MagickFalse)
2317           status=MagickFalse;
2318       }
2319   }
2320   hald_view=DestroyCacheView(hald_view);
2321   image_view=DestroyCacheView(image_view);
2322   return(status);
2323 }
2324 \f
2325 /*
2326 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2327 %                                                                             %
2328 %                                                                             %
2329 %                                                                             %
2330 %     L e v e l I m a g e                                                     %
2331 %                                                                             %
2332 %                                                                             %
2333 %                                                                             %
2334 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2335 %
2336 %  LevelImage() adjusts the levels of a particular image channel by
2337 %  scaling the colors falling between specified white and black points to
2338 %  the full available quantum range.
2339 %
2340 %  The parameters provided represent the black, and white points.  The black
2341 %  point specifies the darkest color in the image. Colors darker than the
2342 %  black point are set to zero.  White point specifies the lightest color in
2343 %  the image.  Colors brighter than the white point are set to the maximum
2344 %  quantum value.
2345 %
2346 %  If a '!' flag is given, map black and white colors to the given levels
2347 %  rather than mapping those levels to black and white.  See
2348 %  LevelizeImage() below.
2349 %
2350 %  Gamma specifies a gamma correction to apply to the image.
2351 %
2352 %  The format of the LevelImage method is:
2353 %
2354 %      MagickBooleanType LevelImage(Image *image,const double black_point,
2355 %        const double white_point,const double gamma,ExceptionInfo *exception)
2356 %
2357 %  A description of each parameter follows:
2358 %
2359 %    o image: the image.
2360 %
2361 %    o black_point: The level to map zero (black) to.
2362 %
2363 %    o white_point: The level to map QuantumRange (white) to.
2364 %
2365 %    o exception: return any errors or warnings in this structure.
2366 %
2367 */
2368
2369 static inline double LevelPixel(const double black_point,
2370   const double white_point,const double gamma,const double pixel)
2371 {
2372   double
2373     level_pixel,
2374     scale;
2375
2376   if (fabs(white_point-black_point) < MagickEpsilon)
2377     return(pixel);
2378   scale=1.0/(white_point-black_point);
2379   level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2380     1.0/gamma);
2381   return(level_pixel);
2382 }
2383
2384 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2385   const double white_point,const double gamma,ExceptionInfo *exception)
2386 {
2387 #define LevelImageTag  "Level/Image"
2388
2389   CacheView
2390     *image_view;
2391
2392   MagickBooleanType
2393     status;
2394
2395   MagickOffsetType
2396     progress;
2397
2398   register ssize_t
2399     i;
2400
2401   ssize_t
2402     y;
2403
2404   /*
2405     Allocate and initialize levels map.
2406   */
2407   assert(image != (Image *) NULL);
2408   assert(image->signature == MagickCoreSignature);
2409   if (image->debug != MagickFalse)
2410     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2411   if (image->storage_class == PseudoClass)
2412     for (i=0; i < (ssize_t) image->colors; i++)
2413     {
2414       /*
2415         Level colormap.
2416       */
2417       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2418         image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2419           white_point,gamma,image->colormap[i].red));
2420       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2421         image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2422           white_point,gamma,image->colormap[i].green));
2423       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2424         image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2425           white_point,gamma,image->colormap[i].blue));
2426       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2427         image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2428           white_point,gamma,image->colormap[i].alpha));
2429     }
2430   /*
2431     Level image.
2432   */
2433   status=MagickTrue;
2434   progress=0;
2435   image_view=AcquireAuthenticCacheView(image,exception);
2436 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2437   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2438     magick_threads(image,image,image->rows,1)
2439 #endif
2440   for (y=0; y < (ssize_t) image->rows; y++)
2441   {
2442     register Quantum
2443       *magick_restrict q;
2444
2445     register ssize_t
2446       x;
2447
2448     if (status == MagickFalse)
2449       continue;
2450     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2451     if (q == (Quantum *) NULL)
2452       {
2453         status=MagickFalse;
2454         continue;
2455       }
2456     for (x=0; x < (ssize_t) image->columns; x++)
2457     {
2458       register ssize_t
2459         j;
2460
2461       if (GetPixelWriteMask(image,q) == 0)
2462         {
2463           q+=GetPixelChannels(image);
2464           continue;
2465         }
2466       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2467       {
2468         PixelChannel channel=GetPixelChannelChannel(image,j);
2469         PixelTrait traits=GetPixelChannelTraits(image,channel);
2470         if ((traits & UpdatePixelTrait) == 0)
2471           continue;
2472         q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2473           (double) q[j]));
2474       }
2475       q+=GetPixelChannels(image);
2476     }
2477     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2478       status=MagickFalse;
2479     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2480       {
2481         MagickBooleanType
2482           proceed;
2483
2484 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2485         #pragma omp critical (MagickCore_LevelImage)
2486 #endif
2487         proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2488         if (proceed == MagickFalse)
2489           status=MagickFalse;
2490       }
2491   }
2492   image_view=DestroyCacheView(image_view);
2493   (void) ClampImage(image,exception);
2494   return(status);
2495 }
2496 \f
2497 /*
2498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499 %                                                                             %
2500 %                                                                             %
2501 %                                                                             %
2502 %     L e v e l i z e I m a g e                                               %
2503 %                                                                             %
2504 %                                                                             %
2505 %                                                                             %
2506 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2507 %
2508 %  LevelizeImage() applies the reversed LevelImage() operation to just
2509 %  the specific channels specified.  It compresses the full range of color
2510 %  values, so that they lie between the given black and white points. Gamma is
2511 %  applied before the values are mapped.
2512 %
2513 %  LevelizeImage() can be called with by using a +level command line
2514 %  API option, or using a '!' on a -level or LevelImage() geometry string.
2515 %
2516 %  It can be used to de-contrast a greyscale image to the exact levels
2517 %  specified.  Or by using specific levels for each channel of an image you
2518 %  can convert a gray-scale image to any linear color gradient, according to
2519 %  those levels.
2520 %
2521 %  The format of the LevelizeImage method is:
2522 %
2523 %      MagickBooleanType LevelizeImage(Image *image,const double black_point,
2524 %        const double white_point,const double gamma,ExceptionInfo *exception)
2525 %
2526 %  A description of each parameter follows:
2527 %
2528 %    o image: the image.
2529 %
2530 %    o black_point: The level to map zero (black) to.
2531 %
2532 %    o white_point: The level to map QuantumRange (white) to.
2533 %
2534 %    o gamma: adjust gamma by this factor before mapping values.
2535 %
2536 %    o exception: return any errors or warnings in this structure.
2537 %
2538 */
2539 MagickExport MagickBooleanType LevelizeImage(Image *image,
2540   const double black_point,const double white_point,const double gamma,
2541   ExceptionInfo *exception)
2542 {
2543 #define LevelizeImageTag  "Levelize/Image"
2544 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2545   (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2546
2547   CacheView
2548     *image_view;
2549
2550   MagickBooleanType
2551     status;
2552
2553   MagickOffsetType
2554     progress;
2555
2556   register ssize_t
2557     i;
2558
2559   ssize_t
2560     y;
2561
2562   /*
2563     Allocate and initialize levels map.
2564   */
2565   assert(image != (Image *) NULL);
2566   assert(image->signature == MagickCoreSignature);
2567   if (image->debug != MagickFalse)
2568     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2569   if (image->storage_class == PseudoClass)
2570     for (i=0; i < (ssize_t) image->colors; i++)
2571     {
2572       /*
2573         Level colormap.
2574       */
2575       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2576         image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2577       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2578         image->colormap[i].green=(double) LevelizeValue(
2579           image->colormap[i].green);
2580       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2581         image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2582       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2583         image->colormap[i].alpha=(double) LevelizeValue(
2584           image->colormap[i].alpha);
2585     }
2586   /*
2587     Level image.
2588   */
2589   status=MagickTrue;
2590   progress=0;
2591   image_view=AcquireAuthenticCacheView(image,exception);
2592 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2593   #pragma omp parallel for schedule(static,4) shared(progress,status) \
2594     magick_threads(image,image,image->rows,1)
2595 #endif
2596   for (y=0; y < (ssize_t) image->rows; y++)
2597   {
2598     register Quantum
2599       *magick_restrict q;
2600
2601     register ssize_t
2602       x;
2603
2604     if (status == MagickFalse)
2605       continue;
2606     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2607     if (q == (Quantum *) NULL)
2608       {
2609         status=MagickFalse;
2610         continue;
2611       }
2612     for (x=0; x < (ssize_t) image->columns; x++)
2613     {
2614       register ssize_t
2615         j;
2616
2617       if (GetPixelWriteMask(image,q) == 0)
2618         {
2619           q+=GetPixelChannels(image);
2620           continue;
2621         }
2622       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2623       {
2624         PixelChannel channel=GetPixelChannelChannel(image,j);
2625         PixelTrait traits=GetPixelChannelTraits(image,channel);
2626         if ((traits & UpdatePixelTrait) == 0)
2627           continue;
2628         q[j]=LevelizeValue(q[j]);
2629       }
2630       q+=GetPixelChannels(image);
2631     }
2632     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2633       status=MagickFalse;
2634     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2635       {
2636         MagickBooleanType
2637           proceed;
2638
2639 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2640         #pragma omp critical (MagickCore_LevelizeImage)
2641 #endif
2642         proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2643         if (proceed == MagickFalse)
2644           status=MagickFalse;
2645       }
2646   }
2647   image_view=DestroyCacheView(image_view);
2648   return(status);
2649 }
2650 \f
2651 /*
2652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2653 %                                                                             %
2654 %                                                                             %
2655 %                                                                             %
2656 %     L e v e l I m a g e C o l o r s                                         %
2657 %                                                                             %
2658 %                                                                             %
2659 %                                                                             %
2660 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2661 %
2662 %  LevelImageColors() maps the given color to "black" and "white" values,
2663 %  linearly spreading out the colors, and level values on a channel by channel
2664 %  bases, as per LevelImage().  The given colors allows you to specify
2665 %  different level ranges for each of the color channels separately.
2666 %
2667 %  If the boolean 'invert' is set true the image values will modifyed in the
2668 %  reverse direction. That is any existing "black" and "white" colors in the
2669 %  image will become the color values given, with all other values compressed
2670 %  appropriatally.  This effectivally maps a greyscale gradient into the given
2671 %  color gradient.
2672 %
2673 %  The format of the LevelImageColors method is:
2674 %
2675 %    MagickBooleanType LevelImageColors(Image *image,
2676 %      const PixelInfo *black_color,const PixelInfo *white_color,
2677 %      const MagickBooleanType invert,ExceptionInfo *exception)
2678 %
2679 %  A description of each parameter follows:
2680 %
2681 %    o image: the image.
2682 %
2683 %    o black_color: The color to map black to/from
2684 %
2685 %    o white_point: The color to map white to/from
2686 %
2687 %    o invert: if true map the colors (levelize), rather than from (level)
2688 %
2689 %    o exception: return any errors or warnings in this structure.
2690 %
2691 */
2692 MagickExport MagickBooleanType LevelImageColors(Image *image,
2693   const PixelInfo *black_color,const PixelInfo *white_color,
2694   const MagickBooleanType invert,ExceptionInfo *exception)
2695 {
2696   ChannelType
2697     channel_mask;
2698
2699   MagickStatusType
2700     status;
2701
2702   /*
2703     Allocate and initialize levels map.
2704   */
2705   assert(image != (Image *) NULL);
2706   assert(image->signature == MagickCoreSignature);
2707   if (image->debug != MagickFalse)
2708     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2709   if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2710       ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2711        (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2712     (void) SetImageColorspace(image,sRGBColorspace,exception);
2713   status=MagickTrue;
2714   if (invert == MagickFalse)
2715     {
2716       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2717         {
2718           channel_mask=SetImageChannelMask(image,RedChannel);
2719           status&=LevelImage(image,black_color->red,white_color->red,1.0,
2720             exception);
2721           (void) SetImageChannelMask(image,channel_mask);
2722         }
2723       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2724         {
2725           channel_mask=SetImageChannelMask(image,GreenChannel);
2726           status&=LevelImage(image,black_color->green,white_color->green,1.0,
2727             exception);
2728           (void) SetImageChannelMask(image,channel_mask);
2729         }
2730       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2731         {
2732           channel_mask=SetImageChannelMask(image,BlueChannel);
2733           status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2734             exception);
2735           (void) SetImageChannelMask(image,channel_mask);
2736         }
2737       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2738           (image->colorspace == CMYKColorspace))
2739         {
2740           channel_mask=SetImageChannelMask(image,BlackChannel);
2741           status&=LevelImage(image,black_color->black,white_color->black,1.0,
2742             exception);
2743           (void) SetImageChannelMask(image,channel_mask);
2744         }
2745       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2746           (image->alpha_trait != UndefinedPixelTrait))
2747         {
2748           channel_mask=SetImageChannelMask(image,AlphaChannel);
2749           status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2750             exception);
2751           (void) SetImageChannelMask(image,channel_mask);
2752         }
2753     }
2754   else
2755     {
2756       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2757         {
2758           channel_mask=SetImageChannelMask(image,RedChannel);
2759           status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2760             exception);
2761           (void) SetImageChannelMask(image,channel_mask);
2762         }
2763       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2764         {
2765           channel_mask=SetImageChannelMask(image,GreenChannel);
2766           status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2767             exception);
2768           (void) SetImageChannelMask(image,channel_mask);
2769         }
2770       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2771         {
2772           channel_mask=SetImageChannelMask(image,BlueChannel);
2773           status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2774             exception);
2775           (void) SetImageChannelMask(image,channel_mask);
2776         }
2777       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2778           (image->colorspace == CMYKColorspace))
2779         {
2780           channel_mask=SetImageChannelMask(image,BlackChannel);
2781           status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2782             exception);
2783           (void) SetImageChannelMask(image,channel_mask);
2784         }
2785       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2786           (image->alpha_trait != UndefinedPixelTrait))
2787         {
2788           channel_mask=SetImageChannelMask(image,AlphaChannel);
2789           status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2790             exception);
2791           (void) SetImageChannelMask(image,channel_mask);
2792         }
2793     }
2794   return(status != 0 ? MagickTrue : MagickFalse);
2795 }
2796 \f
2797 /*
2798 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2799 %                                                                             %
2800 %                                                                             %
2801 %                                                                             %
2802 %     L i n e a r S t r e t c h I m a g e                                     %
2803 %                                                                             %
2804 %                                                                             %
2805 %                                                                             %
2806 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2807 %
2808 %  LinearStretchImage() discards any pixels below the black point and above
2809 %  the white point and levels the remaining pixels.
2810 %
2811 %  The format of the LinearStretchImage method is:
2812 %
2813 %      MagickBooleanType LinearStretchImage(Image *image,
2814 %        const double black_point,const double white_point,
2815 %        ExceptionInfo *exception)
2816 %
2817 %  A description of each parameter follows:
2818 %
2819 %    o image: the image.
2820 %
2821 %    o black_point: the black point.
2822 %
2823 %    o white_point: the white point.
2824 %
2825 %    o exception: return any errors or warnings in this structure.
2826 %
2827 */
2828 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2829   const double black_point,const double white_point,ExceptionInfo *exception)
2830 {
2831 #define LinearStretchImageTag  "LinearStretch/Image"
2832
2833   CacheView
2834     *image_view;
2835
2836   double
2837     *histogram,
2838     intensity;
2839
2840   MagickBooleanType
2841     status;
2842
2843   ssize_t
2844     black,
2845     white,
2846     y;
2847
2848   /*
2849     Allocate histogram and linear map.
2850   */
2851   assert(image != (Image *) NULL);
2852   assert(image->signature == MagickCoreSignature);
2853   histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2854   if (histogram == (double *) NULL)
2855     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2856       image->filename);
2857   /*
2858     Form histogram.
2859   */
2860   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2861   image_view=AcquireVirtualCacheView(image,exception);
2862   for (y=0; y < (ssize_t) image->rows; y++)
2863   {
2864     register const Quantum
2865       *magick_restrict p;
2866
2867     register ssize_t
2868       x;
2869
2870     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2871     if (p == (const Quantum *) NULL)
2872       break;
2873     for (x=0; x < (ssize_t) image->columns; x++)
2874     {
2875       intensity=GetPixelIntensity(image,p);
2876       histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2877       p+=GetPixelChannels(image);
2878     }
2879   }
2880   image_view=DestroyCacheView(image_view);
2881   /*
2882     Find the histogram boundaries by locating the black and white point levels.
2883   */
2884   intensity=0.0;
2885   for (black=0; black < (ssize_t) MaxMap; black++)
2886   {
2887     intensity+=histogram[black];
2888     if (intensity >= black_point)
2889       break;
2890   }
2891   intensity=0.0;
2892   for (white=(ssize_t) MaxMap; white != 0; white--)
2893   {
2894     intensity+=histogram[white];
2895     if (intensity >= white_point)
2896       break;
2897   }
2898   histogram=(double *) RelinquishMagickMemory(histogram);
2899   status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2900     (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2901   return(status);
2902 }
2903
2904
2905 /*
2906 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2907 %                                                                             %
2908 %                                                                             %
2909 %                                                                             %
2910 %     M o d u l a t e I m a g e                                               %
2911 %                                                                             %
2912 %                                                                             %
2913 %                                                                             %
2914 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2915 %
2916 %  ModulateImage() lets you control the brightness, saturation, and hue
2917 %  of an image.  Modulate represents the brightness, saturation, and hue
2918 %  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
2919 %  modulation is lightness, saturation, and hue.  For HWB, use blackness,
2920 %  whiteness, and hue. And for HCL, use chrome, luma, and hue.
2921 %
2922 %  The format of the ModulateImage method is:
2923 %
2924 %      MagickBooleanType ModulateImage(Image *image,const char *modulate,
2925 %        ExceptionInfo *exception)
2926 %
2927 %  A description of each parameter follows:
2928 %
2929 %    o image: the image.
2930 %
2931 %    o modulate: Define the percent change in brightness, saturation, and hue.
2932 %
2933 %    o exception: return any errors or warnings in this structure.
2934 %
2935 */
2936
2937 static inline void ModulateHCL(const double percent_hue,
2938   const double percent_chroma,const double percent_luma,double *red,
2939   double *green,double *blue)
2940 {
2941   double
2942     hue,
2943     luma,
2944     chroma;
2945
2946   /*
2947     Increase or decrease color luma, chroma, or hue.
2948   */
2949   ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2950   hue+=fmod((percent_hue-100.0),200.0)/200.0;
2951   chroma*=0.01*percent_chroma;
2952   luma*=0.01*percent_luma;
2953   ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2954 }
2955
2956 static inline void ModulateHCLp(const double percent_hue,
2957   const double percent_chroma,const double percent_luma,double *red,
2958   double *green,double *blue)
2959 {
2960   double
2961     hue,
2962     luma,
2963     chroma;
2964
2965   /*
2966     Increase or decrease color luma, chroma, or hue.
2967   */
2968   ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2969   hue+=fmod((percent_hue-100.0),200.0)/200.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+=fmod((percent_hue-100.0),200.0)/200.0;
2989   saturation*=0.01*percent_saturation;
2990   brightness*=0.01*percent_brightness;
2991   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2992 }
2993
2994 static inline void ModulateHSI(const double percent_hue,
2995   const double percent_saturation,const double percent_intensity,double *red,
2996   double *green,double *blue)
2997 {
2998   double
2999     intensity,
3000     hue,
3001     saturation;
3002
3003   /*
3004     Increase or decrease color intensity, saturation, or hue.
3005   */
3006   ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3007   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3008   saturation*=0.01*percent_saturation;
3009   intensity*=0.01*percent_intensity;
3010   ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3011 }
3012
3013 static inline void ModulateHSL(const double percent_hue,
3014   const double percent_saturation,const double percent_lightness,double *red,
3015   double *green,double *blue)
3016 {
3017   double
3018     hue,
3019     lightness,
3020     saturation;
3021
3022   /*
3023     Increase or decrease color lightness, saturation, or hue.
3024   */
3025   ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3026   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3027   saturation*=0.01*percent_saturation;
3028   lightness*=0.01*percent_lightness;
3029   ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3030 }
3031
3032 static inline void ModulateHSV(const double percent_hue,
3033   const double percent_saturation,const double percent_value,double *red,
3034   double *green,double *blue)
3035 {
3036   double
3037     hue,
3038     saturation,
3039     value;
3040
3041   /*
3042     Increase or decrease color value, saturation, or hue.
3043   */
3044   ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3045   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3046   saturation*=0.01*percent_saturation;
3047   value*=0.01*percent_value;
3048   ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3049 }
3050
3051 static inline void ModulateHWB(const double percent_hue,
3052   const double percent_whiteness,const double percent_blackness,double *red,
3053   double *green,double *blue)
3054 {
3055   double
3056     blackness,
3057     hue,
3058     whiteness;
3059
3060   /*
3061     Increase or decrease color blackness, whiteness, or hue.
3062   */
3063   ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3064   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3065   blackness*=0.01*percent_blackness;
3066   whiteness*=0.01*percent_whiteness;
3067   ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3068 }
3069
3070 static inline void ModulateLCHab(const double percent_luma,
3071   const double percent_chroma,const double percent_hue,double *red,
3072   double *green,double *blue)
3073 {
3074   double
3075     hue,
3076     luma,
3077     chroma;
3078
3079   /*
3080     Increase or decrease color luma, chroma, or hue.
3081   */
3082   ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3083   luma*=0.01*percent_luma;
3084   chroma*=0.01*percent_chroma;
3085   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3086   ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3087 }
3088
3089 static inline void ModulateLCHuv(const double percent_luma,
3090   const double percent_chroma,const double percent_hue,double *red,
3091   double *green,double *blue)
3092 {
3093   double
3094     hue,
3095     luma,
3096     chroma;
3097
3098   /*
3099     Increase or decrease color luma, chroma, or hue.
3100   */
3101   ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3102   luma*=0.01*percent_luma;
3103   chroma*=0.01*percent_chroma;
3104   hue+=fmod((percent_hue-100.0),200.0)/200.0;
3105   ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3106 }
3107
3108 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3109   ExceptionInfo *exception)
3110 {
3111 #define ModulateImageTag  "Modulate/Image"
3112
3113   CacheView
3114     *image_view;
3115
3116   ColorspaceType
3117     colorspace;
3118
3119   const char
3120     *artifact;
3121
3122   double
3123     percent_brightness,
3124     percent_hue,
3125     percent_saturation;
3126
3127   GeometryInfo
3128     geometry_info;
3129
3130   MagickBooleanType
3131     status;
3132
3133   MagickOffsetType
3134     progress;
3135
3136   MagickStatusType
3137     flags;
3138
3139   register ssize_t
3140     i;
3141
3142   ssize_t
3143     y;
3144
3145   /*
3146     Initialize modulate table.
3147   */
3148   assert(image != (Image *) NULL);
3149   assert(image->signature == MagickCoreSignature);
3150   if (image->debug != MagickFalse)
3151     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3152   if (modulate == (char *) NULL)
3153     return(MagickFalse);
3154   if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3155     (void) SetImageColorspace(image,sRGBColorspace,exception);
3156   flags=ParseGeometry(modulate,&geometry_info);
3157   percent_brightness=geometry_info.rho;
3158   percent_saturation=geometry_info.sigma;
3159   if ((flags & SigmaValue) == 0)
3160     percent_saturation=100.0;
3161   percent_hue=geometry_info.xi;
3162   if ((flags & XiValue) == 0)
3163     percent_hue=100.0;
3164   colorspace=UndefinedColorspace;
3165   artifact=GetImageArtifact(image,"modulate:colorspace");
3166   if (artifact != (const char *) NULL)
3167     colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3168       MagickFalse,artifact);
3169   if (image->storage_class == PseudoClass)
3170     for (i=0; i < (ssize_t) image->colors; i++)
3171     {
3172       double
3173         blue,
3174         green,
3175         red;
3176
3177       /*
3178         Modulate image colormap.
3179       */
3180       red=(double) image->colormap[i].red;
3181       green=(double) image->colormap[i].green;
3182       blue=(double) image->colormap[i].blue;
3183       switch (colorspace)
3184       {
3185         case HCLColorspace:
3186         {
3187           ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3188             &red,&green,&blue);
3189           break;
3190         }
3191         case HCLpColorspace:
3192         {
3193           ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3194             &red,&green,&blue);
3195           break;
3196         }
3197         case HSBColorspace:
3198         {
3199           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3200             &red,&green,&blue);
3201           break;
3202         }
3203         case HSIColorspace:
3204         {
3205           ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3206             &red,&green,&blue);
3207           break;
3208         }
3209         case HSLColorspace:
3210         default:
3211         {
3212           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3213             &red,&green,&blue);
3214           break;
3215         }
3216         case HSVColorspace:
3217         {
3218           ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3219             &red,&green,&blue);
3220           break;
3221         }
3222         case HWBColorspace:
3223         {
3224           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3225             &red,&green,&blue);
3226           break;
3227         }
3228         case LCHColorspace:
3229         case LCHabColorspace:
3230         {
3231           ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3232             &red,&green,&blue);
3233           break;
3234         }
3235         case LCHuvColorspace:
3236         {
3237           ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3238             &red,&green,&blue);
3239           break;
3240         }
3241       }
3242       image->colormap[i].red=red;
3243       image->colormap[i].green=green;
3244       image->colormap[i].blue=blue;
3245     }
3246   /*
3247     Modulate image.
3248   */
3249 #if defined(MAGICKCORE_OPENCL_SUPPORT)
3250   if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3251         percent_saturation,colorspace,exception) != MagickFalse)
3252     return(MagickTrue);
3253 #endif
3254   status=MagickTrue;
3255   progress=0;
3256   image_view=AcquireAuthenticCacheView(image,exception);
3257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3258   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3259     magick_threads(image,image,image->rows,1)
3260 #endif
3261   for (y=0; y < (ssize_t) image->rows; y++)
3262   {
3263     register Quantum
3264       *magick_restrict q;
3265
3266     register ssize_t
3267       x;
3268
3269     if (status == MagickFalse)
3270       continue;
3271     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3272     if (q == (Quantum *) NULL)
3273       {
3274         status=MagickFalse;
3275         continue;
3276       }
3277     for (x=0; x < (ssize_t) image->columns; x++)
3278     {
3279       double
3280         blue,
3281         green,
3282         red;
3283
3284       if (GetPixelWriteMask(image,q) == 0)
3285         {
3286           q+=GetPixelChannels(image);
3287           continue;
3288         }
3289       red=(double) GetPixelRed(image,q);
3290       green=(double) GetPixelGreen(image,q);
3291       blue=(double) GetPixelBlue(image,q);
3292       switch (colorspace)
3293       {
3294         case HCLColorspace:
3295         {
3296           ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3297             &red,&green,&blue);
3298           break;
3299         }
3300         case HCLpColorspace:
3301         {
3302           ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3303             &red,&green,&blue);
3304           break;
3305         }
3306         case HSBColorspace:
3307         {
3308           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3309             &red,&green,&blue);
3310           break;
3311         }
3312         case HSLColorspace:
3313         default:
3314         {
3315           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3316             &red,&green,&blue);
3317           break;
3318         }
3319         case HSVColorspace:
3320         {
3321           ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3322             &red,&green,&blue);
3323           break;
3324         }
3325         case HWBColorspace:
3326         {
3327           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3328             &red,&green,&blue);
3329           break;
3330         }
3331         case LCHabColorspace:
3332         {
3333           ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3334             &red,&green,&blue);
3335           break;
3336         }
3337         case LCHColorspace:
3338         case LCHuvColorspace:
3339         {
3340           ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3341             &red,&green,&blue);
3342           break;
3343         }
3344       }
3345       SetPixelRed(image,ClampToQuantum(red),q);
3346       SetPixelGreen(image,ClampToQuantum(green),q);
3347       SetPixelBlue(image,ClampToQuantum(blue),q);
3348       q+=GetPixelChannels(image);
3349     }
3350     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3351       status=MagickFalse;
3352     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3353       {
3354         MagickBooleanType
3355           proceed;
3356
3357 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3358         #pragma omp critical (MagickCore_ModulateImage)
3359 #endif
3360         proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3361         if (proceed == MagickFalse)
3362           status=MagickFalse;
3363       }
3364   }
3365   image_view=DestroyCacheView(image_view);
3366   return(status);
3367 }
3368 \f
3369 /*
3370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3371 %                                                                             %
3372 %                                                                             %
3373 %                                                                             %
3374 %     N e g a t e I m a g e                                                   %
3375 %                                                                             %
3376 %                                                                             %
3377 %                                                                             %
3378 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3379 %
3380 %  NegateImage() negates the colors in the reference image.  The grayscale
3381 %  option means that only grayscale values within the image are negated.
3382 %
3383 %  The format of the NegateImage method is:
3384 %
3385 %      MagickBooleanType NegateImage(Image *image,
3386 %        const MagickBooleanType grayscale,ExceptionInfo *exception)
3387 %
3388 %  A description of each parameter follows:
3389 %
3390 %    o image: the image.
3391 %
3392 %    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3393 %
3394 %    o exception: return any errors or warnings in this structure.
3395 %
3396 */
3397 MagickExport MagickBooleanType NegateImage(Image *image,
3398   const MagickBooleanType grayscale,ExceptionInfo *exception)
3399 {
3400 #define NegateImageTag  "Negate/Image"
3401
3402   CacheView
3403     *image_view;
3404
3405   MagickBooleanType
3406     status;
3407
3408   MagickOffsetType
3409     progress;
3410
3411   register ssize_t
3412     i;
3413
3414   ssize_t
3415     y;
3416
3417   assert(image != (Image *) NULL);
3418   assert(image->signature == MagickCoreSignature);
3419   if (image->debug != MagickFalse)
3420     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3421   if (image->storage_class == PseudoClass)
3422     for (i=0; i < (ssize_t) image->colors; i++)
3423     {
3424       /*
3425         Negate colormap.
3426       */
3427       if( grayscale != MagickFalse )
3428         if ((image->colormap[i].red != image->colormap[i].green) ||
3429             (image->colormap[i].green != image->colormap[i].blue))
3430           continue;
3431       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3432         image->colormap[i].red=QuantumRange-image->colormap[i].red;
3433       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3434         image->colormap[i].green=QuantumRange-image->colormap[i].green;
3435       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3436         image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3437     }
3438   /*
3439     Negate image.
3440   */
3441   status=MagickTrue;
3442   progress=0;
3443   image_view=AcquireAuthenticCacheView(image,exception);
3444   if( grayscale != MagickFalse )
3445     {
3446       for (y=0; y < (ssize_t) image->rows; y++)
3447       {
3448         MagickBooleanType
3449           sync;
3450
3451         register Quantum
3452           *magick_restrict q;
3453
3454         register ssize_t
3455           x;
3456
3457         if (status == MagickFalse)
3458           continue;
3459         q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3460           exception);
3461         if (q == (Quantum *) NULL)
3462           {
3463             status=MagickFalse;
3464             continue;
3465           }
3466         for (x=0; x < (ssize_t) image->columns; x++)
3467         {
3468           register ssize_t
3469             j;
3470
3471           if ((GetPixelWriteMask(image,q) == 0) ||
3472               IsPixelGray(image,q) != MagickFalse)
3473             {
3474               q+=GetPixelChannels(image);
3475               continue;
3476             }
3477           for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3478           {
3479             PixelChannel channel=GetPixelChannelChannel(image,j);
3480             PixelTrait traits=GetPixelChannelTraits(image,channel);
3481             if ((traits & UpdatePixelTrait) == 0)
3482               continue;
3483             q[j]=QuantumRange-q[j];
3484           }
3485           q+=GetPixelChannels(image);
3486         }
3487         sync=SyncCacheViewAuthenticPixels(image_view,exception);
3488         if (sync == MagickFalse)
3489           status=MagickFalse;
3490         if (image->progress_monitor != (MagickProgressMonitor) NULL)
3491           {
3492             MagickBooleanType
3493               proceed;
3494
3495 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3496             #pragma omp critical (MagickCore_NegateImage)
3497 #endif
3498             proceed=SetImageProgress(image,NegateImageTag,progress++,
3499               image->rows);
3500             if (proceed == MagickFalse)
3501               status=MagickFalse;
3502           }
3503       }
3504       image_view=DestroyCacheView(image_view);
3505       return(MagickTrue);
3506     }
3507   /*
3508     Negate image.
3509   */
3510 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3511   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3512     magick_threads(image,image,image->rows,1)
3513 #endif
3514   for (y=0; y < (ssize_t) image->rows; y++)
3515   {
3516     register Quantum
3517       *magick_restrict q;
3518
3519     register ssize_t
3520       x;
3521
3522     if (status == MagickFalse)
3523       continue;
3524     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3525     if (q == (Quantum *) NULL)
3526       {
3527         status=MagickFalse;
3528         continue;
3529       }
3530     for (x=0; x < (ssize_t) image->columns; x++)
3531     {
3532       register ssize_t
3533         j;
3534
3535       if (GetPixelWriteMask(image,q) == 0)
3536         {
3537           q+=GetPixelChannels(image);
3538           continue;
3539         }
3540       for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3541       {
3542         PixelChannel channel=GetPixelChannelChannel(image,j);
3543         PixelTrait traits=GetPixelChannelTraits(image,channel);
3544         if ((traits & UpdatePixelTrait) == 0)
3545           continue;
3546         q[j]=QuantumRange-q[j];
3547       }
3548       q+=GetPixelChannels(image);
3549     }
3550     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3551       status=MagickFalse;
3552     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3553       {
3554         MagickBooleanType
3555           proceed;
3556
3557 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3558         #pragma omp critical (MagickCore_NegateImage)
3559 #endif
3560         proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3561         if (proceed == MagickFalse)
3562           status=MagickFalse;
3563       }
3564   }
3565   image_view=DestroyCacheView(image_view);
3566   return(status);
3567 }
3568 \f
3569 /*
3570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3571 %                                                                             %
3572 %                                                                             %
3573 %                                                                             %
3574 %     N o r m a l i z e I m a g e                                             %
3575 %                                                                             %
3576 %                                                                             %
3577 %                                                                             %
3578 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3579 %
3580 %  The NormalizeImage() method enhances the contrast of a color image by
3581 %  mapping the darkest 2 percent of all pixel to black and the brightest
3582 %  1 percent to white.
3583 %
3584 %  The format of the NormalizeImage method is:
3585 %
3586 %      MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3587 %
3588 %  A description of each parameter follows:
3589 %
3590 %    o image: the image.
3591 %
3592 %    o exception: return any errors or warnings in this structure.
3593 %
3594 */
3595 MagickExport MagickBooleanType NormalizeImage(Image *image,
3596   ExceptionInfo *exception)
3597 {
3598   double
3599     black_point,
3600     white_point;
3601
3602   black_point=(double) image->columns*image->rows*0.0015;
3603   white_point=(double) image->columns*image->rows*0.9995;
3604   return(ContrastStretchImage(image,black_point,white_point,exception));
3605 }
3606 \f
3607 /*
3608 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3609 %                                                                             %
3610 %                                                                             %
3611 %                                                                             %
3612 %     S i g m o i d a l C o n t r a s t I m a g e                             %
3613 %                                                                             %
3614 %                                                                             %
3615 %                                                                             %
3616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3617 %
3618 %  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3619 %  sigmoidal contrast algorithm.  Increase the contrast of the image using a
3620 %  sigmoidal transfer function without saturating highlights or shadows.
3621 %  Contrast indicates how much to increase the contrast (0 is none; 3 is
3622 %  typical; 20 is pushing it); mid-point indicates where midtones fall in the
3623 %  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
3624 %  sharpen to MagickTrue to increase the image contrast otherwise the contrast
3625 %  is reduced.
3626 %
3627 %  The format of the SigmoidalContrastImage method is:
3628 %
3629 %      MagickBooleanType SigmoidalContrastImage(Image *image,
3630 %        const MagickBooleanType sharpen,const char *levels,
3631 %        ExceptionInfo *exception)
3632 %
3633 %  A description of each parameter follows:
3634 %
3635 %    o image: the image.
3636 %
3637 %    o sharpen: Increase or decrease image contrast.
3638 %
3639 %    o contrast: strength of the contrast, the larger the number the more
3640 %      'threshold-like' it becomes.
3641 %
3642 %    o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3643 %
3644 %    o exception: return any errors or warnings in this structure.
3645 %
3646 */
3647
3648 /*
3649   ImageMagick 6 has a version of this function which uses LUTs.
3650 */
3651
3652 /*
3653   Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3654   constant" set to a.
3655
3656   The first version, based on the hyperbolic tangent tanh, when combined with
3657   the scaling step, is an exact arithmetic clone of the the sigmoid function
3658   based on the logistic curve. The equivalence is based on the identity
3659
3660     1/(1+exp(-t)) = (1+tanh(t/2))/2
3661
3662   (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3663   scaled sigmoidal derivation is invariant under affine transformations of
3664   the ordinate.
3665
3666   The tanh version is almost certainly more accurate and cheaper.  The 0.5
3667   factor in the argument is to clone the legacy ImageMagick behavior. The
3668   reason for making the define depend on atanh even though it only uses tanh
3669   has to do with the construction of the inverse of the scaled sigmoidal.
3670 */
3671 #if defined(MAGICKCORE_HAVE_ATANH)
3672 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3673 #else
3674 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3675 #endif
3676 /*
3677   Scaled sigmoidal function:
3678
3679     ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3680     ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3681
3682   See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3683   http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf.  The limit
3684   of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3685   zero. This is fixed below by exiting immediately when contrast is small,
3686   leaving the image (or colormap) unmodified. This appears to be safe because
3687   the series expansion of the logistic sigmoidal function around x=b is
3688
3689   1/2-a*(b-x)/4+...
3690
3691   so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3692 */
3693 #define ScaledSigmoidal(a,b,x) (                    \
3694   (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3695   (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3696 /*
3697   Inverse of ScaledSigmoidal, used for +sigmoidal-contrast.  Because b
3698   may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3699   sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3700   when creating a LUT from in gamut values, hence the branching.  In
3701   addition, HDRI may have out of gamut values.
3702   InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3703   It is only a right inverse. This is unavoidable.
3704 */
3705 static inline double InverseScaledSigmoidal(const double a,const double b,
3706   const double x)
3707 {
3708   const double sig0=Sigmoidal(a,b,0.0);
3709   const double sig1=Sigmoidal(a,b,1.0);
3710   const double argument=(sig1-sig0)*x+sig0;
3711   const double clamped=
3712     (
3713 #if defined(MAGICKCORE_HAVE_ATANH)
3714       argument < -1+MagickEpsilon
3715       ?
3716       -1+MagickEpsilon
3717       :
3718       ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3719     );
3720   return(b+(2.0/a)*atanh(clamped));
3721 #else
3722       argument < MagickEpsilon
3723       ?
3724       MagickEpsilon
3725       :
3726       ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3727     );
3728   return(b-log(1.0/clamped-1.0)/a);
3729 #endif
3730 }
3731
3732 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3733   const MagickBooleanType sharpen,const double contrast,const double midpoint,
3734   ExceptionInfo *exception)
3735 {
3736 #define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
3737 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3738   ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3739 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3740   InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3741
3742   CacheView
3743     *image_view;
3744
3745   MagickBooleanType
3746     status;
3747
3748   MagickOffsetType
3749     progress;
3750
3751   ssize_t
3752     y;
3753
3754   /*
3755     Convenience macros.
3756   */
3757   assert(image != (Image *) NULL);
3758   assert(image->signature == MagickCoreSignature);
3759   if (image->debug != MagickFalse)
3760     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3761   /*
3762     Side effect: may clamp values unless contrast<MagickEpsilon, in which
3763     case nothing is done.
3764   */
3765   if (contrast < MagickEpsilon)
3766     return(MagickTrue);
3767   /*
3768     Sigmoidal-contrast enhance colormap.
3769   */
3770   if (image->storage_class == PseudoClass)
3771     {
3772       register ssize_t
3773         i;
3774
3775       if( sharpen != MagickFalse )
3776         for (i=0; i < (ssize_t) image->colors; i++)
3777         {
3778           if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3779             image->colormap[i].red=(MagickRealType) ScaledSig(
3780               image->colormap[i].red);
3781           if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3782             image->colormap[i].green=(MagickRealType) ScaledSig(
3783               image->colormap[i].green);
3784           if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3785             image->colormap[i].blue=(MagickRealType) ScaledSig(
3786               image->colormap[i].blue);
3787           if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3788             image->colormap[i].alpha=(MagickRealType) ScaledSig(
3789               image->colormap[i].alpha);
3790         }
3791       else
3792         for (i=0; i < (ssize_t) image->colors; i++)
3793         {
3794           if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3795             image->colormap[i].red=(MagickRealType) InverseScaledSig(
3796               image->colormap[i].red);
3797           if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3798             image->colormap[i].green=(MagickRealType) InverseScaledSig(
3799               image->colormap[i].green);
3800           if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3801             image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3802               image->colormap[i].blue);
3803           if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3804             image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3805               image->colormap[i].alpha);
3806         }
3807     }
3808   /*
3809     Sigmoidal-contrast enhance image.
3810   */
3811   status=MagickTrue;
3812   progress=0;
3813   image_view=AcquireAuthenticCacheView(image,exception);
3814 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3815   #pragma omp parallel for schedule(static,4) shared(progress,status) \
3816     magick_threads(image,image,image->rows,1)
3817 #endif
3818   for (y=0; y < (ssize_t) image->rows; y++)
3819   {
3820     register Quantum
3821       *magick_restrict q;
3822
3823     register ssize_t
3824       x;
3825
3826     if (status == MagickFalse)
3827       continue;
3828     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3829     if (q == (Quantum *) NULL)
3830       {
3831         status=MagickFalse;
3832         continue;
3833       }
3834     for (x=0; x < (ssize_t) image->columns; x++)
3835     {
3836       register ssize_t
3837         i;
3838
3839       if (GetPixelWriteMask(image,q) == 0)
3840         {
3841           q+=GetPixelChannels(image);
3842           continue;
3843         }
3844       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3845       {
3846         PixelChannel channel=GetPixelChannelChannel(image,i);
3847         PixelTrait traits=GetPixelChannelTraits(image,channel);
3848         if ((traits & UpdatePixelTrait) == 0)
3849           continue;
3850         if( sharpen != MagickFalse )
3851           q[i]=ScaledSig(q[i]);
3852         else
3853           q[i]=InverseScaledSig(q[i]);
3854       }
3855       q+=GetPixelChannels(image);
3856     }
3857     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3858       status=MagickFalse;
3859     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3860       {
3861         MagickBooleanType
3862           proceed;
3863
3864 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3865         #pragma omp critical (MagickCore_SigmoidalContrastImage)
3866 #endif
3867         proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3868           image->rows);
3869         if (proceed == MagickFalse)
3870           status=MagickFalse;
3871       }
3872   }
3873   image_view=DestroyCacheView(image_view);
3874   return(status);
3875 }