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-2015 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/channel.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/colorspace.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/composite-private.h"
54 #include "MagickCore/enhance.h"
55 #include "MagickCore/exception.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/fx.h"
58 #include "MagickCore/gem.h"
59 #include "MagickCore/gem-private.h"
60 #include "MagickCore/geometry.h"
61 #include "MagickCore/histogram.h"
62 #include "MagickCore/image.h"
63 #include "MagickCore/image-private.h"
64 #include "MagickCore/memory_.h"
65 #include "MagickCore/monitor.h"
66 #include "MagickCore/monitor-private.h"
67 #include "MagickCore/option.h"
68 #include "MagickCore/pixel.h"
69 #include "MagickCore/pixel-accessor.h"
70 #include "MagickCore/quantum.h"
71 #include "MagickCore/quantum-private.h"
72 #include "MagickCore/resample.h"
73 #include "MagickCore/resample-private.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/statistic.h"
76 #include "MagickCore/string_.h"
77 #include "MagickCore/string-private.h"
78 #include "MagickCore/thread-private.h"
79 #include "MagickCore/threshold.h"
80 #include "MagickCore/token.h"
81 #include "MagickCore/xml-tree.h"
82 #include "MagickCore/xml-tree-private.h"
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89 % A u t o G a m m a I m a g e %
93 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 % AutoGammaImage() extract the 'mean' from the image and adjust the image
96 % to try make set its gamma appropriatally.
98 % The format of the AutoGammaImage method is:
100 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
102 % A description of each parameter follows:
104 % o image: The image to auto-level
106 % o exception: return any errors or warnings in this structure.
109 MagickExport MagickBooleanType AutoGammaImage(Image *image,
110 ExceptionInfo *exception)
125 if (image->channel_mask == DefaultChannels)
128 Apply gamma correction equally across all given channels.
130 (void) GetImageMean(image,&mean,&sans,exception);
131 gamma=log(mean*QuantumScale)/log_mean;
132 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
135 Auto-gamma each channel separately.
138 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
143 PixelChannel channel=GetPixelChannelChannel(image,i);
144 PixelTrait traits=GetPixelChannelTraits(image,channel);
145 if ((traits & UpdatePixelTrait) == 0)
147 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
148 status=GetImageMean(image,&mean,&sans,exception);
149 gamma=log(mean*QuantumScale)/log_mean;
150 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
151 (void) SetImageChannelMask(image,channel_mask);
152 if (status == MagickFalse)
155 return(status != 0 ? MagickTrue : MagickFalse);
159 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
163 % A u t o L e v e l I m a g e %
167 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169 % AutoLevelImage() adjusts the levels of a particular image channel by
170 % scaling the minimum and maximum values to the full quantum range.
172 % The format of the LevelImage method is:
174 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
176 % A description of each parameter follows:
178 % o image: The image to auto-level
180 % o exception: return any errors or warnings in this structure.
183 MagickExport MagickBooleanType AutoLevelImage(Image *image,
184 ExceptionInfo *exception)
186 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194 % B r i g h t n e s s C o n t r a s t I m a g e %
198 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200 % BrightnessContrastImage() changes the brightness and/or contrast of an
201 % image. It converts the brightness and contrast parameters into slope and
202 % intercept and calls a polynomical function to apply to the image.
204 % The format of the BrightnessContrastImage method is:
206 % MagickBooleanType BrightnessContrastImage(Image *image,
207 % const double brightness,const double contrast,ExceptionInfo *exception)
209 % A description of each parameter follows:
211 % o image: the image.
213 % o brightness: the brightness percent (-100 .. 100).
215 % o contrast: the contrast percent (-100 .. 100).
217 % o exception: return any errors or warnings in this structure.
220 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
221 const double brightness,const double contrast,ExceptionInfo *exception)
223 #define BrightnessContastImageTag "BrightnessContast/Image"
235 Compute slope and intercept.
237 assert(image != (Image *) NULL);
238 assert(image->signature == MagickSignature);
239 if (image->debug != MagickFalse)
240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
242 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
245 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
246 coefficients[0]=slope;
247 coefficients[1]=intercept;
248 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
253 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
257 % C l u t I m a g e %
261 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 % ClutImage() replaces each color value in the given image, by using it as an
264 % index to lookup a replacement color value in a Color Look UP Table in the
265 % form of an image. The values are extracted along a diagonal of the CLUT
266 % image so either a horizontal or vertial gradient image can be used.
268 % Typically this is used to either re-color a gray-scale image according to a
269 % color gradient in the CLUT image, or to perform a freeform histogram
270 % (level) adjustment according to the (typically gray-scale) gradient in the
273 % When the 'channel' mask includes the matte/alpha transparency channel but
274 % one image has no such channel it is assumed that that image is a simple
275 % gray-scale image that will effect the alpha channel values, either for
276 % gray-scale coloring (with transparent or semi-transparent colors), or
277 % a histogram adjustment of existing alpha channel values. If both images
278 % have matte channels, direct and normal indexing is applied, which is rarely
281 % The format of the ClutImage method is:
283 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
284 % const PixelInterpolateMethod method,ExceptionInfo *exception)
286 % A description of each parameter follows:
288 % o image: the image, which is replaced by indexed CLUT values
290 % o clut_image: the color lookup table image for replacement color values.
292 % o method: the pixel interpolation method.
294 % o exception: return any errors or warnings in this structure.
297 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
298 const PixelInterpolateMethod method,ExceptionInfo *exception)
300 #define ClutImageTag "Clut/Image"
321 assert(image != (Image *) NULL);
322 assert(image->signature == MagickSignature);
323 if (image->debug != MagickFalse)
324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
325 assert(clut_image != (Image *) NULL);
326 assert(clut_image->signature == MagickSignature);
327 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
329 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
330 IfMagickFalse(IsGrayColorspace(clut_image->colorspace)))
331 (void) SetImageColorspace(image,sRGBColorspace,exception);
332 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
333 if (clut_map == (PixelInfo *) NULL)
334 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
341 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
342 clut_view=AcquireVirtualCacheView(clut_image,exception);
343 for (i=0; i <= (ssize_t) MaxMap; i++)
345 GetPixelInfo(clut_image,clut_map+i);
346 (void) InterpolatePixelInfo(clut_image,clut_view,method,
347 (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
348 (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
350 clut_view=DestroyCacheView(clut_view);
351 image_view=AcquireAuthenticCacheView(image,exception);
352 #if defined(MAGICKCORE_OPENMP_SUPPORT)
353 #pragma omp parallel for schedule(static,4) shared(progress,status) \
354 magick_threads(image,image,image->rows,1)
356 for (y=0; y < (ssize_t) image->rows; y++)
367 if (status == MagickFalse)
369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
370 if (q == (Quantum *) NULL)
375 GetPixelInfo(image,&pixel);
376 for (x=0; x < (ssize_t) image->columns; x++)
381 if (GetPixelReadMask(image,q) == 0)
383 q+=GetPixelChannels(image);
386 GetPixelInfoPixel(image,q,&pixel);
387 traits=GetPixelChannelTraits(image,RedPixelChannel);
388 if ((traits & UpdatePixelTrait) != 0)
389 pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
391 traits=GetPixelChannelTraits(image,GreenPixelChannel);
392 if ((traits & UpdatePixelTrait) != 0)
393 pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
394 pixel.green))].green;
395 traits=GetPixelChannelTraits(image,BluePixelChannel);
396 if ((traits & UpdatePixelTrait) != 0)
397 pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
399 traits=GetPixelChannelTraits(image,BlackPixelChannel);
400 if ((traits & UpdatePixelTrait) != 0)
401 pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
402 pixel.black))].black;
403 traits=GetPixelChannelTraits(image,AlphaPixelChannel);
404 if ((traits & UpdatePixelTrait) != 0)
405 pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
406 pixel.alpha))].alpha;
407 SetPixelViaPixelInfo(image,&pixel,q);
408 q+=GetPixelChannels(image);
410 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
412 if (image->progress_monitor != (MagickProgressMonitor) NULL)
417 #if defined(MAGICKCORE_OPENMP_SUPPORT)
418 #pragma omp critical (MagickCore_ClutImage)
420 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
421 if( IfMagickFalse(proceed) )
425 image_view=DestroyCacheView(image_view);
426 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
427 if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
428 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
429 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
434 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438 % C o l o r D e c i s i o n L i s t I m a g e %
442 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
445 % (CCC) file which solely contains one or more color corrections and applies
446 % the correction to the image. Here is a sample CCC file:
448 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
449 % <ColorCorrection id="cc03345">
451 % <Slope> 0.9 1.2 0.5 </Slope>
452 % <Offset> 0.4 -0.5 0.6 </Offset>
453 % <Power> 1.0 0.8 1.5 </Power>
456 % <Saturation> 0.85 </Saturation>
459 % </ColorCorrectionCollection>
461 % which includes the slop, offset, and power for each of the RGB channels
462 % as well as the saturation.
464 % The format of the ColorDecisionListImage method is:
466 % MagickBooleanType ColorDecisionListImage(Image *image,
467 % const char *color_correction_collection,ExceptionInfo *exception)
469 % A description of each parameter follows:
471 % o image: the image.
473 % o color_correction_collection: the color correction collection in XML.
475 % o exception: return any errors or warnings in this structure.
478 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
479 const char *color_correction_collection,ExceptionInfo *exception)
481 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
483 typedef struct _Correction
491 typedef struct _ColorCorrection
506 token[MaxTextExtent];
537 Allocate and initialize cdl maps.
539 assert(image != (Image *) NULL);
540 assert(image->signature == MagickSignature);
541 if (image->debug != MagickFalse)
542 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
543 if (color_correction_collection == (const char *) NULL)
545 ccc=NewXMLTree((const char *) color_correction_collection,exception);
546 if (ccc == (XMLTreeInfo *) NULL)
548 cc=GetXMLTreeChild(ccc,"ColorCorrection");
549 if (cc == (XMLTreeInfo *) NULL)
551 ccc=DestroyXMLTree(ccc);
554 color_correction.red.slope=1.0;
555 color_correction.red.offset=0.0;
556 color_correction.red.power=1.0;
557 color_correction.green.slope=1.0;
558 color_correction.green.offset=0.0;
559 color_correction.green.power=1.0;
560 color_correction.blue.slope=1.0;
561 color_correction.blue.offset=0.0;
562 color_correction.blue.power=1.0;
563 color_correction.saturation=0.0;
564 sop=GetXMLTreeChild(cc,"SOPNode");
565 if (sop != (XMLTreeInfo *) NULL)
572 slope=GetXMLTreeChild(sop,"Slope");
573 if (slope != (XMLTreeInfo *) NULL)
575 content=GetXMLTreeContent(slope);
576 p=(const char *) content;
577 for (i=0; (*p != '\0') && (i < 3); i++)
579 GetMagickToken(p,&p,token);
581 GetMagickToken(p,&p,token);
586 color_correction.red.slope=StringToDouble(token,(char **) NULL);
591 color_correction.green.slope=StringToDouble(token,
597 color_correction.blue.slope=StringToDouble(token,
604 offset=GetXMLTreeChild(sop,"Offset");
605 if (offset != (XMLTreeInfo *) NULL)
607 content=GetXMLTreeContent(offset);
608 p=(const char *) content;
609 for (i=0; (*p != '\0') && (i < 3); i++)
611 GetMagickToken(p,&p,token);
613 GetMagickToken(p,&p,token);
618 color_correction.red.offset=StringToDouble(token,
624 color_correction.green.offset=StringToDouble(token,
630 color_correction.blue.offset=StringToDouble(token,
637 power=GetXMLTreeChild(sop,"Power");
638 if (power != (XMLTreeInfo *) NULL)
640 content=GetXMLTreeContent(power);
641 p=(const char *) content;
642 for (i=0; (*p != '\0') && (i < 3); i++)
644 GetMagickToken(p,&p,token);
646 GetMagickToken(p,&p,token);
651 color_correction.red.power=StringToDouble(token,(char **) NULL);
656 color_correction.green.power=StringToDouble(token,
662 color_correction.blue.power=StringToDouble(token,
670 sat=GetXMLTreeChild(cc,"SATNode");
671 if (sat != (XMLTreeInfo *) NULL)
676 saturation=GetXMLTreeChild(sat,"Saturation");
677 if (saturation != (XMLTreeInfo *) NULL)
679 content=GetXMLTreeContent(saturation);
680 p=(const char *) content;
681 GetMagickToken(p,&p,token);
682 color_correction.saturation=StringToDouble(token,(char **) NULL);
685 ccc=DestroyXMLTree(ccc);
686 if (image->debug != MagickFalse)
688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
689 " Color Correction Collection:");
690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
691 " color_correction.red.slope: %g",color_correction.red.slope);
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " color_correction.red.offset: %g",color_correction.red.offset);
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.red.power: %g",color_correction.red.power);
696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
697 " color_correction.green.slope: %g",color_correction.green.slope);
698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
699 " color_correction.green.offset: %g",color_correction.green.offset);
700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
701 " color_correction.green.power: %g",color_correction.green.power);
702 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
703 " color_correction.blue.slope: %g",color_correction.blue.slope);
704 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
705 " color_correction.blue.offset: %g",color_correction.blue.offset);
706 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
707 " color_correction.blue.power: %g",color_correction.blue.power);
708 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
709 " color_correction.saturation: %g",color_correction.saturation);
711 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
712 if (cdl_map == (PixelInfo *) NULL)
713 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
715 for (i=0; i <= (ssize_t) MaxMap; i++)
717 cdl_map[i].red=(double) ScaleMapToQuantum((double)
718 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
719 color_correction.red.offset,color_correction.red.power))));
720 cdl_map[i].green=(double) ScaleMapToQuantum((double)
721 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
722 color_correction.green.offset,color_correction.green.power))));
723 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
724 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
725 color_correction.blue.offset,color_correction.blue.power))));
727 if (image->storage_class == PseudoClass)
728 for (i=0; i < (ssize_t) image->colors; i++)
731 Apply transfer function to colormap.
736 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
737 0.07217f*image->colormap[i].blue;
738 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
739 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
740 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
741 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
742 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
743 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
746 Apply transfer function to image.
750 image_view=AcquireAuthenticCacheView(image,exception);
751 #if defined(MAGICKCORE_OPENMP_SUPPORT)
752 #pragma omp parallel for schedule(static,4) shared(progress,status) \
753 magick_threads(image,image,image->rows,1)
755 for (y=0; y < (ssize_t) image->rows; y++)
766 if (status == MagickFalse)
768 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
769 if (q == (Quantum *) NULL)
774 for (x=0; x < (ssize_t) image->columns; x++)
776 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
777 0.07217f*GetPixelBlue(image,q);
778 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
779 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
780 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
781 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
782 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
784 q+=GetPixelChannels(image);
786 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
788 if (image->progress_monitor != (MagickProgressMonitor) NULL)
793 #if defined(MAGICKCORE_OPENMP_SUPPORT)
794 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
796 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
797 progress++,image->rows);
798 if( IfMagickFalse(proceed) )
802 image_view=DestroyCacheView(image_view);
803 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
808 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
812 % C o n t r a s t I m a g e %
816 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818 % ContrastImage() enhances the intensity differences between the lighter and
819 % darker elements of the image. Set sharpen to a MagickTrue to increase the
820 % image contrast otherwise the contrast is reduced.
822 % The format of the ContrastImage method is:
824 % MagickBooleanType ContrastImage(Image *image,
825 % const MagickBooleanType sharpen,ExceptionInfo *exception)
827 % A description of each parameter follows:
829 % o image: the image.
831 % o sharpen: Increase or decrease image contrast.
833 % o exception: return any errors or warnings in this structure.
837 static void Contrast(const int sign,double *red,double *green,double *blue)
845 Enhance contrast: dark color become darker, light color become lighter.
847 assert(red != (double *) NULL);
848 assert(green != (double *) NULL);
849 assert(blue != (double *) NULL);
853 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
854 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
856 if (brightness > 1.0)
859 if (brightness < 0.0)
861 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
864 MagickExport MagickBooleanType ContrastImage(Image *image,
865 const MagickBooleanType sharpen,ExceptionInfo *exception)
867 #define ContrastImageTag "Contrast/Image"
887 assert(image != (Image *) NULL);
888 assert(image->signature == MagickSignature);
889 if (image->debug != MagickFalse)
890 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
891 sign=IfMagickTrue(sharpen) ? 1 : -1;
892 if (image->storage_class == PseudoClass)
895 Contrast enhance colormap.
897 for (i=0; i < (ssize_t) image->colors; i++)
907 Contrast(sign,&red,&green,&blue);
908 image->colormap[i].red=(MagickRealType) red;
909 image->colormap[i].green=(MagickRealType) green;
910 image->colormap[i].blue=(MagickRealType) blue;
914 Contrast enhance image.
918 image_view=AcquireAuthenticCacheView(image,exception);
919 #if defined(MAGICKCORE_OPENMP_SUPPORT)
920 #pragma omp parallel for schedule(static,4) shared(progress,status) \
921 magick_threads(image,image,image->rows,1)
923 for (y=0; y < (ssize_t) image->rows; y++)
936 if (status == MagickFalse)
938 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
939 if (q == (Quantum *) NULL)
944 for (x=0; x < (ssize_t) image->columns; x++)
946 red=(double) GetPixelRed(image,q);
947 green=(double) GetPixelGreen(image,q);
948 blue=(double) GetPixelBlue(image,q);
949 Contrast(sign,&red,&green,&blue);
950 SetPixelRed(image,ClampToQuantum(red),q);
951 SetPixelGreen(image,ClampToQuantum(green),q);
952 SetPixelBlue(image,ClampToQuantum(blue),q);
953 q+=GetPixelChannels(image);
955 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
962 #if defined(MAGICKCORE_OPENMP_SUPPORT)
963 #pragma omp critical (MagickCore_ContrastImage)
965 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
966 if( IfMagickFalse(proceed) )
970 image_view=DestroyCacheView(image_view);
975 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
979 % C o n t r a s t S t r e t c h I m a g e %
983 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985 % ContrastStretchImage() is a simple image enhancement technique that attempts
986 % to improve the contrast in an image by 'stretching' the range of intensity
987 % values it contains to span a desired range of values. It differs from the
988 % more sophisticated histogram equalization in that it can only apply a
989 % linear scaling function to the image pixel values. As a result the
990 % 'enhancement' is less harsh.
992 % The format of the ContrastStretchImage method is:
994 % MagickBooleanType ContrastStretchImage(Image *image,
995 % const char *levels,ExceptionInfo *exception)
997 % A description of each parameter follows:
999 % o image: the image.
1001 % o black_point: the black point.
1003 % o white_point: the white point.
1005 % o levels: Specify the levels where the black and white points have the
1006 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1008 % o exception: return any errors or warnings in this structure.
1011 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1012 const double black_point,const double white_point,ExceptionInfo *exception)
1014 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1015 #define ContrastStretchImageTag "ContrastStretch/Image"
1039 Allocate histogram and stretch map.
1041 assert(image != (Image *) NULL);
1042 assert(image->signature == MagickSignature);
1043 if (image->debug != MagickFalse)
1044 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1045 if (IsImageGray(image,exception) != MagickFalse)
1046 (void) SetImageColorspace(image,GRAYColorspace,exception);
1047 black=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1048 white=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1049 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1050 sizeof(*histogram));
1051 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1052 sizeof(*stretch_map));
1053 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1054 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1056 if (stretch_map != (double *) NULL)
1057 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1058 if (histogram != (double *) NULL)
1059 histogram=(double *) RelinquishMagickMemory(histogram);
1060 if (white != (double *) NULL)
1061 white=(double *) RelinquishMagickMemory(white);
1062 if (black != (double *) NULL)
1063 black=(double *) RelinquishMagickMemory(black);
1064 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1071 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*MaxPixelChannels*
1072 sizeof(*histogram));
1073 image_view=AcquireVirtualCacheView(image,exception);
1074 for (y=0; y < (ssize_t) image->rows; y++)
1076 register const Quantum
1082 if (status == MagickFalse)
1084 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1085 if (p == (const Quantum *) NULL)
1090 for (x=0; x < (ssize_t) image->columns; x++)
1098 pixel=GetPixelIntensity(image,p);
1099 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1101 if (image->channel_mask != DefaultChannels)
1102 pixel=(double) p[i];
1103 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1104 ClampToQuantum(pixel))+i]++;
1106 p+=GetPixelChannels(image);
1109 image_view=DestroyCacheView(image_view);
1111 Find the histogram boundaries by locating the black/white levels.
1113 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1122 white[i]=MaxRange(QuantumRange);
1124 for (j=0; j <= (ssize_t) MaxMap; j++)
1126 intensity+=histogram[GetPixelChannels(image)*j+i];
1127 if (intensity > black_point)
1130 black[i]=(double) j;
1132 for (j=(ssize_t) MaxMap; j != 0; j--)
1134 intensity+=histogram[GetPixelChannels(image)*j+i];
1135 if (intensity > ((double) image->columns*image->rows-white_point))
1138 white[i]=(double) j;
1140 histogram=(double *) RelinquishMagickMemory(histogram);
1142 Stretch the histogram to create the stretched image mapping.
1144 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1145 sizeof(*stretch_map));
1146 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1151 for (j=0; j <= (ssize_t) MaxMap; j++)
1156 gamma=PerceptibleReciprocal(white[i]-black[i]);
1157 if (j < (ssize_t) black[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1160 if (j > (ssize_t) white[i])
1161 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1163 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1164 (double) (MaxMap*gamma*(j-black[i])));
1167 if (image->storage_class == PseudoClass)
1173 Stretch-contrast colormap.
1175 for (j=0; j < (ssize_t) image->colors; j++)
1177 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1179 i=GetPixelChannelChannel(image,RedPixelChannel);
1180 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1183 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelChannel(image,GreenPixelChannel);
1186 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1189 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1191 i=GetPixelChannelChannel(image,BluePixelChannel);
1192 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1193 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1195 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1197 i=GetPixelChannelChannel(image,AlphaPixelChannel);
1198 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1199 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1204 Stretch-contrast image.
1208 image_view=AcquireAuthenticCacheView(image,exception);
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1211 magick_threads(image,image,image->rows,1)
1213 for (y=0; y < (ssize_t) image->rows; y++)
1221 if (status == MagickFalse)
1223 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1224 if (q == (Quantum *) NULL)
1229 for (x=0; x < (ssize_t) image->columns; x++)
1234 if (GetPixelReadMask(image,q) == 0)
1236 q+=GetPixelChannels(image);
1239 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1241 PixelChannel channel=GetPixelChannelChannel(image,i);
1242 PixelTrait traits=GetPixelChannelTraits(image,channel);
1243 if ((traits & UpdatePixelTrait) == 0)
1245 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*ScaleQuantumToMap(q[i])+
1248 q+=GetPixelChannels(image);
1250 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1252 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1258 #pragma omp critical (MagickCore_ContrastStretchImage)
1260 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1262 if (proceed == MagickFalse)
1266 image_view=DestroyCacheView(image_view);
1267 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1268 white=(double *) RelinquishMagickMemory(white);
1269 black=(double *) RelinquishMagickMemory(black);
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1278 % E n h a n c e I m a g e %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284 % EnhanceImage() applies a digital filter that improves the quality of a
1287 % The format of the EnhanceImage method is:
1289 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1291 % A description of each parameter follows:
1293 % o image: the image.
1295 % o exception: return any errors or warnings in this structure.
1298 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1300 #define EnhancePixel(weight) \
1301 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1302 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1303 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1304 distance*distance; \
1305 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
1307 aggregate+=(weight)*r[i]; \
1308 total_weight+=(weight); \
1310 r+=GetPixelChannels(image);
1311 #define EnhanceImageTag "Enhance/Image"
1330 Initialize enhanced image attributes.
1332 assert(image != (const Image *) NULL);
1333 assert(image->signature == MagickSignature);
1334 if (image->debug != MagickFalse)
1335 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1336 assert(exception != (ExceptionInfo *) NULL);
1337 assert(exception->signature == MagickSignature);
1338 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1340 if (enhance_image == (Image *) NULL)
1341 return((Image *) NULL);
1342 if( IfMagickFalse(SetImageStorageClass(enhance_image,DirectClass,exception)) )
1344 enhance_image=DestroyImage(enhance_image);
1345 return((Image *) NULL);
1352 image_view=AcquireVirtualCacheView(image,exception);
1353 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1354 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1355 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1356 magick_threads(image,enhance_image,image->rows,1)
1358 for (y=0; y < (ssize_t) image->rows; y++)
1360 register const Quantum
1372 if (status == MagickFalse)
1374 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1375 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1377 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1382 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1383 for (x=0; x < (ssize_t) image->columns; x++)
1388 if (GetPixelReadMask(image,p) == 0)
1390 SetPixelBackgoundColor(enhance_image,q);
1391 p+=GetPixelChannels(image);
1392 q+=GetPixelChannels(enhance_image);
1395 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1404 register const Quantum
1407 PixelChannel channel=GetPixelChannelChannel(image,i);
1408 PixelTrait traits=GetPixelChannelTraits(image,channel);
1409 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1410 if ((traits == UndefinedPixelTrait) ||
1411 (enhance_traits == UndefinedPixelTrait))
1413 SetPixelChannel(enhance_image,channel,p[center+i],q);
1414 if ((enhance_traits & CopyPixelTrait) != 0)
1417 Compute weighted average of target pixel color components.
1422 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1423 EnhancePixel(8.0); EnhancePixel(5.0);
1424 r=p+1*GetPixelChannels(image)*(image->columns+4);
1425 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1426 EnhancePixel(20.0); EnhancePixel(8.0);
1427 r=p+2*GetPixelChannels(image)*(image->columns+4);
1428 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1429 EnhancePixel(40.0); EnhancePixel(10.0);
1430 r=p+3*GetPixelChannels(image)*(image->columns+4);
1431 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1432 EnhancePixel(20.0); EnhancePixel(8.0);
1433 r=p+4*GetPixelChannels(image)*(image->columns+4);
1434 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1435 EnhancePixel(8.0); EnhancePixel(5.0);
1436 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1439 p+=GetPixelChannels(image);
1440 q+=GetPixelChannels(enhance_image);
1442 if( IfMagickFalse(SyncCacheViewAuthenticPixels(enhance_view,exception)) )
1444 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1449 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1450 #pragma omp critical (MagickCore_EnhanceImage)
1452 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1453 if( IfMagickFalse(proceed) )
1457 enhance_view=DestroyCacheView(enhance_view);
1458 image_view=DestroyCacheView(image_view);
1459 if (status == MagickFalse)
1460 enhance_image=DestroyImage(enhance_image);
1461 return(enhance_image);
1465 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1469 % E q u a l i z e I m a g e %
1473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1475 % EqualizeImage() applies a histogram equalization to the image.
1477 % The format of the EqualizeImage method is:
1479 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1481 % A description of each parameter follows:
1483 % o image: the image.
1485 % o exception: return any errors or warnings in this structure.
1488 MagickExport MagickBooleanType EqualizeImage(Image *image,
1489 ExceptionInfo *exception)
1491 #define EqualizeImageTag "Equalize/Image"
1503 black[CompositePixelChannel+1],
1507 white[CompositePixelChannel+1];
1516 Allocate and initialize histogram arrays.
1518 assert(image != (Image *) NULL);
1519 assert(image->signature == MagickSignature);
1520 if (image->debug != MagickFalse)
1521 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1522 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1523 GetPixelChannels(image)*sizeof(*equalize_map));
1524 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1525 sizeof(*histogram));
1526 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1528 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1529 (map == (double *) NULL))
1531 if (map != (double *) NULL)
1532 map=(double *) RelinquishMagickMemory(map);
1533 if (histogram != (double *) NULL)
1534 histogram=(double *) RelinquishMagickMemory(histogram);
1535 if (equalize_map != (double *) NULL)
1536 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1537 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1544 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1545 sizeof(*histogram));
1546 image_view=AcquireVirtualCacheView(image,exception);
1547 for (y=0; y < (ssize_t) image->rows; y++)
1549 register const Quantum
1555 if (status == MagickFalse)
1557 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1558 if (p == (const Quantum *) NULL)
1563 for (x=0; x < (ssize_t) image->columns; x++)
1568 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1569 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1570 p+=GetPixelChannels(image);
1573 image_view=DestroyCacheView(image_view);
1575 Integrate the histogram to get the equalization map.
1577 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1586 for (j=0; j <= (ssize_t) MaxMap; j++)
1588 intensity+=histogram[GetPixelChannels(image)*j+i];
1589 map[GetPixelChannels(image)*j+i]=intensity;
1592 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1593 sizeof(*equalize_map));
1594 (void) ResetMagickMemory(black,0,sizeof(*black));
1595 (void) ResetMagickMemory(white,0,sizeof(*white));
1596 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1602 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1603 if (black[i] != white[i])
1604 for (j=0; j <= (ssize_t) MaxMap; j++)
1605 equalize_map[GetPixelChannels(image)*j+i]=(double)
1606 ScaleMapToQuantum((double) ((MaxMap*(map[
1607 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1609 histogram=(double *) RelinquishMagickMemory(histogram);
1610 map=(double *) RelinquishMagickMemory(map);
1611 if (image->storage_class == PseudoClass)
1619 for (j=0; j < (ssize_t) image->colors; j++)
1621 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1623 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1624 if (black[channel] != white[channel])
1625 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1626 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1629 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1631 PixelChannel channel=GetPixelChannelChannel(image,
1633 if (black[channel] != white[channel])
1634 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1635 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1638 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1640 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1641 if (black[channel] != white[channel])
1642 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1643 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1646 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1648 PixelChannel channel=GetPixelChannelChannel(image,
1650 if (black[channel] != white[channel])
1651 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1652 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1661 image_view=AcquireAuthenticCacheView(image,exception);
1662 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1663 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1664 magick_threads(image,image,image->rows,1)
1666 for (y=0; y < (ssize_t) image->rows; y++)
1674 if (status == MagickFalse)
1676 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1677 if (q == (Quantum *) NULL)
1682 for (x=0; x < (ssize_t) image->columns; x++)
1687 if (GetPixelReadMask(image,q) == 0)
1689 q+=GetPixelChannels(image);
1692 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1694 PixelChannel channel=GetPixelChannelChannel(image,i);
1695 PixelTrait traits=GetPixelChannelTraits(image,channel);
1696 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1698 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1699 ScaleQuantumToMap(q[i])+i]);
1701 q+=GetPixelChannels(image);
1703 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1705 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1710 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1711 #pragma omp critical (MagickCore_EqualizeImage)
1713 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1714 if( IfMagickFalse(proceed) )
1718 image_view=DestroyCacheView(image_view);
1719 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1724 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728 % G a m m a I m a g e %
1732 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1734 % GammaImage() gamma-corrects a particular image channel. The same
1735 % image viewed on different devices will have perceptual differences in the
1736 % way the image's intensities are represented on the screen. Specify
1737 % individual gamma levels for the red, green, and blue channels, or adjust
1738 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1740 % You can also reduce the influence of a particular channel with a gamma
1743 % The format of the GammaImage method is:
1745 % MagickBooleanType GammaImage(Image *image,const double gamma,
1746 % ExceptionInfo *exception)
1748 % A description of each parameter follows:
1750 % o image: the image.
1752 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1754 % o gamma: the image gamma.
1758 static inline double gamma_pow(const double value,const double gamma)
1760 return(value < 0.0 ? value : pow(value,gamma));
1763 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1764 ExceptionInfo *exception)
1766 #define GammaCorrectImageTag "GammaCorrect/Image"
1787 Allocate and initialize gamma maps.
1789 assert(image != (Image *) NULL);
1790 assert(image->signature == MagickSignature);
1791 if (image->debug != MagickFalse)
1792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1795 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1796 if (gamma_map == (Quantum *) NULL)
1797 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1799 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1801 for (i=0; i <= (ssize_t) MaxMap; i++)
1802 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1803 MaxMap,1.0/gamma)));
1804 if (image->storage_class == PseudoClass)
1805 for (i=0; i < (ssize_t) image->colors; i++)
1808 Gamma-correct colormap.
1810 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1811 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1812 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1813 ClampToQuantum(image->colormap[i].red))];
1814 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1815 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1816 ClampToQuantum(image->colormap[i].green))];
1817 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1818 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1819 ClampToQuantum(image->colormap[i].blue))];
1820 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1821 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1822 ClampToQuantum(image->colormap[i].alpha))];
1824 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1825 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1826 image->colormap[i].red,1.0/gamma);
1827 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1828 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1829 image->colormap[i].green,1.0/gamma);
1830 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1831 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1832 image->colormap[i].blue,1.0/gamma);
1833 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1834 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1835 image->colormap[i].alpha,1.0/gamma);
1839 Gamma-correct image.
1843 image_view=AcquireAuthenticCacheView(image,exception);
1844 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1845 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1846 magick_threads(image,image,image->rows,1)
1848 for (y=0; y < (ssize_t) image->rows; y++)
1856 if (status == MagickFalse)
1858 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1859 if (q == (Quantum *) NULL)
1864 for (x=0; x < (ssize_t) image->columns; x++)
1869 if (GetPixelReadMask(image,q) == 0)
1871 q+=GetPixelChannels(image);
1874 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1876 PixelChannel channel=GetPixelChannelChannel(image,i);
1877 PixelTrait traits=GetPixelChannelTraits(image,channel);
1878 if ((traits & UpdatePixelTrait) == 0)
1880 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1881 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1883 q[i]=QuantumRange*gamma_pow(QuantumScale*q[i],1.0/gamma);
1886 q+=GetPixelChannels(image);
1888 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1890 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1895 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1896 #pragma omp critical (MagickCore_GammaImage)
1898 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1900 if( IfMagickFalse(proceed) )
1904 image_view=DestroyCacheView(image_view);
1905 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1906 if (image->gamma != 0.0)
1907 image->gamma*=gamma;
1912 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1916 % G r a y s c a l e I m a g e %
1920 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1922 % GrayscaleImage() converts the image to grayscale.
1924 % The format of the GrayscaleImage method is:
1926 % MagickBooleanType GrayscaleImage(Image *image,
1927 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1929 % A description of each parameter follows:
1931 % o image: the image.
1933 % o method: the pixel intensity method.
1935 % o exception: return any errors or warnings in this structure.
1938 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1939 const PixelIntensityMethod method,ExceptionInfo *exception)
1941 #define GrayscaleImageTag "Grayscale/Image"
1955 assert(image != (Image *) NULL);
1956 assert(image->signature == MagickSignature);
1957 if (image->debug != MagickFalse)
1958 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1959 if (image->storage_class == PseudoClass)
1961 if( IfMagickFalse(SyncImage(image,exception)) )
1962 return(MagickFalse);
1963 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
1964 return(MagickFalse);
1971 image_view=AcquireAuthenticCacheView(image,exception);
1972 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1973 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1974 magick_threads(image,image,image->rows,1)
1976 for (y=0; y < (ssize_t) image->rows; y++)
1984 if (status == MagickFalse)
1986 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1987 if (q == (Quantum *) NULL)
1992 for (x=0; x < (ssize_t) image->columns; x++)
2000 if (GetPixelReadMask(image,q) == 0)
2002 q+=GetPixelChannels(image);
2005 red=(MagickRealType) GetPixelRed(image,q);
2006 green=(MagickRealType) GetPixelGreen(image,q);
2007 blue=(MagickRealType) GetPixelBlue(image,q);
2011 case AveragePixelIntensityMethod:
2013 intensity=(red+green+blue)/3.0;
2016 case BrightnessPixelIntensityMethod:
2018 intensity=MagickMax(MagickMax(red,green),blue);
2021 case LightnessPixelIntensityMethod:
2023 intensity=(MagickMin(MagickMin(red,green),blue)+
2024 MagickMax(MagickMax(red,green),blue))/2.0;
2027 case MSPixelIntensityMethod:
2029 intensity=(MagickRealType) (((double) red*red+green*green+
2033 case Rec601LumaPixelIntensityMethod:
2035 if (image->colorspace == RGBColorspace)
2037 red=EncodePixelGamma(red);
2038 green=EncodePixelGamma(green);
2039 blue=EncodePixelGamma(blue);
2041 intensity=0.298839*red+0.586811*green+0.114350*blue;
2044 case Rec601LuminancePixelIntensityMethod:
2046 if (image->colorspace == sRGBColorspace)
2048 red=DecodePixelGamma(red);
2049 green=DecodePixelGamma(green);
2050 blue=DecodePixelGamma(blue);
2052 intensity=0.298839*red+0.586811*green+0.114350*blue;
2055 case Rec709LumaPixelIntensityMethod:
2058 if (image->colorspace == RGBColorspace)
2060 red=EncodePixelGamma(red);
2061 green=EncodePixelGamma(green);
2062 blue=EncodePixelGamma(blue);
2064 intensity=0.212656*red+0.715158*green+0.072186*blue;
2067 case Rec709LuminancePixelIntensityMethod:
2069 if (image->colorspace == sRGBColorspace)
2071 red=DecodePixelGamma(red);
2072 green=DecodePixelGamma(green);
2073 blue=DecodePixelGamma(blue);
2075 intensity=0.212656*red+0.715158*green+0.072186*blue;
2078 case RMSPixelIntensityMethod:
2080 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2081 blue*blue)/sqrt(3.0));
2085 SetPixelGray(image,ClampToQuantum(intensity),q);
2086 q+=GetPixelChannels(image);
2088 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
2090 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2095 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2096 #pragma omp critical (MagickCore_GrayscaleImage)
2098 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2100 if( IfMagickFalse(proceed) )
2104 image_view=DestroyCacheView(image_view);
2105 image->intensity=method;
2106 image->type=GrayscaleType;
2107 return(SetImageColorspace(image,GRAYColorspace,exception));
2111 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2115 % H a l d C l u t I m a g e %
2119 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2121 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2122 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2123 % Create it with the HALD coder. You can apply any color transformation to
2124 % the Hald image and then use this method to apply the transform to the
2127 % The format of the HaldClutImage method is:
2129 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2130 % ExceptionInfo *exception)
2132 % A description of each parameter follows:
2134 % o image: the image, which is replaced by indexed CLUT values
2136 % o hald_image: the color lookup table image for replacement color values.
2138 % o exception: return any errors or warnings in this structure.
2141 MagickExport MagickBooleanType HaldClutImage(Image *image,
2142 const Image *hald_image,ExceptionInfo *exception)
2144 #define HaldClutImageTag "Clut/Image"
2146 typedef struct _HaldInfo
2178 assert(image != (Image *) NULL);
2179 assert(image->signature == MagickSignature);
2180 if (image->debug != MagickFalse)
2181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2182 assert(hald_image != (Image *) NULL);
2183 assert(hald_image->signature == MagickSignature);
2184 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
2185 return(MagickFalse);
2186 if (image->alpha_trait == UndefinedPixelTrait)
2187 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2193 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2194 (MagickRealType) hald_image->rows);
2195 for (level=2; (level*level*level) < length; level++) ;
2197 cube_size=level*level;
2198 width=(double) hald_image->columns;
2199 GetPixelInfo(hald_image,&zero);
2200 hald_view=AcquireVirtualCacheView(hald_image,exception);
2201 image_view=AcquireAuthenticCacheView(image,exception);
2202 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2203 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2204 magick_threads(image,image,image->rows,1)
2206 for (y=0; y < (ssize_t) image->rows; y++)
2214 if (status == MagickFalse)
2216 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2217 if (q == (Quantum *) NULL)
2222 for (x=0; x < (ssize_t) image->columns; x++)
2237 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2238 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2239 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2240 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2241 point.x-=floor(point.x);
2242 point.y-=floor(point.y);
2243 point.z-=floor(point.z);
2245 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2246 fmod(offset,width),floor(offset/width),&pixel1,exception);
2248 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2249 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2251 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2254 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2255 fmod(offset,width),floor(offset/width),&pixel1,exception);
2256 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2257 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2259 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2262 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2264 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2265 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2266 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2267 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2268 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2269 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2270 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2271 (image->colorspace == CMYKColorspace))
2272 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2273 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2274 (image->alpha_trait != UndefinedPixelTrait))
2275 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2276 q+=GetPixelChannels(image);
2278 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2280 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2285 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2286 #pragma omp critical (MagickCore_HaldClutImage)
2288 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2289 if( IfMagickFalse(proceed) )
2293 hald_view=DestroyCacheView(hald_view);
2294 image_view=DestroyCacheView(image_view);
2299 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2303 % L e v e l I m a g e %
2307 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2309 % LevelImage() adjusts the levels of a particular image channel by
2310 % scaling the colors falling between specified white and black points to
2311 % the full available quantum range.
2313 % The parameters provided represent the black, and white points. The black
2314 % point specifies the darkest color in the image. Colors darker than the
2315 % black point are set to zero. White point specifies the lightest color in
2316 % the image. Colors brighter than the white point are set to the maximum
2319 % If a '!' flag is given, map black and white colors to the given levels
2320 % rather than mapping those levels to black and white. See
2321 % LevelizeImage() below.
2323 % Gamma specifies a gamma correction to apply to the image.
2325 % The format of the LevelImage method is:
2327 % MagickBooleanType LevelImage(Image *image,const double black_point,
2328 % const double white_point,const double gamma,ExceptionInfo *exception)
2330 % A description of each parameter follows:
2332 % o image: the image.
2334 % o black_point: The level to map zero (black) to.
2336 % o white_point: The level to map QuantumRange (white) to.
2338 % o exception: return any errors or warnings in this structure.
2342 static inline double LevelPixel(const double black_point,
2343 const double white_point,const double gamma,const double pixel)
2349 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2350 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2352 return(level_pixel);
2355 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2356 const double white_point,const double gamma,ExceptionInfo *exception)
2358 #define LevelImageTag "Level/Image"
2376 Allocate and initialize levels map.
2378 assert(image != (Image *) NULL);
2379 assert(image->signature == MagickSignature);
2380 if (image->debug != MagickFalse)
2381 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2382 if (image->storage_class == PseudoClass)
2383 for (i=0; i < (ssize_t) image->colors; i++)
2388 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2389 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2390 white_point,gamma,image->colormap[i].red));
2391 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2392 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2393 white_point,gamma,image->colormap[i].green));
2394 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2395 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2396 white_point,gamma,image->colormap[i].blue));
2397 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2398 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2399 white_point,gamma,image->colormap[i].alpha));
2406 image_view=AcquireAuthenticCacheView(image,exception);
2407 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2408 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2409 magick_threads(image,image,image->rows,1)
2411 for (y=0; y < (ssize_t) image->rows; y++)
2419 if (status == MagickFalse)
2421 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2422 if (q == (Quantum *) NULL)
2427 for (x=0; x < (ssize_t) image->columns; x++)
2432 if (GetPixelReadMask(image,q) == 0)
2434 q+=GetPixelChannels(image);
2437 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2439 PixelChannel channel=GetPixelChannelChannel(image,i);
2440 PixelTrait traits=GetPixelChannelTraits(image,channel);
2441 if ((traits & UpdatePixelTrait) == 0)
2443 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2446 q+=GetPixelChannels(image);
2448 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2450 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2455 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2456 #pragma omp critical (MagickCore_LevelImage)
2458 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2459 if( IfMagickFalse(proceed) )
2463 image_view=DestroyCacheView(image_view);
2464 (void) ClampImage(image,exception);
2469 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2473 % L e v e l i z e I m a g e %
2477 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2479 % LevelizeImage() applies the reversed LevelImage() operation to just
2480 % the specific channels specified. It compresses the full range of color
2481 % values, so that they lie between the given black and white points. Gamma is
2482 % applied before the values are mapped.
2484 % LevelizeImage() can be called with by using a +level command line
2485 % API option, or using a '!' on a -level or LevelImage() geometry string.
2487 % It can be used to de-contrast a greyscale image to the exact levels
2488 % specified. Or by using specific levels for each channel of an image you
2489 % can convert a gray-scale image to any linear color gradient, according to
2492 % The format of the LevelizeImage method is:
2494 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2495 % const double white_point,const double gamma,ExceptionInfo *exception)
2497 % A description of each parameter follows:
2499 % o image: the image.
2501 % o black_point: The level to map zero (black) to.
2503 % o white_point: The level to map QuantumRange (white) to.
2505 % o gamma: adjust gamma by this factor before mapping values.
2507 % o exception: return any errors or warnings in this structure.
2510 MagickExport MagickBooleanType LevelizeImage(Image *image,
2511 const double black_point,const double white_point,const double gamma,
2512 ExceptionInfo *exception)
2514 #define LevelizeImageTag "Levelize/Image"
2515 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2516 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2534 Allocate and initialize levels map.
2536 assert(image != (Image *) NULL);
2537 assert(image->signature == MagickSignature);
2538 if (image->debug != MagickFalse)
2539 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2540 if (image->storage_class == PseudoClass)
2541 for (i=0; i < (ssize_t) image->colors; i++)
2546 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2547 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2548 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2549 image->colormap[i].green=(double) LevelizeValue(
2550 image->colormap[i].green);
2551 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2552 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2553 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2554 image->colormap[i].alpha=(double) LevelizeValue(
2555 image->colormap[i].alpha);
2562 image_view=AcquireAuthenticCacheView(image,exception);
2563 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2564 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2565 magick_threads(image,image,image->rows,1)
2567 for (y=0; y < (ssize_t) image->rows; y++)
2575 if (status == MagickFalse)
2577 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2578 if (q == (Quantum *) NULL)
2583 for (x=0; x < (ssize_t) image->columns; x++)
2588 if (GetPixelReadMask(image,q) == 0)
2590 q+=GetPixelChannels(image);
2593 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2595 PixelChannel channel=GetPixelChannelChannel(image,i);
2596 PixelTrait traits=GetPixelChannelTraits(image,channel);
2597 if ((traits & UpdatePixelTrait) == 0)
2599 q[i]=LevelizeValue(q[i]);
2601 q+=GetPixelChannels(image);
2603 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2605 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2610 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2611 #pragma omp critical (MagickCore_LevelizeImage)
2613 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2614 if( IfMagickFalse(proceed) )
2618 image_view=DestroyCacheView(image_view);
2623 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2627 % L e v e l I m a g e C o l o r s %
2631 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2633 % LevelImageColors() maps the given color to "black" and "white" values,
2634 % linearly spreading out the colors, and level values on a channel by channel
2635 % bases, as per LevelImage(). The given colors allows you to specify
2636 % different level ranges for each of the color channels separately.
2638 % If the boolean 'invert' is set true the image values will modifyed in the
2639 % reverse direction. That is any existing "black" and "white" colors in the
2640 % image will become the color values given, with all other values compressed
2641 % appropriatally. This effectivally maps a greyscale gradient into the given
2644 % The format of the LevelImageColors method is:
2646 % MagickBooleanType LevelImageColors(Image *image,
2647 % const PixelInfo *black_color,const PixelInfo *white_color,
2648 % const MagickBooleanType invert,ExceptionInfo *exception)
2650 % A description of each parameter follows:
2652 % o image: the image.
2654 % o black_color: The color to map black to/from
2656 % o white_point: The color to map white to/from
2658 % o invert: if true map the colors (levelize), rather than from (level)
2660 % o exception: return any errors or warnings in this structure.
2663 MagickExport MagickBooleanType LevelImageColors(Image *image,
2664 const PixelInfo *black_color,const PixelInfo *white_color,
2665 const MagickBooleanType invert,ExceptionInfo *exception)
2674 Allocate and initialize levels map.
2676 assert(image != (Image *) NULL);
2677 assert(image->signature == MagickSignature);
2678 if (image->debug != MagickFalse)
2679 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2680 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2681 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2682 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
2683 (void) SetImageColorspace(image,sRGBColorspace,exception);
2685 if( IfMagickFalse(invert) )
2687 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2689 channel_mask=SetImageChannelMask(image,RedChannel);
2690 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2692 (void) SetImageChannelMask(image,channel_mask);
2694 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2696 channel_mask=SetImageChannelMask(image,GreenChannel);
2697 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2699 (void) SetImageChannelMask(image,channel_mask);
2701 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2703 channel_mask=SetImageChannelMask(image,BlueChannel);
2704 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2706 (void) SetImageChannelMask(image,channel_mask);
2708 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2709 (image->colorspace == CMYKColorspace))
2711 channel_mask=SetImageChannelMask(image,BlackChannel);
2712 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2714 (void) SetImageChannelMask(image,channel_mask);
2716 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2717 (image->alpha_trait != UndefinedPixelTrait))
2719 channel_mask=SetImageChannelMask(image,AlphaChannel);
2720 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2722 (void) SetImageChannelMask(image,channel_mask);
2727 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2729 channel_mask=SetImageChannelMask(image,RedChannel);
2730 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2732 (void) SetImageChannelMask(image,channel_mask);
2734 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2736 channel_mask=SetImageChannelMask(image,GreenChannel);
2737 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2739 (void) SetImageChannelMask(image,channel_mask);
2741 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2743 channel_mask=SetImageChannelMask(image,BlueChannel);
2744 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2746 (void) SetImageChannelMask(image,channel_mask);
2748 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2749 (image->colorspace == CMYKColorspace))
2751 channel_mask=SetImageChannelMask(image,BlackChannel);
2752 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2754 (void) SetImageChannelMask(image,channel_mask);
2756 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2757 (image->alpha_trait != UndefinedPixelTrait))
2759 channel_mask=SetImageChannelMask(image,AlphaChannel);
2760 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2762 (void) SetImageChannelMask(image,channel_mask);
2765 return(status != 0 ? MagickTrue : MagickFalse);
2769 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2773 % L i n e a r S t r e t c h I m a g e %
2777 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2779 % LinearStretchImage() discards any pixels below the black point and above
2780 % the white point and levels the remaining pixels.
2782 % The format of the LinearStretchImage method is:
2784 % MagickBooleanType LinearStretchImage(Image *image,
2785 % const double black_point,const double white_point,
2786 % ExceptionInfo *exception)
2788 % A description of each parameter follows:
2790 % o image: the image.
2792 % o black_point: the black point.
2794 % o white_point: the white point.
2796 % o exception: return any errors or warnings in this structure.
2799 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2800 const double black_point,const double white_point,ExceptionInfo *exception)
2802 #define LinearStretchImageTag "LinearStretch/Image"
2820 Allocate histogram and linear map.
2822 assert(image != (Image *) NULL);
2823 assert(image->signature == MagickSignature);
2824 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2825 if (histogram == (double *) NULL)
2826 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2831 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2832 image_view=AcquireVirtualCacheView(image,exception);
2833 for (y=0; y < (ssize_t) image->rows; y++)
2835 register const Quantum
2841 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2842 if (p == (const Quantum *) NULL)
2844 for (x=0; x < (ssize_t) image->columns; x++)
2849 intensity=GetPixelIntensity(image,p);
2850 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2851 p+=GetPixelChannels(image);
2854 image_view=DestroyCacheView(image_view);
2856 Find the histogram boundaries by locating the black and white point levels.
2859 for (black=0; black < (ssize_t) MaxMap; black++)
2861 intensity+=histogram[black];
2862 if (intensity >= black_point)
2866 for (white=(ssize_t) MaxMap; white != 0; white--)
2868 intensity+=histogram[white];
2869 if (intensity >= white_point)
2872 histogram=(double *) RelinquishMagickMemory(histogram);
2873 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2882 % M o d u l a t e I m a g e %
2886 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2888 % ModulateImage() lets you control the brightness, saturation, and hue
2889 % of an image. Modulate represents the brightness, saturation, and hue
2890 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2891 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2892 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2894 % The format of the ModulateImage method is:
2896 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2897 % ExceptionInfo *exception)
2899 % A description of each parameter follows:
2901 % o image: the image.
2903 % o modulate: Define the percent change in brightness, saturation, and hue.
2905 % o exception: return any errors or warnings in this structure.
2909 static inline void ModulateHCL(const double percent_hue,
2910 const double percent_chroma,const double percent_luma,double *red,
2911 double *green,double *blue)
2919 Increase or decrease color luma, chroma, or hue.
2921 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2922 hue+=0.5*(0.01*percent_hue-1.0);
2927 chroma*=0.01*percent_chroma;
2928 luma*=0.01*percent_luma;
2929 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2932 static inline void ModulateHCLp(const double percent_hue,
2933 const double percent_chroma,const double percent_luma,double *red,
2934 double *green,double *blue)
2942 Increase or decrease color luma, chroma, or hue.
2944 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2945 hue+=0.5*(0.01*percent_hue-1.0);
2950 chroma*=0.01*percent_chroma;
2951 luma*=0.01*percent_luma;
2952 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2955 static inline void ModulateHSB(const double percent_hue,
2956 const double percent_saturation,const double percent_brightness,double *red,
2957 double *green,double *blue)
2965 Increase or decrease color brightness, saturation, or hue.
2967 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2968 hue+=0.5*(0.01*percent_hue-1.0);
2973 saturation*=0.01*percent_saturation;
2974 brightness*=0.01*percent_brightness;
2975 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2978 static inline void ModulateHSI(const double percent_hue,
2979 const double percent_saturation,const double percent_intensity,double *red,
2980 double *green,double *blue)
2988 Increase or decrease color intensity, saturation, or hue.
2990 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
2991 hue+=0.5*(0.01*percent_hue-1.0);
2996 saturation*=0.01*percent_saturation;
2997 intensity*=0.01*percent_intensity;
2998 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3001 static inline void ModulateHSL(const double percent_hue,
3002 const double percent_saturation,const double percent_lightness,double *red,
3003 double *green,double *blue)
3011 Increase or decrease color lightness, saturation, or hue.
3013 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3014 hue+=0.5*(0.01*percent_hue-1.0);
3019 saturation*=0.01*percent_saturation;
3020 lightness*=0.01*percent_lightness;
3021 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3024 static inline void ModulateHSV(const double percent_hue,
3025 const double percent_saturation,const double percent_value,double *red,
3026 double *green,double *blue)
3034 Increase or decrease color value, saturation, or hue.
3036 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3037 hue+=0.5*(0.01*percent_hue-1.0);
3042 saturation*=0.01*percent_saturation;
3043 value*=0.01*percent_value;
3044 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3047 static inline void ModulateHWB(const double percent_hue,
3048 const double percent_whiteness,const double percent_blackness,double *red,
3049 double *green,double *blue)
3057 Increase or decrease color blackness, whiteness, or hue.
3059 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3060 hue+=0.5*(0.01*percent_hue-1.0);
3065 blackness*=0.01*percent_blackness;
3066 whiteness*=0.01*percent_whiteness;
3067 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3070 static inline void ModulateLCHab(const double percent_luma,
3071 const double percent_chroma,const double percent_hue,double *red,
3072 double *green,double *blue)
3080 Increase or decrease color luma, chroma, or hue.
3082 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3083 luma*=0.01*percent_luma;
3084 chroma*=0.01*percent_chroma;
3085 hue+=0.5*(0.01*percent_hue-1.0);
3090 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3093 static inline void ModulateLCHuv(const double percent_luma,
3094 const double percent_chroma,const double percent_hue,double *red,
3095 double *green,double *blue)
3103 Increase or decrease color luma, chroma, or hue.
3105 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3106 luma*=0.01*percent_luma;
3107 chroma*=0.01*percent_chroma;
3108 hue+=0.5*(0.01*percent_hue-1.0);
3113 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3116 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3117 ExceptionInfo *exception)
3119 #define ModulateImageTag "Modulate/Image"
3154 Initialize modulate table.
3156 assert(image != (Image *) NULL);
3157 assert(image->signature == MagickSignature);
3158 if (image->debug != MagickFalse)
3159 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3160 if (modulate == (char *) NULL)
3161 return(MagickFalse);
3162 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
3163 (void) SetImageColorspace(image,sRGBColorspace,exception);
3164 flags=ParseGeometry(modulate,&geometry_info);
3165 percent_brightness=geometry_info.rho;
3166 percent_saturation=geometry_info.sigma;
3167 if ((flags & SigmaValue) == 0)
3168 percent_saturation=100.0;
3169 percent_hue=geometry_info.xi;
3170 if ((flags & XiValue) == 0)
3172 colorspace=UndefinedColorspace;
3173 artifact=GetImageArtifact(image,"modulate:colorspace");
3174 if (artifact != (const char *) NULL)
3175 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3176 MagickFalse,artifact);
3177 if (image->storage_class == PseudoClass)
3178 for (i=0; i < (ssize_t) image->colors; i++)
3186 Modulate image colormap.
3188 red=(double) image->colormap[i].red;
3189 green=(double) image->colormap[i].green;
3190 blue=(double) image->colormap[i].blue;
3195 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3199 case HCLpColorspace:
3201 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3207 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3213 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3220 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3226 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3232 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3237 case LCHabColorspace:
3239 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3243 case LCHuvColorspace:
3245 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3250 image->colormap[i].red=red;
3251 image->colormap[i].green=green;
3252 image->colormap[i].blue=blue;
3259 image_view=AcquireAuthenticCacheView(image,exception);
3260 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3261 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3262 magick_threads(image,image,image->rows,1)
3264 for (y=0; y < (ssize_t) image->rows; y++)
3272 if (status == MagickFalse)
3274 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3275 if (q == (Quantum *) NULL)
3280 for (x=0; x < (ssize_t) image->columns; x++)
3287 red=(double) GetPixelRed(image,q);
3288 green=(double) GetPixelGreen(image,q);
3289 blue=(double) GetPixelBlue(image,q);
3294 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3298 case HCLpColorspace:
3300 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3306 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3313 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3319 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3325 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3329 case LCHabColorspace:
3331 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3336 case LCHuvColorspace:
3338 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3343 SetPixelRed(image,ClampToQuantum(red),q);
3344 SetPixelGreen(image,ClampToQuantum(green),q);
3345 SetPixelBlue(image,ClampToQuantum(blue),q);
3346 q+=GetPixelChannels(image);
3348 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3350 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3355 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3356 #pragma omp critical (MagickCore_ModulateImage)
3358 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3359 if( IfMagickFalse(proceed) )
3363 image_view=DestroyCacheView(image_view);
3368 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3372 % N e g a t e I m a g e %
3376 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3378 % NegateImage() negates the colors in the reference image. The grayscale
3379 % option means that only grayscale values within the image are negated.
3381 % The format of the NegateImage method is:
3383 % MagickBooleanType NegateImage(Image *image,
3384 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3386 % A description of each parameter follows:
3388 % o image: the image.
3390 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3392 % o exception: return any errors or warnings in this structure.
3395 MagickExport MagickBooleanType NegateImage(Image *image,
3396 const MagickBooleanType grayscale,ExceptionInfo *exception)
3398 #define NegateImageTag "Negate/Image"
3415 assert(image != (Image *) NULL);
3416 assert(image->signature == MagickSignature);
3417 if (image->debug != MagickFalse)
3418 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3419 if (image->storage_class == PseudoClass)
3420 for (i=0; i < (ssize_t) image->colors; i++)
3425 if( IfMagickTrue(grayscale) )
3426 if ((image->colormap[i].red != image->colormap[i].green) ||
3427 (image->colormap[i].green != image->colormap[i].blue))
3429 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3430 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3431 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3432 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3433 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3434 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3441 image_view=AcquireAuthenticCacheView(image,exception);
3442 if( IfMagickTrue(grayscale) )
3444 for (y=0; y < (ssize_t) image->rows; y++)
3455 if (status == MagickFalse)
3457 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3459 if (q == (Quantum *) NULL)
3464 for (x=0; x < (ssize_t) image->columns; x++)
3469 if ((GetPixelReadMask(image,q) == 0) ||
3470 IfMagickTrue(IsPixelGray(image,q)))
3472 q+=GetPixelChannels(image);
3475 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3477 PixelChannel channel=GetPixelChannelChannel(image,i);
3478 PixelTrait traits=GetPixelChannelTraits(image,channel);
3479 if ((traits & UpdatePixelTrait) == 0)
3481 q[i]=QuantumRange-q[i];
3483 q+=GetPixelChannels(image);
3485 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3486 if( IfMagickFalse(sync) )
3488 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3493 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3494 #pragma omp critical (MagickCore_NegateImage)
3496 proceed=SetImageProgress(image,NegateImageTag,progress++,
3498 if( IfMagickFalse(proceed) )
3502 image_view=DestroyCacheView(image_view);
3508 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3509 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3510 magick_threads(image,image,image->rows,1)
3512 for (y=0; y < (ssize_t) image->rows; y++)
3520 if (status == MagickFalse)
3522 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3523 if (q == (Quantum *) NULL)
3528 for (x=0; x < (ssize_t) image->columns; x++)
3533 if (GetPixelReadMask(image,q) == 0)
3535 q+=GetPixelChannels(image);
3538 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3540 PixelChannel channel=GetPixelChannelChannel(image,i);
3541 PixelTrait traits=GetPixelChannelTraits(image,channel);
3542 if ((traits & UpdatePixelTrait) == 0)
3544 q[i]=QuantumRange-q[i];
3546 q+=GetPixelChannels(image);
3548 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3550 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3555 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3556 #pragma omp critical (MagickCore_NegateImage)
3558 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3559 if( IfMagickFalse(proceed) )
3563 image_view=DestroyCacheView(image_view);
3568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3572 % N o r m a l i z e I m a g e %
3576 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3578 % The NormalizeImage() method enhances the contrast of a color image by
3579 % mapping the darkest 2 percent of all pixel to black and the brightest
3580 % 1 percent to white.
3582 % The format of the NormalizeImage method is:
3584 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3586 % A description of each parameter follows:
3588 % o image: the image.
3590 % o exception: return any errors or warnings in this structure.
3593 MagickExport MagickBooleanType NormalizeImage(Image *image,
3594 ExceptionInfo *exception)
3600 black_point=(double) image->columns*image->rows*0.0015;
3601 white_point=(double) image->columns*image->rows*0.9995;
3602 return(ContrastStretchImage(image,black_point,white_point,exception));
3606 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3610 % S i g m o i d a l C o n t r a s t I m a g e %
3614 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3616 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3617 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3618 % sigmoidal transfer function without saturating highlights or shadows.
3619 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3620 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3621 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3622 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3625 % The format of the SigmoidalContrastImage method is:
3627 % MagickBooleanType SigmoidalContrastImage(Image *image,
3628 % const MagickBooleanType sharpen,const char *levels,
3629 % ExceptionInfo *exception)
3631 % A description of each parameter follows:
3633 % o image: the image.
3635 % o sharpen: Increase or decrease image contrast.
3637 % o contrast: strength of the contrast, the larger the number the more
3638 % 'threshold-like' it becomes.
3640 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3642 % o exception: return any errors or warnings in this structure.
3647 ImageMagick 6 has a version of this function which uses LUTs.
3651 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3654 The first version, based on the hyperbolic tangent tanh, when combined with
3655 the scaling step, is an exact arithmetic clone of the the sigmoid function
3656 based on the logistic curve. The equivalence is based on the identity
3658 1/(1+exp(-t)) = (1+tanh(t/2))/2
3660 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3661 scaled sigmoidal derivation is invariant under affine transformations of
3664 The tanh version is almost certainly more accurate and cheaper. The 0.5
3665 factor in the argument is to clone the legacy ImageMagick behavior. The
3666 reason for making the define depend on atanh even though it only uses tanh
3667 has to do with the construction of the inverse of the scaled sigmoidal.
3669 #if defined(MAGICKCORE_HAVE_ATANH)
3670 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3672 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3675 Scaled sigmoidal function:
3677 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3678 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3680 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3681 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3682 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3683 zero. This is fixed below by exiting immediately when contrast is small,
3684 leaving the image (or colormap) unmodified. This appears to be safe because
3685 the series expansion of the logistic sigmoidal function around x=b is
3689 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3691 #define ScaledSigmoidal(a,b,x) ( \
3692 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3693 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3695 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3696 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3697 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3698 when creating a LUT from in gamut values, hence the branching. In
3699 addition, HDRI may have out of gamut values.
3700 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3701 It is only a right inverse. This is unavoidable.
3703 static inline double InverseScaledSigmoidal(const double a,const double b,
3706 const double sig0=Sigmoidal(a,b,0.0);
3707 const double sig1=Sigmoidal(a,b,1.0);
3708 const double argument=(sig1-sig0)*x+sig0;
3709 const double clamped=
3711 #if defined(MAGICKCORE_HAVE_ATANH)
3712 argument < -1+MagickEpsilon
3716 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3718 return(b+(2.0/a)*atanh(clamped));
3720 argument < MagickEpsilon
3724 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3726 return(b-log(1.0/clamped-1.0)/a);
3730 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3731 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3732 ExceptionInfo *exception)
3734 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3735 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3736 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3737 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3738 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3755 assert(image != (Image *) NULL);
3756 assert(image->signature == MagickSignature);
3757 if (image->debug != MagickFalse)
3758 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3760 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3761 case nothing is done.
3763 if (contrast < MagickEpsilon)
3766 Sigmoidal-contrast enhance colormap.
3768 if (image->storage_class == PseudoClass)
3773 if( IfMagickTrue(sharpen) )
3774 for (i=0; i < (ssize_t) image->colors; i++)
3776 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3777 image->colormap[i].red=(MagickRealType) ScaledSig(
3778 image->colormap[i].red);
3779 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3780 image->colormap[i].green=(MagickRealType) ScaledSig(
3781 image->colormap[i].green);
3782 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3783 image->colormap[i].blue=(MagickRealType) ScaledSig(
3784 image->colormap[i].blue);
3785 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3786 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3787 image->colormap[i].alpha);
3790 for (i=0; i < (ssize_t) image->colors; i++)
3792 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3793 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3794 image->colormap[i].red);
3795 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3796 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3797 image->colormap[i].green);
3798 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3799 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3800 image->colormap[i].blue);
3801 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3802 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3803 image->colormap[i].alpha);
3807 Sigmoidal-contrast enhance image.
3811 image_view=AcquireAuthenticCacheView(image,exception);
3812 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3813 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3814 magick_threads(image,image,image->rows,1)
3816 for (y=0; y < (ssize_t) image->rows; y++)
3824 if (status == MagickFalse)
3826 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3827 if (q == (Quantum *) NULL)
3832 for (x=0; x < (ssize_t) image->columns; x++)
3837 if (GetPixelReadMask(image,q) == 0)
3839 q+=GetPixelChannels(image);
3842 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3844 PixelChannel channel=GetPixelChannelChannel(image,i);
3845 PixelTrait traits=GetPixelChannelTraits(image,channel);
3846 if ((traits & UpdatePixelTrait) == 0)
3848 if( IfMagickTrue(sharpen) )
3849 q[i]=ScaledSig(q[i]);
3851 q[i]=InverseScaledSig(q[i]);
3853 q+=GetPixelChannels(image);
3855 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3857 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3862 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3863 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3865 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3867 if( IfMagickFalse(proceed) )
3871 image_view=DestroyCacheView(image_view);