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