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