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/channel.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/colorspace.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/composite-private.h"
54 #include "MagickCore/enhance.h"
55 #include "MagickCore/exception.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/fx.h"
58 #include "MagickCore/gem.h"
59 #include "MagickCore/gem-private.h"
60 #include "MagickCore/geometry.h"
61 #include "MagickCore/histogram.h"
62 #include "MagickCore/image.h"
63 #include "MagickCore/image-private.h"
64 #include "MagickCore/memory_.h"
65 #include "MagickCore/monitor.h"
66 #include "MagickCore/monitor-private.h"
67 #include "MagickCore/option.h"
68 #include "MagickCore/pixel.h"
69 #include "MagickCore/pixel-accessor.h"
70 #include "MagickCore/quantum.h"
71 #include "MagickCore/quantum-private.h"
72 #include "MagickCore/resample.h"
73 #include "MagickCore/resample-private.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/statistic.h"
76 #include "MagickCore/string_.h"
77 #include "MagickCore/string-private.h"
78 #include "MagickCore/thread-private.h"
79 #include "MagickCore/threshold.h"
80 #include "MagickCore/token.h"
81 #include "MagickCore/xml-tree.h"
82 #include "MagickCore/xml-tree-private.h"
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89 % A u t o G a m m a I m a g e %
93 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 % AutoGammaImage() extract the 'mean' from the image and adjust the image
96 % to try make set its gamma appropriatally.
98 % The format of the AutoGammaImage method is:
100 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
102 % A description of each parameter follows:
104 % o image: The image to auto-level
106 % o exception: return any errors or warnings in this structure.
109 MagickExport MagickBooleanType AutoGammaImage(Image *image,
110 ExceptionInfo *exception)
125 if (image->channel_mask == DefaultChannels)
128 Apply gamma correction equally across all given channels.
130 (void) GetImageMean(image,&mean,&sans,exception);
131 gamma=log(mean*QuantumScale)/log_mean;
132 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
135 Auto-gamma each channel separately.
138 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
143 PixelChannel channel=GetPixelChannelChannel(image,i);
144 PixelTrait traits=GetPixelChannelTraits(image,channel);
145 if ((traits & UpdatePixelTrait) == 0)
147 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
148 status=GetImageMean(image,&mean,&sans,exception);
149 gamma=log(mean*QuantumScale)/log_mean;
150 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
151 (void) SetImageChannelMask(image,channel_mask);
152 if( IfMagickFalse(status) )
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( IfMagickTrue(image->debug) )
240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
242 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
245 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
246 coefficients[0]=slope;
247 coefficients[1]=intercept;
248 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
253 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
257 % C l u t I m a g e %
261 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 % ClutImage() replaces each color value in the given image, by using it as an
264 % index to lookup a replacement color value in a Color Look UP Table in the
265 % form of an image. The values are extracted along a diagonal of the CLUT
266 % image so either a horizontal or vertial gradient image can be used.
268 % Typically this is used to either re-color a gray-scale image according to a
269 % color gradient in the CLUT image, or to perform a freeform histogram
270 % (level) adjustment according to the (typically gray-scale) gradient in the
273 % When the 'channel' mask includes the matte/alpha transparency channel but
274 % one image has no such channel it is assumed that that image is a simple
275 % gray-scale image that will effect the alpha channel values, either for
276 % gray-scale coloring (with transparent or semi-transparent colors), or
277 % a histogram adjustment of existing alpha channel values. If both images
278 % have matte channels, direct and normal indexing is applied, which is rarely
281 % The format of the ClutImage method is:
283 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
284 % const PixelInterpolateMethod method,ExceptionInfo *exception)
286 % A description of each parameter follows:
288 % o image: the image, which is replaced by indexed CLUT values
290 % o clut_image: the color lookup table image for replacement color values.
292 % o method: the pixel interpolation method.
294 % o exception: return any errors or warnings in this structure.
297 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
298 const PixelInterpolateMethod method,ExceptionInfo *exception)
300 #define ClutImageTag "Clut/Image"
321 assert(image != (Image *) NULL);
322 assert(image->signature == MagickSignature);
323 if( IfMagickTrue(image->debug) )
324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
325 assert(clut_image != (Image *) NULL);
326 assert(clut_image->signature == MagickSignature);
327 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
329 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
330 IfMagickFalse(IsGrayColorspace(clut_image->colorspace)))
331 (void) SetImageColorspace(image,sRGBColorspace,exception);
332 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
333 if (clut_map == (PixelInfo *) NULL)
334 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
341 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
342 clut_view=AcquireVirtualCacheView(clut_image,exception);
343 for (i=0; i <= (ssize_t) MaxMap; i++)
345 GetPixelInfo(clut_image,clut_map+i);
346 (void) InterpolatePixelInfo(clut_image,clut_view,method,
347 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
348 (clut_image->rows-adjust),clut_map+i,exception);
350 clut_view=DestroyCacheView(clut_view);
351 image_view=AcquireAuthenticCacheView(image,exception);
352 #if defined(MAGICKCORE_OPENMP_SUPPORT)
353 #pragma omp parallel for schedule(static,4) shared(progress,status) \
354 magick_threads(image,image,image->rows,1)
356 for (y=0; y < (ssize_t) image->rows; y++)
367 if( IfMagickFalse(status) )
369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
370 if (q == (Quantum *) NULL)
375 GetPixelInfo(image,&pixel);
376 for (x=0; x < (ssize_t) image->columns; x++)
378 if (GetPixelReadMask(image,q) == 0)
380 q+=GetPixelChannels(image);
383 GetPixelInfoPixel(image,q,&pixel);
384 pixel.red=clut_map[ScaleQuantumToMap(
385 ClampToQuantum(pixel.red))].red;
386 pixel.green=clut_map[ScaleQuantumToMap(
387 ClampToQuantum(pixel.green))].green;
388 pixel.blue=clut_map[ScaleQuantumToMap(
389 ClampToQuantum(pixel.blue))].blue;
390 pixel.black=clut_map[ScaleQuantumToMap(
391 ClampToQuantum(pixel.black))].black;
392 pixel.alpha=clut_map[ScaleQuantumToMap(
393 ClampToQuantum(pixel.alpha))].alpha;
394 SetPixelInfoPixel(image,&pixel,q);
395 q+=GetPixelChannels(image);
397 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
399 if (image->progress_monitor != (MagickProgressMonitor) NULL)
404 #if defined(MAGICKCORE_OPENMP_SUPPORT)
405 #pragma omp critical (MagickCore_ClutImage)
407 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
408 if( IfMagickFalse(proceed) )
412 image_view=DestroyCacheView(image_view);
413 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
414 if ((clut_image->alpha_trait == BlendPixelTrait) &&
415 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
416 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
421 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
425 % C o l o r D e c i s i o n L i s t I m a g e %
429 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
431 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
432 % (CCC) file which solely contains one or more color corrections and applies
433 % the correction to the image. Here is a sample CCC file:
435 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
436 % <ColorCorrection id="cc03345">
438 % <Slope> 0.9 1.2 0.5 </Slope>
439 % <Offset> 0.4 -0.5 0.6 </Offset>
440 % <Power> 1.0 0.8 1.5 </Power>
443 % <Saturation> 0.85 </Saturation>
446 % </ColorCorrectionCollection>
448 % which includes the slop, offset, and power for each of the RGB channels
449 % as well as the saturation.
451 % The format of the ColorDecisionListImage method is:
453 % MagickBooleanType ColorDecisionListImage(Image *image,
454 % const char *color_correction_collection,ExceptionInfo *exception)
456 % A description of each parameter follows:
458 % o image: the image.
460 % o color_correction_collection: the color correction collection in XML.
462 % o exception: return any errors or warnings in this structure.
465 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
466 const char *color_correction_collection,ExceptionInfo *exception)
468 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
470 typedef struct _Correction
478 typedef struct _ColorCorrection
493 token[MaxTextExtent];
524 Allocate and initialize cdl maps.
526 assert(image != (Image *) NULL);
527 assert(image->signature == MagickSignature);
528 if( IfMagickTrue(image->debug) )
529 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
530 if (color_correction_collection == (const char *) NULL)
532 ccc=NewXMLTree((const char *) color_correction_collection,exception);
533 if (ccc == (XMLTreeInfo *) NULL)
535 cc=GetXMLTreeChild(ccc,"ColorCorrection");
536 if (cc == (XMLTreeInfo *) NULL)
538 ccc=DestroyXMLTree(ccc);
541 color_correction.red.slope=1.0;
542 color_correction.red.offset=0.0;
543 color_correction.red.power=1.0;
544 color_correction.green.slope=1.0;
545 color_correction.green.offset=0.0;
546 color_correction.green.power=1.0;
547 color_correction.blue.slope=1.0;
548 color_correction.blue.offset=0.0;
549 color_correction.blue.power=1.0;
550 color_correction.saturation=0.0;
551 sop=GetXMLTreeChild(cc,"SOPNode");
552 if (sop != (XMLTreeInfo *) NULL)
559 slope=GetXMLTreeChild(sop,"Slope");
560 if (slope != (XMLTreeInfo *) NULL)
562 content=GetXMLTreeContent(slope);
563 p=(const char *) content;
564 for (i=0; (*p != '\0') && (i < 3); i++)
566 GetMagickToken(p,&p,token);
568 GetMagickToken(p,&p,token);
573 color_correction.red.slope=StringToDouble(token,(char **) NULL);
578 color_correction.green.slope=StringToDouble(token,
584 color_correction.blue.slope=StringToDouble(token,
591 offset=GetXMLTreeChild(sop,"Offset");
592 if (offset != (XMLTreeInfo *) NULL)
594 content=GetXMLTreeContent(offset);
595 p=(const char *) content;
596 for (i=0; (*p != '\0') && (i < 3); i++)
598 GetMagickToken(p,&p,token);
600 GetMagickToken(p,&p,token);
605 color_correction.red.offset=StringToDouble(token,
611 color_correction.green.offset=StringToDouble(token,
617 color_correction.blue.offset=StringToDouble(token,
624 power=GetXMLTreeChild(sop,"Power");
625 if (power != (XMLTreeInfo *) NULL)
627 content=GetXMLTreeContent(power);
628 p=(const char *) content;
629 for (i=0; (*p != '\0') && (i < 3); i++)
631 GetMagickToken(p,&p,token);
633 GetMagickToken(p,&p,token);
638 color_correction.red.power=StringToDouble(token,(char **) NULL);
643 color_correction.green.power=StringToDouble(token,
649 color_correction.blue.power=StringToDouble(token,
657 sat=GetXMLTreeChild(cc,"SATNode");
658 if (sat != (XMLTreeInfo *) NULL)
663 saturation=GetXMLTreeChild(sat,"Saturation");
664 if (saturation != (XMLTreeInfo *) NULL)
666 content=GetXMLTreeContent(saturation);
667 p=(const char *) content;
668 GetMagickToken(p,&p,token);
669 color_correction.saturation=StringToDouble(token,(char **) NULL);
672 ccc=DestroyXMLTree(ccc);
673 if( IfMagickTrue(image->debug) )
675 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
676 " Color Correction Collection:");
677 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
678 " color_correction.red.slope: %g",color_correction.red.slope);
679 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
680 " color_correction.red.offset: %g",color_correction.red.offset);
681 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
682 " color_correction.red.power: %g",color_correction.red.power);
683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
684 " color_correction.green.slope: %g",color_correction.green.slope);
685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
686 " color_correction.green.offset: %g",color_correction.green.offset);
687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
688 " color_correction.green.power: %g",color_correction.green.power);
689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
690 " color_correction.blue.slope: %g",color_correction.blue.slope);
691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
692 " color_correction.blue.offset: %g",color_correction.blue.offset);
693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
694 " color_correction.blue.power: %g",color_correction.blue.power);
695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
696 " color_correction.saturation: %g",color_correction.saturation);
698 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
699 if (cdl_map == (PixelInfo *) NULL)
700 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
702 for (i=0; i <= (ssize_t) MaxMap; i++)
704 cdl_map[i].red=(double) ScaleMapToQuantum((double)
705 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
706 color_correction.red.offset,color_correction.red.power))));
707 cdl_map[i].green=(double) ScaleMapToQuantum((double)
708 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
709 color_correction.green.offset,color_correction.green.power))));
710 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
711 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
712 color_correction.blue.offset,color_correction.blue.power))));
714 if (image->storage_class == PseudoClass)
715 for (i=0; i < (ssize_t) image->colors; i++)
718 Apply transfer function to colormap.
723 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
724 0.07217f*image->colormap[i].blue;
725 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
726 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
727 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
728 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
729 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
730 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
733 Apply transfer function to image.
737 image_view=AcquireAuthenticCacheView(image,exception);
738 #if defined(MAGICKCORE_OPENMP_SUPPORT)
739 #pragma omp parallel for schedule(static,4) shared(progress,status) \
740 magick_threads(image,image,image->rows,1)
742 for (y=0; y < (ssize_t) image->rows; y++)
753 if( IfMagickFalse(status) )
755 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
756 if (q == (Quantum *) NULL)
761 for (x=0; x < (ssize_t) image->columns; x++)
763 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
764 0.07217f*GetPixelBlue(image,q);
765 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
766 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
767 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
768 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
769 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
770 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
771 q+=GetPixelChannels(image);
773 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
775 if (image->progress_monitor != (MagickProgressMonitor) NULL)
780 #if defined(MAGICKCORE_OPENMP_SUPPORT)
781 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
783 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
784 progress++,image->rows);
785 if( IfMagickFalse(proceed) )
789 image_view=DestroyCacheView(image_view);
790 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
795 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
799 % C o n t r a s t I m a g e %
803 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
805 % ContrastImage() enhances the intensity differences between the lighter and
806 % darker elements of the image. Set sharpen to a MagickTrue to increase the
807 % image contrast otherwise the contrast is reduced.
809 % The format of the ContrastImage method is:
811 % MagickBooleanType ContrastImage(Image *image,
812 % const MagickBooleanType sharpen,ExceptionInfo *exception)
814 % A description of each parameter follows:
816 % o image: the image.
818 % o sharpen: Increase or decrease image contrast.
820 % o exception: return any errors or warnings in this structure.
824 static void Contrast(const int sign,double *red,double *green,double *blue)
832 Enhance contrast: dark color become darker, light color become lighter.
834 assert(red != (double *) NULL);
835 assert(green != (double *) NULL);
836 assert(blue != (double *) NULL);
840 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
841 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
843 if (brightness > 1.0)
846 if (brightness < 0.0)
848 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
851 MagickExport MagickBooleanType ContrastImage(Image *image,
852 const MagickBooleanType sharpen,ExceptionInfo *exception)
854 #define ContrastImageTag "Contrast/Image"
874 assert(image != (Image *) NULL);
875 assert(image->signature == MagickSignature);
876 if( IfMagickTrue(image->debug) )
877 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
878 sign=IfMagickTrue(sharpen) ? 1 : -1;
879 if (image->storage_class == PseudoClass)
882 Contrast enhance colormap.
884 for (i=0; i < (ssize_t) image->colors; i++)
891 Contrast(sign,&red,&green,&blue);
892 image->colormap[i].red=(MagickRealType) red;
893 image->colormap[i].red=(MagickRealType) red;
894 image->colormap[i].red=(MagickRealType) red;
898 Contrast enhance image.
902 image_view=AcquireAuthenticCacheView(image,exception);
903 #if defined(MAGICKCORE_OPENMP_SUPPORT)
904 #pragma omp parallel for schedule(static,4) shared(progress,status) \
905 magick_threads(image,image,image->rows,1)
907 for (y=0; y < (ssize_t) image->rows; y++)
920 if( IfMagickFalse(status) )
922 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
923 if (q == (Quantum *) NULL)
928 for (x=0; x < (ssize_t) image->columns; x++)
930 red=(double) GetPixelRed(image,q);
931 green=(double) GetPixelGreen(image,q);
932 blue=(double) GetPixelBlue(image,q);
933 Contrast(sign,&red,&green,&blue);
934 SetPixelRed(image,ClampToQuantum(red),q);
935 SetPixelGreen(image,ClampToQuantum(green),q);
936 SetPixelBlue(image,ClampToQuantum(blue),q);
937 q+=GetPixelChannels(image);
939 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
941 if (image->progress_monitor != (MagickProgressMonitor) NULL)
946 #if defined(MAGICKCORE_OPENMP_SUPPORT)
947 #pragma omp critical (MagickCore_ContrastImage)
949 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
950 if( IfMagickFalse(proceed) )
954 image_view=DestroyCacheView(image_view);
959 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
963 % C o n t r a s t S t r e t c h I m a g e %
967 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
969 % ContrastStretchImage() is a simple image enhancement technique that attempts
970 % to improve the contrast in an image by 'stretching' the range of intensity
971 % values it contains to span a desired range of values. It differs from the
972 % more sophisticated histogram equalization in that it can only apply a
973 % linear scaling function to the image pixel values. As a result the
974 % 'enhancement' is less harsh.
976 % The format of the ContrastStretchImage method is:
978 % MagickBooleanType ContrastStretchImage(Image *image,
979 % const char *levels,ExceptionInfo *exception)
981 % A description of each parameter follows:
983 % o image: the image.
985 % o black_point: the black point.
987 % o white_point: the white point.
989 % o levels: Specify the levels where the black and white points have the
990 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
992 % o exception: return any errors or warnings in this structure.
995 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
996 const double black_point,const double white_point,ExceptionInfo *exception)
998 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
999 #define ContrastStretchImageTag "ContrastStretch/Image"
1026 Allocate histogram and stretch map.
1028 assert(image != (Image *) NULL);
1029 assert(image->signature == MagickSignature);
1030 if( IfMagickTrue(image->debug) )
1031 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1032 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1033 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1034 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1035 sizeof(*histogram));
1036 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1037 GetPixelChannels(image)*sizeof(*stretch_map));
1038 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1039 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1041 if (stretch_map != (double *) NULL)
1042 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1043 if (histogram != (double *) NULL)
1044 histogram=(double *) RelinquishMagickMemory(histogram);
1045 if (white != (double *) NULL)
1046 white=(double *) RelinquishMagickMemory(white);
1047 if (black != (double *) NULL)
1048 black=(double *) RelinquishMagickMemory(black);
1049 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1055 if( IfMagickTrue(IsImageGray(image,exception)) )
1056 (void) SetImageColorspace(image,GRAYColorspace,exception);
1058 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1059 sizeof(*histogram));
1060 image_view=AcquireVirtualCacheView(image,exception);
1061 for (y=0; y < (ssize_t) image->rows; y++)
1063 register const Quantum
1069 if( IfMagickFalse(status) )
1071 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1072 if (p == (const Quantum *) NULL)
1077 for (x=0; x < (ssize_t) image->columns; x++)
1085 pixel=GetPixelIntensity(image,p);
1086 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1088 if (image->channel_mask != DefaultChannels)
1089 pixel=(double) p[i];
1090 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1091 ClampToQuantum(pixel))+i]++;
1093 p+=GetPixelChannels(image);
1096 image_view=DestroyCacheView(image_view);
1098 Find the histogram boundaries by locating the black/white levels.
1100 number_channels=GetPixelChannels(image);
1101 for (i=0; i < (ssize_t) number_channels; i++)
1110 white[i]=MaxRange(QuantumRange);
1112 for (j=0; j <= (ssize_t) MaxMap; j++)
1114 intensity+=histogram[GetPixelChannels(image)*j+i];
1115 if (intensity > black_point)
1118 black[i]=(double) j;
1120 for (j=(ssize_t) MaxMap; j != 0; j--)
1122 intensity+=histogram[GetPixelChannels(image)*j+i];
1123 if (intensity > ((double) image->columns*image->rows-white_point))
1126 white[i]=(double) j;
1128 histogram=(double *) RelinquishMagickMemory(histogram);
1130 Stretch the histogram to create the stretched image mapping.
1132 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1133 sizeof(*stretch_map));
1134 number_channels=GetPixelChannels(image);
1135 for (i=0; i < (ssize_t) number_channels; i++)
1140 for (j=0; j <= (ssize_t) MaxMap; j++)
1142 if (j < (ssize_t) black[i])
1143 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1145 if (j > (ssize_t) white[i])
1146 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1148 if (black[i] != white[i])
1149 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1150 (double) (MaxMap*(j-black[i])/(white[i]-black[i])));
1153 if (image->storage_class == PseudoClass)
1159 Stretch-contrast colormap.
1161 for (j=0; j < (ssize_t) image->colors; j++)
1163 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1165 i=GetPixelChannelChannel(image,RedPixelChannel);
1166 if (black[i] != white[i])
1167 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1168 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
1170 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1172 i=GetPixelChannelChannel(image,GreenPixelChannel);
1173 if (black[i] != white[i])
1174 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1175 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
1177 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1179 i=GetPixelChannelChannel(image,BluePixelChannel);
1180 if (black[i] != white[i])
1181 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1182 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
1184 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1186 i=GetPixelChannelChannel(image,AlphaPixelChannel);
1187 if (black[i] != white[i])
1188 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1189 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
1194 Stretch-contrast image.
1198 image_view=AcquireAuthenticCacheView(image,exception);
1199 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1200 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1201 magick_threads(image,image,image->rows,1)
1203 for (y=0; y < (ssize_t) image->rows; y++)
1211 if( IfMagickFalse(status) )
1213 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1214 if (q == (Quantum *) NULL)
1219 for (x=0; x < (ssize_t) image->columns; x++)
1224 if (GetPixelReadMask(image,q) == 0)
1226 q+=GetPixelChannels(image);
1229 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1231 PixelChannel channel=GetPixelChannelChannel(image,i);
1232 PixelTrait traits=GetPixelChannelTraits(image,channel);
1233 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1235 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1236 ScaleQuantumToMap(q[i])+i]);
1238 q+=GetPixelChannels(image);
1240 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1247 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1248 #pragma omp critical (MagickCore_ContrastStretchImage)
1250 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1252 if( IfMagickFalse(proceed) )
1256 image_view=DestroyCacheView(image_view);
1257 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1258 white=(double *) RelinquishMagickMemory(white);
1259 black=(double *) RelinquishMagickMemory(black);
1264 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1268 % E n h a n c e I m a g e %
1272 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1274 % EnhanceImage() applies a digital filter that improves the quality of a
1277 % The format of the EnhanceImage method is:
1279 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1281 % A description of each parameter follows:
1283 % o image: the image.
1285 % o exception: return any errors or warnings in this structure.
1288 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1290 #define EnhancePixel(weight) \
1291 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1292 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1293 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1294 distance*distance; \
1295 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
1297 aggregate+=(weight)*r[i]; \
1298 total_weight+=(weight); \
1300 r+=GetPixelChannels(image);
1301 #define EnhanceImageTag "Enhance/Image"
1320 Initialize enhanced image attributes.
1322 assert(image != (const Image *) NULL);
1323 assert(image->signature == MagickSignature);
1324 if( IfMagickTrue(image->debug) )
1325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1326 assert(exception != (ExceptionInfo *) NULL);
1327 assert(exception->signature == MagickSignature);
1328 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1330 if (enhance_image == (Image *) NULL)
1331 return((Image *) NULL);
1332 if( IfMagickFalse(SetImageStorageClass(enhance_image,DirectClass,exception)) )
1334 enhance_image=DestroyImage(enhance_image);
1335 return((Image *) NULL);
1342 image_view=AcquireVirtualCacheView(image,exception);
1343 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1344 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1345 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1346 magick_threads(image,enhance_image,image->rows,1)
1348 for (y=0; y < (ssize_t) image->rows; y++)
1350 register const Quantum
1362 if( IfMagickFalse(status) )
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 if (GetPixelReadMask(image,p) == 0)
1380 p+=GetPixelChannels(image);
1381 q+=GetPixelChannels(enhance_image);
1384 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1393 register const Quantum
1396 PixelChannel channel=GetPixelChannelChannel(image,i);
1397 PixelTrait traits=GetPixelChannelTraits(image,channel);
1398 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1399 if ((traits == UndefinedPixelTrait) ||
1400 (enhance_traits == UndefinedPixelTrait))
1402 SetPixelChannel(enhance_image,channel,p[center+i],q);
1403 if ((enhance_traits & CopyPixelTrait) != 0)
1406 Compute weighted average of target pixel color components.
1411 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1412 EnhancePixel(8.0); EnhancePixel(5.0);
1413 r=p+1*GetPixelChannels(image)*(image->columns+4);
1414 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1415 EnhancePixel(20.0); EnhancePixel(8.0);
1416 r=p+2*GetPixelChannels(image)*(image->columns+4);
1417 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1418 EnhancePixel(40.0); EnhancePixel(10.0);
1419 r=p+3*GetPixelChannels(image)*(image->columns+4);
1420 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1421 EnhancePixel(20.0); EnhancePixel(8.0);
1422 r=p+4*GetPixelChannels(image)*(image->columns+4);
1423 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1424 EnhancePixel(8.0); EnhancePixel(5.0);
1425 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1428 p+=GetPixelChannels(image);
1429 q+=GetPixelChannels(enhance_image);
1431 if( IfMagickFalse(SyncCacheViewAuthenticPixels(enhance_view,exception)) )
1433 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1438 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1439 #pragma omp critical (MagickCore_EnhanceImage)
1441 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1442 if( IfMagickFalse(proceed) )
1446 enhance_view=DestroyCacheView(enhance_view);
1447 image_view=DestroyCacheView(image_view);
1448 if( IfMagickFalse(status) )
1449 enhance_image=DestroyImage(enhance_image);
1450 return(enhance_image);
1454 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1458 % E q u a l i z e I m a g e %
1462 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1464 % EqualizeImage() applies a histogram equalization to the image.
1466 % The format of the EqualizeImage method is:
1468 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1470 % A description of each parameter follows:
1472 % o image: the image.
1474 % o exception: return any errors or warnings in this structure.
1477 MagickExport MagickBooleanType EqualizeImage(Image *image,
1478 ExceptionInfo *exception)
1480 #define EqualizeImageTag "Equalize/Image"
1492 black[CompositePixelChannel],
1496 white[CompositePixelChannel];
1508 Allocate and initialize histogram arrays.
1510 assert(image != (Image *) NULL);
1511 assert(image->signature == MagickSignature);
1512 if( IfMagickTrue(image->debug) )
1513 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1514 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1515 GetPixelChannels(image)*sizeof(*equalize_map));
1516 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1517 sizeof(*histogram));
1518 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1520 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1521 (map == (double *) NULL))
1523 if (map != (double *) NULL)
1524 map=(double *) RelinquishMagickMemory(map);
1525 if (histogram != (double *) NULL)
1526 histogram=(double *) RelinquishMagickMemory(histogram);
1527 if (equalize_map != (double *) NULL)
1528 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1529 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1536 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1537 sizeof(*histogram));
1538 image_view=AcquireVirtualCacheView(image,exception);
1539 for (y=0; y < (ssize_t) image->rows; y++)
1541 register const Quantum
1547 if( IfMagickFalse(status) )
1549 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1550 if (p == (const Quantum *) NULL)
1555 for (x=0; x < (ssize_t) image->columns; x++)
1560 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1561 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1562 p+=GetPixelChannels(image);
1565 image_view=DestroyCacheView(image_view);
1567 Integrate the histogram to get the equalization map.
1569 number_channels=GetPixelChannels(image);
1570 for (i=0; i < (ssize_t) number_channels; i++)
1579 for (j=0; j <= (ssize_t) MaxMap; j++)
1581 intensity+=histogram[GetPixelChannels(image)*j+i];
1582 map[GetPixelChannels(image)*j+i]=intensity;
1585 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1586 sizeof(*equalize_map));
1587 number_channels=GetPixelChannels(image);
1588 for (i=0; i < (ssize_t) number_channels; 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]=(double)
1598 ScaleMapToQuantum((double) ((MaxMap*(map[
1599 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1601 histogram=(double *) RelinquishMagickMemory(histogram);
1602 map=(double *) RelinquishMagickMemory(map);
1603 if (image->storage_class == PseudoClass)
1611 for (j=0; j < (ssize_t) image->colors; j++)
1613 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1615 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1616 if (black[channel] != white[channel])
1617 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1618 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1621 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1623 PixelChannel channel=GetPixelChannelChannel(image,
1625 if (black[channel] != white[channel])
1626 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1627 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1630 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1632 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1633 if (black[channel] != white[channel])
1634 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1635 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1638 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1640 PixelChannel channel=GetPixelChannelChannel(image,
1642 if (black[channel] != white[channel])
1643 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1644 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1653 image_view=AcquireAuthenticCacheView(image,exception);
1654 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1655 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1656 magick_threads(image,image,image->rows,1)
1658 for (y=0; y < (ssize_t) image->rows; y++)
1666 if( IfMagickFalse(status) )
1668 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1669 if (q == (Quantum *) NULL)
1674 for (x=0; x < (ssize_t) image->columns; x++)
1679 if (GetPixelReadMask(image,q) == 0)
1681 q+=GetPixelChannels(image);
1684 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1686 PixelChannel channel=GetPixelChannelChannel(image,i);
1687 PixelTrait traits=GetPixelChannelTraits(image,channel);
1688 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1690 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1691 ScaleQuantumToMap(q[i])+i]);
1693 q+=GetPixelChannels(image);
1695 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1697 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1702 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1703 #pragma omp critical (MagickCore_EqualizeImage)
1705 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1706 if( IfMagickFalse(proceed) )
1710 image_view=DestroyCacheView(image_view);
1711 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1716 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1720 % G a m m a I m a g e %
1724 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1726 % GammaImage() gamma-corrects a particular image channel. The same
1727 % image viewed on different devices will have perceptual differences in the
1728 % way the image's intensities are represented on the screen. Specify
1729 % individual gamma levels for the red, green, and blue channels, or adjust
1730 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1732 % You can also reduce the influence of a particular channel with a gamma
1735 % The format of the GammaImage method is:
1737 % MagickBooleanType GammaImage(Image *image,const double gamma,
1738 % ExceptionInfo *exception)
1740 % A description of each parameter follows:
1742 % o image: the image.
1744 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1746 % o gamma: the image gamma.
1750 static inline double gamma_pow(const double value,const double gamma)
1752 return(value < 0.0 ? value : pow(value,gamma));
1755 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1756 ExceptionInfo *exception)
1758 #define GammaCorrectImageTag "GammaCorrect/Image"
1779 Allocate and initialize gamma maps.
1781 assert(image != (Image *) NULL);
1782 assert(image->signature == MagickSignature);
1783 if( IfMagickTrue(image->debug) )
1784 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1787 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1788 if (gamma_map == (Quantum *) NULL)
1789 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1791 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1793 for (i=0; i <= (ssize_t) MaxMap; i++)
1794 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1795 MaxMap,1.0/gamma)));
1796 if (image->storage_class == PseudoClass)
1797 for (i=0; i < (ssize_t) image->colors; i++)
1800 Gamma-correct colormap.
1802 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1803 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1804 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1805 ClampToQuantum(image->colormap[i].red))];
1806 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1807 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1808 ClampToQuantum(image->colormap[i].green))];
1809 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1810 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1811 ClampToQuantum(image->colormap[i].blue))];
1812 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1813 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1814 ClampToQuantum(image->colormap[i].alpha))];
1816 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1817 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1818 image->colormap[i].red,1.0/gamma);
1819 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1820 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1821 image->colormap[i].green,1.0/gamma);
1822 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1823 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1824 image->colormap[i].blue,1.0/gamma);
1825 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1826 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1827 image->colormap[i].alpha,1.0/gamma);
1831 Gamma-correct image.
1835 image_view=AcquireAuthenticCacheView(image,exception);
1836 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1837 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1838 magick_threads(image,image,image->rows,1)
1840 for (y=0; y < (ssize_t) image->rows; y++)
1848 if( IfMagickFalse(status) )
1850 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1851 if (q == (Quantum *) NULL)
1856 for (x=0; x < (ssize_t) image->columns; x++)
1861 if (GetPixelReadMask(image,q) == 0)
1863 q+=GetPixelChannels(image);
1866 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1868 PixelChannel channel=GetPixelChannelChannel(image,i);
1869 PixelTrait traits=GetPixelChannelTraits(image,channel);
1870 if ((traits & UpdatePixelTrait) == 0)
1872 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1873 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1875 q[i]=QuantumRange*gamma_pow(QuantumScale*q[i],1.0/gamma);
1878 q+=GetPixelChannels(image);
1880 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1882 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1887 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1888 #pragma omp critical (MagickCore_GammaImage)
1890 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1892 if( IfMagickFalse(proceed) )
1896 image_view=DestroyCacheView(image_view);
1897 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1898 if (image->gamma != 0.0)
1899 image->gamma*=gamma;
1904 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1908 % G r a y s c a l e I m a g e %
1912 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1914 % GrayscaleImage() converts the image to grayscale.
1916 % The format of the GrayscaleImage method is:
1918 % MagickBooleanType GrayscaleImage(Image *image,
1919 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1921 % A description of each parameter follows:
1923 % o image: the image.
1925 % o method: the pixel intensity method.
1927 % o exception: return any errors or warnings in this structure.
1931 static inline MagickRealType MagickMax(const MagickRealType x,
1932 const MagickRealType y)
1939 static inline MagickRealType MagickMin(const MagickRealType x,
1940 const MagickRealType y)
1947 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1948 const PixelIntensityMethod method,ExceptionInfo *exception)
1950 #define GrayscaleImageTag "Grayscale/Image"
1964 assert(image != (Image *) NULL);
1965 assert(image->signature == MagickSignature);
1966 if( IfMagickTrue(image->debug) )
1967 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1968 if (image->storage_class == PseudoClass)
1970 if( IfMagickFalse(SyncImage(image,exception)) )
1971 return(MagickFalse);
1972 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
1973 return(MagickFalse);
1975 switch (image->intensity)
1977 case Rec601LuminancePixelIntensityMethod:
1978 case Rec709LuminancePixelIntensityMethod:
1980 (void) SetImageColorspace(image,RGBColorspace,exception);
1983 case Rec601LumaPixelIntensityMethod:
1984 case Rec709LumaPixelIntensityMethod:
1985 case UndefinedPixelIntensityMethod:
1987 (void) SetImageColorspace(image,sRGBColorspace,exception);
1998 image_view=AcquireAuthenticCacheView(image,exception);
1999 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2000 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2001 magick_threads(image,image,image->rows,1)
2003 for (y=0; y < (ssize_t) image->rows; y++)
2011 if( IfMagickFalse(status) )
2013 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2014 if (q == (Quantum *) NULL)
2019 for (x=0; x < (ssize_t) image->columns; x++)
2027 if (GetPixelReadMask(image,q) == 0)
2029 q+=GetPixelChannels(image);
2032 red=(MagickRealType) GetPixelRed(image,q);
2033 green=(MagickRealType) GetPixelGreen(image,q);
2034 blue=(MagickRealType) GetPixelBlue(image,q);
2038 case AveragePixelIntensityMethod:
2040 intensity=(red+green+blue)/3.0;
2043 case BrightnessPixelIntensityMethod:
2045 intensity=MagickMax(MagickMax(red,green),blue);
2048 case LightnessPixelIntensityMethod:
2050 intensity=(MagickMin(MagickMin(red,green),blue)+
2051 MagickMax(MagickMax(red,green),blue))/2.0;
2054 case MSPixelIntensityMethod:
2056 intensity=(MagickRealType) (((double) red*red+green*green+
2060 case Rec601LumaPixelIntensityMethod:
2062 if (image->colorspace == RGBColorspace)
2064 red=EncodePixelGamma(red);
2065 green=EncodePixelGamma(green);
2066 blue=EncodePixelGamma(blue);
2068 intensity=0.298839*red+0.586811*green+0.114350*blue;
2071 case Rec601LuminancePixelIntensityMethod:
2073 if (image->colorspace == sRGBColorspace)
2075 red=DecodePixelGamma(red);
2076 green=DecodePixelGamma(green);
2077 blue=DecodePixelGamma(blue);
2079 intensity=0.298839*red+0.586811*green+0.114350*blue;
2082 case Rec709LumaPixelIntensityMethod:
2085 if (image->colorspace == RGBColorspace)
2087 red=EncodePixelGamma(red);
2088 green=EncodePixelGamma(green);
2089 blue=EncodePixelGamma(blue);
2091 intensity=0.212656*red+0.715158*green+0.072186*blue;
2094 case Rec709LuminancePixelIntensityMethod:
2096 if (image->colorspace == sRGBColorspace)
2098 red=DecodePixelGamma(red);
2099 green=DecodePixelGamma(green);
2100 blue=DecodePixelGamma(blue);
2102 intensity=0.212656*red+0.715158*green+0.072186*blue;
2105 case RMSPixelIntensityMethod:
2107 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2108 blue*blue)/sqrt(3.0));
2112 SetPixelGray(image,ClampToQuantum(intensity),q);
2113 q+=GetPixelChannels(image);
2115 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
2117 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2122 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2123 #pragma omp critical (MagickCore_GrayscaleImage)
2125 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2127 if( IfMagickFalse(proceed) )
2131 image_view=DestroyCacheView(image_view);
2132 image->intensity=method;
2133 image->type=GrayscaleType;
2134 return(SetImageColorspace(image,GRAYColorspace,exception));
2138 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2142 % H a l d C l u t I m a g e %
2146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2148 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2149 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2150 % Create it with the HALD coder. You can apply any color transformation to
2151 % the Hald image and then use this method to apply the transform to the
2154 % The format of the HaldClutImage method is:
2156 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2157 % ExceptionInfo *exception)
2159 % A description of each parameter follows:
2161 % o image: the image, which is replaced by indexed CLUT values
2163 % o hald_image: the color lookup table image for replacement color values.
2165 % o exception: return any errors or warnings in this structure.
2168 MagickExport MagickBooleanType HaldClutImage(Image *image,
2169 const Image *hald_image,ExceptionInfo *exception)
2171 #define HaldClutImageTag "Clut/Image"
2173 typedef struct _HaldInfo
2205 assert(image != (Image *) NULL);
2206 assert(image->signature == MagickSignature);
2207 if( IfMagickTrue(image->debug) )
2208 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2209 assert(hald_image != (Image *) NULL);
2210 assert(hald_image->signature == MagickSignature);
2211 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
2212 return(MagickFalse);
2213 if (image->alpha_trait != BlendPixelTrait)
2214 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2220 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2221 (MagickRealType) hald_image->rows);
2222 for (level=2; (level*level*level) < length; level++) ;
2224 cube_size=level*level;
2225 width=(double) hald_image->columns;
2226 GetPixelInfo(hald_image,&zero);
2227 hald_view=AcquireVirtualCacheView(hald_image,exception);
2228 image_view=AcquireAuthenticCacheView(image,exception);
2229 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2230 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2231 magick_threads(image,image,image->rows,1)
2233 for (y=0; y < (ssize_t) image->rows; y++)
2241 if( IfMagickFalse(status) )
2243 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2244 if (q == (Quantum *) NULL)
2249 for (x=0; x < (ssize_t) image->columns; x++)
2264 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2265 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2266 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2267 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2268 point.x-=floor(point.x);
2269 point.y-=floor(point.y);
2270 point.z-=floor(point.z);
2272 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2273 fmod(offset,width),floor(offset/width),&pixel1,exception);
2275 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2276 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2278 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2281 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2282 fmod(offset,width),floor(offset/width),&pixel1,exception);
2283 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2284 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2286 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2289 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2291 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2292 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2293 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2294 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2295 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2296 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2297 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2298 (image->colorspace == CMYKColorspace))
2299 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2300 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2301 (image->alpha_trait == BlendPixelTrait))
2302 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2303 q+=GetPixelChannels(image);
2305 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2307 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2312 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2313 #pragma omp critical (MagickCore_HaldClutImage)
2315 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2316 if( IfMagickFalse(proceed) )
2320 hald_view=DestroyCacheView(hald_view);
2321 image_view=DestroyCacheView(image_view);
2326 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2330 % L e v e l I m a g e %
2334 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2336 % LevelImage() adjusts the levels of a particular image channel by
2337 % scaling the colors falling between specified white and black points to
2338 % the full available quantum range.
2340 % The parameters provided represent the black, and white points. The black
2341 % point specifies the darkest color in the image. Colors darker than the
2342 % black point are set to zero. White point specifies the lightest color in
2343 % the image. Colors brighter than the white point are set to the maximum
2346 % If a '!' flag is given, map black and white colors to the given levels
2347 % rather than mapping those levels to black and white. See
2348 % LevelizeImage() below.
2350 % Gamma specifies a gamma correction to apply to the image.
2352 % The format of the LevelImage method is:
2354 % MagickBooleanType LevelImage(Image *image,const double black_point,
2355 % const double white_point,const double gamma,ExceptionInfo *exception)
2357 % A description of each parameter follows:
2359 % o image: the image.
2361 % o black_point: The level to map zero (black) to.
2363 % o white_point: The level to map QuantumRange (white) to.
2365 % o exception: return any errors or warnings in this structure.
2369 static inline double LevelPixel(const double black_point,
2370 const double white_point,const double gamma,const double pixel)
2376 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2377 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2379 return(level_pixel);
2382 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2383 const double white_point,const double gamma,ExceptionInfo *exception)
2385 #define LevelImageTag "Level/Image"
2403 Allocate and initialize levels map.
2405 assert(image != (Image *) NULL);
2406 assert(image->signature == MagickSignature);
2407 if( IfMagickTrue(image->debug) )
2408 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2409 if (image->storage_class == PseudoClass)
2410 for (i=0; i < (ssize_t) image->colors; i++)
2415 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2416 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2417 white_point,gamma,image->colormap[i].red));
2418 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2419 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2420 white_point,gamma,image->colormap[i].green));
2421 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2422 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2423 white_point,gamma,image->colormap[i].blue));
2424 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2425 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2426 white_point,gamma,image->colormap[i].alpha));
2433 image_view=AcquireAuthenticCacheView(image,exception);
2434 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2435 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2436 magick_threads(image,image,image->rows,1)
2438 for (y=0; y < (ssize_t) image->rows; y++)
2446 if( IfMagickFalse(status) )
2448 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2449 if (q == (Quantum *) NULL)
2454 for (x=0; x < (ssize_t) image->columns; x++)
2459 if (GetPixelReadMask(image,q) == 0)
2461 q+=GetPixelChannels(image);
2464 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2466 PixelChannel channel=GetPixelChannelChannel(image,i);
2467 PixelTrait traits=GetPixelChannelTraits(image,channel);
2468 if ((traits & UpdatePixelTrait) == 0)
2470 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2473 q+=GetPixelChannels(image);
2475 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2477 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2482 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2483 #pragma omp critical (MagickCore_LevelImage)
2485 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2486 if( IfMagickFalse(proceed) )
2490 image_view=DestroyCacheView(image_view);
2495 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499 % L e v e l i z e I m a g e %
2503 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2505 % LevelizeImage() applies the reversed LevelImage() operation to just
2506 % the specific channels specified. It compresses the full range of color
2507 % values, so that they lie between the given black and white points. Gamma is
2508 % applied before the values are mapped.
2510 % LevelizeImage() can be called with by using a +level command line
2511 % API option, or using a '!' on a -level or LevelImage() geometry string.
2513 % It can be used to de-contrast a greyscale image to the exact levels
2514 % specified. Or by using specific levels for each channel of an image you
2515 % can convert a gray-scale image to any linear color gradient, according to
2518 % The format of the LevelizeImage method is:
2520 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2521 % const double white_point,const double gamma,ExceptionInfo *exception)
2523 % A description of each parameter follows:
2525 % o image: the image.
2527 % o black_point: The level to map zero (black) to.
2529 % o white_point: The level to map QuantumRange (white) to.
2531 % o gamma: adjust gamma by this factor before mapping values.
2533 % o exception: return any errors or warnings in this structure.
2536 MagickExport MagickBooleanType LevelizeImage(Image *image,
2537 const double black_point,const double white_point,const double gamma,
2538 ExceptionInfo *exception)
2540 #define LevelizeImageTag "Levelize/Image"
2541 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2542 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2560 Allocate and initialize levels map.
2562 assert(image != (Image *) NULL);
2563 assert(image->signature == MagickSignature);
2564 if( IfMagickTrue(image->debug) )
2565 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2566 if (image->storage_class == PseudoClass)
2567 for (i=0; i < (ssize_t) image->colors; i++)
2572 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2573 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2574 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2575 image->colormap[i].green=(double) LevelizeValue(
2576 image->colormap[i].green);
2577 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2578 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2579 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2580 image->colormap[i].alpha=(double) LevelizeValue(
2581 image->colormap[i].alpha);
2588 image_view=AcquireAuthenticCacheView(image,exception);
2589 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2590 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2591 magick_threads(image,image,image->rows,1)
2593 for (y=0; y < (ssize_t) image->rows; y++)
2601 if( IfMagickFalse(status) )
2603 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2604 if (q == (Quantum *) NULL)
2609 for (x=0; x < (ssize_t) image->columns; x++)
2614 if (GetPixelReadMask(image,q) == 0)
2616 q+=GetPixelChannels(image);
2619 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2621 PixelChannel channel=GetPixelChannelChannel(image,i);
2622 PixelTrait traits=GetPixelChannelTraits(image,channel);
2623 if ((traits & UpdatePixelTrait) == 0)
2625 q[i]=LevelizeValue(q[i]);
2627 q+=GetPixelChannels(image);
2629 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2631 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2636 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2637 #pragma omp critical (MagickCore_LevelizeImage)
2639 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2640 if( IfMagickFalse(proceed) )
2644 image_view=DestroyCacheView(image_view);
2649 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2653 % L e v e l I m a g e C o l o r s %
2657 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2659 % LevelImageColors() maps the given color to "black" and "white" values,
2660 % linearly spreading out the colors, and level values on a channel by channel
2661 % bases, as per LevelImage(). The given colors allows you to specify
2662 % different level ranges for each of the color channels separately.
2664 % If the boolean 'invert' is set true the image values will modifyed in the
2665 % reverse direction. That is any existing "black" and "white" colors in the
2666 % image will become the color values given, with all other values compressed
2667 % appropriatally. This effectivally maps a greyscale gradient into the given
2670 % The format of the LevelImageColors method is:
2672 % MagickBooleanType LevelImageColors(Image *image,
2673 % const PixelInfo *black_color,const PixelInfo *white_color,
2674 % const MagickBooleanType invert,ExceptionInfo *exception)
2676 % A description of each parameter follows:
2678 % o image: the image.
2680 % o black_color: The color to map black to/from
2682 % o white_point: The color to map white to/from
2684 % o invert: if true map the colors (levelize), rather than from (level)
2686 % o exception: return any errors or warnings in this structure.
2689 MagickExport MagickBooleanType LevelImageColors(Image *image,
2690 const PixelInfo *black_color,const PixelInfo *white_color,
2691 const MagickBooleanType invert,ExceptionInfo *exception)
2700 Allocate and initialize levels map.
2702 assert(image != (Image *) NULL);
2703 assert(image->signature == MagickSignature);
2704 if( IfMagickTrue(image->debug) )
2705 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2706 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2707 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2708 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
2709 (void) SetImageColorspace(image,sRGBColorspace,exception);
2711 if( IfMagickFalse(invert) )
2713 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2715 channel_mask=SetImageChannelMask(image,RedChannel);
2716 status|=LevelImage(image,black_color->red,white_color->red,1.0,
2718 (void) SetImageChannelMask(image,channel_mask);
2720 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2722 channel_mask=SetImageChannelMask(image,GreenChannel);
2723 status|=LevelImage(image,black_color->green,white_color->green,1.0,
2725 (void) SetImageChannelMask(image,channel_mask);
2727 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2729 channel_mask=SetImageChannelMask(image,BlueChannel);
2730 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2732 (void) SetImageChannelMask(image,channel_mask);
2734 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2735 (image->colorspace == CMYKColorspace))
2737 channel_mask=SetImageChannelMask(image,BlackChannel);
2738 status|=LevelImage(image,black_color->black,white_color->black,1.0,
2740 (void) SetImageChannelMask(image,channel_mask);
2742 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2743 (image->alpha_trait == BlendPixelTrait))
2745 channel_mask=SetImageChannelMask(image,AlphaChannel);
2746 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2748 (void) SetImageChannelMask(image,channel_mask);
2753 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2755 channel_mask=SetImageChannelMask(image,RedChannel);
2756 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2758 (void) SetImageChannelMask(image,channel_mask);
2760 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2762 channel_mask=SetImageChannelMask(image,GreenChannel);
2763 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2765 (void) SetImageChannelMask(image,channel_mask);
2767 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2769 channel_mask=SetImageChannelMask(image,BlueChannel);
2770 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2772 (void) SetImageChannelMask(image,channel_mask);
2774 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2775 (image->colorspace == CMYKColorspace))
2777 channel_mask=SetImageChannelMask(image,BlackChannel);
2778 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2780 (void) SetImageChannelMask(image,channel_mask);
2782 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2783 (image->alpha_trait == BlendPixelTrait))
2785 channel_mask=SetImageChannelMask(image,AlphaChannel);
2786 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2788 (void) SetImageChannelMask(image,channel_mask);
2791 return(status != 0 ? MagickTrue : MagickFalse);
2795 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2799 % L i n e a r S t r e t c h I m a g e %
2803 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2805 % LinearStretchImage() discards any pixels below the black point and above
2806 % the white point and levels the remaining pixels.
2808 % The format of the LinearStretchImage method is:
2810 % MagickBooleanType LinearStretchImage(Image *image,
2811 % const double black_point,const double white_point,
2812 % ExceptionInfo *exception)
2814 % A description of each parameter follows:
2816 % o image: the image.
2818 % o black_point: the black point.
2820 % o white_point: the white point.
2822 % o exception: return any errors or warnings in this structure.
2825 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2826 const double black_point,const double white_point,ExceptionInfo *exception)
2828 #define LinearStretchImageTag "LinearStretch/Image"
2846 Allocate histogram and linear map.
2848 assert(image != (Image *) NULL);
2849 assert(image->signature == MagickSignature);
2850 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2851 if (histogram == (double *) NULL)
2852 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2857 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2858 image_view=AcquireVirtualCacheView(image,exception);
2859 for (y=0; y < (ssize_t) image->rows; y++)
2861 register const Quantum
2867 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2868 if (p == (const Quantum *) NULL)
2870 for (x=0; x < (ssize_t) image->columns; x++)
2875 intensity=GetPixelIntensity(image,p);
2876 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2877 p+=GetPixelChannels(image);
2880 image_view=DestroyCacheView(image_view);
2882 Find the histogram boundaries by locating the black and white point levels.
2885 for (black=0; black < (ssize_t) MaxMap; black++)
2887 intensity+=histogram[black];
2888 if (intensity >= black_point)
2892 for (white=(ssize_t) MaxMap; white != 0; white--)
2894 intensity+=histogram[white];
2895 if (intensity >= white_point)
2898 histogram=(double *) RelinquishMagickMemory(histogram);
2899 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2904 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2908 % M o d u l a t e I m a g e %
2912 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2914 % ModulateImage() lets you control the brightness, saturation, and hue
2915 % of an image. Modulate represents the brightness, saturation, and hue
2916 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2917 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2918 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2920 % The format of the ModulateImage method is:
2922 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2923 % ExceptionInfo *exception)
2925 % A description of each parameter follows:
2927 % o image: the image.
2929 % o modulate: Define the percent change in brightness, saturation, and hue.
2931 % o exception: return any errors or warnings in this structure.
2935 static inline void ModulateHCL(const double percent_hue,
2936 const double percent_chroma,const double percent_luma,double *red,
2937 double *green,double *blue)
2945 Increase or decrease color luma, chroma, or hue.
2947 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2948 hue+=0.5*(0.01*percent_hue-1.0);
2953 chroma*=0.01*percent_chroma;
2954 luma*=0.01*percent_luma;
2955 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2958 static inline void ModulateHCLp(const double percent_hue,
2959 const double percent_chroma,const double percent_luma,double *red,
2960 double *green,double *blue)
2968 Increase or decrease color luma, chroma, or hue.
2970 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2971 hue+=0.5*(0.01*percent_hue-1.0);
2976 chroma*=0.01*percent_chroma;
2977 luma*=0.01*percent_luma;
2978 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2981 static inline void ModulateHSB(const double percent_hue,
2982 const double percent_saturation,const double percent_brightness,double *red,
2983 double *green,double *blue)
2991 Increase or decrease color brightness, saturation, or hue.
2993 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2994 hue+=0.5*(0.01*percent_hue-1.0);
2999 saturation*=0.01*percent_saturation;
3000 brightness*=0.01*percent_brightness;
3001 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3004 static inline void ModulateHSI(const double percent_hue,
3005 const double percent_saturation,const double percent_intensity,double *red,
3006 double *green,double *blue)
3014 Increase or decrease color intensity, saturation, or hue.
3016 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3017 hue+=0.5*(0.01*percent_hue-1.0);
3022 saturation*=0.01*percent_saturation;
3023 intensity*=0.01*percent_intensity;
3024 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3027 static inline void ModulateHSL(const double percent_hue,
3028 const double percent_saturation,const double percent_lightness,double *red,
3029 double *green,double *blue)
3037 Increase or decrease color lightness, saturation, or hue.
3039 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3040 hue+=0.5*(0.01*percent_hue-1.0);
3045 saturation*=0.01*percent_saturation;
3046 lightness*=0.01*percent_lightness;
3047 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3050 static inline void ModulateHSV(const double percent_hue,
3051 const double percent_saturation,const double percent_value,double *red,
3052 double *green,double *blue)
3060 Increase or decrease color value, saturation, or hue.
3062 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3063 hue+=0.5*(0.01*percent_hue-1.0);
3068 saturation*=0.01*percent_saturation;
3069 value*=0.01*percent_value;
3070 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3073 static inline void ModulateHWB(const double percent_hue,
3074 const double percent_whiteness,const double percent_blackness,double *red,
3075 double *green,double *blue)
3083 Increase or decrease color blackness, whiteness, or hue.
3085 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3086 hue+=0.5*(0.01*percent_hue-1.0);
3091 blackness*=0.01*percent_blackness;
3092 whiteness*=0.01*percent_whiteness;
3093 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3096 static inline void ModulateLCHab(const double percent_luma,
3097 const double percent_chroma,const double percent_hue,double *red,
3098 double *green,double *blue)
3106 Increase or decrease color luma, chroma, or hue.
3108 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3109 luma*=0.01*percent_luma;
3110 chroma*=0.01*percent_chroma;
3111 hue+=0.5*(0.01*percent_hue-1.0);
3116 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3119 static inline void ModulateLCHuv(const double percent_luma,
3120 const double percent_chroma,const double percent_hue,double *red,
3121 double *green,double *blue)
3129 Increase or decrease color luma, chroma, or hue.
3131 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3132 luma*=0.01*percent_luma;
3133 chroma*=0.01*percent_chroma;
3134 hue+=0.5*(0.01*percent_hue-1.0);
3139 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3142 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3143 ExceptionInfo *exception)
3145 #define ModulateImageTag "Modulate/Image"
3180 Initialize modulate table.
3182 assert(image != (Image *) NULL);
3183 assert(image->signature == MagickSignature);
3184 if( IfMagickTrue(image->debug) )
3185 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3186 if (modulate == (char *) NULL)
3187 return(MagickFalse);
3188 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
3189 (void) SetImageColorspace(image,sRGBColorspace,exception);
3190 flags=ParseGeometry(modulate,&geometry_info);
3191 percent_brightness=geometry_info.rho;
3192 percent_saturation=geometry_info.sigma;
3193 if ((flags & SigmaValue) == 0)
3194 percent_saturation=100.0;
3195 percent_hue=geometry_info.xi;
3196 if ((flags & XiValue) == 0)
3198 colorspace=UndefinedColorspace;
3199 artifact=GetImageArtifact(image,"modulate:colorspace");
3200 if (artifact != (const char *) NULL)
3201 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3202 MagickFalse,artifact);
3203 if (image->storage_class == PseudoClass)
3204 for (i=0; i < (ssize_t) image->colors; i++)
3212 Modulate image colormap.
3214 red=(double) image->colormap[i].red;
3215 green=(double) image->colormap[i].green;
3216 blue=(double) image->colormap[i].blue;
3221 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3225 case HCLpColorspace:
3227 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3233 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3239 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3246 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3252 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3258 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3263 case LCHabColorspace:
3265 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3269 case LCHuvColorspace:
3271 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3276 image->colormap[i].red=red;
3277 image->colormap[i].green=green;
3278 image->colormap[i].blue=blue;
3285 image_view=AcquireAuthenticCacheView(image,exception);
3286 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3287 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3288 magick_threads(image,image,image->rows,1)
3290 for (y=0; y < (ssize_t) image->rows; y++)
3298 if( IfMagickFalse(status) )
3300 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3301 if (q == (Quantum *) NULL)
3306 for (x=0; x < (ssize_t) image->columns; x++)
3313 red=(double) GetPixelRed(image,q);
3314 green=(double) GetPixelGreen(image,q);
3315 blue=(double) GetPixelBlue(image,q);
3320 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3326 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3333 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3339 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3345 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3349 case LCHabColorspace:
3351 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3356 case LCHuvColorspace:
3358 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3363 SetPixelRed(image,ClampToQuantum(red),q);
3364 SetPixelGreen(image,ClampToQuantum(green),q);
3365 SetPixelBlue(image,ClampToQuantum(blue),q);
3366 q+=GetPixelChannels(image);
3368 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3370 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3375 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3376 #pragma omp critical (MagickCore_ModulateImage)
3378 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3379 if( IfMagickFalse(proceed) )
3383 image_view=DestroyCacheView(image_view);
3388 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392 % N e g a t e I m a g e %
3396 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3398 % NegateImage() negates the colors in the reference image. The grayscale
3399 % option means that only grayscale values within the image are negated.
3401 % The format of the NegateImage method is:
3403 % MagickBooleanType NegateImage(Image *image,
3404 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3406 % A description of each parameter follows:
3408 % o image: the image.
3410 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3412 % o exception: return any errors or warnings in this structure.
3415 MagickExport MagickBooleanType NegateImage(Image *image,
3416 const MagickBooleanType grayscale,ExceptionInfo *exception)
3418 #define NegateImageTag "Negate/Image"
3435 assert(image != (Image *) NULL);
3436 assert(image->signature == MagickSignature);
3437 if( IfMagickTrue(image->debug) )
3438 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3439 if (image->storage_class == PseudoClass)
3440 for (i=0; i < (ssize_t) image->colors; i++)
3445 if( IfMagickTrue(grayscale) )
3446 if ((image->colormap[i].red != image->colormap[i].green) ||
3447 (image->colormap[i].green != image->colormap[i].blue))
3449 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3450 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3451 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3452 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3453 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3454 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3461 image_view=AcquireAuthenticCacheView(image,exception);
3462 if( IfMagickTrue(grayscale) )
3464 for (y=0; y < (ssize_t) image->rows; y++)
3475 if( IfMagickFalse(status) )
3477 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3479 if (q == (Quantum *) NULL)
3484 for (x=0; x < (ssize_t) image->columns; x++)
3489 if ((GetPixelReadMask(image,q) == 0) ||
3490 IfMagickTrue(IsPixelGray(image,q)))
3492 q+=GetPixelChannels(image);
3495 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3497 PixelChannel channel=GetPixelChannelChannel(image,i);
3498 PixelTrait traits=GetPixelChannelTraits(image,channel);
3499 if ((traits & UpdatePixelTrait) == 0)
3501 q[i]=QuantumRange-q[i];
3503 q+=GetPixelChannels(image);
3505 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3506 if( IfMagickFalse(sync) )
3508 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3513 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3514 #pragma omp critical (MagickCore_NegateImage)
3516 proceed=SetImageProgress(image,NegateImageTag,progress++,
3518 if( IfMagickFalse(proceed) )
3522 image_view=DestroyCacheView(image_view);
3528 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3529 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3530 magick_threads(image,image,image->rows,1)
3532 for (y=0; y < (ssize_t) image->rows; y++)
3540 if( IfMagickFalse(status) )
3542 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3543 if (q == (Quantum *) NULL)
3548 for (x=0; x < (ssize_t) image->columns; x++)
3553 if (GetPixelReadMask(image,q) == 0)
3555 q+=GetPixelChannels(image);
3558 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3560 PixelChannel channel=GetPixelChannelChannel(image,i);
3561 PixelTrait traits=GetPixelChannelTraits(image,channel);
3562 if ((traits & UpdatePixelTrait) == 0)
3564 q[i]=QuantumRange-q[i];
3566 q+=GetPixelChannels(image);
3568 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3570 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3575 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3576 #pragma omp critical (MagickCore_NegateImage)
3578 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3579 if( IfMagickFalse(proceed) )
3583 image_view=DestroyCacheView(image_view);
3588 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3592 % N o r m a l i z e I m a g e %
3596 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3598 % The NormalizeImage() method enhances the contrast of a color image by
3599 % mapping the darkest 2 percent of all pixel to black and the brightest
3600 % 1 percent to white.
3602 % The format of the NormalizeImage method is:
3604 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3606 % A description of each parameter follows:
3608 % o image: the image.
3610 % o exception: return any errors or warnings in this structure.
3613 MagickExport MagickBooleanType NormalizeImage(Image *image,
3614 ExceptionInfo *exception)
3620 black_point=(double) image->columns*image->rows*0.0015;
3621 white_point=(double) image->columns*image->rows*0.9995;
3622 return(ContrastStretchImage(image,black_point,white_point,exception));
3626 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3630 % S i g m o i d a l C o n t r a s t I m a g e %
3634 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3636 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3637 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3638 % sigmoidal transfer function without saturating highlights or shadows.
3639 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3640 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3641 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3642 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3645 % The format of the SigmoidalContrastImage method is:
3647 % MagickBooleanType SigmoidalContrastImage(Image *image,
3648 % const MagickBooleanType sharpen,const char *levels,
3649 % ExceptionInfo *exception)
3651 % A description of each parameter follows:
3653 % o image: the image.
3655 % o sharpen: Increase or decrease image contrast.
3657 % o contrast: strength of the contrast, the larger the number the more
3658 % 'threshold-like' it becomes.
3660 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3662 % o exception: return any errors or warnings in this structure.
3667 ImageMagick 6 has a version of this function which uses LUTs.
3671 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3674 The first version, based on the hyperbolic tangent tanh, when combined with
3675 the scaling step, is an exact arithmetic clone of the the sigmoid function
3676 based on the logistic curve. The equivalence is based on the identity
3678 1/(1+exp(-t)) = (1+tanh(t/2))/2
3680 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3681 scaled sigmoidal derivation is invariant under affine transformations of
3684 The tanh version is almost certainly more accurate and cheaper. The 0.5
3685 factor in the argument is to clone the legacy ImageMagick behavior. The
3686 reason for making the define depend on atanh even though it only uses tanh
3687 has to do with the construction of the inverse of the scaled sigmoidal.
3689 #if defined(MAGICKCORE_HAVE_ATANH)
3690 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3692 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3695 Scaled sigmoidal function:
3697 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3698 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3700 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3701 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3702 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3703 zero. This is fixed below by exiting immediately when contrast is small,
3704 leaving the image (or colormap) unmodified. This appears to be safe because
3705 the series expansion of the logistic sigmoidal function around x=b is
3709 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3711 #define ScaledSigmoidal(a,b,x) ( \
3712 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3713 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3715 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3716 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3717 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3718 when creating a LUT from in gamut values, hence the branching. In
3719 addition, HDRI may have out of gamut values.
3720 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3721 It is only a right inverse. This is unavoidable.
3723 static inline double InverseScaledSigmoidal(const double a,const double b,
3726 const double sig0=Sigmoidal(a,b,0.0);
3727 const double sig1=Sigmoidal(a,b,1.0);
3728 const double argument=(sig1-sig0)*x+sig0;
3729 const double clamped=
3731 #if defined(MAGICKCORE_HAVE_ATANH)
3732 argument < -1+MagickEpsilon
3736 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3738 return(b+(2.0/a)*atanh(clamped));
3740 argument < MagickEpsilon
3744 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3746 return(b-log(1.0/clamped-1.0)/a);
3750 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3751 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3752 ExceptionInfo *exception)
3754 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3755 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3756 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3757 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3758 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3775 assert(image != (Image *) NULL);
3776 assert(image->signature == MagickSignature);
3777 if( IfMagickTrue(image->debug) )
3778 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3780 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3781 case nothing is done.
3783 if (contrast < MagickEpsilon)
3786 Sigmoidal-contrast enhance colormap.
3788 if (image->storage_class == PseudoClass)
3793 if( IfMagickTrue(sharpen) )
3794 for (i=0; i < (ssize_t) image->colors; i++)
3796 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3797 image->colormap[i].red=(MagickRealType) ScaledSig(
3798 image->colormap[i].red);
3799 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3800 image->colormap[i].green=(MagickRealType) ScaledSig(
3801 image->colormap[i].green);
3802 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3803 image->colormap[i].blue=(MagickRealType) ScaledSig(
3804 image->colormap[i].blue);
3805 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3806 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3807 image->colormap[i].alpha);
3810 for (i=0; i < (ssize_t) image->colors; i++)
3812 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3813 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3814 image->colormap[i].red);
3815 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3816 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3817 image->colormap[i].green);
3818 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3819 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3820 image->colormap[i].blue);
3821 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3822 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3823 image->colormap[i].alpha);
3827 Sigmoidal-contrast enhance image.
3831 image_view=AcquireAuthenticCacheView(image,exception);
3832 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3833 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3834 magick_threads(image,image,image->rows,1)
3836 for (y=0; y < (ssize_t) image->rows; y++)
3844 if( IfMagickFalse(status) )
3846 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3847 if (q == (Quantum *) NULL)
3852 for (x=0; x < (ssize_t) image->columns; x++)
3857 if (GetPixelReadMask(image,q) == 0)
3859 q+=GetPixelChannels(image);
3862 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3864 PixelChannel channel=GetPixelChannelChannel(image,i);
3865 PixelTrait traits=GetPixelChannelTraits(image,channel);
3866 if ((traits & UpdatePixelTrait) == 0)
3868 if( IfMagickTrue(sharpen) )
3869 q[i]=ScaledSig(q[i]);
3871 q[i]=InverseScaledSig(q[i]);
3873 q+=GetPixelChannels(image);
3875 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3877 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3882 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3883 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3885 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3887 if( IfMagickFalse(proceed) )
3891 image_view=DestroyCacheView(image_view);