2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
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 %
13 % MagickCore Image Enhancement Methods %
20 % Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
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. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/color.h"
49 #include "MagickCore/color-private.h"
50 #include "MagickCore/colorspace.h"
51 #include "MagickCore/colorspace-private.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/enhance.h"
54 #include "MagickCore/exception.h"
55 #include "MagickCore/exception-private.h"
56 #include "MagickCore/fx.h"
57 #include "MagickCore/gem.h"
58 #include "MagickCore/gem-private.h"
59 #include "MagickCore/geometry.h"
60 #include "MagickCore/histogram.h"
61 #include "MagickCore/image.h"
62 #include "MagickCore/image-private.h"
63 #include "MagickCore/memory_.h"
64 #include "MagickCore/monitor.h"
65 #include "MagickCore/monitor-private.h"
66 #include "MagickCore/option.h"
67 #include "MagickCore/pixel.h"
68 #include "MagickCore/pixel-accessor.h"
69 #include "MagickCore/quantum.h"
70 #include "MagickCore/quantum-private.h"
71 #include "MagickCore/resample.h"
72 #include "MagickCore/resample-private.h"
73 #include "MagickCore/resource_.h"
74 #include "MagickCore/statistic.h"
75 #include "MagickCore/string_.h"
76 #include "MagickCore/string-private.h"
77 #include "MagickCore/thread-private.h"
78 #include "MagickCore/threshold.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/xml-tree.h"
81 #include "MagickCore/xml-tree-private.h"
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88 % A u t o G a m m a I m a g e %
92 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94 % AutoGammaImage() extract the 'mean' from the image and adjust the image
95 % to try make set its gamma appropriatally.
97 % The format of the AutoGammaImage method is:
99 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
101 % A description of each parameter follows:
103 % o image: The image to auto-level
105 % o exception: return any errors or warnings in this structure.
108 MagickExport MagickBooleanType AutoGammaImage(Image *image,
109 ExceptionInfo *exception)
124 if (image->channel_mask == DefaultChannels)
127 Apply gamma correction equally across all given channels.
129 (void) GetImageMean(image,&mean,&sans,exception);
130 gamma=log(mean*QuantumScale)/log_mean;
131 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
134 Auto-gamma each channel separately.
137 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
142 PixelChannel channel=GetPixelChannelChannel(image,i);
143 PixelTrait traits=GetPixelChannelTraits(image,channel);
144 if ((traits & UpdatePixelTrait) == 0)
146 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
147 status=GetImageMean(image,&mean,&sans,exception);
148 gamma=log(mean*QuantumScale)/log_mean;
149 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
150 (void) SetImageChannelMask(image,channel_mask);
151 if (status == MagickFalse)
154 return(status != 0 ? MagickTrue : MagickFalse);
158 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162 % A u t o L e v e l I m a g e %
166 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
168 % AutoLevelImage() adjusts the levels of a particular image channel by
169 % scaling the minimum and maximum values to the full quantum range.
171 % The format of the LevelImage method is:
173 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
175 % A description of each parameter follows:
177 % o image: The image to auto-level
179 % o exception: return any errors or warnings in this structure.
182 MagickExport MagickBooleanType AutoLevelImage(Image *image,
183 ExceptionInfo *exception)
185 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193 % B r i g h t n e s s C o n t r a s t I m a g e %
197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199 % BrightnessContrastImage() changes the brightness and/or contrast of an
200 % image. It converts the brightness and contrast parameters into slope and
201 % intercept and calls a polynomical function to apply to the image.
203 % The format of the BrightnessContrastImage method is:
205 % MagickBooleanType BrightnessContrastImage(Image *image,
206 % const double brightness,const double contrast,ExceptionInfo *exception)
208 % A description of each parameter follows:
210 % o image: the image.
212 % o brightness: the brightness percent (-100 .. 100).
214 % o contrast: the contrast percent (-100 .. 100).
216 % o exception: return any errors or warnings in this structure.
219 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
220 const double brightness,const double contrast,ExceptionInfo *exception)
222 #define BrightnessContastImageTag "BrightnessContast/Image"
234 Compute slope and intercept.
236 assert(image != (Image *) NULL);
237 assert(image->signature == MagickSignature);
238 if (image->debug != MagickFalse)
239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
241 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
244 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
245 coefficients[0]=slope;
246 coefficients[1]=intercept;
247 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
252 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256 % C l u t I m a g e %
260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262 % ClutImage() replaces each color value in the given image, by using it as an
263 % index to lookup a replacement color value in a Color Look UP Table in the
264 % form of an image. The values are extracted along a diagonal of the CLUT
265 % image so either a horizontal or vertial gradient image can be used.
267 % Typically this is used to either re-color a gray-scale image according to a
268 % color gradient in the CLUT image, or to perform a freeform histogram
269 % (level) adjustment according to the (typically gray-scale) gradient in the
272 % When the 'channel' mask includes the matte/alpha transparency channel but
273 % one image has no such channel it is assumed that that image is a simple
274 % gray-scale image that will effect the alpha channel values, either for
275 % gray-scale coloring (with transparent or semi-transparent colors), or
276 % a histogram adjustment of existing alpha channel values. If both images
277 % have matte channels, direct and normal indexing is applied, which is rarely
280 % The format of the ClutImage method is:
282 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
283 % const PixelInterpolateMethod method,ExceptionInfo *exception)
285 % A description of each parameter follows:
287 % o image: the image, which is replaced by indexed CLUT values
289 % o clut_image: the color lookup table image for replacement color values.
291 % o method: the pixel interpolation method.
293 % o exception: return any errors or warnings in this structure.
296 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
297 const PixelInterpolateMethod method,ExceptionInfo *exception)
299 #define ClutImageTag "Clut/Image"
320 assert(image != (Image *) NULL);
321 assert(image->signature == MagickSignature);
322 if (image->debug != MagickFalse)
323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
324 assert(clut_image != (Image *) NULL);
325 assert(clut_image->signature == MagickSignature);
326 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
328 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
329 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
330 (void) TransformImageColorspace(image,sRGBColorspace,exception);
331 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
332 if (clut_map == (PixelInfo *) NULL)
333 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
340 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
341 clut_view=AcquireVirtualCacheView(clut_image,exception);
342 for (i=0; i <= (ssize_t) MaxMap; i++)
344 GetPixelInfo(clut_image,clut_map+i);
345 (void) InterpolatePixelInfo(clut_image,clut_view,method,
346 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
347 (clut_image->rows-adjust),clut_map+i,exception);
349 clut_view=DestroyCacheView(clut_view);
350 image_view=AcquireAuthenticCacheView(image,exception);
351 #if defined(MAGICKCORE_OPENMP_SUPPORT)
352 #pragma omp parallel for schedule(static,4) shared(progress,status) \
353 magick_threads(image,image,image->rows,1)
355 for (y=0; y < (ssize_t) image->rows; y++)
366 if (status == MagickFalse)
368 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
369 if (q == (Quantum *) NULL)
374 GetPixelInfo(image,&pixel);
375 for (x=0; x < (ssize_t) image->columns; x++)
377 if (GetPixelMask(image,q) == 0)
379 q+=GetPixelChannels(image);
382 GetPixelInfoPixel(image,q,&pixel);
383 pixel.red=clut_map[ScaleQuantumToMap(
384 ClampToQuantum(pixel.red))].red;
385 pixel.green=clut_map[ScaleQuantumToMap(
386 ClampToQuantum(pixel.green))].green;
387 pixel.blue=clut_map[ScaleQuantumToMap(
388 ClampToQuantum(pixel.blue))].blue;
389 pixel.black=clut_map[ScaleQuantumToMap(
390 ClampToQuantum(pixel.black))].black;
391 pixel.alpha=clut_map[ScaleQuantumToMap(
392 ClampToQuantum(pixel.alpha))].alpha;
393 SetPixelInfoPixel(image,&pixel,q);
394 q+=GetPixelChannels(image);
396 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
403 #if defined(MAGICKCORE_OPENMP_SUPPORT)
404 #pragma omp critical (MagickCore_ClutImage)
406 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
407 if (proceed == MagickFalse)
411 image_view=DestroyCacheView(image_view);
412 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
413 if ((clut_image->alpha_trait == BlendPixelTrait) &&
414 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
415 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424 % C o l o r D e c i s i o n L i s t I m a g e %
428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
431 % (CCC) file which solely contains one or more color corrections and applies
432 % the correction to the image. Here is a sample CCC file:
434 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
435 % <ColorCorrection id="cc03345">
437 % <Slope> 0.9 1.2 0.5 </Slope>
438 % <Offset> 0.4 -0.5 0.6 </Offset>
439 % <Power> 1.0 0.8 1.5 </Power>
442 % <Saturation> 0.85 </Saturation>
445 % </ColorCorrectionCollection>
447 % which includes the slop, offset, and power for each of the RGB channels
448 % as well as the saturation.
450 % The format of the ColorDecisionListImage method is:
452 % MagickBooleanType ColorDecisionListImage(Image *image,
453 % const char *color_correction_collection,ExceptionInfo *exception)
455 % A description of each parameter follows:
457 % o image: the image.
459 % o color_correction_collection: the color correction collection in XML.
461 % o exception: return any errors or warnings in this structure.
464 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
465 const char *color_correction_collection,ExceptionInfo *exception)
467 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
469 typedef struct _Correction
477 typedef struct _ColorCorrection
492 token[MaxTextExtent];
523 Allocate and initialize cdl maps.
525 assert(image != (Image *) NULL);
526 assert(image->signature == MagickSignature);
527 if (image->debug != MagickFalse)
528 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
529 if (color_correction_collection == (const char *) NULL)
531 ccc=NewXMLTree((const char *) color_correction_collection,exception);
532 if (ccc == (XMLTreeInfo *) NULL)
534 cc=GetXMLTreeChild(ccc,"ColorCorrection");
535 if (cc == (XMLTreeInfo *) NULL)
537 ccc=DestroyXMLTree(ccc);
540 color_correction.red.slope=1.0;
541 color_correction.red.offset=0.0;
542 color_correction.red.power=1.0;
543 color_correction.green.slope=1.0;
544 color_correction.green.offset=0.0;
545 color_correction.green.power=1.0;
546 color_correction.blue.slope=1.0;
547 color_correction.blue.offset=0.0;
548 color_correction.blue.power=1.0;
549 color_correction.saturation=0.0;
550 sop=GetXMLTreeChild(cc,"SOPNode");
551 if (sop != (XMLTreeInfo *) NULL)
558 slope=GetXMLTreeChild(sop,"Slope");
559 if (slope != (XMLTreeInfo *) NULL)
561 content=GetXMLTreeContent(slope);
562 p=(const char *) content;
563 for (i=0; (*p != '\0') && (i < 3); i++)
565 GetMagickToken(p,&p,token);
567 GetMagickToken(p,&p,token);
572 color_correction.red.slope=StringToDouble(token,(char **) NULL);
577 color_correction.green.slope=StringToDouble(token,
583 color_correction.blue.slope=StringToDouble(token,
590 offset=GetXMLTreeChild(sop,"Offset");
591 if (offset != (XMLTreeInfo *) NULL)
593 content=GetXMLTreeContent(offset);
594 p=(const char *) content;
595 for (i=0; (*p != '\0') && (i < 3); i++)
597 GetMagickToken(p,&p,token);
599 GetMagickToken(p,&p,token);
604 color_correction.red.offset=StringToDouble(token,
610 color_correction.green.offset=StringToDouble(token,
616 color_correction.blue.offset=StringToDouble(token,
623 power=GetXMLTreeChild(sop,"Power");
624 if (power != (XMLTreeInfo *) NULL)
626 content=GetXMLTreeContent(power);
627 p=(const char *) content;
628 for (i=0; (*p != '\0') && (i < 3); i++)
630 GetMagickToken(p,&p,token);
632 GetMagickToken(p,&p,token);
637 color_correction.red.power=StringToDouble(token,(char **) NULL);
642 color_correction.green.power=StringToDouble(token,
648 color_correction.blue.power=StringToDouble(token,
656 sat=GetXMLTreeChild(cc,"SATNode");
657 if (sat != (XMLTreeInfo *) NULL)
662 saturation=GetXMLTreeChild(sat,"Saturation");
663 if (saturation != (XMLTreeInfo *) NULL)
665 content=GetXMLTreeContent(saturation);
666 p=(const char *) content;
667 GetMagickToken(p,&p,token);
668 color_correction.saturation=StringToDouble(token,(char **) NULL);
671 ccc=DestroyXMLTree(ccc);
672 if (image->debug != MagickFalse)
674 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
675 " Color Correction Collection:");
676 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
677 " color_correction.red.slope: %g",color_correction.red.slope);
678 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
679 " color_correction.red.offset: %g",color_correction.red.offset);
680 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
681 " color_correction.red.power: %g",color_correction.red.power);
682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
683 " color_correction.green.slope: %g",color_correction.green.slope);
684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
685 " color_correction.green.offset: %g",color_correction.green.offset);
686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
687 " color_correction.green.power: %g",color_correction.green.power);
688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
689 " color_correction.blue.slope: %g",color_correction.blue.slope);
690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
691 " color_correction.blue.offset: %g",color_correction.blue.offset);
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " color_correction.blue.power: %g",color_correction.blue.power);
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.saturation: %g",color_correction.saturation);
697 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
698 if (cdl_map == (PixelInfo *) NULL)
699 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
701 for (i=0; i <= (ssize_t) MaxMap; i++)
703 cdl_map[i].red=(double) ScaleMapToQuantum((double)
704 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
705 color_correction.red.offset,color_correction.red.power))));
706 cdl_map[i].green=(double) ScaleMapToQuantum((double)
707 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
708 color_correction.green.offset,color_correction.green.power))));
709 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
710 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
711 color_correction.blue.offset,color_correction.blue.power))));
713 if (image->storage_class == PseudoClass)
714 for (i=0; i < (ssize_t) image->colors; i++)
717 Apply transfer function to colormap.
722 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
723 0.07217f*image->colormap[i].blue;
724 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
725 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
726 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
727 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
728 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
729 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
732 Apply transfer function to image.
736 image_view=AcquireAuthenticCacheView(image,exception);
737 #if defined(MAGICKCORE_OPENMP_SUPPORT)
738 #pragma omp parallel for schedule(static,4) shared(progress,status) \
739 magick_threads(image,image,image->rows,1)
741 for (y=0; y < (ssize_t) image->rows; y++)
752 if (status == MagickFalse)
754 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
755 if (q == (Quantum *) NULL)
760 for (x=0; x < (ssize_t) image->columns; x++)
762 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
763 0.07217f*GetPixelBlue(image,q);
764 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
765 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
766 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
767 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
768 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
769 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
770 q+=GetPixelChannels(image);
772 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
774 if (image->progress_monitor != (MagickProgressMonitor) NULL)
779 #if defined(MAGICKCORE_OPENMP_SUPPORT)
780 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
782 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
783 progress++,image->rows);
784 if (proceed == MagickFalse)
788 image_view=DestroyCacheView(image_view);
789 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
794 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
798 % C o n t r a s t I m a g e %
802 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
804 % ContrastImage() enhances the intensity differences between the lighter and
805 % darker elements of the image. Set sharpen to a MagickTrue to increase the
806 % image contrast otherwise the contrast is reduced.
808 % The format of the ContrastImage method is:
810 % MagickBooleanType ContrastImage(Image *image,
811 % const MagickBooleanType sharpen,ExceptionInfo *exception)
813 % A description of each parameter follows:
815 % o image: the image.
817 % o sharpen: Increase or decrease image contrast.
819 % o exception: return any errors or warnings in this structure.
823 static void Contrast(const int sign,double *red,double *green,double *blue)
831 Enhance contrast: dark color become darker, light color become lighter.
833 assert(red != (double *) NULL);
834 assert(green != (double *) NULL);
835 assert(blue != (double *) NULL);
839 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
840 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
842 if (brightness > 1.0)
845 if (brightness < 0.0)
847 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
850 MagickExport MagickBooleanType ContrastImage(Image *image,
851 const MagickBooleanType sharpen,ExceptionInfo *exception)
853 #define ContrastImageTag "Contrast/Image"
873 assert(image != (Image *) NULL);
874 assert(image->signature == MagickSignature);
875 if (image->debug != MagickFalse)
876 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
877 sign=sharpen != MagickFalse ? 1 : -1;
878 if (image->storage_class == PseudoClass)
881 Contrast enhance colormap.
883 for (i=0; i < (ssize_t) image->colors; i++)
890 Contrast(sign,&red,&green,&blue);
891 image->colormap[i].red=(MagickRealType) red;
892 image->colormap[i].red=(MagickRealType) red;
893 image->colormap[i].red=(MagickRealType) red;
897 Contrast enhance image.
901 image_view=AcquireAuthenticCacheView(image,exception);
902 #if defined(MAGICKCORE_OPENMP_SUPPORT)
903 #pragma omp parallel for schedule(static,4) shared(progress,status) \
904 magick_threads(image,image,image->rows,1)
906 for (y=0; y < (ssize_t) image->rows; y++)
919 if (status == MagickFalse)
921 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
922 if (q == (Quantum *) NULL)
927 for (x=0; x < (ssize_t) image->columns; x++)
929 red=(double) GetPixelRed(image,q);
930 green=(double) GetPixelGreen(image,q);
931 blue=(double) GetPixelBlue(image,q);
932 Contrast(sign,&red,&green,&blue);
933 SetPixelRed(image,ClampToQuantum(red),q);
934 SetPixelGreen(image,ClampToQuantum(green),q);
935 SetPixelBlue(image,ClampToQuantum(blue),q);
936 q+=GetPixelChannels(image);
938 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
940 if (image->progress_monitor != (MagickProgressMonitor) NULL)
945 #if defined(MAGICKCORE_OPENMP_SUPPORT)
946 #pragma omp critical (MagickCore_ContrastImage)
948 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
949 if (proceed == MagickFalse)
953 image_view=DestroyCacheView(image_view);
958 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
962 % C o n t r a s t S t r e t c h I m a g e %
966 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
968 % ContrastStretchImage() is a simple image enhancement technique that attempts
969 % to improve the contrast in an image by 'stretching' the range of intensity
970 % values it contains to span a desired range of values. It differs from the
971 % more sophisticated histogram equalization in that it can only apply a
972 % linear scaling function to the image pixel values. As a result the
973 % 'enhancement' is less harsh.
975 % The format of the ContrastStretchImage method is:
977 % MagickBooleanType ContrastStretchImage(Image *image,
978 % const char *levels,ExceptionInfo *exception)
980 % A description of each parameter follows:
982 % o image: the image.
984 % o black_point: the black point.
986 % o white_point: the white point.
988 % o levels: Specify the levels where the black and white points have the
989 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
991 % o exception: return any errors or warnings in this structure.
994 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
995 const double black_point,const double white_point,ExceptionInfo *exception)
997 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
998 #define ContrastStretchImageTag "ContrastStretch/Image"
1025 Allocate histogram and stretch map.
1027 assert(image != (Image *) NULL);
1028 assert(image->signature == MagickSignature);
1029 if (image->debug != MagickFalse)
1030 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1031 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1032 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1033 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1034 sizeof(*histogram));
1035 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1036 GetPixelChannels(image)*sizeof(*stretch_map));
1037 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1038 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1040 if (stretch_map != (double *) NULL)
1041 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1042 if (histogram != (double *) NULL)
1043 histogram=(double *) RelinquishMagickMemory(histogram);
1044 if (white != (double *) NULL)
1045 white=(double *) RelinquishMagickMemory(white);
1046 if (black != (double *) NULL)
1047 black=(double *) RelinquishMagickMemory(black);
1048 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1054 if (IsImageGray(image,exception) != MagickFalse)
1055 (void) SetImageColorspace(image,GRAYColorspace,exception);
1057 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1058 sizeof(*histogram));
1059 image_view=AcquireVirtualCacheView(image,exception);
1060 for (y=0; y < (ssize_t) image->rows; y++)
1062 register const Quantum
1068 if (status == MagickFalse)
1070 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1071 if (p == (const Quantum *) NULL)
1076 for (x=0; x < (ssize_t) image->columns; x++)
1084 pixel=GetPixelIntensity(image,p);
1085 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1087 if (image->channel_mask != DefaultChannels)
1088 pixel=(double) p[i];
1089 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1090 ClampToQuantum(pixel))+i]++;
1092 p+=GetPixelChannels(image);
1095 image_view=DestroyCacheView(image_view);
1097 Find the histogram boundaries by locating the black/white levels.
1099 number_channels=GetPixelChannels(image);
1100 for (i=0; i < (ssize_t) number_channels; i++)
1109 white[i]=MaxRange(QuantumRange);
1111 for (j=0; j <= (ssize_t) MaxMap; j++)
1113 intensity+=histogram[GetPixelChannels(image)*j+i];
1114 if (intensity > black_point)
1117 black[i]=(double) j;
1119 for (j=(ssize_t) MaxMap; j != 0; j--)
1121 intensity+=histogram[GetPixelChannels(image)*j+i];
1122 if (intensity > ((double) image->columns*image->rows-white_point))
1125 white[i]=(double) j;
1127 histogram=(double *) RelinquishMagickMemory(histogram);
1129 Stretch the histogram to create the stretched image mapping.
1131 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1132 sizeof(*stretch_map));
1133 number_channels=GetPixelChannels(image);
1134 for (i=0; i < (ssize_t) number_channels; i++)
1139 for (j=0; j <= (ssize_t) MaxMap; j++)
1141 if (j < (ssize_t) black[i])
1142 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1144 if (j > (ssize_t) white[i])
1145 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1147 if (black[i] != white[i])
1148 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1149 (double) (MaxMap*(j-black[i])/(white[i]-black[i])));
1152 if (image->storage_class == PseudoClass)
1158 Stretch-contrast colormap.
1160 for (j=0; j < (ssize_t) image->colors; j++)
1162 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1164 i=GetPixelChannelChannel(image,RedPixelChannel);
1165 if (black[i] != white[i])
1166 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1167 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
1169 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1171 i=GetPixelChannelChannel(image,GreenPixelChannel);
1172 if (black[i] != white[i])
1173 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1174 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
1176 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1178 i=GetPixelChannelChannel(image,BluePixelChannel);
1179 if (black[i] != white[i])
1180 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
1183 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelChannel(image,AlphaPixelChannel);
1186 if (black[i] != white[i])
1187 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1188 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
1193 Stretch-contrast image.
1197 image_view=AcquireAuthenticCacheView(image,exception);
1198 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1199 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1200 magick_threads(image,image,image->rows,1)
1202 for (y=0; y < (ssize_t) image->rows; y++)
1210 if (status == MagickFalse)
1212 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1213 if (q == (Quantum *) NULL)
1218 for (x=0; x < (ssize_t) image->columns; x++)
1223 if (GetPixelMask(image,q) == 0)
1225 q+=GetPixelChannels(image);
1228 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1230 PixelChannel channel=GetPixelChannelChannel(image,i);
1231 PixelTrait traits=GetPixelChannelTraits(image,channel);
1232 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1234 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1235 ScaleQuantumToMap(q[i])+i]);
1237 q+=GetPixelChannels(image);
1239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1246 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1247 #pragma omp critical (MagickCore_ContrastStretchImage)
1249 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1251 if (proceed == MagickFalse)
1255 image_view=DestroyCacheView(image_view);
1256 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1257 white=(double *) RelinquishMagickMemory(white);
1258 black=(double *) RelinquishMagickMemory(black);
1263 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267 % E n h a n c e I m a g e %
1271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1273 % EnhanceImage() applies a digital filter that improves the quality of a
1276 % The format of the EnhanceImage method is:
1278 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1280 % A description of each parameter follows:
1282 % o image: the image.
1284 % o exception: return any errors or warnings in this structure.
1287 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289 #define EnhancePixel(weight) \
1290 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1291 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1292 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1293 distance*distance; \
1294 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
1296 aggregate+=(weight)*r[i]; \
1297 total_weight+=(weight); \
1299 r+=GetPixelChannels(image);
1300 #define EnhanceImageTag "Enhance/Image"
1319 Initialize enhanced image attributes.
1321 assert(image != (const Image *) NULL);
1322 assert(image->signature == MagickSignature);
1323 if (image->debug != MagickFalse)
1324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1325 assert(exception != (ExceptionInfo *) NULL);
1326 assert(exception->signature == MagickSignature);
1327 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1329 if (enhance_image == (Image *) NULL)
1330 return((Image *) NULL);
1331 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1333 enhance_image=DestroyImage(enhance_image);
1334 return((Image *) NULL);
1341 image_view=AcquireVirtualCacheView(image,exception);
1342 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1343 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1344 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1345 magick_threads(image,enhance_image,image->rows,1)
1347 for (y=0; y < (ssize_t) image->rows; y++)
1349 register const Quantum
1361 if (status == MagickFalse)
1363 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1364 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1366 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1371 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1372 for (x=0; x < (ssize_t) image->columns; x++)
1377 if (GetPixelMask(image,p) == 0)
1379 p+=GetPixelChannels(image);
1380 q+=GetPixelChannels(enhance_image);
1383 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1392 register const Quantum
1395 PixelChannel channel=GetPixelChannelChannel(image,i);
1396 PixelTrait traits=GetPixelChannelTraits(image,channel);
1397 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1398 if ((traits == UndefinedPixelTrait) ||
1399 (enhance_traits == UndefinedPixelTrait))
1401 SetPixelChannel(enhance_image,channel,p[center+i],q);
1402 if ((enhance_traits & CopyPixelTrait) != 0)
1405 Compute weighted average of target pixel color components.
1410 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1411 EnhancePixel(8.0); EnhancePixel(5.0);
1412 r=p+1*GetPixelChannels(image)*(image->columns+4);
1413 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1414 EnhancePixel(20.0); EnhancePixel(8.0);
1415 r=p+2*GetPixelChannels(image)*(image->columns+4);
1416 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1417 EnhancePixel(40.0); EnhancePixel(10.0);
1418 r=p+3*GetPixelChannels(image)*(image->columns+4);
1419 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1420 EnhancePixel(20.0); EnhancePixel(8.0);
1421 r=p+4*GetPixelChannels(image)*(image->columns+4);
1422 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1423 EnhancePixel(8.0); EnhancePixel(5.0);
1424 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1427 p+=GetPixelChannels(image);
1428 q+=GetPixelChannels(enhance_image);
1430 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1432 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1437 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1438 #pragma omp critical (MagickCore_EnhanceImage)
1440 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1441 if (proceed == MagickFalse)
1445 enhance_view=DestroyCacheView(enhance_view);
1446 image_view=DestroyCacheView(image_view);
1447 if (status == MagickFalse)
1448 enhance_image=DestroyImage(enhance_image);
1449 return(enhance_image);
1453 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1457 % E q u a l i z e I m a g e %
1461 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463 % EqualizeImage() applies a histogram equalization to the image.
1465 % The format of the EqualizeImage method is:
1467 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1469 % A description of each parameter follows:
1471 % o image: the image.
1473 % o exception: return any errors or warnings in this structure.
1476 MagickExport MagickBooleanType EqualizeImage(Image *image,
1477 ExceptionInfo *exception)
1479 #define EqualizeImageTag "Equalize/Image"
1491 black[CompositePixelChannel],
1495 white[CompositePixelChannel];
1507 Allocate and initialize histogram arrays.
1509 assert(image != (Image *) NULL);
1510 assert(image->signature == MagickSignature);
1511 if (image->debug != MagickFalse)
1512 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1513 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1514 GetPixelChannels(image)*sizeof(*equalize_map));
1515 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
1516 GetPixelChannels(image)*sizeof(*histogram));
1517 map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1518 GetPixelChannels(image)*sizeof(*map));
1519 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1520 (map == (double *) NULL))
1522 if (map != (double *) NULL)
1523 map=(double *) RelinquishMagickMemory(map);
1524 if (histogram != (double *) NULL)
1525 histogram=(double *) RelinquishMagickMemory(histogram);
1526 if (equalize_map != (double *) NULL)
1527 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1528 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1535 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1536 sizeof(*histogram));
1537 image_view=AcquireVirtualCacheView(image,exception);
1538 for (y=0; y < (ssize_t) image->rows; y++)
1540 register const Quantum
1546 if (status == MagickFalse)
1548 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1549 if (p == (const Quantum *) NULL)
1554 for (x=0; x < (ssize_t) image->columns; x++)
1559 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1560 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1561 p+=GetPixelChannels(image);
1564 image_view=DestroyCacheView(image_view);
1566 Integrate the histogram to get the equalization map.
1568 number_channels=GetPixelChannels(image);
1569 for (i=0; i < (ssize_t) number_channels; i++)
1578 for (j=0; j <= (ssize_t) MaxMap; j++)
1580 intensity+=histogram[GetPixelChannels(image)*j+i];
1581 map[GetPixelChannels(image)*j+i]=intensity;
1584 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1585 sizeof(*equalize_map));
1586 number_channels=GetPixelChannels(image);
1587 for (i=0; i < (ssize_t) number_channels; i++)
1593 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1594 if (black[i] != white[i])
1595 for (j=0; j <= (ssize_t) MaxMap; j++)
1596 equalize_map[GetPixelChannels(image)*j+i]=(double)
1597 ScaleMapToQuantum((double) ((MaxMap*(map[
1598 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1600 histogram=(double *) RelinquishMagickMemory(histogram);
1601 map=(double *) RelinquishMagickMemory(map);
1602 if (image->storage_class == PseudoClass)
1610 for (j=0; j < (ssize_t) image->colors; j++)
1612 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1614 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1615 if (black[channel] != white[channel])
1616 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1617 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1620 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1622 PixelChannel channel=GetPixelChannelChannel(image,
1624 if (black[channel] != white[channel])
1625 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1626 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1629 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1631 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1632 if (black[channel] != white[channel])
1633 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1634 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1637 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1639 PixelChannel channel=GetPixelChannelChannel(image,
1641 if (black[channel] != white[channel])
1642 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1643 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1652 image_view=AcquireAuthenticCacheView(image,exception);
1653 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1654 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1655 magick_threads(image,image,image->rows,1)
1657 for (y=0; y < (ssize_t) image->rows; y++)
1665 if (status == MagickFalse)
1667 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1668 if (q == (Quantum *) NULL)
1673 for (x=0; x < (ssize_t) image->columns; x++)
1678 if (GetPixelMask(image,q) == 0)
1680 q+=GetPixelChannels(image);
1683 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1685 PixelChannel channel=GetPixelChannelChannel(image,i);
1686 PixelTrait traits=GetPixelChannelTraits(image,channel);
1687 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1689 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1690 ScaleQuantumToMap(q[i])+i]);
1692 q+=GetPixelChannels(image);
1694 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1696 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1701 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1702 #pragma omp critical (MagickCore_EqualizeImage)
1704 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1705 if (proceed == MagickFalse)
1709 image_view=DestroyCacheView(image_view);
1710 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1715 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719 % G a m m a I m a g e %
1723 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1725 % GammaImage() gamma-corrects a particular image channel. The same
1726 % image viewed on different devices will have perceptual differences in the
1727 % way the image's intensities are represented on the screen. Specify
1728 % individual gamma levels for the red, green, and blue channels, or adjust
1729 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1731 % You can also reduce the influence of a particular channel with a gamma
1734 % The format of the GammaImage method is:
1736 % MagickBooleanType GammaImage(Image *image,const double gamma,
1737 % ExceptionInfo *exception)
1739 % A description of each parameter follows:
1741 % o image: the image.
1743 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1745 % o gamma: the image gamma.
1748 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1749 ExceptionInfo *exception)
1751 #define GammaCorrectImageTag "GammaCorrect/Image"
1772 Allocate and initialize gamma maps.
1774 assert(image != (Image *) NULL);
1775 assert(image->signature == MagickSignature);
1776 if (image->debug != MagickFalse)
1777 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1780 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1781 if (gamma_map == (Quantum *) NULL)
1782 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1784 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1786 for (i=0; i <= (ssize_t) MaxMap; i++)
1787 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1788 MaxMap,1.0/gamma)));
1789 if (image->storage_class == PseudoClass)
1790 for (i=0; i < (ssize_t) image->colors; i++)
1793 Gamma-correct colormap.
1795 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1796 image->colormap[i].red=(double) gamma_map[
1797 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
1798 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1799 image->colormap[i].green=(double) gamma_map[
1800 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
1801 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1802 image->colormap[i].blue=(double) gamma_map[
1803 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
1804 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1805 image->colormap[i].alpha=(double) gamma_map[
1806 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
1809 Gamma-correct image.
1813 image_view=AcquireAuthenticCacheView(image,exception);
1814 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1815 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1816 magick_threads(image,image,image->rows,1)
1818 for (y=0; y < (ssize_t) image->rows; y++)
1826 if (status == MagickFalse)
1828 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1829 if (q == (Quantum *) NULL)
1834 for (x=0; x < (ssize_t) image->columns; x++)
1839 if (GetPixelMask(image,q) == 0)
1841 q+=GetPixelChannels(image);
1844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1846 PixelChannel channel=GetPixelChannelChannel(image,i);
1847 PixelTrait traits=GetPixelChannelTraits(image,channel);
1848 if ((traits & UpdatePixelTrait) == 0)
1850 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1852 q+=GetPixelChannels(image);
1854 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1856 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1862 #pragma omp critical (MagickCore_GammaImage)
1864 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1866 if (proceed == MagickFalse)
1870 image_view=DestroyCacheView(image_view);
1871 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1872 if (image->gamma != 0.0)
1873 image->gamma*=gamma;
1878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1882 % G r a y s c a l e I m a g e %
1886 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1888 % GrayscaleImage() converts the image to grayscale.
1890 % The format of the GrayscaleImage method is:
1892 % MagickBooleanType GrayscaleImage(Image *image,
1893 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1895 % A description of each parameter follows:
1897 % o image: the image.
1899 % o method: the pixel intensity method.
1901 % o exception: return any errors or warnings in this structure.
1905 static inline MagickRealType MagickMax(const MagickRealType x,
1906 const MagickRealType y)
1913 static inline MagickRealType MagickMin(const MagickRealType x,
1914 const MagickRealType y)
1921 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1922 const PixelIntensityMethod grayscale,ExceptionInfo *exception)
1924 #define GrayscaleImageTag "Grayscale/Image"
1938 assert(image != (Image *) NULL);
1939 assert(image->signature == MagickSignature);
1940 if (image->debug != MagickFalse)
1941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1942 if (image->storage_class == PseudoClass)
1944 if (SyncImage(image,exception) == MagickFalse)
1945 return(MagickFalse);
1946 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1947 return(MagickFalse);
1954 image_view=AcquireAuthenticCacheView(image,exception);
1955 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1956 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1957 magick_threads(image,image,image->rows,1)
1959 for (y=0; y < (ssize_t) image->rows; y++)
1967 if (status == MagickFalse)
1969 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1970 if (q == (Quantum *) NULL)
1975 for (x=0; x < (ssize_t) image->columns; x++)
1983 if (GetPixelMask(image,q) == 0)
1985 q+=GetPixelChannels(image);
1988 red=(MagickRealType) GetPixelRed(image,q);
1989 green=(MagickRealType) GetPixelGreen(image,q);
1990 blue=(MagickRealType) GetPixelBlue(image,q);
1991 switch (image->intensity)
1993 case AveragePixelIntensityMethod:
1995 intensity=(red+green+blue)/3.0;
1998 case BrightnessPixelIntensityMethod:
2000 intensity=MagickMax(MagickMax(red,green),blue);
2003 case LightnessPixelIntensityMethod:
2005 intensity=MagickMin(MagickMin(red,green),blue);
2008 case Rec601LumaPixelIntensityMethod:
2010 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2013 case Rec601LuminancePixelIntensityMethod:
2015 if (image->colorspace == sRGBColorspace)
2017 red=DecodePixelGamma(red);
2018 green=DecodePixelGamma(green);
2019 blue=DecodePixelGamma(blue);
2021 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2024 case Rec709LumaPixelIntensityMethod:
2027 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2030 case Rec709LuminancePixelIntensityMethod:
2032 if (image->colorspace == sRGBColorspace)
2034 red=DecodePixelGamma(red);
2035 green=DecodePixelGamma(green);
2036 blue=DecodePixelGamma(blue);
2038 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2041 case RMSPixelIntensityMethod:
2043 intensity=(MagickRealType) sqrt((double) red*red+green*green+
2048 SetPixelGray(image,ClampToQuantum(intensity),q);
2049 q+=GetPixelChannels(image);
2051 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2053 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2058 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2059 #pragma omp critical (MagickCore_GrayscaleImage)
2061 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2063 if (proceed == MagickFalse)
2067 image_view=DestroyCacheView(image_view);
2072 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2076 % H a l d C l u t I m a g e %
2080 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2082 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2083 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2084 % Create it with the HALD coder. You can apply any color transformation to
2085 % the Hald image and then use this method to apply the transform to the
2088 % The format of the HaldClutImage method is:
2090 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2091 % ExceptionInfo *exception)
2093 % A description of each parameter follows:
2095 % o image: the image, which is replaced by indexed CLUT values
2097 % o hald_image: the color lookup table image for replacement color values.
2099 % o exception: return any errors or warnings in this structure.
2102 MagickExport MagickBooleanType HaldClutImage(Image *image,
2103 const Image *hald_image,ExceptionInfo *exception)
2105 #define HaldClutImageTag "Clut/Image"
2107 typedef struct _HaldInfo
2139 assert(image != (Image *) NULL);
2140 assert(image->signature == MagickSignature);
2141 if (image->debug != MagickFalse)
2142 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2143 assert(hald_image != (Image *) NULL);
2144 assert(hald_image->signature == MagickSignature);
2145 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2146 return(MagickFalse);
2147 if (image->alpha_trait != BlendPixelTrait)
2148 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2154 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2155 (MagickRealType) hald_image->rows);
2156 for (level=2; (level*level*level) < length; level++) ;
2158 cube_size=level*level;
2159 width=(double) hald_image->columns;
2160 GetPixelInfo(hald_image,&zero);
2161 hald_view=AcquireVirtualCacheView(hald_image,exception);
2162 image_view=AcquireAuthenticCacheView(image,exception);
2163 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2164 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2165 magick_threads(image,image,image->rows,1)
2167 for (y=0; y < (ssize_t) image->rows; y++)
2175 if (status == MagickFalse)
2177 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2178 if (q == (Quantum *) NULL)
2183 for (x=0; x < (ssize_t) image->columns; x++)
2198 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2199 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2200 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2201 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2202 point.x-=floor(point.x);
2203 point.y-=floor(point.y);
2204 point.z-=floor(point.z);
2206 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2207 fmod(offset,width),floor(offset/width),&pixel1,exception);
2209 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2210 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2212 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2215 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2216 fmod(offset,width),floor(offset/width),&pixel1,exception);
2217 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2218 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2220 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2223 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2225 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2226 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2227 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2228 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2229 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2230 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2231 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2232 (image->colorspace == CMYKColorspace))
2233 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2234 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2235 (image->alpha_trait == BlendPixelTrait))
2236 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2237 q+=GetPixelChannels(image);
2239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2246 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2247 #pragma omp critical (MagickCore_HaldClutImage)
2249 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2250 if (proceed == MagickFalse)
2254 hald_view=DestroyCacheView(hald_view);
2255 image_view=DestroyCacheView(image_view);
2260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2264 % L e v e l I m a g e %
2268 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2270 % LevelImage() adjusts the levels of a particular image channel by
2271 % scaling the colors falling between specified white and black points to
2272 % the full available quantum range.
2274 % The parameters provided represent the black, and white points. The black
2275 % point specifies the darkest color in the image. Colors darker than the
2276 % black point are set to zero. White point specifies the lightest color in
2277 % the image. Colors brighter than the white point are set to the maximum
2280 % If a '!' flag is given, map black and white colors to the given levels
2281 % rather than mapping those levels to black and white. See
2282 % LevelizeImage() below.
2284 % Gamma specifies a gamma correction to apply to the image.
2286 % The format of the LevelImage method is:
2288 % MagickBooleanType LevelImage(Image *image,const double black_point,
2289 % const double white_point,const double gamma,ExceptionInfo *exception)
2291 % A description of each parameter follows:
2293 % o image: the image.
2295 % o black_point: The level to map zero (black) to.
2297 % o white_point: The level to map QuantumRange (white) to.
2299 % o exception: return any errors or warnings in this structure.
2303 static inline double LevelPixel(const double black_point,
2304 const double white_point,const double gamma,const double pixel)
2310 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2311 level_pixel=(double) QuantumRange*pow(scale*((double) pixel-
2312 black_point),1.0/gamma);
2313 return(level_pixel);
2316 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2317 const double white_point,const double gamma,ExceptionInfo *exception)
2319 #define LevelImageTag "Level/Image"
2337 Allocate and initialize levels map.
2339 assert(image != (Image *) NULL);
2340 assert(image->signature == MagickSignature);
2341 if (image->debug != MagickFalse)
2342 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2343 if (image->storage_class == PseudoClass)
2344 for (i=0; i < (ssize_t) image->colors; i++)
2349 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2350 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2351 white_point,gamma,image->colormap[i].red));
2352 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2353 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2354 white_point,gamma,image->colormap[i].green));
2355 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2356 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2357 white_point,gamma,image->colormap[i].blue));
2358 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2359 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2360 white_point,gamma,image->colormap[i].alpha));
2367 image_view=AcquireAuthenticCacheView(image,exception);
2368 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2369 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2370 magick_threads(image,image,image->rows,1)
2372 for (y=0; y < (ssize_t) image->rows; y++)
2380 if (status == MagickFalse)
2382 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2383 if (q == (Quantum *) NULL)
2388 for (x=0; x < (ssize_t) image->columns; x++)
2393 if (GetPixelMask(image,q) == 0)
2395 q+=GetPixelChannels(image);
2398 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2400 PixelChannel channel=GetPixelChannelChannel(image,i);
2401 PixelTrait traits=GetPixelChannelTraits(image,channel);
2402 if ((traits & UpdatePixelTrait) == 0)
2404 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2407 q+=GetPixelChannels(image);
2409 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2411 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2416 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2417 #pragma omp critical (MagickCore_LevelImage)
2419 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2420 if (proceed == MagickFalse)
2424 image_view=DestroyCacheView(image_view);
2429 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2433 % L e v e l i z e I m a g e %
2437 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2439 % LevelizeImage() applies the reversed LevelImage() operation to just
2440 % the specific channels specified. It compresses the full range of color
2441 % values, so that they lie between the given black and white points. Gamma is
2442 % applied before the values are mapped.
2444 % LevelizeImage() can be called with by using a +level command line
2445 % API option, or using a '!' on a -level or LevelImage() geometry string.
2447 % It can be used to de-contrast a greyscale image to the exact levels
2448 % specified. Or by using specific levels for each channel of an image you
2449 % can convert a gray-scale image to any linear color gradient, according to
2452 % The format of the LevelizeImage method is:
2454 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2455 % const double white_point,const double gamma,ExceptionInfo *exception)
2457 % A description of each parameter follows:
2459 % o image: the image.
2461 % o black_point: The level to map zero (black) to.
2463 % o white_point: The level to map QuantumRange (white) to.
2465 % o gamma: adjust gamma by this factor before mapping values.
2467 % o exception: return any errors or warnings in this structure.
2470 MagickExport MagickBooleanType LevelizeImage(Image *image,
2471 const double black_point,const double white_point,const double gamma,
2472 ExceptionInfo *exception)
2474 #define LevelizeImageTag "Levelize/Image"
2475 #define LevelizeValue(x) (ClampToQuantum((pow((double) (QuantumScale*(x)), \
2476 1.0/gamma))*(white_point-black_point)+black_point))
2494 Allocate and initialize levels map.
2496 assert(image != (Image *) NULL);
2497 assert(image->signature == MagickSignature);
2498 if (image->debug != MagickFalse)
2499 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2500 if (image->storage_class == PseudoClass)
2501 for (i=0; i < (ssize_t) image->colors; i++)
2506 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2507 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2508 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2509 image->colormap[i].green=(double) LevelizeValue(
2510 image->colormap[i].green);
2511 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2512 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2513 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2514 image->colormap[i].alpha=(double) LevelizeValue(
2515 image->colormap[i].alpha);
2522 image_view=AcquireAuthenticCacheView(image,exception);
2523 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2524 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2525 magick_threads(image,image,image->rows,1)
2527 for (y=0; y < (ssize_t) image->rows; y++)
2535 if (status == MagickFalse)
2537 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2538 if (q == (Quantum *) NULL)
2543 for (x=0; x < (ssize_t) image->columns; x++)
2548 if (GetPixelMask(image,q) == 0)
2550 q+=GetPixelChannels(image);
2553 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2555 PixelChannel channel=GetPixelChannelChannel(image,i);
2556 PixelTrait traits=GetPixelChannelTraits(image,channel);
2557 if ((traits & UpdatePixelTrait) == 0)
2559 q[i]=LevelizeValue(q[i]);
2561 q+=GetPixelChannels(image);
2563 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2565 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2570 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2571 #pragma omp critical (MagickCore_LevelizeImage)
2573 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2574 if (proceed == MagickFalse)
2578 image_view=DestroyCacheView(image_view);
2583 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2587 % L e v e l I m a g e C o l o r s %
2591 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2593 % LevelImageColors() maps the given color to "black" and "white" values,
2594 % linearly spreading out the colors, and level values on a channel by channel
2595 % bases, as per LevelImage(). The given colors allows you to specify
2596 % different level ranges for each of the color channels separately.
2598 % If the boolean 'invert' is set true the image values will modifyed in the
2599 % reverse direction. That is any existing "black" and "white" colors in the
2600 % image will become the color values given, with all other values compressed
2601 % appropriatally. This effectivally maps a greyscale gradient into the given
2604 % The format of the LevelImageColors method is:
2606 % MagickBooleanType LevelImageColors(Image *image,
2607 % const PixelInfo *black_color,const PixelInfo *white_color,
2608 % const MagickBooleanType invert,ExceptionInfo *exception)
2610 % A description of each parameter follows:
2612 % o image: the image.
2614 % o black_color: The color to map black to/from
2616 % o white_point: The color to map white to/from
2618 % o invert: if true map the colors (levelize), rather than from (level)
2620 % o exception: return any errors or warnings in this structure.
2623 MagickExport MagickBooleanType LevelImageColors(Image *image,
2624 const PixelInfo *black_color,const PixelInfo *white_color,
2625 const MagickBooleanType invert,ExceptionInfo *exception)
2634 Allocate and initialize levels map.
2636 assert(image != (Image *) NULL);
2637 assert(image->signature == MagickSignature);
2638 if (image->debug != MagickFalse)
2639 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2641 if (invert == MagickFalse)
2643 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2645 channel_mask=SetImageChannelMask(image,RedChannel);
2646 status|=LevelImage(image,black_color->red,white_color->red,1.0,
2648 (void) SetImageChannelMask(image,channel_mask);
2650 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2652 channel_mask=SetImageChannelMask(image,GreenChannel);
2653 status|=LevelImage(image,black_color->green,white_color->green,1.0,
2655 (void) SetImageChannelMask(image,channel_mask);
2657 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2659 channel_mask=SetImageChannelMask(image,BlueChannel);
2660 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2662 (void) SetImageChannelMask(image,channel_mask);
2664 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2665 (image->colorspace == CMYKColorspace))
2667 channel_mask=SetImageChannelMask(image,BlackChannel);
2668 status|=LevelImage(image,black_color->black,white_color->black,1.0,
2670 (void) SetImageChannelMask(image,channel_mask);
2672 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2673 (image->alpha_trait == BlendPixelTrait))
2675 channel_mask=SetImageChannelMask(image,AlphaChannel);
2676 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2678 (void) SetImageChannelMask(image,channel_mask);
2683 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2685 channel_mask=SetImageChannelMask(image,RedChannel);
2686 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2688 (void) SetImageChannelMask(image,channel_mask);
2690 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2692 channel_mask=SetImageChannelMask(image,GreenChannel);
2693 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2695 (void) SetImageChannelMask(image,channel_mask);
2697 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2699 channel_mask=SetImageChannelMask(image,BlueChannel);
2700 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2702 (void) SetImageChannelMask(image,channel_mask);
2704 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2705 (image->colorspace == CMYKColorspace))
2707 channel_mask=SetImageChannelMask(image,BlackChannel);
2708 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2710 (void) SetImageChannelMask(image,channel_mask);
2712 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2713 (image->alpha_trait == BlendPixelTrait))
2715 channel_mask=SetImageChannelMask(image,AlphaChannel);
2716 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2718 (void) SetImageChannelMask(image,channel_mask);
2721 return(status == 0 ? MagickFalse : MagickTrue);
2725 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2729 % L i n e a r S t r e t c h I m a g e %
2733 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2735 % LinearStretchImage() discards any pixels below the black point and above
2736 % the white point and levels the remaining pixels.
2738 % The format of the LinearStretchImage method is:
2740 % MagickBooleanType LinearStretchImage(Image *image,
2741 % const double black_point,const double white_point,
2742 % ExceptionInfo *exception)
2744 % A description of each parameter follows:
2746 % o image: the image.
2748 % o black_point: the black point.
2750 % o white_point: the white point.
2752 % o exception: return any errors or warnings in this structure.
2755 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2756 const double black_point,const double white_point,ExceptionInfo *exception)
2758 #define LinearStretchImageTag "LinearStretch/Image"
2776 Allocate histogram and linear map.
2778 assert(image != (Image *) NULL);
2779 assert(image->signature == MagickSignature);
2780 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2781 if (histogram == (double *) NULL)
2782 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2787 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2788 image_view=AcquireVirtualCacheView(image,exception);
2789 for (y=0; y < (ssize_t) image->rows; y++)
2791 register const Quantum
2797 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2798 if (p == (const Quantum *) NULL)
2800 for (x=0; x < (ssize_t) image->columns; x++)
2805 intensity=GetPixelIntensity(image,p);
2806 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2807 p+=GetPixelChannels(image);
2810 image_view=DestroyCacheView(image_view);
2812 Find the histogram boundaries by locating the black and white point levels.
2815 for (black=0; black < (ssize_t) MaxMap; black++)
2817 intensity+=histogram[black];
2818 if (intensity >= black_point)
2822 for (white=(ssize_t) MaxMap; white != 0; white--)
2824 intensity+=histogram[white];
2825 if (intensity >= white_point)
2828 histogram=(double *) RelinquishMagickMemory(histogram);
2829 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2834 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2838 % M o d u l a t e I m a g e %
2842 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2844 % ModulateImage() lets you control the brightness, saturation, and hue
2845 % of an image. Modulate represents the brightness, saturation, and hue
2846 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2847 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2848 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2850 % The format of the ModulateImage method is:
2852 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2853 % ExceptionInfo *exception)
2855 % A description of each parameter follows:
2857 % o image: the image.
2859 % o modulate: Define the percent change in brightness, saturation, and hue.
2861 % o exception: return any errors or warnings in this structure.
2865 static inline void ModulateHCL(const double percent_hue,
2866 const double percent_chroma,const double percent_luma,double *red,
2867 double *green,double *blue)
2875 Increase or decrease color luma, chroma, or hue.
2877 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2878 hue+=0.5*(0.01*percent_hue-1.0);
2883 chroma*=0.01*percent_chroma;
2884 luma*=0.01*percent_luma;
2885 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2888 static inline void ModulateHSB(const double percent_hue,
2889 const double percent_saturation,const double percent_brightness,double *red,
2890 double *green,double *blue)
2898 Increase or decrease color brightness, saturation, or hue.
2900 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2901 hue+=0.5*(0.01*percent_hue-1.0);
2906 saturation*=0.01*percent_saturation;
2907 brightness*=0.01*percent_brightness;
2908 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2911 static inline void ModulateHSL(const double percent_hue,
2912 const double percent_saturation,const double percent_lightness,double *red,
2913 double *green,double *blue)
2921 Increase or decrease color lightness, saturation, or hue.
2923 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2924 hue+=0.5*(0.01*percent_hue-1.0);
2929 saturation*=0.01*percent_saturation;
2930 lightness*=0.01*percent_lightness;
2931 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2934 static inline void ModulateHWB(const double percent_hue,
2935 const double percent_whiteness,const double percent_blackness,double *red,
2936 double *green,double *blue)
2944 Increase or decrease color blackness, whiteness, or hue.
2946 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2947 hue+=0.5*(0.01*percent_hue-1.0);
2952 blackness*=0.01*percent_blackness;
2953 whiteness*=0.01*percent_whiteness;
2954 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2957 static inline void ModulateLCH(const double percent_luma,
2958 const double percent_chroma,const double percent_hue,double *red,
2959 double *green,double *blue)
2967 Increase or decrease color luma, chroma, or hue.
2969 ConvertRGBToLCH(*red,*green,*blue,&luma,&chroma,&hue);
2970 luma*=0.01*percent_luma;
2971 chroma*=0.01*percent_chroma;
2972 hue+=0.5*(0.01*percent_hue-1.0);
2977 ConvertHCLToRGB(luma,chroma,hue,red,green,blue);
2980 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2981 ExceptionInfo *exception)
2983 #define ModulateImageTag "Modulate/Image"
3018 Initialize modulate table.
3020 assert(image != (Image *) NULL);
3021 assert(image->signature == MagickSignature);
3022 if (image->debug != MagickFalse)
3023 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3024 if (modulate == (char *) NULL)
3025 return(MagickFalse);
3026 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3027 (void) TransformImageColorspace(image,sRGBColorspace,exception);
3028 flags=ParseGeometry(modulate,&geometry_info);
3029 percent_brightness=geometry_info.rho;
3030 percent_saturation=geometry_info.sigma;
3031 if ((flags & SigmaValue) == 0)
3032 percent_saturation=100.0;
3033 percent_hue=geometry_info.xi;
3034 if ((flags & XiValue) == 0)
3036 colorspace=UndefinedColorspace;
3037 artifact=GetImageArtifact(image,"modulate:colorspace");
3038 if (artifact != (const char *) NULL)
3039 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3040 MagickFalse,artifact);
3041 if (image->storage_class == PseudoClass)
3042 for (i=0; i < (ssize_t) image->colors; i++)
3050 Modulate image colormap.
3052 red=(double) image->colormap[i].red;
3053 green=(double) image->colormap[i].green;
3054 blue=(double) image->colormap[i].blue;
3055 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3057 red=DecodePixelGamma((MagickRealType) red);
3058 green=DecodePixelGamma((MagickRealType) green);
3059 blue=DecodePixelGamma((MagickRealType) blue);
3065 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3071 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3078 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3084 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3090 ModulateLCH(percent_brightness,percent_saturation,percent_hue,
3095 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3097 red=EncodePixelGamma(red);
3098 green=EncodePixelGamma(green);
3099 blue=EncodePixelGamma(blue);
3101 image->colormap[i].red=red;
3102 image->colormap[i].green=green;
3103 image->colormap[i].blue=blue;
3110 image_view=AcquireAuthenticCacheView(image,exception);
3111 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3112 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3113 magick_threads(image,image,image->rows,1)
3115 for (y=0; y < (ssize_t) image->rows; y++)
3123 if (status == MagickFalse)
3125 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3126 if (q == (Quantum *) NULL)
3131 for (x=0; x < (ssize_t) image->columns; x++)
3138 red=(double) GetPixelRed(image,q);
3139 green=(double) GetPixelGreen(image,q);
3140 blue=(double) GetPixelBlue(image,q);
3141 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3143 red=DecodePixelGamma((MagickRealType) red);
3144 green=DecodePixelGamma((MagickRealType) green);
3145 blue=DecodePixelGamma((MagickRealType) blue);
3151 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3157 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3164 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3170 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3176 ModulateLCH(percent_brightness,percent_saturation,percent_hue,
3181 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3183 red=EncodePixelGamma(red);
3184 green=EncodePixelGamma(green);
3185 blue=EncodePixelGamma(blue);
3187 SetPixelRed(image,ClampToQuantum(red),q);
3188 SetPixelGreen(image,ClampToQuantum(green),q);
3189 SetPixelBlue(image,ClampToQuantum(blue),q);
3190 q+=GetPixelChannels(image);
3192 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3194 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3200 #pragma omp critical (MagickCore_ModulateImage)
3202 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3203 if (proceed == MagickFalse)
3207 image_view=DestroyCacheView(image_view);
3212 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3216 % N e g a t e I m a g e %
3220 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3222 % NegateImage() negates the colors in the reference image. The grayscale
3223 % option means that only grayscale values within the image are negated.
3225 % The format of the NegateImage method is:
3227 % MagickBooleanType NegateImage(Image *image,
3228 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3230 % A description of each parameter follows:
3232 % o image: the image.
3234 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3236 % o exception: return any errors or warnings in this structure.
3239 MagickExport MagickBooleanType NegateImage(Image *image,
3240 const MagickBooleanType grayscale,ExceptionInfo *exception)
3242 #define NegateImageTag "Negate/Image"
3259 assert(image != (Image *) NULL);
3260 assert(image->signature == MagickSignature);
3261 if (image->debug != MagickFalse)
3262 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3263 if (image->storage_class == PseudoClass)
3264 for (i=0; i < (ssize_t) image->colors; i++)
3269 if (grayscale != MagickFalse)
3270 if ((image->colormap[i].red != image->colormap[i].green) ||
3271 (image->colormap[i].green != image->colormap[i].blue))
3273 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3274 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3275 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3276 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3277 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3278 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3285 image_view=AcquireAuthenticCacheView(image,exception);
3286 if (grayscale != MagickFalse)
3288 for (y=0; y < (ssize_t) image->rows; y++)
3299 if (status == MagickFalse)
3301 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3303 if (q == (Quantum *) NULL)
3308 for (x=0; x < (ssize_t) image->columns; x++)
3313 if ((GetPixelMask(image,q) == 0) ||
3314 (IsPixelGray(image,q) != MagickFalse))
3316 q+=GetPixelChannels(image);
3319 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3321 PixelChannel channel=GetPixelChannelChannel(image,i);
3322 PixelTrait traits=GetPixelChannelTraits(image,channel);
3323 if ((traits & UpdatePixelTrait) == 0)
3325 q[i]=QuantumRange-q[i];
3327 q+=GetPixelChannels(image);
3329 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3330 if (sync == MagickFalse)
3332 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3337 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3338 #pragma omp critical (MagickCore_NegateImage)
3340 proceed=SetImageProgress(image,NegateImageTag,progress++,
3342 if (proceed == MagickFalse)
3346 image_view=DestroyCacheView(image_view);
3352 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3353 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3354 magick_threads(image,image,image->rows,1)
3356 for (y=0; y < (ssize_t) image->rows; y++)
3364 if (status == MagickFalse)
3366 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3367 if (q == (Quantum *) NULL)
3372 for (x=0; x < (ssize_t) image->columns; x++)
3377 if (GetPixelMask(image,q) == 0)
3379 q+=GetPixelChannels(image);
3382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3384 PixelChannel channel=GetPixelChannelChannel(image,i);
3385 PixelTrait traits=GetPixelChannelTraits(image,channel);
3386 if ((traits & UpdatePixelTrait) == 0)
3388 q[i]=QuantumRange-q[i];
3390 q+=GetPixelChannels(image);
3392 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3394 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3399 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3400 #pragma omp critical (MagickCore_NegateImage)
3402 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3403 if (proceed == MagickFalse)
3407 image_view=DestroyCacheView(image_view);
3412 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3416 % N o r m a l i z e I m a g e %
3420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3422 % The NormalizeImage() method enhances the contrast of a color image by
3423 % mapping the darkest 2 percent of all pixel to black and the brightest
3424 % 1 percent to white.
3426 % The format of the NormalizeImage method is:
3428 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3430 % A description of each parameter follows:
3432 % o image: the image.
3434 % o exception: return any errors or warnings in this structure.
3437 MagickExport MagickBooleanType NormalizeImage(Image *image,
3438 ExceptionInfo *exception)
3444 black_point=(double) image->columns*image->rows*0.0015;
3445 white_point=(double) image->columns*image->rows*0.9995;
3446 return(ContrastStretchImage(image,black_point,white_point,exception));
3450 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3454 % S i g m o i d a l C o n t r a s t I m a g e %
3458 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3460 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3461 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3462 % sigmoidal transfer function without saturating highlights or shadows.
3463 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3464 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3465 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3466 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3469 % The format of the SigmoidalContrastImage method is:
3471 % MagickBooleanType SigmoidalContrastImage(Image *image,
3472 % const MagickBooleanType sharpen,const char *levels,
3473 % ExceptionInfo *exception)
3475 % A description of each parameter follows:
3477 % o image: the image.
3479 % o sharpen: Increase or decrease image contrast.
3481 % o contrast: strength of the contrast, the larger the number the more
3482 % 'threshold-like' it becomes.
3484 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3486 % o exception: return any errors or warnings in this structure.
3491 ImageMagick 6 has a version of this function which uses LUTs.
3495 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3498 The first version, based on the hyperbolic tangent tanh, when combined with
3499 the scaling step, is an exact arithmetic clone of the the sigmoid function
3500 based on the logistic curve. The equivalence is based on the identity
3502 1/(1+exp(-t)) = (1+tanh(t/2))/2
3504 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3505 scaled sigmoidal derivation is invariant under affine transformations of
3508 The tanh version is almost certainly more accurate and cheaper. The 0.5
3509 factor in the argument is to clone the legacy ImageMagick behavior. The
3510 reason for making the define depend on atanh even though it only uses tanh
3511 has to do with the construction of the inverse of the scaled sigmoidal.
3513 #if defined(MAGICKCORE_HAVE_ATANH)
3514 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3516 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3519 Scaled sigmoidal function:
3521 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3522 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3524 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3525 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3526 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3527 zero. This is fixed below by exiting immediately when contrast is small,
3528 leaving the image (or colormap) unmodified. This appears to be safe because
3529 the series expansion of the logistic sigmoidal function around x=b is
3533 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3535 #define ScaledSigmoidal(a,b,x) ( \
3536 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3537 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3539 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3540 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3541 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3542 when creating a LUT from in gamut values, hence the branching. In
3543 addition, HDRI may have out of gamut values.
3544 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3545 It is only a right inverse. This is unavoidable.
3547 static inline double InverseScaledSigmoidal(const double a,const double b,
3550 const double sig0=Sigmoidal(a,b,0.0);
3551 const double sig1=Sigmoidal(a,b,1.0);
3552 const double argument=(sig1-sig0)*x+sig0;
3553 const double clamped=
3555 #if defined(MAGICKCORE_HAVE_ATANH)
3556 argument < -1+MagickEpsilon
3560 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3562 return(b+(2.0/a)*atanh(clamped));
3564 argument < MagickEpsilon
3568 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3570 return(b-log(1.0/clamped-1.0)/a);
3574 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3575 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3576 ExceptionInfo *exception)
3578 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3579 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3580 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3581 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3582 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3599 assert(image != (Image *) NULL);
3600 assert(image->signature == MagickSignature);
3601 if (image->debug != MagickFalse)
3602 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3604 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3605 case nothing is done.
3607 if (contrast < MagickEpsilon)
3610 Sigmoidal-contrast enhance colormap.
3612 if (image->storage_class == PseudoClass)
3617 if (sharpen != MagickFalse)
3618 for (i=0; i < (ssize_t) image->colors; i++)
3620 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3621 image->colormap[i].red=(MagickRealType) ScaledSig(
3622 image->colormap[i].red);
3623 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3624 image->colormap[i].green=(MagickRealType) ScaledSig(
3625 image->colormap[i].green);
3626 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3627 image->colormap[i].blue=(MagickRealType) ScaledSig(
3628 image->colormap[i].blue);
3629 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3630 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3631 image->colormap[i].alpha);
3634 for (i=0; i < (ssize_t) image->colors; i++)
3636 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3637 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3638 image->colormap[i].red);
3639 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3640 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3641 image->colormap[i].green);
3642 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3643 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3644 image->colormap[i].blue);
3645 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3646 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3647 image->colormap[i].alpha);
3651 Sigmoidal-contrast enhance image.
3655 image_view=AcquireAuthenticCacheView(image,exception);
3656 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3657 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3658 magick_threads(image,image,image->rows,1)
3660 for (y=0; y < (ssize_t) image->rows; y++)
3668 if (status == MagickFalse)
3670 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3671 if (q == (Quantum *) NULL)
3676 for (x=0; x < (ssize_t) image->columns; x++)
3681 if (GetPixelMask(image,q) == 0)
3683 q+=GetPixelChannels(image);
3686 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3688 PixelChannel channel=GetPixelChannelChannel(image,i);
3689 PixelTrait traits=GetPixelChannelTraits(image,channel);
3690 if ((traits & UpdatePixelTrait) == 0)
3692 if (sharpen != MagickFalse)
3693 q[i]=ScaledSig(q[i]);
3695 q[i]=InverseScaledSig(q[i]);
3697 q+=GetPixelChannels(image);
3699 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3701 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3706 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3707 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3709 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3711 if (proceed == MagickFalse)
3715 image_view=DestroyCacheView(image_view);