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