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