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