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-2013 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/attribute.h"
46 #include "MagickCore/cache.h"
47 #include "MagickCore/cache-view.h"
48 #include "MagickCore/color.h"
49 #include "MagickCore/color-private.h"
50 #include "MagickCore/colorspace.h"
51 #include "MagickCore/colorspace-private.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/enhance.h"
54 #include "MagickCore/exception.h"
55 #include "MagickCore/exception-private.h"
56 #include "MagickCore/fx.h"
57 #include "MagickCore/gem.h"
58 #include "MagickCore/gem-private.h"
59 #include "MagickCore/geometry.h"
60 #include "MagickCore/histogram.h"
61 #include "MagickCore/image.h"
62 #include "MagickCore/image-private.h"
63 #include "MagickCore/memory_.h"
64 #include "MagickCore/monitor.h"
65 #include "MagickCore/monitor-private.h"
66 #include "MagickCore/option.h"
67 #include "MagickCore/pixel.h"
68 #include "MagickCore/pixel-accessor.h"
69 #include "MagickCore/quantum.h"
70 #include "MagickCore/quantum-private.h"
71 #include "MagickCore/resample.h"
72 #include "MagickCore/resample-private.h"
73 #include "MagickCore/resource_.h"
74 #include "MagickCore/statistic.h"
75 #include "MagickCore/string_.h"
76 #include "MagickCore/string-private.h"
77 #include "MagickCore/thread-private.h"
78 #include "MagickCore/threshold.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/xml-tree.h"
81 #include "MagickCore/xml-tree-private.h"
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88 % A u t o G a m m a I m a g e %
92 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94 % AutoGammaImage() extract the 'mean' from the image and adjust the image
95 % to try make set its gamma appropriatally.
97 % The format of the AutoGammaImage method is:
99 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
101 % A description of each parameter follows:
103 % o image: The image to auto-level
105 % o exception: return any errors or warnings in this structure.
108 MagickExport MagickBooleanType AutoGammaImage(Image *image,
109 ExceptionInfo *exception)
124 if (image->channel_mask == DefaultChannels)
127 Apply gamma correction equally across all given channels.
129 (void) GetImageMean(image,&mean,&sans,exception);
130 gamma=log(mean*QuantumScale)/log_mean;
131 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
134 Auto-gamma each channel separately.
137 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
142 PixelChannel channel=GetPixelChannelChannel(image,i);
143 PixelTrait traits=GetPixelChannelTraits(image,channel);
144 if ((traits & UpdatePixelTrait) == 0)
146 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
147 status=GetImageMean(image,&mean,&sans,exception);
148 gamma=log(mean*QuantumScale)/log_mean;
149 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
150 (void) SetImageChannelMask(image,channel_mask);
151 if (status == MagickFalse)
154 return(status != 0 ? MagickTrue : MagickFalse);
158 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162 % A u t o L e v e l I m a g e %
166 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
168 % AutoLevelImage() adjusts the levels of a particular image channel by
169 % scaling the minimum and maximum values to the full quantum range.
171 % The format of the LevelImage method is:
173 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
175 % A description of each parameter follows:
177 % o image: The image to auto-level
179 % o exception: return any errors or warnings in this structure.
182 MagickExport MagickBooleanType AutoLevelImage(Image *image,
183 ExceptionInfo *exception)
185 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193 % B r i g h t n e s s C o n t r a s t I m a g e %
197 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199 % BrightnessContrastImage() changes the brightness and/or contrast of an
200 % image. It converts the brightness and contrast parameters into slope and
201 % intercept and calls a polynomical function to apply to the image.
203 % The format of the BrightnessContrastImage method is:
205 % MagickBooleanType BrightnessContrastImage(Image *image,
206 % const double brightness,const double contrast,ExceptionInfo *exception)
208 % A description of each parameter follows:
210 % o image: the image.
212 % o brightness: the brightness percent (-100 .. 100).
214 % o contrast: the contrast percent (-100 .. 100).
216 % o exception: return any errors or warnings in this structure.
219 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
220 const double brightness,const double contrast,ExceptionInfo *exception)
222 #define BrightnessContastImageTag "BrightnessContast/Image"
234 Compute slope and intercept.
236 assert(image != (Image *) NULL);
237 assert(image->signature == MagickSignature);
238 if (image->debug != MagickFalse)
239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
241 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
244 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
245 coefficients[0]=slope;
246 coefficients[1]=intercept;
247 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
252 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256 % C l u t I m a g e %
260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262 % ClutImage() replaces each color value in the given image, by using it as an
263 % index to lookup a replacement color value in a Color Look UP Table in the
264 % form of an image. The values are extracted along a diagonal of the CLUT
265 % image so either a horizontal or vertial gradient image can be used.
267 % Typically this is used to either re-color a gray-scale image according to a
268 % color gradient in the CLUT image, or to perform a freeform histogram
269 % (level) adjustment according to the (typically gray-scale) gradient in the
272 % When the 'channel' mask includes the matte/alpha transparency channel but
273 % one image has no such channel it is assumed that that image is a simple
274 % gray-scale image that will effect the alpha channel values, either for
275 % gray-scale coloring (with transparent or semi-transparent colors), or
276 % a histogram adjustment of existing alpha channel values. If both images
277 % have matte channels, direct and normal indexing is applied, which is rarely
280 % The format of the ClutImage method is:
282 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
283 % const PixelInterpolateMethod method,ExceptionInfo *exception)
285 % A description of each parameter follows:
287 % o image: the image, which is replaced by indexed CLUT values
289 % o clut_image: the color lookup table image for replacement color values.
291 % o method: the pixel interpolation method.
293 % o exception: return any errors or warnings in this structure.
296 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
297 const PixelInterpolateMethod method,ExceptionInfo *exception)
299 #define ClutImageTag "Clut/Image"
320 assert(image != (Image *) NULL);
321 assert(image->signature == MagickSignature);
322 if (image->debug != MagickFalse)
323 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
324 assert(clut_image != (Image *) NULL);
325 assert(clut_image->signature == MagickSignature);
326 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
328 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
329 if (clut_map == (PixelInfo *) NULL)
330 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
337 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
338 clut_view=AcquireVirtualCacheView(clut_image,exception);
339 for (i=0; i <= (ssize_t) MaxMap; i++)
341 GetPixelInfo(clut_image,clut_map+i);
342 (void) InterpolatePixelInfo(clut_image,clut_view,method,
343 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
344 (clut_image->rows-adjust),clut_map+i,exception);
346 clut_view=DestroyCacheView(clut_view);
347 image_view=AcquireAuthenticCacheView(image,exception);
348 #if defined(MAGICKCORE_OPENMP_SUPPORT)
349 #pragma omp parallel for schedule(static,4) shared(progress,status) \
350 magick_threads(image,image,image->rows,1)
352 for (y=0; y < (ssize_t) image->rows; y++)
363 if (status == MagickFalse)
365 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
366 if (q == (Quantum *) NULL)
371 GetPixelInfo(image,&pixel);
372 for (x=0; x < (ssize_t) image->columns; x++)
374 if (GetPixelMask(image,q) == 0)
376 q+=GetPixelChannels(image);
379 GetPixelInfoPixel(image,q,&pixel);
380 pixel.red=clut_map[ScaleQuantumToMap(
381 ClampToQuantum(pixel.red))].red;
382 pixel.green=clut_map[ScaleQuantumToMap(
383 ClampToQuantum(pixel.green))].green;
384 pixel.blue=clut_map[ScaleQuantumToMap(
385 ClampToQuantum(pixel.blue))].blue;
386 pixel.black=clut_map[ScaleQuantumToMap(
387 ClampToQuantum(pixel.black))].black;
388 pixel.alpha=clut_map[ScaleQuantumToMap(
389 ClampToQuantum(pixel.alpha))].alpha;
390 SetPixelInfoPixel(image,&pixel,q);
391 q+=GetPixelChannels(image);
393 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
395 if (image->progress_monitor != (MagickProgressMonitor) NULL)
400 #if defined(MAGICKCORE_OPENMP_SUPPORT)
401 #pragma omp critical (MagickCore_ClutImage)
403 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
404 if (proceed == MagickFalse)
408 image_view=DestroyCacheView(image_view);
409 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
410 if ((clut_image->alpha_trait == BlendPixelTrait) &&
411 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
412 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
417 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
421 % C o l o r D e c i s i o n L i s t I m a g e %
425 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
428 % (CCC) file which solely contains one or more color corrections and applies
429 % the correction to the image. Here is a sample CCC file:
431 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
432 % <ColorCorrection id="cc03345">
434 % <Slope> 0.9 1.2 0.5 </Slope>
435 % <Offset> 0.4 -0.5 0.6 </Offset>
436 % <Power> 1.0 0.8 1.5 </Power>
439 % <Saturation> 0.85 </Saturation>
442 % </ColorCorrectionCollection>
444 % which includes the slop, offset, and power for each of the RGB channels
445 % as well as the saturation.
447 % The format of the ColorDecisionListImage method is:
449 % MagickBooleanType ColorDecisionListImage(Image *image,
450 % const char *color_correction_collection,ExceptionInfo *exception)
452 % A description of each parameter follows:
454 % o image: the image.
456 % o color_correction_collection: the color correction collection in XML.
458 % o exception: return any errors or warnings in this structure.
461 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
462 const char *color_correction_collection,ExceptionInfo *exception)
464 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
466 typedef struct _Correction
474 typedef struct _ColorCorrection
489 token[MaxTextExtent];
520 Allocate and initialize cdl maps.
522 assert(image != (Image *) NULL);
523 assert(image->signature == MagickSignature);
524 if (image->debug != MagickFalse)
525 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
526 if (color_correction_collection == (const char *) NULL)
528 ccc=NewXMLTree((const char *) color_correction_collection,exception);
529 if (ccc == (XMLTreeInfo *) NULL)
531 cc=GetXMLTreeChild(ccc,"ColorCorrection");
532 if (cc == (XMLTreeInfo *) NULL)
534 ccc=DestroyXMLTree(ccc);
537 color_correction.red.slope=1.0;
538 color_correction.red.offset=0.0;
539 color_correction.red.power=1.0;
540 color_correction.green.slope=1.0;
541 color_correction.green.offset=0.0;
542 color_correction.green.power=1.0;
543 color_correction.blue.slope=1.0;
544 color_correction.blue.offset=0.0;
545 color_correction.blue.power=1.0;
546 color_correction.saturation=0.0;
547 sop=GetXMLTreeChild(cc,"SOPNode");
548 if (sop != (XMLTreeInfo *) NULL)
555 slope=GetXMLTreeChild(sop,"Slope");
556 if (slope != (XMLTreeInfo *) NULL)
558 content=GetXMLTreeContent(slope);
559 p=(const char *) content;
560 for (i=0; (*p != '\0') && (i < 3); i++)
562 GetMagickToken(p,&p,token);
564 GetMagickToken(p,&p,token);
569 color_correction.red.slope=StringToDouble(token,(char **) NULL);
574 color_correction.green.slope=StringToDouble(token,
580 color_correction.blue.slope=StringToDouble(token,
587 offset=GetXMLTreeChild(sop,"Offset");
588 if (offset != (XMLTreeInfo *) NULL)
590 content=GetXMLTreeContent(offset);
591 p=(const char *) content;
592 for (i=0; (*p != '\0') && (i < 3); i++)
594 GetMagickToken(p,&p,token);
596 GetMagickToken(p,&p,token);
601 color_correction.red.offset=StringToDouble(token,
607 color_correction.green.offset=StringToDouble(token,
613 color_correction.blue.offset=StringToDouble(token,
620 power=GetXMLTreeChild(sop,"Power");
621 if (power != (XMLTreeInfo *) NULL)
623 content=GetXMLTreeContent(power);
624 p=(const char *) content;
625 for (i=0; (*p != '\0') && (i < 3); i++)
627 GetMagickToken(p,&p,token);
629 GetMagickToken(p,&p,token);
634 color_correction.red.power=StringToDouble(token,(char **) NULL);
639 color_correction.green.power=StringToDouble(token,
645 color_correction.blue.power=StringToDouble(token,
653 sat=GetXMLTreeChild(cc,"SATNode");
654 if (sat != (XMLTreeInfo *) NULL)
659 saturation=GetXMLTreeChild(sat,"Saturation");
660 if (saturation != (XMLTreeInfo *) NULL)
662 content=GetXMLTreeContent(saturation);
663 p=(const char *) content;
664 GetMagickToken(p,&p,token);
665 color_correction.saturation=StringToDouble(token,(char **) NULL);
668 ccc=DestroyXMLTree(ccc);
669 if (image->debug != MagickFalse)
671 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
672 " Color Correction Collection:");
673 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
674 " color_correction.red.slope: %g",color_correction.red.slope);
675 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
676 " color_correction.red.offset: %g",color_correction.red.offset);
677 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
678 " color_correction.red.power: %g",color_correction.red.power);
679 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
680 " color_correction.green.slope: %g",color_correction.green.slope);
681 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
682 " color_correction.green.offset: %g",color_correction.green.offset);
683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
684 " color_correction.green.power: %g",color_correction.green.power);
685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
686 " color_correction.blue.slope: %g",color_correction.blue.slope);
687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
688 " color_correction.blue.offset: %g",color_correction.blue.offset);
689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
690 " color_correction.blue.power: %g",color_correction.blue.power);
691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
692 " color_correction.saturation: %g",color_correction.saturation);
694 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
695 if (cdl_map == (PixelInfo *) NULL)
696 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
698 for (i=0; i <= (ssize_t) MaxMap; i++)
700 cdl_map[i].red=(double) ScaleMapToQuantum((double)
701 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
702 color_correction.red.offset,color_correction.red.power))));
703 cdl_map[i].green=(double) ScaleMapToQuantum((double)
704 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
705 color_correction.green.offset,color_correction.green.power))));
706 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
707 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
708 color_correction.blue.offset,color_correction.blue.power))));
710 if (image->storage_class == PseudoClass)
711 for (i=0; i < (ssize_t) image->colors; i++)
714 Apply transfer function to colormap.
719 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
720 0.07217f*image->colormap[i].blue;
721 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
722 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
723 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
724 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
725 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
726 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
729 Apply transfer function to image.
733 image_view=AcquireAuthenticCacheView(image,exception);
734 #if defined(MAGICKCORE_OPENMP_SUPPORT)
735 #pragma omp parallel for schedule(static,4) shared(progress,status) \
736 magick_threads(image,image,image->rows,1)
738 for (y=0; y < (ssize_t) image->rows; y++)
749 if (status == MagickFalse)
751 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
752 if (q == (Quantum *) NULL)
757 for (x=0; x < (ssize_t) image->columns; x++)
759 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
760 0.07217f*GetPixelBlue(image,q);
761 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
762 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
763 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
764 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
765 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
766 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
767 q+=GetPixelChannels(image);
769 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
771 if (image->progress_monitor != (MagickProgressMonitor) NULL)
776 #if defined(MAGICKCORE_OPENMP_SUPPORT)
777 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
779 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
780 progress++,image->rows);
781 if (proceed == MagickFalse)
785 image_view=DestroyCacheView(image_view);
786 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
791 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
795 % C o n t r a s t I m a g e %
799 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
801 % ContrastImage() enhances the intensity differences between the lighter and
802 % darker elements of the image. Set sharpen to a MagickTrue to increase the
803 % image contrast otherwise the contrast is reduced.
805 % The format of the ContrastImage method is:
807 % MagickBooleanType ContrastImage(Image *image,
808 % const MagickBooleanType sharpen,ExceptionInfo *exception)
810 % A description of each parameter follows:
812 % o image: the image.
814 % o sharpen: Increase or decrease image contrast.
816 % o exception: return any errors or warnings in this structure.
820 static void Contrast(const int sign,double *red,double *green,double *blue)
828 Enhance contrast: dark color become darker, light color become lighter.
830 assert(red != (double *) NULL);
831 assert(green != (double *) NULL);
832 assert(blue != (double *) NULL);
836 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
837 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
839 if (brightness > 1.0)
842 if (brightness < 0.0)
844 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
847 MagickExport MagickBooleanType ContrastImage(Image *image,
848 const MagickBooleanType sharpen,ExceptionInfo *exception)
850 #define ContrastImageTag "Contrast/Image"
870 assert(image != (Image *) NULL);
871 assert(image->signature == MagickSignature);
872 if (image->debug != MagickFalse)
873 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
874 sign=sharpen != MagickFalse ? 1 : -1;
875 if (image->storage_class == PseudoClass)
878 Contrast enhance colormap.
880 for (i=0; i < (ssize_t) image->colors; i++)
887 Contrast(sign,&red,&green,&blue);
888 image->colormap[i].red=(MagickRealType) red;
889 image->colormap[i].red=(MagickRealType) red;
890 image->colormap[i].red=(MagickRealType) red;
894 Contrast enhance image.
898 image_view=AcquireAuthenticCacheView(image,exception);
899 #if defined(MAGICKCORE_OPENMP_SUPPORT)
900 #pragma omp parallel for schedule(static,4) shared(progress,status) \
901 magick_threads(image,image,image->rows,1)
903 for (y=0; y < (ssize_t) image->rows; y++)
916 if (status == MagickFalse)
918 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
919 if (q == (Quantum *) NULL)
924 for (x=0; x < (ssize_t) image->columns; x++)
926 red=(double) GetPixelRed(image,q);
927 green=(double) GetPixelGreen(image,q);
928 blue=(double) GetPixelBlue(image,q);
929 Contrast(sign,&red,&green,&blue);
930 SetPixelRed(image,ClampToQuantum(red),q);
931 SetPixelGreen(image,ClampToQuantum(green),q);
932 SetPixelBlue(image,ClampToQuantum(blue),q);
933 q+=GetPixelChannels(image);
935 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
937 if (image->progress_monitor != (MagickProgressMonitor) NULL)
942 #if defined(MAGICKCORE_OPENMP_SUPPORT)
943 #pragma omp critical (MagickCore_ContrastImage)
945 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
946 if (proceed == MagickFalse)
950 image_view=DestroyCacheView(image_view);
955 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
959 % C o n t r a s t S t r e t c h I m a g e %
963 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
965 % ContrastStretchImage() is a simple image enhancement technique that attempts
966 % to improve the contrast in an image by 'stretching' the range of intensity
967 % values it contains to span a desired range of values. It differs from the
968 % more sophisticated histogram equalization in that it can only apply a
969 % linear scaling function to the image pixel values. As a result the
970 % 'enhancement' is less harsh.
972 % The format of the ContrastStretchImage method is:
974 % MagickBooleanType ContrastStretchImage(Image *image,
975 % const char *levels,ExceptionInfo *exception)
977 % A description of each parameter follows:
979 % o image: the image.
981 % o black_point: the black point.
983 % o white_point: the white point.
985 % o levels: Specify the levels where the black and white points have the
986 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
988 % o exception: return any errors or warnings in this structure.
991 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
992 const double black_point,const double white_point,ExceptionInfo *exception)
994 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
995 #define ContrastStretchImageTag "ContrastStretch/Image"
1022 Allocate histogram and stretch map.
1024 assert(image != (Image *) NULL);
1025 assert(image->signature == MagickSignature);
1026 if (image->debug != MagickFalse)
1027 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1028 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1029 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1030 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1031 sizeof(*histogram));
1032 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1033 GetPixelChannels(image)*sizeof(*stretch_map));
1034 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1035 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1037 if (stretch_map != (double *) NULL)
1038 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1039 if (histogram != (double *) NULL)
1040 histogram=(double *) RelinquishMagickMemory(histogram);
1041 if (white != (double *) NULL)
1042 white=(double *) RelinquishMagickMemory(white);
1043 if (black != (double *) NULL)
1044 black=(double *) RelinquishMagickMemory(black);
1045 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1051 if (IsImageGray(image,exception) != MagickFalse)
1052 (void) SetImageColorspace(image,GRAYColorspace,exception);
1054 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1055 sizeof(*histogram));
1056 image_view=AcquireVirtualCacheView(image,exception);
1057 for (y=0; y < (ssize_t) image->rows; y++)
1059 register const Quantum
1065 if (status == MagickFalse)
1067 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1068 if (p == (const Quantum *) NULL)
1073 for (x=0; x < (ssize_t) image->columns; x++)
1081 pixel=GetPixelIntensity(image,p);
1082 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1084 if (image->channel_mask != DefaultChannels)
1085 pixel=(double) p[i];
1086 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1087 ClampToQuantum(pixel))+i]++;
1089 p+=GetPixelChannels(image);
1092 image_view=DestroyCacheView(image_view);
1094 Find the histogram boundaries by locating the black/white levels.
1096 number_channels=GetPixelChannels(image);
1097 for (i=0; i < (ssize_t) number_channels; i++)
1106 white[i]=MaxRange(QuantumRange);
1108 for (j=0; j <= (ssize_t) MaxMap; j++)
1110 intensity+=histogram[GetPixelChannels(image)*j+i];
1111 if (intensity > black_point)
1114 black[i]=(double) j;
1116 for (j=(ssize_t) MaxMap; j != 0; j--)
1118 intensity+=histogram[GetPixelChannels(image)*j+i];
1119 if (intensity > ((double) image->columns*image->rows-white_point))
1122 white[i]=(double) j;
1124 histogram=(double *) RelinquishMagickMemory(histogram);
1126 Stretch the histogram to create the stretched image mapping.
1128 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1129 sizeof(*stretch_map));
1130 number_channels=GetPixelChannels(image);
1131 for (i=0; i < (ssize_t) number_channels; i++)
1136 for (j=0; j <= (ssize_t) MaxMap; j++)
1138 if (j < (ssize_t) black[i])
1139 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1141 if (j > (ssize_t) white[i])
1142 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1144 if (black[i] != white[i])
1145 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1146 (double) (MaxMap*(j-black[i])/(white[i]-black[i])));
1149 if (image->storage_class == PseudoClass)
1155 Stretch-contrast colormap.
1157 for (j=0; j < (ssize_t) image->colors; j++)
1159 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1161 i=GetPixelChannelChannel(image,RedPixelChannel);
1162 if (black[i] != white[i])
1163 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1164 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
1166 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1168 i=GetPixelChannelChannel(image,GreenPixelChannel);
1169 if (black[i] != white[i])
1170 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1171 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
1173 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1175 i=GetPixelChannelChannel(image,BluePixelChannel);
1176 if (black[i] != white[i])
1177 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1178 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
1180 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1182 i=GetPixelChannelChannel(image,AlphaPixelChannel);
1183 if (black[i] != white[i])
1184 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1185 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
1190 Stretch-contrast image.
1194 image_view=AcquireAuthenticCacheView(image,exception);
1195 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1196 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1197 magick_threads(image,image,image->rows,1)
1199 for (y=0; y < (ssize_t) image->rows; y++)
1207 if (status == MagickFalse)
1209 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1210 if (q == (Quantum *) NULL)
1215 for (x=0; x < (ssize_t) image->columns; x++)
1220 if (GetPixelMask(image,q) == 0)
1222 q+=GetPixelChannels(image);
1225 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1227 PixelChannel channel=GetPixelChannelChannel(image,i);
1228 PixelTrait traits=GetPixelChannelTraits(image,channel);
1229 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1231 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1232 ScaleQuantumToMap(q[i])+i]);
1234 q+=GetPixelChannels(image);
1236 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1238 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1243 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1244 #pragma omp critical (MagickCore_ContrastStretchImage)
1246 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1248 if (proceed == MagickFalse)
1252 image_view=DestroyCacheView(image_view);
1253 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1254 white=(double *) RelinquishMagickMemory(white);
1255 black=(double *) RelinquishMagickMemory(black);
1260 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1264 % E n h a n c e I m a g e %
1268 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1270 % EnhanceImage() applies a digital filter that improves the quality of a
1273 % The format of the EnhanceImage method is:
1275 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1277 % A description of each parameter follows:
1279 % o image: the image.
1281 % o exception: return any errors or warnings in this structure.
1284 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1286 #define EnhancePixel(weight) \
1287 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1288 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1289 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1290 distance*distance; \
1291 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
1293 aggregate+=(weight)*r[i]; \
1294 total_weight+=(weight); \
1296 r+=GetPixelChannels(image);
1297 #define EnhanceImageTag "Enhance/Image"
1316 Initialize enhanced image attributes.
1318 assert(image != (const Image *) NULL);
1319 assert(image->signature == MagickSignature);
1320 if (image->debug != MagickFalse)
1321 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1322 assert(exception != (ExceptionInfo *) NULL);
1323 assert(exception->signature == MagickSignature);
1324 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1326 if (enhance_image == (Image *) NULL)
1327 return((Image *) NULL);
1328 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1330 enhance_image=DestroyImage(enhance_image);
1331 return((Image *) NULL);
1338 image_view=AcquireVirtualCacheView(image,exception);
1339 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1340 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1341 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1342 magick_threads(image,enhance_image,image->rows,1)
1344 for (y=0; y < (ssize_t) image->rows; y++)
1346 register const Quantum
1358 if (status == MagickFalse)
1360 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1361 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1363 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1368 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1369 for (x=0; x < (ssize_t) image->columns; x++)
1374 if (GetPixelMask(image,p) == 0)
1376 p+=GetPixelChannels(image);
1377 q+=GetPixelChannels(enhance_image);
1380 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1389 register const Quantum
1392 PixelChannel channel=GetPixelChannelChannel(image,i);
1393 PixelTrait traits=GetPixelChannelTraits(image,channel);
1394 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
1395 if ((traits == UndefinedPixelTrait) ||
1396 (enhance_traits == UndefinedPixelTrait))
1398 SetPixelChannel(enhance_image,channel,p[center+i],q);
1399 if ((enhance_traits & CopyPixelTrait) != 0)
1402 Compute weighted average of target pixel color components.
1407 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1408 EnhancePixel(8.0); EnhancePixel(5.0);
1409 r=p+1*GetPixelChannels(image)*(image->columns+4);
1410 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1411 EnhancePixel(20.0); EnhancePixel(8.0);
1412 r=p+2*GetPixelChannels(image)*(image->columns+4);
1413 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1414 EnhancePixel(40.0); EnhancePixel(10.0);
1415 r=p+3*GetPixelChannels(image)*(image->columns+4);
1416 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1417 EnhancePixel(20.0); EnhancePixel(8.0);
1418 r=p+4*GetPixelChannels(image)*(image->columns+4);
1419 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1420 EnhancePixel(8.0); EnhancePixel(5.0);
1421 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1424 p+=GetPixelChannels(image);
1425 q+=GetPixelChannels(enhance_image);
1427 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1429 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1434 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1435 #pragma omp critical (MagickCore_EnhanceImage)
1437 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1438 if (proceed == MagickFalse)
1442 enhance_view=DestroyCacheView(enhance_view);
1443 image_view=DestroyCacheView(image_view);
1444 if (status == MagickFalse)
1445 enhance_image=DestroyImage(enhance_image);
1446 return(enhance_image);
1450 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1454 % E q u a l i z e I m a g e %
1458 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1460 % EqualizeImage() applies a histogram equalization to the image.
1462 % The format of the EqualizeImage method is:
1464 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1466 % A description of each parameter follows:
1468 % o image: the image.
1470 % o exception: return any errors or warnings in this structure.
1473 MagickExport MagickBooleanType EqualizeImage(Image *image,
1474 ExceptionInfo *exception)
1476 #define EqualizeImageTag "Equalize/Image"
1488 black[CompositePixelChannel],
1492 white[CompositePixelChannel];
1504 Allocate and initialize histogram arrays.
1506 assert(image != (Image *) NULL);
1507 assert(image->signature == MagickSignature);
1508 if (image->debug != MagickFalse)
1509 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1510 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1511 GetPixelChannels(image)*sizeof(*equalize_map));
1512 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
1513 GetPixelChannels(image)*sizeof(*histogram));
1514 map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1515 GetPixelChannels(image)*sizeof(*map));
1516 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1517 (map == (double *) NULL))
1519 if (map != (double *) NULL)
1520 map=(double *) RelinquishMagickMemory(map);
1521 if (histogram != (double *) NULL)
1522 histogram=(double *) RelinquishMagickMemory(histogram);
1523 if (equalize_map != (double *) NULL)
1524 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1525 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1532 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1533 sizeof(*histogram));
1534 image_view=AcquireVirtualCacheView(image,exception);
1535 for (y=0; y < (ssize_t) image->rows; y++)
1537 register const Quantum
1543 if (status == MagickFalse)
1545 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1546 if (p == (const Quantum *) NULL)
1551 for (x=0; x < (ssize_t) image->columns; x++)
1556 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1557 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
1558 p+=GetPixelChannels(image);
1561 image_view=DestroyCacheView(image_view);
1563 Integrate the histogram to get the equalization map.
1565 number_channels=GetPixelChannels(image);
1566 for (i=0; i < (ssize_t) number_channels; i++)
1575 for (j=0; j <= (ssize_t) MaxMap; j++)
1577 intensity+=histogram[GetPixelChannels(image)*j+i];
1578 map[GetPixelChannels(image)*j+i]=intensity;
1581 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1582 sizeof(*equalize_map));
1583 number_channels=GetPixelChannels(image);
1584 for (i=0; i < (ssize_t) number_channels; i++)
1590 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1591 if (black[i] != white[i])
1592 for (j=0; j <= (ssize_t) MaxMap; j++)
1593 equalize_map[GetPixelChannels(image)*j+i]=(double)
1594 ScaleMapToQuantum((double) ((MaxMap*(map[
1595 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1597 histogram=(double *) RelinquishMagickMemory(histogram);
1598 map=(double *) RelinquishMagickMemory(map);
1599 if (image->storage_class == PseudoClass)
1607 for (j=0; j < (ssize_t) image->colors; j++)
1609 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1611 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1612 if (black[channel] != white[channel])
1613 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1614 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1617 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1619 PixelChannel channel=GetPixelChannelChannel(image,
1621 if (black[channel] != white[channel])
1622 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1623 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1626 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1628 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1629 if (black[channel] != white[channel])
1630 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1631 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1634 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1636 PixelChannel channel=GetPixelChannelChannel(image,
1638 if (black[channel] != white[channel])
1639 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1640 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1649 image_view=AcquireAuthenticCacheView(image,exception);
1650 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1651 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1652 magick_threads(image,image,image->rows,1)
1654 for (y=0; y < (ssize_t) image->rows; y++)
1662 if (status == MagickFalse)
1664 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1665 if (q == (Quantum *) NULL)
1670 for (x=0; x < (ssize_t) image->columns; x++)
1675 if (GetPixelMask(image,q) == 0)
1677 q+=GetPixelChannels(image);
1680 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1682 PixelChannel channel=GetPixelChannelChannel(image,i);
1683 PixelTrait traits=GetPixelChannelTraits(image,channel);
1684 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1686 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1687 ScaleQuantumToMap(q[i])+i]);
1689 q+=GetPixelChannels(image);
1691 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1693 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1698 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1699 #pragma omp critical (MagickCore_EqualizeImage)
1701 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1702 if (proceed == MagickFalse)
1706 image_view=DestroyCacheView(image_view);
1707 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1712 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1716 % G a m m a I m a g e %
1720 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1722 % GammaImage() gamma-corrects a particular image channel. The same
1723 % image viewed on different devices will have perceptual differences in the
1724 % way the image's intensities are represented on the screen. Specify
1725 % individual gamma levels for the red, green, and blue channels, or adjust
1726 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1728 % You can also reduce the influence of a particular channel with a gamma
1731 % The format of the GammaImage method is:
1733 % MagickBooleanType GammaImage(Image *image,const double gamma,
1734 % ExceptionInfo *exception)
1736 % A description of each parameter follows:
1738 % o image: the image.
1740 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1742 % o gamma: the image gamma.
1745 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1746 ExceptionInfo *exception)
1748 #define GammaCorrectImageTag "GammaCorrect/Image"
1769 Allocate and initialize gamma maps.
1771 assert(image != (Image *) NULL);
1772 assert(image->signature == MagickSignature);
1773 if (image->debug != MagickFalse)
1774 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1777 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1778 if (gamma_map == (Quantum *) NULL)
1779 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1781 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1783 for (i=0; i <= (ssize_t) MaxMap; i++)
1784 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1785 MaxMap,1.0/gamma)));
1786 if (image->storage_class == PseudoClass)
1787 for (i=0; i < (ssize_t) image->colors; i++)
1790 Gamma-correct colormap.
1792 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1793 image->colormap[i].red=(double) gamma_map[
1794 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
1795 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1796 image->colormap[i].green=(double) gamma_map[
1797 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
1798 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1799 image->colormap[i].blue=(double) gamma_map[
1800 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
1801 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1802 image->colormap[i].alpha=(double) gamma_map[
1803 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
1806 Gamma-correct image.
1810 image_view=AcquireAuthenticCacheView(image,exception);
1811 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1812 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1813 magick_threads(image,image,image->rows,1)
1815 for (y=0; y < (ssize_t) image->rows; y++)
1823 if (status == MagickFalse)
1825 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1826 if (q == (Quantum *) NULL)
1831 for (x=0; x < (ssize_t) image->columns; x++)
1836 if (GetPixelMask(image,q) == 0)
1838 q+=GetPixelChannels(image);
1841 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1843 PixelChannel channel=GetPixelChannelChannel(image,i);
1844 PixelTrait traits=GetPixelChannelTraits(image,channel);
1845 if ((traits & UpdatePixelTrait) == 0)
1847 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
1849 q+=GetPixelChannels(image);
1851 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1853 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1858 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1859 #pragma omp critical (MagickCore_GammaImage)
1861 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1863 if (proceed == MagickFalse)
1867 image_view=DestroyCacheView(image_view);
1868 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1869 if (image->gamma != 0.0)
1870 image->gamma*=gamma;
1875 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1879 % G r a y s c a l e I m a g e %
1883 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1885 % GrayscaleImage() converts the image to grayscale.
1887 % The format of the GrayscaleImage method is:
1889 % MagickBooleanType GrayscaleImage(Image *image,
1890 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1892 % A description of each parameter follows:
1894 % o image: the image.
1896 % o method: the pixel intensity method.
1898 % o exception: return any errors or warnings in this structure.
1902 static inline MagickRealType MagickMax(const MagickRealType x,
1903 const MagickRealType y)
1910 static inline MagickRealType MagickMin(const MagickRealType x,
1911 const MagickRealType y)
1918 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1919 const PixelIntensityMethod grayscale,ExceptionInfo *exception)
1921 #define GrayscaleImageTag "Grayscale/Image"
1935 assert(image != (Image *) NULL);
1936 assert(image->signature == MagickSignature);
1937 if (image->debug != MagickFalse)
1938 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1939 if (image->storage_class == PseudoClass)
1941 if (SyncImage(image,exception) == MagickFalse)
1942 return(MagickFalse);
1943 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1944 return(MagickFalse);
1951 image_view=AcquireAuthenticCacheView(image,exception);
1952 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1953 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1954 magick_threads(image,image,image->rows,1)
1956 for (y=0; y < (ssize_t) image->rows; y++)
1964 if (status == MagickFalse)
1966 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1967 if (q == (Quantum *) NULL)
1972 for (x=0; x < (ssize_t) image->columns; x++)
1980 if (GetPixelMask(image,q) == 0)
1982 q+=GetPixelChannels(image);
1985 red=(MagickRealType) GetPixelRed(image,q);
1986 green=(MagickRealType) GetPixelGreen(image,q);
1987 blue=(MagickRealType) GetPixelBlue(image,q);
1988 switch (image->intensity)
1990 case AveragePixelIntensityMethod:
1992 intensity=(red+green+blue)/3.0;
1995 case BrightnessPixelIntensityMethod:
1997 intensity=MagickMax(MagickMax(red,green),blue);
2000 case LightnessPixelIntensityMethod:
2002 intensity=MagickMin(MagickMin(red,green),blue);
2005 case Rec601LumaPixelIntensityMethod:
2007 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2010 case Rec601LuminancePixelIntensityMethod:
2012 if (image->colorspace == sRGBColorspace)
2014 red=DecodePixelGamma(red);
2015 green=DecodePixelGamma(green);
2016 blue=DecodePixelGamma(blue);
2018 intensity=0.298839f*red+0.586811f*green+0.114350f*blue;
2021 case Rec709LumaPixelIntensityMethod:
2024 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2027 case Rec709LuminancePixelIntensityMethod:
2029 if (image->colorspace == sRGBColorspace)
2031 red=DecodePixelGamma(red);
2032 green=DecodePixelGamma(green);
2033 blue=DecodePixelGamma(blue);
2035 intensity=0.21260f*red+0.71520f*green+0.07220f*blue;
2038 case RMSPixelIntensityMethod:
2040 intensity=(MagickRealType) sqrt((double) red*red+green*green+
2045 SetPixelGray(image,ClampToQuantum(intensity),q);
2046 q+=GetPixelChannels(image);
2048 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2050 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2055 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2056 #pragma omp critical (MagickCore_GrayscaleImage)
2058 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2060 if (proceed == MagickFalse)
2064 image_view=DestroyCacheView(image_view);
2069 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2073 % H a l d C l u t I m a g e %
2077 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2079 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2080 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2081 % Create it with the HALD coder. You can apply any color transformation to
2082 % the Hald image and then use this method to apply the transform to the
2085 % The format of the HaldClutImage method is:
2087 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2088 % ExceptionInfo *exception)
2090 % A description of each parameter follows:
2092 % o image: the image, which is replaced by indexed CLUT values
2094 % o hald_image: the color lookup table image for replacement color values.
2096 % o exception: return any errors or warnings in this structure.
2099 MagickExport MagickBooleanType HaldClutImage(Image *image,
2100 const Image *hald_image,ExceptionInfo *exception)
2102 #define HaldClutImageTag "Clut/Image"
2104 typedef struct _HaldInfo
2136 assert(image != (Image *) NULL);
2137 assert(image->signature == MagickSignature);
2138 if (image->debug != MagickFalse)
2139 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2140 assert(hald_image != (Image *) NULL);
2141 assert(hald_image->signature == MagickSignature);
2142 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2143 return(MagickFalse);
2144 if (image->alpha_trait != BlendPixelTrait)
2145 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2151 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2152 (MagickRealType) hald_image->rows);
2153 for (level=2; (level*level*level) < length; level++) ;
2155 cube_size=level*level;
2156 width=(double) hald_image->columns;
2157 GetPixelInfo(hald_image,&zero);
2158 hald_view=AcquireVirtualCacheView(hald_image,exception);
2159 image_view=AcquireAuthenticCacheView(image,exception);
2160 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2161 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2162 magick_threads(image,image,image->rows,1)
2164 for (y=0; y < (ssize_t) image->rows; y++)
2172 if (status == MagickFalse)
2174 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2175 if (q == (Quantum *) NULL)
2180 for (x=0; x < (ssize_t) image->columns; x++)
2195 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2196 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2197 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2198 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2199 point.x-=floor(point.x);
2200 point.y-=floor(point.y);
2201 point.z-=floor(point.z);
2203 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2204 fmod(offset,width),floor(offset/width),&pixel1,exception);
2206 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2207 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2209 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2212 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2213 fmod(offset,width),floor(offset/width),&pixel1,exception);
2214 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2215 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2217 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2220 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2222 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2223 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2224 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2225 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2226 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2227 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2228 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2229 (image->colorspace == CMYKColorspace))
2230 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2231 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2232 (image->alpha_trait == BlendPixelTrait))
2233 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2234 q+=GetPixelChannels(image);
2236 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2238 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2243 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2244 #pragma omp critical (MagickCore_HaldClutImage)
2246 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2247 if (proceed == MagickFalse)
2251 hald_view=DestroyCacheView(hald_view);
2252 image_view=DestroyCacheView(image_view);
2257 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2261 % L e v e l I m a g e %
2265 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2267 % LevelImage() adjusts the levels of a particular image channel by
2268 % scaling the colors falling between specified white and black points to
2269 % the full available quantum range.
2271 % The parameters provided represent the black, and white points. The black
2272 % point specifies the darkest color in the image. Colors darker than the
2273 % black point are set to zero. White point specifies the lightest color in
2274 % the image. Colors brighter than the white point are set to the maximum
2277 % If a '!' flag is given, map black and white colors to the given levels
2278 % rather than mapping those levels to black and white. See
2279 % LevelizeImage() below.
2281 % Gamma specifies a gamma correction to apply to the image.
2283 % The format of the LevelImage method is:
2285 % MagickBooleanType LevelImage(Image *image,const double black_point,
2286 % const double white_point,const double gamma,ExceptionInfo *exception)
2288 % A description of each parameter follows:
2290 % o image: the image.
2292 % o black_point: The level to map zero (black) to.
2294 % o white_point: The level to map QuantumRange (white) to.
2296 % o exception: return any errors or warnings in this structure.
2300 static inline double LevelPixel(const double black_point,
2301 const double white_point,const double gamma,const double pixel)
2307 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2308 level_pixel=(double) QuantumRange*pow(scale*((double) pixel-
2309 black_point),1.0/gamma);
2310 return(level_pixel);
2313 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2314 const double white_point,const double gamma,ExceptionInfo *exception)
2316 #define LevelImageTag "Level/Image"
2334 Allocate and initialize levels map.
2336 assert(image != (Image *) NULL);
2337 assert(image->signature == MagickSignature);
2338 if (image->debug != MagickFalse)
2339 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2340 if (image->storage_class == PseudoClass)
2341 for (i=0; i < (ssize_t) image->colors; i++)
2346 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2347 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2348 white_point,gamma,image->colormap[i].red));
2349 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2350 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2351 white_point,gamma,image->colormap[i].green));
2352 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2353 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2354 white_point,gamma,image->colormap[i].blue));
2355 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2356 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2357 white_point,gamma,image->colormap[i].alpha));
2364 image_view=AcquireAuthenticCacheView(image,exception);
2365 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2366 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2367 magick_threads(image,image,image->rows,1)
2369 for (y=0; y < (ssize_t) image->rows; y++)
2377 if (status == MagickFalse)
2379 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2380 if (q == (Quantum *) NULL)
2385 for (x=0; x < (ssize_t) image->columns; x++)
2390 if (GetPixelMask(image,q) == 0)
2392 q+=GetPixelChannels(image);
2395 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2397 PixelChannel channel=GetPixelChannelChannel(image,i);
2398 PixelTrait traits=GetPixelChannelTraits(image,channel);
2399 if ((traits & UpdatePixelTrait) == 0)
2401 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2404 q+=GetPixelChannels(image);
2406 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2408 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2413 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2414 #pragma omp critical (MagickCore_LevelImage)
2416 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2417 if (proceed == MagickFalse)
2421 image_view=DestroyCacheView(image_view);
2426 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2430 % L e v e l i z e I m a g e %
2434 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2436 % LevelizeImage() applies the reversed LevelImage() operation to just
2437 % the specific channels specified. It compresses the full range of color
2438 % values, so that they lie between the given black and white points. Gamma is
2439 % applied before the values are mapped.
2441 % LevelizeImage() can be called with by using a +level command line
2442 % API option, or using a '!' on a -level or LevelImage() geometry string.
2444 % It can be used to de-contrast a greyscale image to the exact levels
2445 % specified. Or by using specific levels for each channel of an image you
2446 % can convert a gray-scale image to any linear color gradient, according to
2449 % The format of the LevelizeImage method is:
2451 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2452 % const double white_point,const double gamma,ExceptionInfo *exception)
2454 % A description of each parameter follows:
2456 % o image: the image.
2458 % o black_point: The level to map zero (black) to.
2460 % o white_point: The level to map QuantumRange (white) to.
2462 % o gamma: adjust gamma by this factor before mapping values.
2464 % o exception: return any errors or warnings in this structure.
2467 MagickExport MagickBooleanType LevelizeImage(Image *image,
2468 const double black_point,const double white_point,const double gamma,
2469 ExceptionInfo *exception)
2471 #define LevelizeImageTag "Levelize/Image"
2472 #define LevelizeValue(x) (ClampToQuantum((pow((double) (QuantumScale*(x)), \
2473 1.0/gamma))*(white_point-black_point)+black_point))
2491 Allocate and initialize levels map.
2493 assert(image != (Image *) NULL);
2494 assert(image->signature == MagickSignature);
2495 if (image->debug != MagickFalse)
2496 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2497 if (image->storage_class == PseudoClass)
2498 for (i=0; i < (ssize_t) image->colors; i++)
2503 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2504 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2505 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2506 image->colormap[i].green=(double) LevelizeValue(
2507 image->colormap[i].green);
2508 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2509 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2510 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2511 image->colormap[i].alpha=(double) LevelizeValue(
2512 image->colormap[i].alpha);
2519 image_view=AcquireAuthenticCacheView(image,exception);
2520 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2521 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2522 magick_threads(image,image,image->rows,1)
2524 for (y=0; y < (ssize_t) image->rows; y++)
2532 if (status == MagickFalse)
2534 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2535 if (q == (Quantum *) NULL)
2540 for (x=0; x < (ssize_t) image->columns; x++)
2545 if (GetPixelMask(image,q) == 0)
2547 q+=GetPixelChannels(image);
2550 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2552 PixelChannel channel=GetPixelChannelChannel(image,i);
2553 PixelTrait traits=GetPixelChannelTraits(image,channel);
2554 if ((traits & UpdatePixelTrait) == 0)
2556 q[i]=LevelizeValue(q[i]);
2558 q+=GetPixelChannels(image);
2560 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2562 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2567 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2568 #pragma omp critical (MagickCore_LevelizeImage)
2570 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2571 if (proceed == MagickFalse)
2575 image_view=DestroyCacheView(image_view);
2580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2584 % L e v e l I m a g e C o l o r s %
2588 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2590 % LevelImageColors() maps the given color to "black" and "white" values,
2591 % linearly spreading out the colors, and level values on a channel by channel
2592 % bases, as per LevelImage(). The given colors allows you to specify
2593 % different level ranges for each of the color channels separately.
2595 % If the boolean 'invert' is set true the image values will modifyed in the
2596 % reverse direction. That is any existing "black" and "white" colors in the
2597 % image will become the color values given, with all other values compressed
2598 % appropriatally. This effectivally maps a greyscale gradient into the given
2601 % The format of the LevelImageColors method is:
2603 % MagickBooleanType LevelImageColors(Image *image,
2604 % const PixelInfo *black_color,const PixelInfo *white_color,
2605 % const MagickBooleanType invert,ExceptionInfo *exception)
2607 % A description of each parameter follows:
2609 % o image: the image.
2611 % o black_color: The color to map black to/from
2613 % o white_point: The color to map white to/from
2615 % o invert: if true map the colors (levelize), rather than from (level)
2617 % o exception: return any errors or warnings in this structure.
2620 MagickExport MagickBooleanType LevelImageColors(Image *image,
2621 const PixelInfo *black_color,const PixelInfo *white_color,
2622 const MagickBooleanType invert,ExceptionInfo *exception)
2631 Allocate and initialize levels map.
2633 assert(image != (Image *) NULL);
2634 assert(image->signature == MagickSignature);
2635 if (image->debug != MagickFalse)
2636 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2638 if (invert == MagickFalse)
2640 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2642 channel_mask=SetImageChannelMask(image,RedChannel);
2643 status|=LevelImage(image,black_color->red,white_color->red,1.0,
2645 (void) SetImageChannelMask(image,channel_mask);
2647 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2649 channel_mask=SetImageChannelMask(image,GreenChannel);
2650 status|=LevelImage(image,black_color->green,white_color->green,1.0,
2652 (void) SetImageChannelMask(image,channel_mask);
2654 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2656 channel_mask=SetImageChannelMask(image,BlueChannel);
2657 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
2659 (void) SetImageChannelMask(image,channel_mask);
2661 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2662 (image->colorspace == CMYKColorspace))
2664 channel_mask=SetImageChannelMask(image,BlackChannel);
2665 status|=LevelImage(image,black_color->black,white_color->black,1.0,
2667 (void) SetImageChannelMask(image,channel_mask);
2669 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2670 (image->alpha_trait == BlendPixelTrait))
2672 channel_mask=SetImageChannelMask(image,AlphaChannel);
2673 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2675 (void) SetImageChannelMask(image,channel_mask);
2680 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2682 channel_mask=SetImageChannelMask(image,RedChannel);
2683 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2685 (void) SetImageChannelMask(image,channel_mask);
2687 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2689 channel_mask=SetImageChannelMask(image,GreenChannel);
2690 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2692 (void) SetImageChannelMask(image,channel_mask);
2694 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2696 channel_mask=SetImageChannelMask(image,BlueChannel);
2697 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2699 (void) SetImageChannelMask(image,channel_mask);
2701 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2702 (image->colorspace == CMYKColorspace))
2704 channel_mask=SetImageChannelMask(image,BlackChannel);
2705 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2707 (void) SetImageChannelMask(image,channel_mask);
2709 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2710 (image->alpha_trait == BlendPixelTrait))
2712 channel_mask=SetImageChannelMask(image,AlphaChannel);
2713 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2715 (void) SetImageChannelMask(image,channel_mask);
2718 return(status == 0 ? MagickFalse : MagickTrue);
2722 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2726 % L i n e a r S t r e t c h I m a g e %
2730 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2732 % LinearStretchImage() discards any pixels below the black point and above
2733 % the white point and levels the remaining pixels.
2735 % The format of the LinearStretchImage method is:
2737 % MagickBooleanType LinearStretchImage(Image *image,
2738 % const double black_point,const double white_point,
2739 % ExceptionInfo *exception)
2741 % A description of each parameter follows:
2743 % o image: the image.
2745 % o black_point: the black point.
2747 % o white_point: the white point.
2749 % o exception: return any errors or warnings in this structure.
2752 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2753 const double black_point,const double white_point,ExceptionInfo *exception)
2755 #define LinearStretchImageTag "LinearStretch/Image"
2773 Allocate histogram and linear map.
2775 assert(image != (Image *) NULL);
2776 assert(image->signature == MagickSignature);
2777 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2778 if (histogram == (double *) NULL)
2779 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2784 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2785 image_view=AcquireVirtualCacheView(image,exception);
2786 for (y=0; y < (ssize_t) image->rows; y++)
2788 register const Quantum
2794 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2795 if (p == (const Quantum *) NULL)
2797 for (x=0; x < (ssize_t) image->columns; x++)
2802 intensity=GetPixelIntensity(image,p);
2803 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2804 p+=GetPixelChannels(image);
2807 image_view=DestroyCacheView(image_view);
2809 Find the histogram boundaries by locating the black and white point levels.
2812 for (black=0; black < (ssize_t) MaxMap; black++)
2814 intensity+=histogram[black];
2815 if (intensity >= black_point)
2819 for (white=(ssize_t) MaxMap; white != 0; white--)
2821 intensity+=histogram[white];
2822 if (intensity >= white_point)
2825 histogram=(double *) RelinquishMagickMemory(histogram);
2826 status=LevelImage(image,(double) black,(double) white,1.0,exception);
2831 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2835 % M o d u l a t e I m a g e %
2839 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2841 % ModulateImage() lets you control the brightness, saturation, and hue
2842 % of an image. Modulate represents the brightness, saturation, and hue
2843 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2844 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2845 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2847 % The format of the ModulateImage method is:
2849 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2850 % ExceptionInfo *exception)
2852 % A description of each parameter follows:
2854 % o image: the image.
2856 % o modulate: Define the percent change in brightness, saturation, and hue.
2858 % o exception: return any errors or warnings in this structure.
2862 static inline void ModulateHCL(const double percent_hue,
2863 const double percent_chroma,const double percent_luma,double *red,
2864 double *green,double *blue)
2872 Increase or decrease color luma, chroma, or hue.
2874 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2875 hue+=0.5*(0.01*percent_hue-1.0);
2880 chroma*=0.01*percent_chroma;
2881 luma*=0.01*percent_luma;
2882 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2885 static inline void ModulateHSB(const double percent_hue,
2886 const double percent_saturation,const double percent_brightness,double *red,
2887 double *green,double *blue)
2895 Increase or decrease color brightness, saturation, or hue.
2897 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2898 hue+=0.5*(0.01*percent_hue-1.0);
2903 saturation*=0.01*percent_saturation;
2904 brightness*=0.01*percent_brightness;
2905 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2908 static inline void ModulateHSL(const double percent_hue,
2909 const double percent_saturation,const double percent_lightness,double *red,
2910 double *green,double *blue)
2918 Increase or decrease color lightness, saturation, or hue.
2920 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2921 hue+=0.5*(0.01*percent_hue-1.0);
2926 saturation*=0.01*percent_saturation;
2927 lightness*=0.01*percent_lightness;
2928 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2931 static inline void ModulateHWB(const double percent_hue,
2932 const double percent_whiteness,const double percent_blackness,double *red,
2933 double *green,double *blue)
2941 Increase or decrease color blackness, whiteness, or hue.
2943 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2944 hue+=0.5*(0.01*percent_hue-1.0);
2949 blackness*=0.01*percent_blackness;
2950 whiteness*=0.01*percent_whiteness;
2951 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2954 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2955 ExceptionInfo *exception)
2957 #define ModulateImageTag "Modulate/Image"
2992 Initialize modulate table.
2994 assert(image != (Image *) NULL);
2995 assert(image->signature == MagickSignature);
2996 if (image->debug != MagickFalse)
2997 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2998 if (modulate == (char *) NULL)
2999 return(MagickFalse);
3000 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3001 (void) TransformImageColorspace(image,sRGBColorspace,exception);
3002 flags=ParseGeometry(modulate,&geometry_info);
3003 percent_brightness=geometry_info.rho;
3004 percent_saturation=geometry_info.sigma;
3005 if ((flags & SigmaValue) == 0)
3006 percent_saturation=100.0;
3007 percent_hue=geometry_info.xi;
3008 if ((flags & XiValue) == 0)
3010 colorspace=UndefinedColorspace;
3011 artifact=GetImageArtifact(image,"modulate:colorspace");
3012 if (artifact != (const char *) NULL)
3013 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3014 MagickFalse,artifact);
3015 if (image->storage_class == PseudoClass)
3016 for (i=0; i < (ssize_t) image->colors; i++)
3026 red=image->colormap[i].red;
3027 green=image->colormap[i].green;
3028 blue=image->colormap[i].blue;
3033 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3039 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3046 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3052 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3063 image_view=AcquireAuthenticCacheView(image,exception);
3064 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3065 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3066 magick_threads(image,image,image->rows,1)
3068 for (y=0; y < (ssize_t) image->rows; y++)
3076 if (status == MagickFalse)
3078 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3079 if (q == (Quantum *) NULL)
3084 for (x=0; x < (ssize_t) image->columns; x++)
3091 red=(double) GetPixelRed(image,q);
3092 green=(double) GetPixelGreen(image,q);
3093 blue=(double) GetPixelBlue(image,q);
3098 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3104 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3111 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3117 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3122 SetPixelRed(image,ClampToQuantum(red),q);
3123 SetPixelGreen(image,ClampToQuantum(green),q);
3124 SetPixelBlue(image,ClampToQuantum(blue),q);
3125 q+=GetPixelChannels(image);
3127 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3129 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3134 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3135 #pragma omp critical (MagickCore_ModulateImage)
3137 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3138 if (proceed == MagickFalse)
3142 image_view=DestroyCacheView(image_view);
3147 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3151 % N e g a t e I m a g e %
3155 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3157 % NegateImage() negates the colors in the reference image. The grayscale
3158 % option means that only grayscale values within the image are negated.
3160 % The format of the NegateImage method is:
3162 % MagickBooleanType NegateImage(Image *image,
3163 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3165 % A description of each parameter follows:
3167 % o image: the image.
3169 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3171 % o exception: return any errors or warnings in this structure.
3174 MagickExport MagickBooleanType NegateImage(Image *image,
3175 const MagickBooleanType grayscale,ExceptionInfo *exception)
3177 #define NegateImageTag "Negate/Image"
3194 assert(image != (Image *) NULL);
3195 assert(image->signature == MagickSignature);
3196 if (image->debug != MagickFalse)
3197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3198 if (image->storage_class == PseudoClass)
3199 for (i=0; i < (ssize_t) image->colors; i++)
3204 if (grayscale != MagickFalse)
3205 if ((image->colormap[i].red != image->colormap[i].green) ||
3206 (image->colormap[i].green != image->colormap[i].blue))
3208 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3209 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3210 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3211 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3212 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3213 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3220 image_view=AcquireAuthenticCacheView(image,exception);
3221 if (grayscale != MagickFalse)
3223 for (y=0; y < (ssize_t) image->rows; y++)
3234 if (status == MagickFalse)
3236 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3238 if (q == (Quantum *) NULL)
3243 for (x=0; x < (ssize_t) image->columns; x++)
3248 if ((GetPixelMask(image,q) == 0) ||
3249 (IsPixelGray(image,q) != MagickFalse))
3251 q+=GetPixelChannels(image);
3254 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3256 PixelChannel channel=GetPixelChannelChannel(image,i);
3257 PixelTrait traits=GetPixelChannelTraits(image,channel);
3258 if ((traits & UpdatePixelTrait) == 0)
3260 q[i]=QuantumRange-q[i];
3262 q+=GetPixelChannels(image);
3264 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3265 if (sync == MagickFalse)
3267 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3272 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3273 #pragma omp critical (MagickCore_NegateImage)
3275 proceed=SetImageProgress(image,NegateImageTag,progress++,
3277 if (proceed == MagickFalse)
3281 image_view=DestroyCacheView(image_view);
3287 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3288 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3289 magick_threads(image,image,image->rows,1)
3291 for (y=0; y < (ssize_t) image->rows; y++)
3299 if (status == MagickFalse)
3301 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3302 if (q == (Quantum *) NULL)
3307 for (x=0; x < (ssize_t) image->columns; x++)
3312 if (GetPixelMask(image,q) == 0)
3314 q+=GetPixelChannels(image);
3317 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3319 PixelChannel channel=GetPixelChannelChannel(image,i);
3320 PixelTrait traits=GetPixelChannelTraits(image,channel);
3321 if ((traits & UpdatePixelTrait) == 0)
3323 q[i]=QuantumRange-q[i];
3325 q+=GetPixelChannels(image);
3327 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3329 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3334 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3335 #pragma omp critical (MagickCore_NegateImage)
3337 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3338 if (proceed == MagickFalse)
3342 image_view=DestroyCacheView(image_view);
3347 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3351 % N o r m a l i z e I m a g e %
3355 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3357 % The NormalizeImage() method enhances the contrast of a color image by
3358 % mapping the darkest 2 percent of all pixel to black and the brightest
3359 % 1 percent to white.
3361 % The format of the NormalizeImage method is:
3363 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3365 % A description of each parameter follows:
3367 % o image: the image.
3369 % o exception: return any errors or warnings in this structure.
3372 MagickExport MagickBooleanType NormalizeImage(Image *image,
3373 ExceptionInfo *exception)
3379 black_point=(double) image->columns*image->rows*0.0015;
3380 white_point=(double) image->columns*image->rows*0.9995;
3381 return(ContrastStretchImage(image,black_point,white_point,exception));
3385 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3389 % S i g m o i d a l C o n t r a s t I m a g e %
3393 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3395 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3396 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3397 % sigmoidal transfer function without saturating highlights or shadows.
3398 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3399 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3400 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3401 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3404 % The format of the SigmoidalContrastImage method is:
3406 % MagickBooleanType SigmoidalContrastImage(Image *image,
3407 % const MagickBooleanType sharpen,const char *levels,
3408 % ExceptionInfo *exception)
3410 % A description of each parameter follows:
3412 % o image: the image.
3414 % o sharpen: Increase or decrease image contrast.
3416 % o contrast: strength of the contrast, the larger the number the more
3417 % 'threshold-like' it becomes.
3419 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3421 % o exception: return any errors or warnings in this structure.
3426 ImageMagick 6 has a version of this function which uses LUTs.
3430 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3433 The first version, based on the hyperbolic tangent tanh, when combined with
3434 the scaling step, is an exact arithmetic clone of the the sigmoid function
3435 based on the logistic curve. The equivalence is based on the identity
3437 1/(1+exp(-t)) = (1+tanh(t/2))/2
3439 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3440 scaled sigmoidal derivation is invariant under affine transformations of
3443 The tanh version is almost certainly more accurate and cheaper. The 0.5
3444 factor in the argument is to clone the legacy ImageMagick behavior. The
3445 reason for making the define depend on atanh even though it only uses tanh
3446 has to do with the construction of the inverse of the scaled sigmoidal.
3448 #if defined(MAGICKCORE_HAVE_ATANH)
3449 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3451 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3454 Scaled sigmoidal function:
3456 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3457 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3459 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3460 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3461 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3462 zero. This is fixed below by exiting immediately when contrast is small,
3463 leaving the image (or colormap) unmodified. This appears to be safe because
3464 the series expansion of the logistic sigmoidal function around x=b is
3468 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3470 #define ScaledSigmoidal(a,b,x) ( \
3471 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3472 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3474 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3475 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3476 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3477 when creating a LUT from in gamut values, hence the branching. In
3478 addition, HDRI may have out of gamut values.
3479 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3480 It is only a right inverse. This is unavoidable.
3482 static inline double InverseScaledSigmoidal(const double a,const double b,
3485 const double sig0=Sigmoidal(a,b,0.0);
3486 const double sig1=Sigmoidal(a,b,1.0);
3487 const double argument=(sig1-sig0)*x+sig0;
3488 const double clamped=
3490 #if defined(MAGICKCORE_HAVE_ATANH)
3491 argument < -1+MagickEpsilon
3495 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3497 return(b+(2.0/a)*atanh(clamped));
3499 argument < MagickEpsilon
3503 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3505 return(b-log(1.0/clamped-1.0)/a);
3509 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3510 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3511 ExceptionInfo *exception)
3513 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3514 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3515 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3516 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3517 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3534 assert(image != (Image *) NULL);
3535 assert(image->signature == MagickSignature);
3536 if (image->debug != MagickFalse)
3537 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3539 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3540 case nothing is done.
3542 if (contrast < MagickEpsilon)
3545 Sigmoidal-contrast enhance colormap.
3547 if (image->storage_class == PseudoClass)
3552 if (sharpen != MagickFalse)
3553 for (i=0; i < (ssize_t) image->colors; i++)
3555 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3556 image->colormap[i].red=ScaledSig(image->colormap[i].red);
3557 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3558 image->colormap[i].green=ScaledSig(image->colormap[i].green);
3559 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3560 image->colormap[i].blue=ScaledSig(image->colormap[i].blue);
3561 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3562 image->colormap[i].alpha=ScaledSig(image->colormap[i].alpha);
3565 for (i=0; i < (ssize_t) image->colors; i++)
3567 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3568 image->colormap[i].red=InverseScaledSig(image->colormap[i].red);
3569 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3570 image->colormap[i].green=InverseScaledSig(image->colormap[i].green);
3571 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3572 image->colormap[i].blue=InverseScaledSig(image->colormap[i].blue);
3573 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3574 image->colormap[i].alpha=InverseScaledSig(image->colormap[i].alpha);
3578 Sigmoidal-contrast enhance image.
3582 image_view=AcquireAuthenticCacheView(image,exception);
3583 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3584 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3585 magick_threads(image,image,image->rows,1)
3587 for (y=0; y < (ssize_t) image->rows; y++)
3595 if (status == MagickFalse)
3597 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3598 if (q == (Quantum *) NULL)
3603 for (x=0; x < (ssize_t) image->columns; x++)
3608 if (GetPixelMask(image,q) == 0)
3610 q+=GetPixelChannels(image);
3613 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3615 PixelChannel channel=GetPixelChannelChannel(image,i);
3616 PixelTrait traits=GetPixelChannelTraits(image,channel);
3617 if ((traits & UpdatePixelTrait) == 0)
3619 if (sharpen != MagickFalse)
3620 q[i]=ScaledSig(q[i]);
3622 q[i]=InverseScaledSig(q[i]);
3624 q+=GetPixelChannels(image);
3626 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3628 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3633 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3634 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3636 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3638 if (proceed == MagickFalse)
3642 image_view=DestroyCacheView(image_view);