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