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