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