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-2015 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++)
1098 pixel=GetPixelIntensity(image,p);
1099 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1101 if (image->channel_mask != DefaultChannels)
1102 pixel=(double) p[i];
1103 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1104 ClampToQuantum(pixel))+i]++;
1106 p+=GetPixelChannels(image);
1109 image_view=DestroyCacheView(image_view);
1111 Find the histogram boundaries by locating the black/white levels.
1113 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1122 white[i]=MaxRange(QuantumRange);
1124 for (j=0; j <= (ssize_t) MaxMap; j++)
1126 intensity+=histogram[GetPixelChannels(image)*j+i];
1127 if (intensity > black_point)
1130 black[i]=(double) j;
1132 for (j=(ssize_t) MaxMap; j != 0; j--)
1134 intensity+=histogram[GetPixelChannels(image)*j+i];
1135 if (intensity > ((double) image->columns*image->rows-white_point))
1138 white[i]=(double) j;
1140 histogram=(double *) RelinquishMagickMemory(histogram);
1142 Stretch the histogram to create the stretched image mapping.
1144 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1145 sizeof(*stretch_map));
1146 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1151 for (j=0; j <= (ssize_t) MaxMap; j++)
1156 gamma=PerceptibleReciprocal(white[i]-black[i]);
1157 if (j < (ssize_t) black[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1160 if (j > (ssize_t) white[i])
1161 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1163 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1164 (double) (MaxMap*gamma*(j-black[i])));
1167 if (image->storage_class == PseudoClass)
1173 Stretch-contrast colormap.
1175 for (j=0; j < (ssize_t) image->colors; j++)
1177 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1179 i=GetPixelChannelOffset(image,RedPixelChannel);
1180 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1183 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelOffset(image,GreenPixelChannel);
1186 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1189 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1191 i=GetPixelChannelOffset(image,BluePixelChannel);
1192 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1193 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1195 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1197 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1198 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1199 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1204 Stretch-contrast image.
1208 image_view=AcquireAuthenticCacheView(image,exception);
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1211 magick_threads(image,image,image->rows,1)
1213 for (y=0; y < (ssize_t) image->rows; y++)
1221 if (status == MagickFalse)
1223 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1224 if (q == (Quantum *) NULL)
1229 for (x=0; x < (ssize_t) image->columns; x++)
1234 if (GetPixelReadMask(image,q) == 0)
1236 q+=GetPixelChannels(image);
1239 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1241 PixelChannel channel=GetPixelChannelChannel(image,i);
1242 PixelTrait traits=GetPixelChannelTraits(image,channel);
1243 if ((traits & UpdatePixelTrait) == 0)
1245 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1246 ScaleQuantumToMap(q[i])+i]);
1248 q+=GetPixelChannels(image);
1250 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1252 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1258 #pragma omp critical (MagickCore_ContrastStretchImage)
1260 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1262 if (proceed == MagickFalse)
1266 image_view=DestroyCacheView(image_view);
1267 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1268 white=(double *) RelinquishMagickMemory(white);
1269 black=(double *) RelinquishMagickMemory(black);
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1278 % E n h a n c e I m a g e %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284 % EnhanceImage() applies a digital filter that improves the quality of a
1287 % The format of the EnhanceImage method is:
1289 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1291 % A description of each parameter follows:
1293 % o image: the image.
1295 % o exception: return any errors or warnings in this structure.
1298 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1300 #define EnhanceImageTag "Enhance/Image"
1301 #define EnhancePixel(weight) \
1302 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1303 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1304 distance_squared=(4.0+mean)*distance*distance; \
1305 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1306 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1307 distance_squared+=(7.0-mean)*distance*distance; \
1308 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1309 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1310 distance_squared+=(5.0-mean)*distance*distance; \
1311 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1312 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1313 distance_squared+=(5.0-mean)*distance*distance; \
1314 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1315 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1316 distance_squared+=(5.0-mean)*distance*distance; \
1317 if (distance_squared < 0.069) \
1319 aggregate.red+=(weight)*GetPixelRed(image,r); \
1320 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1321 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1322 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1323 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1324 total_weight+=(weight); \
1326 r+=GetPixelChannels(image);
1345 Initialize enhanced image attributes.
1347 assert(image != (const Image *) NULL);
1348 assert(image->signature == MagickCoreSignature);
1349 if (image->debug != MagickFalse)
1350 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1351 assert(exception != (ExceptionInfo *) NULL);
1352 assert(exception->signature == MagickCoreSignature);
1353 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1355 if (enhance_image == (Image *) NULL)
1356 return((Image *) NULL);
1357 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1359 enhance_image=DestroyImage(enhance_image);
1360 return((Image *) NULL);
1367 image_view=AcquireVirtualCacheView(image,exception);
1368 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1370 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1371 magick_threads(image,enhance_image,image->rows,1)
1373 for (y=0; y < (ssize_t) image->rows; y++)
1378 register const Quantum
1390 if (status == MagickFalse)
1392 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1393 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1395 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1400 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1401 GetPixelInfo(image,&pixel);
1402 for (x=0; x < (ssize_t) image->columns; x++)
1413 register const Quantum
1416 if (GetPixelReadMask(image,p) == 0)
1418 SetPixelBackgoundColor(enhance_image,q);
1419 p+=GetPixelChannels(image);
1420 q+=GetPixelChannels(enhance_image);
1423 GetPixelInfo(image,&aggregate);
1425 GetPixelInfoPixel(image,p+center,&pixel);
1427 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1428 EnhancePixel(8.0); EnhancePixel(5.0);
1429 r=p+GetPixelChannels(image)*(image->columns+4);
1430 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1431 EnhancePixel(20.0); EnhancePixel(8.0);
1432 r=p+2*GetPixelChannels(image)*(image->columns+4);
1433 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1434 EnhancePixel(40.0); EnhancePixel(10.0);
1435 r=p+3*GetPixelChannels(image)*(image->columns+4);
1436 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1437 EnhancePixel(20.0); EnhancePixel(8.0);
1438 r=p+4*GetPixelChannels(image)*(image->columns+4);
1439 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1440 EnhancePixel(8.0); EnhancePixel(5.0);
1441 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1442 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1443 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1444 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1445 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1446 SetPixelViaPixelInfo(image,&pixel,q);
1447 p+=GetPixelChannels(image);
1448 q+=GetPixelChannels(enhance_image);
1450 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1452 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1458 #pragma omp critical (MagickCore_EnhanceImage)
1460 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1461 if (proceed == MagickFalse)
1465 enhance_view=DestroyCacheView(enhance_view);
1466 image_view=DestroyCacheView(image_view);
1467 if (status == MagickFalse)
1468 enhance_image=DestroyImage(enhance_image);
1469 return(enhance_image);
1473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477 % E q u a l i z e I m a g e %
1481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483 % EqualizeImage() applies a histogram equalization to the image.
1485 % The format of the EqualizeImage method is:
1487 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1489 % A description of each parameter follows:
1491 % o image: the image.
1493 % o exception: return any errors or warnings in this structure.
1496 MagickExport MagickBooleanType EqualizeImage(Image *image,
1497 ExceptionInfo *exception)
1499 #define EqualizeImageTag "Equalize/Image"
1511 black[CompositePixelChannel+1],
1515 white[CompositePixelChannel+1];
1524 Allocate and initialize histogram arrays.
1526 assert(image != (Image *) NULL);
1527 assert(image->signature == MagickCoreSignature);
1528 if (image->debug != MagickFalse)
1529 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1530 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1531 GetPixelChannels(image)*sizeof(*equalize_map));
1532 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1533 sizeof(*histogram));
1534 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1536 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1537 (map == (double *) NULL))
1539 if (map != (double *) NULL)
1540 map=(double *) RelinquishMagickMemory(map);
1541 if (histogram != (double *) NULL)
1542 histogram=(double *) RelinquishMagickMemory(histogram);
1543 if (equalize_map != (double *) NULL)
1544 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1545 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1552 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1553 sizeof(*histogram));
1554 image_view=AcquireVirtualCacheView(image,exception);
1555 for (y=0; y < (ssize_t) image->rows; y++)
1557 register const Quantum
1563 if (status == MagickFalse)
1565 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1566 if (p == (const Quantum *) NULL)
1571 for (x=0; x < (ssize_t) image->columns; x++)
1576 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1577 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1578 p+=GetPixelChannels(image);
1581 image_view=DestroyCacheView(image_view);
1583 Integrate the histogram to get the equalization map.
1585 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1594 for (j=0; j <= (ssize_t) MaxMap; j++)
1596 intensity+=histogram[GetPixelChannels(image)*j+i];
1597 map[GetPixelChannels(image)*j+i]=intensity;
1600 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1601 sizeof(*equalize_map));
1602 (void) ResetMagickMemory(black,0,sizeof(*black));
1603 (void) ResetMagickMemory(white,0,sizeof(*white));
1604 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1610 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1611 if (black[i] != white[i])
1612 for (j=0; j <= (ssize_t) MaxMap; j++)
1613 equalize_map[GetPixelChannels(image)*j+i]=(double)
1614 ScaleMapToQuantum((double) ((MaxMap*(map[
1615 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1617 histogram=(double *) RelinquishMagickMemory(histogram);
1618 map=(double *) RelinquishMagickMemory(map);
1619 if (image->storage_class == PseudoClass)
1627 for (j=0; j < (ssize_t) image->colors; j++)
1629 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1631 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1632 if (black[channel] != white[channel])
1633 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1634 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1637 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1639 PixelChannel channel=GetPixelChannelChannel(image,
1641 if (black[channel] != white[channel])
1642 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1643 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1646 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1648 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1649 if (black[channel] != white[channel])
1650 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1651 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1654 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1656 PixelChannel channel=GetPixelChannelChannel(image,
1658 if (black[channel] != white[channel])
1659 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1660 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1669 image_view=AcquireAuthenticCacheView(image,exception);
1670 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1671 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1672 magick_threads(image,image,image->rows,1)
1674 for (y=0; y < (ssize_t) image->rows; y++)
1682 if (status == MagickFalse)
1684 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1685 if (q == (Quantum *) NULL)
1690 for (x=0; x < (ssize_t) image->columns; x++)
1695 if (GetPixelReadMask(image,q) == 0)
1697 q+=GetPixelChannels(image);
1700 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1702 PixelChannel channel=GetPixelChannelChannel(image,i);
1703 PixelTrait traits=GetPixelChannelTraits(image,channel);
1704 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1706 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1707 ScaleQuantumToMap(q[i])+i]);
1709 q+=GetPixelChannels(image);
1711 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1713 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1718 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1719 #pragma omp critical (MagickCore_EqualizeImage)
1721 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1722 if( IfMagickFalse(proceed) )
1726 image_view=DestroyCacheView(image_view);
1727 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1732 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1736 % G a m m a I m a g e %
1740 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1742 % GammaImage() gamma-corrects a particular image channel. The same
1743 % image viewed on different devices will have perceptual differences in the
1744 % way the image's intensities are represented on the screen. Specify
1745 % individual gamma levels for the red, green, and blue channels, or adjust
1746 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1748 % You can also reduce the influence of a particular channel with a gamma
1751 % The format of the GammaImage method is:
1753 % MagickBooleanType GammaImage(Image *image,const double gamma,
1754 % ExceptionInfo *exception)
1756 % A description of each parameter follows:
1758 % o image: the image.
1760 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1762 % o gamma: the image gamma.
1766 static inline double gamma_pow(const double value,const double gamma)
1768 return(value < 0.0 ? value : pow(value,gamma));
1771 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1772 ExceptionInfo *exception)
1774 #define GammaCorrectImageTag "GammaCorrect/Image"
1795 Allocate and initialize gamma maps.
1797 assert(image != (Image *) NULL);
1798 assert(image->signature == MagickCoreSignature);
1799 if (image->debug != MagickFalse)
1800 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1803 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1804 if (gamma_map == (Quantum *) NULL)
1805 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1807 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1809 for (i=0; i <= (ssize_t) MaxMap; i++)
1810 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1811 MaxMap,1.0/gamma)));
1812 if (image->storage_class == PseudoClass)
1813 for (i=0; i < (ssize_t) image->colors; i++)
1816 Gamma-correct colormap.
1818 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1819 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1820 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1821 ClampToQuantum(image->colormap[i].red))];
1822 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1823 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1824 ClampToQuantum(image->colormap[i].green))];
1825 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1826 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1827 ClampToQuantum(image->colormap[i].blue))];
1828 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1829 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1830 ClampToQuantum(image->colormap[i].alpha))];
1832 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1833 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1834 image->colormap[i].red,1.0/gamma);
1835 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1836 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1837 image->colormap[i].green,1.0/gamma);
1838 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1839 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1840 image->colormap[i].blue,1.0/gamma);
1841 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1842 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1843 image->colormap[i].alpha,1.0/gamma);
1847 Gamma-correct image.
1851 image_view=AcquireAuthenticCacheView(image,exception);
1852 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1853 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1854 magick_threads(image,image,image->rows,1)
1856 for (y=0; y < (ssize_t) image->rows; y++)
1864 if (status == MagickFalse)
1866 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1867 if (q == (Quantum *) NULL)
1872 for (x=0; x < (ssize_t) image->columns; x++)
1877 if (GetPixelReadMask(image,q) == 0)
1879 q+=GetPixelChannels(image);
1882 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1884 PixelChannel channel=GetPixelChannelChannel(image,i);
1885 PixelTrait traits=GetPixelChannelTraits(image,channel);
1886 if ((traits & UpdatePixelTrait) == 0)
1888 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1889 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1891 q[i]=QuantumRange*gamma_pow(QuantumScale*q[i],1.0/gamma);
1894 q+=GetPixelChannels(image);
1896 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
1898 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1903 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1904 #pragma omp critical (MagickCore_GammaImage)
1906 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1908 if( IfMagickFalse(proceed) )
1912 image_view=DestroyCacheView(image_view);
1913 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1914 if (image->gamma != 0.0)
1915 image->gamma*=gamma;
1920 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1924 % G r a y s c a l e I m a g e %
1928 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1930 % GrayscaleImage() converts the image to grayscale.
1932 % The format of the GrayscaleImage method is:
1934 % MagickBooleanType GrayscaleImage(Image *image,
1935 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1937 % A description of each parameter follows:
1939 % o image: the image.
1941 % o method: the pixel intensity method.
1943 % o exception: return any errors or warnings in this structure.
1946 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1947 const PixelIntensityMethod method,ExceptionInfo *exception)
1949 #define GrayscaleImageTag "Grayscale/Image"
1963 assert(image != (Image *) NULL);
1964 assert(image->signature == MagickCoreSignature);
1965 if (image->debug != MagickFalse)
1966 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1967 if (image->storage_class == PseudoClass)
1969 if( IfMagickFalse(SyncImage(image,exception)) )
1970 return(MagickFalse);
1971 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
1972 return(MagickFalse);
1979 image_view=AcquireAuthenticCacheView(image,exception);
1980 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1981 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1982 magick_threads(image,image,image->rows,1)
1984 for (y=0; y < (ssize_t) image->rows; y++)
1992 if (status == MagickFalse)
1994 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1995 if (q == (Quantum *) NULL)
2000 for (x=0; x < (ssize_t) image->columns; x++)
2008 if (GetPixelReadMask(image,q) == 0)
2010 q+=GetPixelChannels(image);
2013 red=(MagickRealType) GetPixelRed(image,q);
2014 green=(MagickRealType) GetPixelGreen(image,q);
2015 blue=(MagickRealType) GetPixelBlue(image,q);
2019 case AveragePixelIntensityMethod:
2021 intensity=(red+green+blue)/3.0;
2024 case BrightnessPixelIntensityMethod:
2026 intensity=MagickMax(MagickMax(red,green),blue);
2029 case LightnessPixelIntensityMethod:
2031 intensity=(MagickMin(MagickMin(red,green),blue)+
2032 MagickMax(MagickMax(red,green),blue))/2.0;
2035 case MSPixelIntensityMethod:
2037 intensity=(MagickRealType) (((double) red*red+green*green+
2041 case Rec601LumaPixelIntensityMethod:
2043 if (image->colorspace == RGBColorspace)
2045 red=EncodePixelGamma(red);
2046 green=EncodePixelGamma(green);
2047 blue=EncodePixelGamma(blue);
2049 intensity=0.298839*red+0.586811*green+0.114350*blue;
2052 case Rec601LuminancePixelIntensityMethod:
2054 if (image->colorspace == sRGBColorspace)
2056 red=DecodePixelGamma(red);
2057 green=DecodePixelGamma(green);
2058 blue=DecodePixelGamma(blue);
2060 intensity=0.298839*red+0.586811*green+0.114350*blue;
2063 case Rec709LumaPixelIntensityMethod:
2066 if (image->colorspace == RGBColorspace)
2068 red=EncodePixelGamma(red);
2069 green=EncodePixelGamma(green);
2070 blue=EncodePixelGamma(blue);
2072 intensity=0.212656*red+0.715158*green+0.072186*blue;
2075 case Rec709LuminancePixelIntensityMethod:
2077 if (image->colorspace == sRGBColorspace)
2079 red=DecodePixelGamma(red);
2080 green=DecodePixelGamma(green);
2081 blue=DecodePixelGamma(blue);
2083 intensity=0.212656*red+0.715158*green+0.072186*blue;
2086 case RMSPixelIntensityMethod:
2088 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2089 blue*blue)/sqrt(3.0));
2093 SetPixelGray(image,ClampToQuantum(intensity),q);
2094 q+=GetPixelChannels(image);
2096 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
2098 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2103 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2104 #pragma omp critical (MagickCore_GrayscaleImage)
2106 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2108 if( IfMagickFalse(proceed) )
2112 image_view=DestroyCacheView(image_view);
2113 image->intensity=method;
2114 image->type=GrayscaleType;
2115 return(SetImageColorspace(image,GRAYColorspace,exception));
2119 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2123 % H a l d C l u t I m a g e %
2127 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2129 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2130 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2131 % Create it with the HALD coder. You can apply any color transformation to
2132 % the Hald image and then use this method to apply the transform to the
2135 % The format of the HaldClutImage method is:
2137 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2138 % ExceptionInfo *exception)
2140 % A description of each parameter follows:
2142 % o image: the image, which is replaced by indexed CLUT values
2144 % o hald_image: the color lookup table image for replacement color values.
2146 % o exception: return any errors or warnings in this structure.
2149 MagickExport MagickBooleanType HaldClutImage(Image *image,
2150 const Image *hald_image,ExceptionInfo *exception)
2152 #define HaldClutImageTag "Clut/Image"
2154 typedef struct _HaldInfo
2186 assert(image != (Image *) NULL);
2187 assert(image->signature == MagickCoreSignature);
2188 if (image->debug != MagickFalse)
2189 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2190 assert(hald_image != (Image *) NULL);
2191 assert(hald_image->signature == MagickCoreSignature);
2192 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
2193 return(MagickFalse);
2194 if (image->alpha_trait == UndefinedPixelTrait)
2195 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2201 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2202 (MagickRealType) hald_image->rows);
2203 for (level=2; (level*level*level) < length; level++) ;
2205 cube_size=level*level;
2206 width=(double) hald_image->columns;
2207 GetPixelInfo(hald_image,&zero);
2208 hald_view=AcquireVirtualCacheView(hald_image,exception);
2209 image_view=AcquireAuthenticCacheView(image,exception);
2210 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2211 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2212 magick_threads(image,image,image->rows,1)
2214 for (y=0; y < (ssize_t) image->rows; y++)
2222 if (status == MagickFalse)
2224 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2225 if (q == (Quantum *) NULL)
2230 for (x=0; x < (ssize_t) image->columns; x++)
2245 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2246 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2247 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2248 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2249 point.x-=floor(point.x);
2250 point.y-=floor(point.y);
2251 point.z-=floor(point.z);
2253 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2254 fmod(offset,width),floor(offset/width),&pixel1,exception);
2256 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2257 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2259 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2262 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2263 fmod(offset,width),floor(offset/width),&pixel1,exception);
2264 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2265 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2267 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2270 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2272 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2273 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2274 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2275 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2276 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2277 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2278 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2279 (image->colorspace == CMYKColorspace))
2280 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2281 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2282 (image->alpha_trait != UndefinedPixelTrait))
2283 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2284 q+=GetPixelChannels(image);
2286 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2288 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2293 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2294 #pragma omp critical (MagickCore_HaldClutImage)
2296 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2297 if( IfMagickFalse(proceed) )
2301 hald_view=DestroyCacheView(hald_view);
2302 image_view=DestroyCacheView(image_view);
2307 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2311 % L e v e l I m a g e %
2315 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2317 % LevelImage() adjusts the levels of a particular image channel by
2318 % scaling the colors falling between specified white and black points to
2319 % the full available quantum range.
2321 % The parameters provided represent the black, and white points. The black
2322 % point specifies the darkest color in the image. Colors darker than the
2323 % black point are set to zero. White point specifies the lightest color in
2324 % the image. Colors brighter than the white point are set to the maximum
2327 % If a '!' flag is given, map black and white colors to the given levels
2328 % rather than mapping those levels to black and white. See
2329 % LevelizeImage() below.
2331 % Gamma specifies a gamma correction to apply to the image.
2333 % The format of the LevelImage method is:
2335 % MagickBooleanType LevelImage(Image *image,const double black_point,
2336 % const double white_point,const double gamma,ExceptionInfo *exception)
2338 % A description of each parameter follows:
2340 % o image: the image.
2342 % o black_point: The level to map zero (black) to.
2344 % o white_point: The level to map QuantumRange (white) to.
2346 % o exception: return any errors or warnings in this structure.
2350 static inline double LevelPixel(const double black_point,
2351 const double white_point,const double gamma,const double pixel)
2357 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2358 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2360 return(level_pixel);
2363 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2364 const double white_point,const double gamma,ExceptionInfo *exception)
2366 #define LevelImageTag "Level/Image"
2384 Allocate and initialize levels map.
2386 assert(image != (Image *) NULL);
2387 assert(image->signature == MagickCoreSignature);
2388 if (image->debug != MagickFalse)
2389 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2390 if (image->storage_class == PseudoClass)
2391 for (i=0; i < (ssize_t) image->colors; i++)
2396 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2397 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2398 white_point,gamma,image->colormap[i].red));
2399 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2400 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2401 white_point,gamma,image->colormap[i].green));
2402 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2403 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2404 white_point,gamma,image->colormap[i].blue));
2405 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2406 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2407 white_point,gamma,image->colormap[i].alpha));
2414 image_view=AcquireAuthenticCacheView(image,exception);
2415 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2416 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2417 magick_threads(image,image,image->rows,1)
2419 for (y=0; y < (ssize_t) image->rows; y++)
2427 if (status == MagickFalse)
2429 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2430 if (q == (Quantum *) NULL)
2435 for (x=0; x < (ssize_t) image->columns; x++)
2440 if (GetPixelReadMask(image,q) == 0)
2442 q+=GetPixelChannels(image);
2445 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2447 PixelChannel channel=GetPixelChannelChannel(image,i);
2448 PixelTrait traits=GetPixelChannelTraits(image,channel);
2449 if ((traits & UpdatePixelTrait) == 0)
2451 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2454 q+=GetPixelChannels(image);
2456 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2458 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2463 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2464 #pragma omp critical (MagickCore_LevelImage)
2466 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2467 if( IfMagickFalse(proceed) )
2471 image_view=DestroyCacheView(image_view);
2472 (void) ClampImage(image,exception);
2477 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2481 % L e v e l i z e I m a g e %
2485 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2487 % LevelizeImage() applies the reversed LevelImage() operation to just
2488 % the specific channels specified. It compresses the full range of color
2489 % values, so that they lie between the given black and white points. Gamma is
2490 % applied before the values are mapped.
2492 % LevelizeImage() can be called with by using a +level command line
2493 % API option, or using a '!' on a -level or LevelImage() geometry string.
2495 % It can be used to de-contrast a greyscale image to the exact levels
2496 % specified. Or by using specific levels for each channel of an image you
2497 % can convert a gray-scale image to any linear color gradient, according to
2500 % The format of the LevelizeImage method is:
2502 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2503 % const double white_point,const double gamma,ExceptionInfo *exception)
2505 % A description of each parameter follows:
2507 % o image: the image.
2509 % o black_point: The level to map zero (black) to.
2511 % o white_point: The level to map QuantumRange (white) to.
2513 % o gamma: adjust gamma by this factor before mapping values.
2515 % o exception: return any errors or warnings in this structure.
2518 MagickExport MagickBooleanType LevelizeImage(Image *image,
2519 const double black_point,const double white_point,const double gamma,
2520 ExceptionInfo *exception)
2522 #define LevelizeImageTag "Levelize/Image"
2523 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2524 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2542 Allocate and initialize levels map.
2544 assert(image != (Image *) NULL);
2545 assert(image->signature == MagickCoreSignature);
2546 if (image->debug != MagickFalse)
2547 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2548 if (image->storage_class == PseudoClass)
2549 for (i=0; i < (ssize_t) image->colors; i++)
2554 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2555 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2556 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2557 image->colormap[i].green=(double) LevelizeValue(
2558 image->colormap[i].green);
2559 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2560 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2561 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2562 image->colormap[i].alpha=(double) LevelizeValue(
2563 image->colormap[i].alpha);
2570 image_view=AcquireAuthenticCacheView(image,exception);
2571 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2572 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2573 magick_threads(image,image,image->rows,1)
2575 for (y=0; y < (ssize_t) image->rows; y++)
2583 if (status == MagickFalse)
2585 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2586 if (q == (Quantum *) NULL)
2591 for (x=0; x < (ssize_t) image->columns; x++)
2596 if (GetPixelReadMask(image,q) == 0)
2598 q+=GetPixelChannels(image);
2601 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2603 PixelChannel channel=GetPixelChannelChannel(image,i);
2604 PixelTrait traits=GetPixelChannelTraits(image,channel);
2605 if ((traits & UpdatePixelTrait) == 0)
2607 q[i]=LevelizeValue(q[i]);
2609 q+=GetPixelChannels(image);
2611 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
2613 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2618 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2619 #pragma omp critical (MagickCore_LevelizeImage)
2621 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2622 if( IfMagickFalse(proceed) )
2626 image_view=DestroyCacheView(image_view);
2631 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2635 % L e v e l I m a g e C o l o r s %
2639 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2641 % LevelImageColors() maps the given color to "black" and "white" values,
2642 % linearly spreading out the colors, and level values on a channel by channel
2643 % bases, as per LevelImage(). The given colors allows you to specify
2644 % different level ranges for each of the color channels separately.
2646 % If the boolean 'invert' is set true the image values will modifyed in the
2647 % reverse direction. That is any existing "black" and "white" colors in the
2648 % image will become the color values given, with all other values compressed
2649 % appropriatally. This effectivally maps a greyscale gradient into the given
2652 % The format of the LevelImageColors method is:
2654 % MagickBooleanType LevelImageColors(Image *image,
2655 % const PixelInfo *black_color,const PixelInfo *white_color,
2656 % const MagickBooleanType invert,ExceptionInfo *exception)
2658 % A description of each parameter follows:
2660 % o image: the image.
2662 % o black_color: The color to map black to/from
2664 % o white_point: The color to map white to/from
2666 % o invert: if true map the colors (levelize), rather than from (level)
2668 % o exception: return any errors or warnings in this structure.
2671 MagickExport MagickBooleanType LevelImageColors(Image *image,
2672 const PixelInfo *black_color,const PixelInfo *white_color,
2673 const MagickBooleanType invert,ExceptionInfo *exception)
2682 Allocate and initialize levels map.
2684 assert(image != (Image *) NULL);
2685 assert(image->signature == MagickCoreSignature);
2686 if (image->debug != MagickFalse)
2687 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2688 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2689 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2690 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
2691 (void) SetImageColorspace(image,sRGBColorspace,exception);
2693 if( IfMagickFalse(invert) )
2695 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2697 channel_mask=SetImageChannelMask(image,RedChannel);
2698 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2700 (void) SetImageChannelMask(image,channel_mask);
2702 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2704 channel_mask=SetImageChannelMask(image,GreenChannel);
2705 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2707 (void) SetImageChannelMask(image,channel_mask);
2709 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2711 channel_mask=SetImageChannelMask(image,BlueChannel);
2712 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2714 (void) SetImageChannelMask(image,channel_mask);
2716 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2717 (image->colorspace == CMYKColorspace))
2719 channel_mask=SetImageChannelMask(image,BlackChannel);
2720 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2722 (void) SetImageChannelMask(image,channel_mask);
2724 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2725 (image->alpha_trait != UndefinedPixelTrait))
2727 channel_mask=SetImageChannelMask(image,AlphaChannel);
2728 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2730 (void) SetImageChannelMask(image,channel_mask);
2735 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2737 channel_mask=SetImageChannelMask(image,RedChannel);
2738 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2740 (void) SetImageChannelMask(image,channel_mask);
2742 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2744 channel_mask=SetImageChannelMask(image,GreenChannel);
2745 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2747 (void) SetImageChannelMask(image,channel_mask);
2749 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2751 channel_mask=SetImageChannelMask(image,BlueChannel);
2752 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2754 (void) SetImageChannelMask(image,channel_mask);
2756 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2757 (image->colorspace == CMYKColorspace))
2759 channel_mask=SetImageChannelMask(image,BlackChannel);
2760 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2762 (void) SetImageChannelMask(image,channel_mask);
2764 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2765 (image->alpha_trait != UndefinedPixelTrait))
2767 channel_mask=SetImageChannelMask(image,AlphaChannel);
2768 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2770 (void) SetImageChannelMask(image,channel_mask);
2773 return(status != 0 ? MagickTrue : MagickFalse);
2777 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2781 % L i n e a r S t r e t c h I m a g e %
2785 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2787 % LinearStretchImage() discards any pixels below the black point and above
2788 % the white point and levels the remaining pixels.
2790 % The format of the LinearStretchImage method is:
2792 % MagickBooleanType LinearStretchImage(Image *image,
2793 % const double black_point,const double white_point,
2794 % ExceptionInfo *exception)
2796 % A description of each parameter follows:
2798 % o image: the image.
2800 % o black_point: the black point.
2802 % o white_point: the white point.
2804 % o exception: return any errors or warnings in this structure.
2807 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2808 const double black_point,const double white_point,ExceptionInfo *exception)
2810 #define LinearStretchImageTag "LinearStretch/Image"
2828 Allocate histogram and linear map.
2830 assert(image != (Image *) NULL);
2831 assert(image->signature == MagickCoreSignature);
2832 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2833 if (histogram == (double *) NULL)
2834 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2839 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2840 image_view=AcquireVirtualCacheView(image,exception);
2841 for (y=0; y < (ssize_t) image->rows; y++)
2843 register const Quantum
2849 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2850 if (p == (const Quantum *) NULL)
2852 for (x=0; x < (ssize_t) image->columns; x++)
2857 intensity=GetPixelIntensity(image,p);
2858 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2859 p+=GetPixelChannels(image);
2862 image_view=DestroyCacheView(image_view);
2864 Find the histogram boundaries by locating the black and white point levels.
2867 for (black=0; black < (ssize_t) MaxMap; black++)
2869 intensity+=histogram[black];
2870 if (intensity >= black_point)
2874 for (white=(ssize_t) MaxMap; white != 0; white--)
2876 intensity+=histogram[white];
2877 if (intensity >= white_point)
2880 histogram=(double *) RelinquishMagickMemory(histogram);
2881 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2882 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2887 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2891 % M o d u l a t e I m a g e %
2895 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2897 % ModulateImage() lets you control the brightness, saturation, and hue
2898 % of an image. Modulate represents the brightness, saturation, and hue
2899 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2900 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2901 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2903 % The format of the ModulateImage method is:
2905 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2906 % ExceptionInfo *exception)
2908 % A description of each parameter follows:
2910 % o image: the image.
2912 % o modulate: Define the percent change in brightness, saturation, and hue.
2914 % o exception: return any errors or warnings in this structure.
2918 static inline void ModulateHCL(const double percent_hue,
2919 const double percent_chroma,const double percent_luma,double *red,
2920 double *green,double *blue)
2928 Increase or decrease color luma, chroma, or hue.
2930 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2931 hue+=0.5*(0.01*percent_hue-1.0);
2936 chroma*=0.01*percent_chroma;
2937 luma*=0.01*percent_luma;
2938 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2941 static inline void ModulateHCLp(const double percent_hue,
2942 const double percent_chroma,const double percent_luma,double *red,
2943 double *green,double *blue)
2951 Increase or decrease color luma, chroma, or hue.
2953 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2954 hue+=0.5*(0.01*percent_hue-1.0);
2959 chroma*=0.01*percent_chroma;
2960 luma*=0.01*percent_luma;
2961 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2964 static inline void ModulateHSB(const double percent_hue,
2965 const double percent_saturation,const double percent_brightness,double *red,
2966 double *green,double *blue)
2974 Increase or decrease color brightness, saturation, or hue.
2976 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2977 hue+=0.5*(0.01*percent_hue-1.0);
2982 saturation*=0.01*percent_saturation;
2983 brightness*=0.01*percent_brightness;
2984 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2987 static inline void ModulateHSI(const double percent_hue,
2988 const double percent_saturation,const double percent_intensity,double *red,
2989 double *green,double *blue)
2997 Increase or decrease color intensity, saturation, or hue.
2999 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3000 hue+=0.5*(0.01*percent_hue-1.0);
3005 saturation*=0.01*percent_saturation;
3006 intensity*=0.01*percent_intensity;
3007 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3010 static inline void ModulateHSL(const double percent_hue,
3011 const double percent_saturation,const double percent_lightness,double *red,
3012 double *green,double *blue)
3020 Increase or decrease color lightness, saturation, or hue.
3022 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3023 hue+=0.5*(0.01*percent_hue-1.0);
3028 saturation*=0.01*percent_saturation;
3029 lightness*=0.01*percent_lightness;
3030 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3033 static inline void ModulateHSV(const double percent_hue,
3034 const double percent_saturation,const double percent_value,double *red,
3035 double *green,double *blue)
3043 Increase or decrease color value, saturation, or hue.
3045 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3046 hue+=0.5*(0.01*percent_hue-1.0);
3051 saturation*=0.01*percent_saturation;
3052 value*=0.01*percent_value;
3053 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3056 static inline void ModulateHWB(const double percent_hue,
3057 const double percent_whiteness,const double percent_blackness,double *red,
3058 double *green,double *blue)
3066 Increase or decrease color blackness, whiteness, or hue.
3068 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3069 hue+=0.5*(0.01*percent_hue-1.0);
3074 blackness*=0.01*percent_blackness;
3075 whiteness*=0.01*percent_whiteness;
3076 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3079 static inline void ModulateLCHab(const double percent_luma,
3080 const double percent_chroma,const double percent_hue,double *red,
3081 double *green,double *blue)
3089 Increase or decrease color luma, chroma, or hue.
3091 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3092 luma*=0.01*percent_luma;
3093 chroma*=0.01*percent_chroma;
3094 hue+=0.5*(0.01*percent_hue-1.0);
3099 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3102 static inline void ModulateLCHuv(const double percent_luma,
3103 const double percent_chroma,const double percent_hue,double *red,
3104 double *green,double *blue)
3112 Increase or decrease color luma, chroma, or hue.
3114 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3115 luma*=0.01*percent_luma;
3116 chroma*=0.01*percent_chroma;
3117 hue+=0.5*(0.01*percent_hue-1.0);
3122 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3125 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3126 ExceptionInfo *exception)
3128 #define ModulateImageTag "Modulate/Image"
3163 Initialize modulate table.
3165 assert(image != (Image *) NULL);
3166 assert(image->signature == MagickCoreSignature);
3167 if (image->debug != MagickFalse)
3168 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3169 if (modulate == (char *) NULL)
3170 return(MagickFalse);
3171 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
3172 (void) SetImageColorspace(image,sRGBColorspace,exception);
3173 flags=ParseGeometry(modulate,&geometry_info);
3174 percent_brightness=geometry_info.rho;
3175 percent_saturation=geometry_info.sigma;
3176 if ((flags & SigmaValue) == 0)
3177 percent_saturation=100.0;
3178 percent_hue=geometry_info.xi;
3179 if ((flags & XiValue) == 0)
3181 colorspace=UndefinedColorspace;
3182 artifact=GetImageArtifact(image,"modulate:colorspace");
3183 if (artifact != (const char *) NULL)
3184 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3185 MagickFalse,artifact);
3186 if (image->storage_class == PseudoClass)
3187 for (i=0; i < (ssize_t) image->colors; i++)
3195 Modulate image colormap.
3197 red=(double) image->colormap[i].red;
3198 green=(double) image->colormap[i].green;
3199 blue=(double) image->colormap[i].blue;
3204 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3208 case HCLpColorspace:
3210 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3216 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3222 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3229 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3235 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3241 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3246 case LCHabColorspace:
3248 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3252 case LCHuvColorspace:
3254 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3259 image->colormap[i].red=red;
3260 image->colormap[i].green=green;
3261 image->colormap[i].blue=blue;
3268 image_view=AcquireAuthenticCacheView(image,exception);
3269 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3270 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3271 magick_threads(image,image,image->rows,1)
3273 for (y=0; y < (ssize_t) image->rows; y++)
3281 if (status == MagickFalse)
3283 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3284 if (q == (Quantum *) NULL)
3289 for (x=0; x < (ssize_t) image->columns; x++)
3296 red=(double) GetPixelRed(image,q);
3297 green=(double) GetPixelGreen(image,q);
3298 blue=(double) GetPixelBlue(image,q);
3303 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3307 case HCLpColorspace:
3309 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3315 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3322 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3328 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3334 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3338 case LCHabColorspace:
3340 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3345 case LCHuvColorspace:
3347 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3352 SetPixelRed(image,ClampToQuantum(red),q);
3353 SetPixelGreen(image,ClampToQuantum(green),q);
3354 SetPixelBlue(image,ClampToQuantum(blue),q);
3355 q+=GetPixelChannels(image);
3357 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3359 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3364 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3365 #pragma omp critical (MagickCore_ModulateImage)
3367 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3368 if( IfMagickFalse(proceed) )
3372 image_view=DestroyCacheView(image_view);
3377 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3381 % N e g a t e I m a g e %
3385 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3387 % NegateImage() negates the colors in the reference image. The grayscale
3388 % option means that only grayscale values within the image are negated.
3390 % The format of the NegateImage method is:
3392 % MagickBooleanType NegateImage(Image *image,
3393 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3395 % A description of each parameter follows:
3397 % o image: the image.
3399 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3401 % o exception: return any errors or warnings in this structure.
3404 MagickExport MagickBooleanType NegateImage(Image *image,
3405 const MagickBooleanType grayscale,ExceptionInfo *exception)
3407 #define NegateImageTag "Negate/Image"
3424 assert(image != (Image *) NULL);
3425 assert(image->signature == MagickCoreSignature);
3426 if (image->debug != MagickFalse)
3427 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3428 if (image->storage_class == PseudoClass)
3429 for (i=0; i < (ssize_t) image->colors; i++)
3434 if( IfMagickTrue(grayscale) )
3435 if ((image->colormap[i].red != image->colormap[i].green) ||
3436 (image->colormap[i].green != image->colormap[i].blue))
3438 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3439 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3440 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3441 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3442 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3443 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3450 image_view=AcquireAuthenticCacheView(image,exception);
3451 if( IfMagickTrue(grayscale) )
3453 for (y=0; y < (ssize_t) image->rows; y++)
3464 if (status == MagickFalse)
3466 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3468 if (q == (Quantum *) NULL)
3473 for (x=0; x < (ssize_t) image->columns; x++)
3478 if ((GetPixelReadMask(image,q) == 0) ||
3479 IfMagickTrue(IsPixelGray(image,q)))
3481 q+=GetPixelChannels(image);
3484 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3486 PixelChannel channel=GetPixelChannelChannel(image,i);
3487 PixelTrait traits=GetPixelChannelTraits(image,channel);
3488 if ((traits & UpdatePixelTrait) == 0)
3490 q[i]=QuantumRange-q[i];
3492 q+=GetPixelChannels(image);
3494 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3495 if( IfMagickFalse(sync) )
3497 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3502 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3503 #pragma omp critical (MagickCore_NegateImage)
3505 proceed=SetImageProgress(image,NegateImageTag,progress++,
3507 if( IfMagickFalse(proceed) )
3511 image_view=DestroyCacheView(image_view);
3517 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3518 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3519 magick_threads(image,image,image->rows,1)
3521 for (y=0; y < (ssize_t) image->rows; y++)
3529 if (status == MagickFalse)
3531 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3532 if (q == (Quantum *) NULL)
3537 for (x=0; x < (ssize_t) image->columns; x++)
3542 if (GetPixelReadMask(image,q) == 0)
3544 q+=GetPixelChannels(image);
3547 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3549 PixelChannel channel=GetPixelChannelChannel(image,i);
3550 PixelTrait traits=GetPixelChannelTraits(image,channel);
3551 if ((traits & UpdatePixelTrait) == 0)
3553 q[i]=QuantumRange-q[i];
3555 q+=GetPixelChannels(image);
3557 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3559 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3564 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3565 #pragma omp critical (MagickCore_NegateImage)
3567 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3568 if( IfMagickFalse(proceed) )
3572 image_view=DestroyCacheView(image_view);
3577 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3581 % N o r m a l i z e I m a g e %
3585 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3587 % The NormalizeImage() method enhances the contrast of a color image by
3588 % mapping the darkest 2 percent of all pixel to black and the brightest
3589 % 1 percent to white.
3591 % The format of the NormalizeImage method is:
3593 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3595 % A description of each parameter follows:
3597 % o image: the image.
3599 % o exception: return any errors or warnings in this structure.
3602 MagickExport MagickBooleanType NormalizeImage(Image *image,
3603 ExceptionInfo *exception)
3609 black_point=(double) image->columns*image->rows*0.0015;
3610 white_point=(double) image->columns*image->rows*0.9995;
3611 return(ContrastStretchImage(image,black_point,white_point,exception));
3615 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3619 % S i g m o i d a l C o n t r a s t I m a g e %
3623 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3625 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3626 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3627 % sigmoidal transfer function without saturating highlights or shadows.
3628 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3629 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3630 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3631 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3634 % The format of the SigmoidalContrastImage method is:
3636 % MagickBooleanType SigmoidalContrastImage(Image *image,
3637 % const MagickBooleanType sharpen,const char *levels,
3638 % ExceptionInfo *exception)
3640 % A description of each parameter follows:
3642 % o image: the image.
3644 % o sharpen: Increase or decrease image contrast.
3646 % o contrast: strength of the contrast, the larger the number the more
3647 % 'threshold-like' it becomes.
3649 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3651 % o exception: return any errors or warnings in this structure.
3656 ImageMagick 6 has a version of this function which uses LUTs.
3660 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3663 The first version, based on the hyperbolic tangent tanh, when combined with
3664 the scaling step, is an exact arithmetic clone of the the sigmoid function
3665 based on the logistic curve. The equivalence is based on the identity
3667 1/(1+exp(-t)) = (1+tanh(t/2))/2
3669 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3670 scaled sigmoidal derivation is invariant under affine transformations of
3673 The tanh version is almost certainly more accurate and cheaper. The 0.5
3674 factor in the argument is to clone the legacy ImageMagick behavior. The
3675 reason for making the define depend on atanh even though it only uses tanh
3676 has to do with the construction of the inverse of the scaled sigmoidal.
3678 #if defined(MAGICKCORE_HAVE_ATANH)
3679 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3681 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3684 Scaled sigmoidal function:
3686 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3687 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3689 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3690 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3691 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3692 zero. This is fixed below by exiting immediately when contrast is small,
3693 leaving the image (or colormap) unmodified. This appears to be safe because
3694 the series expansion of the logistic sigmoidal function around x=b is
3698 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3700 #define ScaledSigmoidal(a,b,x) ( \
3701 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3702 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3704 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3705 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3706 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3707 when creating a LUT from in gamut values, hence the branching. In
3708 addition, HDRI may have out of gamut values.
3709 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3710 It is only a right inverse. This is unavoidable.
3712 static inline double InverseScaledSigmoidal(const double a,const double b,
3715 const double sig0=Sigmoidal(a,b,0.0);
3716 const double sig1=Sigmoidal(a,b,1.0);
3717 const double argument=(sig1-sig0)*x+sig0;
3718 const double clamped=
3720 #if defined(MAGICKCORE_HAVE_ATANH)
3721 argument < -1+MagickEpsilon
3725 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3727 return(b+(2.0/a)*atanh(clamped));
3729 argument < MagickEpsilon
3733 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3735 return(b-log(1.0/clamped-1.0)/a);
3739 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3740 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3741 ExceptionInfo *exception)
3743 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3744 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3745 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3746 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3747 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3764 assert(image != (Image *) NULL);
3765 assert(image->signature == MagickCoreSignature);
3766 if (image->debug != MagickFalse)
3767 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3769 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3770 case nothing is done.
3772 if (contrast < MagickEpsilon)
3775 Sigmoidal-contrast enhance colormap.
3777 if (image->storage_class == PseudoClass)
3782 if( IfMagickTrue(sharpen) )
3783 for (i=0; i < (ssize_t) image->colors; i++)
3785 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3786 image->colormap[i].red=(MagickRealType) ScaledSig(
3787 image->colormap[i].red);
3788 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3789 image->colormap[i].green=(MagickRealType) ScaledSig(
3790 image->colormap[i].green);
3791 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3792 image->colormap[i].blue=(MagickRealType) ScaledSig(
3793 image->colormap[i].blue);
3794 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3795 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3796 image->colormap[i].alpha);
3799 for (i=0; i < (ssize_t) image->colors; i++)
3801 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3802 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3803 image->colormap[i].red);
3804 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3805 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3806 image->colormap[i].green);
3807 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3808 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3809 image->colormap[i].blue);
3810 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3811 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3812 image->colormap[i].alpha);
3816 Sigmoidal-contrast enhance image.
3820 image_view=AcquireAuthenticCacheView(image,exception);
3821 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3822 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3823 magick_threads(image,image,image->rows,1)
3825 for (y=0; y < (ssize_t) image->rows; y++)
3833 if (status == MagickFalse)
3835 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3836 if (q == (Quantum *) NULL)
3841 for (x=0; x < (ssize_t) image->columns; x++)
3846 if (GetPixelReadMask(image,q) == 0)
3848 q+=GetPixelChannels(image);
3851 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3853 PixelChannel channel=GetPixelChannelChannel(image,i);
3854 PixelTrait traits=GetPixelChannelTraits(image,channel);
3855 if ((traits & UpdatePixelTrait) == 0)
3857 if( IfMagickTrue(sharpen) )
3858 q[i]=ScaledSig(q[i]);
3860 q[i]=InverseScaledSig(q[i]);
3862 q+=GetPixelChannels(image);
3864 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
3866 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3871 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3872 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3874 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3876 if( IfMagickFalse(proceed) )
3880 image_view=DestroyCacheView(image_view);