2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % EEEEE N N H H AAA N N CCCC EEEEE %
7 % E NN N H H A A NN N C E %
8 % EEE N N N HHHHH AAAAA N N N C EEE %
9 % E N NN H H A A N NN C E %
10 % EEEEE N N H H A A N N CCCC EEEEE %
13 % MagickCore Image Enhancement Methods %
20 % Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/color.h"
49 #include "MagickCore/color-private.h"
50 #include "MagickCore/colorspace.h"
51 #include "MagickCore/colorspace-private.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/enhance.h"
54 #include "MagickCore/exception.h"
55 #include "MagickCore/exception-private.h"
56 #include "MagickCore/fx.h"
57 #include "MagickCore/gem.h"
58 #include "MagickCore/gem-private.h"
59 #include "MagickCore/geometry.h"
60 #include "MagickCore/histogram.h"
61 #include "MagickCore/image.h"
62 #include "MagickCore/image-private.h"
63 #include "MagickCore/memory_.h"
64 #include "MagickCore/monitor.h"
65 #include "MagickCore/monitor-private.h"
66 #include "MagickCore/option.h"
67 #include "MagickCore/pixel.h"
68 #include "MagickCore/pixel-accessor.h"
69 #include "MagickCore/quantum.h"
70 #include "MagickCore/quantum-private.h"
71 #include "MagickCore/resample.h"
72 #include "MagickCore/resample-private.h"
73 #include "MagickCore/resource_.h"
74 #include "MagickCore/statistic.h"
75 #include "MagickCore/string_.h"
76 #include "MagickCore/string-private.h"
77 #include "MagickCore/thread-private.h"
78 #include "MagickCore/threshold.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/xml-tree.h"
81 #include "MagickCore/xml-tree-private.h"
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88 % A u t o G a m m a I m a g e %
92 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94 % AutoGammaImage() extract the 'mean' from the image and adjust the image
95 % to try make set its gamma appropriatally.
97 % The format of the AutoGammaImage method is:
99 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
101 % A description of each parameter follows:
103 % o image: The image to auto-level
105 % o exception: return any errors or warnings in this structure.
108 MagickExport MagickBooleanType AutoGammaImage(Image *image,
109 ExceptionInfo *exception)
124 if (image->channel_mask == DefaultChannels)
127 Apply gamma correction equally across all given channels.
129 (void) GetImageMean(image,&mean,&sans,exception);
130 gamma=log(mean*QuantumScale)/log_mean;
131 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
134 Auto-gamma each channel separately.
137 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
142 PixelChannel channel=GetPixelChannelChannel(image,i);
143 PixelTrait traits=GetPixelChannelTraits(image,channel);
144 if ((traits & UpdatePixelTrait) == 0)
146 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
147 status=GetImageMean(image,&mean,&sans,exception);
148 gamma=log(mean*QuantumScale)/log_mean;
149 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
150 (void) SetImageChannelMask(image,channel_mask);
151 if (status == MagickFalse)
154 return(status != 0 ? MagickTrue : MagickFalse);
158 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162 % A u t o L e v e l I m a g e %
166 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
168 % AutoLevelImage() adjusts the levels of a particular image channel by
169 % scaling the minimum and maximum values to the full quantum range.
171 % The format of the LevelImage method is:
173 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
175 % A description of each parameter follows:
177 % o image: The image to auto-level
179 % o exception: return any errors or warnings in this structure.
182 MagickExport MagickBooleanType AutoLevelImage(Image *image,
183 ExceptionInfo *exception)
185 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193 % B r i g h t n e s s C o n t r a s t I m a g e %
197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199 % BrightnessContrastImage() changes the brightness and/or contrast of an
200 % image. It converts the brightness and contrast parameters into slope and
201 % intercept and calls a polynomical function to apply to the image.
203 % The format of the BrightnessContrastImage method is:
205 % MagickBooleanType BrightnessContrastImage(Image *image,
206 % const double brightness,const double contrast,ExceptionInfo *exception)
208 % A description of each parameter follows:
210 % o image: the image.
212 % o brightness: the brightness percent (-100 .. 100).
214 % o contrast: the contrast percent (-100 .. 100).
216 % o exception: return any errors or warnings in this structure.
219 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
220 const double brightness,const double contrast,ExceptionInfo *exception)
222 #define BrightnessContastImageTag "BrightnessContast/Image"
234 Compute slope and intercept.
236 assert(image != (Image *) NULL);
237 assert(image->signature == MagickSignature);
238 if (image->debug != MagickFalse)
239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
241 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
244 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
245 coefficients[0]=slope;
246 coefficients[1]=intercept;
247 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
252 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256 % C l u t I m a g e %
260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262 % ClutImage() replaces each color value in the given image, by using it as an
263 % index to lookup a replacement color value in a Color Look UP Table in the
264 % form of an image. The values are extracted along a diagonal of the CLUT
265 % image so either a horizontal or vertial gradient image can be used.
267 % Typically this is used to either re-color a gray-scale image according to a
268 % color gradient in the CLUT image, or to perform a freeform histogram
269 % (level) adjustment according to the (typically gray-scale) gradient in the
272 % When the 'channel' mask includes the matte/alpha transparency channel but
273 % one image has no such channel it is assumed that that image is a simple
274 % gray-scale image that will effect the alpha channel values, either for
275 % gray-scale coloring (with transparent or semi-transparent colors), or
276 % a histogram adjustment of existing alpha channel values. If both images
277 % have matte channels, direct and normal indexing is applied, which is rarely
280 % The format of the ClutImage method is:
282 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
283 % const PixelInterpolateMethod method,ExceptionInfo *exception)
285 % A description of each parameter follows:
287 % o image: the image, which is replaced by indexed CLUT values
289 % o clut_image: the color lookup table image for replacement color values.
291 % o method: the pixel interpolation method.
293 % o exception: return any errors or warnings in this structure.
296 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
297 const PixelInterpolateMethod method,ExceptionInfo *exception)
299 #define ClutImageTag "Clut/Image"
320 assert(image != (Image *) NULL);
321 assert(image->signature == MagickSignature);
322 if (image->debug != MagickFalse)
323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
324 assert(clut_image != (Image *) NULL);
325 assert(clut_image->signature == MagickSignature);
326 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
328 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
329 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
330 (void) SetImageColorspace(image,sRGBColorspace,exception);
331 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
332 if (clut_map == (PixelInfo *) NULL)
333 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
340 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
341 clut_view=AcquireVirtualCacheView(clut_image,exception);
342 for (i=0; i <= (ssize_t) MaxMap; i++)
344 GetPixelInfo(clut_image,clut_map+i);
345 (void) InterpolatePixelInfo(clut_image,clut_view,method,
346 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
347 (clut_image->rows-adjust),clut_map+i,exception);
349 clut_view=DestroyCacheView(clut_view);
350 image_view=AcquireAuthenticCacheView(image,exception);
351 #if defined(MAGICKCORE_OPENMP_SUPPORT)
352 #pragma omp parallel for schedule(static,4) shared(progress,status) \
353 magick_threads(image,image,image->rows,1)
355 for (y=0; y < (ssize_t) image->rows; y++)
366 if (status == MagickFalse)
368 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
369 if (q == (Quantum *) NULL)
374 GetPixelInfo(image,&pixel);
375 for (x=0; x < (ssize_t) image->columns; x++)
377 if (GetPixelReadMask(image,q) == 0)
379 q+=GetPixelChannels(image);
382 GetPixelInfoPixel(image,q,&pixel);
383 pixel.red=clut_map[ScaleQuantumToMap(
384 ClampToQuantum(pixel.red))].red;
385 pixel.green=clut_map[ScaleQuantumToMap(
386 ClampToQuantum(pixel.green))].green;
387 pixel.blue=clut_map[ScaleQuantumToMap(
388 ClampToQuantum(pixel.blue))].blue;
389 pixel.black=clut_map[ScaleQuantumToMap(
390 ClampToQuantum(pixel.black))].black;
391 pixel.alpha=clut_map[ScaleQuantumToMap(
392 ClampToQuantum(pixel.alpha))].alpha;
393 SetPixelInfoPixel(image,&pixel,q);
394 q+=GetPixelChannels(image);
396 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
403 #if defined(MAGICKCORE_OPENMP_SUPPORT)
404 #pragma omp critical (MagickCore_ClutImage)
406 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
407 if (proceed == MagickFalse)
411 image_view=DestroyCacheView(image_view);
412 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
413 if ((clut_image->alpha_trait == BlendPixelTrait) &&
414 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
415 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424 % C o l o r D e c i s i o n L i s t I m a g e %
428 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
431 % (CCC) file which solely contains one or more color corrections and applies
432 % the correction to the image. Here is a sample CCC file:
434 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
435 % <ColorCorrection id="cc03345">
437 % <Slope> 0.9 1.2 0.5 </Slope>
438 % <Offset> 0.4 -0.5 0.6 </Offset>
439 % <Power> 1.0 0.8 1.5 </Power>
442 % <Saturation> 0.85 </Saturation>
445 % </ColorCorrectionCollection>
447 % which includes the slop, offset, and power for each of the RGB channels
448 % as well as the saturation.
450 % The format of the ColorDecisionListImage method is:
452 % MagickBooleanType ColorDecisionListImage(Image *image,
453 % const char *color_correction_collection,ExceptionInfo *exception)
455 % A description of each parameter follows:
457 % o image: the image.
459 % o color_correction_collection: the color correction collection in XML.
461 % o exception: return any errors or warnings in this structure.
464 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
465 const char *color_correction_collection,ExceptionInfo *exception)
467 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
469 typedef struct _Correction
477 typedef struct _ColorCorrection
492 token[MaxTextExtent];
523 Allocate and initialize cdl maps.
525 assert(image != (Image *) NULL);
526 assert(image->signature == MagickSignature);
527 if (image->debug != MagickFalse)
528 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
529 if (color_correction_collection == (const char *) NULL)
531 ccc=NewXMLTree((const char *) color_correction_collection,exception);
532 if (ccc == (XMLTreeInfo *) NULL)
534 cc=GetXMLTreeChild(ccc,"ColorCorrection");
535 if (cc == (XMLTreeInfo *) NULL)
537 ccc=DestroyXMLTree(ccc);
540 color_correction.red.slope=1.0;
541 color_correction.red.offset=0.0;
542 color_correction.red.power=1.0;
543 color_correction.green.slope=1.0;
544 color_correction.green.offset=0.0;
545 color_correction.green.power=1.0;
546 color_correction.blue.slope=1.0;
547 color_correction.blue.offset=0.0;
548 color_correction.blue.power=1.0;
549 color_correction.saturation=0.0;
550 sop=GetXMLTreeChild(cc,"SOPNode");
551 if (sop != (XMLTreeInfo *) NULL)
558 slope=GetXMLTreeChild(sop,"Slope");
559 if (slope != (XMLTreeInfo *) NULL)
561 content=GetXMLTreeContent(slope);
562 p=(const char *) content;
563 for (i=0; (*p != '\0') && (i < 3); i++)
565 GetMagickToken(p,&p,token);
567 GetMagickToken(p,&p,token);
572 color_correction.red.slope=StringToDouble(token,(char **) NULL);
577 color_correction.green.slope=StringToDouble(token,
583 color_correction.blue.slope=StringToDouble(token,
590 offset=GetXMLTreeChild(sop,"Offset");
591 if (offset != (XMLTreeInfo *) NULL)
593 content=GetXMLTreeContent(offset);
594 p=(const char *) content;
595 for (i=0; (*p != '\0') && (i < 3); i++)
597 GetMagickToken(p,&p,token);
599 GetMagickToken(p,&p,token);
604 color_correction.red.offset=StringToDouble(token,
610 color_correction.green.offset=StringToDouble(token,
616 color_correction.blue.offset=StringToDouble(token,
623 power=GetXMLTreeChild(sop,"Power");
624 if (power != (XMLTreeInfo *) NULL)
626 content=GetXMLTreeContent(power);
627 p=(const char *) content;
628 for (i=0; (*p != '\0') && (i < 3); i++)
630 GetMagickToken(p,&p,token);
632 GetMagickToken(p,&p,token);
637 color_correction.red.power=StringToDouble(token,(char **) NULL);
642 color_correction.green.power=StringToDouble(token,
648 color_correction.blue.power=StringToDouble(token,
656 sat=GetXMLTreeChild(cc,"SATNode");
657 if (sat != (XMLTreeInfo *) NULL)
662 saturation=GetXMLTreeChild(sat,"Saturation");
663 if (saturation != (XMLTreeInfo *) NULL)
665 content=GetXMLTreeContent(saturation);
666 p=(const char *) content;
667 GetMagickToken(p,&p,token);
668 color_correction.saturation=StringToDouble(token,(char **) NULL);
671 ccc=DestroyXMLTree(ccc);
672 if (image->debug != MagickFalse)
674 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
675 " Color Correction Collection:");
676 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
677 " color_correction.red.slope: %g",color_correction.red.slope);
678 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
679 " color_correction.red.offset: %g",color_correction.red.offset);
680 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
681 " color_correction.red.power: %g",color_correction.red.power);
682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
683 " color_correction.green.slope: %g",color_correction.green.slope);
684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
685 " color_correction.green.offset: %g",color_correction.green.offset);
686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
687 " color_correction.green.power: %g",color_correction.green.power);
688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
689 " color_correction.blue.slope: %g",color_correction.blue.slope);
690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
691 " color_correction.blue.offset: %g",color_correction.blue.offset);
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " color_correction.blue.power: %g",color_correction.blue.power);
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.saturation: %g",color_correction.saturation);
697 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
698 if (cdl_map == (PixelInfo *) NULL)
699 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
701 for (i=0; i <= (ssize_t) MaxMap; i++)
703 cdl_map[i].red=(double) ScaleMapToQuantum((double)
704 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
705 color_correction.red.offset,color_correction.red.power))));
706 cdl_map[i].green=(double) ScaleMapToQuantum((double)
707 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
708 color_correction.green.offset,color_correction.green.power))));
709 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
710 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
711 color_correction.blue.offset,color_correction.blue.power))));
713 if (image->storage_class == PseudoClass)
714 for (i=0; i < (ssize_t) image->colors; i++)
717 Apply transfer function to colormap.
722 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
723 0.07217f*image->colormap[i].blue;
724 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
725 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
726 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
727 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
728 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
729 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
732 Apply transfer function to image.
736 image_view=AcquireAuthenticCacheView(image,exception);
737 #if defined(MAGICKCORE_OPENMP_SUPPORT)
738 #pragma omp parallel for schedule(static,4) shared(progress,status) \
739 magick_threads(image,image,image->rows,1)
741 for (y=0; y < (ssize_t) image->rows; y++)
752 if (status == MagickFalse)
754 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
755 if (q == (Quantum *) NULL)
760 for (x=0; x < (ssize_t) image->columns; x++)
762 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
763 0.07217f*GetPixelBlue(image,q);
764 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
765 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
766 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
767 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
768 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
769 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
770 q+=GetPixelChannels(image);
772 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
774 if (image->progress_monitor != (MagickProgressMonitor) NULL)
779 #if defined(MAGICKCORE_OPENMP_SUPPORT)
780 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
782 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
783 progress++,image->rows);
784 if (proceed == MagickFalse)
788 image_view=DestroyCacheView(image_view);
789 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
794 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
798 % C o n t r a s t I m a g e %
802 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
804 % ContrastImage() enhances the intensity differences between the lighter and
805 % darker elements of the image. Set sharpen to a MagickTrue to increase the
806 % image contrast otherwise the contrast is reduced.
808 % The format of the ContrastImage method is:
810 % MagickBooleanType ContrastImage(Image *image,
811 % const MagickBooleanType sharpen,ExceptionInfo *exception)
813 % A description of each parameter follows:
815 % o image: the image.
817 % o sharpen: Increase or decrease image contrast.
819 % o exception: return any errors or warnings in this structure.
823 static void Contrast(const int sign,double *red,double *green,double *blue)
831 Enhance contrast: dark color become darker, light color become lighter.
833 assert(red != (double *) NULL);
834 assert(green != (double *) NULL);
835 assert(blue != (double *) NULL);
839 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
840 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
842 if (brightness > 1.0)
845 if (brightness < 0.0)
847 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
850 MagickExport MagickBooleanType ContrastImage(Image *image,
851 const MagickBooleanType sharpen,ExceptionInfo *exception)
853 #define ContrastImageTag "Contrast/Image"
873 assert(image != (Image *) NULL);
874 assert(image->signature == MagickSignature);
875 if (image->debug != MagickFalse)
876 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
877 sign=sharpen != MagickFalse ? 1 : -1;
878 if (image->storage_class == PseudoClass)
881 Contrast enhance colormap.
883 for (i=0; i < (ssize_t) image->colors; i++)
890 Contrast(sign,&red,&green,&blue);
891 image->colormap[i].red=(MagickRealType) red;
892 image->colormap[i].red=(MagickRealType) red;
893 image->colormap[i].red=(MagickRealType) red;
897 Contrast enhance image.
901 image_view=AcquireAuthenticCacheView(image,exception);
902 #if defined(MAGICKCORE_OPENMP_SUPPORT)
903 #pragma omp parallel for schedule(static,4) shared(progress,status) \
904 magick_threads(image,image,image->rows,1)
906 for (y=0; y < (ssize_t) image->rows; y++)
919 if (status == MagickFalse)
921 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
922 if (q == (Quantum *) NULL)
927 for (x=0; x < (ssize_t) image->columns; x++)
929 red=(double) GetPixelRed(image,q);
930 green=(double) GetPixelGreen(image,q);
931 blue=(double) GetPixelBlue(image,q);
932 Contrast(sign,&red,&green,&blue);
933 SetPixelRed(image,ClampToQuantum(red),q);
934 SetPixelGreen(image,ClampToQuantum(green),q);
935 SetPixelBlue(image,ClampToQuantum(blue),q);
936 q+=GetPixelChannels(image);
938 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
940 if (image->progress_monitor != (MagickProgressMonitor) NULL)
945 #if defined(MAGICKCORE_OPENMP_SUPPORT)
946 #pragma omp critical (MagickCore_ContrastImage)
948 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
949 if (proceed == MagickFalse)
953 image_view=DestroyCacheView(image_view);
958 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
962 % C o n t r a s t S t r e t c h I m a g e %
966 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
968 % ContrastStretchImage() is a simple image enhancement technique that attempts
969 % to improve the contrast in an image by 'stretching' the range of intensity
970 % values it contains to span a desired range of values. It differs from the
971 % more sophisticated histogram equalization in that it can only apply a
972 % linear scaling function to the image pixel values. As a result the
973 % 'enhancement' is less harsh.
975 % The format of the ContrastStretchImage method is:
977 % MagickBooleanType ContrastStretchImage(Image *image,
978 % const char *levels,ExceptionInfo *exception)
980 % A description of each parameter follows:
982 % o image: the image.
984 % o black_point: the black point.
986 % o white_point: the white point.
988 % o levels: Specify the levels where the black and white points have the
989 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
991 % o exception: return any errors or warnings in this structure.
994 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
995 const double black_point,const double white_point,ExceptionInfo *exception)
997 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
998 #define ContrastStretchImageTag "ContrastStretch/Image"
1025 Allocate histogram and stretch map.
1027 assert(image != (Image *) NULL);
1028 assert(image->signature == MagickSignature);
1029 if (image->debug != MagickFalse)
1030 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1031 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1032 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1033 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1034 sizeof(*histogram));
1035 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1036 GetPixelChannels(image)*sizeof(*stretch_map));
1037 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1038 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1040 if (stretch_map != (double *) NULL)
1041 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1042 if (histogram != (double *) NULL)
1043 histogram=(double *) RelinquishMagickMemory(histogram);
1044 if (white != (double *) NULL)
1045 white=(double *) RelinquishMagickMemory(white);
1046 if (black != (double *) NULL)
1047 black=(double *) RelinquishMagickMemory(black);
1048 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1054 if (IsImageGray(image,exception) != MagickFalse)
1055 (void) SetImageColorspace(image,GRAYColorspace,exception);
1057 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1058 sizeof(*histogram));
1059 image_view=AcquireVirtualCacheView(image,exception);
1060 for (y=0; y < (ssize_t) image->rows; y++)
1062 register const Quantum
1068 if (status == MagickFalse)
1070 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1071 if (p == (const Quantum *) NULL)
1076 for (x=0; x < (ssize_t) image->columns; x++)
1084 pixel=GetPixelIntensity(image,p);
1085 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1087 if (image->channel_mask != DefaultChannels)
1088 pixel=(double) p[i];
1089 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1090 ClampToQuantum(pixel))+i]++;
1092 p+=GetPixelChannels(image);
1095 image_view=DestroyCacheView(image_view);
1097 Find the histogram boundaries by locating the black/white levels.
1099 number_channels=GetPixelChannels(image);
1100 for (i=0; i < (ssize_t) number_channels; i++)
1109 white[i]=MaxRange(QuantumRange);
1111 for (j=0; j <= (ssize_t) MaxMap; j++)
1113 intensity+=histogram[GetPixelChannels(image)*j+i];
1114 if (intensity > black_point)
1117 black[i]=(double) j;
1119 for (j=(ssize_t) MaxMap; j != 0; j--)
1121 intensity+=histogram[GetPixelChannels(image)*j+i];
1122 if (intensity > ((double) image->columns*image->rows-white_point))
1125 white[i]=(double) j;
1127 histogram=(double *) RelinquishMagickMemory(histogram);
1129 Stretch the histogram to create the stretched image mapping.
1131 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1132 sizeof(*stretch_map));
1133 number_channels=GetPixelChannels(image);
1134 for (i=0; i < (ssize_t) number_channels; i++)
1139 for (j=0; j <= (ssize_t) MaxMap; j++)
1141 if (j < (ssize_t) black[i])
1142 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1144 if (j > (ssize_t) white[i])
1145 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1147 if (black[i] != white[i])
1148 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1149 (double) (MaxMap*(j-black[i])/(white[i]-black[i])));
1152 if (image->storage_class == PseudoClass)
1158 Stretch-contrast colormap.
1160 for (j=0; j < (ssize_t) image->colors; j++)
1162 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1164 i=GetPixelChannelChannel(image,RedPixelChannel);
1165 if (black[i] != white[i])
1166 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1167 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
1169 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1171 i=GetPixelChannelChannel(image,GreenPixelChannel);
1172 if (black[i] != white[i])
1173 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1174 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
1176 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1178 i=GetPixelChannelChannel(image,BluePixelChannel);
1179 if (black[i] != white[i])
1180 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
1183 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelChannel(image,AlphaPixelChannel);
1186 if (black[i] != white[i])
1187 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1188 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
1193 Stretch-contrast image.
1197 image_view=AcquireAuthenticCacheView(image,exception);
1198 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1199 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1200 magick_threads(image,image,image->rows,1)
1202 for (y=0; y < (ssize_t) image->rows; y++)
1210 if (status == MagickFalse)
1212 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1213 if (q == (Quantum *) NULL)
1218 for (x=0; x < (ssize_t) image->columns; x++)
1223 if (GetPixelReadMask(image,q) == 0)
1225 q+=GetPixelChannels(image);
1228 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1230 PixelChannel channel=GetPixelChannelChannel(image,i);
1231 PixelTrait traits=GetPixelChannelTraits(image,channel);
1232 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1234 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1235 ScaleQuantumToMap(q[i])+i]);
1237 q+=GetPixelChannels(image);
1239 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1241 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1246 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1247 #pragma omp critical (MagickCore_ContrastStretchImage)
1249 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1251 if (proceed == MagickFalse)
1255 image_view=DestroyCacheView(image_view);
1256 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1257 white=(double *) RelinquishMagickMemory(white);
1258 black=(double *) RelinquishMagickMemory(black);
1263 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267 % E n h a n c e I m a g e %
1271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1273 % EnhanceImage() applies a digital filter that improves the quality of a
1276 % The format of the EnhanceImage method is:
1278 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1280 % A description of each parameter follows:
1282 % o image: the image.
1284 % o exception: return any errors or warnings in this structure.
1287 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289 #define EnhancePixel(weight) \
1290 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1291 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1292 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1293 distance*distance; \
1294 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
1296 aggregate+=(weight)*r[i]; \
1297 total_weight+=(weight); \
1299 r+=GetPixelChannels(image);
1300 #define EnhanceImageTag "Enhance/Image"
1319 Initialize enhanced image attributes.
1321 assert(image != (const Image *) NULL);
1322 assert(image->signature == MagickSignature);
1323 if (image->debug != MagickFalse)
1324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1325 assert(exception != (ExceptionInfo *) NULL);
1326 assert(exception->signature == MagickSignature);
1327 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1329 if (enhance_image == (Image *) NULL)
1330 return((Image *) NULL);
1331 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1333 enhance_image=DestroyImage(enhance_image);
1334 return((Image *) NULL);
1341 image_view=AcquireVirtualCacheView(image,exception);
1342 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1343 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1344 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1345 magick_threads(image,enhance_image,image->rows,1)
1347 for (y=0; y < (ssize_t) image->rows; y++)
1349 register const Quantum
1361 if (status == MagickFalse)
1363 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1364 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1366 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1371 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1372 for (x=0; x < (ssize_t) image->columns; x++)
1377 if (GetPixelReadMask(image,p) == 0)
1379 p+=GetPixelChannels(image);
1380 q+=GetPixelChannels(enhance_image);
1383 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1392 register const Quantum
1395 PixelChannel channel=GetPixelChannelChannel(image,i);
1396 PixelTrait traits=GetPixelChannelTraits(image,channel);
1397 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1398 if ((traits == UndefinedPixelTrait) ||
1399 (enhance_traits == UndefinedPixelTrait))
1401 SetPixelChannel(enhance_image,channel,p[center+i],q);
1402 if ((enhance_traits & CopyPixelTrait) != 0)
1405 Compute weighted average of target pixel color components.
1410 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1411 EnhancePixel(8.0); EnhancePixel(5.0);
1412 r=p+1*GetPixelChannels(image)*(image->columns+4);
1413 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1414 EnhancePixel(20.0); EnhancePixel(8.0);
1415 r=p+2*GetPixelChannels(image)*(image->columns+4);
1416 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1417 EnhancePixel(40.0); EnhancePixel(10.0);
1418 r=p+3*GetPixelChannels(image)*(image->columns+4);
1419 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1420 EnhancePixel(20.0); EnhancePixel(8.0);
1421 r=p+4*GetPixelChannels(image)*(image->columns+4);
1422 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1423 EnhancePixel(8.0); EnhancePixel(5.0);
1424 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1427 p+=GetPixelChannels(image);
1428 q+=GetPixelChannels(enhance_image);
1430 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1432 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1437 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1438 #pragma omp critical (MagickCore_EnhanceImage)
1440 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1441 if (proceed == MagickFalse)
1445 enhance_view=DestroyCacheView(enhance_view);
1446 image_view=DestroyCacheView(image_view);
1447 if (status == MagickFalse)
1448 enhance_image=DestroyImage(enhance_image);
1449 return(enhance_image);
1453 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1457 % E q u a l i z e I m a g e %
1461 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463 % EqualizeImage() applies a histogram equalization to the image.
1465 % The format of the EqualizeImage method is:
1467 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1469 % A description of each parameter follows:
1471 % o image: the image.
1473 % o exception: return any errors or warnings in this structure.
1476 MagickExport MagickBooleanType EqualizeImage(Image *image,
1477 ExceptionInfo *exception)
1479 #define EqualizeImageTag "Equalize/Image"
1491 black[CompositePixelChannel],
1495 white[CompositePixelChannel];
1507 Allocate and initialize histogram arrays.
1509 assert(image != (Image *) NULL);
1510 assert(image->signature == MagickSignature);
1511 if (image->debug != MagickFalse)
1512 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1513 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1514 GetPixelChannels(image)*sizeof(*equalize_map));
1515 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
1516 GetPixelChannels(image)*sizeof(*histogram));
1517 map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1518 GetPixelChannels(image)*sizeof(*map));
1519 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1520 (map == (double *) NULL))
1522 if (map != (double *) NULL)
1523 map=(double *) RelinquishMagickMemory(map);
1524 if (histogram != (double *) NULL)
1525 histogram=(double *) RelinquishMagickMemory(histogram);
1526 if (equalize_map != (double *) NULL)
1527 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1528 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1535 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1536 sizeof(*histogram));
1537 image_view=AcquireVirtualCacheView(image,exception);
1538 for (y=0; y < (ssize_t) image->rows; y++)
1540 register const Quantum
1546 if (status == MagickFalse)
1548 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1549 if (p == (const Quantum *) NULL)
1554 for (x=0; x < (ssize_t) image->columns; x++)
1559 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1560 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1561 p+=GetPixelChannels(image);
1564 image_view=DestroyCacheView(image_view);
1566 Integrate the histogram to get the equalization map.
1568 number_channels=GetPixelChannels(image);
1569 for (i=0; i < (ssize_t) number_channels; i++)
1578 for (j=0; j <= (ssize_t) MaxMap; j++)
1580 intensity+=histogram[GetPixelChannels(image)*j+i];
1581 map[GetPixelChannels(image)*j+i]=intensity;
1584 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1585 sizeof(*equalize_map));
1586 number_channels=GetPixelChannels(image);
1587 for (i=0; i < (ssize_t) number_channels; i++)
1593 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1594 if (black[i] != white[i])
1595 for (j=0; j <= (ssize_t) MaxMap; j++)
1596 equalize_map[GetPixelChannels(image)*j+i]=(double)
1597 ScaleMapToQuantum((double) ((MaxMap*(map[
1598 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1600 histogram=(double *) RelinquishMagickMemory(histogram);
1601 map=(double *) RelinquishMagickMemory(map);
1602 if (image->storage_class == PseudoClass)
1610 for (j=0; j < (ssize_t) image->colors; j++)
1612 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1614 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1615 if (black[channel] != white[channel])
1616 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1617 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1620 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1622 PixelChannel channel=GetPixelChannelChannel(image,
1624 if (black[channel] != white[channel])
1625 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1626 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1629 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1631 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1632 if (black[channel] != white[channel])
1633 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1634 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1637 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1639 PixelChannel channel=GetPixelChannelChannel(image,
1641 if (black[channel] != white[channel])
1642 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1643 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1652 image_view=AcquireAuthenticCacheView(image,exception);
1653 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1654 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1655 magick_threads(image,image,image->rows,1)
1657 for (y=0; y < (ssize_t) image->rows; y++)
1665 if (status == MagickFalse)
1667 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1668 if (q == (Quantum *) NULL)
1673 for (x=0; x < (ssize_t) image->columns; x++)
1678 if (GetPixelReadMask(image,q) == 0)
1680 q+=GetPixelChannels(image);
1683 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1685 PixelChannel channel=GetPixelChannelChannel(image,i);
1686 PixelTrait traits=GetPixelChannelTraits(image,channel);
1687 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1689 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1690 ScaleQuantumToMap(q[i])+i]);
1692 q+=GetPixelChannels(image);
1694 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1696 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1701 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1702 #pragma omp critical (MagickCore_EqualizeImage)
1704 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1705 if (proceed == MagickFalse)
1709 image_view=DestroyCacheView(image_view);
1710 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1715 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719 % G a m m a I m a g e %
1723 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1725 % GammaImage() gamma-corrects a particular image channel. The same
1726 % image viewed on different devices will have perceptual differences in the
1727 % way the image's intensities are represented on the screen. Specify
1728 % individual gamma levels for the red, green, and blue channels, or adjust
1729 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1731 % You can also reduce the influence of a particular channel with a gamma
1734 % The format of the GammaImage method is:
1736 % MagickBooleanType GammaImage(Image *image,const double gamma,
1737 % ExceptionInfo *exception)
1739 % A description of each parameter follows:
1741 % o image: the image.
1743 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1745 % o gamma: the image gamma.
1748 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1749 ExceptionInfo *exception)
1751 #define GammaCorrectImageTag "GammaCorrect/Image"
1772 Allocate and initialize gamma maps.
1774 assert(image != (Image *) NULL);
1775 assert(image->signature == MagickSignature);
1776 if (image->debug != MagickFalse)
1777 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1780 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1781 if (gamma_map == (Quantum *) NULL)
1782 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1784 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1786 for (i=0; i <= (ssize_t) MaxMap; i++)
1787 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1788 MaxMap,1.0/gamma)));
1789 if (image->storage_class == PseudoClass)
1790 for (i=0; i < (ssize_t) image->colors; i++)
1793 Gamma-correct colormap.
1795 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1796 image->colormap[i].red=(double) gamma_map[
1797 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
1798 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1799 image->colormap[i].green=(double) gamma_map[
1800 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
1801 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1802 image->colormap[i].blue=(double) gamma_map[
1803 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
1804 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1805 image->colormap[i].alpha=(double) gamma_map[
1806 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
1809 Gamma-correct image.
1813 image_view=AcquireAuthenticCacheView(image,exception);
1814 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1815 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1816 magick_threads(image,image,image->rows,1)
1818 for (y=0; y < (ssize_t) image->rows; y++)
1826 if (status == MagickFalse)
1828 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1829 if (q == (Quantum *) NULL)
1834 for (x=0; x < (ssize_t) image->columns; x++)
1839 if (GetPixelReadMask(image,q) == 0)
1841 q+=GetPixelChannels(image);
1844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1846 PixelChannel channel=GetPixelChannelChannel(image,i);
1847 PixelTrait traits=GetPixelChannelTraits(image,channel);
1848 if ((traits & UpdatePixelTrait) == 0)
1850 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1852 q+=GetPixelChannels(image);
1854 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1856 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1862 #pragma omp critical (MagickCore_GammaImage)
1864 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1866 if (proceed == MagickFalse)
1870 image_view=DestroyCacheView(image_view);
1871 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1872 if (image->gamma != 0.0)
1873 image->gamma*=gamma;
1878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1882 % G r a y s c a l e I m a g e %
1886 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1888 % GrayscaleImage() converts the image to grayscale.
1890 % The format of the GrayscaleImage method is:
1892 % MagickBooleanType GrayscaleImage(Image *image,
1893 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1895 % A description of each parameter follows:
1897 % o image: the image.
1899 % o method: the pixel intensity method.
1901 % o exception: return any errors or warnings in this structure.
1905 static inline MagickRealType MagickMax(const MagickRealType x,
1906 const MagickRealType y)
1913 static inline MagickRealType MagickMin(const MagickRealType x,
1914 const MagickRealType y)
1921 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1922 const PixelIntensityMethod grayscale,ExceptionInfo *exception)
1924 #define GrayscaleImageTag "Grayscale/Image"
1938 assert(image != (Image *) NULL);
1939 assert(image->signature == MagickSignature);
1940 if (image->debug != MagickFalse)
1941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1942 if (image->storage_class == PseudoClass)
1944 if (SyncImage(image,exception) == MagickFalse)
1945 return(MagickFalse);
1946 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1947 return(MagickFalse);
1949 switch (image->intensity)
1951 case Rec601LuminancePixelIntensityMethod:
1952 case Rec709LuminancePixelIntensityMethod:
1954 (void) SetImageColorspace(image,RGBColorspace,exception);
1957 case Rec601LumaPixelIntensityMethod:
1958 case Rec709LumaPixelIntensityMethod:
1959 case UndefinedPixelIntensityMethod:
1961 (void) SetImageColorspace(image,sRGBColorspace,exception);
1972 image_view=AcquireAuthenticCacheView(image,exception);
1973 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1974 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1975 magick_threads(image,image,image->rows,1)
1977 for (y=0; y < (ssize_t) image->rows; y++)
1985 if (status == MagickFalse)
1987 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1988 if (q == (Quantum *) NULL)
1993 for (x=0; x < (ssize_t) image->columns; x++)
2001 if (GetPixelReadMask(image,q) == 0)
2003 q+=GetPixelChannels(image);
2006 red=(MagickRealType) GetPixelRed(image,q);
2007 green=(MagickRealType) GetPixelGreen(image,q);
2008 blue=(MagickRealType) GetPixelBlue(image,q);
2010 switch (image->intensity)
2012 case AveragePixelIntensityMethod:
2014 intensity=(red+green+blue)/3.0;
2017 case BrightnessPixelIntensityMethod:
2019 intensity=MagickMax(MagickMax(red,green),blue);
2022 case LightnessPixelIntensityMethod:
2024 intensity=MagickMin(MagickMin(red,green),blue);
2027 case MSPixelIntensityMethod:
2029 intensity=(MagickRealType) (((double) red*red+green*green+
2033 case Rec601LumaPixelIntensityMethod:
2035 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2038 case Rec601LuminancePixelIntensityMethod:
2040 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2043 case Rec709LumaPixelIntensityMethod:
2044 case UndefinedPixelIntensityMethod:
2046 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2049 case Rec709LuminancePixelIntensityMethod:
2051 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2054 case RMSPixelIntensityMethod:
2056 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2057 blue*blue)/sqrt(3.0));
2063 SetPixelGray(image,ClampToQuantum(intensity),q);
2064 q+=GetPixelChannels(image);
2066 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2068 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2073 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2074 #pragma omp critical (MagickCore_GrayscaleImage)
2076 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2078 if (proceed == MagickFalse)
2082 image_view=DestroyCacheView(image_view);
2083 if (SetImageColorspace(image,GRAYColorspace,exception) == MagickFalse)
2084 return(MagickFalse);
2085 image->type=GrayscaleType;
2090 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2094 % H a l d C l u t I m a g e %
2098 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2100 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2101 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2102 % Create it with the HALD coder. You can apply any color transformation to
2103 % the Hald image and then use this method to apply the transform to the
2106 % The format of the HaldClutImage method is:
2108 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2109 % ExceptionInfo *exception)
2111 % A description of each parameter follows:
2113 % o image: the image, which is replaced by indexed CLUT values
2115 % o hald_image: the color lookup table image for replacement color values.
2117 % o exception: return any errors or warnings in this structure.
2120 MagickExport MagickBooleanType HaldClutImage(Image *image,
2121 const Image *hald_image,ExceptionInfo *exception)
2123 #define HaldClutImageTag "Clut/Image"
2125 typedef struct _HaldInfo
2157 assert(image != (Image *) NULL);
2158 assert(image->signature == MagickSignature);
2159 if (image->debug != MagickFalse)
2160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2161 assert(hald_image != (Image *) NULL);
2162 assert(hald_image->signature == MagickSignature);
2163 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2164 return(MagickFalse);
2165 if (image->alpha_trait != BlendPixelTrait)
2166 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2172 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2173 (MagickRealType) hald_image->rows);
2174 for (level=2; (level*level*level) < length; level++) ;
2176 cube_size=level*level;
2177 width=(double) hald_image->columns;
2178 GetPixelInfo(hald_image,&zero);
2179 hald_view=AcquireVirtualCacheView(hald_image,exception);
2180 image_view=AcquireAuthenticCacheView(image,exception);
2181 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2182 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2183 magick_threads(image,image,image->rows,1)
2185 for (y=0; y < (ssize_t) image->rows; y++)
2193 if (status == MagickFalse)
2195 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2196 if (q == (Quantum *) NULL)
2201 for (x=0; x < (ssize_t) image->columns; x++)
2216 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2217 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2218 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2219 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2220 point.x-=floor(point.x);
2221 point.y-=floor(point.y);
2222 point.z-=floor(point.z);
2224 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2225 fmod(offset,width),floor(offset/width),&pixel1,exception);
2227 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2228 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2230 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2233 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2234 fmod(offset,width),floor(offset/width),&pixel1,exception);
2235 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2236 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2238 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2241 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2243 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2244 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2245 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2246 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2247 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2248 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2249 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2250 (image->colorspace == CMYKColorspace))
2251 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2252 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2253 (image->alpha_trait == BlendPixelTrait))
2254 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2255 q+=GetPixelChannels(image);
2257 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2259 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2264 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2265 #pragma omp critical (MagickCore_HaldClutImage)
2267 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2268 if (proceed == MagickFalse)
2272 hald_view=DestroyCacheView(hald_view);
2273 image_view=DestroyCacheView(image_view);
2278 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2282 % L e v e l I m a g e %
2286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2288 % LevelImage() adjusts the levels of a particular image channel by
2289 % scaling the colors falling between specified white and black points to
2290 % the full available quantum range.
2292 % The parameters provided represent the black, and white points. The black
2293 % point specifies the darkest color in the image. Colors darker than the
2294 % black point are set to zero. White point specifies the lightest color in
2295 % the image. Colors brighter than the white point are set to the maximum
2298 % If a '!' flag is given, map black and white colors to the given levels
2299 % rather than mapping those levels to black and white. See
2300 % LevelizeImage() below.
2302 % Gamma specifies a gamma correction to apply to the image.
2304 % The format of the LevelImage method is:
2306 % MagickBooleanType LevelImage(Image *image,const double black_point,
2307 % const double white_point,const double gamma,ExceptionInfo *exception)
2309 % A description of each parameter follows:
2311 % o image: the image.
2313 % o black_point: The level to map zero (black) to.
2315 % o white_point: The level to map QuantumRange (white) to.
2317 % o exception: return any errors or warnings in this structure.
2321 static inline double LevelPixel(const double black_point,
2322 const double white_point,const double gamma,const double pixel)
2328 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2329 level_pixel=(double) QuantumRange*pow(scale*((double) pixel-
2330 black_point),1.0/gamma);
2331 return((MagickRealType) (IsNaN(level_pixel) ? 0.0 : level_pixel));
2334 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2335 const double white_point,const double gamma,ExceptionInfo *exception)
2337 #define LevelImageTag "Level/Image"
2355 Allocate and initialize levels map.
2357 assert(image != (Image *) NULL);
2358 assert(image->signature == MagickSignature);
2359 if (image->debug != MagickFalse)
2360 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2361 if (image->storage_class == PseudoClass)
2362 for (i=0; i < (ssize_t) image->colors; i++)
2367 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2368 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2369 white_point,gamma,image->colormap[i].red));
2370 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2371 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2372 white_point,gamma,image->colormap[i].green));
2373 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2374 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2375 white_point,gamma,image->colormap[i].blue));
2376 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2377 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2378 white_point,gamma,image->colormap[i].alpha));
2385 image_view=AcquireAuthenticCacheView(image,exception);
2386 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2387 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2388 magick_threads(image,image,image->rows,1)
2390 for (y=0; y < (ssize_t) image->rows; y++)
2398 if (status == MagickFalse)
2400 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2401 if (q == (Quantum *) NULL)
2406 for (x=0; x < (ssize_t) image->columns; x++)
2411 if (GetPixelReadMask(image,q) == 0)
2413 q+=GetPixelChannels(image);
2416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2418 PixelChannel channel=GetPixelChannelChannel(image,i);
2419 PixelTrait traits=GetPixelChannelTraits(image,channel);
2420 if ((traits & UpdatePixelTrait) == 0)
2422 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2425 q+=GetPixelChannels(image);
2427 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2429 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2434 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2435 #pragma omp critical (MagickCore_LevelImage)
2437 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2438 if (proceed == MagickFalse)
2442 image_view=DestroyCacheView(image_view);
2447 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2451 % L e v e l i z e I m a g e %
2455 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2457 % LevelizeImage() applies the reversed LevelImage() operation to just
2458 % the specific channels specified. It compresses the full range of color
2459 % values, so that they lie between the given black and white points. Gamma is
2460 % applied before the values are mapped.
2462 % LevelizeImage() can be called with by using a +level command line
2463 % API option, or using a '!' on a -level or LevelImage() geometry string.
2465 % It can be used to de-contrast a greyscale image to the exact levels
2466 % specified. Or by using specific levels for each channel of an image you
2467 % can convert a gray-scale image to any linear color gradient, according to
2470 % The format of the LevelizeImage method is:
2472 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2473 % const double white_point,const double gamma,ExceptionInfo *exception)
2475 % A description of each parameter follows:
2477 % o image: the image.
2479 % o black_point: The level to map zero (black) to.
2481 % o white_point: The level to map QuantumRange (white) to.
2483 % o gamma: adjust gamma by this factor before mapping values.
2485 % o exception: return any errors or warnings in this structure.
2488 MagickExport MagickBooleanType LevelizeImage(Image *image,
2489 const double black_point,const double white_point,const double gamma,
2490 ExceptionInfo *exception)
2492 #define LevelizeImageTag "Levelize/Image"
2493 #define LevelizeValue(x) (ClampToQuantum((pow((double) (QuantumScale*(x)), \
2494 1.0/gamma))*(white_point-black_point)+black_point))
2512 Allocate and initialize levels map.
2514 assert(image != (Image *) NULL);
2515 assert(image->signature == MagickSignature);
2516 if (image->debug != MagickFalse)
2517 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2518 if (image->storage_class == PseudoClass)
2519 for (i=0; i < (ssize_t) image->colors; i++)
2524 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2525 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2526 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2527 image->colormap[i].green=(double) LevelizeValue(
2528 image->colormap[i].green);
2529 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2530 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2531 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2532 image->colormap[i].alpha=(double) LevelizeValue(
2533 image->colormap[i].alpha);
2540 image_view=AcquireAuthenticCacheView(image,exception);
2541 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2542 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2543 magick_threads(image,image,image->rows,1)
2545 for (y=0; y < (ssize_t) image->rows; y++)
2553 if (status == MagickFalse)
2555 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2556 if (q == (Quantum *) NULL)
2561 for (x=0; x < (ssize_t) image->columns; x++)
2566 if (GetPixelReadMask(image,q) == 0)
2568 q+=GetPixelChannels(image);
2571 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2573 PixelChannel channel=GetPixelChannelChannel(image,i);
2574 PixelTrait traits=GetPixelChannelTraits(image,channel);
2575 if ((traits & UpdatePixelTrait) == 0)
2577 q[i]=LevelizeValue(q[i]);
2579 q+=GetPixelChannels(image);
2581 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2583 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2588 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2589 #pragma omp critical (MagickCore_LevelizeImage)
2591 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2592 if (proceed == MagickFalse)
2596 image_view=DestroyCacheView(image_view);
2601 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2605 % L e v e l I m a g e C o l o r s %
2609 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2611 % LevelImageColors() maps the given color to "black" and "white" values,
2612 % linearly spreading out the colors, and level values on a channel by channel
2613 % bases, as per LevelImage(). The given colors allows you to specify
2614 % different level ranges for each of the color channels separately.
2616 % If the boolean 'invert' is set true the image values will modifyed in the
2617 % reverse direction. That is any existing "black" and "white" colors in the
2618 % image will become the color values given, with all other values compressed
2619 % appropriatally. This effectivally maps a greyscale gradient into the given
2622 % The format of the LevelImageColors method is:
2624 % MagickBooleanType LevelImageColors(Image *image,
2625 % const PixelInfo *black_color,const PixelInfo *white_color,
2626 % const MagickBooleanType invert,ExceptionInfo *exception)
2628 % A description of each parameter follows:
2630 % o image: the image.
2632 % o black_color: The color to map black to/from
2634 % o white_point: The color to map white to/from
2636 % o invert: if true map the colors (levelize), rather than from (level)
2638 % o exception: return any errors or warnings in this structure.
2641 MagickExport MagickBooleanType LevelImageColors(Image *image,
2642 const PixelInfo *black_color,const PixelInfo *white_color,
2643 const MagickBooleanType invert,ExceptionInfo *exception)
2652 Allocate and initialize levels map.
2654 assert(image != (Image *) NULL);
2655 assert(image->signature == MagickSignature);
2656 if (image->debug != MagickFalse)
2657 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2658 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2659 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2660 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2661 (void) SetImageColorspace(image,sRGBColorspace,exception);
2663 if (invert == MagickFalse)
2665 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2667 channel_mask=SetImageChannelMask(image,RedChannel);
2668 status|=LevelImage(image,black_color->red,white_color->red,1.0,
2670 (void) SetImageChannelMask(image,channel_mask);
2672 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2674 channel_mask=SetImageChannelMask(image,GreenChannel);
2675 status|=LevelImage(image,black_color->green,white_color->green,1.0,
2677 (void) SetImageChannelMask(image,channel_mask);
2679 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2681 channel_mask=SetImageChannelMask(image,BlueChannel);
2682 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2684 (void) SetImageChannelMask(image,channel_mask);
2686 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2687 (image->colorspace == CMYKColorspace))
2689 channel_mask=SetImageChannelMask(image,BlackChannel);
2690 status|=LevelImage(image,black_color->black,white_color->black,1.0,
2692 (void) SetImageChannelMask(image,channel_mask);
2694 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2695 (image->alpha_trait == BlendPixelTrait))
2697 channel_mask=SetImageChannelMask(image,AlphaChannel);
2698 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2700 (void) SetImageChannelMask(image,channel_mask);
2705 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2707 channel_mask=SetImageChannelMask(image,RedChannel);
2708 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2710 (void) SetImageChannelMask(image,channel_mask);
2712 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2714 channel_mask=SetImageChannelMask(image,GreenChannel);
2715 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2717 (void) SetImageChannelMask(image,channel_mask);
2719 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2721 channel_mask=SetImageChannelMask(image,BlueChannel);
2722 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2724 (void) SetImageChannelMask(image,channel_mask);
2726 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2727 (image->colorspace == CMYKColorspace))
2729 channel_mask=SetImageChannelMask(image,BlackChannel);
2730 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2732 (void) SetImageChannelMask(image,channel_mask);
2734 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2735 (image->alpha_trait == BlendPixelTrait))
2737 channel_mask=SetImageChannelMask(image,AlphaChannel);
2738 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2740 (void) SetImageChannelMask(image,channel_mask);
2743 return(status == 0 ? MagickFalse : MagickTrue);
2747 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2751 % L i n e a r S t r e t c h I m a g e %
2755 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2757 % LinearStretchImage() discards any pixels below the black point and above
2758 % the white point and levels the remaining pixels.
2760 % The format of the LinearStretchImage method is:
2762 % MagickBooleanType LinearStretchImage(Image *image,
2763 % const double black_point,const double white_point,
2764 % ExceptionInfo *exception)
2766 % A description of each parameter follows:
2768 % o image: the image.
2770 % o black_point: the black point.
2772 % o white_point: the white point.
2774 % o exception: return any errors or warnings in this structure.
2777 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2778 const double black_point,const double white_point,ExceptionInfo *exception)
2780 #define LinearStretchImageTag "LinearStretch/Image"
2798 Allocate histogram and linear map.
2800 assert(image != (Image *) NULL);
2801 assert(image->signature == MagickSignature);
2802 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2803 if (histogram == (double *) NULL)
2804 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2809 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2810 image_view=AcquireVirtualCacheView(image,exception);
2811 for (y=0; y < (ssize_t) image->rows; y++)
2813 register const Quantum
2819 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2820 if (p == (const Quantum *) NULL)
2822 for (x=0; x < (ssize_t) image->columns; x++)
2827 intensity=GetPixelIntensity(image,p);
2828 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2829 p+=GetPixelChannels(image);
2832 image_view=DestroyCacheView(image_view);
2834 Find the histogram boundaries by locating the black and white point levels.
2837 for (black=0; black < (ssize_t) MaxMap; black++)
2839 intensity+=histogram[black];
2840 if (intensity >= black_point)
2844 for (white=(ssize_t) MaxMap; white != 0; white--)
2846 intensity+=histogram[white];
2847 if (intensity >= white_point)
2850 histogram=(double *) RelinquishMagickMemory(histogram);
2851 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2856 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2860 % M o d u l a t e I m a g e %
2864 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2866 % ModulateImage() lets you control the brightness, saturation, and hue
2867 % of an image. Modulate represents the brightness, saturation, and hue
2868 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2869 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2870 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2872 % The format of the ModulateImage method is:
2874 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2875 % ExceptionInfo *exception)
2877 % A description of each parameter follows:
2879 % o image: the image.
2881 % o modulate: Define the percent change in brightness, saturation, and hue.
2883 % o exception: return any errors or warnings in this structure.
2887 static inline void ModulateHCL(const double percent_hue,
2888 const double percent_chroma,const double percent_luma,double *red,
2889 double *green,double *blue)
2897 Increase or decrease color luma, chroma, or hue.
2899 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2900 hue+=0.5*(0.01*percent_hue-1.0);
2905 chroma*=0.01*percent_chroma;
2906 luma*=0.01*percent_luma;
2907 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2910 static inline void ModulateHSB(const double percent_hue,
2911 const double percent_saturation,const double percent_brightness,double *red,
2912 double *green,double *blue)
2920 Increase or decrease color brightness, saturation, or hue.
2922 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2923 hue+=0.5*(0.01*percent_hue-1.0);
2928 saturation*=0.01*percent_saturation;
2929 brightness*=0.01*percent_brightness;
2930 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2933 static inline void ModulateHSL(const double percent_hue,
2934 const double percent_saturation,const double percent_lightness,double *red,
2935 double *green,double *blue)
2943 Increase or decrease color lightness, saturation, or hue.
2945 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2946 hue+=0.5*(0.01*percent_hue-1.0);
2951 saturation*=0.01*percent_saturation;
2952 lightness*=0.01*percent_lightness;
2953 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2956 static inline void ModulateHWB(const double percent_hue,
2957 const double percent_whiteness,const double percent_blackness,double *red,
2958 double *green,double *blue)
2966 Increase or decrease color blackness, whiteness, or hue.
2968 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2969 hue+=0.5*(0.01*percent_hue-1.0);
2974 blackness*=0.01*percent_blackness;
2975 whiteness*=0.01*percent_whiteness;
2976 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2979 static inline void ModulateLCHab(const double percent_luma,
2980 const double percent_chroma,const double percent_hue,double *red,
2981 double *green,double *blue)
2989 Increase or decrease color luma, chroma, or hue.
2991 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
2992 luma*=0.01*percent_luma;
2993 chroma*=0.01*percent_chroma;
2994 hue+=0.5*(0.01*percent_hue-1.0);
2999 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3002 static inline void ModulateLCHuv(const double percent_luma,
3003 const double percent_chroma,const double percent_hue,double *red,
3004 double *green,double *blue)
3012 Increase or decrease color luma, chroma, or hue.
3014 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3015 luma*=0.01*percent_luma;
3016 chroma*=0.01*percent_chroma;
3017 hue+=0.5*(0.01*percent_hue-1.0);
3022 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3025 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3026 ExceptionInfo *exception)
3028 #define ModulateImageTag "Modulate/Image"
3063 Initialize modulate table.
3065 assert(image != (Image *) NULL);
3066 assert(image->signature == MagickSignature);
3067 if (image->debug != MagickFalse)
3068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3069 if (modulate == (char *) NULL)
3070 return(MagickFalse);
3071 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3072 (void) SetImageColorspace(image,sRGBColorspace,exception);
3073 flags=ParseGeometry(modulate,&geometry_info);
3074 percent_brightness=geometry_info.rho;
3075 percent_saturation=geometry_info.sigma;
3076 if ((flags & SigmaValue) == 0)
3077 percent_saturation=100.0;
3078 percent_hue=geometry_info.xi;
3079 if ((flags & XiValue) == 0)
3081 colorspace=UndefinedColorspace;
3082 artifact=GetImageArtifact(image,"modulate:colorspace");
3083 if (artifact != (const char *) NULL)
3084 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3085 MagickFalse,artifact);
3086 if (image->storage_class == PseudoClass)
3087 for (i=0; i < (ssize_t) image->colors; i++)
3095 Modulate image colormap.
3097 red=(double) image->colormap[i].red;
3098 green=(double) image->colormap[i].green;
3099 blue=(double) image->colormap[i].blue;
3100 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3102 red=DecodePixelGamma((MagickRealType) red);
3103 green=DecodePixelGamma((MagickRealType) green);
3104 blue=DecodePixelGamma((MagickRealType) blue);
3110 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3116 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3123 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3129 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3133 case LCHabColorspace:
3135 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3140 case LCHuvColorspace:
3142 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3147 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3149 red=EncodePixelGamma(red);
3150 green=EncodePixelGamma(green);
3151 blue=EncodePixelGamma(blue);
3153 image->colormap[i].red=red;
3154 image->colormap[i].green=green;
3155 image->colormap[i].blue=blue;
3162 image_view=AcquireAuthenticCacheView(image,exception);
3163 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3164 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3165 magick_threads(image,image,image->rows,1)
3167 for (y=0; y < (ssize_t) image->rows; y++)
3175 if (status == MagickFalse)
3177 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3178 if (q == (Quantum *) NULL)
3183 for (x=0; x < (ssize_t) image->columns; x++)
3190 red=(double) GetPixelRed(image,q);
3191 green=(double) GetPixelGreen(image,q);
3192 blue=(double) GetPixelBlue(image,q);
3193 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3195 red=DecodePixelGamma((MagickRealType) red);
3196 green=DecodePixelGamma((MagickRealType) green);
3197 blue=DecodePixelGamma((MagickRealType) blue);
3203 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3209 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3216 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3222 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3226 case LCHabColorspace:
3228 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3233 case LCHuvColorspace:
3235 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3240 if (IssRGBColorspace(image->colorspace) != MagickFalse)
3242 red=EncodePixelGamma(red);
3243 green=EncodePixelGamma(green);
3244 blue=EncodePixelGamma(blue);
3246 SetPixelRed(image,ClampToQuantum(red),q);
3247 SetPixelGreen(image,ClampToQuantum(green),q);
3248 SetPixelBlue(image,ClampToQuantum(blue),q);
3249 q+=GetPixelChannels(image);
3251 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3253 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3258 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3259 #pragma omp critical (MagickCore_ModulateImage)
3261 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3262 if (proceed == MagickFalse)
3266 image_view=DestroyCacheView(image_view);
3271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3275 % N e g a t e I m a g e %
3279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3281 % NegateImage() negates the colors in the reference image. The grayscale
3282 % option means that only grayscale values within the image are negated.
3284 % The format of the NegateImage method is:
3286 % MagickBooleanType NegateImage(Image *image,
3287 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3289 % A description of each parameter follows:
3291 % o image: the image.
3293 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3295 % o exception: return any errors or warnings in this structure.
3298 MagickExport MagickBooleanType NegateImage(Image *image,
3299 const MagickBooleanType grayscale,ExceptionInfo *exception)
3301 #define NegateImageTag "Negate/Image"
3318 assert(image != (Image *) NULL);
3319 assert(image->signature == MagickSignature);
3320 if (image->debug != MagickFalse)
3321 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3322 if (image->storage_class == PseudoClass)
3323 for (i=0; i < (ssize_t) image->colors; i++)
3328 if (grayscale != MagickFalse)
3329 if ((image->colormap[i].red != image->colormap[i].green) ||
3330 (image->colormap[i].green != image->colormap[i].blue))
3332 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3333 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3334 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3335 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3336 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3337 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3344 image_view=AcquireAuthenticCacheView(image,exception);
3345 if (grayscale != MagickFalse)
3347 for (y=0; y < (ssize_t) image->rows; y++)
3358 if (status == MagickFalse)
3360 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3362 if (q == (Quantum *) NULL)
3367 for (x=0; x < (ssize_t) image->columns; x++)
3372 if ((GetPixelReadMask(image,q) == 0) ||
3373 (IsPixelGray(image,q) != MagickFalse))
3375 q+=GetPixelChannels(image);
3378 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3380 PixelChannel channel=GetPixelChannelChannel(image,i);
3381 PixelTrait traits=GetPixelChannelTraits(image,channel);
3382 if ((traits & UpdatePixelTrait) == 0)
3384 q[i]=QuantumRange-q[i];
3386 q+=GetPixelChannels(image);
3388 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3389 if (sync == MagickFalse)
3391 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3396 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3397 #pragma omp critical (MagickCore_NegateImage)
3399 proceed=SetImageProgress(image,NegateImageTag,progress++,
3401 if (proceed == MagickFalse)
3405 image_view=DestroyCacheView(image_view);
3411 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3412 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3413 magick_threads(image,image,image->rows,1)
3415 for (y=0; y < (ssize_t) image->rows; y++)
3423 if (status == MagickFalse)
3425 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3426 if (q == (Quantum *) NULL)
3431 for (x=0; x < (ssize_t) image->columns; x++)
3436 if (GetPixelReadMask(image,q) == 0)
3438 q+=GetPixelChannels(image);
3441 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3443 PixelChannel channel=GetPixelChannelChannel(image,i);
3444 PixelTrait traits=GetPixelChannelTraits(image,channel);
3445 if ((traits & UpdatePixelTrait) == 0)
3447 q[i]=QuantumRange-q[i];
3449 q+=GetPixelChannels(image);
3451 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3453 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3458 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3459 #pragma omp critical (MagickCore_NegateImage)
3461 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3462 if (proceed == MagickFalse)
3466 image_view=DestroyCacheView(image_view);
3471 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3475 % N o r m a l i z e I m a g e %
3479 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3481 % The NormalizeImage() method enhances the contrast of a color image by
3482 % mapping the darkest 2 percent of all pixel to black and the brightest
3483 % 1 percent to white.
3485 % The format of the NormalizeImage method is:
3487 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3489 % A description of each parameter follows:
3491 % o image: the image.
3493 % o exception: return any errors or warnings in this structure.
3496 MagickExport MagickBooleanType NormalizeImage(Image *image,
3497 ExceptionInfo *exception)
3503 black_point=(double) image->columns*image->rows*0.0015;
3504 white_point=(double) image->columns*image->rows*0.9995;
3505 return(ContrastStretchImage(image,black_point,white_point,exception));
3509 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3513 % S i g m o i d a l C o n t r a s t I m a g e %
3517 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3519 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3520 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3521 % sigmoidal transfer function without saturating highlights or shadows.
3522 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3523 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3524 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3525 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3528 % The format of the SigmoidalContrastImage method is:
3530 % MagickBooleanType SigmoidalContrastImage(Image *image,
3531 % const MagickBooleanType sharpen,const char *levels,
3532 % ExceptionInfo *exception)
3534 % A description of each parameter follows:
3536 % o image: the image.
3538 % o sharpen: Increase or decrease image contrast.
3540 % o contrast: strength of the contrast, the larger the number the more
3541 % 'threshold-like' it becomes.
3543 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3545 % o exception: return any errors or warnings in this structure.
3550 ImageMagick 6 has a version of this function which uses LUTs.
3554 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3557 The first version, based on the hyperbolic tangent tanh, when combined with
3558 the scaling step, is an exact arithmetic clone of the the sigmoid function
3559 based on the logistic curve. The equivalence is based on the identity
3561 1/(1+exp(-t)) = (1+tanh(t/2))/2
3563 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3564 scaled sigmoidal derivation is invariant under affine transformations of
3567 The tanh version is almost certainly more accurate and cheaper. The 0.5
3568 factor in the argument is to clone the legacy ImageMagick behavior. The
3569 reason for making the define depend on atanh even though it only uses tanh
3570 has to do with the construction of the inverse of the scaled sigmoidal.
3572 #if defined(MAGICKCORE_HAVE_ATANH)
3573 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3575 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3578 Scaled sigmoidal function:
3580 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3581 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3583 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3584 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3585 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3586 zero. This is fixed below by exiting immediately when contrast is small,
3587 leaving the image (or colormap) unmodified. This appears to be safe because
3588 the series expansion of the logistic sigmoidal function around x=b is
3592 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3594 #define ScaledSigmoidal(a,b,x) ( \
3595 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3596 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3598 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3599 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3600 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3601 when creating a LUT from in gamut values, hence the branching. In
3602 addition, HDRI may have out of gamut values.
3603 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3604 It is only a right inverse. This is unavoidable.
3606 static inline double InverseScaledSigmoidal(const double a,const double b,
3609 const double sig0=Sigmoidal(a,b,0.0);
3610 const double sig1=Sigmoidal(a,b,1.0);
3611 const double argument=(sig1-sig0)*x+sig0;
3612 const double clamped=
3614 #if defined(MAGICKCORE_HAVE_ATANH)
3615 argument < -1+MagickEpsilon
3619 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3621 return(b+(2.0/a)*atanh(clamped));
3623 argument < MagickEpsilon
3627 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3629 return(b-log(1.0/clamped-1.0)/a);
3633 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3634 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3635 ExceptionInfo *exception)
3637 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3638 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3639 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3640 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3641 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3658 assert(image != (Image *) NULL);
3659 assert(image->signature == MagickSignature);
3660 if (image->debug != MagickFalse)
3661 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3663 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3664 case nothing is done.
3666 if (contrast < MagickEpsilon)
3669 Sigmoidal-contrast enhance colormap.
3671 if (image->storage_class == PseudoClass)
3676 if (sharpen != MagickFalse)
3677 for (i=0; i < (ssize_t) image->colors; i++)
3679 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3680 image->colormap[i].red=(MagickRealType) ScaledSig(
3681 image->colormap[i].red);
3682 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3683 image->colormap[i].green=(MagickRealType) ScaledSig(
3684 image->colormap[i].green);
3685 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3686 image->colormap[i].blue=(MagickRealType) ScaledSig(
3687 image->colormap[i].blue);
3688 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3689 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3690 image->colormap[i].alpha);
3693 for (i=0; i < (ssize_t) image->colors; i++)
3695 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3696 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3697 image->colormap[i].red);
3698 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3699 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3700 image->colormap[i].green);
3701 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3702 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3703 image->colormap[i].blue);
3704 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3705 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3706 image->colormap[i].alpha);
3710 Sigmoidal-contrast enhance image.
3714 image_view=AcquireAuthenticCacheView(image,exception);
3715 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3716 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3717 magick_threads(image,image,image->rows,1)
3719 for (y=0; y < (ssize_t) image->rows; y++)
3727 if (status == MagickFalse)
3729 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3730 if (q == (Quantum *) NULL)
3735 for (x=0; x < (ssize_t) image->columns; x++)
3740 if (GetPixelReadMask(image,q) == 0)
3742 q+=GetPixelChannels(image);
3745 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3747 PixelChannel channel=GetPixelChannelChannel(image,i);
3748 PixelTrait traits=GetPixelChannelTraits(image,channel);
3749 if ((traits & UpdatePixelTrait) == 0)
3751 if (sharpen != MagickFalse)
3752 q[i]=ScaledSig(q[i]);
3754 q[i]=InverseScaledSig(q[i]);
3756 q+=GetPixelChannels(image);
3758 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3760 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3765 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3766 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3768 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3770 if (proceed == MagickFalse)
3774 image_view=DestroyCacheView(image_view);