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-2018 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 % https://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/accelerate-private.h"
45 #include "MagickCore/artifact.h"
46 #include "MagickCore/attribute.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/cache-private.h"
49 #include "MagickCore/cache-view.h"
50 #include "MagickCore/channel.h"
51 #include "MagickCore/color.h"
52 #include "MagickCore/color-private.h"
53 #include "MagickCore/colorspace.h"
54 #include "MagickCore/colorspace-private.h"
55 #include "MagickCore/composite-private.h"
56 #include "MagickCore/enhance.h"
57 #include "MagickCore/exception.h"
58 #include "MagickCore/exception-private.h"
59 #include "MagickCore/fx.h"
60 #include "MagickCore/gem.h"
61 #include "MagickCore/gem-private.h"
62 #include "MagickCore/geometry.h"
63 #include "MagickCore/histogram.h"
64 #include "MagickCore/image.h"
65 #include "MagickCore/image-private.h"
66 #include "MagickCore/memory_.h"
67 #include "MagickCore/monitor.h"
68 #include "MagickCore/monitor-private.h"
69 #include "MagickCore/option.h"
70 #include "MagickCore/pixel.h"
71 #include "MagickCore/pixel-accessor.h"
72 #include "MagickCore/quantum.h"
73 #include "MagickCore/quantum-private.h"
74 #include "MagickCore/resample.h"
75 #include "MagickCore/resample-private.h"
76 #include "MagickCore/resource_.h"
77 #include "MagickCore/statistic.h"
78 #include "MagickCore/string_.h"
79 #include "MagickCore/string-private.h"
80 #include "MagickCore/thread-private.h"
81 #include "MagickCore/threshold.h"
82 #include "MagickCore/token.h"
83 #include "MagickCore/xml-tree.h"
84 #include "MagickCore/xml-tree-private.h"
87 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91 % A u t o G a m m a I m a g e %
95 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97 % AutoGammaImage() extract the 'mean' from the image and adjust the image
98 % to try make set its gamma appropriatally.
100 % The format of the AutoGammaImage method is:
102 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
104 % A description of each parameter follows:
106 % o image: The image to auto-level
108 % o exception: return any errors or warnings in this structure.
111 MagickExport MagickBooleanType AutoGammaImage(Image *image,
112 ExceptionInfo *exception)
127 if (image->channel_mask == DefaultChannels)
130 Apply gamma correction equally across all given channels.
132 (void) GetImageMean(image,&mean,&sans,exception);
133 gamma=log(mean*QuantumScale)/log_mean;
134 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
137 Auto-gamma each channel separately.
140 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
145 PixelChannel channel = GetPixelChannelChannel(image,i);
146 PixelTrait traits = GetPixelChannelTraits(image,channel);
147 if ((traits & UpdatePixelTrait) == 0)
149 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
150 status=GetImageMean(image,&mean,&sans,exception);
151 gamma=log(mean*QuantumScale)/log_mean;
152 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
153 (void) SetImageChannelMask(image,channel_mask);
154 if (status == MagickFalse)
157 return(status != 0 ? MagickTrue : MagickFalse);
161 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
165 % A u t o L e v e l I m a g e %
169 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171 % AutoLevelImage() adjusts the levels of a particular image channel by
172 % scaling the minimum and maximum values to the full quantum range.
174 % The format of the LevelImage method is:
176 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
178 % A description of each parameter follows:
180 % o image: The image to auto-level
182 % o exception: return any errors or warnings in this structure.
185 MagickExport MagickBooleanType AutoLevelImage(Image *image,
186 ExceptionInfo *exception)
188 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
192 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
196 % B r i g h t n e s s C o n t r a s t I m a g e %
200 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202 % BrightnessContrastImage() changes the brightness and/or contrast of an
203 % image. It converts the brightness and contrast parameters into slope and
204 % intercept and calls a polynomical function to apply to the image.
206 % The format of the BrightnessContrastImage method is:
208 % MagickBooleanType BrightnessContrastImage(Image *image,
209 % const double brightness,const double contrast,ExceptionInfo *exception)
211 % A description of each parameter follows:
213 % o image: the image.
215 % o brightness: the brightness percent (-100 .. 100).
217 % o contrast: the contrast percent (-100 .. 100).
219 % o exception: return any errors or warnings in this structure.
222 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
223 const double brightness,const double contrast,ExceptionInfo *exception)
225 #define BrightnessContastImageTag "BrightnessContast/Image"
237 Compute slope and intercept.
239 assert(image != (Image *) NULL);
240 assert(image->signature == MagickCoreSignature);
241 if (image->debug != MagickFalse)
242 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
244 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
247 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
248 coefficients[0]=slope;
249 coefficients[1]=intercept;
250 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
255 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
259 % C l u t I m a g e %
263 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265 % ClutImage() replaces each color value in the given image, by using it as an
266 % index to lookup a replacement color value in a Color Look UP Table in the
267 % form of an image. The values are extracted along a diagonal of the CLUT
268 % image so either a horizontal or vertial gradient image can be used.
270 % Typically this is used to either re-color a gray-scale image according to a
271 % color gradient in the CLUT image, or to perform a freeform histogram
272 % (level) adjustment according to the (typically gray-scale) gradient in the
275 % When the 'channel' mask includes the matte/alpha transparency channel but
276 % one image has no such channel it is assumed that that image is a simple
277 % gray-scale image that will effect the alpha channel values, either for
278 % gray-scale coloring (with transparent or semi-transparent colors), or
279 % a histogram adjustment of existing alpha channel values. If both images
280 % have matte channels, direct and normal indexing is applied, which is rarely
283 % The format of the ClutImage method is:
285 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
286 % const PixelInterpolateMethod method,ExceptionInfo *exception)
288 % A description of each parameter follows:
290 % o image: the image, which is replaced by indexed CLUT values
292 % o clut_image: the color lookup table image for replacement color values.
294 % o method: the pixel interpolation method.
296 % o exception: return any errors or warnings in this structure.
299 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
300 const PixelInterpolateMethod method,ExceptionInfo *exception)
302 #define ClutImageTag "Clut/Image"
323 assert(image != (Image *) NULL);
324 assert(image->signature == MagickCoreSignature);
325 if (image->debug != MagickFalse)
326 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
327 assert(clut_image != (Image *) NULL);
328 assert(clut_image->signature == MagickCoreSignature);
329 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
331 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
332 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
333 (void) SetImageColorspace(image,sRGBColorspace,exception);
334 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
335 if (clut_map == (PixelInfo *) NULL)
336 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
343 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
344 clut_view=AcquireVirtualCacheView(clut_image,exception);
345 for (i=0; i <= (ssize_t) MaxMap; i++)
347 GetPixelInfo(clut_image,clut_map+i);
348 status=InterpolatePixelInfo(clut_image,clut_view,method,
349 (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
350 (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
351 if (status == MagickFalse)
354 clut_view=DestroyCacheView(clut_view);
355 image_view=AcquireAuthenticCacheView(image,exception);
356 #if defined(MAGICKCORE_OPENMP_SUPPORT)
357 #pragma omp parallel for schedule(static,4) shared(progress,status) \
358 magick_number_threads(image,image,image->rows,1)
360 for (y=0; y < (ssize_t) image->rows; y++)
371 if (status == MagickFalse)
373 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
374 if (q == (Quantum *) NULL)
379 GetPixelInfo(image,&pixel);
380 for (x=0; x < (ssize_t) image->columns; x++)
385 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
387 q+=GetPixelChannels(image);
390 GetPixelInfoPixel(image,q,&pixel);
391 traits=GetPixelChannelTraits(image,RedPixelChannel);
392 if ((traits & UpdatePixelTrait) != 0)
393 pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
395 traits=GetPixelChannelTraits(image,GreenPixelChannel);
396 if ((traits & UpdatePixelTrait) != 0)
397 pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
398 pixel.green))].green;
399 traits=GetPixelChannelTraits(image,BluePixelChannel);
400 if ((traits & UpdatePixelTrait) != 0)
401 pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
403 traits=GetPixelChannelTraits(image,BlackPixelChannel);
404 if ((traits & UpdatePixelTrait) != 0)
405 pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
406 pixel.black))].black;
407 traits=GetPixelChannelTraits(image,AlphaPixelChannel);
408 if ((traits & UpdatePixelTrait) != 0)
409 pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
410 pixel.alpha))].alpha;
411 SetPixelViaPixelInfo(image,&pixel,q);
412 q+=GetPixelChannels(image);
414 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
416 if (image->progress_monitor != (MagickProgressMonitor) NULL)
421 #if defined(MAGICKCORE_OPENMP_SUPPORT)
422 #pragma omp critical (MagickCore_ClutImage)
424 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
425 if (proceed == MagickFalse)
429 image_view=DestroyCacheView(image_view);
430 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
431 if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
432 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
433 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
438 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
442 % C o l o r D e c i s i o n L i s t I m a g e %
446 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
448 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
449 % (CCC) file which solely contains one or more color corrections and applies
450 % the correction to the image. Here is a sample CCC file:
452 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
453 % <ColorCorrection id="cc03345">
455 % <Slope> 0.9 1.2 0.5 </Slope>
456 % <Offset> 0.4 -0.5 0.6 </Offset>
457 % <Power> 1.0 0.8 1.5 </Power>
460 % <Saturation> 0.85 </Saturation>
463 % </ColorCorrectionCollection>
465 % which includes the slop, offset, and power for each of the RGB channels
466 % as well as the saturation.
468 % The format of the ColorDecisionListImage method is:
470 % MagickBooleanType ColorDecisionListImage(Image *image,
471 % const char *color_correction_collection,ExceptionInfo *exception)
473 % A description of each parameter follows:
475 % o image: the image.
477 % o color_correction_collection: the color correction collection in XML.
479 % o exception: return any errors or warnings in this structure.
482 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
483 const char *color_correction_collection,ExceptionInfo *exception)
485 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
487 typedef struct _Correction
495 typedef struct _ColorCorrection
510 token[MagickPathExtent];
541 Allocate and initialize cdl maps.
543 assert(image != (Image *) NULL);
544 assert(image->signature == MagickCoreSignature);
545 if (image->debug != MagickFalse)
546 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
547 if (color_correction_collection == (const char *) NULL)
549 ccc=NewXMLTree((const char *) color_correction_collection,exception);
550 if (ccc == (XMLTreeInfo *) NULL)
552 cc=GetXMLTreeChild(ccc,"ColorCorrection");
553 if (cc == (XMLTreeInfo *) NULL)
555 ccc=DestroyXMLTree(ccc);
558 color_correction.red.slope=1.0;
559 color_correction.red.offset=0.0;
560 color_correction.red.power=1.0;
561 color_correction.green.slope=1.0;
562 color_correction.green.offset=0.0;
563 color_correction.green.power=1.0;
564 color_correction.blue.slope=1.0;
565 color_correction.blue.offset=0.0;
566 color_correction.blue.power=1.0;
567 color_correction.saturation=0.0;
568 sop=GetXMLTreeChild(cc,"SOPNode");
569 if (sop != (XMLTreeInfo *) NULL)
576 slope=GetXMLTreeChild(sop,"Slope");
577 if (slope != (XMLTreeInfo *) NULL)
579 content=GetXMLTreeContent(slope);
580 p=(const char *) content;
581 for (i=0; (*p != '\0') && (i < 3); i++)
583 GetNextToken(p,&p,MagickPathExtent,token);
585 GetNextToken(p,&p,MagickPathExtent,token);
590 color_correction.red.slope=StringToDouble(token,(char **) NULL);
595 color_correction.green.slope=StringToDouble(token,
601 color_correction.blue.slope=StringToDouble(token,
608 offset=GetXMLTreeChild(sop,"Offset");
609 if (offset != (XMLTreeInfo *) NULL)
611 content=GetXMLTreeContent(offset);
612 p=(const char *) content;
613 for (i=0; (*p != '\0') && (i < 3); i++)
615 GetNextToken(p,&p,MagickPathExtent,token);
617 GetNextToken(p,&p,MagickPathExtent,token);
622 color_correction.red.offset=StringToDouble(token,
628 color_correction.green.offset=StringToDouble(token,
634 color_correction.blue.offset=StringToDouble(token,
641 power=GetXMLTreeChild(sop,"Power");
642 if (power != (XMLTreeInfo *) NULL)
644 content=GetXMLTreeContent(power);
645 p=(const char *) content;
646 for (i=0; (*p != '\0') && (i < 3); i++)
648 GetNextToken(p,&p,MagickPathExtent,token);
650 GetNextToken(p,&p,MagickPathExtent,token);
655 color_correction.red.power=StringToDouble(token,(char **) NULL);
660 color_correction.green.power=StringToDouble(token,
666 color_correction.blue.power=StringToDouble(token,
674 sat=GetXMLTreeChild(cc,"SATNode");
675 if (sat != (XMLTreeInfo *) NULL)
680 saturation=GetXMLTreeChild(sat,"Saturation");
681 if (saturation != (XMLTreeInfo *) NULL)
683 content=GetXMLTreeContent(saturation);
684 p=(const char *) content;
685 GetNextToken(p,&p,MagickPathExtent,token);
686 color_correction.saturation=StringToDouble(token,(char **) NULL);
689 ccc=DestroyXMLTree(ccc);
690 if (image->debug != MagickFalse)
692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
693 " Color Correction Collection:");
694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
695 " color_correction.red.slope: %g",color_correction.red.slope);
696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
697 " color_correction.red.offset: %g",color_correction.red.offset);
698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
699 " color_correction.red.power: %g",color_correction.red.power);
700 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
701 " color_correction.green.slope: %g",color_correction.green.slope);
702 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
703 " color_correction.green.offset: %g",color_correction.green.offset);
704 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
705 " color_correction.green.power: %g",color_correction.green.power);
706 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
707 " color_correction.blue.slope: %g",color_correction.blue.slope);
708 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
709 " color_correction.blue.offset: %g",color_correction.blue.offset);
710 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
711 " color_correction.blue.power: %g",color_correction.blue.power);
712 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
713 " color_correction.saturation: %g",color_correction.saturation);
715 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
716 if (cdl_map == (PixelInfo *) NULL)
717 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
719 for (i=0; i <= (ssize_t) MaxMap; i++)
721 cdl_map[i].red=(double) ScaleMapToQuantum((double)
722 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
723 color_correction.red.offset,color_correction.red.power))));
724 cdl_map[i].green=(double) ScaleMapToQuantum((double)
725 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
726 color_correction.green.offset,color_correction.green.power))));
727 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
728 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
729 color_correction.blue.offset,color_correction.blue.power))));
731 if (image->storage_class == PseudoClass)
732 for (i=0; i < (ssize_t) image->colors; i++)
735 Apply transfer function to colormap.
740 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
741 0.07217f*image->colormap[i].blue;
742 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
743 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
744 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
745 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
746 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
747 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
750 Apply transfer function to image.
754 image_view=AcquireAuthenticCacheView(image,exception);
755 #if defined(MAGICKCORE_OPENMP_SUPPORT)
756 #pragma omp parallel for schedule(static,4) shared(progress,status) \
757 magick_number_threads(image,image,image->rows,1)
759 for (y=0; y < (ssize_t) image->rows; y++)
770 if (status == MagickFalse)
772 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
773 if (q == (Quantum *) NULL)
778 for (x=0; x < (ssize_t) image->columns; x++)
780 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
781 0.07217f*GetPixelBlue(image,q);
782 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
783 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
784 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
785 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
786 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
787 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
788 q+=GetPixelChannels(image);
790 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
792 if (image->progress_monitor != (MagickProgressMonitor) NULL)
797 #if defined(MAGICKCORE_OPENMP_SUPPORT)
798 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
800 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
801 progress++,image->rows);
802 if (proceed == MagickFalse)
806 image_view=DestroyCacheView(image_view);
807 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
812 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
816 % C o n t r a s t I m a g e %
820 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
822 % ContrastImage() enhances the intensity differences between the lighter and
823 % darker elements of the image. Set sharpen to a MagickTrue to increase the
824 % image contrast otherwise the contrast is reduced.
826 % The format of the ContrastImage method is:
828 % MagickBooleanType ContrastImage(Image *image,
829 % const MagickBooleanType sharpen,ExceptionInfo *exception)
831 % A description of each parameter follows:
833 % o image: the image.
835 % o sharpen: Increase or decrease image contrast.
837 % o exception: return any errors or warnings in this structure.
841 static void Contrast(const int sign,double *red,double *green,double *blue)
849 Enhance contrast: dark color become darker, light color become lighter.
851 assert(red != (double *) NULL);
852 assert(green != (double *) NULL);
853 assert(blue != (double *) NULL);
857 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
858 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
860 if (brightness > 1.0)
863 if (brightness < 0.0)
865 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
868 MagickExport MagickBooleanType ContrastImage(Image *image,
869 const MagickBooleanType sharpen,ExceptionInfo *exception)
871 #define ContrastImageTag "Contrast/Image"
891 assert(image != (Image *) NULL);
892 assert(image->signature == MagickCoreSignature);
893 #if defined(MAGICKCORE_OPENCL_SUPPORT)
894 if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
897 if (image->debug != MagickFalse)
898 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
899 sign=sharpen != MagickFalse ? 1 : -1;
900 if (image->storage_class == PseudoClass)
903 Contrast enhance colormap.
905 for (i=0; i < (ssize_t) image->colors; i++)
912 red=(double) image->colormap[i].red;
913 green=(double) image->colormap[i].green;
914 blue=(double) image->colormap[i].blue;
915 Contrast(sign,&red,&green,&blue);
916 image->colormap[i].red=(MagickRealType) red;
917 image->colormap[i].green=(MagickRealType) green;
918 image->colormap[i].blue=(MagickRealType) blue;
922 Contrast enhance image.
926 image_view=AcquireAuthenticCacheView(image,exception);
927 #if defined(MAGICKCORE_OPENMP_SUPPORT)
928 #pragma omp parallel for schedule(static,4) shared(progress,status) \
929 magick_number_threads(image,image,image->rows,1)
931 for (y=0; y < (ssize_t) image->rows; y++)
944 if (status == MagickFalse)
946 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
947 if (q == (Quantum *) NULL)
952 for (x=0; x < (ssize_t) image->columns; x++)
954 red=(double) GetPixelRed(image,q);
955 green=(double) GetPixelGreen(image,q);
956 blue=(double) GetPixelBlue(image,q);
957 Contrast(sign,&red,&green,&blue);
958 SetPixelRed(image,ClampToQuantum(red),q);
959 SetPixelGreen(image,ClampToQuantum(green),q);
960 SetPixelBlue(image,ClampToQuantum(blue),q);
961 q+=GetPixelChannels(image);
963 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
965 if (image->progress_monitor != (MagickProgressMonitor) NULL)
970 #if defined(MAGICKCORE_OPENMP_SUPPORT)
971 #pragma omp critical (MagickCore_ContrastImage)
973 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
974 if (proceed == MagickFalse)
978 image_view=DestroyCacheView(image_view);
983 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
987 % C o n t r a s t S t r e t c h I m a g e %
991 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
993 % ContrastStretchImage() is a simple image enhancement technique that attempts
994 % to improve the contrast in an image by 'stretching' the range of intensity
995 % values it contains to span a desired range of values. It differs from the
996 % more sophisticated histogram equalization in that it can only apply a
997 % linear scaling function to the image pixel values. As a result the
998 % 'enhancement' is less harsh.
1000 % The format of the ContrastStretchImage method is:
1002 % MagickBooleanType ContrastStretchImage(Image *image,
1003 % const char *levels,ExceptionInfo *exception)
1005 % A description of each parameter follows:
1007 % o image: the image.
1009 % o black_point: the black point.
1011 % o white_point: the white point.
1013 % o levels: Specify the levels where the black and white points have the
1014 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1016 % o exception: return any errors or warnings in this structure.
1019 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1020 const double black_point,const double white_point,ExceptionInfo *exception)
1022 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1023 #define ContrastStretchImageTag "ContrastStretch/Image"
1047 Allocate histogram and stretch map.
1049 assert(image != (Image *) NULL);
1050 assert(image->signature == MagickCoreSignature);
1051 if (image->debug != MagickFalse)
1052 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1053 if (SetImageGray(image,exception) != MagickFalse)
1054 (void) SetImageColorspace(image,GRAYColorspace,exception);
1055 black=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1056 white=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1057 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1058 sizeof(*histogram));
1059 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1060 sizeof(*stretch_map));
1061 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1062 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1064 if (stretch_map != (double *) NULL)
1065 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1066 if (histogram != (double *) NULL)
1067 histogram=(double *) RelinquishMagickMemory(histogram);
1068 if (white != (double *) NULL)
1069 white=(double *) RelinquishMagickMemory(white);
1070 if (black != (double *) NULL)
1071 black=(double *) RelinquishMagickMemory(black);
1072 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1079 (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1080 sizeof(*histogram));
1081 image_view=AcquireVirtualCacheView(image,exception);
1082 for (y=0; y < (ssize_t) image->rows; y++)
1084 register const Quantum
1090 if (status == MagickFalse)
1092 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1093 if (p == (const Quantum *) NULL)
1098 for (x=0; x < (ssize_t) image->columns; x++)
1103 pixel=GetPixelIntensity(image,p);
1104 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1106 if (image->channel_mask != DefaultChannels)
1107 pixel=(double) p[i];
1108 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1109 ClampToQuantum(pixel))+i]++;
1111 p+=GetPixelChannels(image);
1114 image_view=DestroyCacheView(image_view);
1116 Find the histogram boundaries by locating the black/white levels.
1118 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1127 white[i]=MaxRange(QuantumRange);
1129 for (j=0; j <= (ssize_t) MaxMap; j++)
1131 intensity+=histogram[GetPixelChannels(image)*j+i];
1132 if (intensity > black_point)
1135 black[i]=(double) j;
1137 for (j=(ssize_t) MaxMap; j != 0; j--)
1139 intensity+=histogram[GetPixelChannels(image)*j+i];
1140 if (intensity > ((double) image->columns*image->rows-white_point))
1143 white[i]=(double) j;
1145 histogram=(double *) RelinquishMagickMemory(histogram);
1147 Stretch the histogram to create the stretched image mapping.
1149 (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1150 sizeof(*stretch_map));
1151 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1156 for (j=0; j <= (ssize_t) MaxMap; j++)
1161 gamma=PerceptibleReciprocal(white[i]-black[i]);
1162 if (j < (ssize_t) black[i])
1163 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1165 if (j > (ssize_t) white[i])
1166 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1168 if (black[i] != white[i])
1169 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1170 (double) (MaxMap*gamma*(j-black[i])));
1173 if (image->storage_class == PseudoClass)
1179 Stretch-contrast colormap.
1181 for (j=0; j < (ssize_t) image->colors; j++)
1183 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelOffset(image,RedPixelChannel);
1186 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1189 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1191 i=GetPixelChannelOffset(image,GreenPixelChannel);
1192 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1193 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1195 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1197 i=GetPixelChannelOffset(image,BluePixelChannel);
1198 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1199 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1201 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1203 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1204 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1205 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1210 Stretch-contrast image.
1214 image_view=AcquireAuthenticCacheView(image,exception);
1215 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1216 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1217 magick_number_threads(image,image,image->rows,1)
1219 for (y=0; y < (ssize_t) image->rows; y++)
1227 if (status == MagickFalse)
1229 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1230 if (q == (Quantum *) NULL)
1235 for (x=0; x < (ssize_t) image->columns; x++)
1240 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
1242 q+=GetPixelChannels(image);
1245 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1247 PixelChannel channel = GetPixelChannelChannel(image,j);
1248 PixelTrait traits = GetPixelChannelTraits(image,channel);
1249 if ((traits & UpdatePixelTrait) == 0)
1251 if (black[j] == white[j])
1253 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1254 ScaleQuantumToMap(q[j])+j]);
1256 q+=GetPixelChannels(image);
1258 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1260 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1265 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1266 #pragma omp critical (MagickCore_ContrastStretchImage)
1268 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1270 if (proceed == MagickFalse)
1274 image_view=DestroyCacheView(image_view);
1275 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1276 white=(double *) RelinquishMagickMemory(white);
1277 black=(double *) RelinquishMagickMemory(black);
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1286 % E n h a n c e I m a g e %
1290 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1292 % EnhanceImage() applies a digital filter that improves the quality of a
1295 % The format of the EnhanceImage method is:
1297 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1299 % A description of each parameter follows:
1301 % o image: the image.
1303 % o exception: return any errors or warnings in this structure.
1306 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1308 #define EnhanceImageTag "Enhance/Image"
1309 #define EnhancePixel(weight) \
1310 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1311 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1312 distance_squared=(4.0+mean)*distance*distance; \
1313 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1314 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1315 distance_squared+=(7.0-mean)*distance*distance; \
1316 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1317 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1318 distance_squared+=(5.0-mean)*distance*distance; \
1319 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1320 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1321 distance_squared+=(5.0-mean)*distance*distance; \
1322 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1323 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1324 distance_squared+=(5.0-mean)*distance*distance; \
1325 if (distance_squared < 0.069) \
1327 aggregate.red+=(weight)*GetPixelRed(image,r); \
1328 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1329 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1330 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1331 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1332 total_weight+=(weight); \
1334 r+=GetPixelChannels(image);
1353 Initialize enhanced image attributes.
1355 assert(image != (const Image *) NULL);
1356 assert(image->signature == MagickCoreSignature);
1357 if (image->debug != MagickFalse)
1358 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1359 assert(exception != (ExceptionInfo *) NULL);
1360 assert(exception->signature == MagickCoreSignature);
1361 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1363 if (enhance_image == (Image *) NULL)
1364 return((Image *) NULL);
1365 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1367 enhance_image=DestroyImage(enhance_image);
1368 return((Image *) NULL);
1375 image_view=AcquireVirtualCacheView(image,exception);
1376 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1377 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1378 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1379 magick_number_threads(image,enhance_image,image->rows,1)
1381 for (y=0; y < (ssize_t) image->rows; y++)
1386 register const Quantum
1398 if (status == MagickFalse)
1400 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1401 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1403 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1408 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1409 GetPixelInfo(image,&pixel);
1410 for (x=0; x < (ssize_t) image->columns; x++)
1421 register const Quantum
1424 if (GetPixelWriteMask(image,p) <= (QuantumRange/2))
1426 SetPixelBackgoundColor(enhance_image,q);
1427 p+=GetPixelChannels(image);
1428 q+=GetPixelChannels(enhance_image);
1431 GetPixelInfo(image,&aggregate);
1433 GetPixelInfoPixel(image,p+center,&pixel);
1435 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1436 EnhancePixel(8.0); EnhancePixel(5.0);
1437 r=p+GetPixelChannels(image)*(image->columns+4);
1438 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1439 EnhancePixel(20.0); EnhancePixel(8.0);
1440 r=p+2*GetPixelChannels(image)*(image->columns+4);
1441 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1442 EnhancePixel(40.0); EnhancePixel(10.0);
1443 r=p+3*GetPixelChannels(image)*(image->columns+4);
1444 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1445 EnhancePixel(20.0); EnhancePixel(8.0);
1446 r=p+4*GetPixelChannels(image)*(image->columns+4);
1447 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1448 EnhancePixel(8.0); EnhancePixel(5.0);
1449 if (total_weight > MagickEpsilon)
1451 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1452 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1453 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1454 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1455 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1457 SetPixelViaPixelInfo(image,&pixel,q);
1458 p+=GetPixelChannels(image);
1459 q+=GetPixelChannels(enhance_image);
1461 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1463 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1468 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1469 #pragma omp critical (MagickCore_EnhanceImage)
1471 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1472 if (proceed == MagickFalse)
1476 enhance_view=DestroyCacheView(enhance_view);
1477 image_view=DestroyCacheView(image_view);
1478 if (status == MagickFalse)
1479 enhance_image=DestroyImage(enhance_image);
1480 return(enhance_image);
1484 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1488 % E q u a l i z e I m a g e %
1492 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1494 % EqualizeImage() applies a histogram equalization to the image.
1496 % The format of the EqualizeImage method is:
1498 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1500 % A description of each parameter follows:
1502 % o image: the image.
1504 % o exception: return any errors or warnings in this structure.
1507 MagickExport MagickBooleanType EqualizeImage(Image *image,
1508 ExceptionInfo *exception)
1510 #define EqualizeImageTag "Equalize/Image"
1516 black[CompositePixelChannel+1],
1520 white[CompositePixelChannel+1];
1535 Allocate and initialize histogram arrays.
1537 assert(image != (Image *) NULL);
1538 assert(image->signature == MagickCoreSignature);
1539 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1540 if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1543 if (image->debug != MagickFalse)
1544 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1545 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1546 sizeof(*equalize_map));
1547 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1548 sizeof(*histogram));
1549 map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map));
1550 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1551 (map == (double *) NULL))
1553 if (map != (double *) NULL)
1554 map=(double *) RelinquishMagickMemory(map);
1555 if (histogram != (double *) NULL)
1556 histogram=(double *) RelinquishMagickMemory(histogram);
1557 if (equalize_map != (double *) NULL)
1558 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1559 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1566 (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1567 sizeof(*histogram));
1568 image_view=AcquireVirtualCacheView(image,exception);
1569 for (y=0; y < (ssize_t) image->rows; y++)
1571 register const Quantum
1577 if (status == MagickFalse)
1579 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1580 if (p == (const Quantum *) NULL)
1585 for (x=0; x < (ssize_t) image->columns; x++)
1587 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1593 if ((image->channel_mask & SyncChannels) != 0)
1594 intensity=GetPixelIntensity(image,p);
1595 histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1597 p+=GetPixelChannels(image);
1600 image_view=DestroyCacheView(image_view);
1602 Integrate the histogram to get the equalization map.
1604 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1613 for (j=0; j <= (ssize_t) MaxMap; j++)
1615 intensity+=histogram[GetPixelChannels(image)*j+i];
1616 map[GetPixelChannels(image)*j+i]=intensity;
1619 (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1620 sizeof(*equalize_map));
1621 (void) memset(black,0,sizeof(*black));
1622 (void) memset(white,0,sizeof(*white));
1623 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1629 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1630 if (black[i] != white[i])
1631 for (j=0; j <= (ssize_t) MaxMap; j++)
1632 equalize_map[GetPixelChannels(image)*j+i]=(double)
1633 ScaleMapToQuantum((double) ((MaxMap*(map[
1634 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1636 histogram=(double *) RelinquishMagickMemory(histogram);
1637 map=(double *) RelinquishMagickMemory(map);
1638 if (image->storage_class == PseudoClass)
1646 for (j=0; j < (ssize_t) image->colors; j++)
1648 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1650 PixelChannel channel = GetPixelChannelChannel(image,RedPixelChannel);
1651 if (black[channel] != white[channel])
1652 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1653 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1656 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1658 PixelChannel channel = GetPixelChannelChannel(image,
1660 if (black[channel] != white[channel])
1661 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1662 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1665 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1667 PixelChannel channel = GetPixelChannelChannel(image,BluePixelChannel);
1668 if (black[channel] != white[channel])
1669 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1670 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1673 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1675 PixelChannel channel = GetPixelChannelChannel(image,
1677 if (black[channel] != white[channel])
1678 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1679 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1688 image_view=AcquireAuthenticCacheView(image,exception);
1689 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1690 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1691 magick_number_threads(image,image,image->rows,1)
1693 for (y=0; y < (ssize_t) image->rows; y++)
1701 if (status == MagickFalse)
1703 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1704 if (q == (Quantum *) NULL)
1709 for (x=0; x < (ssize_t) image->columns; x++)
1714 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
1716 q+=GetPixelChannels(image);
1719 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1721 PixelChannel channel = GetPixelChannelChannel(image,j);
1722 PixelTrait traits = GetPixelChannelTraits(image,channel);
1723 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1725 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1726 ScaleQuantumToMap(q[j])+j]);
1728 q+=GetPixelChannels(image);
1730 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1732 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1737 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1738 #pragma omp critical (MagickCore_EqualizeImage)
1740 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1741 if (proceed == MagickFalse)
1745 image_view=DestroyCacheView(image_view);
1746 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1751 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1755 % G a m m a I m a g e %
1759 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1761 % GammaImage() gamma-corrects a particular image channel. The same
1762 % image viewed on different devices will have perceptual differences in the
1763 % way the image's intensities are represented on the screen. Specify
1764 % individual gamma levels for the red, green, and blue channels, or adjust
1765 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1767 % You can also reduce the influence of a particular channel with a gamma
1770 % The format of the GammaImage method is:
1772 % MagickBooleanType GammaImage(Image *image,const double gamma,
1773 % ExceptionInfo *exception)
1775 % A description of each parameter follows:
1777 % o image: the image.
1779 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1781 % o gamma: the image gamma.
1785 static inline double gamma_pow(const double value,const double gamma)
1787 return(value < 0.0 ? value : pow(value,gamma));
1790 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1791 ExceptionInfo *exception)
1793 #define GammaCorrectImageTag "GammaCorrect/Image"
1814 Allocate and initialize gamma maps.
1816 assert(image != (Image *) NULL);
1817 assert(image->signature == MagickCoreSignature);
1818 if (image->debug != MagickFalse)
1819 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1822 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1823 if (gamma_map == (Quantum *) NULL)
1824 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1826 (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1828 for (i=0; i <= (ssize_t) MaxMap; i++)
1829 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1830 MaxMap,1.0/gamma)));
1831 if (image->storage_class == PseudoClass)
1832 for (i=0; i < (ssize_t) image->colors; i++)
1835 Gamma-correct colormap.
1837 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1838 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1839 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1840 ClampToQuantum(image->colormap[i].red))];
1841 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1842 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1843 ClampToQuantum(image->colormap[i].green))];
1844 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1845 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1846 ClampToQuantum(image->colormap[i].blue))];
1847 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1848 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1849 ClampToQuantum(image->colormap[i].alpha))];
1851 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1852 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1853 image->colormap[i].red,1.0/gamma);
1854 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1855 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1856 image->colormap[i].green,1.0/gamma);
1857 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1858 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1859 image->colormap[i].blue,1.0/gamma);
1860 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1861 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1862 image->colormap[i].alpha,1.0/gamma);
1866 Gamma-correct image.
1870 image_view=AcquireAuthenticCacheView(image,exception);
1871 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1872 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1873 magick_number_threads(image,image,image->rows,1)
1875 for (y=0; y < (ssize_t) image->rows; y++)
1883 if (status == MagickFalse)
1885 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1886 if (q == (Quantum *) NULL)
1891 for (x=0; x < (ssize_t) image->columns; x++)
1896 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
1898 q+=GetPixelChannels(image);
1901 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1903 PixelChannel channel = GetPixelChannelChannel(image,j);
1904 PixelTrait traits = GetPixelChannelTraits(image,channel);
1905 if ((traits & UpdatePixelTrait) == 0)
1907 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1908 q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1910 q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1913 q+=GetPixelChannels(image);
1915 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1917 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1922 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1923 #pragma omp critical (MagickCore_GammaImage)
1925 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1927 if (proceed == MagickFalse)
1931 image_view=DestroyCacheView(image_view);
1932 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1933 if (image->gamma != 0.0)
1934 image->gamma*=gamma;
1939 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1943 % G r a y s c a l e I m a g e %
1947 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1949 % GrayscaleImage() converts the image to grayscale.
1951 % The format of the GrayscaleImage method is:
1953 % MagickBooleanType GrayscaleImage(Image *image,
1954 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1956 % A description of each parameter follows:
1958 % o image: the image.
1960 % o method: the pixel intensity method.
1962 % o exception: return any errors or warnings in this structure.
1965 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1966 const PixelIntensityMethod method,ExceptionInfo *exception)
1968 #define GrayscaleImageTag "Grayscale/Image"
1982 assert(image != (Image *) NULL);
1983 assert(image->signature == MagickCoreSignature);
1984 if (image->debug != MagickFalse)
1985 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1986 if (image->storage_class == PseudoClass)
1988 if (SyncImage(image,exception) == MagickFalse)
1989 return(MagickFalse);
1990 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1991 return(MagickFalse);
1993 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1994 if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1996 image->intensity=method;
1997 image->type=GrayscaleType;
1998 if ((method == Rec601LuminancePixelIntensityMethod) ||
1999 (method == Rec709LuminancePixelIntensityMethod))
2000 return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2001 return(SetImageColorspace(image,GRAYColorspace,exception));
2009 image_view=AcquireAuthenticCacheView(image,exception);
2010 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2011 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2012 magick_number_threads(image,image,image->rows,1)
2014 for (y=0; y < (ssize_t) image->rows; y++)
2022 if (status == MagickFalse)
2024 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2025 if (q == (Quantum *) NULL)
2030 for (x=0; x < (ssize_t) image->columns; x++)
2038 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
2040 q+=GetPixelChannels(image);
2043 red=(MagickRealType) GetPixelRed(image,q);
2044 green=(MagickRealType) GetPixelGreen(image,q);
2045 blue=(MagickRealType) GetPixelBlue(image,q);
2049 case AveragePixelIntensityMethod:
2051 intensity=(red+green+blue)/3.0;
2054 case BrightnessPixelIntensityMethod:
2056 intensity=MagickMax(MagickMax(red,green),blue);
2059 case LightnessPixelIntensityMethod:
2061 intensity=(MagickMin(MagickMin(red,green),blue)+
2062 MagickMax(MagickMax(red,green),blue))/2.0;
2065 case MSPixelIntensityMethod:
2067 intensity=(MagickRealType) (((double) red*red+green*green+
2071 case Rec601LumaPixelIntensityMethod:
2073 if (image->colorspace == RGBColorspace)
2075 red=EncodePixelGamma(red);
2076 green=EncodePixelGamma(green);
2077 blue=EncodePixelGamma(blue);
2079 intensity=0.298839*red+0.586811*green+0.114350*blue;
2082 case Rec601LuminancePixelIntensityMethod:
2084 if (image->colorspace == sRGBColorspace)
2086 red=DecodePixelGamma(red);
2087 green=DecodePixelGamma(green);
2088 blue=DecodePixelGamma(blue);
2090 intensity=0.298839*red+0.586811*green+0.114350*blue;
2093 case Rec709LumaPixelIntensityMethod:
2096 if (image->colorspace == RGBColorspace)
2098 red=EncodePixelGamma(red);
2099 green=EncodePixelGamma(green);
2100 blue=EncodePixelGamma(blue);
2102 intensity=0.212656*red+0.715158*green+0.072186*blue;
2105 case Rec709LuminancePixelIntensityMethod:
2107 if (image->colorspace == sRGBColorspace)
2109 red=DecodePixelGamma(red);
2110 green=DecodePixelGamma(green);
2111 blue=DecodePixelGamma(blue);
2113 intensity=0.212656*red+0.715158*green+0.072186*blue;
2116 case RMSPixelIntensityMethod:
2118 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2119 blue*blue)/sqrt(3.0));
2123 SetPixelGray(image,ClampToQuantum(intensity),q);
2124 q+=GetPixelChannels(image);
2126 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2128 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2133 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2134 #pragma omp critical (MagickCore_GrayscaleImage)
2136 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2138 if (proceed == MagickFalse)
2142 image_view=DestroyCacheView(image_view);
2143 image->intensity=method;
2144 image->type=GrayscaleType;
2145 if ((method == Rec601LuminancePixelIntensityMethod) ||
2146 (method == Rec709LuminancePixelIntensityMethod))
2147 return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2148 return(SetImageColorspace(image,GRAYColorspace,exception));
2152 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2156 % H a l d C l u t I m a g e %
2160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2162 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2163 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2164 % Create it with the HALD coder. You can apply any color transformation to
2165 % the Hald image and then use this method to apply the transform to the
2168 % The format of the HaldClutImage method is:
2170 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2171 % ExceptionInfo *exception)
2173 % A description of each parameter follows:
2175 % o image: the image, which is replaced by indexed CLUT values
2177 % o hald_image: the color lookup table image for replacement color values.
2179 % o exception: return any errors or warnings in this structure.
2182 MagickExport MagickBooleanType HaldClutImage(Image *image,
2183 const Image *hald_image,ExceptionInfo *exception)
2185 #define HaldClutImageTag "Clut/Image"
2187 typedef struct _HaldInfo
2219 assert(image != (Image *) NULL);
2220 assert(image->signature == MagickCoreSignature);
2221 if (image->debug != MagickFalse)
2222 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2223 assert(hald_image != (Image *) NULL);
2224 assert(hald_image->signature == MagickCoreSignature);
2225 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2226 return(MagickFalse);
2227 if (image->alpha_trait == UndefinedPixelTrait)
2228 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2234 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2235 (MagickRealType) hald_image->rows);
2236 for (level=2; (level*level*level) < length; level++) ;
2238 cube_size=level*level;
2239 width=(double) hald_image->columns;
2240 GetPixelInfo(hald_image,&zero);
2241 hald_view=AcquireVirtualCacheView(hald_image,exception);
2242 image_view=AcquireAuthenticCacheView(image,exception);
2243 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2244 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2245 magick_number_threads(image,image,image->rows,1)
2247 for (y=0; y < (ssize_t) image->rows; y++)
2255 if (status == MagickFalse)
2257 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2258 if (q == (Quantum *) NULL)
2263 for (x=0; x < (ssize_t) image->columns; x++)
2278 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2279 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2280 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2281 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2282 point.x-=floor(point.x);
2283 point.y-=floor(point.y);
2284 point.z-=floor(point.z);
2286 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2287 fmod(offset,width),floor(offset/width),&pixel1,exception);
2288 if (status == MagickFalse)
2291 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2292 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2293 if (status == MagickFalse)
2296 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2299 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2300 fmod(offset,width),floor(offset/width),&pixel1,exception);
2301 if (status == MagickFalse)
2303 status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2304 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2305 if (status == MagickFalse)
2308 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2311 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2313 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2314 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2315 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2316 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2317 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2318 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2319 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2320 (image->colorspace == CMYKColorspace))
2321 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2322 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2323 (image->alpha_trait != UndefinedPixelTrait))
2324 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2325 q+=GetPixelChannels(image);
2327 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2329 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2334 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2335 #pragma omp critical (MagickCore_HaldClutImage)
2337 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2338 if (proceed == MagickFalse)
2342 hald_view=DestroyCacheView(hald_view);
2343 image_view=DestroyCacheView(image_view);
2348 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2352 % L e v e l I m a g e %
2356 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2358 % LevelImage() adjusts the levels of a particular image channel by
2359 % scaling the colors falling between specified white and black points to
2360 % the full available quantum range.
2362 % The parameters provided represent the black, and white points. The black
2363 % point specifies the darkest color in the image. Colors darker than the
2364 % black point are set to zero. White point specifies the lightest color in
2365 % the image. Colors brighter than the white point are set to the maximum
2368 % If a '!' flag is given, map black and white colors to the given levels
2369 % rather than mapping those levels to black and white. See
2370 % LevelizeImage() below.
2372 % Gamma specifies a gamma correction to apply to the image.
2374 % The format of the LevelImage method is:
2376 % MagickBooleanType LevelImage(Image *image,const double black_point,
2377 % const double white_point,const double gamma,ExceptionInfo *exception)
2379 % A description of each parameter follows:
2381 % o image: the image.
2383 % o black_point: The level to map zero (black) to.
2385 % o white_point: The level to map QuantumRange (white) to.
2387 % o exception: return any errors or warnings in this structure.
2391 static inline double LevelPixel(const double black_point,
2392 const double white_point,const double gamma,const double pixel)
2398 if (fabs(white_point-black_point) < MagickEpsilon)
2400 scale=1.0/(white_point-black_point);
2401 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2403 return(level_pixel);
2406 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2407 const double white_point,const double gamma,ExceptionInfo *exception)
2409 #define LevelImageTag "Level/Image"
2427 Allocate and initialize levels map.
2429 assert(image != (Image *) NULL);
2430 assert(image->signature == MagickCoreSignature);
2431 if (image->debug != MagickFalse)
2432 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2433 if (image->storage_class == PseudoClass)
2434 for (i=0; i < (ssize_t) image->colors; i++)
2439 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2440 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2441 white_point,gamma,image->colormap[i].red));
2442 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2443 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2444 white_point,gamma,image->colormap[i].green));
2445 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2446 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2447 white_point,gamma,image->colormap[i].blue));
2448 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2449 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2450 white_point,gamma,image->colormap[i].alpha));
2457 image_view=AcquireAuthenticCacheView(image,exception);
2458 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2459 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2460 magick_number_threads(image,image,image->rows,1)
2462 for (y=0; y < (ssize_t) image->rows; y++)
2470 if (status == MagickFalse)
2472 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2473 if (q == (Quantum *) NULL)
2478 for (x=0; x < (ssize_t) image->columns; x++)
2483 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
2485 q+=GetPixelChannels(image);
2488 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2490 PixelChannel channel = GetPixelChannelChannel(image,j);
2491 PixelTrait traits = GetPixelChannelTraits(image,channel);
2492 if ((traits & UpdatePixelTrait) == 0)
2494 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2497 q+=GetPixelChannels(image);
2499 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2501 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2506 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2507 #pragma omp critical (MagickCore_LevelImage)
2509 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2510 if (proceed == MagickFalse)
2514 image_view=DestroyCacheView(image_view);
2515 (void) ClampImage(image,exception);
2520 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2524 % L e v e l i z e I m a g e %
2528 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2530 % LevelizeImage() applies the reversed LevelImage() operation to just
2531 % the specific channels specified. It compresses the full range of color
2532 % values, so that they lie between the given black and white points. Gamma is
2533 % applied before the values are mapped.
2535 % LevelizeImage() can be called with by using a +level command line
2536 % API option, or using a '!' on a -level or LevelImage() geometry string.
2538 % It can be used to de-contrast a greyscale image to the exact levels
2539 % specified. Or by using specific levels for each channel of an image you
2540 % can convert a gray-scale image to any linear color gradient, according to
2543 % The format of the LevelizeImage method is:
2545 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2546 % const double white_point,const double gamma,ExceptionInfo *exception)
2548 % A description of each parameter follows:
2550 % o image: the image.
2552 % o black_point: The level to map zero (black) to.
2554 % o white_point: The level to map QuantumRange (white) to.
2556 % o gamma: adjust gamma by this factor before mapping values.
2558 % o exception: return any errors or warnings in this structure.
2561 MagickExport MagickBooleanType LevelizeImage(Image *image,
2562 const double black_point,const double white_point,const double gamma,
2563 ExceptionInfo *exception)
2565 #define LevelizeImageTag "Levelize/Image"
2566 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2567 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2585 Allocate and initialize levels map.
2587 assert(image != (Image *) NULL);
2588 assert(image->signature == MagickCoreSignature);
2589 if (image->debug != MagickFalse)
2590 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2591 if (image->storage_class == PseudoClass)
2592 for (i=0; i < (ssize_t) image->colors; i++)
2597 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2598 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2599 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2600 image->colormap[i].green=(double) LevelizeValue(
2601 image->colormap[i].green);
2602 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2603 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2604 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2605 image->colormap[i].alpha=(double) LevelizeValue(
2606 image->colormap[i].alpha);
2613 image_view=AcquireAuthenticCacheView(image,exception);
2614 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2615 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2616 magick_number_threads(image,image,image->rows,1)
2618 for (y=0; y < (ssize_t) image->rows; y++)
2626 if (status == MagickFalse)
2628 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2629 if (q == (Quantum *) NULL)
2634 for (x=0; x < (ssize_t) image->columns; x++)
2639 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
2641 q+=GetPixelChannels(image);
2644 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2646 PixelChannel channel = GetPixelChannelChannel(image,j);
2647 PixelTrait traits = GetPixelChannelTraits(image,channel);
2648 if ((traits & UpdatePixelTrait) == 0)
2650 q[j]=LevelizeValue(q[j]);
2652 q+=GetPixelChannels(image);
2654 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2656 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2661 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2662 #pragma omp critical (MagickCore_LevelizeImage)
2664 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2665 if (proceed == MagickFalse)
2669 image_view=DestroyCacheView(image_view);
2674 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2678 % L e v e l I m a g e C o l o r s %
2682 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2684 % LevelImageColors() maps the given color to "black" and "white" values,
2685 % linearly spreading out the colors, and level values on a channel by channel
2686 % bases, as per LevelImage(). The given colors allows you to specify
2687 % different level ranges for each of the color channels separately.
2689 % If the boolean 'invert' is set true the image values will modifyed in the
2690 % reverse direction. That is any existing "black" and "white" colors in the
2691 % image will become the color values given, with all other values compressed
2692 % appropriatally. This effectivally maps a greyscale gradient into the given
2695 % The format of the LevelImageColors method is:
2697 % MagickBooleanType LevelImageColors(Image *image,
2698 % const PixelInfo *black_color,const PixelInfo *white_color,
2699 % const MagickBooleanType invert,ExceptionInfo *exception)
2701 % A description of each parameter follows:
2703 % o image: the image.
2705 % o black_color: The color to map black to/from
2707 % o white_point: The color to map white to/from
2709 % o invert: if true map the colors (levelize), rather than from (level)
2711 % o exception: return any errors or warnings in this structure.
2714 MagickExport MagickBooleanType LevelImageColors(Image *image,
2715 const PixelInfo *black_color,const PixelInfo *white_color,
2716 const MagickBooleanType invert,ExceptionInfo *exception)
2725 Allocate and initialize levels map.
2727 assert(image != (Image *) NULL);
2728 assert(image->signature == MagickCoreSignature);
2729 if (image->debug != MagickFalse)
2730 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2731 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2732 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2733 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2734 (void) SetImageColorspace(image,sRGBColorspace,exception);
2736 if (invert == MagickFalse)
2738 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2740 channel_mask=SetImageChannelMask(image,RedChannel);
2741 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2743 (void) SetImageChannelMask(image,channel_mask);
2745 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2747 channel_mask=SetImageChannelMask(image,GreenChannel);
2748 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2750 (void) SetImageChannelMask(image,channel_mask);
2752 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2754 channel_mask=SetImageChannelMask(image,BlueChannel);
2755 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2757 (void) SetImageChannelMask(image,channel_mask);
2759 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2760 (image->colorspace == CMYKColorspace))
2762 channel_mask=SetImageChannelMask(image,BlackChannel);
2763 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2765 (void) SetImageChannelMask(image,channel_mask);
2767 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2768 (image->alpha_trait != UndefinedPixelTrait))
2770 channel_mask=SetImageChannelMask(image,AlphaChannel);
2771 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2773 (void) SetImageChannelMask(image,channel_mask);
2778 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2780 channel_mask=SetImageChannelMask(image,RedChannel);
2781 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2783 (void) SetImageChannelMask(image,channel_mask);
2785 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2787 channel_mask=SetImageChannelMask(image,GreenChannel);
2788 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2790 (void) SetImageChannelMask(image,channel_mask);
2792 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2794 channel_mask=SetImageChannelMask(image,BlueChannel);
2795 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2797 (void) SetImageChannelMask(image,channel_mask);
2799 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2800 (image->colorspace == CMYKColorspace))
2802 channel_mask=SetImageChannelMask(image,BlackChannel);
2803 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2805 (void) SetImageChannelMask(image,channel_mask);
2807 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2808 (image->alpha_trait != UndefinedPixelTrait))
2810 channel_mask=SetImageChannelMask(image,AlphaChannel);
2811 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2813 (void) SetImageChannelMask(image,channel_mask);
2816 return(status != 0 ? MagickTrue : MagickFalse);
2820 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2824 % L i n e a r S t r e t c h I m a g e %
2828 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2830 % LinearStretchImage() discards any pixels below the black point and above
2831 % the white point and levels the remaining pixels.
2833 % The format of the LinearStretchImage method is:
2835 % MagickBooleanType LinearStretchImage(Image *image,
2836 % const double black_point,const double white_point,
2837 % ExceptionInfo *exception)
2839 % A description of each parameter follows:
2841 % o image: the image.
2843 % o black_point: the black point.
2845 % o white_point: the white point.
2847 % o exception: return any errors or warnings in this structure.
2850 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2851 const double black_point,const double white_point,ExceptionInfo *exception)
2853 #define LinearStretchImageTag "LinearStretch/Image"
2871 Allocate histogram and linear map.
2873 assert(image != (Image *) NULL);
2874 assert(image->signature == MagickCoreSignature);
2875 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2876 if (histogram == (double *) NULL)
2877 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2882 (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
2883 image_view=AcquireVirtualCacheView(image,exception);
2884 for (y=0; y < (ssize_t) image->rows; y++)
2886 register const Quantum
2892 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2893 if (p == (const Quantum *) NULL)
2895 for (x=0; x < (ssize_t) image->columns; x++)
2897 intensity=GetPixelIntensity(image,p);
2898 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2899 p+=GetPixelChannels(image);
2902 image_view=DestroyCacheView(image_view);
2904 Find the histogram boundaries by locating the black and white point levels.
2907 for (black=0; black < (ssize_t) MaxMap; black++)
2909 intensity+=histogram[black];
2910 if (intensity >= black_point)
2914 for (white=(ssize_t) MaxMap; white != 0; white--)
2916 intensity+=histogram[white];
2917 if (intensity >= white_point)
2920 histogram=(double *) RelinquishMagickMemory(histogram);
2921 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2922 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2928 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2932 % M o d u l a t e I m a g e %
2936 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2938 % ModulateImage() lets you control the brightness, saturation, and hue
2939 % of an image. Modulate represents the brightness, saturation, and hue
2940 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2941 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2942 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2944 % The format of the ModulateImage method is:
2946 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2947 % ExceptionInfo *exception)
2949 % A description of each parameter follows:
2951 % o image: the image.
2953 % o modulate: Define the percent change in brightness, saturation, and hue.
2955 % o exception: return any errors or warnings in this structure.
2959 static inline void ModulateHCL(const double percent_hue,
2960 const double percent_chroma,const double percent_luma,double *red,
2961 double *green,double *blue)
2969 Increase or decrease color luma, chroma, or hue.
2971 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2972 hue+=fmod((percent_hue-100.0),200.0)/200.0;
2973 chroma*=0.01*percent_chroma;
2974 luma*=0.01*percent_luma;
2975 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2978 static inline void ModulateHCLp(const double percent_hue,
2979 const double percent_chroma,const double percent_luma,double *red,
2980 double *green,double *blue)
2988 Increase or decrease color luma, chroma, or hue.
2990 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2991 hue+=fmod((percent_hue-100.0),200.0)/200.0;
2992 chroma*=0.01*percent_chroma;
2993 luma*=0.01*percent_luma;
2994 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2997 static inline void ModulateHSB(const double percent_hue,
2998 const double percent_saturation,const double percent_brightness,double *red,
2999 double *green,double *blue)
3007 Increase or decrease color brightness, saturation, or hue.
3009 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3010 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3011 saturation*=0.01*percent_saturation;
3012 brightness*=0.01*percent_brightness;
3013 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3016 static inline void ModulateHSI(const double percent_hue,
3017 const double percent_saturation,const double percent_intensity,double *red,
3018 double *green,double *blue)
3026 Increase or decrease color intensity, saturation, or hue.
3028 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3029 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3030 saturation*=0.01*percent_saturation;
3031 intensity*=0.01*percent_intensity;
3032 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3035 static inline void ModulateHSL(const double percent_hue,
3036 const double percent_saturation,const double percent_lightness,double *red,
3037 double *green,double *blue)
3045 Increase or decrease color lightness, saturation, or hue.
3047 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3048 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3049 saturation*=0.01*percent_saturation;
3050 lightness*=0.01*percent_lightness;
3051 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3054 static inline void ModulateHSV(const double percent_hue,
3055 const double percent_saturation,const double percent_value,double *red,
3056 double *green,double *blue)
3064 Increase or decrease color value, saturation, or hue.
3066 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3067 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3068 saturation*=0.01*percent_saturation;
3069 value*=0.01*percent_value;
3070 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3073 static inline void ModulateHWB(const double percent_hue,
3074 const double percent_whiteness,const double percent_blackness,double *red,
3075 double *green,double *blue)
3083 Increase or decrease color blackness, whiteness, or hue.
3085 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3086 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3087 blackness*=0.01*percent_blackness;
3088 whiteness*=0.01*percent_whiteness;
3089 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3092 static inline void ModulateLCHab(const double percent_luma,
3093 const double percent_chroma,const double percent_hue,double *red,
3094 double *green,double *blue)
3102 Increase or decrease color luma, chroma, or hue.
3104 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3105 luma*=0.01*percent_luma;
3106 chroma*=0.01*percent_chroma;
3107 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3108 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3111 static inline void ModulateLCHuv(const double percent_luma,
3112 const double percent_chroma,const double percent_hue,double *red,
3113 double *green,double *blue)
3121 Increase or decrease color luma, chroma, or hue.
3123 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3124 luma*=0.01*percent_luma;
3125 chroma*=0.01*percent_chroma;
3126 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3127 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3130 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3131 ExceptionInfo *exception)
3133 #define ModulateImageTag "Modulate/Image"
3168 Initialize modulate table.
3170 assert(image != (Image *) NULL);
3171 assert(image->signature == MagickCoreSignature);
3172 if (image->debug != MagickFalse)
3173 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3174 if (modulate == (char *) NULL)
3175 return(MagickFalse);
3176 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3177 (void) SetImageColorspace(image,sRGBColorspace,exception);
3178 flags=ParseGeometry(modulate,&geometry_info);
3179 percent_brightness=geometry_info.rho;
3180 percent_saturation=geometry_info.sigma;
3181 if ((flags & SigmaValue) == 0)
3182 percent_saturation=100.0;
3183 percent_hue=geometry_info.xi;
3184 if ((flags & XiValue) == 0)
3186 colorspace=UndefinedColorspace;
3187 artifact=GetImageArtifact(image,"modulate:colorspace");
3188 if (artifact != (const char *) NULL)
3189 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3190 MagickFalse,artifact);
3191 if (image->storage_class == PseudoClass)
3192 for (i=0; i < (ssize_t) image->colors; i++)
3200 Modulate image colormap.
3202 red=(double) image->colormap[i].red;
3203 green=(double) image->colormap[i].green;
3204 blue=(double) image->colormap[i].blue;
3209 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3213 case HCLpColorspace:
3215 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3221 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3227 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3234 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3240 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3246 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3251 case LCHabColorspace:
3253 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3257 case LCHuvColorspace:
3259 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3264 image->colormap[i].red=red;
3265 image->colormap[i].green=green;
3266 image->colormap[i].blue=blue;
3271 #if defined(MAGICKCORE_OPENCL_SUPPORT)
3272 if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3273 percent_saturation,colorspace,exception) != MagickFalse)
3278 image_view=AcquireAuthenticCacheView(image,exception);
3279 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3280 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3281 magick_number_threads(image,image,image->rows,1)
3283 for (y=0; y < (ssize_t) image->rows; y++)
3291 if (status == MagickFalse)
3293 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3294 if (q == (Quantum *) NULL)
3299 for (x=0; x < (ssize_t) image->columns; x++)
3306 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
3308 q+=GetPixelChannels(image);
3311 red=(double) GetPixelRed(image,q);
3312 green=(double) GetPixelGreen(image,q);
3313 blue=(double) GetPixelBlue(image,q);
3318 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3322 case HCLpColorspace:
3324 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3330 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3337 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3343 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3349 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3353 case LCHabColorspace:
3355 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3360 case LCHuvColorspace:
3362 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3367 SetPixelRed(image,ClampToQuantum(red),q);
3368 SetPixelGreen(image,ClampToQuantum(green),q);
3369 SetPixelBlue(image,ClampToQuantum(blue),q);
3370 q+=GetPixelChannels(image);
3372 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3374 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3379 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3380 #pragma omp critical (MagickCore_ModulateImage)
3382 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3383 if (proceed == MagickFalse)
3387 image_view=DestroyCacheView(image_view);
3392 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3396 % N e g a t e I m a g e %
3400 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3402 % NegateImage() negates the colors in the reference image. The grayscale
3403 % option means that only grayscale values within the image are negated.
3405 % The format of the NegateImage method is:
3407 % MagickBooleanType NegateImage(Image *image,
3408 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3410 % A description of each parameter follows:
3412 % o image: the image.
3414 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3416 % o exception: return any errors or warnings in this structure.
3419 MagickExport MagickBooleanType NegateImage(Image *image,
3420 const MagickBooleanType grayscale,ExceptionInfo *exception)
3422 #define NegateImageTag "Negate/Image"
3439 assert(image != (Image *) NULL);
3440 assert(image->signature == MagickCoreSignature);
3441 if (image->debug != MagickFalse)
3442 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3443 if (image->storage_class == PseudoClass)
3444 for (i=0; i < (ssize_t) image->colors; i++)
3449 if( grayscale != MagickFalse )
3450 if ((image->colormap[i].red != image->colormap[i].green) ||
3451 (image->colormap[i].green != image->colormap[i].blue))
3453 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3454 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3455 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3456 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3457 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3458 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3465 image_view=AcquireAuthenticCacheView(image,exception);
3466 if( grayscale != MagickFalse )
3468 for (y=0; y < (ssize_t) image->rows; y++)
3479 if (status == MagickFalse)
3481 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3483 if (q == (Quantum *) NULL)
3488 for (x=0; x < (ssize_t) image->columns; x++)
3493 if ((GetPixelWriteMask(image,q) <= (QuantumRange/2)) ||
3494 IsPixelGray(image,q) != MagickFalse)
3496 q+=GetPixelChannels(image);
3499 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3501 PixelChannel channel = GetPixelChannelChannel(image,j);
3502 PixelTrait traits = GetPixelChannelTraits(image,channel);
3503 if ((traits & UpdatePixelTrait) == 0)
3505 q[j]=QuantumRange-q[j];
3507 q+=GetPixelChannels(image);
3509 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3510 if (sync == MagickFalse)
3512 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3517 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3518 #pragma omp critical (MagickCore_NegateImage)
3520 proceed=SetImageProgress(image,NegateImageTag,progress++,
3522 if (proceed == MagickFalse)
3526 image_view=DestroyCacheView(image_view);
3532 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3533 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3534 magick_number_threads(image,image,image->rows,1)
3536 for (y=0; y < (ssize_t) image->rows; y++)
3544 if (status == MagickFalse)
3546 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3547 if (q == (Quantum *) NULL)
3552 for (x=0; x < (ssize_t) image->columns; x++)
3557 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
3559 q+=GetPixelChannels(image);
3562 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3564 PixelChannel channel = GetPixelChannelChannel(image,j);
3565 PixelTrait traits = GetPixelChannelTraits(image,channel);
3566 if ((traits & UpdatePixelTrait) == 0)
3568 q[j]=QuantumRange-q[j];
3570 q+=GetPixelChannels(image);
3572 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3574 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3579 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3580 #pragma omp critical (MagickCore_NegateImage)
3582 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3583 if (proceed == MagickFalse)
3587 image_view=DestroyCacheView(image_view);
3592 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3596 % N o r m a l i z e I m a g e %
3600 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3602 % The NormalizeImage() method enhances the contrast of a color image by
3603 % mapping the darkest 2 percent of all pixel to black and the brightest
3604 % 1 percent to white.
3606 % The format of the NormalizeImage method is:
3608 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3610 % A description of each parameter follows:
3612 % o image: the image.
3614 % o exception: return any errors or warnings in this structure.
3617 MagickExport MagickBooleanType NormalizeImage(Image *image,
3618 ExceptionInfo *exception)
3624 black_point=(double) image->columns*image->rows*0.0015;
3625 white_point=(double) image->columns*image->rows*0.9995;
3626 return(ContrastStretchImage(image,black_point,white_point,exception));
3630 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3634 % S i g m o i d a l C o n t r a s t I m a g e %
3638 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3640 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3641 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3642 % sigmoidal transfer function without saturating highlights or shadows.
3643 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3644 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3645 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3646 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3649 % The format of the SigmoidalContrastImage method is:
3651 % MagickBooleanType SigmoidalContrastImage(Image *image,
3652 % const MagickBooleanType sharpen,const char *levels,
3653 % ExceptionInfo *exception)
3655 % A description of each parameter follows:
3657 % o image: the image.
3659 % o sharpen: Increase or decrease image contrast.
3661 % o contrast: strength of the contrast, the larger the number the more
3662 % 'threshold-like' it becomes.
3664 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3666 % o exception: return any errors or warnings in this structure.
3671 ImageMagick 6 has a version of this function which uses LUTs.
3675 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3678 The first version, based on the hyperbolic tangent tanh, when combined with
3679 the scaling step, is an exact arithmetic clone of the the sigmoid function
3680 based on the logistic curve. The equivalence is based on the identity
3682 1/(1+exp(-t)) = (1+tanh(t/2))/2
3684 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3685 scaled sigmoidal derivation is invariant under affine transformations of
3688 The tanh version is almost certainly more accurate and cheaper. The 0.5
3689 factor in the argument is to clone the legacy ImageMagick behavior. The
3690 reason for making the define depend on atanh even though it only uses tanh
3691 has to do with the construction of the inverse of the scaled sigmoidal.
3693 #if defined(MAGICKCORE_HAVE_ATANH)
3694 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3696 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3699 Scaled sigmoidal function:
3701 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3702 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3704 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3705 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3706 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3707 zero. This is fixed below by exiting immediately when contrast is small,
3708 leaving the image (or colormap) unmodified. This appears to be safe because
3709 the series expansion of the logistic sigmoidal function around x=b is
3713 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3715 #define ScaledSigmoidal(a,b,x) ( \
3716 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3717 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3719 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3720 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3721 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3722 when creating a LUT from in gamut values, hence the branching. In
3723 addition, HDRI may have out of gamut values.
3724 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3725 It is only a right inverse. This is unavoidable.
3727 static inline double InverseScaledSigmoidal(const double a,const double b,
3730 const double sig0=Sigmoidal(a,b,0.0);
3731 const double sig1=Sigmoidal(a,b,1.0);
3732 const double argument=(sig1-sig0)*x+sig0;
3733 const double clamped=
3735 #if defined(MAGICKCORE_HAVE_ATANH)
3736 argument < -1+MagickEpsilon
3740 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3742 return(b+(2.0/a)*atanh(clamped));
3744 argument < MagickEpsilon
3748 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3750 return(b-log(1.0/clamped-1.0)/a);
3754 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3755 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3756 ExceptionInfo *exception)
3758 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3759 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3760 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3761 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3762 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3779 assert(image != (Image *) NULL);
3780 assert(image->signature == MagickCoreSignature);
3781 if (image->debug != MagickFalse)
3782 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3784 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3785 case nothing is done.
3787 if (contrast < MagickEpsilon)
3790 Sigmoidal-contrast enhance colormap.
3792 if (image->storage_class == PseudoClass)
3797 if( sharpen != MagickFalse )
3798 for (i=0; i < (ssize_t) image->colors; i++)
3800 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3801 image->colormap[i].red=(MagickRealType) ScaledSig(
3802 image->colormap[i].red);
3803 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3804 image->colormap[i].green=(MagickRealType) ScaledSig(
3805 image->colormap[i].green);
3806 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3807 image->colormap[i].blue=(MagickRealType) ScaledSig(
3808 image->colormap[i].blue);
3809 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3810 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3811 image->colormap[i].alpha);
3814 for (i=0; i < (ssize_t) image->colors; i++)
3816 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3817 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3818 image->colormap[i].red);
3819 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3820 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3821 image->colormap[i].green);
3822 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3823 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3824 image->colormap[i].blue);
3825 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3826 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3827 image->colormap[i].alpha);
3831 Sigmoidal-contrast enhance image.
3835 image_view=AcquireAuthenticCacheView(image,exception);
3836 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3837 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3838 magick_number_threads(image,image,image->rows,1)
3840 for (y=0; y < (ssize_t) image->rows; y++)
3848 if (status == MagickFalse)
3850 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3851 if (q == (Quantum *) NULL)
3856 for (x=0; x < (ssize_t) image->columns; x++)
3861 if (GetPixelWriteMask(image,q) <= (QuantumRange/2))
3863 q+=GetPixelChannels(image);
3866 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3868 PixelChannel channel = GetPixelChannelChannel(image,i);
3869 PixelTrait traits = GetPixelChannelTraits(image,channel);
3870 if ((traits & UpdatePixelTrait) == 0)
3872 if( sharpen != MagickFalse )
3873 q[i]=ScaledSig(q[i]);
3875 q[i]=InverseScaledSig(q[i]);
3877 q+=GetPixelChannels(image);
3879 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3881 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3886 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3887 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3889 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3891 if (proceed == MagickFalse)
3895 image_view=DestroyCacheView(image_view);