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