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