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