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-2012 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/cache.h"
46 #include "MagickCore/cache-view.h"
47 #include "MagickCore/color.h"
48 #include "MagickCore/color-private.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/composite-private.h"
51 #include "MagickCore/enhance.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/fx.h"
55 #include "MagickCore/gem.h"
56 #include "MagickCore/gem-private.h"
57 #include "MagickCore/geometry.h"
58 #include "MagickCore/histogram.h"
59 #include "MagickCore/image.h"
60 #include "MagickCore/image-private.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/monitor.h"
63 #include "MagickCore/monitor-private.h"
64 #include "MagickCore/option.h"
65 #include "MagickCore/pixel-accessor.h"
66 #include "MagickCore/quantum.h"
67 #include "MagickCore/quantum-private.h"
68 #include "MagickCore/resample.h"
69 #include "MagickCore/resample-private.h"
70 #include "MagickCore/statistic.h"
71 #include "MagickCore/string_.h"
72 #include "MagickCore/string-private.h"
73 #include "MagickCore/thread-private.h"
74 #include "MagickCore/token.h"
75 #include "MagickCore/xml-tree.h"
76 #include "MagickCore/xml-tree-private.h"
79 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83 % A u t o G a m m a I m a g e %
87 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89 % AutoGammaImage() extract the 'mean' from the image and adjust the image
90 % to try make set its gamma appropriatally.
92 % The format of the AutoGammaImage method is:
94 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
96 % A description of each parameter follows:
98 % o image: The image to auto-level
100 % o exception: return any errors or warnings in this structure.
103 MagickExport MagickBooleanType AutoGammaImage(Image *image,
104 ExceptionInfo *exception)
119 if (image->channel_mask == DefaultChannels)
122 Apply gamma correction equally across all given channels.
124 (void) GetImageMean(image,&mean,&sans,exception);
125 gamma=log(mean*QuantumScale)/log_mean;
126 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
129 Auto-gamma each channel separately.
132 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
143 channel=GetPixelChannelMapChannel(image,i);
144 traits=GetPixelChannelMapTraits(image,channel);
145 if ((traits & UpdatePixelTrait) == 0)
147 channel_mask=SetPixelChannelMask(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) SetPixelChannelMask(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"
322 assert(image != (Image *) NULL);
323 assert(image->signature == MagickSignature);
324 if (image->debug != MagickFalse)
325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
326 assert(clut_image != (Image *) NULL);
327 assert(clut_image->signature == MagickSignature);
328 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
330 clut_map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
332 if (clut_map == (double *) NULL)
333 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
340 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
341 clut_view=AcquireCacheView(clut_image);
342 #if defined(MAGICKCORE_OPENMP_SUPPORT)
343 #pragma omp parallel for schedule(static,4)
345 for (x=0; x <= (ssize_t) MaxMap; x++)
350 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
351 (void) InterpolatePixelChannel(clut_image,clut_view,(PixelChannel) i,
352 method,QuantumScale*x*(clut_image->columns-adjust),QuantumScale*x*
353 (clut_image->rows-adjust),clut_map+x*GetPixelChannels(clut_image)+i,
356 clut_view=DestroyCacheView(clut_view);
357 image_view=AcquireCacheView(image);
358 #if defined(MAGICKCORE_OPENMP_SUPPORT)
359 #pragma omp parallel for schedule(static,4) shared(progress,status)
361 for (y=0; y < (ssize_t) image->rows; y++)
369 if (status == MagickFalse)
371 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
372 if (q == (Quantum *) NULL)
377 for (x=0; x < (ssize_t) image->columns; x++)
382 for (i=0; i < (ssize_t) GetPixelChannels(clut_image); i++)
391 channel=GetPixelChannelMapChannel(clut_image,i);
392 clut_traits=GetPixelChannelMapTraits(clut_image,channel);
393 traits=GetPixelChannelMapTraits(clut_image,channel);
394 if ((traits == UndefinedPixelTrait) ||
395 (clut_traits == UndefinedPixelTrait) ||
396 ((traits & UpdatePixelTrait) == 0))
398 SetPixelChannel(clut_image,channel,ClampToQuantum(clut_map[
399 ScaleQuantumToMap(GetPixelChannel(clut_image,channel,q))*
400 GetPixelChannels(clut_image)+channel]),q);
402 q+=GetPixelChannels(image);
404 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
406 if (image->progress_monitor != (MagickProgressMonitor) NULL)
411 #if defined(MAGICKCORE_OPENMP_SUPPORT)
412 #pragma omp critical (MagickCore_ClutImage)
414 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
415 if (proceed == MagickFalse)
419 image_view=DestroyCacheView(image_view);
420 clut_map=(double *) RelinquishMagickMemory(clut_map);
421 if ((clut_image->matte != MagickFalse) &&
422 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
423 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
432 % C o l o r D e c i s i o n L i s t I m a g e %
436 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
439 % (CCC) file which solely contains one or more color corrections and applies
440 % the correction to the image. Here is a sample CCC file:
442 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
443 % <ColorCorrection id="cc03345">
445 % <Slope> 0.9 1.2 0.5 </Slope>
446 % <Offset> 0.4 -0.5 0.6 </Offset>
447 % <Power> 1.0 0.8 1.5 </Power>
450 % <Saturation> 0.85 </Saturation>
453 % </ColorCorrectionCollection>
455 % which includes the slop, offset, and power for each of the RGB channels
456 % as well as the saturation.
458 % The format of the ColorDecisionListImage method is:
460 % MagickBooleanType ColorDecisionListImage(Image *image,
461 % const char *color_correction_collection,ExceptionInfo *exception)
463 % A description of each parameter follows:
465 % o image: the image.
467 % o color_correction_collection: the color correction collection in XML.
469 % o exception: return any errors or warnings in this structure.
472 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
473 const char *color_correction_collection,ExceptionInfo *exception)
475 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
477 typedef struct _Correction
485 typedef struct _ColorCorrection
500 token[MaxTextExtent];
531 Allocate and initialize cdl maps.
533 assert(image != (Image *) NULL);
534 assert(image->signature == MagickSignature);
535 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537 if (color_correction_collection == (const char *) NULL)
539 ccc=NewXMLTree((const char *) color_correction_collection,exception);
540 if (ccc == (XMLTreeInfo *) NULL)
542 cc=GetXMLTreeChild(ccc,"ColorCorrection");
543 if (cc == (XMLTreeInfo *) NULL)
545 ccc=DestroyXMLTree(ccc);
548 color_correction.red.slope=1.0;
549 color_correction.red.offset=0.0;
550 color_correction.red.power=1.0;
551 color_correction.green.slope=1.0;
552 color_correction.green.offset=0.0;
553 color_correction.green.power=1.0;
554 color_correction.blue.slope=1.0;
555 color_correction.blue.offset=0.0;
556 color_correction.blue.power=1.0;
557 color_correction.saturation=0.0;
558 sop=GetXMLTreeChild(cc,"SOPNode");
559 if (sop != (XMLTreeInfo *) NULL)
566 slope=GetXMLTreeChild(sop,"Slope");
567 if (slope != (XMLTreeInfo *) NULL)
569 content=GetXMLTreeContent(slope);
570 p=(const char *) content;
571 for (i=0; (*p != '\0') && (i < 3); i++)
573 GetMagickToken(p,&p,token);
575 GetMagickToken(p,&p,token);
580 color_correction.red.slope=StringToDouble(token,(char **) NULL);
585 color_correction.green.slope=StringToDouble(token,
591 color_correction.blue.slope=StringToDouble(token,
598 offset=GetXMLTreeChild(sop,"Offset");
599 if (offset != (XMLTreeInfo *) NULL)
601 content=GetXMLTreeContent(offset);
602 p=(const char *) content;
603 for (i=0; (*p != '\0') && (i < 3); i++)
605 GetMagickToken(p,&p,token);
607 GetMagickToken(p,&p,token);
612 color_correction.red.offset=StringToDouble(token,
618 color_correction.green.offset=StringToDouble(token,
624 color_correction.blue.offset=StringToDouble(token,
631 power=GetXMLTreeChild(sop,"Power");
632 if (power != (XMLTreeInfo *) NULL)
634 content=GetXMLTreeContent(power);
635 p=(const char *) content;
636 for (i=0; (*p != '\0') && (i < 3); i++)
638 GetMagickToken(p,&p,token);
640 GetMagickToken(p,&p,token);
645 color_correction.red.power=StringToDouble(token,(char **) NULL);
650 color_correction.green.power=StringToDouble(token,
656 color_correction.blue.power=StringToDouble(token,
664 sat=GetXMLTreeChild(cc,"SATNode");
665 if (sat != (XMLTreeInfo *) NULL)
670 saturation=GetXMLTreeChild(sat,"Saturation");
671 if (saturation != (XMLTreeInfo *) NULL)
673 content=GetXMLTreeContent(saturation);
674 p=(const char *) content;
675 GetMagickToken(p,&p,token);
676 color_correction.saturation=StringToDouble(token,(char **) NULL);
679 ccc=DestroyXMLTree(ccc);
680 if (image->debug != MagickFalse)
682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
683 " Color Correction Collection:");
684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
685 " color_correction.red.slope: %g",color_correction.red.slope);
686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
687 " color_correction.red.offset: %g",color_correction.red.offset);
688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
689 " color_correction.red.power: %g",color_correction.red.power);
690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
691 " color_correction.green.slope: %g",color_correction.green.slope);
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " color_correction.green.offset: %g",color_correction.green.offset);
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.green.power: %g",color_correction.green.power);
696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
697 " color_correction.blue.slope: %g",color_correction.blue.slope);
698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
699 " color_correction.blue.offset: %g",color_correction.blue.offset);
700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
701 " color_correction.blue.power: %g",color_correction.blue.power);
702 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
703 " color_correction.saturation: %g",color_correction.saturation);
705 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
706 if (cdl_map == (PixelInfo *) NULL)
707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
709 #if defined(MAGICKCORE_OPENMP_SUPPORT)
710 #pragma omp parallel for schedule(static,4)
712 for (i=0; i <= (ssize_t) MaxMap; i++)
714 cdl_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
715 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
716 color_correction.red.offset,color_correction.red.power))));
717 cdl_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
718 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
719 color_correction.green.offset,color_correction.green.power))));
720 cdl_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
721 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
722 color_correction.blue.offset,color_correction.blue.power))));
724 if (image->storage_class == PseudoClass)
727 Apply transfer function to colormap.
729 #if defined(MAGICKCORE_OPENMP_SUPPORT)
730 #pragma omp parallel for schedule(static,4) shared(progress,status)
732 for (i=0; i < (ssize_t) image->colors; i++)
737 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
738 0.0722*image->colormap[i].blue;
739 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
740 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-
742 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
743 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-
745 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
746 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-
751 Apply transfer function to image.
755 image_view=AcquireCacheView(image);
756 #if defined(MAGICKCORE_OPENMP_SUPPORT)
757 #pragma omp parallel for schedule(static,4) shared(progress,status)
759 for (y=0; y < (ssize_t) image->rows; y++)
770 if (status == MagickFalse)
772 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
773 if (q == (Quantum *) NULL)
778 for (x=0; x < (ssize_t) image->columns; x++)
780 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
781 GetPixelBlue(image,q);
782 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
784 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
785 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
786 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
787 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
788 q+=GetPixelChannels(image);
790 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
792 if (image->progress_monitor != (MagickProgressMonitor) NULL)
797 #if defined(MAGICKCORE_OPENMP_SUPPORT)
798 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
800 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
801 progress++,image->rows);
802 if (proceed == MagickFalse)
806 image_view=DestroyCacheView(image_view);
807 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
812 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
816 % C o n t r a s t I m a g e %
820 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
822 % ContrastImage() enhances the intensity differences between the lighter and
823 % darker elements of the image. Set sharpen to a MagickTrue to increase the
824 % image contrast otherwise the contrast is reduced.
826 % The format of the ContrastImage method is:
828 % MagickBooleanType ContrastImage(Image *image,
829 % const MagickBooleanType sharpen,ExceptionInfo *exception)
831 % A description of each parameter follows:
833 % o image: the image.
835 % o sharpen: Increase or decrease image contrast.
837 % o exception: return any errors or warnings in this structure.
841 static void Contrast(const int sign,double *red,double *green,double *blue)
849 Enhance contrast: dark color become darker, light color become lighter.
851 assert(red != (double *) NULL);
852 assert(green != (double *) NULL);
853 assert(blue != (double *) NULL);
857 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
858 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
860 if (brightness > 1.0)
863 if (brightness < 0.0)
865 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
868 MagickExport MagickBooleanType ContrastImage(Image *image,
869 const MagickBooleanType sharpen,ExceptionInfo *exception)
871 #define ContrastImageTag "Contrast/Image"
891 assert(image != (Image *) NULL);
892 assert(image->signature == MagickSignature);
893 if (image->debug != MagickFalse)
894 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
895 sign=sharpen != MagickFalse ? 1 : -1;
896 if (image->storage_class == PseudoClass)
899 Contrast enhance colormap.
901 for (i=0; i < (ssize_t) image->colors; i++)
902 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
903 &image->colormap[i].blue);
906 Contrast enhance image.
910 image_view=AcquireCacheView(image);
911 #if defined(MAGICKCORE_OPENMP_SUPPORT)
912 #pragma omp parallel for schedule(static,4) shared(progress,status)
914 for (y=0; y < (ssize_t) image->rows; y++)
927 if (status == MagickFalse)
929 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
930 if (q == (Quantum *) NULL)
935 for (x=0; x < (ssize_t) image->columns; x++)
937 red=(double) GetPixelRed(image,q);
938 green=(double) GetPixelGreen(image,q);
939 blue=(double) GetPixelBlue(image,q);
940 Contrast(sign,&red,&green,&blue);
941 SetPixelRed(image,ClampToQuantum(red),q);
942 SetPixelGreen(image,ClampToQuantum(green),q);
943 SetPixelBlue(image,ClampToQuantum(blue),q);
944 q+=GetPixelChannels(image);
946 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
948 if (image->progress_monitor != (MagickProgressMonitor) NULL)
953 #if defined(MAGICKCORE_OPENMP_SUPPORT)
954 #pragma omp critical (MagickCore_ContrastImage)
956 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
957 if (proceed == MagickFalse)
961 image_view=DestroyCacheView(image_view);
966 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
970 % C o n t r a s t S t r e t c h I m a g e %
974 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
976 % ContrastStretchImage() is a simple image enhancement technique that attempts
977 % to improve the contrast in an image by `stretching' the range of intensity
978 % values it contains to span a desired range of values. It differs from the
979 % more sophisticated histogram equalization in that it can only apply a
980 % linear scaling function to the image pixel values. As a result the
981 % `enhancement' is less harsh.
983 % The format of the ContrastStretchImage method is:
985 % MagickBooleanType ContrastStretchImage(Image *image,
986 % const char *levels,ExceptionInfo *exception)
988 % A description of each parameter follows:
990 % o image: the image.
992 % o black_point: the black point.
994 % o white_point: the white point.
996 % o levels: Specify the levels where the black and white points have the
997 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
999 % o exception: return any errors or warnings in this structure.
1002 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1003 const double black_point,const double white_point,ExceptionInfo *exception)
1005 #define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1006 #define ContrastStretchImageTag "ContrastStretch/Image"
1030 Allocate histogram and stretch map.
1032 assert(image != (Image *) NULL);
1033 assert(image->signature == MagickSignature);
1034 if (image->debug != MagickFalse)
1035 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1036 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1037 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1038 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
1039 GetPixelChannels(image)*sizeof(*histogram));
1040 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1041 GetPixelChannels(image)*sizeof(*stretch_map));
1042 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1043 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1045 if (stretch_map != (double *) NULL)
1046 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1047 if (histogram != (double *) NULL)
1048 histogram=(double *) RelinquishMagickMemory(histogram);
1049 if (white != (double *) NULL)
1050 white=(double *) RelinquishMagickMemory(white);
1051 if (black != (double *) NULL)
1052 black=(double *) RelinquishMagickMemory(black);
1053 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1060 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1061 sizeof(*histogram));
1062 image_view=AcquireCacheView(image);
1063 for (y=0; y < (ssize_t) image->rows; y++)
1065 register const Quantum
1071 if (status == MagickFalse)
1073 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1074 if (p == (const Quantum *) NULL)
1079 for (x=0; x < (ssize_t) image->columns; x++)
1084 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1085 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1086 p+=GetPixelChannels(image);
1090 Find the histogram boundaries by locating the black/white levels.
1092 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1093 #pragma omp parallel for schedule(static,4) shared(progress,status)
1095 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1104 white[i]=MaxRange(QuantumRange);
1106 for (j=0; j <= (ssize_t) MaxMap; j++)
1108 intensity+=histogram[GetPixelChannels(image)*j+i];
1109 if (intensity > black_point)
1112 black[i]=(MagickRealType) j;
1114 for (j=(ssize_t) MaxMap; j != 0; j--)
1116 intensity+=histogram[GetPixelChannels(image)*j+i];
1117 if (intensity > ((double) image->columns*image->rows-white_point))
1120 white[i]=(MagickRealType) j;
1122 histogram=(double *) RelinquishMagickMemory(histogram);
1124 Stretch the histogram to create the stretched image mapping.
1126 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1127 sizeof(*stretch_map));
1128 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1129 #pragma omp parallel for schedule(static,4) shared(progress,status)
1131 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1136 for (j=0; j <= (ssize_t) MaxMap; j++)
1138 if (j < (ssize_t) black[i])
1139 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1141 if (j > (ssize_t) white[i])
1142 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1145 if (black[i] != white[i])
1146 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1147 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1148 (white[i]-black[i])));
1151 if (image->storage_class == PseudoClass)
1157 Stretch-contrast colormap.
1159 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1160 #pragma omp parallel for schedule(static,4) shared(progress,status)
1162 for (j=0; j < (ssize_t) image->colors; j++)
1164 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1166 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1167 if (black[i] != white[i])
1168 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1169 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
1171 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1173 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1174 if (black[i] != white[i])
1175 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1176 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
1178 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1180 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1181 if (black[i] != white[i])
1182 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1183 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
1185 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1187 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1188 if (black[i] != white[i])
1189 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1190 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
1195 Stretch-contrast image.
1199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1200 #pragma omp parallel for schedule(static,4) shared(progress,status)
1202 for (y=0; y < (ssize_t) image->rows; y++)
1210 if (status == MagickFalse)
1212 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1213 if (q == (Quantum *) NULL)
1218 for (x=0; x < (ssize_t) image->columns; x++)
1223 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1231 channel=GetPixelChannelMapChannel(image,i);
1232 traits=GetPixelChannelMapTraits(image,channel);
1233 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
1234 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1235 ScaleQuantumToMap(q[i])+i]);
1237 q+=GetPixelChannels(image);
1239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1246 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1247 #pragma omp critical (MagickCore_ContrastStretchImage)
1249 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1251 if (proceed == MagickFalse)
1255 image_view=DestroyCacheView(image_view);
1256 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1257 white=(double *) RelinquishMagickMemory(white);
1258 black=(double *) RelinquishMagickMemory(black);
1263 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267 % E n h a n c e I m a g e %
1271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1273 % EnhanceImage() applies a digital filter that improves the quality of a
1276 % The format of the EnhanceImage method is:
1278 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1280 % A description of each parameter follows:
1282 % o image: the image.
1284 % o exception: return any errors or warnings in this structure.
1287 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289 #define EnhancePixel(weight) \
1290 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1291 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1292 enhance_image,channel,q); \
1293 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1294 mean)*distance*distance; \
1295 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1296 QuantumRange/25.0f)) \
1298 aggregate+=(weight)*r[i]; \
1299 total_weight+=(weight); \
1301 r+=GetPixelChannels(image);
1302 #define EnhanceImageTag "Enhance/Image"
1321 Initialize enhanced image attributes.
1323 assert(image != (const Image *) NULL);
1324 assert(image->signature == MagickSignature);
1325 if (image->debug != MagickFalse)
1326 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1327 assert(exception != (ExceptionInfo *) NULL);
1328 assert(exception->signature == MagickSignature);
1329 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1331 if (enhance_image == (Image *) NULL)
1332 return((Image *) NULL);
1333 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1335 enhance_image=DestroyImage(enhance_image);
1336 return((Image *) NULL);
1343 image_view=AcquireCacheView(image);
1344 enhance_view=AcquireCacheView(enhance_image);
1345 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1346 #pragma omp parallel for schedule(static,4) shared(progress,status)
1348 for (y=0; y < (ssize_t) image->rows; y++)
1350 register const Quantum
1362 if (status == MagickFalse)
1364 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1365 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1367 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1372 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1373 for (x=0; x < (ssize_t) image->columns; x++)
1378 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1394 register const Quantum
1397 channel=GetPixelChannelMapChannel(image,i);
1398 traits=GetPixelChannelMapTraits(image,channel);
1399 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
1400 if ((traits == UndefinedPixelTrait) ||
1401 (enhance_traits == UndefinedPixelTrait))
1403 SetPixelChannel(enhance_image,channel,p[center+i],q);
1404 if ((enhance_traits & CopyPixelTrait) != 0)
1407 Compute weighted average of target pixel color components.
1412 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1413 EnhancePixel(8.0); EnhancePixel(5.0);
1414 r=p+1*GetPixelChannels(image)*(image->columns+4);
1415 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1416 EnhancePixel(20.0); EnhancePixel(8.0);
1417 r=p+2*GetPixelChannels(image)*(image->columns+4);
1418 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1419 EnhancePixel(40.0); EnhancePixel(10.0);
1420 r=p+3*GetPixelChannels(image)*(image->columns+4);
1421 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1422 EnhancePixel(20.0); EnhancePixel(8.0);
1423 r=p+4*GetPixelChannels(image)*(image->columns+4);
1424 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1425 EnhancePixel(8.0); EnhancePixel(5.0);
1426 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1429 p+=GetPixelChannels(image);
1430 q+=GetPixelChannels(enhance_image);
1432 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1434 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1439 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1440 #pragma omp critical (MagickCore_EnhanceImage)
1442 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1443 if (proceed == MagickFalse)
1447 enhance_view=DestroyCacheView(enhance_view);
1448 image_view=DestroyCacheView(image_view);
1449 return(enhance_image);
1453 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1457 % E q u a l i z e I m a g e %
1461 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463 % EqualizeImage() applies a histogram equalization to the image.
1465 % The format of the EqualizeImage method is:
1467 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1469 % A description of each parameter follows:
1471 % o image: the image.
1473 % o exception: return any errors or warnings in this structure.
1476 MagickExport MagickBooleanType EqualizeImage(Image *image,
1477 ExceptionInfo *exception)
1479 #define EqualizeImageTag "Equalize/Image"
1491 black[CompositePixelChannel],
1495 white[CompositePixelChannel];
1504 Allocate and initialize histogram arrays.
1506 assert(image != (Image *) NULL);
1507 assert(image->signature == MagickSignature);
1508 if (image->debug != MagickFalse)
1509 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1510 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1511 GetPixelChannels(image)*sizeof(*equalize_map));
1512 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1513 GetPixelChannels(image)*sizeof(*histogram));
1514 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1515 GetPixelChannels(image)*sizeof(*map));
1516 if ((equalize_map == (MagickRealType *) NULL) ||
1517 (histogram == (MagickRealType *) NULL) ||
1518 (map == (MagickRealType *) NULL))
1520 if (map != (MagickRealType *) NULL)
1521 map=(MagickRealType *) RelinquishMagickMemory(map);
1522 if (histogram != (MagickRealType *) NULL)
1523 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1524 if (equalize_map != (MagickRealType *) NULL)
1525 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
1526 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1533 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1534 sizeof(*histogram));
1535 image_view=AcquireCacheView(image);
1536 for (y=0; y < (ssize_t) image->rows; y++)
1538 register const Quantum
1544 if (status == MagickFalse)
1546 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1547 if (p == (const Quantum *) NULL)
1552 for (x=0; x < (ssize_t) image->columns; x++)
1557 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1558 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1559 p+=GetPixelChannels(image);
1563 Integrate the histogram to get the equalization map.
1565 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1566 #pragma omp parallel for schedule(static,4) shared(progress,status)
1568 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1577 for (j=0; j <= (ssize_t) MaxMap; j++)
1579 intensity+=histogram[GetPixelChannels(image)*j+i];
1580 map[GetPixelChannels(image)*j+i]=intensity;
1583 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1584 sizeof(*equalize_map));
1585 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1586 #pragma omp parallel for schedule(static,4) shared(progress,status)
1588 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1594 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1595 if (black[i] != white[i])
1596 for (j=0; j <= (ssize_t) MaxMap; j++)
1597 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1598 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1599 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1601 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1602 map=(MagickRealType *) RelinquishMagickMemory(map);
1603 if (image->storage_class == PseudoClass)
1614 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1615 #pragma omp parallel for schedule(static,4) shared(progress,status)
1617 for (j=0; j < (ssize_t) image->colors; j++)
1619 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1621 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1622 if (black[channel] != white[channel])
1623 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1624 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1627 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1629 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1630 if (black[channel] != white[channel])
1631 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1632 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1635 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1637 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1638 if (black[channel] != white[channel])
1639 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1640 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1643 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1645 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1646 if (black[channel] != white[channel])
1647 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1648 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1657 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1658 #pragma omp parallel for schedule(static,4) shared(progress,status)
1660 for (y=0; y < (ssize_t) image->rows; y++)
1668 if (status == MagickFalse)
1670 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1671 if (q == (Quantum *) NULL)
1676 for (x=0; x < (ssize_t) image->columns; x++)
1681 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1689 channel=GetPixelChannelMapChannel(image,i);
1690 traits=GetPixelChannelMapTraits(image,channel);
1691 if (((traits & UpdatePixelTrait) != 0) && (black[i] != white[i]))
1692 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1693 ScaleQuantumToMap(q[i])+i]);
1695 q+=GetPixelChannels(image);
1697 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1699 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1704 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1705 #pragma omp critical (MagickCore_EqualizeImage)
1707 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1708 if (proceed == MagickFalse)
1712 image_view=DestroyCacheView(image_view);
1713 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
1718 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1722 % G a m m a I m a g e %
1726 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728 % GammaImage() gamma-corrects a particular image channel. The same
1729 % image viewed on different devices will have perceptual differences in the
1730 % way the image's intensities are represented on the screen. Specify
1731 % individual gamma levels for the red, green, and blue channels, or adjust
1732 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1734 % You can also reduce the influence of a particular channel with a gamma
1737 % The format of the GammaImage method is:
1739 % MagickBooleanType GammaImage(Image *image,const double gamma,
1740 % ExceptionInfo *exception)
1742 % A description of each parameter follows:
1744 % o image: the image.
1746 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1748 % o gamma: the image gamma.
1751 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1752 ExceptionInfo *exception)
1754 #define GammaCorrectImageTag "GammaCorrect/Image"
1775 Allocate and initialize gamma maps.
1777 assert(image != (Image *) NULL);
1778 assert(image->signature == MagickSignature);
1779 if (image->debug != MagickFalse)
1780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1783 gamma_map=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1784 if (gamma_map == (double *) NULL)
1785 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1787 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1789 #if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
1790 #pragma omp parallel for
1792 for (i=0; i <= (ssize_t) MaxMap; i++)
1793 gamma_map[i]=(MagickRealType) ScaleMapToQuantum((
1794 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma)));
1795 if (image->storage_class == PseudoClass)
1798 Gamma-correct colormap.
1800 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1801 #pragma omp parallel for schedule(static,4) shared(progress,status)
1803 for (i=0; i < (ssize_t) image->colors; i++)
1805 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1806 image->colormap[i].red=gamma_map[
1807 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
1808 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1809 image->colormap[i].green=gamma_map[
1810 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
1811 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1812 image->colormap[i].blue=gamma_map[
1813 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
1814 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1815 image->colormap[i].alpha=gamma_map[
1816 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
1820 Gamma-correct image.
1824 image_view=AcquireCacheView(image);
1825 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1826 #pragma omp parallel for schedule(static,4) shared(progress,status)
1828 for (y=0; y < (ssize_t) image->rows; y++)
1836 if (status == MagickFalse)
1838 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1839 if (q == (Quantum *) NULL)
1844 for (x=0; x < (ssize_t) image->columns; x++)
1849 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1857 channel=GetPixelChannelMapChannel(image,i);
1858 traits=GetPixelChannelMapTraits(image,channel);
1859 if ((traits & UpdatePixelTrait) != 0)
1860 q[i]=ClampToQuantum(gamma_map[ScaleQuantumToMap(q[i])]);
1862 q+=GetPixelChannels(image);
1864 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1866 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1871 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1872 #pragma omp critical (MagickCore_GammaImage)
1874 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1876 if (proceed == MagickFalse)
1880 image_view=DestroyCacheView(image_view);
1881 gamma_map=(double *) RelinquishMagickMemory(gamma_map);
1882 if (image->gamma != 0.0)
1883 image->gamma*=gamma;
1888 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1892 % H a l d C l u t I m a g e %
1896 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1898 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
1899 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1900 % Create it with the HALD coder. You can apply any color transformation to
1901 % the Hald image and then use this method to apply the transform to the
1904 % The format of the HaldClutImage method is:
1906 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1907 % ExceptionInfo *exception)
1909 % A description of each parameter follows:
1911 % o image: the image, which is replaced by indexed CLUT values
1913 % o hald_image: the color lookup table image for replacement color values.
1915 % o exception: return any errors or warnings in this structure.
1919 static inline size_t MagickMin(const size_t x,const size_t y)
1926 MagickExport MagickBooleanType HaldClutImage(Image *image,
1927 const Image *hald_image,ExceptionInfo *exception)
1929 #define HaldClutImageTag "Clut/Image"
1931 typedef struct _HaldInfo
1963 assert(image != (Image *) NULL);
1964 assert(image->signature == MagickSignature);
1965 if (image->debug != MagickFalse)
1966 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1967 assert(hald_image != (Image *) NULL);
1968 assert(hald_image->signature == MagickSignature);
1969 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1970 return(MagickFalse);
1971 if (image->matte == MagickFalse)
1972 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
1978 length=MagickMin(hald_image->columns,hald_image->rows);
1979 for (level=2; (level*level*level) < length; level++) ;
1981 cube_size=level*level;
1982 width=(double) hald_image->columns;
1983 GetPixelInfo(hald_image,&zero);
1984 image_view=AcquireCacheView(image);
1985 hald_view=AcquireCacheView(hald_image);
1986 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1987 #pragma omp parallel for schedule(static,4) shared(progress,status)
1989 for (y=0; y < (ssize_t) image->rows; y++)
1997 if (status == MagickFalse)
1999 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2000 if (q == (Quantum *) NULL)
2005 for (x=0; x < (ssize_t) image->columns; x++)
2020 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2021 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2022 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2023 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2024 point.x-=floor(point.x);
2025 point.y-=floor(point.y);
2026 point.z-=floor(point.z);
2028 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2029 fmod(offset,width),floor(offset/width),&pixel1,exception);
2031 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2032 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2034 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2037 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2038 fmod(offset,width),floor(offset/width),&pixel1,exception);
2039 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2040 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2042 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2045 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2047 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2048 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2049 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2050 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2051 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2052 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2053 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2054 (image->colorspace == CMYKColorspace))
2055 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2056 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2057 (image->matte != MagickFalse))
2058 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2059 q+=GetPixelChannels(image);
2061 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2063 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2068 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2069 #pragma omp critical (MagickCore_HaldClutImage)
2071 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2072 if (proceed == MagickFalse)
2076 hald_view=DestroyCacheView(hald_view);
2077 image_view=DestroyCacheView(image_view);
2082 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2086 % L e v e l I m a g e %
2090 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2092 % LevelImage() adjusts the levels of a particular image channel by
2093 % scaling the colors falling between specified white and black points to
2094 % the full available quantum range.
2096 % The parameters provided represent the black, and white points. The black
2097 % point specifies the darkest color in the image. Colors darker than the
2098 % black point are set to zero. White point specifies the lightest color in
2099 % the image. Colors brighter than the white point are set to the maximum
2102 % If a '!' flag is given, map black and white colors to the given levels
2103 % rather than mapping those levels to black and white. See
2104 % LevelizeImage() below.
2106 % Gamma specifies a gamma correction to apply to the image.
2108 % The format of the LevelImage method is:
2110 % MagickBooleanType LevelImage(Image *image,const double black_point,
2111 % const double white_point,const double gamma,ExceptionInfo *exception)
2113 % A description of each parameter follows:
2115 % o image: the image.
2117 % o black_point: The level to map zero (black) to.
2119 % o white_point: The level to map QuantumRange (white) to.
2121 % o exception: return any errors or warnings in this structure.
2125 static inline MagickRealType LevelPixel(const double black_point,
2126 const double white_point,const double gamma,const MagickRealType pixel)
2132 if (pixel < black_point)
2134 if (pixel > white_point)
2135 return((MagickRealType) QuantumRange);
2136 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2137 level_pixel=(MagickRealType) QuantumRange*pow(scale*((double) pixel-
2138 black_point),1.0/gamma);
2139 return(level_pixel);
2142 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2143 const double white_point,const double gamma,ExceptionInfo *exception)
2145 #define LevelImageTag "Level/Image"
2163 Allocate and initialize levels map.
2165 assert(image != (Image *) NULL);
2166 assert(image->signature == MagickSignature);
2167 if (image->debug != MagickFalse)
2168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2169 if (image->storage_class == PseudoClass)
2170 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2171 #pragma omp parallel for schedule(static,4) shared(progress,status)
2173 for (i=0; i < (ssize_t) image->colors; i++)
2178 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2179 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2180 white_point,gamma,image->colormap[i].red));
2181 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2182 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2183 white_point,gamma,image->colormap[i].green));
2184 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2185 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2186 white_point,gamma,image->colormap[i].blue));
2187 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2188 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2189 white_point,gamma,image->colormap[i].alpha));
2196 image_view=AcquireCacheView(image);
2197 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2198 #pragma omp parallel for schedule(static,4) shared(progress,status)
2200 for (y=0; y < (ssize_t) image->rows; y++)
2208 if (status == MagickFalse)
2210 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2211 if (q == (Quantum *) NULL)
2216 for (x=0; x < (ssize_t) image->columns; x++)
2221 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2229 channel=GetPixelChannelMapChannel(image,i);
2230 traits=GetPixelChannelMapTraits(image,channel);
2231 if (traits == UndefinedPixelTrait)
2233 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2234 (MagickRealType) q[i]));
2236 q+=GetPixelChannels(image);
2238 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2240 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2245 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2246 #pragma omp critical (MagickCore_LevelImage)
2248 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2249 if (proceed == MagickFalse)
2253 image_view=DestroyCacheView(image_view);
2258 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2262 % L e v e l i z e I m a g e %
2266 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2268 % LevelizeImage() applies the reversed LevelImage() operation to just
2269 % the specific channels specified. It compresses the full range of color
2270 % values, so that they lie between the given black and white points. Gamma is
2271 % applied before the values are mapped.
2273 % LevelizeImage() can be called with by using a +level command line
2274 % API option, or using a '!' on a -level or LevelImage() geometry string.
2276 % It can be used for example de-contrast a greyscale image to the exact
2277 % levels specified. Or by using specific levels for each channel of an image
2278 % you can convert a gray-scale image to any linear color gradient, according
2281 % The format of the LevelizeImage method is:
2283 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2284 % const double white_point,const double gamma,ExceptionInfo *exception)
2286 % A description of each parameter follows:
2288 % o image: the image.
2290 % o black_point: The level to map zero (black) to.
2292 % o white_point: The level to map QuantumRange (white) to.
2294 % o gamma: adjust gamma by this factor before mapping values.
2296 % o exception: return any errors or warnings in this structure.
2299 MagickExport MagickBooleanType LevelizeImage(Image *image,
2300 const double black_point,const double white_point,const double gamma,
2301 ExceptionInfo *exception)
2303 #define LevelizeImageTag "Levelize/Image"
2304 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2305 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2324 Allocate and initialize levels map.
2326 assert(image != (Image *) NULL);
2327 assert(image->signature == MagickSignature);
2328 if (image->debug != MagickFalse)
2329 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2330 if (image->storage_class == PseudoClass)
2331 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2332 #pragma omp parallel for schedule(static,4) shared(progress,status)
2334 for (i=0; i < (ssize_t) image->colors; i++)
2339 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2340 image->colormap[i].red=(double) LevelizeValue(
2341 image->colormap[i].red);
2342 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2343 image->colormap[i].green=(double) LevelizeValue(
2344 image->colormap[i].green);
2345 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2346 image->colormap[i].blue=(double) LevelizeValue(
2347 image->colormap[i].blue);
2348 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2349 image->colormap[i].alpha=(double) LevelizeValue(
2350 image->colormap[i].alpha);
2357 image_view=AcquireCacheView(image);
2358 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2359 #pragma omp parallel for schedule(static,4) shared(progress,status)
2361 for (y=0; y < (ssize_t) image->rows; y++)
2369 if (status == MagickFalse)
2371 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2372 if (q == (Quantum *) NULL)
2377 for (x=0; x < (ssize_t) image->columns; x++)
2382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2390 channel=GetPixelChannelMapChannel(image,i);
2391 traits=GetPixelChannelMapTraits(image,channel);
2392 if (traits == UndefinedPixelTrait)
2394 q[i]=LevelizeValue(q[i]);
2396 q+=GetPixelChannels(image);
2398 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2400 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2405 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2406 #pragma omp critical (MagickCore_LevelizeImage)
2408 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2409 if (proceed == MagickFalse)
2413 image_view=DestroyCacheView(image_view);
2418 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2422 % L e v e l I m a g e C o l o r s %
2426 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2428 % LevelImageColors() maps the given color to "black" and "white" values,
2429 % linearly spreading out the colors, and level values on a channel by channel
2430 % bases, as per LevelImage(). The given colors allows you to specify
2431 % different level ranges for each of the color channels separately.
2433 % If the boolean 'invert' is set true the image values will modifyed in the
2434 % reverse direction. That is any existing "black" and "white" colors in the
2435 % image will become the color values given, with all other values compressed
2436 % appropriatally. This effectivally maps a greyscale gradient into the given
2439 % The format of the LevelImageColors method is:
2441 % MagickBooleanType LevelImageColors(Image *image,
2442 % const PixelInfo *black_color,const PixelInfo *white_color,
2443 % const MagickBooleanType invert,ExceptionInfo *exception)
2445 % A description of each parameter follows:
2447 % o image: the image.
2449 % o black_color: The color to map black to/from
2451 % o white_point: The color to map white to/from
2453 % o invert: if true map the colors (levelize), rather than from (level)
2455 % o exception: return any errors or warnings in this structure.
2458 MagickExport MagickBooleanType LevelImageColors(Image *image,
2459 const PixelInfo *black_color,const PixelInfo *white_color,
2460 const MagickBooleanType invert,ExceptionInfo *exception)
2469 Allocate and initialize levels map.
2471 assert(image != (Image *) NULL);
2472 assert(image->signature == MagickSignature);
2473 if (image->debug != MagickFalse)
2474 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2476 if (invert == MagickFalse)
2478 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2480 channel_mask=SetPixelChannelMask(image,RedChannel);
2481 status|=LevelImage(image,black_color->red,white_color->red,1.0,
2483 (void) SetPixelChannelMask(image,channel_mask);
2485 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2487 channel_mask=SetPixelChannelMask(image,GreenChannel);
2488 status|=LevelImage(image,black_color->green,white_color->green,1.0,
2490 (void) SetPixelChannelMask(image,channel_mask);
2492 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2494 channel_mask=SetPixelChannelMask(image,BlueChannel);
2495 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2497 (void) SetPixelChannelMask(image,channel_mask);
2499 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2500 (image->colorspace == CMYKColorspace))
2502 channel_mask=SetPixelChannelMask(image,BlackChannel);
2503 status|=LevelImage(image,black_color->black,white_color->black,1.0,
2505 (void) SetPixelChannelMask(image,channel_mask);
2507 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2508 (image->matte == MagickTrue))
2510 channel_mask=SetPixelChannelMask(image,AlphaChannel);
2511 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2513 (void) SetPixelChannelMask(image,channel_mask);
2518 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2520 channel_mask=SetPixelChannelMask(image,RedChannel);
2521 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2523 (void) SetPixelChannelMask(image,channel_mask);
2525 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2527 channel_mask=SetPixelChannelMask(image,GreenChannel);
2528 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2530 (void) SetPixelChannelMask(image,channel_mask);
2532 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2534 channel_mask=SetPixelChannelMask(image,BlueChannel);
2535 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2537 (void) SetPixelChannelMask(image,channel_mask);
2539 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2540 (image->colorspace == CMYKColorspace))
2542 channel_mask=SetPixelChannelMask(image,BlackChannel);
2543 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2545 (void) SetPixelChannelMask(image,channel_mask);
2547 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2548 (image->matte == MagickTrue))
2550 channel_mask=SetPixelChannelMask(image,AlphaChannel);
2551 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2553 (void) SetPixelChannelMask(image,channel_mask);
2556 return(status == 0 ? MagickFalse : MagickTrue);
2560 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2564 % L i n e a r S t r e t c h I m a g e %
2568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570 % LinearStretchImage() discards any pixels below the black point and above
2571 % the white point and levels the remaining pixels.
2573 % The format of the LinearStretchImage method is:
2575 % MagickBooleanType LinearStretchImage(Image *image,
2576 % const double black_point,const double white_point,
2577 % ExceptionInfo *exception)
2579 % A description of each parameter follows:
2581 % o image: the image.
2583 % o black_point: the black point.
2585 % o white_point: the white point.
2587 % o exception: return any errors or warnings in this structure.
2590 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2591 const double black_point,const double white_point,ExceptionInfo *exception)
2593 #define LinearStretchImageTag "LinearStretch/Image"
2611 Allocate histogram and linear map.
2613 assert(image != (Image *) NULL);
2614 assert(image->signature == MagickSignature);
2615 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2616 sizeof(*histogram));
2617 if (histogram == (MagickRealType *) NULL)
2618 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2623 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2624 image_view=AcquireCacheView(image);
2625 for (y=0; y < (ssize_t) image->rows; y++)
2627 register const Quantum
2633 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2634 if (p == (const Quantum *) NULL)
2636 for (x=0; x < (ssize_t) image->columns; x++)
2638 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
2639 p+=GetPixelChannels(image);
2642 image_view=DestroyCacheView(image_view);
2644 Find the histogram boundaries by locating the black and white point levels.
2647 for (black=0; black < (ssize_t) MaxMap; black++)
2649 intensity+=histogram[black];
2650 if (intensity >= black_point)
2654 for (white=(ssize_t) MaxMap; white != 0; white--)
2656 intensity+=histogram[white];
2657 if (intensity >= white_point)
2660 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
2661 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2666 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2670 % M o d u l a t e I m a g e %
2674 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2676 % ModulateImage() lets you control the brightness, saturation, and hue
2677 % of an image. Modulate represents the brightness, saturation, and hue
2678 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2679 % modulation is lightness, saturation, and hue. And if the colorspace is
2680 % HWB, use blackness, whiteness, and hue.
2682 % The format of the ModulateImage method is:
2684 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2685 % ExceptionInfo *exception)
2687 % A description of each parameter follows:
2689 % o image: the image.
2691 % o modulate: Define the percent change in brightness, saturation, and hue.
2693 % o exception: return any errors or warnings in this structure.
2697 static void ModulateHSB(const double percent_hue,
2698 const double percent_saturation,const double percent_brightness,double *red,
2699 double *green,double *blue)
2707 Increase or decrease color brightness, saturation, or hue.
2709 assert(red != (double *) NULL);
2710 assert(green != (double *) NULL);
2711 assert(blue != (double *) NULL);
2712 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2713 hue+=0.5*(0.01*percent_hue-1.0);
2718 saturation*=0.01*percent_saturation;
2719 brightness*=0.01*percent_brightness;
2720 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2723 static void ModulateHSL(const double percent_hue,
2724 const double percent_saturation,const double percent_lightness,double *red,
2725 double *green,double *blue)
2733 Increase or decrease color lightness, saturation, or hue.
2735 assert(red != (double *) NULL);
2736 assert(green != (double *) NULL);
2737 assert(blue != (double *) NULL);
2738 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2739 hue+=0.5*(0.01*percent_hue-1.0);
2744 saturation*=0.01*percent_saturation;
2745 lightness*=0.01*percent_lightness;
2746 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2749 static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,double *red,double *green,double *blue)
2757 Increase or decrease color blackness, whiteness, or hue.
2759 assert(red != (double *) NULL);
2760 assert(green != (double *) NULL);
2761 assert(blue != (double *) NULL);
2762 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2763 hue+=0.5*(0.01*percent_hue-1.0);
2768 blackness*=0.01*percent_blackness;
2769 whiteness*=0.01*percent_whiteness;
2770 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2773 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2774 ExceptionInfo *exception)
2776 #define ModulateImageTag "Modulate/Image"
2811 Initialize modulate table.
2813 assert(image != (Image *) NULL);
2814 assert(image->signature == MagickSignature);
2815 if (image->debug != MagickFalse)
2816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2817 if (modulate == (char *) NULL)
2818 return(MagickFalse);
2819 flags=ParseGeometry(modulate,&geometry_info);
2820 percent_brightness=geometry_info.rho;
2821 percent_saturation=geometry_info.sigma;
2822 if ((flags & SigmaValue) == 0)
2823 percent_saturation=100.0;
2824 percent_hue=geometry_info.xi;
2825 if ((flags & XiValue) == 0)
2827 colorspace=UndefinedColorspace;
2828 artifact=GetImageArtifact(image,"modulate:colorspace");
2829 if (artifact != (const char *) NULL)
2830 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
2831 MagickFalse,artifact);
2832 if (image->storage_class == PseudoClass)
2837 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2838 #pragma omp parallel for schedule(static,4) shared(progress,status)
2840 for (i=0; i < (ssize_t) image->colors; i++)
2845 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2846 &image->colormap[i].red,&image->colormap[i].green,
2847 &image->colormap[i].blue);
2853 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2854 &image->colormap[i].red,&image->colormap[i].green,
2855 &image->colormap[i].blue);
2860 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2861 &image->colormap[i].red,&image->colormap[i].green,
2862 &image->colormap[i].blue);
2872 image_view=AcquireCacheView(image);
2873 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2874 #pragma omp parallel for schedule(static,4) shared(progress,status)
2876 for (y=0; y < (ssize_t) image->rows; y++)
2889 if (status == MagickFalse)
2891 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2892 if (q == (Quantum *) NULL)
2897 for (x=0; x < (ssize_t) image->columns; x++)
2899 red=(double) GetPixelRed(image,q);
2900 green=(double) GetPixelGreen(image,q);
2901 blue=(double) GetPixelBlue(image,q);
2906 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2913 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2919 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2924 SetPixelRed(image,ClampToQuantum(red),q);
2925 SetPixelGreen(image,ClampToQuantum(green),q);
2926 SetPixelBlue(image,ClampToQuantum(blue),q);
2927 q+=GetPixelChannels(image);
2929 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2931 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2936 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2937 #pragma omp critical (MagickCore_ModulateImage)
2939 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
2940 if (proceed == MagickFalse)
2944 image_view=DestroyCacheView(image_view);
2949 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2953 % N e g a t e I m a g e %
2957 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2959 % NegateImage() negates the colors in the reference image. The grayscale
2960 % option means that only grayscale values within the image are negated.
2962 % The format of the NegateImage method is:
2964 % MagickBooleanType NegateImage(Image *image,
2965 % const MagickBooleanType grayscale,ExceptionInfo *exception)
2967 % A description of each parameter follows:
2969 % o image: the image.
2971 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
2973 % o exception: return any errors or warnings in this structure.
2976 MagickExport MagickBooleanType NegateImage(Image *image,
2977 const MagickBooleanType grayscale,ExceptionInfo *exception)
2979 #define NegateImageTag "Negate/Image"
2996 assert(image != (Image *) NULL);
2997 assert(image->signature == MagickSignature);
2998 if (image->debug != MagickFalse)
2999 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3000 if (image->storage_class == PseudoClass)
3005 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3006 #pragma omp parallel for schedule(static,4) shared(progress,status)
3008 for (i=0; i < (ssize_t) image->colors; i++)
3010 if (grayscale != MagickFalse)
3011 if ((image->colormap[i].red != image->colormap[i].green) ||
3012 (image->colormap[i].green != image->colormap[i].blue))
3014 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3015 image->colormap[i].red=(Quantum) QuantumRange-
3016 image->colormap[i].red;
3017 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3018 image->colormap[i].green=(Quantum) QuantumRange-
3019 image->colormap[i].green;
3020 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3021 image->colormap[i].blue=(Quantum) QuantumRange-
3022 image->colormap[i].blue;
3030 image_view=AcquireCacheView(image);
3031 if (grayscale != MagickFalse)
3033 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3034 #pragma omp parallel for schedule(static,4) shared(progress,status)
3036 for (y=0; y < (ssize_t) image->rows; y++)
3047 if (status == MagickFalse)
3049 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3051 if (q == (Quantum *) NULL)
3056 for (x=0; x < (ssize_t) image->columns; x++)
3061 if (IsPixelGray(image,q) != MagickFalse)
3063 q+=GetPixelChannels(image);
3066 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3074 channel=GetPixelChannelMapChannel(image,i);
3075 traits=GetPixelChannelMapTraits(image,channel);
3076 if ((traits & UpdatePixelTrait) != 0)
3077 q[i]=QuantumRange-q[i];
3079 q+=GetPixelChannels(image);
3081 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3082 if (sync == MagickFalse)
3084 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3089 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3090 #pragma omp critical (MagickCore_NegateImage)
3092 proceed=SetImageProgress(image,NegateImageTag,progress++,
3094 if (proceed == MagickFalse)
3098 image_view=DestroyCacheView(image_view);
3104 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3105 #pragma omp parallel for schedule(static,4) shared(progress,status)
3107 for (y=0; y < (ssize_t) image->rows; y++)
3115 if (status == MagickFalse)
3117 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3118 if (q == (Quantum *) NULL)
3123 for (x=0; x < (ssize_t) image->columns; x++)
3128 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3136 channel=GetPixelChannelMapChannel(image,i);
3137 traits=GetPixelChannelMapTraits(image,channel);
3138 if ((traits & UpdatePixelTrait) != 0)
3139 q[i]=QuantumRange-q[i];
3141 q+=GetPixelChannels(image);
3143 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3145 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3150 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3151 #pragma omp critical (MagickCore_NegateImage)
3153 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3154 if (proceed == MagickFalse)
3158 image_view=DestroyCacheView(image_view);
3163 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3167 % N o r m a l i z e I m a g e %
3171 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3173 % NormalizeImage() enhances the contrast of a color image by mapping the
3174 % darkest 2 percent of all pixel to black and the brightest 1 percent to white.
3176 % The format of the NormalizeImage method is:
3178 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3180 % A description of each parameter follows:
3182 % o image: the image.
3184 % o exception: return any errors or warnings in this structure.
3187 MagickExport MagickBooleanType NormalizeImage(Image *image,
3188 ExceptionInfo *exception)
3194 black_point=(double) image->columns*image->rows*0.0015;
3195 white_point=(double) image->columns*image->rows*0.9995;
3196 return(ContrastStretchImage(image,black_point,white_point,exception));
3200 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3204 % S i g m o i d a l C o n t r a s t I m a g e %
3208 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3210 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3211 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3212 % sigmoidal transfer function without saturating highlights or shadows.
3213 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3214 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3215 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3216 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3219 % The format of the SigmoidalContrastImage method is:
3221 % MagickBooleanType SigmoidalContrastImage(Image *image,
3222 % const MagickBooleanType sharpen,const char *levels,
3223 % ExceptionInfo *exception)
3225 % A description of each parameter follows:
3227 % o image: the image.
3229 % o sharpen: Increase or decrease image contrast.
3231 % o alpha: strength of the contrast, the larger the number the more
3232 % 'threshold-like' it becomes.
3234 % o beta: midpoint of the function as a color value 0 to QuantumRange.
3236 % o exception: return any errors or warnings in this structure.
3239 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3240 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3241 ExceptionInfo *exception)
3243 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3264 Allocate and initialize sigmoidal maps.
3266 assert(image != (Image *) NULL);
3267 assert(image->signature == MagickSignature);
3268 if (image->debug != MagickFalse)
3269 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3270 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3271 sizeof(*sigmoidal_map));
3272 if (sigmoidal_map == (MagickRealType *) NULL)
3273 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3275 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3276 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3277 #pragma omp parallel for schedule(static,4) shared(progress,status)
3279 for (i=0; i <= (ssize_t) MaxMap; i++)
3281 if (sharpen != MagickFalse)
3283 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3284 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3285 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3286 QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/(double)
3287 QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/(double)
3288 QuantumRange)))))+0.5));
3291 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3292 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/(double)
3293 QuantumRange*contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(
3294 midpoint/(double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double)
3295 QuantumRange*contrast))))))/(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3296 contrast))+((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/(double)
3297 QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/(double) QuantumRange*
3298 contrast))))))/contrast)));
3300 if (image->storage_class == PseudoClass)
3303 Sigmoidal-contrast enhance colormap.
3305 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3306 #pragma omp parallel for schedule(static,4) shared(progress,status)
3308 for (i=0; i < (ssize_t) image->colors; i++)
3310 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3311 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3312 ClampToQuantum(image->colormap[i].red))];
3313 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3314 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3315 ClampToQuantum(image->colormap[i].green))];
3316 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3317 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3318 ClampToQuantum(image->colormap[i].blue))];
3319 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3320 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3321 ClampToQuantum(image->colormap[i].alpha))];
3325 Sigmoidal-contrast enhance image.
3329 image_view=AcquireCacheView(image);
3330 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3331 #pragma omp parallel for schedule(static,4) shared(progress,status)
3333 for (y=0; y < (ssize_t) image->rows; y++)
3341 if (status == MagickFalse)
3343 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3344 if (q == (Quantum *) NULL)
3349 for (x=0; x < (ssize_t) image->columns; x++)
3354 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3362 channel=GetPixelChannelMapChannel(image,i);
3363 traits=GetPixelChannelMapTraits(image,channel);
3364 if ((traits & UpdatePixelTrait) != 0)
3365 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
3367 q+=GetPixelChannels(image);
3369 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3371 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3376 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3377 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3379 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3381 if (proceed == MagickFalse)
3385 image_view=DestroyCacheView(image_view);
3386 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);