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-2014 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 SetPixelBackgoundColor(enhance_image,q);
1381 p+=GetPixelChannels(image);
1382 q+=GetPixelChannels(enhance_image);
1385 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1394 register const Quantum
1397 PixelChannel channel=GetPixelChannelChannel(image,i);
1398 PixelTrait traits=GetPixelChannelTraits(image,channel);
1399 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1400 if ((traits == UndefinedPixelTrait) ||
1401 (enhance_traits == UndefinedPixelTrait))
1403 SetPixelChannel(enhance_image,channel,p[center+i],q);
1404 if ((enhance_traits & CopyPixelTrait) != 0)
1407 Compute weighted average of target pixel color components.
1412 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1413 EnhancePixel(8.0); EnhancePixel(5.0);
1414 r=p+1*GetPixelChannels(image)*(image->columns+4);
1415 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1416 EnhancePixel(20.0); EnhancePixel(8.0);
1417 r=p+2*GetPixelChannels(image)*(image->columns+4);
1418 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1419 EnhancePixel(40.0); EnhancePixel(10.0);
1420 r=p+3*GetPixelChannels(image)*(image->columns+4);
1421 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1422 EnhancePixel(20.0); EnhancePixel(8.0);
1423 r=p+4*GetPixelChannels(image)*(image->columns+4);
1424 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1425 EnhancePixel(8.0); EnhancePixel(5.0);
1426 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1429 p+=GetPixelChannels(image);
1430 q+=GetPixelChannels(enhance_image);
1432 if( IfMagickFalse(SyncCacheViewAuthenticPixels(enhance_view,exception)) )
1434 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1439 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1440 #pragma omp critical (MagickCore_EnhanceImage)
1442 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1443 if( IfMagickFalse(proceed) )
1447 enhance_view=DestroyCacheView(enhance_view);
1448 image_view=DestroyCacheView(image_view);
1449 if( IfMagickFalse(status) )
1450 enhance_image=DestroyImage(enhance_image);
1451 return(enhance_image);
1455 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1459 % E q u a l i z e I m a g e %
1463 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1465 % EqualizeImage() applies a histogram equalization to the image.
1467 % The format of the EqualizeImage method is:
1469 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1471 % A description of each parameter follows:
1473 % o image: the image.
1475 % o exception: return any errors or warnings in this structure.
1478 MagickExport MagickBooleanType EqualizeImage(Image *image,
1479 ExceptionInfo *exception)
1481 #define EqualizeImageTag "Equalize/Image"
1493 black[CompositePixelChannel],
1497 white[CompositePixelChannel];
1509 Allocate and initialize histogram arrays.
1511 assert(image != (Image *) NULL);
1512 assert(image->signature == MagickSignature);
1513 if( IfMagickTrue(image->debug) )
1514 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1515 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1516 GetPixelChannels(image)*sizeof(*equalize_map));
1517 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1518 sizeof(*histogram));
1519 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1521 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1522 (map == (double *) NULL))
1524 if (map != (double *) NULL)
1525 map=(double *) RelinquishMagickMemory(map);
1526 if (histogram != (double *) NULL)
1527 histogram=(double *) RelinquishMagickMemory(histogram);
1528 if (equalize_map != (double *) NULL)
1529 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1530 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1537 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1538 sizeof(*histogram));
1539 image_view=AcquireVirtualCacheView(image,exception);
1540 for (y=0; y < (ssize_t) image->rows; y++)
1542 register const Quantum
1548 if( IfMagickFalse(status) )
1550 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1551 if (p == (const Quantum *) NULL)
1556 for (x=0; x < (ssize_t) image->columns; x++)
1561 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1562 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1563 p+=GetPixelChannels(image);
1566 image_view=DestroyCacheView(image_view);
1568 Integrate the histogram to get the equalization map.
1570 number_channels=GetPixelChannels(image);
1571 for (i=0; i < (ssize_t) number_channels; i++)
1580 for (j=0; j <= (ssize_t) MaxMap; j++)
1582 intensity+=histogram[GetPixelChannels(image)*j+i];
1583 map[GetPixelChannels(image)*j+i]=intensity;
1586 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1587 sizeof(*equalize_map));
1588 number_channels=GetPixelChannels(image);
1589 for (i=0; i < (ssize_t) number_channels; i++)
1595 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1596 if (black[i] != white[i])
1597 for (j=0; j <= (ssize_t) MaxMap; j++)
1598 equalize_map[GetPixelChannels(image)*j+i]=(double)
1599 ScaleMapToQuantum((double) ((MaxMap*(map[
1600 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1602 histogram=(double *) RelinquishMagickMemory(histogram);
1603 map=(double *) RelinquishMagickMemory(map);
1604 if (image->storage_class == PseudoClass)
1612 for (j=0; j < (ssize_t) image->colors; j++)
1614 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1616 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1617 if (black[channel] != white[channel])
1618 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1619 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1622 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1624 PixelChannel channel=GetPixelChannelChannel(image,
1626 if (black[channel] != white[channel])
1627 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1628 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1631 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1633 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1634 if (black[channel] != white[channel])
1635 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1636 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1639 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1641 PixelChannel channel=GetPixelChannelChannel(image,
1643 if (black[channel] != white[channel])
1644 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1645 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1654 image_view=AcquireAuthenticCacheView(image,exception);
1655 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1656 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1657 magick_threads(image,image,image->rows,1)
1659 for (y=0; y < (ssize_t) image->rows; y++)
1667 if( IfMagickFalse(status) )
1669 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1670 if (q == (Quantum *) NULL)
1675 for (x=0; x < (ssize_t) image->columns; x++)
1680 if (GetPixelReadMask(image,q) == 0)
1682 q+=GetPixelChannels(image);
1685 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1687 PixelChannel channel=GetPixelChannelChannel(image,i);
1688 PixelTrait traits=GetPixelChannelTraits(image,channel);
1689 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1691 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1692 ScaleQuantumToMap(q[i])+i]);
1694 q+=GetPixelChannels(image);
1696 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1698 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1703 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1704 #pragma omp critical (MagickCore_EqualizeImage)
1706 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1707 if( IfMagickFalse(proceed) )
1711 image_view=DestroyCacheView(image_view);
1712 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1717 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1721 % G a m m a I m a g e %
1725 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727 % GammaImage() gamma-corrects a particular image channel. The same
1728 % image viewed on different devices will have perceptual differences in the
1729 % way the image's intensities are represented on the screen. Specify
1730 % individual gamma levels for the red, green, and blue channels, or adjust
1731 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1733 % You can also reduce the influence of a particular channel with a gamma
1736 % The format of the GammaImage method is:
1738 % MagickBooleanType GammaImage(Image *image,const double gamma,
1739 % ExceptionInfo *exception)
1741 % A description of each parameter follows:
1743 % o image: the image.
1745 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1747 % o gamma: the image gamma.
1751 static inline double gamma_pow(const double value,const double gamma)
1753 return(value < 0.0 ? value : pow(value,gamma));
1756 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1757 ExceptionInfo *exception)
1759 #define GammaCorrectImageTag "GammaCorrect/Image"
1780 Allocate and initialize gamma maps.
1782 assert(image != (Image *) NULL);
1783 assert(image->signature == MagickSignature);
1784 if( IfMagickTrue(image->debug) )
1785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1788 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1789 if (gamma_map == (Quantum *) NULL)
1790 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1792 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1794 for (i=0; i <= (ssize_t) MaxMap; i++)
1795 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1796 MaxMap,1.0/gamma)));
1797 if (image->storage_class == PseudoClass)
1798 for (i=0; i < (ssize_t) image->colors; i++)
1801 Gamma-correct colormap.
1803 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1804 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1805 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1806 ClampToQuantum(image->colormap[i].red))];
1807 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1808 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1809 ClampToQuantum(image->colormap[i].green))];
1810 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1811 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1812 ClampToQuantum(image->colormap[i].blue))];
1813 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1814 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1815 ClampToQuantum(image->colormap[i].alpha))];
1817 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1818 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1819 image->colormap[i].red,1.0/gamma);
1820 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1821 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1822 image->colormap[i].green,1.0/gamma);
1823 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1824 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1825 image->colormap[i].blue,1.0/gamma);
1826 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1827 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1828 image->colormap[i].alpha,1.0/gamma);
1832 Gamma-correct image.
1836 image_view=AcquireAuthenticCacheView(image,exception);
1837 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1838 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1839 magick_threads(image,image,image->rows,1)
1841 for (y=0; y < (ssize_t) image->rows; y++)
1849 if( IfMagickFalse(status) )
1851 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1852 if (q == (Quantum *) NULL)
1857 for (x=0; x < (ssize_t) image->columns; x++)
1862 if (GetPixelReadMask(image,q) == 0)
1864 q+=GetPixelChannels(image);
1867 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1869 PixelChannel channel=GetPixelChannelChannel(image,i);
1870 PixelTrait traits=GetPixelChannelTraits(image,channel);
1871 if ((traits & UpdatePixelTrait) == 0)
1873 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1874 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1876 q[i]=QuantumRange*gamma_pow(QuantumScale*q[i],1.0/gamma);
1879 q+=GetPixelChannels(image);
1881 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1883 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1888 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1889 #pragma omp critical (MagickCore_GammaImage)
1891 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1893 if( IfMagickFalse(proceed) )
1897 image_view=DestroyCacheView(image_view);
1898 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1899 if (image->gamma != 0.0)
1900 image->gamma*=gamma;
1905 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1909 % G r a y s c a l e I m a g e %
1913 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1915 % GrayscaleImage() converts the image to grayscale.
1917 % The format of the GrayscaleImage method is:
1919 % MagickBooleanType GrayscaleImage(Image *image,
1920 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1922 % A description of each parameter follows:
1924 % o image: the image.
1926 % o method: the pixel intensity method.
1928 % o exception: return any errors or warnings in this structure.
1932 static inline MagickRealType MagickMax(const MagickRealType x,
1933 const MagickRealType y)
1940 static inline MagickRealType MagickMin(const MagickRealType x,
1941 const MagickRealType y)
1948 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1949 const PixelIntensityMethod method,ExceptionInfo *exception)
1951 #define GrayscaleImageTag "Grayscale/Image"
1965 assert(image != (Image *) NULL);
1966 assert(image->signature == MagickSignature);
1967 if( IfMagickTrue(image->debug) )
1968 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1969 if (image->storage_class == PseudoClass)
1971 if( IfMagickFalse(SyncImage(image,exception)) )
1972 return(MagickFalse);
1973 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
1974 return(MagickFalse);
1981 image_view=AcquireAuthenticCacheView(image,exception);
1982 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1983 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1984 magick_threads(image,image,image->rows,1)
1986 for (y=0; y < (ssize_t) image->rows; y++)
1994 if( IfMagickFalse(status) )
1996 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1997 if (q == (Quantum *) NULL)
2002 for (x=0; x < (ssize_t) image->columns; x++)
2010 if (GetPixelReadMask(image,q) == 0)
2012 q+=GetPixelChannels(image);
2015 red=(MagickRealType) GetPixelRed(image,q);
2016 green=(MagickRealType) GetPixelGreen(image,q);
2017 blue=(MagickRealType) GetPixelBlue(image,q);
2021 case AveragePixelIntensityMethod:
2023 intensity=(red+green+blue)/3.0;
2026 case BrightnessPixelIntensityMethod:
2028 intensity=MagickMax(MagickMax(red,green),blue);
2031 case LightnessPixelIntensityMethod:
2033 intensity=(MagickMin(MagickMin(red,green),blue)+
2034 MagickMax(MagickMax(red,green),blue))/2.0;
2037 case MSPixelIntensityMethod:
2039 intensity=(MagickRealType) (((double) red*red+green*green+
2043 case Rec601LumaPixelIntensityMethod:
2045 if (image->colorspace == RGBColorspace)
2047 red=EncodePixelGamma(red);
2048 green=EncodePixelGamma(green);
2049 blue=EncodePixelGamma(blue);
2051 intensity=0.298839*red+0.586811*green+0.114350*blue;
2054 case Rec601LuminancePixelIntensityMethod:
2056 if (image->colorspace == sRGBColorspace)
2058 red=DecodePixelGamma(red);
2059 green=DecodePixelGamma(green);
2060 blue=DecodePixelGamma(blue);
2062 intensity=0.298839*red+0.586811*green+0.114350*blue;
2065 case Rec709LumaPixelIntensityMethod:
2068 if (image->colorspace == RGBColorspace)
2070 red=EncodePixelGamma(red);
2071 green=EncodePixelGamma(green);
2072 blue=EncodePixelGamma(blue);
2074 intensity=0.212656*red+0.715158*green+0.072186*blue;
2077 case Rec709LuminancePixelIntensityMethod:
2079 if (image->colorspace == sRGBColorspace)
2081 red=DecodePixelGamma(red);
2082 green=DecodePixelGamma(green);
2083 blue=DecodePixelGamma(blue);
2085 intensity=0.212656*red+0.715158*green+0.072186*blue;
2088 case RMSPixelIntensityMethod:
2090 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2091 blue*blue)/sqrt(3.0));
2095 SetPixelGray(image,ClampToQuantum(intensity),q);
2096 q+=GetPixelChannels(image);
2098 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
2100 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2105 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2106 #pragma omp critical (MagickCore_GrayscaleImage)
2108 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2110 if( IfMagickFalse(proceed) )
2114 image_view=DestroyCacheView(image_view);
2115 image->intensity=method;
2116 image->type=GrayscaleType;
2117 return(SetImageColorspace(image,GRAYColorspace,exception));
2121 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2125 % H a l d C l u t I m a g e %
2129 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2131 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2132 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2133 % Create it with the HALD coder. You can apply any color transformation to
2134 % the Hald image and then use this method to apply the transform to the
2137 % The format of the HaldClutImage method is:
2139 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2140 % ExceptionInfo *exception)
2142 % A description of each parameter follows:
2144 % o image: the image, which is replaced by indexed CLUT values
2146 % o hald_image: the color lookup table image for replacement color values.
2148 % o exception: return any errors or warnings in this structure.
2151 MagickExport MagickBooleanType HaldClutImage(Image *image,
2152 const Image *hald_image,ExceptionInfo *exception)
2154 #define HaldClutImageTag "Clut/Image"
2156 typedef struct _HaldInfo
2188 assert(image != (Image *) NULL);
2189 assert(image->signature == MagickSignature);
2190 if( IfMagickTrue(image->debug) )
2191 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2192 assert(hald_image != (Image *) NULL);
2193 assert(hald_image->signature == MagickSignature);
2194 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
2195 return(MagickFalse);
2196 if (image->alpha_trait != BlendPixelTrait)
2197 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2203 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2204 (MagickRealType) hald_image->rows);
2205 for (level=2; (level*level*level) < length; level++) ;
2207 cube_size=level*level;
2208 width=(double) hald_image->columns;
2209 GetPixelInfo(hald_image,&zero);
2210 hald_view=AcquireVirtualCacheView(hald_image,exception);
2211 image_view=AcquireAuthenticCacheView(image,exception);
2212 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2213 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2214 magick_threads(image,image,image->rows,1)
2216 for (y=0; y < (ssize_t) image->rows; y++)
2224 if( IfMagickFalse(status) )
2226 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2227 if (q == (Quantum *) NULL)
2232 for (x=0; x < (ssize_t) image->columns; x++)
2247 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2248 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2249 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2250 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2251 point.x-=floor(point.x);
2252 point.y-=floor(point.y);
2253 point.z-=floor(point.z);
2255 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2256 fmod(offset,width),floor(offset/width),&pixel1,exception);
2258 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2259 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2261 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2264 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2265 fmod(offset,width),floor(offset/width),&pixel1,exception);
2266 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2267 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2269 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2272 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2274 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2275 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2276 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2277 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2278 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2279 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2280 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2281 (image->colorspace == CMYKColorspace))
2282 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2283 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2284 (image->alpha_trait == BlendPixelTrait))
2285 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2286 q+=GetPixelChannels(image);
2288 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2290 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2295 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2296 #pragma omp critical (MagickCore_HaldClutImage)
2298 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2299 if( IfMagickFalse(proceed) )
2303 hald_view=DestroyCacheView(hald_view);
2304 image_view=DestroyCacheView(image_view);
2309 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2313 % L e v e l I m a g e %
2317 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2319 % LevelImage() adjusts the levels of a particular image channel by
2320 % scaling the colors falling between specified white and black points to
2321 % the full available quantum range.
2323 % The parameters provided represent the black, and white points. The black
2324 % point specifies the darkest color in the image. Colors darker than the
2325 % black point are set to zero. White point specifies the lightest color in
2326 % the image. Colors brighter than the white point are set to the maximum
2329 % If a '!' flag is given, map black and white colors to the given levels
2330 % rather than mapping those levels to black and white. See
2331 % LevelizeImage() below.
2333 % Gamma specifies a gamma correction to apply to the image.
2335 % The format of the LevelImage method is:
2337 % MagickBooleanType LevelImage(Image *image,const double black_point,
2338 % const double white_point,const double gamma,ExceptionInfo *exception)
2340 % A description of each parameter follows:
2342 % o image: the image.
2344 % o black_point: The level to map zero (black) to.
2346 % o white_point: The level to map QuantumRange (white) to.
2348 % o exception: return any errors or warnings in this structure.
2352 static inline double LevelPixel(const double black_point,
2353 const double white_point,const double gamma,const double pixel)
2359 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2360 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2362 return(level_pixel);
2365 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2366 const double white_point,const double gamma,ExceptionInfo *exception)
2368 #define LevelImageTag "Level/Image"
2386 Allocate and initialize levels map.
2388 assert(image != (Image *) NULL);
2389 assert(image->signature == MagickSignature);
2390 if( IfMagickTrue(image->debug) )
2391 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2392 if (image->storage_class == PseudoClass)
2393 for (i=0; i < (ssize_t) image->colors; i++)
2398 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2399 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2400 white_point,gamma,image->colormap[i].red));
2401 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2402 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2403 white_point,gamma,image->colormap[i].green));
2404 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2405 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2406 white_point,gamma,image->colormap[i].blue));
2407 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2408 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2409 white_point,gamma,image->colormap[i].alpha));
2416 image_view=AcquireAuthenticCacheView(image,exception);
2417 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2418 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2419 magick_threads(image,image,image->rows,1)
2421 for (y=0; y < (ssize_t) image->rows; y++)
2429 if( IfMagickFalse(status) )
2431 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2432 if (q == (Quantum *) NULL)
2437 for (x=0; x < (ssize_t) image->columns; x++)
2442 if (GetPixelReadMask(image,q) == 0)
2444 q+=GetPixelChannels(image);
2447 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2449 PixelChannel channel=GetPixelChannelChannel(image,i);
2450 PixelTrait traits=GetPixelChannelTraits(image,channel);
2451 if ((traits & UpdatePixelTrait) == 0)
2453 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2456 q+=GetPixelChannels(image);
2458 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2460 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2465 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2466 #pragma omp critical (MagickCore_LevelImage)
2468 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2469 if( IfMagickFalse(proceed) )
2473 image_view=DestroyCacheView(image_view);
2474 (void) ClampImage(image,exception);
2479 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2483 % L e v e l i z e I m a g e %
2487 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2489 % LevelizeImage() applies the reversed LevelImage() operation to just
2490 % the specific channels specified. It compresses the full range of color
2491 % values, so that they lie between the given black and white points. Gamma is
2492 % applied before the values are mapped.
2494 % LevelizeImage() can be called with by using a +level command line
2495 % API option, or using a '!' on a -level or LevelImage() geometry string.
2497 % It can be used to de-contrast a greyscale image to the exact levels
2498 % specified. Or by using specific levels for each channel of an image you
2499 % can convert a gray-scale image to any linear color gradient, according to
2502 % The format of the LevelizeImage method is:
2504 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2505 % const double white_point,const double gamma,ExceptionInfo *exception)
2507 % A description of each parameter follows:
2509 % o image: the image.
2511 % o black_point: The level to map zero (black) to.
2513 % o white_point: The level to map QuantumRange (white) to.
2515 % o gamma: adjust gamma by this factor before mapping values.
2517 % o exception: return any errors or warnings in this structure.
2520 MagickExport MagickBooleanType LevelizeImage(Image *image,
2521 const double black_point,const double white_point,const double gamma,
2522 ExceptionInfo *exception)
2524 #define LevelizeImageTag "Levelize/Image"
2525 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2526 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2544 Allocate and initialize levels map.
2546 assert(image != (Image *) NULL);
2547 assert(image->signature == MagickSignature);
2548 if( IfMagickTrue(image->debug) )
2549 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2550 if (image->storage_class == PseudoClass)
2551 for (i=0; i < (ssize_t) image->colors; i++)
2556 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2557 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2558 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2559 image->colormap[i].green=(double) LevelizeValue(
2560 image->colormap[i].green);
2561 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2562 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2563 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2564 image->colormap[i].alpha=(double) LevelizeValue(
2565 image->colormap[i].alpha);
2572 image_view=AcquireAuthenticCacheView(image,exception);
2573 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2574 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2575 magick_threads(image,image,image->rows,1)
2577 for (y=0; y < (ssize_t) image->rows; y++)
2585 if( IfMagickFalse(status) )
2587 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2588 if (q == (Quantum *) NULL)
2593 for (x=0; x < (ssize_t) image->columns; x++)
2598 if (GetPixelReadMask(image,q) == 0)
2600 q+=GetPixelChannels(image);
2603 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2605 PixelChannel channel=GetPixelChannelChannel(image,i);
2606 PixelTrait traits=GetPixelChannelTraits(image,channel);
2607 if ((traits & UpdatePixelTrait) == 0)
2609 q[i]=LevelizeValue(q[i]);
2611 q+=GetPixelChannels(image);
2613 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2615 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2620 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2621 #pragma omp critical (MagickCore_LevelizeImage)
2623 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2624 if( IfMagickFalse(proceed) )
2628 image_view=DestroyCacheView(image_view);
2633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2637 % L e v e l I m a g e C o l o r s %
2641 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2643 % LevelImageColors() maps the given color to "black" and "white" values,
2644 % linearly spreading out the colors, and level values on a channel by channel
2645 % bases, as per LevelImage(). The given colors allows you to specify
2646 % different level ranges for each of the color channels separately.
2648 % If the boolean 'invert' is set true the image values will modifyed in the
2649 % reverse direction. That is any existing "black" and "white" colors in the
2650 % image will become the color values given, with all other values compressed
2651 % appropriatally. This effectivally maps a greyscale gradient into the given
2654 % The format of the LevelImageColors method is:
2656 % MagickBooleanType LevelImageColors(Image *image,
2657 % const PixelInfo *black_color,const PixelInfo *white_color,
2658 % const MagickBooleanType invert,ExceptionInfo *exception)
2660 % A description of each parameter follows:
2662 % o image: the image.
2664 % o black_color: The color to map black to/from
2666 % o white_point: The color to map white to/from
2668 % o invert: if true map the colors (levelize), rather than from (level)
2670 % o exception: return any errors or warnings in this structure.
2673 MagickExport MagickBooleanType LevelImageColors(Image *image,
2674 const PixelInfo *black_color,const PixelInfo *white_color,
2675 const MagickBooleanType invert,ExceptionInfo *exception)
2684 Allocate and initialize levels map.
2686 assert(image != (Image *) NULL);
2687 assert(image->signature == MagickSignature);
2688 if( IfMagickTrue(image->debug) )
2689 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2690 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2691 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2692 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
2693 (void) SetImageColorspace(image,sRGBColorspace,exception);
2695 if( IfMagickFalse(invert) )
2697 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2699 channel_mask=SetImageChannelMask(image,RedChannel);
2700 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2702 (void) SetImageChannelMask(image,channel_mask);
2704 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2706 channel_mask=SetImageChannelMask(image,GreenChannel);
2707 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2709 (void) SetImageChannelMask(image,channel_mask);
2711 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2713 channel_mask=SetImageChannelMask(image,BlueChannel);
2714 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2716 (void) SetImageChannelMask(image,channel_mask);
2718 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2719 (image->colorspace == CMYKColorspace))
2721 channel_mask=SetImageChannelMask(image,BlackChannel);
2722 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2724 (void) SetImageChannelMask(image,channel_mask);
2726 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2727 (image->alpha_trait == BlendPixelTrait))
2729 channel_mask=SetImageChannelMask(image,AlphaChannel);
2730 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2732 (void) SetImageChannelMask(image,channel_mask);
2737 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2739 channel_mask=SetImageChannelMask(image,RedChannel);
2740 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2742 (void) SetImageChannelMask(image,channel_mask);
2744 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2746 channel_mask=SetImageChannelMask(image,GreenChannel);
2747 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2749 (void) SetImageChannelMask(image,channel_mask);
2751 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2753 channel_mask=SetImageChannelMask(image,BlueChannel);
2754 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2756 (void) SetImageChannelMask(image,channel_mask);
2758 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2759 (image->colorspace == CMYKColorspace))
2761 channel_mask=SetImageChannelMask(image,BlackChannel);
2762 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2764 (void) SetImageChannelMask(image,channel_mask);
2766 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2767 (image->alpha_trait == BlendPixelTrait))
2769 channel_mask=SetImageChannelMask(image,AlphaChannel);
2770 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2772 (void) SetImageChannelMask(image,channel_mask);
2775 return(status != 0 ? MagickTrue : MagickFalse);
2779 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2783 % L i n e a r S t r e t c h I m a g e %
2787 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2789 % LinearStretchImage() discards any pixels below the black point and above
2790 % the white point and levels the remaining pixels.
2792 % The format of the LinearStretchImage method is:
2794 % MagickBooleanType LinearStretchImage(Image *image,
2795 % const double black_point,const double white_point,
2796 % ExceptionInfo *exception)
2798 % A description of each parameter follows:
2800 % o image: the image.
2802 % o black_point: the black point.
2804 % o white_point: the white point.
2806 % o exception: return any errors or warnings in this structure.
2809 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2810 const double black_point,const double white_point,ExceptionInfo *exception)
2812 #define LinearStretchImageTag "LinearStretch/Image"
2830 Allocate histogram and linear map.
2832 assert(image != (Image *) NULL);
2833 assert(image->signature == MagickSignature);
2834 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2835 if (histogram == (double *) NULL)
2836 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2841 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2842 image_view=AcquireVirtualCacheView(image,exception);
2843 for (y=0; y < (ssize_t) image->rows; y++)
2845 register const Quantum
2851 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2852 if (p == (const Quantum *) NULL)
2854 for (x=0; x < (ssize_t) image->columns; x++)
2859 intensity=GetPixelIntensity(image,p);
2860 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2861 p+=GetPixelChannels(image);
2864 image_view=DestroyCacheView(image_view);
2866 Find the histogram boundaries by locating the black and white point levels.
2869 for (black=0; black < (ssize_t) MaxMap; black++)
2871 intensity+=histogram[black];
2872 if (intensity >= black_point)
2876 for (white=(ssize_t) MaxMap; white != 0; white--)
2878 intensity+=histogram[white];
2879 if (intensity >= white_point)
2882 histogram=(double *) RelinquishMagickMemory(histogram);
2883 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2888 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2892 % M o d u l a t e I m a g e %
2896 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2898 % ModulateImage() lets you control the brightness, saturation, and hue
2899 % of an image. Modulate represents the brightness, saturation, and hue
2900 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2901 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2902 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2904 % The format of the ModulateImage method is:
2906 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2907 % ExceptionInfo *exception)
2909 % A description of each parameter follows:
2911 % o image: the image.
2913 % o modulate: Define the percent change in brightness, saturation, and hue.
2915 % o exception: return any errors or warnings in this structure.
2919 static inline void ModulateHCL(const double percent_hue,
2920 const double percent_chroma,const double percent_luma,double *red,
2921 double *green,double *blue)
2929 Increase or decrease color luma, chroma, or hue.
2931 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2932 hue+=0.5*(0.01*percent_hue-1.0);
2937 chroma*=0.01*percent_chroma;
2938 luma*=0.01*percent_luma;
2939 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2942 static inline void ModulateHCLp(const double percent_hue,
2943 const double percent_chroma,const double percent_luma,double *red,
2944 double *green,double *blue)
2952 Increase or decrease color luma, chroma, or hue.
2954 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2955 hue+=0.5*(0.01*percent_hue-1.0);
2960 chroma*=0.01*percent_chroma;
2961 luma*=0.01*percent_luma;
2962 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2965 static inline void ModulateHSB(const double percent_hue,
2966 const double percent_saturation,const double percent_brightness,double *red,
2967 double *green,double *blue)
2975 Increase or decrease color brightness, saturation, or hue.
2977 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2978 hue+=0.5*(0.01*percent_hue-1.0);
2983 saturation*=0.01*percent_saturation;
2984 brightness*=0.01*percent_brightness;
2985 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2988 static inline void ModulateHSI(const double percent_hue,
2989 const double percent_saturation,const double percent_intensity,double *red,
2990 double *green,double *blue)
2998 Increase or decrease color intensity, saturation, or hue.
3000 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3001 hue+=0.5*(0.01*percent_hue-1.0);
3006 saturation*=0.01*percent_saturation;
3007 intensity*=0.01*percent_intensity;
3008 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3011 static inline void ModulateHSL(const double percent_hue,
3012 const double percent_saturation,const double percent_lightness,double *red,
3013 double *green,double *blue)
3021 Increase or decrease color lightness, saturation, or hue.
3023 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3024 hue+=0.5*(0.01*percent_hue-1.0);
3029 saturation*=0.01*percent_saturation;
3030 lightness*=0.01*percent_lightness;
3031 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3034 static inline void ModulateHSV(const double percent_hue,
3035 const double percent_saturation,const double percent_value,double *red,
3036 double *green,double *blue)
3044 Increase or decrease color value, saturation, or hue.
3046 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3047 hue+=0.5*(0.01*percent_hue-1.0);
3052 saturation*=0.01*percent_saturation;
3053 value*=0.01*percent_value;
3054 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3057 static inline void ModulateHWB(const double percent_hue,
3058 const double percent_whiteness,const double percent_blackness,double *red,
3059 double *green,double *blue)
3067 Increase or decrease color blackness, whiteness, or hue.
3069 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3070 hue+=0.5*(0.01*percent_hue-1.0);
3075 blackness*=0.01*percent_blackness;
3076 whiteness*=0.01*percent_whiteness;
3077 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3080 static inline void ModulateLCHab(const double percent_luma,
3081 const double percent_chroma,const double percent_hue,double *red,
3082 double *green,double *blue)
3090 Increase or decrease color luma, chroma, or hue.
3092 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3093 luma*=0.01*percent_luma;
3094 chroma*=0.01*percent_chroma;
3095 hue+=0.5*(0.01*percent_hue-1.0);
3100 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3103 static inline void ModulateLCHuv(const double percent_luma,
3104 const double percent_chroma,const double percent_hue,double *red,
3105 double *green,double *blue)
3113 Increase or decrease color luma, chroma, or hue.
3115 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3116 luma*=0.01*percent_luma;
3117 chroma*=0.01*percent_chroma;
3118 hue+=0.5*(0.01*percent_hue-1.0);
3123 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3126 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3127 ExceptionInfo *exception)
3129 #define ModulateImageTag "Modulate/Image"
3164 Initialize modulate table.
3166 assert(image != (Image *) NULL);
3167 assert(image->signature == MagickSignature);
3168 if( IfMagickTrue(image->debug) )
3169 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3170 if (modulate == (char *) NULL)
3171 return(MagickFalse);
3172 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
3173 (void) SetImageColorspace(image,sRGBColorspace,exception);
3174 flags=ParseGeometry(modulate,&geometry_info);
3175 percent_brightness=geometry_info.rho;
3176 percent_saturation=geometry_info.sigma;
3177 if ((flags & SigmaValue) == 0)
3178 percent_saturation=100.0;
3179 percent_hue=geometry_info.xi;
3180 if ((flags & XiValue) == 0)
3182 colorspace=UndefinedColorspace;
3183 artifact=GetImageArtifact(image,"modulate:colorspace");
3184 if (artifact != (const char *) NULL)
3185 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3186 MagickFalse,artifact);
3187 if (image->storage_class == PseudoClass)
3188 for (i=0; i < (ssize_t) image->colors; i++)
3196 Modulate image colormap.
3198 red=(double) image->colormap[i].red;
3199 green=(double) image->colormap[i].green;
3200 blue=(double) image->colormap[i].blue;
3205 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3209 case HCLpColorspace:
3211 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3217 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3223 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3230 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3236 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3242 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3247 case LCHabColorspace:
3249 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3253 case LCHuvColorspace:
3255 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3260 image->colormap[i].red=red;
3261 image->colormap[i].green=green;
3262 image->colormap[i].blue=blue;
3269 image_view=AcquireAuthenticCacheView(image,exception);
3270 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3271 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3272 magick_threads(image,image,image->rows,1)
3274 for (y=0; y < (ssize_t) image->rows; y++)
3282 if( IfMagickFalse(status) )
3284 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3285 if (q == (Quantum *) NULL)
3290 for (x=0; x < (ssize_t) image->columns; x++)
3297 red=(double) GetPixelRed(image,q);
3298 green=(double) GetPixelGreen(image,q);
3299 blue=(double) GetPixelBlue(image,q);
3304 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3308 case HCLpColorspace:
3310 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3316 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3323 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3329 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3335 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3339 case LCHabColorspace:
3341 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3346 case LCHuvColorspace:
3348 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3353 SetPixelRed(image,ClampToQuantum(red),q);
3354 SetPixelGreen(image,ClampToQuantum(green),q);
3355 SetPixelBlue(image,ClampToQuantum(blue),q);
3356 q+=GetPixelChannels(image);
3358 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3360 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3365 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3366 #pragma omp critical (MagickCore_ModulateImage)
3368 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3369 if( IfMagickFalse(proceed) )
3373 image_view=DestroyCacheView(image_view);
3378 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3382 % N e g a t e I m a g e %
3386 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3388 % NegateImage() negates the colors in the reference image. The grayscale
3389 % option means that only grayscale values within the image are negated.
3391 % The format of the NegateImage method is:
3393 % MagickBooleanType NegateImage(Image *image,
3394 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3396 % A description of each parameter follows:
3398 % o image: the image.
3400 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3402 % o exception: return any errors or warnings in this structure.
3405 MagickExport MagickBooleanType NegateImage(Image *image,
3406 const MagickBooleanType grayscale,ExceptionInfo *exception)
3408 #define NegateImageTag "Negate/Image"
3425 assert(image != (Image *) NULL);
3426 assert(image->signature == MagickSignature);
3427 if( IfMagickTrue(image->debug) )
3428 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3429 if (image->storage_class == PseudoClass)
3430 for (i=0; i < (ssize_t) image->colors; i++)
3435 if( IfMagickTrue(grayscale) )
3436 if ((image->colormap[i].red != image->colormap[i].green) ||
3437 (image->colormap[i].green != image->colormap[i].blue))
3439 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3440 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3441 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3442 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3443 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3444 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3451 image_view=AcquireAuthenticCacheView(image,exception);
3452 if( IfMagickTrue(grayscale) )
3454 for (y=0; y < (ssize_t) image->rows; y++)
3465 if( IfMagickFalse(status) )
3467 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3469 if (q == (Quantum *) NULL)
3474 for (x=0; x < (ssize_t) image->columns; x++)
3479 if ((GetPixelReadMask(image,q) == 0) ||
3480 IfMagickTrue(IsPixelGray(image,q)))
3482 q+=GetPixelChannels(image);
3485 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3487 PixelChannel channel=GetPixelChannelChannel(image,i);
3488 PixelTrait traits=GetPixelChannelTraits(image,channel);
3489 if ((traits & UpdatePixelTrait) == 0)
3491 q[i]=QuantumRange-q[i];
3493 q+=GetPixelChannels(image);
3495 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3496 if( IfMagickFalse(sync) )
3498 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3503 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3504 #pragma omp critical (MagickCore_NegateImage)
3506 proceed=SetImageProgress(image,NegateImageTag,progress++,
3508 if( IfMagickFalse(proceed) )
3512 image_view=DestroyCacheView(image_view);
3518 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3519 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3520 magick_threads(image,image,image->rows,1)
3522 for (y=0; y < (ssize_t) image->rows; y++)
3530 if( IfMagickFalse(status) )
3532 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3533 if (q == (Quantum *) NULL)
3538 for (x=0; x < (ssize_t) image->columns; x++)
3543 if (GetPixelReadMask(image,q) == 0)
3545 q+=GetPixelChannels(image);
3548 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3550 PixelChannel channel=GetPixelChannelChannel(image,i);
3551 PixelTrait traits=GetPixelChannelTraits(image,channel);
3552 if ((traits & UpdatePixelTrait) == 0)
3554 q[i]=QuantumRange-q[i];
3556 q+=GetPixelChannels(image);
3558 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3560 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3565 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3566 #pragma omp critical (MagickCore_NegateImage)
3568 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3569 if( IfMagickFalse(proceed) )
3573 image_view=DestroyCacheView(image_view);
3578 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3582 % N o r m a l i z e I m a g e %
3586 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3588 % The NormalizeImage() method enhances the contrast of a color image by
3589 % mapping the darkest 2 percent of all pixel to black and the brightest
3590 % 1 percent to white.
3592 % The format of the NormalizeImage method is:
3594 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3596 % A description of each parameter follows:
3598 % o image: the image.
3600 % o exception: return any errors or warnings in this structure.
3603 MagickExport MagickBooleanType NormalizeImage(Image *image,
3604 ExceptionInfo *exception)
3610 black_point=(double) image->columns*image->rows*0.0015;
3611 white_point=(double) image->columns*image->rows*0.9995;
3612 return(ContrastStretchImage(image,black_point,white_point,exception));
3616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3620 % S i g m o i d a l C o n t r a s t I m a g e %
3624 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3626 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3627 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3628 % sigmoidal transfer function without saturating highlights or shadows.
3629 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3630 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3631 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3632 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3635 % The format of the SigmoidalContrastImage method is:
3637 % MagickBooleanType SigmoidalContrastImage(Image *image,
3638 % const MagickBooleanType sharpen,const char *levels,
3639 % ExceptionInfo *exception)
3641 % A description of each parameter follows:
3643 % o image: the image.
3645 % o sharpen: Increase or decrease image contrast.
3647 % o contrast: strength of the contrast, the larger the number the more
3648 % 'threshold-like' it becomes.
3650 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3652 % o exception: return any errors or warnings in this structure.
3657 ImageMagick 6 has a version of this function which uses LUTs.
3661 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3664 The first version, based on the hyperbolic tangent tanh, when combined with
3665 the scaling step, is an exact arithmetic clone of the the sigmoid function
3666 based on the logistic curve. The equivalence is based on the identity
3668 1/(1+exp(-t)) = (1+tanh(t/2))/2
3670 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3671 scaled sigmoidal derivation is invariant under affine transformations of
3674 The tanh version is almost certainly more accurate and cheaper. The 0.5
3675 factor in the argument is to clone the legacy ImageMagick behavior. The
3676 reason for making the define depend on atanh even though it only uses tanh
3677 has to do with the construction of the inverse of the scaled sigmoidal.
3679 #if defined(MAGICKCORE_HAVE_ATANH)
3680 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3682 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3685 Scaled sigmoidal function:
3687 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3688 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3690 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3691 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3692 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3693 zero. This is fixed below by exiting immediately when contrast is small,
3694 leaving the image (or colormap) unmodified. This appears to be safe because
3695 the series expansion of the logistic sigmoidal function around x=b is
3699 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3701 #define ScaledSigmoidal(a,b,x) ( \
3702 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3703 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3705 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3706 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3707 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3708 when creating a LUT from in gamut values, hence the branching. In
3709 addition, HDRI may have out of gamut values.
3710 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3711 It is only a right inverse. This is unavoidable.
3713 static inline double InverseScaledSigmoidal(const double a,const double b,
3716 const double sig0=Sigmoidal(a,b,0.0);
3717 const double sig1=Sigmoidal(a,b,1.0);
3718 const double argument=(sig1-sig0)*x+sig0;
3719 const double clamped=
3721 #if defined(MAGICKCORE_HAVE_ATANH)
3722 argument < -1+MagickEpsilon
3726 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3728 return(b+(2.0/a)*atanh(clamped));
3730 argument < MagickEpsilon
3734 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3736 return(b-log(1.0/clamped-1.0)/a);
3740 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3741 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3742 ExceptionInfo *exception)
3744 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3745 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3746 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3747 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3748 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3765 assert(image != (Image *) NULL);
3766 assert(image->signature == MagickSignature);
3767 if( IfMagickTrue(image->debug) )
3768 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3770 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3771 case nothing is done.
3773 if (contrast < MagickEpsilon)
3776 Sigmoidal-contrast enhance colormap.
3778 if (image->storage_class == PseudoClass)
3783 if( IfMagickTrue(sharpen) )
3784 for (i=0; i < (ssize_t) image->colors; i++)
3786 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3787 image->colormap[i].red=(MagickRealType) ScaledSig(
3788 image->colormap[i].red);
3789 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3790 image->colormap[i].green=(MagickRealType) ScaledSig(
3791 image->colormap[i].green);
3792 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3793 image->colormap[i].blue=(MagickRealType) ScaledSig(
3794 image->colormap[i].blue);
3795 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3796 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3797 image->colormap[i].alpha);
3800 for (i=0; i < (ssize_t) image->colors; i++)
3802 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3803 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3804 image->colormap[i].red);
3805 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3806 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3807 image->colormap[i].green);
3808 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3809 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3810 image->colormap[i].blue);
3811 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3812 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3813 image->colormap[i].alpha);
3817 Sigmoidal-contrast enhance image.
3821 image_view=AcquireAuthenticCacheView(image,exception);
3822 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3823 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3824 magick_threads(image,image,image->rows,1)
3826 for (y=0; y < (ssize_t) image->rows; y++)
3834 if( IfMagickFalse(status) )
3836 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3837 if (q == (Quantum *) NULL)
3842 for (x=0; x < (ssize_t) image->columns; x++)
3847 if (GetPixelReadMask(image,q) == 0)
3849 q+=GetPixelChannels(image);
3852 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3854 PixelChannel channel=GetPixelChannelChannel(image,i);
3855 PixelTrait traits=GetPixelChannelTraits(image,channel);
3856 if ((traits & UpdatePixelTrait) == 0)
3858 if( IfMagickTrue(sharpen) )
3859 q[i]=ScaledSig(q[i]);
3861 q[i]=InverseScaledSig(q[i]);
3863 q+=GetPixelChannels(image);
3865 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3867 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3872 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3873 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3875 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3877 if( IfMagickFalse(proceed) )
3881 image_view=DestroyCacheView(image_view);