2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % EEEEE N N H H AAA N N CCCC EEEEE %
7 % E NN N H H A A NN N C E %
8 % EEE N N N HHHHH AAAAA N N N C EEE %
9 % E N NN H H A A N NN C E %
10 % EEEEE N N H H A A N N CCCC EEEEE %
13 % MagickCore Image Enhancement Methods %
20 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/accelerate.h"
45 #include "MagickCore/artifact.h"
46 #include "MagickCore/attribute.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/cache-view.h"
49 #include "MagickCore/channel.h"
50 #include "MagickCore/color.h"
51 #include "MagickCore/color-private.h"
52 #include "MagickCore/colorspace.h"
53 #include "MagickCore/colorspace-private.h"
54 #include "MagickCore/composite-private.h"
55 #include "MagickCore/enhance.h"
56 #include "MagickCore/exception.h"
57 #include "MagickCore/exception-private.h"
58 #include "MagickCore/fx.h"
59 #include "MagickCore/gem.h"
60 #include "MagickCore/gem-private.h"
61 #include "MagickCore/geometry.h"
62 #include "MagickCore/histogram.h"
63 #include "MagickCore/image.h"
64 #include "MagickCore/image-private.h"
65 #include "MagickCore/memory_.h"
66 #include "MagickCore/monitor.h"
67 #include "MagickCore/monitor-private.h"
68 #include "MagickCore/option.h"
69 #include "MagickCore/pixel.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/quantum.h"
72 #include "MagickCore/quantum-private.h"
73 #include "MagickCore/resample.h"
74 #include "MagickCore/resample-private.h"
75 #include "MagickCore/resource_.h"
76 #include "MagickCore/statistic.h"
77 #include "MagickCore/string_.h"
78 #include "MagickCore/string-private.h"
79 #include "MagickCore/thread-private.h"
80 #include "MagickCore/threshold.h"
81 #include "MagickCore/token.h"
82 #include "MagickCore/xml-tree.h"
83 #include "MagickCore/xml-tree-private.h"
86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90 % A u t o G a m m a I m a g e %
94 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96 % AutoGammaImage() extract the 'mean' from the image and adjust the image
97 % to try make set its gamma appropriatally.
99 % The format of the AutoGammaImage method is:
101 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
103 % A description of each parameter follows:
105 % o image: The image to auto-level
107 % o exception: return any errors or warnings in this structure.
110 MagickExport MagickBooleanType AutoGammaImage(Image *image,
111 ExceptionInfo *exception)
126 if (image->channel_mask == DefaultChannels)
129 Apply gamma correction equally across all given channels.
131 (void) GetImageMean(image,&mean,&sans,exception);
132 gamma=log(mean*QuantumScale)/log_mean;
133 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
136 Auto-gamma each channel separately.
139 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
144 PixelChannel channel=GetPixelChannelChannel(image,i);
145 PixelTrait traits=GetPixelChannelTraits(image,channel);
146 if ((traits & UpdatePixelTrait) == 0)
148 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
149 status=GetImageMean(image,&mean,&sans,exception);
150 gamma=log(mean*QuantumScale)/log_mean;
151 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
152 (void) SetImageChannelMask(image,channel_mask);
153 if (status == MagickFalse)
156 return(status != 0 ? MagickTrue : MagickFalse);
160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164 % A u t o L e v e l I m a g e %
168 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
170 % AutoLevelImage() adjusts the levels of a particular image channel by
171 % scaling the minimum and maximum values to the full quantum range.
173 % The format of the LevelImage method is:
175 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
177 % A description of each parameter follows:
179 % o image: The image to auto-level
181 % o exception: return any errors or warnings in this structure.
184 MagickExport MagickBooleanType AutoLevelImage(Image *image,
185 ExceptionInfo *exception)
187 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195 % B r i g h t n e s s C o n t r a s t I m a g e %
199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
201 % BrightnessContrastImage() changes the brightness and/or contrast of an
202 % image. It converts the brightness and contrast parameters into slope and
203 % intercept and calls a polynomical function to apply to the image.
205 % The format of the BrightnessContrastImage method is:
207 % MagickBooleanType BrightnessContrastImage(Image *image,
208 % const double brightness,const double contrast,ExceptionInfo *exception)
210 % A description of each parameter follows:
212 % o image: the image.
214 % o brightness: the brightness percent (-100 .. 100).
216 % o contrast: the contrast percent (-100 .. 100).
218 % o exception: return any errors or warnings in this structure.
221 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
222 const double brightness,const double contrast,ExceptionInfo *exception)
224 #define BrightnessContastImageTag "BrightnessContast/Image"
236 Compute slope and intercept.
238 assert(image != (Image *) NULL);
239 assert(image->signature == MagickCoreSignature);
240 if (image->debug != MagickFalse)
241 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
243 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
246 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
247 coefficients[0]=slope;
248 coefficients[1]=intercept;
249 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
254 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258 % C l u t I m a g e %
262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
264 % ClutImage() replaces each color value in the given image, by using it as an
265 % index to lookup a replacement color value in a Color Look UP Table in the
266 % form of an image. The values are extracted along a diagonal of the CLUT
267 % image so either a horizontal or vertial gradient image can be used.
269 % Typically this is used to either re-color a gray-scale image according to a
270 % color gradient in the CLUT image, or to perform a freeform histogram
271 % (level) adjustment according to the (typically gray-scale) gradient in the
274 % When the 'channel' mask includes the matte/alpha transparency channel but
275 % one image has no such channel it is assumed that that image is a simple
276 % gray-scale image that will effect the alpha channel values, either for
277 % gray-scale coloring (with transparent or semi-transparent colors), or
278 % a histogram adjustment of existing alpha channel values. If both images
279 % have matte channels, direct and normal indexing is applied, which is rarely
282 % The format of the ClutImage method is:
284 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
285 % const PixelInterpolateMethod method,ExceptionInfo *exception)
287 % A description of each parameter follows:
289 % o image: the image, which is replaced by indexed CLUT values
291 % o clut_image: the color lookup table image for replacement color values.
293 % o method: the pixel interpolation method.
295 % o exception: return any errors or warnings in this structure.
298 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
299 const PixelInterpolateMethod method,ExceptionInfo *exception)
301 #define ClutImageTag "Clut/Image"
322 assert(image != (Image *) NULL);
323 assert(image->signature == MagickCoreSignature);
324 if (image->debug != MagickFalse)
325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
326 assert(clut_image != (Image *) NULL);
327 assert(clut_image->signature == MagickCoreSignature);
328 if( SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
330 if( (IsGrayColorspace(image->colorspace) != MagickFalse) &&
331 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
332 (void) SetImageColorspace(image,sRGBColorspace,exception);
333 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
334 if (clut_map == (PixelInfo *) NULL)
335 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
342 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
343 clut_view=AcquireVirtualCacheView(clut_image,exception);
344 for (i=0; i <= (ssize_t) MaxMap; i++)
346 GetPixelInfo(clut_image,clut_map+i);
347 (void) InterpolatePixelInfo(clut_image,clut_view,method,
348 (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
349 (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
351 clut_view=DestroyCacheView(clut_view);
352 image_view=AcquireAuthenticCacheView(image,exception);
353 #if defined(MAGICKCORE_OPENMP_SUPPORT)
354 #pragma omp parallel for schedule(static,4) shared(progress,status) \
355 magick_threads(image,image,image->rows,1)
357 for (y=0; y < (ssize_t) image->rows; y++)
368 if (status == MagickFalse)
370 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
371 if (q == (Quantum *) NULL)
376 GetPixelInfo(image,&pixel);
377 for (x=0; x < (ssize_t) image->columns; x++)
382 if (GetPixelReadMask(image,q) == 0)
384 q+=GetPixelChannels(image);
387 GetPixelInfoPixel(image,q,&pixel);
388 traits=GetPixelChannelTraits(image,RedPixelChannel);
389 if ((traits & UpdatePixelTrait) != 0)
390 pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
392 traits=GetPixelChannelTraits(image,GreenPixelChannel);
393 if ((traits & UpdatePixelTrait) != 0)
394 pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
395 pixel.green))].green;
396 traits=GetPixelChannelTraits(image,BluePixelChannel);
397 if ((traits & UpdatePixelTrait) != 0)
398 pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
400 traits=GetPixelChannelTraits(image,BlackPixelChannel);
401 if ((traits & UpdatePixelTrait) != 0)
402 pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
403 pixel.black))].black;
404 traits=GetPixelChannelTraits(image,AlphaPixelChannel);
405 if ((traits & UpdatePixelTrait) != 0)
406 pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
407 pixel.alpha))].alpha;
408 SetPixelViaPixelInfo(image,&pixel,q);
409 q+=GetPixelChannels(image);
411 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
413 if (image->progress_monitor != (MagickProgressMonitor) NULL)
418 #if defined(MAGICKCORE_OPENMP_SUPPORT)
419 #pragma omp critical (MagickCore_ClutImage)
421 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
422 if (proceed == MagickFalse)
426 image_view=DestroyCacheView(image_view);
427 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
428 if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
429 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
430 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
439 % C o l o r D e c i s i o n L i s t I m a g e %
443 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
445 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
446 % (CCC) file which solely contains one or more color corrections and applies
447 % the correction to the image. Here is a sample CCC file:
449 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
450 % <ColorCorrection id="cc03345">
452 % <Slope> 0.9 1.2 0.5 </Slope>
453 % <Offset> 0.4 -0.5 0.6 </Offset>
454 % <Power> 1.0 0.8 1.5 </Power>
457 % <Saturation> 0.85 </Saturation>
460 % </ColorCorrectionCollection>
462 % which includes the slop, offset, and power for each of the RGB channels
463 % as well as the saturation.
465 % The format of the ColorDecisionListImage method is:
467 % MagickBooleanType ColorDecisionListImage(Image *image,
468 % const char *color_correction_collection,ExceptionInfo *exception)
470 % A description of each parameter follows:
472 % o image: the image.
474 % o color_correction_collection: the color correction collection in XML.
476 % o exception: return any errors or warnings in this structure.
479 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
480 const char *color_correction_collection,ExceptionInfo *exception)
482 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
484 typedef struct _Correction
492 typedef struct _ColorCorrection
507 token[MagickPathExtent];
538 Allocate and initialize cdl maps.
540 assert(image != (Image *) NULL);
541 assert(image->signature == MagickCoreSignature);
542 if (image->debug != MagickFalse)
543 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
544 if (color_correction_collection == (const char *) NULL)
546 ccc=NewXMLTree((const char *) color_correction_collection,exception);
547 if (ccc == (XMLTreeInfo *) NULL)
549 cc=GetXMLTreeChild(ccc,"ColorCorrection");
550 if (cc == (XMLTreeInfo *) NULL)
552 ccc=DestroyXMLTree(ccc);
555 color_correction.red.slope=1.0;
556 color_correction.red.offset=0.0;
557 color_correction.red.power=1.0;
558 color_correction.green.slope=1.0;
559 color_correction.green.offset=0.0;
560 color_correction.green.power=1.0;
561 color_correction.blue.slope=1.0;
562 color_correction.blue.offset=0.0;
563 color_correction.blue.power=1.0;
564 color_correction.saturation=0.0;
565 sop=GetXMLTreeChild(cc,"SOPNode");
566 if (sop != (XMLTreeInfo *) NULL)
573 slope=GetXMLTreeChild(sop,"Slope");
574 if (slope != (XMLTreeInfo *) NULL)
576 content=GetXMLTreeContent(slope);
577 p=(const char *) content;
578 for (i=0; (*p != '\0') && (i < 3); i++)
580 GetMagickToken(p,&p,token);
582 GetMagickToken(p,&p,token);
587 color_correction.red.slope=StringToDouble(token,(char **) NULL);
592 color_correction.green.slope=StringToDouble(token,
598 color_correction.blue.slope=StringToDouble(token,
605 offset=GetXMLTreeChild(sop,"Offset");
606 if (offset != (XMLTreeInfo *) NULL)
608 content=GetXMLTreeContent(offset);
609 p=(const char *) content;
610 for (i=0; (*p != '\0') && (i < 3); i++)
612 GetMagickToken(p,&p,token);
614 GetMagickToken(p,&p,token);
619 color_correction.red.offset=StringToDouble(token,
625 color_correction.green.offset=StringToDouble(token,
631 color_correction.blue.offset=StringToDouble(token,
638 power=GetXMLTreeChild(sop,"Power");
639 if (power != (XMLTreeInfo *) NULL)
641 content=GetXMLTreeContent(power);
642 p=(const char *) content;
643 for (i=0; (*p != '\0') && (i < 3); i++)
645 GetMagickToken(p,&p,token);
647 GetMagickToken(p,&p,token);
652 color_correction.red.power=StringToDouble(token,(char **) NULL);
657 color_correction.green.power=StringToDouble(token,
663 color_correction.blue.power=StringToDouble(token,
671 sat=GetXMLTreeChild(cc,"SATNode");
672 if (sat != (XMLTreeInfo *) NULL)
677 saturation=GetXMLTreeChild(sat,"Saturation");
678 if (saturation != (XMLTreeInfo *) NULL)
680 content=GetXMLTreeContent(saturation);
681 p=(const char *) content;
682 GetMagickToken(p,&p,token);
683 color_correction.saturation=StringToDouble(token,(char **) NULL);
686 ccc=DestroyXMLTree(ccc);
687 if (image->debug != MagickFalse)
689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
690 " Color Correction Collection:");
691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
692 " color_correction.red.slope: %g",color_correction.red.slope);
693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
694 " color_correction.red.offset: %g",color_correction.red.offset);
695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
696 " color_correction.red.power: %g",color_correction.red.power);
697 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
698 " color_correction.green.slope: %g",color_correction.green.slope);
699 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
700 " color_correction.green.offset: %g",color_correction.green.offset);
701 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
702 " color_correction.green.power: %g",color_correction.green.power);
703 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
704 " color_correction.blue.slope: %g",color_correction.blue.slope);
705 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
706 " color_correction.blue.offset: %g",color_correction.blue.offset);
707 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
708 " color_correction.blue.power: %g",color_correction.blue.power);
709 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
710 " color_correction.saturation: %g",color_correction.saturation);
712 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
713 if (cdl_map == (PixelInfo *) NULL)
714 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
716 for (i=0; i <= (ssize_t) MaxMap; i++)
718 cdl_map[i].red=(double) ScaleMapToQuantum((double)
719 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
720 color_correction.red.offset,color_correction.red.power))));
721 cdl_map[i].green=(double) ScaleMapToQuantum((double)
722 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
723 color_correction.green.offset,color_correction.green.power))));
724 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
725 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
726 color_correction.blue.offset,color_correction.blue.power))));
728 if (image->storage_class == PseudoClass)
729 for (i=0; i < (ssize_t) image->colors; i++)
732 Apply transfer function to colormap.
737 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
738 0.07217f*image->colormap[i].blue;
739 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
740 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
741 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
742 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
743 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
744 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
747 Apply transfer function to image.
751 image_view=AcquireAuthenticCacheView(image,exception);
752 #if defined(MAGICKCORE_OPENMP_SUPPORT)
753 #pragma omp parallel for schedule(static,4) shared(progress,status) \
754 magick_threads(image,image,image->rows,1)
756 for (y=0; y < (ssize_t) image->rows; y++)
767 if (status == MagickFalse)
769 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
770 if (q == (Quantum *) NULL)
775 for (x=0; x < (ssize_t) image->columns; x++)
777 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
778 0.07217f*GetPixelBlue(image,q);
779 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
780 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
781 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
782 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
783 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
784 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
785 q+=GetPixelChannels(image);
787 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
789 if (image->progress_monitor != (MagickProgressMonitor) NULL)
794 #if defined(MAGICKCORE_OPENMP_SUPPORT)
795 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
797 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
798 progress++,image->rows);
799 if (proceed == MagickFalse)
803 image_view=DestroyCacheView(image_view);
804 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
809 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
813 % C o n t r a s t I m a g e %
817 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
819 % ContrastImage() enhances the intensity differences between the lighter and
820 % darker elements of the image. Set sharpen to a MagickTrue to increase the
821 % image contrast otherwise the contrast is reduced.
823 % The format of the ContrastImage method is:
825 % MagickBooleanType ContrastImage(Image *image,
826 % const MagickBooleanType sharpen,ExceptionInfo *exception)
828 % A description of each parameter follows:
830 % o image: the image.
832 % o sharpen: Increase or decrease image contrast.
834 % o exception: return any errors or warnings in this structure.
838 static void Contrast(const int sign,double *red,double *green,double *blue)
846 Enhance contrast: dark color become darker, light color become lighter.
848 assert(red != (double *) NULL);
849 assert(green != (double *) NULL);
850 assert(blue != (double *) NULL);
854 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
855 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
857 if (brightness > 1.0)
860 if (brightness < 0.0)
862 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
865 MagickExport MagickBooleanType ContrastImage(Image *image,
866 const MagickBooleanType sharpen,ExceptionInfo *exception)
868 #define ContrastImageTag "Contrast/Image"
888 assert(image != (Image *) NULL);
889 assert(image->signature == MagickCoreSignature);
890 if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
892 if (image->debug != MagickFalse)
893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
894 sign=sharpen != MagickFalse ? 1 : -1;
895 if (image->storage_class == PseudoClass)
898 Contrast enhance colormap.
900 for (i=0; i < (ssize_t) image->colors; i++)
907 red=(double) image->colormap[i].red;
908 green=(double) image->colormap[i].green;
909 blue=(double) image->colormap[i].blue;
910 Contrast(sign,&red,&green,&blue);
911 image->colormap[i].red=(MagickRealType) red;
912 image->colormap[i].green=(MagickRealType) green;
913 image->colormap[i].blue=(MagickRealType) blue;
917 Contrast enhance image.
921 image_view=AcquireAuthenticCacheView(image,exception);
922 #if defined(MAGICKCORE_OPENMP_SUPPORT)
923 #pragma omp parallel for schedule(static,4) shared(progress,status) \
924 magick_threads(image,image,image->rows,1)
926 for (y=0; y < (ssize_t) image->rows; y++)
939 if (status == MagickFalse)
941 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
942 if (q == (Quantum *) NULL)
947 for (x=0; x < (ssize_t) image->columns; x++)
949 red=(double) GetPixelRed(image,q);
950 green=(double) GetPixelGreen(image,q);
951 blue=(double) GetPixelBlue(image,q);
952 Contrast(sign,&red,&green,&blue);
953 SetPixelRed(image,ClampToQuantum(red),q);
954 SetPixelGreen(image,ClampToQuantum(green),q);
955 SetPixelBlue(image,ClampToQuantum(blue),q);
956 q+=GetPixelChannels(image);
958 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
960 if (image->progress_monitor != (MagickProgressMonitor) NULL)
965 #if defined(MAGICKCORE_OPENMP_SUPPORT)
966 #pragma omp critical (MagickCore_ContrastImage)
968 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
969 if (proceed == MagickFalse)
973 image_view=DestroyCacheView(image_view);
978 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982 % C o n t r a s t S t r e t c h I m a g e %
986 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988 % ContrastStretchImage() is a simple image enhancement technique that attempts
989 % to improve the contrast in an image by 'stretching' the range of intensity
990 % values it contains to span a desired range of values. It differs from the
991 % more sophisticated histogram equalization in that it can only apply a
992 % linear scaling function to the image pixel values. As a result the
993 % 'enhancement' is less harsh.
995 % The format of the ContrastStretchImage method is:
997 % MagickBooleanType ContrastStretchImage(Image *image,
998 % const char *levels,ExceptionInfo *exception)
1000 % A description of each parameter follows:
1002 % o image: the image.
1004 % o black_point: the black point.
1006 % o white_point: the white point.
1008 % o levels: Specify the levels where the black and white points have the
1009 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1011 % o exception: return any errors or warnings in this structure.
1014 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1015 const double black_point,const double white_point,ExceptionInfo *exception)
1017 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1018 #define ContrastStretchImageTag "ContrastStretch/Image"
1042 Allocate histogram and stretch map.
1044 assert(image != (Image *) NULL);
1045 assert(image->signature == MagickCoreSignature);
1046 if (image->debug != MagickFalse)
1047 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1048 if (SetImageGray(image,exception) != MagickFalse)
1049 (void) SetImageColorspace(image,GRAYColorspace,exception);
1050 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1051 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1052 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1053 sizeof(*histogram));
1054 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1055 GetPixelChannels(image)*sizeof(*stretch_map));
1056 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1057 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1059 if (stretch_map != (double *) NULL)
1060 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1061 if (histogram != (double *) NULL)
1062 histogram=(double *) RelinquishMagickMemory(histogram);
1063 if (white != (double *) NULL)
1064 white=(double *) RelinquishMagickMemory(white);
1065 if (black != (double *) NULL)
1066 black=(double *) RelinquishMagickMemory(black);
1067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1074 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1075 sizeof(*histogram));
1076 image_view=AcquireVirtualCacheView(image,exception);
1077 for (y=0; y < (ssize_t) image->rows; y++)
1079 register const Quantum
1085 if (status == MagickFalse)
1087 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1088 if (p == (const Quantum *) NULL)
1093 for (x=0; x < (ssize_t) image->columns; x++)
1098 pixel=GetPixelIntensity(image,p);
1099 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1101 if (image->channel_mask != DefaultChannels)
1102 pixel=(double) p[i];
1103 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1104 ClampToQuantum(pixel))+i]++;
1106 p+=GetPixelChannels(image);
1109 image_view=DestroyCacheView(image_view);
1111 Find the histogram boundaries by locating the black/white levels.
1113 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1122 white[i]=MaxRange(QuantumRange);
1124 for (j=0; j <= (ssize_t) MaxMap; j++)
1126 intensity+=histogram[GetPixelChannels(image)*j+i];
1127 if (intensity > black_point)
1130 black[i]=(double) j;
1132 for (j=(ssize_t) MaxMap; j != 0; j--)
1134 intensity+=histogram[GetPixelChannels(image)*j+i];
1135 if (intensity > ((double) image->columns*image->rows-white_point))
1138 white[i]=(double) j;
1140 histogram=(double *) RelinquishMagickMemory(histogram);
1142 Stretch the histogram to create the stretched image mapping.
1144 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1145 sizeof(*stretch_map));
1146 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1151 for (j=0; j <= (ssize_t) MaxMap; j++)
1156 gamma=PerceptibleReciprocal(white[i]-black[i]);
1157 if (j < (ssize_t) black[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1160 if (j > (ssize_t) white[i])
1161 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1163 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1164 (double) (MaxMap*gamma*(j-black[i])));
1167 if (image->storage_class == PseudoClass)
1173 Stretch-contrast colormap.
1175 for (j=0; j < (ssize_t) image->colors; j++)
1177 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1179 i=GetPixelChannelOffset(image,RedPixelChannel);
1180 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1183 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelOffset(image,GreenPixelChannel);
1186 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1189 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1191 i=GetPixelChannelOffset(image,BluePixelChannel);
1192 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1193 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1195 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1197 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1198 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1199 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1204 Stretch-contrast image.
1208 image_view=AcquireAuthenticCacheView(image,exception);
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1211 magick_threads(image,image,image->rows,1)
1213 for (y=0; y < (ssize_t) image->rows; y++)
1221 if (status == MagickFalse)
1223 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1224 if (q == (Quantum *) NULL)
1229 for (x=0; x < (ssize_t) image->columns; x++)
1234 if (GetPixelReadMask(image,q) == 0)
1236 q+=GetPixelChannels(image);
1239 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1241 PixelChannel channel=GetPixelChannelChannel(image,j);
1242 PixelTrait traits=GetPixelChannelTraits(image,channel);
1243 if ((traits & UpdatePixelTrait) == 0)
1245 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1246 ScaleQuantumToMap(q[j])+j]);
1248 q+=GetPixelChannels(image);
1250 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1252 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1258 #pragma omp critical (MagickCore_ContrastStretchImage)
1260 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1262 if (proceed == MagickFalse)
1266 image_view=DestroyCacheView(image_view);
1267 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1268 white=(double *) RelinquishMagickMemory(white);
1269 black=(double *) RelinquishMagickMemory(black);
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1278 % E n h a n c e I m a g e %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284 % EnhanceImage() applies a digital filter that improves the quality of a
1287 % The format of the EnhanceImage method is:
1289 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1291 % A description of each parameter follows:
1293 % o image: the image.
1295 % o exception: return any errors or warnings in this structure.
1298 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1300 #define EnhanceImageTag "Enhance/Image"
1301 #define EnhancePixel(weight) \
1302 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1303 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1304 distance_squared=(4.0+mean)*distance*distance; \
1305 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1306 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1307 distance_squared+=(7.0-mean)*distance*distance; \
1308 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1309 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1310 distance_squared+=(5.0-mean)*distance*distance; \
1311 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1312 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1313 distance_squared+=(5.0-mean)*distance*distance; \
1314 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1315 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1316 distance_squared+=(5.0-mean)*distance*distance; \
1317 if (distance_squared < 0.069) \
1319 aggregate.red+=(weight)*GetPixelRed(image,r); \
1320 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1321 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1322 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1323 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1324 total_weight+=(weight); \
1326 r+=GetPixelChannels(image);
1345 Initialize enhanced image attributes.
1347 assert(image != (const Image *) NULL);
1348 assert(image->signature == MagickCoreSignature);
1349 if (image->debug != MagickFalse)
1350 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1351 assert(exception != (ExceptionInfo *) NULL);
1352 assert(exception->signature == MagickCoreSignature);
1353 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1355 if (enhance_image == (Image *) NULL)
1356 return((Image *) NULL);
1357 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1359 enhance_image=DestroyImage(enhance_image);
1360 return((Image *) NULL);
1367 image_view=AcquireVirtualCacheView(image,exception);
1368 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1370 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1371 magick_threads(image,enhance_image,image->rows,1)
1373 for (y=0; y < (ssize_t) image->rows; y++)
1378 register const Quantum
1390 if (status == MagickFalse)
1392 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1393 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1395 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1400 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1401 GetPixelInfo(image,&pixel);
1402 for (x=0; x < (ssize_t) image->columns; x++)
1413 register const Quantum
1416 if (GetPixelReadMask(image,p) == 0)
1418 SetPixelBackgoundColor(enhance_image,q);
1419 p+=GetPixelChannels(image);
1420 q+=GetPixelChannels(enhance_image);
1423 GetPixelInfo(image,&aggregate);
1425 GetPixelInfoPixel(image,p+center,&pixel);
1427 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1428 EnhancePixel(8.0); EnhancePixel(5.0);
1429 r=p+GetPixelChannels(image)*(image->columns+4);
1430 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1431 EnhancePixel(20.0); EnhancePixel(8.0);
1432 r=p+2*GetPixelChannels(image)*(image->columns+4);
1433 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1434 EnhancePixel(40.0); EnhancePixel(10.0);
1435 r=p+3*GetPixelChannels(image)*(image->columns+4);
1436 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1437 EnhancePixel(20.0); EnhancePixel(8.0);
1438 r=p+4*GetPixelChannels(image)*(image->columns+4);
1439 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1440 EnhancePixel(8.0); EnhancePixel(5.0);
1441 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1442 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1443 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1444 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1445 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1446 SetPixelViaPixelInfo(image,&pixel,q);
1447 p+=GetPixelChannels(image);
1448 q+=GetPixelChannels(enhance_image);
1450 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1452 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1458 #pragma omp critical (MagickCore_EnhanceImage)
1460 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1461 if (proceed == MagickFalse)
1465 enhance_view=DestroyCacheView(enhance_view);
1466 image_view=DestroyCacheView(image_view);
1467 if (status == MagickFalse)
1468 enhance_image=DestroyImage(enhance_image);
1469 return(enhance_image);
1473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477 % E q u a l i z e I m a g e %
1481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483 % EqualizeImage() applies a histogram equalization to the image.
1485 % The format of the EqualizeImage method is:
1487 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1489 % A description of each parameter follows:
1491 % o image: the image.
1493 % o exception: return any errors or warnings in this structure.
1496 MagickExport MagickBooleanType EqualizeImage(Image *image,
1497 ExceptionInfo *exception)
1499 #define EqualizeImageTag "Equalize/Image"
1505 black[CompositePixelChannel+1],
1509 white[CompositePixelChannel+1];
1524 Allocate and initialize histogram arrays.
1526 assert(image != (Image *) NULL);
1527 assert(image->signature == MagickCoreSignature);
1528 if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1530 if (image->debug != MagickFalse)
1531 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1532 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1533 GetPixelChannels(image)*sizeof(*equalize_map));
1534 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1535 sizeof(*histogram));
1536 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1538 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1539 (map == (double *) NULL))
1541 if (map != (double *) NULL)
1542 map=(double *) RelinquishMagickMemory(map);
1543 if (histogram != (double *) NULL)
1544 histogram=(double *) RelinquishMagickMemory(histogram);
1545 if (equalize_map != (double *) NULL)
1546 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1547 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1554 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1555 sizeof(*histogram));
1556 image_view=AcquireVirtualCacheView(image,exception);
1557 for (y=0; y < (ssize_t) image->rows; y++)
1559 register const Quantum
1565 if (status == MagickFalse)
1567 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1568 if (p == (const Quantum *) NULL)
1573 for (x=0; x < (ssize_t) image->columns; x++)
1575 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1577 double intensity=GetPixelIntensity(image,p);
1578 histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1580 p+=GetPixelChannels(image);
1583 image_view=DestroyCacheView(image_view);
1585 Integrate the histogram to get the equalization map.
1587 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1596 for (j=0; j <= (ssize_t) MaxMap; j++)
1598 intensity+=histogram[GetPixelChannels(image)*j+i];
1599 map[GetPixelChannels(image)*j+i]=intensity;
1602 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1603 sizeof(*equalize_map));
1604 (void) ResetMagickMemory(black,0,sizeof(*black));
1605 (void) ResetMagickMemory(white,0,sizeof(*white));
1606 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1612 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1613 if (black[i] != white[i])
1614 for (j=0; j <= (ssize_t) MaxMap; j++)
1615 equalize_map[GetPixelChannels(image)*j+i]=(double)
1616 ScaleMapToQuantum((double) ((MaxMap*(map[
1617 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1619 histogram=(double *) RelinquishMagickMemory(histogram);
1620 map=(double *) RelinquishMagickMemory(map);
1621 if (image->storage_class == PseudoClass)
1629 for (j=0; j < (ssize_t) image->colors; j++)
1631 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1633 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1634 if (black[channel] != white[channel])
1635 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1636 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1639 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1641 PixelChannel channel=GetPixelChannelChannel(image,
1643 if (black[channel] != white[channel])
1644 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1645 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1648 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1650 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1651 if (black[channel] != white[channel])
1652 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1653 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1656 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1658 PixelChannel channel=GetPixelChannelChannel(image,
1660 if (black[channel] != white[channel])
1661 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1662 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1671 image_view=AcquireAuthenticCacheView(image,exception);
1672 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1673 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1674 magick_threads(image,image,image->rows,1)
1676 for (y=0; y < (ssize_t) image->rows; y++)
1684 if (status == MagickFalse)
1686 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1687 if (q == (Quantum *) NULL)
1692 for (x=0; x < (ssize_t) image->columns; x++)
1697 if (GetPixelReadMask(image,q) == 0)
1699 q+=GetPixelChannels(image);
1702 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1704 PixelChannel channel=GetPixelChannelChannel(image,j);
1705 PixelTrait traits=GetPixelChannelTraits(image,channel);
1706 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1708 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1709 ScaleQuantumToMap(q[j])+j]);
1711 q+=GetPixelChannels(image);
1713 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1715 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1720 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1721 #pragma omp critical (MagickCore_EqualizeImage)
1723 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1724 if (proceed == MagickFalse)
1728 image_view=DestroyCacheView(image_view);
1729 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1734 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1738 % G a m m a I m a g e %
1742 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744 % GammaImage() gamma-corrects a particular image channel. The same
1745 % image viewed on different devices will have perceptual differences in the
1746 % way the image's intensities are represented on the screen. Specify
1747 % individual gamma levels for the red, green, and blue channels, or adjust
1748 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1750 % You can also reduce the influence of a particular channel with a gamma
1753 % The format of the GammaImage method is:
1755 % MagickBooleanType GammaImage(Image *image,const double gamma,
1756 % ExceptionInfo *exception)
1758 % A description of each parameter follows:
1760 % o image: the image.
1762 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1764 % o gamma: the image gamma.
1768 static inline double gamma_pow(const double value,const double gamma)
1770 return(value < 0.0 ? value : pow(value,gamma));
1773 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1774 ExceptionInfo *exception)
1776 #define GammaCorrectImageTag "GammaCorrect/Image"
1797 Allocate and initialize gamma maps.
1799 assert(image != (Image *) NULL);
1800 assert(image->signature == MagickCoreSignature);
1801 if (image->debug != MagickFalse)
1802 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1805 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1806 if (gamma_map == (Quantum *) NULL)
1807 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1809 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1811 for (i=0; i <= (ssize_t) MaxMap; i++)
1812 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1813 MaxMap,1.0/gamma)));
1814 if (image->storage_class == PseudoClass)
1815 for (i=0; i < (ssize_t) image->colors; i++)
1818 Gamma-correct colormap.
1820 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1821 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1822 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1823 ClampToQuantum(image->colormap[i].red))];
1824 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1825 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1826 ClampToQuantum(image->colormap[i].green))];
1827 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1828 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1829 ClampToQuantum(image->colormap[i].blue))];
1830 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1831 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1832 ClampToQuantum(image->colormap[i].alpha))];
1834 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1835 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1836 image->colormap[i].red,1.0/gamma);
1837 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1838 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1839 image->colormap[i].green,1.0/gamma);
1840 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1841 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1842 image->colormap[i].blue,1.0/gamma);
1843 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1844 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1845 image->colormap[i].alpha,1.0/gamma);
1849 Gamma-correct image.
1853 image_view=AcquireAuthenticCacheView(image,exception);
1854 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1855 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1856 magick_threads(image,image,image->rows,1)
1858 for (y=0; y < (ssize_t) image->rows; y++)
1866 if (status == MagickFalse)
1868 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1869 if (q == (Quantum *) NULL)
1874 for (x=0; x < (ssize_t) image->columns; x++)
1879 if (GetPixelReadMask(image,q) == 0)
1881 q+=GetPixelChannels(image);
1884 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1886 PixelChannel channel=GetPixelChannelChannel(image,j);
1887 PixelTrait traits=GetPixelChannelTraits(image,channel);
1888 if ((traits & UpdatePixelTrait) == 0)
1890 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1891 q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1893 q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1896 q+=GetPixelChannels(image);
1898 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1900 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1905 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1906 #pragma omp critical (MagickCore_GammaImage)
1908 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1910 if (proceed == MagickFalse)
1914 image_view=DestroyCacheView(image_view);
1915 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1916 if (image->gamma != 0.0)
1917 image->gamma*=gamma;
1922 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1926 % G r a y s c a l e I m a g e %
1930 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1932 % GrayscaleImage() converts the image to grayscale.
1934 % The format of the GrayscaleImage method is:
1936 % MagickBooleanType GrayscaleImage(Image *image,
1937 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1939 % A description of each parameter follows:
1941 % o image: the image.
1943 % o method: the pixel intensity method.
1945 % o exception: return any errors or warnings in this structure.
1948 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1949 const PixelIntensityMethod method,ExceptionInfo *exception)
1951 #define GrayscaleImageTag "Grayscale/Image"
1965 assert(image != (Image *) NULL);
1966 assert(image->signature == MagickCoreSignature);
1967 if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1969 if (image->debug != MagickFalse)
1970 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1971 if (image->storage_class == PseudoClass)
1973 if (SyncImage(image,exception) == MagickFalse)
1974 return(MagickFalse);
1975 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1976 return(MagickFalse);
1983 image_view=AcquireAuthenticCacheView(image,exception);
1984 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1985 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1986 magick_threads(image,image,image->rows,1)
1988 for (y=0; y < (ssize_t) image->rows; y++)
1996 if (status == MagickFalse)
1998 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1999 if (q == (Quantum *) NULL)
2004 for (x=0; x < (ssize_t) image->columns; x++)
2012 if (GetPixelReadMask(image,q) == 0)
2014 q+=GetPixelChannels(image);
2017 red=(MagickRealType) GetPixelRed(image,q);
2018 green=(MagickRealType) GetPixelGreen(image,q);
2019 blue=(MagickRealType) GetPixelBlue(image,q);
2023 case AveragePixelIntensityMethod:
2025 intensity=(red+green+blue)/3.0;
2028 case BrightnessPixelIntensityMethod:
2030 intensity=MagickMax(MagickMax(red,green),blue);
2033 case LightnessPixelIntensityMethod:
2035 intensity=(MagickMin(MagickMin(red,green),blue)+
2036 MagickMax(MagickMax(red,green),blue))/2.0;
2039 case MSPixelIntensityMethod:
2041 intensity=(MagickRealType) (((double) red*red+green*green+
2045 case Rec601LumaPixelIntensityMethod:
2047 if (image->colorspace == RGBColorspace)
2049 red=EncodePixelGamma(red);
2050 green=EncodePixelGamma(green);
2051 blue=EncodePixelGamma(blue);
2053 intensity=0.298839*red+0.586811*green+0.114350*blue;
2056 case Rec601LuminancePixelIntensityMethod:
2058 if (image->colorspace == sRGBColorspace)
2060 red=DecodePixelGamma(red);
2061 green=DecodePixelGamma(green);
2062 blue=DecodePixelGamma(blue);
2064 intensity=0.298839*red+0.586811*green+0.114350*blue;
2067 case Rec709LumaPixelIntensityMethod:
2070 if (image->colorspace == RGBColorspace)
2072 red=EncodePixelGamma(red);
2073 green=EncodePixelGamma(green);
2074 blue=EncodePixelGamma(blue);
2076 intensity=0.212656*red+0.715158*green+0.072186*blue;
2079 case Rec709LuminancePixelIntensityMethod:
2081 if (image->colorspace == sRGBColorspace)
2083 red=DecodePixelGamma(red);
2084 green=DecodePixelGamma(green);
2085 blue=DecodePixelGamma(blue);
2087 intensity=0.212656*red+0.715158*green+0.072186*blue;
2090 case RMSPixelIntensityMethod:
2092 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2093 blue*blue)/sqrt(3.0));
2097 SetPixelGray(image,ClampToQuantum(intensity),q);
2098 q+=GetPixelChannels(image);
2100 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2102 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2107 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2108 #pragma omp critical (MagickCore_GrayscaleImage)
2110 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2112 if (proceed == MagickFalse)
2116 image_view=DestroyCacheView(image_view);
2117 image->intensity=method;
2118 image->type=GrayscaleType;
2119 return(SetImageColorspace(image,GRAYColorspace,exception));
2123 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2127 % H a l d C l u t I m a g e %
2131 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2133 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2134 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2135 % Create it with the HALD coder. You can apply any color transformation to
2136 % the Hald image and then use this method to apply the transform to the
2139 % The format of the HaldClutImage method is:
2141 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2142 % ExceptionInfo *exception)
2144 % A description of each parameter follows:
2146 % o image: the image, which is replaced by indexed CLUT values
2148 % o hald_image: the color lookup table image for replacement color values.
2150 % o exception: return any errors or warnings in this structure.
2153 MagickExport MagickBooleanType HaldClutImage(Image *image,
2154 const Image *hald_image,ExceptionInfo *exception)
2156 #define HaldClutImageTag "Clut/Image"
2158 typedef struct _HaldInfo
2190 assert(image != (Image *) NULL);
2191 assert(image->signature == MagickCoreSignature);
2192 if (image->debug != MagickFalse)
2193 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2194 assert(hald_image != (Image *) NULL);
2195 assert(hald_image->signature == MagickCoreSignature);
2196 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2197 return(MagickFalse);
2198 if (image->alpha_trait == UndefinedPixelTrait)
2199 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2205 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2206 (MagickRealType) hald_image->rows);
2207 for (level=2; (level*level*level) < length; level++) ;
2209 cube_size=level*level;
2210 width=(double) hald_image->columns;
2211 GetPixelInfo(hald_image,&zero);
2212 hald_view=AcquireVirtualCacheView(hald_image,exception);
2213 image_view=AcquireAuthenticCacheView(image,exception);
2214 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2215 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2216 magick_threads(image,image,image->rows,1)
2218 for (y=0; y < (ssize_t) image->rows; y++)
2226 if (status == MagickFalse)
2228 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2229 if (q == (Quantum *) NULL)
2234 for (x=0; x < (ssize_t) image->columns; x++)
2249 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2250 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2251 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2252 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2253 point.x-=floor(point.x);
2254 point.y-=floor(point.y);
2255 point.z-=floor(point.z);
2257 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2258 fmod(offset,width),floor(offset/width),&pixel1,exception);
2260 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2261 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2263 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2266 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2267 fmod(offset,width),floor(offset/width),&pixel1,exception);
2268 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2269 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2271 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2274 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2276 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2277 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2278 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2279 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2280 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2281 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2282 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2283 (image->colorspace == CMYKColorspace))
2284 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2285 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2286 (image->alpha_trait != UndefinedPixelTrait))
2287 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2288 q+=GetPixelChannels(image);
2290 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2292 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2297 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2298 #pragma omp critical (MagickCore_HaldClutImage)
2300 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2301 if (proceed == MagickFalse)
2305 hald_view=DestroyCacheView(hald_view);
2306 image_view=DestroyCacheView(image_view);
2311 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2315 % L e v e l I m a g e %
2319 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2321 % LevelImage() adjusts the levels of a particular image channel by
2322 % scaling the colors falling between specified white and black points to
2323 % the full available quantum range.
2325 % The parameters provided represent the black, and white points. The black
2326 % point specifies the darkest color in the image. Colors darker than the
2327 % black point are set to zero. White point specifies the lightest color in
2328 % the image. Colors brighter than the white point are set to the maximum
2331 % If a '!' flag is given, map black and white colors to the given levels
2332 % rather than mapping those levels to black and white. See
2333 % LevelizeImage() below.
2335 % Gamma specifies a gamma correction to apply to the image.
2337 % The format of the LevelImage method is:
2339 % MagickBooleanType LevelImage(Image *image,const double black_point,
2340 % const double white_point,const double gamma,ExceptionInfo *exception)
2342 % A description of each parameter follows:
2344 % o image: the image.
2346 % o black_point: The level to map zero (black) to.
2348 % o white_point: The level to map QuantumRange (white) to.
2350 % o exception: return any errors or warnings in this structure.
2354 static inline double LevelPixel(const double black_point,
2355 const double white_point,const double gamma,const double pixel)
2361 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2362 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2364 return(level_pixel);
2367 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2368 const double white_point,const double gamma,ExceptionInfo *exception)
2370 #define LevelImageTag "Level/Image"
2388 Allocate and initialize levels map.
2390 assert(image != (Image *) NULL);
2391 assert(image->signature == MagickCoreSignature);
2392 if (image->debug != MagickFalse)
2393 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2394 if (image->storage_class == PseudoClass)
2395 for (i=0; i < (ssize_t) image->colors; i++)
2400 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2401 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2402 white_point,gamma,image->colormap[i].red));
2403 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2404 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2405 white_point,gamma,image->colormap[i].green));
2406 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2407 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2408 white_point,gamma,image->colormap[i].blue));
2409 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2410 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2411 white_point,gamma,image->colormap[i].alpha));
2418 image_view=AcquireAuthenticCacheView(image,exception);
2419 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2420 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2421 magick_threads(image,image,image->rows,1)
2423 for (y=0; y < (ssize_t) image->rows; y++)
2431 if (status == MagickFalse)
2433 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2434 if (q == (Quantum *) NULL)
2439 for (x=0; x < (ssize_t) image->columns; x++)
2444 if (GetPixelReadMask(image,q) == 0)
2446 q+=GetPixelChannels(image);
2449 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2451 PixelChannel channel=GetPixelChannelChannel(image,j);
2452 PixelTrait traits=GetPixelChannelTraits(image,channel);
2453 if ((traits & UpdatePixelTrait) == 0)
2455 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2458 q+=GetPixelChannels(image);
2460 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2462 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2467 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2468 #pragma omp critical (MagickCore_LevelImage)
2470 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2471 if (proceed == MagickFalse)
2475 image_view=DestroyCacheView(image_view);
2476 (void) ClampImage(image,exception);
2481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2485 % L e v e l i z e I m a g e %
2489 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2491 % LevelizeImage() applies the reversed LevelImage() operation to just
2492 % the specific channels specified. It compresses the full range of color
2493 % values, so that they lie between the given black and white points. Gamma is
2494 % applied before the values are mapped.
2496 % LevelizeImage() can be called with by using a +level command line
2497 % API option, or using a '!' on a -level or LevelImage() geometry string.
2499 % It can be used to de-contrast a greyscale image to the exact levels
2500 % specified. Or by using specific levels for each channel of an image you
2501 % can convert a gray-scale image to any linear color gradient, according to
2504 % The format of the LevelizeImage method is:
2506 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2507 % const double white_point,const double gamma,ExceptionInfo *exception)
2509 % A description of each parameter follows:
2511 % o image: the image.
2513 % o black_point: The level to map zero (black) to.
2515 % o white_point: The level to map QuantumRange (white) to.
2517 % o gamma: adjust gamma by this factor before mapping values.
2519 % o exception: return any errors or warnings in this structure.
2522 MagickExport MagickBooleanType LevelizeImage(Image *image,
2523 const double black_point,const double white_point,const double gamma,
2524 ExceptionInfo *exception)
2526 #define LevelizeImageTag "Levelize/Image"
2527 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2528 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2546 Allocate and initialize levels map.
2548 assert(image != (Image *) NULL);
2549 assert(image->signature == MagickCoreSignature);
2550 if (image->debug != MagickFalse)
2551 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2552 if (image->storage_class == PseudoClass)
2553 for (i=0; i < (ssize_t) image->colors; i++)
2558 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2559 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2560 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2561 image->colormap[i].green=(double) LevelizeValue(
2562 image->colormap[i].green);
2563 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2564 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2565 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2566 image->colormap[i].alpha=(double) LevelizeValue(
2567 image->colormap[i].alpha);
2574 image_view=AcquireAuthenticCacheView(image,exception);
2575 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2576 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2577 magick_threads(image,image,image->rows,1)
2579 for (y=0; y < (ssize_t) image->rows; y++)
2587 if (status == MagickFalse)
2589 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2590 if (q == (Quantum *) NULL)
2595 for (x=0; x < (ssize_t) image->columns; x++)
2600 if (GetPixelReadMask(image,q) == 0)
2602 q+=GetPixelChannels(image);
2605 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2607 PixelChannel channel=GetPixelChannelChannel(image,j);
2608 PixelTrait traits=GetPixelChannelTraits(image,channel);
2609 if ((traits & UpdatePixelTrait) == 0)
2611 q[j]=LevelizeValue(q[j]);
2613 q+=GetPixelChannels(image);
2615 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2617 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2622 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2623 #pragma omp critical (MagickCore_LevelizeImage)
2625 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2626 if (proceed == MagickFalse)
2630 image_view=DestroyCacheView(image_view);
2635 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2639 % L e v e l I m a g e C o l o r s %
2643 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2645 % LevelImageColors() maps the given color to "black" and "white" values,
2646 % linearly spreading out the colors, and level values on a channel by channel
2647 % bases, as per LevelImage(). The given colors allows you to specify
2648 % different level ranges for each of the color channels separately.
2650 % If the boolean 'invert' is set true the image values will modifyed in the
2651 % reverse direction. That is any existing "black" and "white" colors in the
2652 % image will become the color values given, with all other values compressed
2653 % appropriatally. This effectivally maps a greyscale gradient into the given
2656 % The format of the LevelImageColors method is:
2658 % MagickBooleanType LevelImageColors(Image *image,
2659 % const PixelInfo *black_color,const PixelInfo *white_color,
2660 % const MagickBooleanType invert,ExceptionInfo *exception)
2662 % A description of each parameter follows:
2664 % o image: the image.
2666 % o black_color: The color to map black to/from
2668 % o white_point: The color to map white to/from
2670 % o invert: if true map the colors (levelize), rather than from (level)
2672 % o exception: return any errors or warnings in this structure.
2675 MagickExport MagickBooleanType LevelImageColors(Image *image,
2676 const PixelInfo *black_color,const PixelInfo *white_color,
2677 const MagickBooleanType invert,ExceptionInfo *exception)
2686 Allocate and initialize levels map.
2688 assert(image != (Image *) NULL);
2689 assert(image->signature == MagickCoreSignature);
2690 if (image->debug != MagickFalse)
2691 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2692 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2693 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2694 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2695 (void) SetImageColorspace(image,sRGBColorspace,exception);
2697 if (invert == MagickFalse)
2699 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2701 channel_mask=SetImageChannelMask(image,RedChannel);
2702 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2704 (void) SetImageChannelMask(image,channel_mask);
2706 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2708 channel_mask=SetImageChannelMask(image,GreenChannel);
2709 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2711 (void) SetImageChannelMask(image,channel_mask);
2713 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2715 channel_mask=SetImageChannelMask(image,BlueChannel);
2716 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2718 (void) SetImageChannelMask(image,channel_mask);
2720 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2721 (image->colorspace == CMYKColorspace))
2723 channel_mask=SetImageChannelMask(image,BlackChannel);
2724 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2726 (void) SetImageChannelMask(image,channel_mask);
2728 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2729 (image->alpha_trait != UndefinedPixelTrait))
2731 channel_mask=SetImageChannelMask(image,AlphaChannel);
2732 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2734 (void) SetImageChannelMask(image,channel_mask);
2739 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2741 channel_mask=SetImageChannelMask(image,RedChannel);
2742 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2744 (void) SetImageChannelMask(image,channel_mask);
2746 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2748 channel_mask=SetImageChannelMask(image,GreenChannel);
2749 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2751 (void) SetImageChannelMask(image,channel_mask);
2753 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2755 channel_mask=SetImageChannelMask(image,BlueChannel);
2756 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2758 (void) SetImageChannelMask(image,channel_mask);
2760 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2761 (image->colorspace == CMYKColorspace))
2763 channel_mask=SetImageChannelMask(image,BlackChannel);
2764 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2766 (void) SetImageChannelMask(image,channel_mask);
2768 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2769 (image->alpha_trait != UndefinedPixelTrait))
2771 channel_mask=SetImageChannelMask(image,AlphaChannel);
2772 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2774 (void) SetImageChannelMask(image,channel_mask);
2777 return(status != 0 ? MagickTrue : MagickFalse);
2781 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2785 % L i n e a r S t r e t c h I m a g e %
2789 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2791 % LinearStretchImage() discards any pixels below the black point and above
2792 % the white point and levels the remaining pixels.
2794 % The format of the LinearStretchImage method is:
2796 % MagickBooleanType LinearStretchImage(Image *image,
2797 % const double black_point,const double white_point,
2798 % ExceptionInfo *exception)
2800 % A description of each parameter follows:
2802 % o image: the image.
2804 % o black_point: the black point.
2806 % o white_point: the white point.
2808 % o exception: return any errors or warnings in this structure.
2811 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2812 const double black_point,const double white_point,ExceptionInfo *exception)
2814 #define LinearStretchImageTag "LinearStretch/Image"
2832 Allocate histogram and linear map.
2834 assert(image != (Image *) NULL);
2835 assert(image->signature == MagickCoreSignature);
2836 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2837 if (histogram == (double *) NULL)
2838 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2843 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2844 image_view=AcquireVirtualCacheView(image,exception);
2845 for (y=0; y < (ssize_t) image->rows; y++)
2847 register const Quantum
2853 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2854 if (p == (const Quantum *) NULL)
2856 for (x=0; x < (ssize_t) image->columns; x++)
2858 intensity=GetPixelIntensity(image,p);
2859 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2860 p+=GetPixelChannels(image);
2863 image_view=DestroyCacheView(image_view);
2865 Find the histogram boundaries by locating the black and white point levels.
2868 for (black=0; black < (ssize_t) MaxMap; black++)
2870 intensity+=histogram[black];
2871 if (intensity >= black_point)
2875 for (white=(ssize_t) MaxMap; white != 0; white--)
2877 intensity+=histogram[white];
2878 if (intensity >= white_point)
2881 histogram=(double *) RelinquishMagickMemory(histogram);
2882 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2883 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2889 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2893 % M o d u l a t e I m a g e %
2897 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2899 % ModulateImage() lets you control the brightness, saturation, and hue
2900 % of an image. Modulate represents the brightness, saturation, and hue
2901 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2902 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2903 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2905 % The format of the ModulateImage method is:
2907 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2908 % ExceptionInfo *exception)
2910 % A description of each parameter follows:
2912 % o image: the image.
2914 % o modulate: Define the percent change in brightness, saturation, and hue.
2916 % o exception: return any errors or warnings in this structure.
2920 static inline void ModulateHCL(const double percent_hue,
2921 const double percent_chroma,const double percent_luma,double *red,
2922 double *green,double *blue)
2930 Increase or decrease color luma, chroma, or hue.
2932 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2933 hue+=0.5*(0.01*percent_hue-1.0);
2938 chroma*=0.01*percent_chroma;
2939 luma*=0.01*percent_luma;
2940 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2943 static inline void ModulateHCLp(const double percent_hue,
2944 const double percent_chroma,const double percent_luma,double *red,
2945 double *green,double *blue)
2953 Increase or decrease color luma, chroma, or hue.
2955 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2956 hue+=0.5*(0.01*percent_hue-1.0);
2961 chroma*=0.01*percent_chroma;
2962 luma*=0.01*percent_luma;
2963 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2966 static inline void ModulateHSB(const double percent_hue,
2967 const double percent_saturation,const double percent_brightness,double *red,
2968 double *green,double *blue)
2976 Increase or decrease color brightness, saturation, or hue.
2978 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2979 hue+=0.5*(0.01*percent_hue-1.0);
2984 saturation*=0.01*percent_saturation;
2985 brightness*=0.01*percent_brightness;
2986 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2989 static inline void ModulateHSI(const double percent_hue,
2990 const double percent_saturation,const double percent_intensity,double *red,
2991 double *green,double *blue)
2999 Increase or decrease color intensity, saturation, or hue.
3001 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3002 hue+=0.5*(0.01*percent_hue-1.0);
3007 saturation*=0.01*percent_saturation;
3008 intensity*=0.01*percent_intensity;
3009 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3012 static inline void ModulateHSL(const double percent_hue,
3013 const double percent_saturation,const double percent_lightness,double *red,
3014 double *green,double *blue)
3022 Increase or decrease color lightness, saturation, or hue.
3024 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3025 hue+=0.5*(0.01*percent_hue-1.0);
3030 saturation*=0.01*percent_saturation;
3031 lightness*=0.01*percent_lightness;
3032 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3035 static inline void ModulateHSV(const double percent_hue,
3036 const double percent_saturation,const double percent_value,double *red,
3037 double *green,double *blue)
3045 Increase or decrease color value, saturation, or hue.
3047 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3048 hue+=0.5*(0.01*percent_hue-1.0);
3053 saturation*=0.01*percent_saturation;
3054 value*=0.01*percent_value;
3055 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3058 static inline void ModulateHWB(const double percent_hue,
3059 const double percent_whiteness,const double percent_blackness,double *red,
3060 double *green,double *blue)
3068 Increase or decrease color blackness, whiteness, or hue.
3070 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3071 hue+=0.5*(0.01*percent_hue-1.0);
3076 blackness*=0.01*percent_blackness;
3077 whiteness*=0.01*percent_whiteness;
3078 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3081 static inline void ModulateLCHab(const double percent_luma,
3082 const double percent_chroma,const double percent_hue,double *red,
3083 double *green,double *blue)
3091 Increase or decrease color luma, chroma, or hue.
3093 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3094 luma*=0.01*percent_luma;
3095 chroma*=0.01*percent_chroma;
3096 hue+=0.5*(0.01*percent_hue-1.0);
3101 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3104 static inline void ModulateLCHuv(const double percent_luma,
3105 const double percent_chroma,const double percent_hue,double *red,
3106 double *green,double *blue)
3114 Increase or decrease color luma, chroma, or hue.
3116 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3117 luma*=0.01*percent_luma;
3118 chroma*=0.01*percent_chroma;
3119 hue+=0.5*(0.01*percent_hue-1.0);
3124 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3127 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3128 ExceptionInfo *exception)
3130 #define ModulateImageTag "Modulate/Image"
3165 Initialize modulate table.
3167 assert(image != (Image *) NULL);
3168 assert(image->signature == MagickCoreSignature);
3169 if (image->debug != MagickFalse)
3170 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3171 if (modulate == (char *) NULL)
3172 return(MagickFalse);
3173 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3174 (void) SetImageColorspace(image,sRGBColorspace,exception);
3175 flags=ParseGeometry(modulate,&geometry_info);
3176 percent_brightness=geometry_info.rho;
3177 percent_saturation=geometry_info.sigma;
3178 if ((flags & SigmaValue) == 0)
3179 percent_saturation=100.0;
3180 percent_hue=geometry_info.xi;
3181 if ((flags & XiValue) == 0)
3183 colorspace=UndefinedColorspace;
3184 artifact=GetImageArtifact(image,"modulate:colorspace");
3185 if (artifact != (const char *) NULL)
3186 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3187 MagickFalse,artifact);
3188 if (image->storage_class == PseudoClass)
3189 for (i=0; i < (ssize_t) image->colors; i++)
3197 Modulate image colormap.
3199 red=(double) image->colormap[i].red;
3200 green=(double) image->colormap[i].green;
3201 blue=(double) image->colormap[i].blue;
3206 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3210 case HCLpColorspace:
3212 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3218 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3224 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3231 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3237 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3243 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3248 case LCHabColorspace:
3250 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3254 case LCHuvColorspace:
3256 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3261 image->colormap[i].red=red;
3262 image->colormap[i].green=green;
3263 image->colormap[i].blue=blue;
3268 if(AccelerateModulateImage(image,percent_brightness,percent_hue,
3269 percent_saturation,colorspace,exception) != MagickFalse)
3273 image_view=AcquireAuthenticCacheView(image,exception);
3274 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3275 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3276 magick_threads(image,image,image->rows,1)
3278 for (y=0; y < (ssize_t) image->rows; y++)
3286 if (status == MagickFalse)
3288 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3289 if (q == (Quantum *) NULL)
3294 for (x=0; x < (ssize_t) image->columns; x++)
3301 red=(double) GetPixelRed(image,q);
3302 green=(double) GetPixelGreen(image,q);
3303 blue=(double) GetPixelBlue(image,q);
3308 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3312 case HCLpColorspace:
3314 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3320 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3327 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3333 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3339 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3343 case LCHabColorspace:
3345 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3350 case LCHuvColorspace:
3352 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3357 SetPixelRed(image,ClampToQuantum(red),q);
3358 SetPixelGreen(image,ClampToQuantum(green),q);
3359 SetPixelBlue(image,ClampToQuantum(blue),q);
3360 q+=GetPixelChannels(image);
3362 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3364 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3370 #pragma omp critical (MagickCore_ModulateImage)
3372 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3373 if (proceed == MagickFalse)
3377 image_view=DestroyCacheView(image_view);
3382 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3386 % N e g a t e I m a g e %
3390 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392 % NegateImage() negates the colors in the reference image. The grayscale
3393 % option means that only grayscale values within the image are negated.
3395 % The format of the NegateImage method is:
3397 % MagickBooleanType NegateImage(Image *image,
3398 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3400 % A description of each parameter follows:
3402 % o image: the image.
3404 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3406 % o exception: return any errors or warnings in this structure.
3409 MagickExport MagickBooleanType NegateImage(Image *image,
3410 const MagickBooleanType grayscale,ExceptionInfo *exception)
3412 #define NegateImageTag "Negate/Image"
3429 assert(image != (Image *) NULL);
3430 assert(image->signature == MagickCoreSignature);
3431 if (image->debug != MagickFalse)
3432 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3433 if (image->storage_class == PseudoClass)
3434 for (i=0; i < (ssize_t) image->colors; i++)
3439 if( grayscale != MagickFalse )
3440 if ((image->colormap[i].red != image->colormap[i].green) ||
3441 (image->colormap[i].green != image->colormap[i].blue))
3443 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3444 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3445 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3446 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3447 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3448 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3455 image_view=AcquireAuthenticCacheView(image,exception);
3456 if( grayscale != MagickFalse )
3458 for (y=0; y < (ssize_t) image->rows; y++)
3469 if (status == MagickFalse)
3471 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3473 if (q == (Quantum *) NULL)
3478 for (x=0; x < (ssize_t) image->columns; x++)
3483 if ((GetPixelReadMask(image,q) == 0) ||
3484 IsPixelGray(image,q) != MagickFalse)
3486 q+=GetPixelChannels(image);
3489 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3491 PixelChannel channel=GetPixelChannelChannel(image,j);
3492 PixelTrait traits=GetPixelChannelTraits(image,channel);
3493 if ((traits & UpdatePixelTrait) == 0)
3495 q[j]=QuantumRange-q[j];
3497 q+=GetPixelChannels(image);
3499 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3500 if (sync == MagickFalse)
3502 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3507 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3508 #pragma omp critical (MagickCore_NegateImage)
3510 proceed=SetImageProgress(image,NegateImageTag,progress++,
3512 if (proceed == MagickFalse)
3516 image_view=DestroyCacheView(image_view);
3522 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3523 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3524 magick_threads(image,image,image->rows,1)
3526 for (y=0; y < (ssize_t) image->rows; y++)
3534 if (status == MagickFalse)
3536 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3537 if (q == (Quantum *) NULL)
3542 for (x=0; x < (ssize_t) image->columns; x++)
3547 if (GetPixelReadMask(image,q) == 0)
3549 q+=GetPixelChannels(image);
3552 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3554 PixelChannel channel=GetPixelChannelChannel(image,j);
3555 PixelTrait traits=GetPixelChannelTraits(image,channel);
3556 if ((traits & UpdatePixelTrait) == 0)
3558 q[j]=QuantumRange-q[j];
3560 q+=GetPixelChannels(image);
3562 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3564 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3569 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3570 #pragma omp critical (MagickCore_NegateImage)
3572 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3573 if (proceed == MagickFalse)
3577 image_view=DestroyCacheView(image_view);
3582 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3586 % N o r m a l i z e I m a g e %
3590 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3592 % The NormalizeImage() method enhances the contrast of a color image by
3593 % mapping the darkest 2 percent of all pixel to black and the brightest
3594 % 1 percent to white.
3596 % The format of the NormalizeImage method is:
3598 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3600 % A description of each parameter follows:
3602 % o image: the image.
3604 % o exception: return any errors or warnings in this structure.
3607 MagickExport MagickBooleanType NormalizeImage(Image *image,
3608 ExceptionInfo *exception)
3614 black_point=(double) image->columns*image->rows*0.0015;
3615 white_point=(double) image->columns*image->rows*0.9995;
3616 return(ContrastStretchImage(image,black_point,white_point,exception));
3620 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3624 % S i g m o i d a l C o n t r a s t I m a g e %
3628 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3630 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3631 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3632 % sigmoidal transfer function without saturating highlights or shadows.
3633 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3634 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3635 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3636 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3639 % The format of the SigmoidalContrastImage method is:
3641 % MagickBooleanType SigmoidalContrastImage(Image *image,
3642 % const MagickBooleanType sharpen,const char *levels,
3643 % ExceptionInfo *exception)
3645 % A description of each parameter follows:
3647 % o image: the image.
3649 % o sharpen: Increase or decrease image contrast.
3651 % o contrast: strength of the contrast, the larger the number the more
3652 % 'threshold-like' it becomes.
3654 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3656 % o exception: return any errors or warnings in this structure.
3661 ImageMagick 6 has a version of this function which uses LUTs.
3665 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3668 The first version, based on the hyperbolic tangent tanh, when combined with
3669 the scaling step, is an exact arithmetic clone of the the sigmoid function
3670 based on the logistic curve. The equivalence is based on the identity
3672 1/(1+exp(-t)) = (1+tanh(t/2))/2
3674 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3675 scaled sigmoidal derivation is invariant under affine transformations of
3678 The tanh version is almost certainly more accurate and cheaper. The 0.5
3679 factor in the argument is to clone the legacy ImageMagick behavior. The
3680 reason for making the define depend on atanh even though it only uses tanh
3681 has to do with the construction of the inverse of the scaled sigmoidal.
3683 #if defined(MAGICKCORE_HAVE_ATANH)
3684 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3686 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3689 Scaled sigmoidal function:
3691 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3692 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3694 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3695 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3696 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3697 zero. This is fixed below by exiting immediately when contrast is small,
3698 leaving the image (or colormap) unmodified. This appears to be safe because
3699 the series expansion of the logistic sigmoidal function around x=b is
3703 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3705 #define ScaledSigmoidal(a,b,x) ( \
3706 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3707 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3709 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3710 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3711 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3712 when creating a LUT from in gamut values, hence the branching. In
3713 addition, HDRI may have out of gamut values.
3714 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3715 It is only a right inverse. This is unavoidable.
3717 static inline double InverseScaledSigmoidal(const double a,const double b,
3720 const double sig0=Sigmoidal(a,b,0.0);
3721 const double sig1=Sigmoidal(a,b,1.0);
3722 const double argument=(sig1-sig0)*x+sig0;
3723 const double clamped=
3725 #if defined(MAGICKCORE_HAVE_ATANH)
3726 argument < -1+MagickEpsilon
3730 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3732 return(b+(2.0/a)*atanh(clamped));
3734 argument < MagickEpsilon
3738 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3740 return(b-log(1.0/clamped-1.0)/a);
3744 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3745 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3746 ExceptionInfo *exception)
3748 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3749 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3750 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3751 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3752 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3769 assert(image != (Image *) NULL);
3770 assert(image->signature == MagickCoreSignature);
3771 if (image->debug != MagickFalse)
3772 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3774 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3775 case nothing is done.
3777 if (contrast < MagickEpsilon)
3780 Sigmoidal-contrast enhance colormap.
3782 if (image->storage_class == PseudoClass)
3787 if( sharpen != MagickFalse )
3788 for (i=0; i < (ssize_t) image->colors; i++)
3790 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3791 image->colormap[i].red=(MagickRealType) ScaledSig(
3792 image->colormap[i].red);
3793 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3794 image->colormap[i].green=(MagickRealType) ScaledSig(
3795 image->colormap[i].green);
3796 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3797 image->colormap[i].blue=(MagickRealType) ScaledSig(
3798 image->colormap[i].blue);
3799 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3800 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3801 image->colormap[i].alpha);
3804 for (i=0; i < (ssize_t) image->colors; i++)
3806 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3807 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3808 image->colormap[i].red);
3809 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3810 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3811 image->colormap[i].green);
3812 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3813 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3814 image->colormap[i].blue);
3815 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3816 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3817 image->colormap[i].alpha);
3821 Sigmoidal-contrast enhance image.
3825 image_view=AcquireAuthenticCacheView(image,exception);
3826 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3827 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3828 magick_threads(image,image,image->rows,1)
3830 for (y=0; y < (ssize_t) image->rows; y++)
3838 if (status == MagickFalse)
3840 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3841 if (q == (Quantum *) NULL)
3846 for (x=0; x < (ssize_t) image->columns; x++)
3851 if (GetPixelReadMask(image,q) == 0)
3853 q+=GetPixelChannels(image);
3856 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3858 PixelChannel channel=GetPixelChannelChannel(image,i);
3859 PixelTrait traits=GetPixelChannelTraits(image,channel);
3860 if ((traits & UpdatePixelTrait) == 0)
3862 if( sharpen != MagickFalse )
3863 q[i]=ScaledSig(q[i]);
3865 q[i]=InverseScaledSig(q[i]);
3867 q+=GetPixelChannels(image);
3869 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3871 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3876 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3877 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3879 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3881 if (proceed == MagickFalse)
3885 image_view=DestroyCacheView(image_view);