]> 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 %        ExceptionInfo *exception)
1911 %
1912 %  A description of each parameter follows:
1913 %
1914 %    o image: the image.
1915 %
1916 %    o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1917 %
1918 %    o gamma: the image gamma.
1919 %
1920 */
1921 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1922   ExceptionInfo *exception)
1923 {
1924 #define GammaCorrectImageTag  "GammaCorrect/Image"
1925
1926   CacheView
1927     *image_view;
1928
1929   MagickBooleanType
1930     status;
1931
1932   MagickOffsetType
1933     progress;
1934
1935   Quantum
1936     *gamma_map;
1937
1938   register ssize_t
1939     i;
1940
1941   ssize_t
1942     y;
1943
1944   /*
1945     Allocate and initialize gamma maps.
1946   */
1947   assert(image != (Image *) NULL);
1948   assert(image->signature == MagickSignature);
1949   if (image->debug != MagickFalse)
1950     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1951   if (gamma == 1.0)
1952     return(MagickTrue);
1953   gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1954   if (gamma_map == (Quantum *) NULL)
1955     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1956       image->filename);
1957   (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1958   if (gamma != 0.0)
1959 #if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1960   #pragma omp parallel for
1961 #endif
1962     for (i=0; i <= (ssize_t) MaxMap; i++)
1963       gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
1964         MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
1965   if (image->storage_class == PseudoClass)
1966     {
1967       /*
1968         Gamma-correct colormap.
1969       */
1970 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1971   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1972 #endif
1973       for (i=0; i < (ssize_t) image->colors; i++)
1974       {
1975         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1976           image->colormap[i].red=gamma_map[
1977             ScaleQuantumToMap(image->colormap[i].red)];
1978         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1979           image->colormap[i].green=gamma_map[
1980             ScaleQuantumToMap(image->colormap[i].green)];
1981         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1982           image->colormap[i].blue=gamma_map[
1983             ScaleQuantumToMap(image->colormap[i].blue)];
1984         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1985           image->colormap[i].alpha=gamma_map[
1986             ScaleQuantumToMap(image->colormap[i].alpha)];
1987       }
1988     }
1989   /*
1990     Gamma-correct image.
1991   */
1992   status=MagickTrue;
1993   progress=0;
1994   image_view=AcquireCacheView(image);
1995 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1996   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1997 #endif
1998   for (y=0; y < (ssize_t) image->rows; y++)
1999   {
2000     register Quantum
2001       *restrict q;
2002
2003     register ssize_t
2004       x;
2005
2006     if (status == MagickFalse)
2007       continue;
2008     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2009     if (q == (const Quantum *) NULL)
2010       {
2011         status=MagickFalse;
2012         continue;
2013       }
2014     for (x=0; x < (ssize_t) image->columns; x++)
2015     {
2016       register ssize_t
2017         i;
2018
2019       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2020       {
2021         PixelTrait
2022           traits;
2023
2024         traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
2025         if ((traits & UpdatePixelTrait) != 0)
2026           q[i]=gamma_map[ScaleQuantumToMap(q[i])];
2027       }
2028       q+=GetPixelChannels(image);
2029     }
2030     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2031       status=MagickFalse;
2032     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2033       {
2034         MagickBooleanType
2035           proceed;
2036
2037 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2038   #pragma omp critical (MagickCore_GammaImage)
2039 #endif
2040         proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2041           image->rows);
2042         if (proceed == MagickFalse)
2043           status=MagickFalse;
2044       }
2045   }
2046   image_view=DestroyCacheView(image_view);
2047   gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2048   if (image->gamma != 0.0)
2049     image->gamma*=gamma;
2050   return(status);
2051 }
2052 \f
2053 /*
2054 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2055 %                                                                             %
2056 %                                                                             %
2057 %                                                                             %
2058 %     H a l d C l u t I m a g e                                               %
2059 %                                                                             %
2060 %                                                                             %
2061 %                                                                             %
2062 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2063 %
2064 %  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
2065 %  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2066 %  Create it with the HALD coder.  You can apply any color transformation to
2067 %  the Hald image and then use this method to apply the transform to the
2068 %  image.
2069 %
2070 %  The format of the HaldClutImage method is:
2071 %
2072 %      MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2073 %
2074 %  A description of each parameter follows:
2075 %
2076 %    o image: the image, which is replaced by indexed CLUT values
2077 %
2078 %    o hald_image: the color lookup table image for replacement color values.
2079 %
2080 */
2081
2082 static inline size_t MagickMin(const size_t x,const size_t y)
2083 {
2084   if (x < y)
2085     return(x);
2086   return(y);
2087 }
2088
2089 MagickExport MagickBooleanType HaldClutImage(Image *image,
2090   const Image *hald_image)
2091 {
2092 #define HaldClutImageTag  "Clut/Image"
2093
2094   typedef struct _HaldInfo
2095   {
2096     MagickRealType
2097       x,
2098       y,
2099       z;
2100   } HaldInfo;
2101
2102   CacheView
2103     *hald_view,
2104     *image_view;
2105
2106   double
2107     width;
2108
2109   ExceptionInfo
2110     *exception;
2111
2112   MagickBooleanType
2113     status;
2114
2115   MagickOffsetType
2116     progress;
2117
2118   PixelInfo
2119     zero;
2120
2121   size_t
2122     cube_size,
2123     length,
2124     level;
2125
2126   ssize_t
2127     y;
2128
2129   assert(image != (Image *) NULL);
2130   assert(image->signature == MagickSignature);
2131   if (image->debug != MagickFalse)
2132     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2133   assert(hald_image != (Image *) NULL);
2134   assert(hald_image->signature == MagickSignature);
2135   if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2136     return(MagickFalse);
2137   if (image->matte == MagickFalse)
2138     (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2139   /*
2140     Hald clut image.
2141   */
2142   status=MagickTrue;
2143   progress=0;
2144   length=MagickMin(hald_image->columns,hald_image->rows);
2145   for (level=2; (level*level*level) < length; level++) ;
2146   level*=level;
2147   cube_size=level*level;
2148   width=(double) hald_image->columns;
2149   GetPixelInfo(hald_image,&zero);
2150   exception=(&image->exception);
2151   image_view=AcquireCacheView(image);
2152   hald_view=AcquireCacheView(hald_image);
2153 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2154   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2155 #endif
2156   for (y=0; y < (ssize_t) image->rows; y++)
2157   {
2158     double
2159       offset;
2160
2161     HaldInfo
2162       point;
2163
2164     PixelInfo
2165       pixel,
2166       pixel1,
2167       pixel2,
2168       pixel3,
2169       pixel4;
2170
2171     register Quantum
2172       *restrict q;
2173
2174     register ssize_t
2175       x;
2176
2177     if (status == MagickFalse)
2178       continue;
2179     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2180     if (q == (const Quantum *) NULL)
2181       {
2182         status=MagickFalse;
2183         continue;
2184       }
2185     pixel=zero;
2186     pixel1=zero;
2187     pixel2=zero;
2188     pixel3=zero;
2189     pixel4=zero;
2190     for (x=0; x < (ssize_t) image->columns; x++)
2191     {
2192       point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2193       point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2194       point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2195       offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2196       point.x-=floor(point.x);
2197       point.y-=floor(point.y);
2198       point.z-=floor(point.z);
2199       (void) InterpolatePixelInfo(image,hald_view,
2200         UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2201         &pixel1,exception);
2202       (void) InterpolatePixelInfo(image,hald_view,
2203         UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2204         width),&pixel2,exception);
2205       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2206         pixel2.alpha,point.y,&pixel3);
2207       offset+=cube_size;
2208       (void) InterpolatePixelInfo(image,hald_view,
2209         UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2210         &pixel1,exception);
2211       (void) InterpolatePixelInfo(image,hald_view,
2212         UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2213         width),&pixel2,exception);
2214       CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2215         pixel2.alpha,point.y,&pixel4);
2216       CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2217         pixel4.alpha,point.z,&pixel);
2218       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2219         SetPixelRed(image,
2220           ClampToQuantum(pixel.red),q);
2221       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2222         SetPixelGreen(image,
2223           ClampToQuantum(pixel.green),q);
2224       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2225         SetPixelBlue(image,
2226           ClampToQuantum(pixel.blue),q);
2227       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2228           (image->colorspace == CMYKColorspace))
2229         SetPixelBlack(image,
2230           ClampToQuantum(pixel.black),q);
2231       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && (image->matte != MagickFalse))
2232         SetPixelAlpha(image,
2233           ClampToQuantum(pixel.alpha),q);
2234       q+=GetPixelChannels(image);
2235     }
2236     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2237       status=MagickFalse;
2238     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2239       {
2240         MagickBooleanType
2241           proceed;
2242
2243 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2244   #pragma omp critical (MagickCore_HaldClutImage)
2245 #endif
2246         proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2247         if (proceed == MagickFalse)
2248           status=MagickFalse;
2249       }
2250   }
2251   hald_view=DestroyCacheView(hald_view);
2252   image_view=DestroyCacheView(image_view);
2253   return(status);
2254 }
2255 \f
2256 /*
2257 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2258 %                                                                             %
2259 %                                                                             %
2260 %                                                                             %
2261 %     L e v e l I m a g e                                                     %
2262 %                                                                             %
2263 %                                                                             %
2264 %                                                                             %
2265 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2266 %
2267 %  LevelImage() adjusts the levels of a particular image channel by
2268 %  scaling the colors falling between specified white and black points to
2269 %  the full available quantum range.
2270 %
2271 %  The parameters provided represent the black, and white points.  The black
2272 %  point specifies the darkest color in the image. Colors darker than the
2273 %  black point are set to zero.  White point specifies the lightest color in
2274 %  the image.  Colors brighter than the white point are set to the maximum
2275 %  quantum value.
2276 %
2277 %  If a '!' flag is given, map black and white colors to the given levels
2278 %  rather than mapping those levels to black and white.  See
2279 %  LevelizeImage() below.
2280 %
2281 %  Gamma specifies a gamma correction to apply to the image.
2282 %
2283 %  The format of the LevelImage method is:
2284 %
2285 %      MagickBooleanType LevelImage(Image *image,const char *levels)
2286 %
2287 %  A description of each parameter follows:
2288 %
2289 %    o image: the image.
2290 %
2291 %    o levels: Specify the levels where the black and white points have the
2292 %      range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2293 %      A '!' flag inverts the re-mapping.
2294 %
2295 */
2296 MagickExport MagickBooleanType LevelImage(Image *image,
2297   const double black_point,const double white_point,const double gamma)
2298 {
2299 #define LevelImageTag  "Level/Image"
2300 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2301   pow(scale*((double) (x)-black_point),1.0/gamma)))
2302
2303   CacheView
2304     *image_view;
2305
2306   ExceptionInfo
2307     *exception;
2308
2309   MagickBooleanType
2310     status;
2311
2312   MagickOffsetType
2313     progress;
2314
2315   register double
2316     scale;
2317
2318   register ssize_t
2319     i;
2320
2321   ssize_t
2322     y;
2323
2324   /*
2325     Allocate and initialize levels map.
2326   */
2327   assert(image != (Image *) NULL);
2328   assert(image->signature == MagickSignature);
2329   if (image->debug != MagickFalse)
2330     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2331   scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2332   if (image->storage_class == PseudoClass)
2333 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2334   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2335 #endif
2336     for (i=0; i < (ssize_t) image->colors; i++)
2337     {
2338       /*
2339         Level colormap.
2340       */
2341       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2342         image->colormap[i].red=LevelQuantum(image->colormap[i].red);
2343       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2344         image->colormap[i].green=LevelQuantum(image->colormap[i].green);
2345       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2346         image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
2347       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2348         image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
2349       }
2350   /*
2351     Level image.
2352   */
2353   status=MagickTrue;
2354   progress=0;
2355   exception=(&image->exception);
2356   image_view=AcquireCacheView(image);
2357 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2358   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2359 #endif
2360   for (y=0; y < (ssize_t) image->rows; y++)
2361   {
2362     register Quantum
2363       *restrict q;
2364
2365     register ssize_t
2366       x;
2367
2368     if (status == MagickFalse)
2369       continue;
2370     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2371     if (q == (const Quantum *) NULL)
2372       {
2373         status=MagickFalse;
2374         continue;
2375       }
2376     for (x=0; x < (ssize_t) image->columns; x++)
2377     {
2378       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2379         SetPixelRed(image,LevelQuantum(
2380           GetPixelRed(image,q)),q);
2381       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2382         SetPixelGreen(image,
2383           LevelQuantum(GetPixelGreen(image,q)),q);
2384       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2385         SetPixelBlue(image,
2386           LevelQuantum(GetPixelBlue(image,q)),q);
2387       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2388           (image->matte == MagickTrue))
2389         SetPixelAlpha(image,
2390           LevelQuantum(GetPixelAlpha(image,q)),q);
2391       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2392           (image->colorspace == CMYKColorspace))
2393         SetPixelBlack(image,
2394           LevelQuantum(GetPixelBlack(image,q)),q);
2395       q+=GetPixelChannels(image);
2396     }
2397     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2398       status=MagickFalse;
2399     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2400       {
2401         MagickBooleanType
2402           proceed;
2403
2404 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2405   #pragma omp critical (MagickCore_LevelImage)
2406 #endif
2407         proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2408         if (proceed == MagickFalse)
2409           status=MagickFalse;
2410       }
2411   }
2412   image_view=DestroyCacheView(image_view);
2413   return(status);
2414 }
2415 \f
2416 /*
2417 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2418 %                                                                             %
2419 %                                                                             %
2420 %                                                                             %
2421 %     L e v e l i z e I m a g e C h a n n e l                                 %
2422 %                                                                             %
2423 %                                                                             %
2424 %                                                                             %
2425 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2426 %
2427 %  LevelizeImage() applies the reversed LevelImage() operation to just
2428 %  the specific channels specified.  It compresses the full range of color
2429 %  values, so that they lie between the given black and white points. Gamma is
2430 %  applied before the values are mapped.
2431 %
2432 %  LevelizeImage() can be called with by using a +level command line
2433 %  API option, or using a '!' on a -level or LevelImage() geometry string.
2434 %
2435 %  It can be used for example de-contrast a greyscale image to the exact
2436 %  levels specified.  Or by using specific levels for each channel of an image
2437 %  you can convert a gray-scale image to any linear color gradient, according
2438 %  to those levels.
2439 %
2440 %  The format of the LevelizeImage method is:
2441 %
2442 %      MagickBooleanType LevelizeImage(Image *image,const double black_point,
2443 %        const double white_point,const double gamma)
2444 %
2445 %  A description of each parameter follows:
2446 %
2447 %    o image: the image.
2448 %
2449 %    o black_point: The level to map zero (black) to.
2450 %
2451 %    o white_point: The level to map QuantiumRange (white) to.
2452 %
2453 %    o gamma: adjust gamma by this factor before mapping values.
2454 %
2455 */
2456 MagickExport MagickBooleanType LevelizeImage(Image *image,
2457   const double black_point,const double white_point,const double gamma)
2458 {
2459 #define LevelizeImageTag  "Levelize/Image"
2460 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2461   pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2462   black_point))
2463
2464   CacheView
2465     *image_view;
2466
2467   ExceptionInfo
2468     *exception;
2469
2470   MagickBooleanType
2471     status;
2472
2473   MagickOffsetType
2474     progress;
2475
2476   register ssize_t
2477     i;
2478
2479   ssize_t
2480     y;
2481
2482   /*
2483     Allocate and initialize levels map.
2484   */
2485   assert(image != (Image *) NULL);
2486   assert(image->signature == MagickSignature);
2487   if (image->debug != MagickFalse)
2488     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2489   if (image->storage_class == PseudoClass)
2490 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2491   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2492 #endif
2493     for (i=0; i < (ssize_t) image->colors; i++)
2494     {
2495       /*
2496         Level colormap.
2497       */
2498       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2499         image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2500       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2501         image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2502       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2503         image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2504       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2505         image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
2506     }
2507   /*
2508     Level image.
2509   */
2510   status=MagickTrue;
2511   progress=0;
2512   exception=(&image->exception);
2513   image_view=AcquireCacheView(image);
2514 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2515   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2516 #endif
2517   for (y=0; y < (ssize_t) image->rows; y++)
2518   {
2519     register Quantum
2520       *restrict q;
2521
2522     register ssize_t
2523       x;
2524
2525     if (status == MagickFalse)
2526       continue;
2527     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2528     if (q == (const Quantum *) NULL)
2529       {
2530         status=MagickFalse;
2531         continue;
2532       }
2533     for (x=0; x < (ssize_t) image->columns; x++)
2534     {
2535       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2536         SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
2537       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2538         SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
2539       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2540         SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
2541       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2542           (image->colorspace == CMYKColorspace))
2543         SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
2544       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2545           (image->matte == MagickTrue))
2546         SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2547       q+=GetPixelChannels(image);
2548     }
2549     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2550       status=MagickFalse;
2551     if (image->progress_monitor != (MagickProgressMonitor) NULL)
2552       {
2553         MagickBooleanType
2554           proceed;
2555
2556 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2557   #pragma omp critical (MagickCore_LevelizeImage)
2558 #endif
2559         proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2560         if (proceed == MagickFalse)
2561           status=MagickFalse;
2562       }
2563   }
2564   image_view=DestroyCacheView(image_view);
2565   return(status);
2566 }
2567 \f
2568 /*
2569 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570 %                                                                             %
2571 %                                                                             %
2572 %                                                                             %
2573 %     L e v e l I m a g e C o l o r s                                         %
2574 %                                                                             %
2575 %                                                                             %
2576 %                                                                             %
2577 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2578 %
2579 %  LevelImageColors() maps the given color to "black" and "white" values,
2580 %  linearly spreading out the colors, and level values on a channel by channel
2581 %  bases, as per LevelImage().  The given colors allows you to specify
2582 %  different level ranges for each of the color channels separately.
2583 %
2584 %  If the boolean 'invert' is set true the image values will modifyed in the
2585 %  reverse direction. That is any existing "black" and "white" colors in the
2586 %  image will become the color values given, with all other values compressed
2587 %  appropriatally.  This effectivally maps a greyscale gradient into the given
2588 %  color gradient.
2589 %
2590 %  The format of the LevelImageColors method is:
2591 %
2592 %    MagickBooleanType LevelImageColors(Image *image,
2593 %      const PixelInfo *black_color,const PixelInfo *white_color,
2594 %      const MagickBooleanType invert)
2595 %
2596 %  A description of each parameter follows:
2597 %
2598 %    o image: the image.
2599 %
2600 %    o black_color: The color to map black to/from
2601 %
2602 %    o white_point: The color to map white to/from
2603 %
2604 %    o invert: if true map the colors (levelize), rather than from (level)
2605 %
2606 */
2607 MagickExport MagickBooleanType LevelImageColors(Image *image,
2608   const PixelInfo *black_color,const PixelInfo *white_color,
2609   const MagickBooleanType invert)
2610 {
2611   MagickStatusType
2612     status;
2613
2614   /*
2615     Allocate and initialize levels map.
2616   */
2617   assert(image != (Image *) NULL);
2618   assert(image->signature == MagickSignature);
2619   if (image->debug != MagickFalse)
2620     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2621   status=MagickFalse;
2622   if (invert == MagickFalse)
2623     {
2624       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2625         {
2626           PushPixelChannelMap(image,RedChannel);
2627           status|=LevelImage(image,black_color->red,white_color->red,1.0);
2628           PopPixelChannelMap(image);
2629         }
2630       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2631         {
2632           PushPixelChannelMap(image,GreenChannel);
2633           status|=LevelImage(image,black_color->green,white_color->green,1.0);
2634           PopPixelChannelMap(image);
2635         }
2636       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2637         {
2638           PushPixelChannelMap(image,BlueChannel);
2639           status|=LevelImage(image,black_color->blue,white_color->blue,1.0);
2640           PopPixelChannelMap(image);
2641         }
2642       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2643           (image->colorspace == CMYKColorspace))
2644         {
2645           PushPixelChannelMap(image,BlackChannel);
2646           status|=LevelImage(image,black_color->black,white_color->black,1.0);
2647           PopPixelChannelMap(image);
2648         }
2649       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2650           (image->matte == MagickTrue))
2651         {
2652           PushPixelChannelMap(image,AlphaChannel);
2653           status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0);
2654           PopPixelChannelMap(image);
2655         }
2656     }
2657   else
2658     {
2659       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2660         {
2661           PushPixelChannelMap(image,RedChannel);
2662           status|=LevelizeImage(image,black_color->red,white_color->red,1.0);
2663           PopPixelChannelMap(image);
2664         }
2665       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2666         {
2667           PushPixelChannelMap(image,GreenChannel);
2668           status|=LevelizeImage(image,black_color->green,white_color->green,
2669             1.0);
2670           PopPixelChannelMap(image);
2671         }
2672       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2673         {
2674           PushPixelChannelMap(image,BlueChannel);
2675           status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0);
2676           PopPixelChannelMap(image);
2677         }
2678       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2679           (image->colorspace == CMYKColorspace))
2680         {
2681           PushPixelChannelMap(image,BlackChannel);
2682           status|=LevelizeImage(image,black_color->black,white_color->black,
2683             1.0);
2684           PopPixelChannelMap(image);
2685         }
2686       if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2687           (image->matte == MagickTrue))
2688         {
2689           PushPixelChannelMap(image,AlphaChannel);
2690           status|=LevelizeImage(image,black_color->alpha,white_color->alpha,
2691             1.0);
2692           PopPixelChannelMap(image);
2693         }
2694     }
2695   return(status == 0 ? MagickFalse : MagickTrue);
2696 }
2697 \f
2698 /*
2699 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2700 %                                                                             %
2701 %                                                                             %
2702 %                                                                             %
2703 %     L i n e a r S t r e t c h I m a g e                                     %
2704 %                                                                             %
2705 %                                                                             %
2706 %                                                                             %
2707 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2708 %
2709 %  The LinearStretchImage() discards any pixels below the black point and
2710 %  above the white point and levels the remaining pixels.
2711 %
2712 %  The format of the LinearStretchImage method is:
2713 %
2714 %      MagickBooleanType LinearStretchImage(Image *image,
2715 %        const double black_point,const double white_point)
2716 %
2717 %  A description of each parameter follows:
2718 %
2719 %    o image: the image.
2720 %
2721 %    o black_point: the black point.
2722 %
2723 %    o white_point: the white point.
2724 %
2725 */
2726 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2727   const double black_point,const double white_point)
2728 {
2729 #define LinearStretchImageTag  "LinearStretch/Image"
2730
2731   ExceptionInfo
2732     *exception;
2733
2734   MagickBooleanType
2735     status;
2736
2737   MagickRealType
2738     *histogram,
2739     intensity;
2740
2741   ssize_t
2742     black,
2743     white,
2744     y;
2745
2746   /*
2747     Allocate histogram and linear map.
2748   */
2749   assert(image != (Image *) NULL);
2750   assert(image->signature == MagickSignature);
2751   histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2752     sizeof(*histogram));
2753   if (histogram == (MagickRealType *) NULL)
2754     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2755       image->filename);
2756   /*
2757     Form histogram.
2758   */
2759   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2760   exception=(&image->exception);
2761   for (y=0; y < (ssize_t) image->rows; y++)
2762   {
2763     register const Quantum
2764       *restrict p;
2765
2766     register ssize_t
2767       x;
2768
2769     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2770     if (p == (const Quantum *) NULL)
2771       break;
2772     for (x=(ssize_t) image->columns-1; x >= 0; x--)
2773     {
2774       histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
2775       p+=GetPixelChannels(image);
2776     }
2777   }
2778   /*
2779     Find the histogram boundaries by locating the black and white point levels.
2780   */
2781   intensity=0.0;
2782   for (black=0; black < (ssize_t) MaxMap; black++)
2783   {
2784     intensity+=histogram[black];
2785     if (intensity >= black_point)
2786       break;
2787   }
2788   intensity=0.0;
2789   for (white=(ssize_t) MaxMap; white != 0; white--)
2790   {
2791     intensity+=histogram[white];
2792     if (intensity >= white_point)
2793       break;
2794   }
2795   histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2796   status=LevelImage(image,(double) black,(double) white,1.0);
2797   return(status);
2798 }
2799 \f
2800 /*
2801 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2802 %                                                                             %
2803 %                                                                             %
2804 %                                                                             %
2805 %     M o d u l a t e I m a g e                                               %
2806 %                                                                             %
2807 %                                                                             %
2808 %                                                                             %
2809 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2810 %
2811 %  ModulateImage() lets you control the brightness, saturation, and hue
2812 %  of an image.  Modulate represents the brightness, saturation, and hue
2813 %  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
2814 %  modulation is lightness, saturation, and hue.  And if the colorspace is
2815 %  HWB, use blackness, whiteness, and hue.
2816 %
2817 %  The format of the ModulateImage method is:
2818 %
2819 %      MagickBooleanType ModulateImage(Image *image,const char *modulate)
2820 %
2821 %  A description of each parameter follows:
2822 %
2823 %    o image: the image.
2824 %
2825 %    o modulate: Define the percent change in brightness, saturation, and
2826 %      hue.
2827 %
2828 */
2829
2830 static void ModulateHSB(const double percent_hue,
2831   const double percent_saturation,const double percent_brightness,
2832   Quantum *red,Quantum *green,Quantum *blue)
2833 {
2834   double
2835     brightness,
2836     hue,
2837     saturation;
2838
2839   /*
2840     Increase or decrease color brightness, saturation, or hue.
2841   */
2842   assert(red != (Quantum *) NULL);
2843   assert(green != (Quantum *) NULL);
2844   assert(blue != (Quantum *) NULL);
2845   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2846   hue+=0.5*(0.01*percent_hue-1.0);
2847   while (hue < 0.0)
2848     hue+=1.0;
2849   while (hue > 1.0)
2850     hue-=1.0;
2851   saturation*=0.01*percent_saturation;
2852   brightness*=0.01*percent_brightness;
2853   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2854 }
2855
2856 static void ModulateHSL(const double percent_hue,
2857   const double percent_saturation,const double percent_lightness,
2858   Quantum *red,Quantum *green,Quantum *blue)
2859 {
2860   double
2861     hue,
2862     lightness,
2863     saturation;
2864
2865   /*
2866     Increase or decrease color lightness, saturation, or hue.
2867   */
2868   assert(red != (Quantum *) NULL);
2869   assert(green != (Quantum *) NULL);
2870   assert(blue != (Quantum *) NULL);
2871   ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2872   hue+=0.5*(0.01*percent_hue-1.0);
2873   while (hue < 0.0)
2874     hue+=1.0;
2875   while (hue > 1.0)
2876     hue-=1.0;
2877   saturation*=0.01*percent_saturation;
2878   lightness*=0.01*percent_lightness;
2879   ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2880 }
2881
2882 static void ModulateHWB(const double percent_hue,const double percent_whiteness,  const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
2883 {
2884   double
2885     blackness,
2886     hue,
2887     whiteness;
2888
2889   /*
2890     Increase or decrease color blackness, whiteness, or hue.
2891   */
2892   assert(red != (Quantum *) NULL);
2893   assert(green != (Quantum *) NULL);
2894   assert(blue != (Quantum *) NULL);
2895   ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2896   hue+=0.5*(0.01*percent_hue-1.0);
2897   while (hue < 0.0)
2898     hue+=1.0;
2899   while (hue > 1.0)
2900     hue-=1.0;
2901   blackness*=0.01*percent_blackness;
2902   whiteness*=0.01*percent_whiteness;
2903   ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2904 }
2905
2906 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
2907 {
2908 #define ModulateImageTag  "Modulate/Image"
2909
2910   CacheView
2911     *image_view;
2912
2913   ColorspaceType
2914     colorspace;
2915
2916   const char
2917     *artifact;
2918
2919   double
2920     percent_brightness,
2921     percent_hue,
2922     percent_saturation;
2923
2924   ExceptionInfo
2925     *exception;
2926
2927   GeometryInfo
2928     geometry_info;
2929
2930   MagickBooleanType
2931     status;
2932
2933   MagickOffsetType
2934     progress;
2935
2936   MagickStatusType
2937     flags;
2938
2939   register ssize_t
2940     i;
2941
2942   ssize_t
2943     y;
2944
2945   /*
2946     Initialize modulate table.
2947   */
2948   assert(image != (Image *) NULL);
2949   assert(image->signature == MagickSignature);
2950   if (image->debug != MagickFalse)
2951     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2952   if (modulate == (char *) NULL)
2953     return(MagickFalse);
2954   flags=ParseGeometry(modulate,&geometry_info);
2955   percent_brightness=geometry_info.rho;
2956   percent_saturation=geometry_info.sigma;
2957   if ((flags & SigmaValue) == 0)
2958     percent_saturation=100.0;
2959   percent_hue=geometry_info.xi;
2960   if ((flags & XiValue) == 0)
2961     percent_hue=100.0;
2962   colorspace=UndefinedColorspace;
2963   artifact=GetImageArtifact(image,"modulate:colorspace");
2964   if (artifact != (const char *) NULL)
2965     colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
2966       MagickFalse,artifact);
2967   if (image->storage_class == PseudoClass)
2968     {
2969       /*
2970         Modulate colormap.
2971       */
2972 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2973   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2974 #endif
2975       for (i=0; i < (ssize_t) image->colors; i++)
2976         switch (colorspace)
2977         {
2978           case HSBColorspace:
2979           {
2980             ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2981               &image->colormap[i].red,&image->colormap[i].green,
2982               &image->colormap[i].blue);
2983             break;
2984           }
2985           case HSLColorspace:
2986           default:
2987           {
2988             ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2989               &image->colormap[i].red,&image->colormap[i].green,
2990               &image->colormap[i].blue);
2991             break;
2992           }
2993           case HWBColorspace:
2994           {
2995             ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2996               &image->colormap[i].red,&image->colormap[i].green,
2997               &image->colormap[i].blue);
2998             break;
2999           }
3000         }
3001     }
3002   /*
3003     Modulate image.
3004   */
3005   status=MagickTrue;
3006   progress=0;
3007   exception=(&image->exception);
3008   image_view=AcquireCacheView(image);
3009 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3010   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3011 #endif
3012   for (y=0; y < (ssize_t) image->rows; y++)
3013   {
3014     Quantum
3015       blue,
3016       green,
3017       red;
3018
3019     register Quantum
3020       *restrict q;
3021
3022     register ssize_t
3023       x;
3024
3025     if (status == MagickFalse)
3026       continue;
3027     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3028     if (q == (const Quantum *) NULL)
3029       {
3030         status=MagickFalse;
3031         continue;
3032       }
3033     for (x=0; x < (ssize_t) image->columns; x++)
3034     {
3035       red=GetPixelRed(image,q);
3036       green=GetPixelGreen(image,q);
3037       blue=GetPixelBlue(image,q);
3038       switch (colorspace)
3039       {
3040         case HSBColorspace:
3041         {
3042           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3043             &red,&green,&blue);
3044           break;
3045         }
3046         case HSLColorspace:
3047         default:
3048         {
3049           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3050             &red,&green,&blue);
3051           break;
3052         }
3053         case HWBColorspace:
3054         {
3055           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3056             &red,&green,&blue);
3057           break;
3058         }
3059       }
3060       SetPixelRed(image,red,q);
3061       SetPixelGreen(image,green,q);
3062       SetPixelBlue(image,blue,q);
3063       q+=GetPixelChannels(image);
3064     }
3065     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3066       status=MagickFalse;
3067     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3068       {
3069         MagickBooleanType
3070           proceed;
3071
3072 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3073   #pragma omp critical (MagickCore_ModulateImage)
3074 #endif
3075         proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3076         if (proceed == MagickFalse)
3077           status=MagickFalse;
3078       }
3079   }
3080   image_view=DestroyCacheView(image_view);
3081   return(status);
3082 }
3083 \f
3084 /*
3085 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3086 %                                                                             %
3087 %                                                                             %
3088 %                                                                             %
3089 %     N e g a t e I m a g e                                                   %
3090 %                                                                             %
3091 %                                                                             %
3092 %                                                                             %
3093 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3094 %
3095 %  NegateImage() negates the colors in the reference image.  The grayscale
3096 %  option means that only grayscale values within the image are negated.
3097 %
3098 %  The format of the NegateImage method is:
3099 %
3100 %      MagickBooleanType NegateImage(Image *image,
3101 %        const MagickBooleanType grayscale,ExceptionInfo *exception)
3102 %
3103 %  A description of each parameter follows:
3104 %
3105 %    o image: the image.
3106 %
3107 %    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3108 %
3109 %    o exception: return any errors or warnings in this structure.
3110 %
3111 */
3112 MagickExport MagickBooleanType NegateImage(Image *image,
3113   const MagickBooleanType grayscale,ExceptionInfo *exception)
3114 {
3115 #define NegateImageTag  "Negate/Image"
3116
3117   CacheView
3118     *image_view;
3119
3120   MagickBooleanType
3121     status;
3122
3123   MagickOffsetType
3124     progress;
3125
3126   register ssize_t
3127     i;
3128
3129   ssize_t
3130     y;
3131
3132   assert(image != (Image *) NULL);
3133   assert(image->signature == MagickSignature);
3134   if (image->debug != MagickFalse)
3135     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3136   if (image->storage_class == PseudoClass)
3137     {
3138       /*
3139         Negate colormap.
3140       */
3141 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3142   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3143 #endif
3144       for (i=0; i < (ssize_t) image->colors; i++)
3145       {
3146         if (grayscale != MagickFalse)
3147           if ((image->colormap[i].red != image->colormap[i].green) ||
3148               (image->colormap[i].green != image->colormap[i].blue))
3149             continue;
3150         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3151           image->colormap[i].red=(Quantum) QuantumRange-
3152             image->colormap[i].red;
3153         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3154           image->colormap[i].green=(Quantum) QuantumRange-
3155             image->colormap[i].green;
3156         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3157           image->colormap[i].blue=(Quantum) QuantumRange-
3158             image->colormap[i].blue;
3159       }
3160     }
3161   /*
3162     Negate image.
3163   */
3164   status=MagickTrue;
3165   progress=0;
3166   image_view=AcquireCacheView(image);
3167   if (grayscale != MagickFalse)
3168     {
3169 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3170   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3171 #endif
3172       for (y=0; y < (ssize_t) image->rows; y++)
3173       {
3174         MagickBooleanType
3175           sync;
3176
3177         register Quantum
3178           *restrict q;
3179
3180         register ssize_t
3181           x;
3182
3183         if (status == MagickFalse)
3184           continue;
3185         q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3186           exception);
3187         if (q == (const Quantum *) NULL)
3188           {
3189             status=MagickFalse;
3190             continue;
3191           }
3192         for (x=0; x < (ssize_t) image->columns; x++)
3193         {
3194           register ssize_t
3195             i;
3196
3197           if (IsPixelGray(image,q) != MagickFalse)
3198             {
3199               q+=GetPixelChannels(image);
3200               continue;
3201             }
3202           for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3203           {
3204             PixelTrait 
3205               traits;
3206
3207             traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3208             if ((traits & UpdatePixelTrait) != 0)
3209               q[i]=QuantumRange-q[i];
3210           }
3211           q+=GetPixelChannels(image);
3212         }
3213         sync=SyncCacheViewAuthenticPixels(image_view,exception);
3214         if (sync == MagickFalse)
3215           status=MagickFalse;
3216         if (image->progress_monitor != (MagickProgressMonitor) NULL)
3217           {
3218             MagickBooleanType
3219               proceed;
3220
3221 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3222   #pragma omp critical (MagickCore_NegateImage)
3223 #endif
3224             proceed=SetImageProgress(image,NegateImageTag,progress++,
3225               image->rows);
3226             if (proceed == MagickFalse)
3227               status=MagickFalse;
3228           }
3229       }
3230       image_view=DestroyCacheView(image_view);
3231       return(MagickTrue);
3232     }
3233   /*
3234     Negate image.
3235   */
3236 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3237   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3238 #endif
3239   for (y=0; y < (ssize_t) image->rows; y++)
3240   {
3241     register Quantum
3242       *restrict q;
3243
3244     register ssize_t
3245       x;
3246
3247     if (status == MagickFalse)
3248       continue;
3249     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3250     if (q == (const Quantum *) NULL)
3251       {
3252         status=MagickFalse;
3253         continue;
3254       }
3255     for (x=0; x < (ssize_t) image->columns; x++)
3256     {
3257       register ssize_t
3258         i;
3259
3260       for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3261       {
3262         PixelTrait 
3263           traits;
3264
3265         traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
3266         if ((traits & UpdatePixelTrait) != 0)
3267           q[i]=QuantumRange-q[i];
3268       }
3269       q+=GetPixelChannels(image);
3270     }
3271     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3272       status=MagickFalse;
3273     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3274       {
3275         MagickBooleanType
3276           proceed;
3277
3278 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3279   #pragma omp critical (MagickCore_NegateImage)
3280 #endif
3281         proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3282         if (proceed == MagickFalse)
3283           status=MagickFalse;
3284       }
3285   }
3286   image_view=DestroyCacheView(image_view);
3287   return(status);
3288 }
3289 \f
3290 /*
3291 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3292 %                                                                             %
3293 %                                                                             %
3294 %                                                                             %
3295 %     N o r m a l i z e I m a g e                                             %
3296 %                                                                             %
3297 %                                                                             %
3298 %                                                                             %
3299 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3300 %
3301 %  NormalizeImage() enhances the contrast of a color image by mapping the
3302 %  darkest 2 percent of all pixel to black and the brightest 1 percent to white.
3303 %
3304 %  The format of the NormalizeImage method is:
3305 %
3306 %      MagickBooleanType NormalizeImage(Image *image)
3307 %
3308 %  A description of each parameter follows:
3309 %
3310 %    o image: the image.
3311 %
3312 */
3313 MagickExport MagickBooleanType NormalizeImage(Image *image)
3314 {
3315   double
3316     black_point,
3317     white_point;
3318
3319   black_point=(double) image->columns*image->rows*0.0015;
3320   white_point=(double) image->columns*image->rows*0.9995;
3321   return(ContrastStretchImage(image,black_point,white_point));
3322 }
3323 \f
3324 /*
3325 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326 %                                                                             %
3327 %                                                                             %
3328 %                                                                             %
3329 %     S i g m o i d a l C o n t r a s t I m a g e                             %
3330 %                                                                             %
3331 %                                                                             %
3332 %                                                                             %
3333 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3334 %
3335 %  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3336 %  sigmoidal contrast algorithm.  Increase the contrast of the image using a
3337 %  sigmoidal transfer function without saturating highlights or shadows.
3338 %  Contrast indicates how much to increase the contrast (0 is none; 3 is
3339 %  typical; 20 is pushing it); mid-point indicates where midtones fall in the
3340 %  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
3341 %  sharpen to MagickTrue to increase the image contrast otherwise the contrast
3342 %  is reduced.
3343 %
3344 %  The format of the SigmoidalContrastImage method is:
3345 %
3346 %      MagickBooleanType SigmoidalContrastImage(Image *image,
3347 %        const MagickBooleanType sharpen,const char *levels)
3348 %
3349 %  A description of each parameter follows:
3350 %
3351 %    o image: the image.
3352 %
3353 %    o sharpen: Increase or decrease image contrast.
3354 %
3355 %    o alpha: strength of the contrast, the larger the number the more
3356 %      'threshold-like' it becomes.
3357 %
3358 %    o beta: midpoint of the function as a color value 0 to QuantumRange.
3359 %
3360 */
3361 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3362   const MagickBooleanType sharpen,const double contrast,const double midpoint)
3363 {
3364 #define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
3365
3366   CacheView
3367     *image_view;
3368
3369   ExceptionInfo
3370     *exception;
3371
3372   MagickBooleanType
3373     status;
3374
3375   MagickOffsetType
3376     progress;
3377
3378   MagickRealType
3379     *sigmoidal_map;
3380
3381   register ssize_t
3382     i;
3383
3384   ssize_t
3385     y;
3386
3387   /*
3388     Allocate and initialize sigmoidal maps.
3389   */
3390   assert(image != (Image *) NULL);
3391   assert(image->signature == MagickSignature);
3392   if (image->debug != MagickFalse)
3393     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3394   sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3395     sizeof(*sigmoidal_map));
3396   if (sigmoidal_map == (MagickRealType *) NULL)
3397     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3398       image->filename);
3399   (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3400 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3401   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3402 #endif
3403   for (i=0; i <= (ssize_t) MaxMap; i++)
3404   {
3405     if (sharpen != MagickFalse)
3406       {
3407         sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3408           (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3409           (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3410           (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3411           (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3412           (double) QuantumRange)))))+0.5));
3413         continue;
3414       }
3415     sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3416       (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3417       (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3418       (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3419       (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3420       (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3421       ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3422       (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3423       (double) QuantumRange*contrast))))))/contrast)));
3424   }
3425   if (image->storage_class == PseudoClass)
3426     {
3427       /*
3428         Sigmoidal-contrast enhance colormap.
3429       */
3430 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3431   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3432 #endif
3433       for (i=0; i < (ssize_t) image->colors; i++)
3434       {
3435         if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3436           image->colormap[i].red=ClampToQuantum(sigmoidal_map[
3437             ScaleQuantumToMap(image->colormap[i].red)]);
3438         if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3439           image->colormap[i].green=ClampToQuantum(sigmoidal_map[
3440             ScaleQuantumToMap(image->colormap[i].green)]);
3441         if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3442           image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
3443             ScaleQuantumToMap(image->colormap[i].blue)]);
3444         if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3445           image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3446             ScaleQuantumToMap(image->colormap[i].alpha)]);
3447       }
3448     }
3449   /*
3450     Sigmoidal-contrast enhance image.
3451   */
3452   status=MagickTrue;
3453   progress=0;
3454   exception=(&image->exception);
3455   image_view=AcquireCacheView(image);
3456 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3457   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3458 #endif
3459   for (y=0; y < (ssize_t) image->rows; y++)
3460   {
3461     register Quantum
3462       *restrict q;
3463
3464     register ssize_t
3465       x;
3466
3467     if (status == MagickFalse)
3468       continue;
3469     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3470     if (q == (const Quantum *) NULL)
3471       {
3472         status=MagickFalse;
3473         continue;
3474       }
3475     for (x=0; x < (ssize_t) image->columns; x++)
3476     {
3477       if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3478         SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3479           GetPixelRed(image,q))]),q);
3480       if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3481         SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3482           GetPixelGreen(image,q))]),q);
3483       if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3484         SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3485           GetPixelBlue(image,q))]),q);
3486       if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3487           (image->colorspace == CMYKColorspace))
3488         SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3489           GetPixelBlack(image,q))]),q);
3490       if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3491         SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3492           GetPixelAlpha(image,q))]),q);
3493       q+=GetPixelChannels(image);
3494     }
3495     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3496       status=MagickFalse;
3497     if (image->progress_monitor != (MagickProgressMonitor) NULL)
3498       {
3499         MagickBooleanType
3500           proceed;
3501
3502 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3503   #pragma omp critical (MagickCore_SigmoidalContrastImage)
3504 #endif
3505         proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3506           image->rows);
3507         if (proceed == MagickFalse)
3508           status=MagickFalse;
3509       }
3510   }
3511   image_view=DestroyCacheView(image_view);
3512   sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3513   return(status);
3514 }