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