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