]> 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-2011 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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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(dynamic,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 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2126   const double white_point,const double gamma,ExceptionInfo *exception)
2127 {
2128 #define LevelImageTag  "Level/Image"
2129 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2130   pow(scale*((double) (x)-black_point),1.0/gamma)))
2131
2132   CacheView
2133     *image_view;
2134
2135   MagickBooleanType
2136     status;
2137
2138   MagickOffsetType
2139     progress;
2140
2141   register double
2142     scale;
2143
2144   register ssize_t
2145     i;
2146
2147   ssize_t
2148     y;
2149
2150   /*
2151     Allocate and initialize levels map.
2152   */
2153   assert(image != (Image *) NULL);
2154   assert(image->signature == MagickSignature);
2155   if (image->debug != MagickFalse)
2156     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2157   scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2158   if (image->storage_class == PseudoClass)
2159 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2160   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2161 #endif
2162     for (i=0; i < (ssize_t) image->colors; i++)
2163     {
2164       /*
2165         Level colormap.
2166       */
2167       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2168         image->colormap[i].red=(double) LevelQuantum(
2169           image->colormap[i].red);
2170       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2171         image->colormap[i].green=(double) LevelQuantum(
2172           image->colormap[i].green);
2173       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2174         image->colormap[i].blue=(double) LevelQuantum(
2175           image->colormap[i].blue);
2176       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2177         image->colormap[i].alpha=(double) LevelQuantum(
2178           image->colormap[i].alpha);
2179       }
2180   /*
2181     Level image.
2182   */
2183   status=MagickTrue;
2184   progress=0;
2185   image_view=AcquireCacheView(image);
2186 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2187   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2188 #endif
2189   for (y=0; y < (ssize_t) image->rows; y++)
2190   {
2191     register Quantum
2192       *restrict q;
2193
2194     register ssize_t
2195       x;
2196
2197     if (status == MagickFalse)
2198       continue;
2199     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2200     if (q == (Quantum *) NULL)
2201       {
2202         status=MagickFalse;
2203         continue;
2204       }
2205     for (x=0; x < (ssize_t) image->columns; x++)
2206     {
2207       register ssize_t
2208         i;
2209
2210       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2211       {
2212         PixelChannel
2213           channel;
2214
2215         PixelTrait
2216           traits;
2217
2218         channel=GetPixelChannelMapChannel(image,i);
2219         traits=GetPixelChannelMapTraits(image,channel);
2220         if ((traits == UndefinedPixelTrait) ||
2221             ((traits & UpdatePixelTrait) == 0))
2222           continue;
2223         q[i]=LevelQuantum(q[i]);
2224       }
2225       q+=GetPixelChannels(image);
2226     }
2227     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2228       status=MagickFalse;
2229     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2230       {
2231         MagickBooleanType
2232           proceed;
2233
2234 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2235   #pragma omp critical (MagickCore_LevelImage)
2236 #endif
2237         proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2238         if (proceed == MagickFalse)
2239           status=MagickFalse;
2240       }
2241   }
2242   image_view=DestroyCacheView(image_view);
2243   return(status);
2244 }
2245 \f
2246 /*
2247 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2248 %                                                                             %
2249 %                                                                             %
2250 %                                                                             %
2251 %     L e v e l i z e I m a g e                                               %
2252 %                                                                             %
2253 %                                                                             %
2254 %                                                                             %
2255 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2256 %
2257 %  LevelizeImage() applies the reversed LevelImage() operation to just
2258 %  the specific channels specified.  It compresses the full range of color
2259 %  values, so that they lie between the given black and white points. Gamma is
2260 %  applied before the values are mapped.
2261 %
2262 %  LevelizeImage() can be called with by using a +level command line
2263 %  API option, or using a '!' on a -level or LevelImage() geometry string.
2264 %
2265 %  It can be used for example de-contrast a greyscale image to the exact
2266 %  levels specified.  Or by using specific levels for each channel of an image
2267 %  you can convert a gray-scale image to any linear color gradient, according
2268 %  to those levels.
2269 %
2270 %  The format of the LevelizeImage method is:
2271 %
2272 %      MagickBooleanType LevelizeImage(Image *image,const double black_point,
2273 %        const double white_point,const double gamma,ExceptionInfo *exception)
2274 %
2275 %  A description of each parameter follows:
2276 %
2277 %    o image: the image.
2278 %
2279 %    o black_point: The level to map zero (black) to.
2280 %
2281 %    o white_point: The level to map QuantumRange (white) to.
2282 %
2283 %    o gamma: adjust gamma by this factor before mapping values.
2284 %
2285 %    o exception: return any errors or warnings in this structure.
2286 %
2287 */
2288 MagickExport MagickBooleanType LevelizeImage(Image *image,
2289   const double black_point,const double white_point,const double gamma,
2290   ExceptionInfo *exception)
2291 {
2292 #define LevelizeImageTag  "Levelize/Image"
2293 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2294   pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2295   black_point))
2296
2297   CacheView
2298     *image_view;
2299
2300   MagickBooleanType
2301     status;
2302
2303   MagickOffsetType
2304     progress;
2305
2306   register ssize_t
2307     i;
2308
2309   ssize_t
2310     y;
2311
2312   /*
2313     Allocate and initialize levels map.
2314   */
2315   assert(image != (Image *) NULL);
2316   assert(image->signature == MagickSignature);
2317   if (image->debug != MagickFalse)
2318     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2319   if (image->storage_class == PseudoClass)
2320 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2321   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2322 #endif
2323     for (i=0; i < (ssize_t) image->colors; i++)
2324     {
2325       /*
2326         Level colormap.
2327       */
2328       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2329         image->colormap[i].red=(double) LevelizeValue(
2330           image->colormap[i].red);
2331       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2332         image->colormap[i].green=(double) LevelizeValue(
2333           image->colormap[i].green);
2334       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2335         image->colormap[i].blue=(double) LevelizeValue(
2336           image->colormap[i].blue);
2337       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2338         image->colormap[i].alpha=(double) LevelizeValue(
2339           image->colormap[i].alpha);
2340     }
2341   /*
2342     Level image.
2343   */
2344   status=MagickTrue;
2345   progress=0;
2346   image_view=AcquireCacheView(image);
2347 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2348   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2349 #endif
2350   for (y=0; y < (ssize_t) image->rows; y++)
2351   {
2352     register Quantum
2353       *restrict q;
2354
2355     register ssize_t
2356       x;
2357
2358     if (status == MagickFalse)
2359       continue;
2360     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2361     if (q == (Quantum *) NULL)
2362       {
2363         status=MagickFalse;
2364         continue;
2365       }
2366     for (x=0; x < (ssize_t) image->columns; x++)
2367     {
2368       register ssize_t
2369         i;
2370
2371       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2372       {
2373         PixelChannel
2374           channel;
2375
2376         PixelTrait
2377           traits;
2378
2379         channel=GetPixelChannelMapChannel(image,i);
2380         traits=GetPixelChannelMapTraits(image,channel);
2381         if ((traits & UpdatePixelTrait) != 0)
2382           q[i]=LevelizeValue(q[i]);
2383       }
2384       q+=GetPixelChannels(image);
2385     }
2386     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2387       status=MagickFalse;
2388     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2389       {
2390         MagickBooleanType
2391           proceed;
2392
2393 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2394   #pragma omp critical (MagickCore_LevelizeImage)
2395 #endif
2396         proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2397         if (proceed == MagickFalse)
2398           status=MagickFalse;
2399       }
2400   }
2401   image_view=DestroyCacheView(image_view);
2402   return(status);
2403 }
2404 \f
2405 /*
2406 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2407 %                                                                             %
2408 %                                                                             %
2409 %                                                                             %
2410 %     L e v e l I m a g e C o l o r s                                         %
2411 %                                                                             %
2412 %                                                                             %
2413 %                                                                             %
2414 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2415 %
2416 %  LevelImageColors() maps the given color to "black" and "white" values,
2417 %  linearly spreading out the colors, and level values on a channel by channel
2418 %  bases, as per LevelImage().  The given colors allows you to specify
2419 %  different level ranges for each of the color channels separately.
2420 %
2421 %  If the boolean 'invert' is set true the image values will modifyed in the
2422 %  reverse direction. That is any existing "black" and "white" colors in the
2423 %  image will become the color values given, with all other values compressed
2424 %  appropriatally.  This effectivally maps a greyscale gradient into the given
2425 %  color gradient.
2426 %
2427 %  The format of the LevelImageColors method is:
2428 %
2429 %    MagickBooleanType LevelImageColors(Image *image,
2430 %      const PixelInfo *black_color,const PixelInfo *white_color,
2431 %      const MagickBooleanType invert,ExceptionInfo *exception)
2432 %
2433 %  A description of each parameter follows:
2434 %
2435 %    o image: the image.
2436 %
2437 %    o black_color: The color to map black to/from
2438 %
2439 %    o white_point: The color to map white to/from
2440 %
2441 %    o invert: if true map the colors (levelize), rather than from (level)
2442 %
2443 %    o exception: return any errors or warnings in this structure.
2444 %
2445 */
2446 MagickExport MagickBooleanType LevelImageColors(Image *image,
2447   const PixelInfo *black_color,const PixelInfo *white_color,
2448   const MagickBooleanType invert,ExceptionInfo *exception)
2449 {
2450   ChannelType
2451     channel_mask;
2452
2453   MagickStatusType
2454     status;
2455
2456   /*
2457     Allocate and initialize levels map.
2458   */
2459   assert(image != (Image *) NULL);
2460   assert(image->signature == MagickSignature);
2461   if (image->debug != MagickFalse)
2462     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2463   status=MagickFalse;
2464   if (invert == MagickFalse)
2465     {
2466       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2467         {
2468           channel_mask=SetPixelChannelMask(image,RedChannel);
2469           status|=LevelImage(image,black_color->red,white_color->red,1.0,
2470             exception);
2471           (void) SetPixelChannelMask(image,channel_mask);
2472         }
2473       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2474         {
2475           channel_mask=SetPixelChannelMask(image,GreenChannel);
2476           status|=LevelImage(image,black_color->green,white_color->green,1.0,
2477             exception);
2478           (void) SetPixelChannelMask(image,channel_mask);
2479         }
2480       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2481         {
2482           channel_mask=SetPixelChannelMask(image,BlueChannel);
2483           status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2484             exception);
2485           (void) SetPixelChannelMask(image,channel_mask);
2486         }
2487       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2488           (image->colorspace == CMYKColorspace))
2489         {
2490           channel_mask=SetPixelChannelMask(image,BlackChannel);
2491           status|=LevelImage(image,black_color->black,white_color->black,1.0,
2492             exception);
2493           (void) SetPixelChannelMask(image,channel_mask);
2494         }
2495       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2496           (image->matte == MagickTrue))
2497         {
2498           channel_mask=SetPixelChannelMask(image,AlphaChannel);
2499           status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2500             exception);
2501           (void) SetPixelChannelMask(image,channel_mask);
2502         }
2503     }
2504   else
2505     {
2506       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2507         {
2508           channel_mask=SetPixelChannelMask(image,RedChannel);
2509           status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2510             exception);
2511           (void) SetPixelChannelMask(image,channel_mask);
2512         }
2513       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2514         {
2515           channel_mask=SetPixelChannelMask(image,GreenChannel);
2516           status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2517             exception);
2518           (void) SetPixelChannelMask(image,channel_mask);
2519         }
2520       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2521         {
2522           channel_mask=SetPixelChannelMask(image,BlueChannel);
2523           status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2524             exception);
2525           (void) SetPixelChannelMask(image,channel_mask);
2526         }
2527       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2528           (image->colorspace == CMYKColorspace))
2529         {
2530           channel_mask=SetPixelChannelMask(image,BlackChannel);
2531           status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2532             exception);
2533           (void) SetPixelChannelMask(image,channel_mask);
2534         }
2535       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2536           (image->matte == MagickTrue))
2537         {
2538           channel_mask=SetPixelChannelMask(image,AlphaChannel);
2539           status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2540             exception);
2541           (void) SetPixelChannelMask(image,channel_mask);
2542         }
2543     }
2544   return(status == 0 ? MagickFalse : MagickTrue);
2545 }
2546 \f
2547 /*
2548 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2549 %                                                                             %
2550 %                                                                             %
2551 %                                                                             %
2552 %     L i n e a r S t r e t c h I m a g e                                     %
2553 %                                                                             %
2554 %                                                                             %
2555 %                                                                             %
2556 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2557 %
2558 %  LinearStretchImage() discards any pixels below the black point and above
2559 %  the white point and levels the remaining pixels.
2560 %
2561 %  The format of the LinearStretchImage method is:
2562 %
2563 %      MagickBooleanType LinearStretchImage(Image *image,
2564 %        const double black_point,const double white_point,
2565 %        ExceptionInfo *exception)
2566 %
2567 %  A description of each parameter follows:
2568 %
2569 %    o image: the image.
2570 %
2571 %    o black_point: the black point.
2572 %
2573 %    o white_point: the white point.
2574 %
2575 %    o exception: return any errors or warnings in this structure.
2576 %
2577 */
2578 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2579   const double black_point,const double white_point,ExceptionInfo *exception)
2580 {
2581 #define LinearStretchImageTag  "LinearStretch/Image"
2582
2583   CacheView
2584     *image_view;
2585
2586   MagickBooleanType
2587     status;
2588
2589   MagickRealType
2590     *histogram,
2591     intensity;
2592
2593   ssize_t
2594     black,
2595     white,
2596     y;
2597
2598   /*
2599     Allocate histogram and linear map.
2600   */
2601   assert(image != (Image *) NULL);
2602   assert(image->signature == MagickSignature);
2603   histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2604     sizeof(*histogram));
2605   if (histogram == (MagickRealType *) NULL)
2606     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2607       image->filename);
2608   /*
2609     Form histogram.
2610   */
2611   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2612   image_view=AcquireCacheView(image);
2613   for (y=0; y < (ssize_t) image->rows; y++)
2614   {
2615     register const Quantum
2616       *restrict p;
2617
2618     register ssize_t
2619       x;
2620
2621     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2622     if (p == (const Quantum *) NULL)
2623       break;
2624     for (x=0; x < (ssize_t) image->columns; x++)
2625     {
2626       histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
2627       p+=GetPixelChannels(image);
2628     }
2629   }
2630   image_view=DestroyCacheView(image_view);
2631   /*
2632     Find the histogram boundaries by locating the black and white point levels.
2633   */
2634   intensity=0.0;
2635   for (black=0; black < (ssize_t) MaxMap; black++)
2636   {
2637     intensity+=histogram[black];
2638     if (intensity >= black_point)
2639       break;
2640   }
2641   intensity=0.0;
2642   for (white=(ssize_t) MaxMap; white != 0; white--)
2643   {
2644     intensity+=histogram[white];
2645     if (intensity >= white_point)
2646       break;
2647   }
2648   histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2649   status=LevelImage(image,(double) black,(double) white,1.0,exception);
2650   return(status);
2651 }
2652 \f
2653 /*
2654 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2655 %                                                                             %
2656 %                                                                             %
2657 %                                                                             %
2658 %     M o d u l a t e I m a g e                                               %
2659 %                                                                             %
2660 %                                                                             %
2661 %                                                                             %
2662 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2663 %
2664 %  ModulateImage() lets you control the brightness, saturation, and hue
2665 %  of an image.  Modulate represents the brightness, saturation, and hue
2666 %  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
2667 %  modulation is lightness, saturation, and hue.  And if the colorspace is
2668 %  HWB, use blackness, whiteness, and hue.
2669 %
2670 %  The format of the ModulateImage method is:
2671 %
2672 %      MagickBooleanType ModulateImage(Image *image,const char *modulate,
2673 %        ExceptionInfo *exception)
2674 %
2675 %  A description of each parameter follows:
2676 %
2677 %    o image: the image.
2678 %
2679 %    o modulate: Define the percent change in brightness, saturation, and hue.
2680 %
2681 %    o exception: return any errors or warnings in this structure.
2682 %
2683 */
2684
2685 static void ModulateHSB(const double percent_hue,
2686   const double percent_saturation,const double percent_brightness,double *red,
2687   double *green,double *blue)
2688 {
2689   double
2690     brightness,
2691     hue,
2692     saturation;
2693
2694   /*
2695     Increase or decrease color brightness, saturation, or hue.
2696   */
2697   assert(red != (double *) NULL);
2698   assert(green != (double *) NULL);
2699   assert(blue != (double *) NULL);
2700   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2701   hue+=0.5*(0.01*percent_hue-1.0);
2702   while (hue < 0.0)
2703     hue+=1.0;
2704   while (hue > 1.0)
2705     hue-=1.0;
2706   saturation*=0.01*percent_saturation;
2707   brightness*=0.01*percent_brightness;
2708   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2709 }
2710
2711 static void ModulateHSL(const double percent_hue,
2712   const double percent_saturation,const double percent_lightness,double *red,
2713   double *green,double *blue)
2714 {
2715   double
2716     hue,
2717     lightness,
2718     saturation;
2719
2720   /*
2721     Increase or decrease color lightness, saturation, or hue.
2722   */
2723   assert(red != (double *) NULL);
2724   assert(green != (double *) NULL);
2725   assert(blue != (double *) NULL);
2726   ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2727   hue+=0.5*(0.01*percent_hue-1.0);
2728   while (hue < 0.0)
2729     hue+=1.0;
2730   while (hue > 1.0)
2731     hue-=1.0;
2732   saturation*=0.01*percent_saturation;
2733   lightness*=0.01*percent_lightness;
2734   ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2735 }
2736
2737 static void ModulateHWB(const double percent_hue,const double percent_whiteness,  const double percent_blackness,double *red,double *green,double *blue)
2738 {
2739   double
2740     blackness,
2741     hue,
2742     whiteness;
2743
2744   /*
2745     Increase or decrease color blackness, whiteness, or hue.
2746   */
2747   assert(red != (double *) NULL);
2748   assert(green != (double *) NULL);
2749   assert(blue != (double *) NULL);
2750   ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2751   hue+=0.5*(0.01*percent_hue-1.0);
2752   while (hue < 0.0)
2753     hue+=1.0;
2754   while (hue > 1.0)
2755     hue-=1.0;
2756   blackness*=0.01*percent_blackness;
2757   whiteness*=0.01*percent_whiteness;
2758   ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2759 }
2760
2761 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2762   ExceptionInfo *exception)
2763 {
2764 #define ModulateImageTag  "Modulate/Image"
2765
2766   CacheView
2767     *image_view;
2768
2769   ColorspaceType
2770     colorspace;
2771
2772   const char
2773     *artifact;
2774
2775   double
2776     percent_brightness,
2777     percent_hue,
2778     percent_saturation;
2779
2780   GeometryInfo
2781     geometry_info;
2782
2783   MagickBooleanType
2784     status;
2785
2786   MagickOffsetType
2787     progress;
2788
2789   MagickStatusType
2790     flags;
2791
2792   register ssize_t
2793     i;
2794
2795   ssize_t
2796     y;
2797
2798   /*
2799     Initialize modulate table.
2800   */
2801   assert(image != (Image *) NULL);
2802   assert(image->signature == MagickSignature);
2803   if (image->debug != MagickFalse)
2804     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2805   if (modulate == (char *) NULL)
2806     return(MagickFalse);
2807   flags=ParseGeometry(modulate,&geometry_info);
2808   percent_brightness=geometry_info.rho;
2809   percent_saturation=geometry_info.sigma;
2810   if ((flags & SigmaValue) == 0)
2811     percent_saturation=100.0;
2812   percent_hue=geometry_info.xi;
2813   if ((flags & XiValue) == 0)
2814     percent_hue=100.0;
2815   colorspace=UndefinedColorspace;
2816   artifact=GetImageArtifact(image,"modulate:colorspace");
2817   if (artifact != (const char *) NULL)
2818     colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
2819       MagickFalse,artifact);
2820   if (image->storage_class == PseudoClass)
2821     {
2822       /*
2823         Modulate colormap.
2824       */
2825 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2826   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2827 #endif
2828       for (i=0; i < (ssize_t) image->colors; i++)
2829         switch (colorspace)
2830         {
2831           case HSBColorspace:
2832           {
2833             ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2834               &image->colormap[i].red,&image->colormap[i].green,
2835               &image->colormap[i].blue);
2836             break;
2837           }
2838           case HSLColorspace:
2839           default:
2840           {
2841             ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2842               &image->colormap[i].red,&image->colormap[i].green,
2843               &image->colormap[i].blue);
2844             break;
2845           }
2846           case HWBColorspace:
2847           {
2848             ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2849               &image->colormap[i].red,&image->colormap[i].green,
2850               &image->colormap[i].blue);
2851             break;
2852           }
2853         }
2854     }
2855   /*
2856     Modulate image.
2857   */
2858   status=MagickTrue;
2859   progress=0;
2860   image_view=AcquireCacheView(image);
2861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2862   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2863 #endif
2864   for (y=0; y < (ssize_t) image->rows; y++)
2865   {
2866     double
2867       blue,
2868       green,
2869       red;
2870
2871     register Quantum
2872       *restrict q;
2873
2874     register ssize_t
2875       x;
2876
2877     if (status == MagickFalse)
2878       continue;
2879     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2880     if (q == (Quantum *) NULL)
2881       {
2882         status=MagickFalse;
2883         continue;
2884       }
2885     for (x=0; x < (ssize_t) image->columns; x++)
2886     {
2887       red=(double) GetPixelRed(image,q);
2888       green=(double) GetPixelGreen(image,q);
2889       blue=(double) GetPixelBlue(image,q);
2890       switch (colorspace)
2891       {
2892         case HSBColorspace:
2893         {
2894           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2895             &red,&green,&blue);
2896           break;
2897         }
2898         case HSLColorspace:
2899         default:
2900         {
2901           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2902             &red,&green,&blue);
2903           break;
2904         }
2905         case HWBColorspace:
2906         {
2907           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2908             &red,&green,&blue);
2909           break;
2910         }
2911       }
2912       SetPixelRed(image,ClampToQuantum(red),q);
2913       SetPixelGreen(image,ClampToQuantum(green),q);
2914       SetPixelBlue(image,ClampToQuantum(blue),q);
2915       q+=GetPixelChannels(image);
2916     }
2917     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2918       status=MagickFalse;
2919     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2920       {
2921         MagickBooleanType
2922           proceed;
2923
2924 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2925   #pragma omp critical (MagickCore_ModulateImage)
2926 #endif
2927         proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2928         if (proceed == MagickFalse)
2929           status=MagickFalse;
2930       }
2931   }
2932   image_view=DestroyCacheView(image_view);
2933   return(status);
2934 }
2935 \f
2936 /*
2937 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2938 %                                                                             %
2939 %                                                                             %
2940 %                                                                             %
2941 %     N e g a t e I m a g e                                                   %
2942 %                                                                             %
2943 %                                                                             %
2944 %                                                                             %
2945 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2946 %
2947 %  NegateImage() negates the colors in the reference image.  The grayscale
2948 %  option means that only grayscale values within the image are negated.
2949 %
2950 %  The format of the NegateImage method is:
2951 %
2952 %      MagickBooleanType NegateImage(Image *image,
2953 %        const MagickBooleanType grayscale,ExceptionInfo *exception)
2954 %
2955 %  A description of each parameter follows:
2956 %
2957 %    o image: the image.
2958 %
2959 %    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2960 %
2961 %    o exception: return any errors or warnings in this structure.
2962 %
2963 */
2964 MagickExport MagickBooleanType NegateImage(Image *image,
2965   const MagickBooleanType grayscale,ExceptionInfo *exception)
2966 {
2967 #define NegateImageTag  "Negate/Image"
2968
2969   CacheView
2970     *image_view;
2971
2972   MagickBooleanType
2973     status;
2974
2975   MagickOffsetType
2976     progress;
2977
2978   register ssize_t
2979     i;
2980
2981   ssize_t
2982     y;
2983
2984   assert(image != (Image *) NULL);
2985   assert(image->signature == MagickSignature);
2986   if (image->debug != MagickFalse)
2987     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2988   if (image->storage_class == PseudoClass)
2989     {
2990       /*
2991         Negate colormap.
2992       */
2993 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2994   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2995 #endif
2996       for (i=0; i < (ssize_t) image->colors; i++)
2997       {
2998         if (grayscale != MagickFalse)
2999           if ((image->colormap[i].red != image->colormap[i].green) ||
3000               (image->colormap[i].green != image->colormap[i].blue))
3001             continue;
3002         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3003           image->colormap[i].red=(Quantum) QuantumRange-
3004             image->colormap[i].red;
3005         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3006           image->colormap[i].green=(Quantum) QuantumRange-
3007             image->colormap[i].green;
3008         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3009           image->colormap[i].blue=(Quantum) QuantumRange-
3010             image->colormap[i].blue;
3011       }
3012     }
3013   /*
3014     Negate image.
3015   */
3016   status=MagickTrue;
3017   progress=0;
3018   image_view=AcquireCacheView(image);
3019   if (grayscale != MagickFalse)
3020     {
3021 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3022   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3023 #endif
3024       for (y=0; y < (ssize_t) image->rows; y++)
3025       {
3026         MagickBooleanType
3027           sync;
3028
3029         register Quantum
3030           *restrict q;
3031
3032         register ssize_t
3033           x;
3034
3035         if (status == MagickFalse)
3036           continue;
3037         q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3038           exception);
3039         if (q == (Quantum *) NULL)
3040           {
3041             status=MagickFalse;
3042             continue;
3043           }
3044         for (x=0; x < (ssize_t) image->columns; x++)
3045         {
3046           register ssize_t
3047             i;
3048
3049           if (IsPixelGray(image,q) != MagickFalse)
3050             {
3051               q+=GetPixelChannels(image);
3052               continue;
3053             }
3054           for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3055           {
3056             PixelChannel
3057               channel;
3058
3059             PixelTrait
3060               traits;
3061
3062             channel=GetPixelChannelMapChannel(image,i);
3063             traits=GetPixelChannelMapTraits(image,channel);
3064             if ((traits & UpdatePixelTrait) != 0)
3065               q[i]=QuantumRange-q[i];
3066           }
3067           q+=GetPixelChannels(image);
3068         }
3069         sync=SyncCacheViewAuthenticPixels(image_view,exception);
3070         if (sync == MagickFalse)
3071           status=MagickFalse;
3072         if (image->progress_monitor != (MagickProgressMonitor) NULL)
3073           {
3074             MagickBooleanType
3075               proceed;
3076
3077 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3078   #pragma omp critical (MagickCore_NegateImage)
3079 #endif
3080             proceed=SetImageProgress(image,NegateImageTag,progress++,
3081               image->rows);
3082             if (proceed == MagickFalse)
3083               status=MagickFalse;
3084           }
3085       }
3086       image_view=DestroyCacheView(image_view);
3087       return(MagickTrue);
3088     }
3089   /*
3090     Negate image.
3091   */
3092 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3093   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3094 #endif
3095   for (y=0; y < (ssize_t) image->rows; y++)
3096   {
3097     register Quantum
3098       *restrict q;
3099
3100     register ssize_t
3101       x;
3102
3103     if (status == MagickFalse)
3104       continue;
3105     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3106     if (q == (Quantum *) NULL)
3107       {
3108         status=MagickFalse;
3109         continue;
3110       }
3111     for (x=0; x < (ssize_t) image->columns; x++)
3112     {
3113       register ssize_t
3114         i;
3115
3116       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3117       {
3118         PixelChannel
3119           channel;
3120
3121         PixelTrait
3122           traits;
3123
3124         channel=GetPixelChannelMapChannel(image,i);
3125         traits=GetPixelChannelMapTraits(image,channel);
3126         if ((traits & UpdatePixelTrait) != 0)
3127           q[i]=QuantumRange-q[i];
3128       }
3129       q+=GetPixelChannels(image);
3130     }
3131     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3132       status=MagickFalse;
3133     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3134       {
3135         MagickBooleanType
3136           proceed;
3137
3138 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3139   #pragma omp critical (MagickCore_NegateImage)
3140 #endif
3141         proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3142         if (proceed == MagickFalse)
3143           status=MagickFalse;
3144       }
3145   }
3146   image_view=DestroyCacheView(image_view);
3147   return(status);
3148 }
3149 \f
3150 /*
3151 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3152 %                                                                             %
3153 %                                                                             %
3154 %                                                                             %
3155 %     N o r m a l i z e I m a g e                                             %
3156 %                                                                             %
3157 %                                                                             %
3158 %                                                                             %
3159 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3160 %
3161 %  NormalizeImage() enhances the contrast of a color image by mapping the
3162 %  darkest 2 percent of all pixel to black and the brightest 1 percent to white.
3163 %
3164 %  The format of the NormalizeImage method is:
3165 %
3166 %      MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3167 %
3168 %  A description of each parameter follows:
3169 %
3170 %    o image: the image.
3171 %
3172 %    o exception: return any errors or warnings in this structure.
3173 %
3174 */
3175 MagickExport MagickBooleanType NormalizeImage(Image *image,
3176   ExceptionInfo *exception)
3177 {
3178   double
3179     black_point,
3180     white_point;
3181
3182   black_point=(double) image->columns*image->rows*0.0015;
3183   white_point=(double) image->columns*image->rows*0.9995;
3184   return(ContrastStretchImage(image,black_point,white_point,exception));
3185 }
3186 \f
3187 /*
3188 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3189 %                                                                             %
3190 %                                                                             %
3191 %                                                                             %
3192 %     S i g m o i d a l C o n t r a s t I m a g e                             %
3193 %                                                                             %
3194 %                                                                             %
3195 %                                                                             %
3196 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3197 %
3198 %  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3199 %  sigmoidal contrast algorithm.  Increase the contrast of the image using a
3200 %  sigmoidal transfer function without saturating highlights or shadows.
3201 %  Contrast indicates how much to increase the contrast (0 is none; 3 is
3202 %  typical; 20 is pushing it); mid-point indicates where midtones fall in the
3203 %  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
3204 %  sharpen to MagickTrue to increase the image contrast otherwise the contrast
3205 %  is reduced.
3206 %
3207 %  The format of the SigmoidalContrastImage method is:
3208 %
3209 %      MagickBooleanType SigmoidalContrastImage(Image *image,
3210 %        const MagickBooleanType sharpen,const char *levels,
3211 %        ExceptionInfo *exception)
3212 %
3213 %  A description of each parameter follows:
3214 %
3215 %    o image: the image.
3216 %
3217 %    o sharpen: Increase or decrease image contrast.
3218 %
3219 %    o alpha: strength of the contrast, the larger the number the more
3220 %      'threshold-like' it becomes.
3221 %
3222 %    o beta: midpoint of the function as a color value 0 to QuantumRange.
3223 %
3224 %    o exception: return any errors or warnings in this structure.
3225 %
3226 */
3227 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3228   const MagickBooleanType sharpen,const double contrast,const double midpoint,
3229   ExceptionInfo *exception)
3230 {
3231 #define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
3232
3233   CacheView
3234     *image_view;
3235
3236   MagickBooleanType
3237     status;
3238
3239   MagickOffsetType
3240     progress;
3241
3242   MagickRealType
3243     *sigmoidal_map;
3244
3245   register ssize_t
3246     i;
3247
3248   ssize_t
3249     y;
3250
3251   /*
3252     Allocate and initialize sigmoidal maps.
3253   */
3254   assert(image != (Image *) NULL);
3255   assert(image->signature == MagickSignature);
3256   if (image->debug != MagickFalse)
3257     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3258   sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3259     sizeof(*sigmoidal_map));
3260   if (sigmoidal_map == (MagickRealType *) NULL)
3261     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3262       image->filename);
3263   (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3264 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3265   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3266 #endif
3267   for (i=0; i <= (ssize_t) MaxMap; i++)
3268   {
3269     if (sharpen != MagickFalse)
3270       {
3271         sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3272           (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3273           (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3274           QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3275           QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3276           QuantumRange)))))+0.5));
3277         continue;
3278       }
3279     sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3280       (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3281       QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3282       midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3283       QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3284       contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3285       QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3286       contrast))))))/contrast)));
3287   }
3288   if (image->storage_class == PseudoClass)
3289     {
3290       /*
3291         Sigmoidal-contrast enhance colormap.
3292       */
3293 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3294   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3295 #endif
3296       for (i=0; i < (ssize_t) image->colors; i++)
3297       {
3298         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3299           image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3300             ClampToQuantum(image->colormap[i].red))];
3301         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3302           image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3303             ClampToQuantum(image->colormap[i].green))];
3304         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3305           image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3306             ClampToQuantum(image->colormap[i].blue))];
3307         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3308           image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3309             ClampToQuantum(image->colormap[i].alpha))];
3310       }
3311     }
3312   /*
3313     Sigmoidal-contrast enhance image.
3314   */
3315   status=MagickTrue;
3316   progress=0;
3317   image_view=AcquireCacheView(image);
3318 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3319   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3320 #endif
3321   for (y=0; y < (ssize_t) image->rows; y++)
3322   {
3323     register Quantum
3324       *restrict q;
3325
3326     register ssize_t
3327       x;
3328
3329     if (status == MagickFalse)
3330       continue;
3331     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3332     if (q == (Quantum *) NULL)
3333       {
3334         status=MagickFalse;
3335         continue;
3336       }
3337     for (x=0; x < (ssize_t) image->columns; x++)
3338     {
3339       register ssize_t
3340         i;
3341
3342       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3343       {
3344         PixelChannel
3345           channel;
3346
3347         PixelTrait
3348           traits;
3349
3350         channel=GetPixelChannelMapChannel(image,i);
3351         traits=GetPixelChannelMapTraits(image,channel);
3352         if ((traits & UpdatePixelTrait) != 0)
3353           q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3354       }
3355       q+=GetPixelChannels(image);
3356     }
3357     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3358       status=MagickFalse;
3359     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3360       {
3361         MagickBooleanType
3362           proceed;
3363
3364 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3365   #pragma omp critical (MagickCore_SigmoidalContrastImage)
3366 #endif
3367         proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3368           image->rows);
3369         if (proceed == MagickFalse)
3370           status=MagickFalse;
3371       }
3372   }
3373   image_view=DestroyCacheView(image_view);
3374   sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3375   return(status);
3376 }