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