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-2016 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/channel.h"
49 #include "MagickCore/color.h"
50 #include "MagickCore/color-private.h"
51 #include "MagickCore/colorspace.h"
52 #include "MagickCore/colorspace-private.h"
53 #include "MagickCore/composite-private.h"
54 #include "MagickCore/enhance.h"
55 #include "MagickCore/exception.h"
56 #include "MagickCore/exception-private.h"
57 #include "MagickCore/fx.h"
58 #include "MagickCore/gem.h"
59 #include "MagickCore/gem-private.h"
60 #include "MagickCore/geometry.h"
61 #include "MagickCore/histogram.h"
62 #include "MagickCore/image.h"
63 #include "MagickCore/image-private.h"
64 #include "MagickCore/memory_.h"
65 #include "MagickCore/monitor.h"
66 #include "MagickCore/monitor-private.h"
67 #include "MagickCore/option.h"
68 #include "MagickCore/pixel.h"
69 #include "MagickCore/pixel-accessor.h"
70 #include "MagickCore/quantum.h"
71 #include "MagickCore/quantum-private.h"
72 #include "MagickCore/resample.h"
73 #include "MagickCore/resample-private.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/statistic.h"
76 #include "MagickCore/string_.h"
77 #include "MagickCore/string-private.h"
78 #include "MagickCore/thread-private.h"
79 #include "MagickCore/threshold.h"
80 #include "MagickCore/token.h"
81 #include "MagickCore/xml-tree.h"
82 #include "MagickCore/xml-tree-private.h"
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89 % A u t o G a m m a I m a g e %
93 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
95 % AutoGammaImage() extract the 'mean' from the image and adjust the image
96 % to try make set its gamma appropriatally.
98 % The format of the AutoGammaImage method is:
100 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
102 % A description of each parameter follows:
104 % o image: The image to auto-level
106 % o exception: return any errors or warnings in this structure.
109 MagickExport MagickBooleanType AutoGammaImage(Image *image,
110 ExceptionInfo *exception)
125 if (image->channel_mask == DefaultChannels)
128 Apply gamma correction equally across all given channels.
130 (void) GetImageMean(image,&mean,&sans,exception);
131 gamma=log(mean*QuantumScale)/log_mean;
132 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
135 Auto-gamma each channel separately.
138 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
143 PixelChannel channel=GetPixelChannelChannel(image,i);
144 PixelTrait traits=GetPixelChannelTraits(image,channel);
145 if ((traits & UpdatePixelTrait) == 0)
147 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
148 status=GetImageMean(image,&mean,&sans,exception);
149 gamma=log(mean*QuantumScale)/log_mean;
150 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
151 (void) SetImageChannelMask(image,channel_mask);
152 if (status == MagickFalse)
155 return(status != 0 ? MagickTrue : MagickFalse);
159 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
163 % A u t o L e v e l I m a g e %
167 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
169 % AutoLevelImage() adjusts the levels of a particular image channel by
170 % scaling the minimum and maximum values to the full quantum range.
172 % The format of the LevelImage method is:
174 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
176 % A description of each parameter follows:
178 % o image: The image to auto-level
180 % o exception: return any errors or warnings in this structure.
183 MagickExport MagickBooleanType AutoLevelImage(Image *image,
184 ExceptionInfo *exception)
186 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194 % B r i g h t n e s s C o n t r a s t I m a g e %
198 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200 % BrightnessContrastImage() changes the brightness and/or contrast of an
201 % image. It converts the brightness and contrast parameters into slope and
202 % intercept and calls a polynomical function to apply to the image.
204 % The format of the BrightnessContrastImage method is:
206 % MagickBooleanType BrightnessContrastImage(Image *image,
207 % const double brightness,const double contrast,ExceptionInfo *exception)
209 % A description of each parameter follows:
211 % o image: the image.
213 % o brightness: the brightness percent (-100 .. 100).
215 % o contrast: the contrast percent (-100 .. 100).
217 % o exception: return any errors or warnings in this structure.
220 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
221 const double brightness,const double contrast,ExceptionInfo *exception)
223 #define BrightnessContastImageTag "BrightnessContast/Image"
235 Compute slope and intercept.
237 assert(image != (Image *) NULL);
238 assert(image->signature == MagickCoreSignature);
239 if (image->debug != MagickFalse)
240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
242 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
245 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
246 coefficients[0]=slope;
247 coefficients[1]=intercept;
248 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
253 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
257 % C l u t I m a g e %
261 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 % ClutImage() replaces each color value in the given image, by using it as an
264 % index to lookup a replacement color value in a Color Look UP Table in the
265 % form of an image. The values are extracted along a diagonal of the CLUT
266 % image so either a horizontal or vertial gradient image can be used.
268 % Typically this is used to either re-color a gray-scale image according to a
269 % color gradient in the CLUT image, or to perform a freeform histogram
270 % (level) adjustment according to the (typically gray-scale) gradient in the
273 % When the 'channel' mask includes the matte/alpha transparency channel but
274 % one image has no such channel it is assumed that that image is a simple
275 % gray-scale image that will effect the alpha channel values, either for
276 % gray-scale coloring (with transparent or semi-transparent colors), or
277 % a histogram adjustment of existing alpha channel values. If both images
278 % have matte channels, direct and normal indexing is applied, which is rarely
281 % The format of the ClutImage method is:
283 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
284 % const PixelInterpolateMethod method,ExceptionInfo *exception)
286 % A description of each parameter follows:
288 % o image: the image, which is replaced by indexed CLUT values
290 % o clut_image: the color lookup table image for replacement color values.
292 % o method: the pixel interpolation method.
294 % o exception: return any errors or warnings in this structure.
297 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
298 const PixelInterpolateMethod method,ExceptionInfo *exception)
300 #define ClutImageTag "Clut/Image"
321 assert(image != (Image *) NULL);
322 assert(image->signature == MagickCoreSignature);
323 if (image->debug != MagickFalse)
324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
325 assert(clut_image != (Image *) NULL);
326 assert(clut_image->signature == MagickCoreSignature);
327 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
329 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
330 IfMagickFalse(IsGrayColorspace(clut_image->colorspace)))
331 (void) SetImageColorspace(image,sRGBColorspace,exception);
332 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
333 if (clut_map == (PixelInfo *) NULL)
334 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
341 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
342 clut_view=AcquireVirtualCacheView(clut_image,exception);
343 for (i=0; i <= (ssize_t) MaxMap; i++)
345 GetPixelInfo(clut_image,clut_map+i);
346 (void) InterpolatePixelInfo(clut_image,clut_view,method,
347 (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
348 (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
350 clut_view=DestroyCacheView(clut_view);
351 image_view=AcquireAuthenticCacheView(image,exception);
352 #if defined(MAGICKCORE_OPENMP_SUPPORT)
353 #pragma omp parallel for schedule(static,4) shared(progress,status) \
354 magick_threads(image,image,image->rows,1)
356 for (y=0; y < (ssize_t) image->rows; y++)
367 if (status == MagickFalse)
369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
370 if (q == (Quantum *) NULL)
375 GetPixelInfo(image,&pixel);
376 for (x=0; x < (ssize_t) image->columns; x++)
381 if (GetPixelReadMask(image,q) == 0)
383 q+=GetPixelChannels(image);
386 GetPixelInfoPixel(image,q,&pixel);
387 traits=GetPixelChannelTraits(image,RedPixelChannel);
388 if ((traits & UpdatePixelTrait) != 0)
389 pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
391 traits=GetPixelChannelTraits(image,GreenPixelChannel);
392 if ((traits & UpdatePixelTrait) != 0)
393 pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
394 pixel.green))].green;
395 traits=GetPixelChannelTraits(image,BluePixelChannel);
396 if ((traits & UpdatePixelTrait) != 0)
397 pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
399 traits=GetPixelChannelTraits(image,BlackPixelChannel);
400 if ((traits & UpdatePixelTrait) != 0)
401 pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
402 pixel.black))].black;
403 traits=GetPixelChannelTraits(image,AlphaPixelChannel);
404 if ((traits & UpdatePixelTrait) != 0)
405 pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
406 pixel.alpha))].alpha;
407 SetPixelViaPixelInfo(image,&pixel,q);
408 q+=GetPixelChannels(image);
410 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
412 if (image->progress_monitor != (MagickProgressMonitor) NULL)
417 #if defined(MAGICKCORE_OPENMP_SUPPORT)
418 #pragma omp critical (MagickCore_ClutImage)
420 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
421 if( IfMagickFalse(proceed) )
425 image_view=DestroyCacheView(image_view);
426 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
427 if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
428 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
429 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
434 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438 % C o l o r D e c i s i o n L i s t I m a g e %
442 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
444 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
445 % (CCC) file which solely contains one or more color corrections and applies
446 % the correction to the image. Here is a sample CCC file:
448 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
449 % <ColorCorrection id="cc03345">
451 % <Slope> 0.9 1.2 0.5 </Slope>
452 % <Offset> 0.4 -0.5 0.6 </Offset>
453 % <Power> 1.0 0.8 1.5 </Power>
456 % <Saturation> 0.85 </Saturation>
459 % </ColorCorrectionCollection>
461 % which includes the slop, offset, and power for each of the RGB channels
462 % as well as the saturation.
464 % The format of the ColorDecisionListImage method is:
466 % MagickBooleanType ColorDecisionListImage(Image *image,
467 % const char *color_correction_collection,ExceptionInfo *exception)
469 % A description of each parameter follows:
471 % o image: the image.
473 % o color_correction_collection: the color correction collection in XML.
475 % o exception: return any errors or warnings in this structure.
478 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
479 const char *color_correction_collection,ExceptionInfo *exception)
481 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
483 typedef struct _Correction
491 typedef struct _ColorCorrection
506 token[MagickPathExtent];
537 Allocate and initialize cdl maps.
539 assert(image != (Image *) NULL);
540 assert(image->signature == MagickCoreSignature);
541 if (image->debug != MagickFalse)
542 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
543 if (color_correction_collection == (const char *) NULL)
545 ccc=NewXMLTree((const char *) color_correction_collection,exception);
546 if (ccc == (XMLTreeInfo *) NULL)
548 cc=GetXMLTreeChild(ccc,"ColorCorrection");
549 if (cc == (XMLTreeInfo *) NULL)
551 ccc=DestroyXMLTree(ccc);
554 color_correction.red.slope=1.0;
555 color_correction.red.offset=0.0;
556 color_correction.red.power=1.0;
557 color_correction.green.slope=1.0;
558 color_correction.green.offset=0.0;
559 color_correction.green.power=1.0;
560 color_correction.blue.slope=1.0;
561 color_correction.blue.offset=0.0;
562 color_correction.blue.power=1.0;
563 color_correction.saturation=0.0;
564 sop=GetXMLTreeChild(cc,"SOPNode");
565 if (sop != (XMLTreeInfo *) NULL)
572 slope=GetXMLTreeChild(sop,"Slope");
573 if (slope != (XMLTreeInfo *) NULL)
575 content=GetXMLTreeContent(slope);
576 p=(const char *) content;
577 for (i=0; (*p != '\0') && (i < 3); i++)
579 GetMagickToken(p,&p,token);
581 GetMagickToken(p,&p,token);
586 color_correction.red.slope=StringToDouble(token,(char **) NULL);
591 color_correction.green.slope=StringToDouble(token,
597 color_correction.blue.slope=StringToDouble(token,
604 offset=GetXMLTreeChild(sop,"Offset");
605 if (offset != (XMLTreeInfo *) NULL)
607 content=GetXMLTreeContent(offset);
608 p=(const char *) content;
609 for (i=0; (*p != '\0') && (i < 3); i++)
611 GetMagickToken(p,&p,token);
613 GetMagickToken(p,&p,token);
618 color_correction.red.offset=StringToDouble(token,
624 color_correction.green.offset=StringToDouble(token,
630 color_correction.blue.offset=StringToDouble(token,
637 power=GetXMLTreeChild(sop,"Power");
638 if (power != (XMLTreeInfo *) NULL)
640 content=GetXMLTreeContent(power);
641 p=(const char *) content;
642 for (i=0; (*p != '\0') && (i < 3); i++)
644 GetMagickToken(p,&p,token);
646 GetMagickToken(p,&p,token);
651 color_correction.red.power=StringToDouble(token,(char **) NULL);
656 color_correction.green.power=StringToDouble(token,
662 color_correction.blue.power=StringToDouble(token,
670 sat=GetXMLTreeChild(cc,"SATNode");
671 if (sat != (XMLTreeInfo *) NULL)
676 saturation=GetXMLTreeChild(sat,"Saturation");
677 if (saturation != (XMLTreeInfo *) NULL)
679 content=GetXMLTreeContent(saturation);
680 p=(const char *) content;
681 GetMagickToken(p,&p,token);
682 color_correction.saturation=StringToDouble(token,(char **) NULL);
685 ccc=DestroyXMLTree(ccc);
686 if (image->debug != MagickFalse)
688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
689 " Color Correction Collection:");
690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
691 " color_correction.red.slope: %g",color_correction.red.slope);
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " color_correction.red.offset: %g",color_correction.red.offset);
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.red.power: %g",color_correction.red.power);
696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
697 " color_correction.green.slope: %g",color_correction.green.slope);
698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
699 " color_correction.green.offset: %g",color_correction.green.offset);
700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
701 " color_correction.green.power: %g",color_correction.green.power);
702 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
703 " color_correction.blue.slope: %g",color_correction.blue.slope);
704 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
705 " color_correction.blue.offset: %g",color_correction.blue.offset);
706 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
707 " color_correction.blue.power: %g",color_correction.blue.power);
708 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
709 " color_correction.saturation: %g",color_correction.saturation);
711 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
712 if (cdl_map == (PixelInfo *) NULL)
713 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
715 for (i=0; i <= (ssize_t) MaxMap; i++)
717 cdl_map[i].red=(double) ScaleMapToQuantum((double)
718 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
719 color_correction.red.offset,color_correction.red.power))));
720 cdl_map[i].green=(double) ScaleMapToQuantum((double)
721 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
722 color_correction.green.offset,color_correction.green.power))));
723 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
724 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
725 color_correction.blue.offset,color_correction.blue.power))));
727 if (image->storage_class == PseudoClass)
728 for (i=0; i < (ssize_t) image->colors; i++)
731 Apply transfer function to colormap.
736 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
737 0.07217f*image->colormap[i].blue;
738 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
739 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
740 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
741 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
742 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
743 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
746 Apply transfer function to image.
750 image_view=AcquireAuthenticCacheView(image,exception);
751 #if defined(MAGICKCORE_OPENMP_SUPPORT)
752 #pragma omp parallel for schedule(static,4) shared(progress,status) \
753 magick_threads(image,image,image->rows,1)
755 for (y=0; y < (ssize_t) image->rows; y++)
766 if (status == MagickFalse)
768 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
769 if (q == (Quantum *) NULL)
774 for (x=0; x < (ssize_t) image->columns; x++)
776 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
777 0.07217f*GetPixelBlue(image,q);
778 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
779 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
780 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
781 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
782 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
784 q+=GetPixelChannels(image);
786 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
788 if (image->progress_monitor != (MagickProgressMonitor) NULL)
793 #if defined(MAGICKCORE_OPENMP_SUPPORT)
794 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
796 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
797 progress++,image->rows);
798 if( IfMagickFalse(proceed) )
802 image_view=DestroyCacheView(image_view);
803 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
808 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
812 % C o n t r a s t I m a g e %
816 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
818 % ContrastImage() enhances the intensity differences between the lighter and
819 % darker elements of the image. Set sharpen to a MagickTrue to increase the
820 % image contrast otherwise the contrast is reduced.
822 % The format of the ContrastImage method is:
824 % MagickBooleanType ContrastImage(Image *image,
825 % const MagickBooleanType sharpen,ExceptionInfo *exception)
827 % A description of each parameter follows:
829 % o image: the image.
831 % o sharpen: Increase or decrease image contrast.
833 % o exception: return any errors or warnings in this structure.
837 static void Contrast(const int sign,double *red,double *green,double *blue)
845 Enhance contrast: dark color become darker, light color become lighter.
847 assert(red != (double *) NULL);
848 assert(green != (double *) NULL);
849 assert(blue != (double *) NULL);
853 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
854 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
856 if (brightness > 1.0)
859 if (brightness < 0.0)
861 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
864 MagickExport MagickBooleanType ContrastImage(Image *image,
865 const MagickBooleanType sharpen,ExceptionInfo *exception)
867 #define ContrastImageTag "Contrast/Image"
887 assert(image != (Image *) NULL);
888 assert(image->signature == MagickCoreSignature);
889 if (image->debug != MagickFalse)
890 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
891 sign=IfMagickTrue(sharpen) ? 1 : -1;
892 if (image->storage_class == PseudoClass)
895 Contrast enhance colormap.
897 for (i=0; i < (ssize_t) image->colors; i++)
907 Contrast(sign,&red,&green,&blue);
908 image->colormap[i].red=(MagickRealType) red;
909 image->colormap[i].green=(MagickRealType) green;
910 image->colormap[i].blue=(MagickRealType) blue;
914 Contrast enhance image.
918 image_view=AcquireAuthenticCacheView(image,exception);
919 #if defined(MAGICKCORE_OPENMP_SUPPORT)
920 #pragma omp parallel for schedule(static,4) shared(progress,status) \
921 magick_threads(image,image,image->rows,1)
923 for (y=0; y < (ssize_t) image->rows; y++)
936 if (status == MagickFalse)
938 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
939 if (q == (Quantum *) NULL)
944 for (x=0; x < (ssize_t) image->columns; x++)
946 red=(double) GetPixelRed(image,q);
947 green=(double) GetPixelGreen(image,q);
948 blue=(double) GetPixelBlue(image,q);
949 Contrast(sign,&red,&green,&blue);
950 SetPixelRed(image,ClampToQuantum(red),q);
951 SetPixelGreen(image,ClampToQuantum(green),q);
952 SetPixelBlue(image,ClampToQuantum(blue),q);
953 q+=GetPixelChannels(image);
955 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
962 #if defined(MAGICKCORE_OPENMP_SUPPORT)
963 #pragma omp critical (MagickCore_ContrastImage)
965 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
966 if( IfMagickFalse(proceed) )
970 image_view=DestroyCacheView(image_view);
975 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
979 % C o n t r a s t S t r e t c h I m a g e %
983 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985 % ContrastStretchImage() is a simple image enhancement technique that attempts
986 % to improve the contrast in an image by 'stretching' the range of intensity
987 % values it contains to span a desired range of values. It differs from the
988 % more sophisticated histogram equalization in that it can only apply a
989 % linear scaling function to the image pixel values. As a result the
990 % 'enhancement' is less harsh.
992 % The format of the ContrastStretchImage method is:
994 % MagickBooleanType ContrastStretchImage(Image *image,
995 % const char *levels,ExceptionInfo *exception)
997 % A description of each parameter follows:
999 % o image: the image.
1001 % o black_point: the black point.
1003 % o white_point: the white point.
1005 % o levels: Specify the levels where the black and white points have the
1006 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1008 % o exception: return any errors or warnings in this structure.
1011 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1012 const double black_point,const double white_point,ExceptionInfo *exception)
1014 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1015 #define ContrastStretchImageTag "ContrastStretch/Image"
1039 Allocate histogram and stretch map.
1041 assert(image != (Image *) NULL);
1042 assert(image->signature == MagickCoreSignature);
1043 if (image->debug != MagickFalse)
1044 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1045 if (SetImageGray(image,exception) != MagickFalse)
1046 (void) SetImageColorspace(image,GRAYColorspace,exception);
1047 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1048 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1049 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1050 sizeof(*histogram));
1051 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1052 GetPixelChannels(image)*sizeof(*stretch_map));
1053 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1054 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1056 if (stretch_map != (double *) NULL)
1057 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1058 if (histogram != (double *) NULL)
1059 histogram=(double *) RelinquishMagickMemory(histogram);
1060 if (white != (double *) NULL)
1061 white=(double *) RelinquishMagickMemory(white);
1062 if (black != (double *) NULL)
1063 black=(double *) RelinquishMagickMemory(black);
1064 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1071 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1072 sizeof(*histogram));
1073 image_view=AcquireVirtualCacheView(image,exception);
1074 for (y=0; y < (ssize_t) image->rows; y++)
1076 register const Quantum
1082 if (status == MagickFalse)
1084 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1085 if (p == (const Quantum *) NULL)
1090 for (x=0; x < (ssize_t) image->columns; x++)
1095 pixel=GetPixelIntensity(image,p);
1096 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1098 if (image->channel_mask != DefaultChannels)
1099 pixel=(double) p[i];
1100 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1101 ClampToQuantum(pixel))+i]++;
1103 p+=GetPixelChannels(image);
1106 image_view=DestroyCacheView(image_view);
1108 Find the histogram boundaries by locating the black/white levels.
1110 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1119 white[i]=MaxRange(QuantumRange);
1121 for (j=0; j <= (ssize_t) MaxMap; j++)
1123 intensity+=histogram[GetPixelChannels(image)*j+i];
1124 if (intensity > black_point)
1127 black[i]=(double) j;
1129 for (j=(ssize_t) MaxMap; j != 0; j--)
1131 intensity+=histogram[GetPixelChannels(image)*j+i];
1132 if (intensity > ((double) image->columns*image->rows-white_point))
1135 white[i]=(double) j;
1137 histogram=(double *) RelinquishMagickMemory(histogram);
1139 Stretch the histogram to create the stretched image mapping.
1141 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1142 sizeof(*stretch_map));
1143 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1148 for (j=0; j <= (ssize_t) MaxMap; j++)
1153 gamma=PerceptibleReciprocal(white[i]-black[i]);
1154 if (j < (ssize_t) black[i])
1155 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1157 if (j > (ssize_t) white[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1160 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1161 (double) (MaxMap*gamma*(j-black[i])));
1164 if (image->storage_class == PseudoClass)
1170 Stretch-contrast colormap.
1172 for (j=0; j < (ssize_t) image->colors; j++)
1174 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1176 i=GetPixelChannelOffset(image,RedPixelChannel);
1177 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1178 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1180 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1182 i=GetPixelChannelOffset(image,GreenPixelChannel);
1183 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1184 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1186 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1188 i=GetPixelChannelOffset(image,BluePixelChannel);
1189 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1190 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1192 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1194 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1195 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1196 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1201 Stretch-contrast image.
1205 image_view=AcquireAuthenticCacheView(image,exception);
1206 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1207 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1208 magick_threads(image,image,image->rows,1)
1210 for (y=0; y < (ssize_t) image->rows; y++)
1218 if (status == MagickFalse)
1220 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1221 if (q == (Quantum *) NULL)
1226 for (x=0; x < (ssize_t) image->columns; x++)
1231 if (GetPixelReadMask(image,q) == 0)
1233 q+=GetPixelChannels(image);
1236 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1238 PixelChannel channel=GetPixelChannelChannel(image,j);
1239 PixelTrait traits=GetPixelChannelTraits(image,channel);
1240 if ((traits & UpdatePixelTrait) == 0)
1242 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1243 ScaleQuantumToMap(q[j])+j]);
1245 q+=GetPixelChannels(image);
1247 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1249 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1254 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1255 #pragma omp critical (MagickCore_ContrastStretchImage)
1257 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1259 if (proceed == MagickFalse)
1263 image_view=DestroyCacheView(image_view);
1264 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1265 white=(double *) RelinquishMagickMemory(white);
1266 black=(double *) RelinquishMagickMemory(black);
1271 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1275 % E n h a n c e I m a g e %
1279 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1281 % EnhanceImage() applies a digital filter that improves the quality of a
1284 % The format of the EnhanceImage method is:
1286 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1288 % A description of each parameter follows:
1290 % o image: the image.
1292 % o exception: return any errors or warnings in this structure.
1295 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1297 #define EnhanceImageTag "Enhance/Image"
1298 #define EnhancePixel(weight) \
1299 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1300 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1301 distance_squared=(4.0+mean)*distance*distance; \
1302 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1303 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1304 distance_squared+=(7.0-mean)*distance*distance; \
1305 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1306 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1307 distance_squared+=(5.0-mean)*distance*distance; \
1308 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1309 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1310 distance_squared+=(5.0-mean)*distance*distance; \
1311 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1312 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1313 distance_squared+=(5.0-mean)*distance*distance; \
1314 if (distance_squared < 0.069) \
1316 aggregate.red+=(weight)*GetPixelRed(image,r); \
1317 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1318 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1319 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1320 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1321 total_weight+=(weight); \
1323 r+=GetPixelChannels(image);
1342 Initialize enhanced image attributes.
1344 assert(image != (const Image *) NULL);
1345 assert(image->signature == MagickCoreSignature);
1346 if (image->debug != MagickFalse)
1347 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1348 assert(exception != (ExceptionInfo *) NULL);
1349 assert(exception->signature == MagickCoreSignature);
1350 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1352 if (enhance_image == (Image *) NULL)
1353 return((Image *) NULL);
1354 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1356 enhance_image=DestroyImage(enhance_image);
1357 return((Image *) NULL);
1364 image_view=AcquireVirtualCacheView(image,exception);
1365 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1366 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1367 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1368 magick_threads(image,enhance_image,image->rows,1)
1370 for (y=0; y < (ssize_t) image->rows; y++)
1375 register const Quantum
1387 if (status == MagickFalse)
1389 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1390 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1392 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1397 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1398 GetPixelInfo(image,&pixel);
1399 for (x=0; x < (ssize_t) image->columns; x++)
1410 register const Quantum
1413 if (GetPixelReadMask(image,p) == 0)
1415 SetPixelBackgoundColor(enhance_image,q);
1416 p+=GetPixelChannels(image);
1417 q+=GetPixelChannels(enhance_image);
1420 GetPixelInfo(image,&aggregate);
1422 GetPixelInfoPixel(image,p+center,&pixel);
1424 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1425 EnhancePixel(8.0); EnhancePixel(5.0);
1426 r=p+GetPixelChannels(image)*(image->columns+4);
1427 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1428 EnhancePixel(20.0); EnhancePixel(8.0);
1429 r=p+2*GetPixelChannels(image)*(image->columns+4);
1430 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1431 EnhancePixel(40.0); EnhancePixel(10.0);
1432 r=p+3*GetPixelChannels(image)*(image->columns+4);
1433 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1434 EnhancePixel(20.0); EnhancePixel(8.0);
1435 r=p+4*GetPixelChannels(image)*(image->columns+4);
1436 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1437 EnhancePixel(8.0); EnhancePixel(5.0);
1438 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1439 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1440 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1441 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1442 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1443 SetPixelViaPixelInfo(image,&pixel,q);
1444 p+=GetPixelChannels(image);
1445 q+=GetPixelChannels(enhance_image);
1447 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1449 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1454 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1455 #pragma omp critical (MagickCore_EnhanceImage)
1457 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1458 if (proceed == MagickFalse)
1462 enhance_view=DestroyCacheView(enhance_view);
1463 image_view=DestroyCacheView(image_view);
1464 if (status == MagickFalse)
1465 enhance_image=DestroyImage(enhance_image);
1466 return(enhance_image);
1470 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1474 % E q u a l i z e I m a g e %
1478 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1480 % EqualizeImage() applies a histogram equalization to the image.
1482 % The format of the EqualizeImage method is:
1484 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1486 % A description of each parameter follows:
1488 % o image: the image.
1490 % o exception: return any errors or warnings in this structure.
1493 MagickExport MagickBooleanType EqualizeImage(Image *image,
1494 ExceptionInfo *exception)
1496 #define EqualizeImageTag "Equalize/Image"
1508 black[CompositePixelChannel+1],
1512 white[CompositePixelChannel+1];
1521 Allocate and initialize histogram arrays.
1523 assert(image != (Image *) NULL);
1524 assert(image->signature == MagickCoreSignature);
1525 if (image->debug != MagickFalse)
1526 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1527 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1528 GetPixelChannels(image)*sizeof(*equalize_map));
1529 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1530 sizeof(*histogram));
1531 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1533 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1534 (map == (double *) NULL))
1536 if (map != (double *) NULL)
1537 map=(double *) RelinquishMagickMemory(map);
1538 if (histogram != (double *) NULL)
1539 histogram=(double *) RelinquishMagickMemory(histogram);
1540 if (equalize_map != (double *) NULL)
1541 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1542 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1549 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1550 sizeof(*histogram));
1551 image_view=AcquireVirtualCacheView(image,exception);
1552 for (y=0; y < (ssize_t) image->rows; y++)
1554 register const Quantum
1560 if (status == MagickFalse)
1562 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1563 if (p == (const Quantum *) NULL)
1568 for (x=0; x < (ssize_t) image->columns; x++)
1570 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1571 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1572 p+=GetPixelChannels(image);
1575 image_view=DestroyCacheView(image_view);
1577 Integrate the histogram to get the equalization map.
1579 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1588 for (j=0; j <= (ssize_t) MaxMap; j++)
1590 intensity+=histogram[GetPixelChannels(image)*j+i];
1591 map[GetPixelChannels(image)*j+i]=intensity;
1594 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1595 sizeof(*equalize_map));
1596 (void) ResetMagickMemory(black,0,sizeof(*black));
1597 (void) ResetMagickMemory(white,0,sizeof(*white));
1598 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1604 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1605 if (black[i] != white[i])
1606 for (j=0; j <= (ssize_t) MaxMap; j++)
1607 equalize_map[GetPixelChannels(image)*j+i]=(double)
1608 ScaleMapToQuantum((double) ((MaxMap*(map[
1609 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1611 histogram=(double *) RelinquishMagickMemory(histogram);
1612 map=(double *) RelinquishMagickMemory(map);
1613 if (image->storage_class == PseudoClass)
1621 for (j=0; j < (ssize_t) image->colors; j++)
1623 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1625 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1626 if (black[channel] != white[channel])
1627 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1628 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1631 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1633 PixelChannel channel=GetPixelChannelChannel(image,
1635 if (black[channel] != white[channel])
1636 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1637 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1640 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1642 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1643 if (black[channel] != white[channel])
1644 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1645 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1648 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1650 PixelChannel channel=GetPixelChannelChannel(image,
1652 if (black[channel] != white[channel])
1653 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1654 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1663 image_view=AcquireAuthenticCacheView(image,exception);
1664 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1665 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1666 magick_threads(image,image,image->rows,1)
1668 for (y=0; y < (ssize_t) image->rows; y++)
1676 if (status == MagickFalse)
1678 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1679 if (q == (Quantum *) NULL)
1684 for (x=0; x < (ssize_t) image->columns; x++)
1689 if (GetPixelReadMask(image,q) == 0)
1691 q+=GetPixelChannels(image);
1694 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1696 PixelChannel channel=GetPixelChannelChannel(image,j);
1697 PixelTrait traits=GetPixelChannelTraits(image,channel);
1698 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1700 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1701 ScaleQuantumToMap(q[j])+j]);
1703 q+=GetPixelChannels(image);
1705 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1707 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1712 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1713 #pragma omp critical (MagickCore_EqualizeImage)
1715 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1716 if( IfMagickFalse(proceed) )
1720 image_view=DestroyCacheView(image_view);
1721 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1726 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1730 % G a m m a I m a g e %
1734 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1736 % GammaImage() gamma-corrects a particular image channel. The same
1737 % image viewed on different devices will have perceptual differences in the
1738 % way the image's intensities are represented on the screen. Specify
1739 % individual gamma levels for the red, green, and blue channels, or adjust
1740 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1742 % You can also reduce the influence of a particular channel with a gamma
1745 % The format of the GammaImage method is:
1747 % MagickBooleanType GammaImage(Image *image,const double gamma,
1748 % ExceptionInfo *exception)
1750 % A description of each parameter follows:
1752 % o image: the image.
1754 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1756 % o gamma: the image gamma.
1760 static inline double gamma_pow(const double value,const double gamma)
1762 return(value < 0.0 ? value : pow(value,gamma));
1765 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1766 ExceptionInfo *exception)
1768 #define GammaCorrectImageTag "GammaCorrect/Image"
1789 Allocate and initialize gamma maps.
1791 assert(image != (Image *) NULL);
1792 assert(image->signature == MagickCoreSignature);
1793 if (image->debug != MagickFalse)
1794 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1797 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1798 if (gamma_map == (Quantum *) NULL)
1799 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1801 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1803 for (i=0; i <= (ssize_t) MaxMap; i++)
1804 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1805 MaxMap,1.0/gamma)));
1806 if (image->storage_class == PseudoClass)
1807 for (i=0; i < (ssize_t) image->colors; i++)
1810 Gamma-correct colormap.
1812 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1813 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1814 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1815 ClampToQuantum(image->colormap[i].red))];
1816 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1817 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1818 ClampToQuantum(image->colormap[i].green))];
1819 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1820 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1821 ClampToQuantum(image->colormap[i].blue))];
1822 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1823 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1824 ClampToQuantum(image->colormap[i].alpha))];
1826 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1827 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1828 image->colormap[i].red,1.0/gamma);
1829 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1830 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1831 image->colormap[i].green,1.0/gamma);
1832 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1833 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1834 image->colormap[i].blue,1.0/gamma);
1835 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1836 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1837 image->colormap[i].alpha,1.0/gamma);
1841 Gamma-correct image.
1845 image_view=AcquireAuthenticCacheView(image,exception);
1846 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1847 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1848 magick_threads(image,image,image->rows,1)
1850 for (y=0; y < (ssize_t) image->rows; y++)
1858 if (status == MagickFalse)
1860 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1861 if (q == (Quantum *) NULL)
1866 for (x=0; x < (ssize_t) image->columns; x++)
1871 if (GetPixelReadMask(image,q) == 0)
1873 q+=GetPixelChannels(image);
1876 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1878 PixelChannel channel=GetPixelChannelChannel(image,j);
1879 PixelTrait traits=GetPixelChannelTraits(image,channel);
1880 if ((traits & UpdatePixelTrait) == 0)
1882 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1883 q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1885 q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1888 q+=GetPixelChannels(image);
1890 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1892 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1897 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1898 #pragma omp critical (MagickCore_GammaImage)
1900 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1902 if( IfMagickFalse(proceed) )
1906 image_view=DestroyCacheView(image_view);
1907 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1908 if (image->gamma != 0.0)
1909 image->gamma*=gamma;
1914 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1918 % G r a y s c a l e I m a g e %
1922 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1924 % GrayscaleImage() converts the image to grayscale.
1926 % The format of the GrayscaleImage method is:
1928 % MagickBooleanType GrayscaleImage(Image *image,
1929 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1931 % A description of each parameter follows:
1933 % o image: the image.
1935 % o method: the pixel intensity method.
1937 % o exception: return any errors or warnings in this structure.
1940 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1941 const PixelIntensityMethod method,ExceptionInfo *exception)
1943 #define GrayscaleImageTag "Grayscale/Image"
1957 assert(image != (Image *) NULL);
1958 assert(image->signature == MagickCoreSignature);
1959 if (image->debug != MagickFalse)
1960 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1961 if (image->storage_class == PseudoClass)
1963 if( IfMagickFalse(SyncImage(image,exception)) )
1964 return(MagickFalse);
1965 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
1966 return(MagickFalse);
1973 image_view=AcquireAuthenticCacheView(image,exception);
1974 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1975 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1976 magick_threads(image,image,image->rows,1)
1978 for (y=0; y < (ssize_t) image->rows; y++)
1986 if (status == MagickFalse)
1988 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1989 if (q == (Quantum *) NULL)
1994 for (x=0; x < (ssize_t) image->columns; x++)
2002 if (GetPixelReadMask(image,q) == 0)
2004 q+=GetPixelChannels(image);
2007 red=(MagickRealType) GetPixelRed(image,q);
2008 green=(MagickRealType) GetPixelGreen(image,q);
2009 blue=(MagickRealType) GetPixelBlue(image,q);
2013 case AveragePixelIntensityMethod:
2015 intensity=(red+green+blue)/3.0;
2018 case BrightnessPixelIntensityMethod:
2020 intensity=MagickMax(MagickMax(red,green),blue);
2023 case LightnessPixelIntensityMethod:
2025 intensity=(MagickMin(MagickMin(red,green),blue)+
2026 MagickMax(MagickMax(red,green),blue))/2.0;
2029 case MSPixelIntensityMethod:
2031 intensity=(MagickRealType) (((double) red*red+green*green+
2035 case Rec601LumaPixelIntensityMethod:
2037 if (image->colorspace == RGBColorspace)
2039 red=EncodePixelGamma(red);
2040 green=EncodePixelGamma(green);
2041 blue=EncodePixelGamma(blue);
2043 intensity=0.298839*red+0.586811*green+0.114350*blue;
2046 case Rec601LuminancePixelIntensityMethod:
2048 if (image->colorspace == sRGBColorspace)
2050 red=DecodePixelGamma(red);
2051 green=DecodePixelGamma(green);
2052 blue=DecodePixelGamma(blue);
2054 intensity=0.298839*red+0.586811*green+0.114350*blue;
2057 case Rec709LumaPixelIntensityMethod:
2060 if (image->colorspace == RGBColorspace)
2062 red=EncodePixelGamma(red);
2063 green=EncodePixelGamma(green);
2064 blue=EncodePixelGamma(blue);
2066 intensity=0.212656*red+0.715158*green+0.072186*blue;
2069 case Rec709LuminancePixelIntensityMethod:
2071 if (image->colorspace == sRGBColorspace)
2073 red=DecodePixelGamma(red);
2074 green=DecodePixelGamma(green);
2075 blue=DecodePixelGamma(blue);
2077 intensity=0.212656*red+0.715158*green+0.072186*blue;
2080 case RMSPixelIntensityMethod:
2082 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2083 blue*blue)/sqrt(3.0));
2087 SetPixelGray(image,ClampToQuantum(intensity),q);
2088 q+=GetPixelChannels(image);
2090 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
2092 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2097 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2098 #pragma omp critical (MagickCore_GrayscaleImage)
2100 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2102 if( IfMagickFalse(proceed) )
2106 image_view=DestroyCacheView(image_view);
2107 image->intensity=method;
2108 image->type=GrayscaleType;
2109 return(SetImageColorspace(image,GRAYColorspace,exception));
2113 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2117 % H a l d C l u t I m a g e %
2121 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2123 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2124 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2125 % Create it with the HALD coder. You can apply any color transformation to
2126 % the Hald image and then use this method to apply the transform to the
2129 % The format of the HaldClutImage method is:
2131 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2132 % ExceptionInfo *exception)
2134 % A description of each parameter follows:
2136 % o image: the image, which is replaced by indexed CLUT values
2138 % o hald_image: the color lookup table image for replacement color values.
2140 % o exception: return any errors or warnings in this structure.
2143 MagickExport MagickBooleanType HaldClutImage(Image *image,
2144 const Image *hald_image,ExceptionInfo *exception)
2146 #define HaldClutImageTag "Clut/Image"
2148 typedef struct _HaldInfo
2180 assert(image != (Image *) NULL);
2181 assert(image->signature == MagickCoreSignature);
2182 if (image->debug != MagickFalse)
2183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2184 assert(hald_image != (Image *) NULL);
2185 assert(hald_image->signature == MagickCoreSignature);
2186 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
2187 return(MagickFalse);
2188 if (image->alpha_trait == UndefinedPixelTrait)
2189 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2195 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2196 (MagickRealType) hald_image->rows);
2197 for (level=2; (level*level*level) < length; level++) ;
2199 cube_size=level*level;
2200 width=(double) hald_image->columns;
2201 GetPixelInfo(hald_image,&zero);
2202 hald_view=AcquireVirtualCacheView(hald_image,exception);
2203 image_view=AcquireAuthenticCacheView(image,exception);
2204 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2205 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2206 magick_threads(image,image,image->rows,1)
2208 for (y=0; y < (ssize_t) image->rows; y++)
2216 if (status == MagickFalse)
2218 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2219 if (q == (Quantum *) NULL)
2224 for (x=0; x < (ssize_t) image->columns; x++)
2239 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2240 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2241 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2242 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2243 point.x-=floor(point.x);
2244 point.y-=floor(point.y);
2245 point.z-=floor(point.z);
2247 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2248 fmod(offset,width),floor(offset/width),&pixel1,exception);
2250 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2251 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2253 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2256 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2257 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 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2266 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2267 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2268 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2269 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2270 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2271 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2272 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2273 (image->colorspace == CMYKColorspace))
2274 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2275 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2276 (image->alpha_trait != UndefinedPixelTrait))
2277 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2278 q+=GetPixelChannels(image);
2280 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2282 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2287 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2288 #pragma omp critical (MagickCore_HaldClutImage)
2290 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2291 if( IfMagickFalse(proceed) )
2295 hald_view=DestroyCacheView(hald_view);
2296 image_view=DestroyCacheView(image_view);
2301 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2305 % L e v e l I m a g e %
2309 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2311 % LevelImage() adjusts the levels of a particular image channel by
2312 % scaling the colors falling between specified white and black points to
2313 % the full available quantum range.
2315 % The parameters provided represent the black, and white points. The black
2316 % point specifies the darkest color in the image. Colors darker than the
2317 % black point are set to zero. White point specifies the lightest color in
2318 % the image. Colors brighter than the white point are set to the maximum
2321 % If a '!' flag is given, map black and white colors to the given levels
2322 % rather than mapping those levels to black and white. See
2323 % LevelizeImage() below.
2325 % Gamma specifies a gamma correction to apply to the image.
2327 % The format of the LevelImage method is:
2329 % MagickBooleanType LevelImage(Image *image,const double black_point,
2330 % const double white_point,const double gamma,ExceptionInfo *exception)
2332 % A description of each parameter follows:
2334 % o image: the image.
2336 % o black_point: The level to map zero (black) to.
2338 % o white_point: The level to map QuantumRange (white) to.
2340 % o exception: return any errors or warnings in this structure.
2344 static inline double LevelPixel(const double black_point,
2345 const double white_point,const double gamma,const double pixel)
2351 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2352 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2354 return(level_pixel);
2357 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2358 const double white_point,const double gamma,ExceptionInfo *exception)
2360 #define LevelImageTag "Level/Image"
2378 Allocate and initialize levels map.
2380 assert(image != (Image *) NULL);
2381 assert(image->signature == MagickCoreSignature);
2382 if (image->debug != MagickFalse)
2383 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2384 if (image->storage_class == PseudoClass)
2385 for (i=0; i < (ssize_t) image->colors; i++)
2390 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2391 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2392 white_point,gamma,image->colormap[i].red));
2393 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2394 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2395 white_point,gamma,image->colormap[i].green));
2396 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2397 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2398 white_point,gamma,image->colormap[i].blue));
2399 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2400 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2401 white_point,gamma,image->colormap[i].alpha));
2408 image_view=AcquireAuthenticCacheView(image,exception);
2409 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2410 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2411 magick_threads(image,image,image->rows,1)
2413 for (y=0; y < (ssize_t) image->rows; y++)
2421 if (status == MagickFalse)
2423 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2424 if (q == (Quantum *) NULL)
2429 for (x=0; x < (ssize_t) image->columns; x++)
2434 if (GetPixelReadMask(image,q) == 0)
2436 q+=GetPixelChannels(image);
2439 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2441 PixelChannel channel=GetPixelChannelChannel(image,j);
2442 PixelTrait traits=GetPixelChannelTraits(image,channel);
2443 if ((traits & UpdatePixelTrait) == 0)
2445 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2448 q+=GetPixelChannels(image);
2450 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2452 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2458 #pragma omp critical (MagickCore_LevelImage)
2460 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2461 if( IfMagickFalse(proceed) )
2465 image_view=DestroyCacheView(image_view);
2466 (void) ClampImage(image,exception);
2471 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2475 % L e v e l i z e I m a g e %
2479 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2481 % LevelizeImage() applies the reversed LevelImage() operation to just
2482 % the specific channels specified. It compresses the full range of color
2483 % values, so that they lie between the given black and white points. Gamma is
2484 % applied before the values are mapped.
2486 % LevelizeImage() can be called with by using a +level command line
2487 % API option, or using a '!' on a -level or LevelImage() geometry string.
2489 % It can be used to de-contrast a greyscale image to the exact levels
2490 % specified. Or by using specific levels for each channel of an image you
2491 % can convert a gray-scale image to any linear color gradient, according to
2494 % The format of the LevelizeImage method is:
2496 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2497 % const double white_point,const double gamma,ExceptionInfo *exception)
2499 % A description of each parameter follows:
2501 % o image: the image.
2503 % o black_point: The level to map zero (black) to.
2505 % o white_point: The level to map QuantumRange (white) to.
2507 % o gamma: adjust gamma by this factor before mapping values.
2509 % o exception: return any errors or warnings in this structure.
2512 MagickExport MagickBooleanType LevelizeImage(Image *image,
2513 const double black_point,const double white_point,const double gamma,
2514 ExceptionInfo *exception)
2516 #define LevelizeImageTag "Levelize/Image"
2517 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2518 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2536 Allocate and initialize levels map.
2538 assert(image != (Image *) NULL);
2539 assert(image->signature == MagickCoreSignature);
2540 if (image->debug != MagickFalse)
2541 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2542 if (image->storage_class == PseudoClass)
2543 for (i=0; i < (ssize_t) image->colors; i++)
2548 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2549 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2550 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2551 image->colormap[i].green=(double) LevelizeValue(
2552 image->colormap[i].green);
2553 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2554 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2555 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2556 image->colormap[i].alpha=(double) LevelizeValue(
2557 image->colormap[i].alpha);
2564 image_view=AcquireAuthenticCacheView(image,exception);
2565 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2566 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2567 magick_threads(image,image,image->rows,1)
2569 for (y=0; y < (ssize_t) image->rows; y++)
2577 if (status == MagickFalse)
2579 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2580 if (q == (Quantum *) NULL)
2585 for (x=0; x < (ssize_t) image->columns; x++)
2590 if (GetPixelReadMask(image,q) == 0)
2592 q+=GetPixelChannels(image);
2595 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2597 PixelChannel channel=GetPixelChannelChannel(image,j);
2598 PixelTrait traits=GetPixelChannelTraits(image,channel);
2599 if ((traits & UpdatePixelTrait) == 0)
2601 q[j]=LevelizeValue(q[j]);
2603 q+=GetPixelChannels(image);
2605 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2607 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2612 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2613 #pragma omp critical (MagickCore_LevelizeImage)
2615 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2616 if( IfMagickFalse(proceed) )
2620 image_view=DestroyCacheView(image_view);
2625 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2629 % L e v e l I m a g e C o l o r s %
2633 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2635 % LevelImageColors() maps the given color to "black" and "white" values,
2636 % linearly spreading out the colors, and level values on a channel by channel
2637 % bases, as per LevelImage(). The given colors allows you to specify
2638 % different level ranges for each of the color channels separately.
2640 % If the boolean 'invert' is set true the image values will modifyed in the
2641 % reverse direction. That is any existing "black" and "white" colors in the
2642 % image will become the color values given, with all other values compressed
2643 % appropriatally. This effectivally maps a greyscale gradient into the given
2646 % The format of the LevelImageColors method is:
2648 % MagickBooleanType LevelImageColors(Image *image,
2649 % const PixelInfo *black_color,const PixelInfo *white_color,
2650 % const MagickBooleanType invert,ExceptionInfo *exception)
2652 % A description of each parameter follows:
2654 % o image: the image.
2656 % o black_color: The color to map black to/from
2658 % o white_point: The color to map white to/from
2660 % o invert: if true map the colors (levelize), rather than from (level)
2662 % o exception: return any errors or warnings in this structure.
2665 MagickExport MagickBooleanType LevelImageColors(Image *image,
2666 const PixelInfo *black_color,const PixelInfo *white_color,
2667 const MagickBooleanType invert,ExceptionInfo *exception)
2676 Allocate and initialize levels map.
2678 assert(image != (Image *) NULL);
2679 assert(image->signature == MagickCoreSignature);
2680 if (image->debug != MagickFalse)
2681 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2682 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2683 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2684 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
2685 (void) SetImageColorspace(image,sRGBColorspace,exception);
2687 if( IfMagickFalse(invert) )
2689 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2691 channel_mask=SetImageChannelMask(image,RedChannel);
2692 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2694 (void) SetImageChannelMask(image,channel_mask);
2696 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2698 channel_mask=SetImageChannelMask(image,GreenChannel);
2699 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2701 (void) SetImageChannelMask(image,channel_mask);
2703 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2705 channel_mask=SetImageChannelMask(image,BlueChannel);
2706 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2708 (void) SetImageChannelMask(image,channel_mask);
2710 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2711 (image->colorspace == CMYKColorspace))
2713 channel_mask=SetImageChannelMask(image,BlackChannel);
2714 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2716 (void) SetImageChannelMask(image,channel_mask);
2718 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2719 (image->alpha_trait != UndefinedPixelTrait))
2721 channel_mask=SetImageChannelMask(image,AlphaChannel);
2722 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2724 (void) SetImageChannelMask(image,channel_mask);
2729 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2731 channel_mask=SetImageChannelMask(image,RedChannel);
2732 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2734 (void) SetImageChannelMask(image,channel_mask);
2736 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2738 channel_mask=SetImageChannelMask(image,GreenChannel);
2739 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2741 (void) SetImageChannelMask(image,channel_mask);
2743 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2745 channel_mask=SetImageChannelMask(image,BlueChannel);
2746 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2748 (void) SetImageChannelMask(image,channel_mask);
2750 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2751 (image->colorspace == CMYKColorspace))
2753 channel_mask=SetImageChannelMask(image,BlackChannel);
2754 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2756 (void) SetImageChannelMask(image,channel_mask);
2758 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2759 (image->alpha_trait != UndefinedPixelTrait))
2761 channel_mask=SetImageChannelMask(image,AlphaChannel);
2762 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2764 (void) SetImageChannelMask(image,channel_mask);
2767 return(status != 0 ? MagickTrue : MagickFalse);
2771 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2775 % L i n e a r S t r e t c h I m a g e %
2779 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2781 % LinearStretchImage() discards any pixels below the black point and above
2782 % the white point and levels the remaining pixels.
2784 % The format of the LinearStretchImage method is:
2786 % MagickBooleanType LinearStretchImage(Image *image,
2787 % const double black_point,const double white_point,
2788 % ExceptionInfo *exception)
2790 % A description of each parameter follows:
2792 % o image: the image.
2794 % o black_point: the black point.
2796 % o white_point: the white point.
2798 % o exception: return any errors or warnings in this structure.
2801 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2802 const double black_point,const double white_point,ExceptionInfo *exception)
2804 #define LinearStretchImageTag "LinearStretch/Image"
2822 Allocate histogram and linear map.
2824 assert(image != (Image *) NULL);
2825 assert(image->signature == MagickCoreSignature);
2826 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2827 if (histogram == (double *) NULL)
2828 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2833 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2834 image_view=AcquireVirtualCacheView(image,exception);
2835 for (y=0; y < (ssize_t) image->rows; y++)
2837 register const Quantum
2843 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2844 if (p == (const Quantum *) NULL)
2846 for (x=0; x < (ssize_t) image->columns; x++)
2848 intensity=GetPixelIntensity(image,p);
2849 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2850 p+=GetPixelChannels(image);
2853 image_view=DestroyCacheView(image_view);
2855 Find the histogram boundaries by locating the black and white point levels.
2858 for (black=0; black < (ssize_t) MaxMap; black++)
2860 intensity+=histogram[black];
2861 if (intensity >= black_point)
2865 for (white=(ssize_t) MaxMap; white != 0; white--)
2867 intensity+=histogram[white];
2868 if (intensity >= white_point)
2871 histogram=(double *) RelinquishMagickMemory(histogram);
2872 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2873 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2879 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2883 % M o d u l a t e I m a g e %
2887 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2889 % ModulateImage() lets you control the brightness, saturation, and hue
2890 % of an image. Modulate represents the brightness, saturation, and hue
2891 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2892 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2893 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2895 % The format of the ModulateImage method is:
2897 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2898 % ExceptionInfo *exception)
2900 % A description of each parameter follows:
2902 % o image: the image.
2904 % o modulate: Define the percent change in brightness, saturation, and hue.
2906 % o exception: return any errors or warnings in this structure.
2910 static inline void ModulateHCL(const double percent_hue,
2911 const double percent_chroma,const double percent_luma,double *red,
2912 double *green,double *blue)
2920 Increase or decrease color luma, chroma, or hue.
2922 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2923 hue+=0.5*(0.01*percent_hue-1.0);
2928 chroma*=0.01*percent_chroma;
2929 luma*=0.01*percent_luma;
2930 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2933 static inline void ModulateHCLp(const double percent_hue,
2934 const double percent_chroma,const double percent_luma,double *red,
2935 double *green,double *blue)
2943 Increase or decrease color luma, chroma, or hue.
2945 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2946 hue+=0.5*(0.01*percent_hue-1.0);
2951 chroma*=0.01*percent_chroma;
2952 luma*=0.01*percent_luma;
2953 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2956 static inline void ModulateHSB(const double percent_hue,
2957 const double percent_saturation,const double percent_brightness,double *red,
2958 double *green,double *blue)
2966 Increase or decrease color brightness, saturation, or hue.
2968 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2969 hue+=0.5*(0.01*percent_hue-1.0);
2974 saturation*=0.01*percent_saturation;
2975 brightness*=0.01*percent_brightness;
2976 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2979 static inline void ModulateHSI(const double percent_hue,
2980 const double percent_saturation,const double percent_intensity,double *red,
2981 double *green,double *blue)
2989 Increase or decrease color intensity, saturation, or hue.
2991 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
2992 hue+=0.5*(0.01*percent_hue-1.0);
2997 saturation*=0.01*percent_saturation;
2998 intensity*=0.01*percent_intensity;
2999 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3002 static inline void ModulateHSL(const double percent_hue,
3003 const double percent_saturation,const double percent_lightness,double *red,
3004 double *green,double *blue)
3012 Increase or decrease color lightness, saturation, or hue.
3014 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3015 hue+=0.5*(0.01*percent_hue-1.0);
3020 saturation*=0.01*percent_saturation;
3021 lightness*=0.01*percent_lightness;
3022 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3025 static inline void ModulateHSV(const double percent_hue,
3026 const double percent_saturation,const double percent_value,double *red,
3027 double *green,double *blue)
3035 Increase or decrease color value, saturation, or hue.
3037 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3038 hue+=0.5*(0.01*percent_hue-1.0);
3043 saturation*=0.01*percent_saturation;
3044 value*=0.01*percent_value;
3045 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3048 static inline void ModulateHWB(const double percent_hue,
3049 const double percent_whiteness,const double percent_blackness,double *red,
3050 double *green,double *blue)
3058 Increase or decrease color blackness, whiteness, or hue.
3060 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3061 hue+=0.5*(0.01*percent_hue-1.0);
3066 blackness*=0.01*percent_blackness;
3067 whiteness*=0.01*percent_whiteness;
3068 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3071 static inline void ModulateLCHab(const double percent_luma,
3072 const double percent_chroma,const double percent_hue,double *red,
3073 double *green,double *blue)
3081 Increase or decrease color luma, chroma, or hue.
3083 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3084 luma*=0.01*percent_luma;
3085 chroma*=0.01*percent_chroma;
3086 hue+=0.5*(0.01*percent_hue-1.0);
3091 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3094 static inline void ModulateLCHuv(const double percent_luma,
3095 const double percent_chroma,const double percent_hue,double *red,
3096 double *green,double *blue)
3104 Increase or decrease color luma, chroma, or hue.
3106 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3107 luma*=0.01*percent_luma;
3108 chroma*=0.01*percent_chroma;
3109 hue+=0.5*(0.01*percent_hue-1.0);
3114 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3117 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3118 ExceptionInfo *exception)
3120 #define ModulateImageTag "Modulate/Image"
3155 Initialize modulate table.
3157 assert(image != (Image *) NULL);
3158 assert(image->signature == MagickCoreSignature);
3159 if (image->debug != MagickFalse)
3160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3161 if (modulate == (char *) NULL)
3162 return(MagickFalse);
3163 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
3164 (void) SetImageColorspace(image,sRGBColorspace,exception);
3165 flags=ParseGeometry(modulate,&geometry_info);
3166 percent_brightness=geometry_info.rho;
3167 percent_saturation=geometry_info.sigma;
3168 if ((flags & SigmaValue) == 0)
3169 percent_saturation=100.0;
3170 percent_hue=geometry_info.xi;
3171 if ((flags & XiValue) == 0)
3173 colorspace=UndefinedColorspace;
3174 artifact=GetImageArtifact(image,"modulate:colorspace");
3175 if (artifact != (const char *) NULL)
3176 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3177 MagickFalse,artifact);
3178 if (image->storage_class == PseudoClass)
3179 for (i=0; i < (ssize_t) image->colors; i++)
3187 Modulate image colormap.
3189 red=(double) image->colormap[i].red;
3190 green=(double) image->colormap[i].green;
3191 blue=(double) image->colormap[i].blue;
3196 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3200 case HCLpColorspace:
3202 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3208 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3214 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3221 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3227 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3233 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3238 case LCHabColorspace:
3240 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3244 case LCHuvColorspace:
3246 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3251 image->colormap[i].red=red;
3252 image->colormap[i].green=green;
3253 image->colormap[i].blue=blue;
3260 image_view=AcquireAuthenticCacheView(image,exception);
3261 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3262 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3263 magick_threads(image,image,image->rows,1)
3265 for (y=0; y < (ssize_t) image->rows; y++)
3273 if (status == MagickFalse)
3275 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3276 if (q == (Quantum *) NULL)
3281 for (x=0; x < (ssize_t) image->columns; x++)
3288 red=(double) GetPixelRed(image,q);
3289 green=(double) GetPixelGreen(image,q);
3290 blue=(double) GetPixelBlue(image,q);
3295 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3299 case HCLpColorspace:
3301 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3307 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3314 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3320 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3326 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3330 case LCHabColorspace:
3332 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3337 case LCHuvColorspace:
3339 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3344 SetPixelRed(image,ClampToQuantum(red),q);
3345 SetPixelGreen(image,ClampToQuantum(green),q);
3346 SetPixelBlue(image,ClampToQuantum(blue),q);
3347 q+=GetPixelChannels(image);
3349 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3351 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3356 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3357 #pragma omp critical (MagickCore_ModulateImage)
3359 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3360 if( IfMagickFalse(proceed) )
3364 image_view=DestroyCacheView(image_view);
3369 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3373 % N e g a t e I m a g e %
3377 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3379 % NegateImage() negates the colors in the reference image. The grayscale
3380 % option means that only grayscale values within the image are negated.
3382 % The format of the NegateImage method is:
3384 % MagickBooleanType NegateImage(Image *image,
3385 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3387 % A description of each parameter follows:
3389 % o image: the image.
3391 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3393 % o exception: return any errors or warnings in this structure.
3396 MagickExport MagickBooleanType NegateImage(Image *image,
3397 const MagickBooleanType grayscale,ExceptionInfo *exception)
3399 #define NegateImageTag "Negate/Image"
3416 assert(image != (Image *) NULL);
3417 assert(image->signature == MagickCoreSignature);
3418 if (image->debug != MagickFalse)
3419 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3420 if (image->storage_class == PseudoClass)
3421 for (i=0; i < (ssize_t) image->colors; i++)
3426 if( IfMagickTrue(grayscale) )
3427 if ((image->colormap[i].red != image->colormap[i].green) ||
3428 (image->colormap[i].green != image->colormap[i].blue))
3430 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3431 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3432 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3433 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3434 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3435 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3442 image_view=AcquireAuthenticCacheView(image,exception);
3443 if( IfMagickTrue(grayscale) )
3445 for (y=0; y < (ssize_t) image->rows; y++)
3456 if (status == MagickFalse)
3458 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3460 if (q == (Quantum *) NULL)
3465 for (x=0; x < (ssize_t) image->columns; x++)
3470 if ((GetPixelReadMask(image,q) == 0) ||
3471 IfMagickTrue(IsPixelGray(image,q)))
3473 q+=GetPixelChannels(image);
3476 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3478 PixelChannel channel=GetPixelChannelChannel(image,j);
3479 PixelTrait traits=GetPixelChannelTraits(image,channel);
3480 if ((traits & UpdatePixelTrait) == 0)
3482 q[j]=QuantumRange-q[j];
3484 q+=GetPixelChannels(image);
3486 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3487 if( IfMagickFalse(sync) )
3489 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3494 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3495 #pragma omp critical (MagickCore_NegateImage)
3497 proceed=SetImageProgress(image,NegateImageTag,progress++,
3499 if( IfMagickFalse(proceed) )
3503 image_view=DestroyCacheView(image_view);
3509 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3510 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3511 magick_threads(image,image,image->rows,1)
3513 for (y=0; y < (ssize_t) image->rows; y++)
3521 if (status == MagickFalse)
3523 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3524 if (q == (Quantum *) NULL)
3529 for (x=0; x < (ssize_t) image->columns; x++)
3534 if (GetPixelReadMask(image,q) == 0)
3536 q+=GetPixelChannels(image);
3539 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3541 PixelChannel channel=GetPixelChannelChannel(image,j);
3542 PixelTrait traits=GetPixelChannelTraits(image,channel);
3543 if ((traits & UpdatePixelTrait) == 0)
3545 q[j]=QuantumRange-q[j];
3547 q+=GetPixelChannels(image);
3549 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3551 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3556 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3557 #pragma omp critical (MagickCore_NegateImage)
3559 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3560 if( IfMagickFalse(proceed) )
3564 image_view=DestroyCacheView(image_view);
3569 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3573 % N o r m a l i z e I m a g e %
3577 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3579 % The NormalizeImage() method enhances the contrast of a color image by
3580 % mapping the darkest 2 percent of all pixel to black and the brightest
3581 % 1 percent to white.
3583 % The format of the NormalizeImage method is:
3585 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3587 % A description of each parameter follows:
3589 % o image: the image.
3591 % o exception: return any errors or warnings in this structure.
3594 MagickExport MagickBooleanType NormalizeImage(Image *image,
3595 ExceptionInfo *exception)
3601 black_point=(double) image->columns*image->rows*0.0015;
3602 white_point=(double) image->columns*image->rows*0.9995;
3603 return(ContrastStretchImage(image,black_point,white_point,exception));
3607 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3611 % S i g m o i d a l C o n t r a s t I m a g e %
3615 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3617 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3618 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3619 % sigmoidal transfer function without saturating highlights or shadows.
3620 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3621 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3622 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3623 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3626 % The format of the SigmoidalContrastImage method is:
3628 % MagickBooleanType SigmoidalContrastImage(Image *image,
3629 % const MagickBooleanType sharpen,const char *levels,
3630 % ExceptionInfo *exception)
3632 % A description of each parameter follows:
3634 % o image: the image.
3636 % o sharpen: Increase or decrease image contrast.
3638 % o contrast: strength of the contrast, the larger the number the more
3639 % 'threshold-like' it becomes.
3641 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3643 % o exception: return any errors or warnings in this structure.
3648 ImageMagick 6 has a version of this function which uses LUTs.
3652 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3655 The first version, based on the hyperbolic tangent tanh, when combined with
3656 the scaling step, is an exact arithmetic clone of the the sigmoid function
3657 based on the logistic curve. The equivalence is based on the identity
3659 1/(1+exp(-t)) = (1+tanh(t/2))/2
3661 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3662 scaled sigmoidal derivation is invariant under affine transformations of
3665 The tanh version is almost certainly more accurate and cheaper. The 0.5
3666 factor in the argument is to clone the legacy ImageMagick behavior. The
3667 reason for making the define depend on atanh even though it only uses tanh
3668 has to do with the construction of the inverse of the scaled sigmoidal.
3670 #if defined(MAGICKCORE_HAVE_ATANH)
3671 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3673 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3676 Scaled sigmoidal function:
3678 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3679 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3681 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3682 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3683 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3684 zero. This is fixed below by exiting immediately when contrast is small,
3685 leaving the image (or colormap) unmodified. This appears to be safe because
3686 the series expansion of the logistic sigmoidal function around x=b is
3690 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3692 #define ScaledSigmoidal(a,b,x) ( \
3693 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3694 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3696 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3697 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3698 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3699 when creating a LUT from in gamut values, hence the branching. In
3700 addition, HDRI may have out of gamut values.
3701 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3702 It is only a right inverse. This is unavoidable.
3704 static inline double InverseScaledSigmoidal(const double a,const double b,
3707 const double sig0=Sigmoidal(a,b,0.0);
3708 const double sig1=Sigmoidal(a,b,1.0);
3709 const double argument=(sig1-sig0)*x+sig0;
3710 const double clamped=
3712 #if defined(MAGICKCORE_HAVE_ATANH)
3713 argument < -1+MagickEpsilon
3717 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3719 return(b+(2.0/a)*atanh(clamped));
3721 argument < MagickEpsilon
3725 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3727 return(b-log(1.0/clamped-1.0)/a);
3731 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3732 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3733 ExceptionInfo *exception)
3735 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3736 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3737 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3738 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3739 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3756 assert(image != (Image *) NULL);
3757 assert(image->signature == MagickCoreSignature);
3758 if (image->debug != MagickFalse)
3759 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3761 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3762 case nothing is done.
3764 if (contrast < MagickEpsilon)
3767 Sigmoidal-contrast enhance colormap.
3769 if (image->storage_class == PseudoClass)
3774 if( IfMagickTrue(sharpen) )
3775 for (i=0; i < (ssize_t) image->colors; i++)
3777 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3778 image->colormap[i].red=(MagickRealType) ScaledSig(
3779 image->colormap[i].red);
3780 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3781 image->colormap[i].green=(MagickRealType) ScaledSig(
3782 image->colormap[i].green);
3783 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3784 image->colormap[i].blue=(MagickRealType) ScaledSig(
3785 image->colormap[i].blue);
3786 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3787 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3788 image->colormap[i].alpha);
3791 for (i=0; i < (ssize_t) image->colors; i++)
3793 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3794 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3795 image->colormap[i].red);
3796 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3797 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3798 image->colormap[i].green);
3799 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3800 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3801 image->colormap[i].blue);
3802 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3803 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3804 image->colormap[i].alpha);
3808 Sigmoidal-contrast enhance image.
3812 image_view=AcquireAuthenticCacheView(image,exception);
3813 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3814 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3815 magick_threads(image,image,image->rows,1)
3817 for (y=0; y < (ssize_t) image->rows; y++)
3825 if (status == MagickFalse)
3827 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3828 if (q == (Quantum *) NULL)
3833 for (x=0; x < (ssize_t) image->columns; x++)
3838 if (GetPixelReadMask(image,q) == 0)
3840 q+=GetPixelChannels(image);
3843 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3845 PixelChannel channel=GetPixelChannelChannel(image,i);
3846 PixelTrait traits=GetPixelChannelTraits(image,channel);
3847 if ((traits & UpdatePixelTrait) == 0)
3849 if( IfMagickTrue(sharpen) )
3850 q[i]=ScaledSig(q[i]);
3852 q[i]=InverseScaledSig(q[i]);
3854 q+=GetPixelChannels(image);
3856 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3858 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3863 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3864 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3866 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3868 if( IfMagickFalse(proceed) )
3872 image_view=DestroyCacheView(image_view);