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-2017 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-private.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 (GetPixelWriteMask(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 GetNextToken(p,&p,MagickPathExtent,token);
582 GetNextToken(p,&p,MagickPathExtent,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 GetNextToken(p,&p,MagickPathExtent,token);
614 GetNextToken(p,&p,MagickPathExtent,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 GetNextToken(p,&p,MagickPathExtent,token);
647 GetNextToken(p,&p,MagickPathExtent,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 GetNextToken(p,&p,MagickPathExtent,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 defined(MAGICKCORE_OPENCL_SUPPORT)
891 if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
894 if (image->debug != MagickFalse)
895 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
896 sign=sharpen != MagickFalse ? 1 : -1;
897 if (image->storage_class == PseudoClass)
900 Contrast enhance colormap.
902 for (i=0; i < (ssize_t) image->colors; i++)
909 red=(double) image->colormap[i].red;
910 green=(double) image->colormap[i].green;
911 blue=(double) image->colormap[i].blue;
912 Contrast(sign,&red,&green,&blue);
913 image->colormap[i].red=(MagickRealType) red;
914 image->colormap[i].green=(MagickRealType) green;
915 image->colormap[i].blue=(MagickRealType) blue;
919 Contrast enhance image.
923 image_view=AcquireAuthenticCacheView(image,exception);
924 #if defined(MAGICKCORE_OPENMP_SUPPORT)
925 #pragma omp parallel for schedule(static,4) shared(progress,status) \
926 magick_threads(image,image,image->rows,1)
928 for (y=0; y < (ssize_t) image->rows; y++)
941 if (status == MagickFalse)
943 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
944 if (q == (Quantum *) NULL)
949 for (x=0; x < (ssize_t) image->columns; x++)
951 red=(double) GetPixelRed(image,q);
952 green=(double) GetPixelGreen(image,q);
953 blue=(double) GetPixelBlue(image,q);
954 Contrast(sign,&red,&green,&blue);
955 SetPixelRed(image,ClampToQuantum(red),q);
956 SetPixelGreen(image,ClampToQuantum(green),q);
957 SetPixelBlue(image,ClampToQuantum(blue),q);
958 q+=GetPixelChannels(image);
960 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
962 if (image->progress_monitor != (MagickProgressMonitor) NULL)
967 #if defined(MAGICKCORE_OPENMP_SUPPORT)
968 #pragma omp critical (MagickCore_ContrastImage)
970 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
971 if (proceed == MagickFalse)
975 image_view=DestroyCacheView(image_view);
980 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
984 % C o n t r a s t S t r e t c h I m a g e %
988 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
990 % ContrastStretchImage() is a simple image enhancement technique that attempts
991 % to improve the contrast in an image by 'stretching' the range of intensity
992 % values it contains to span a desired range of values. It differs from the
993 % more sophisticated histogram equalization in that it can only apply a
994 % linear scaling function to the image pixel values. As a result the
995 % 'enhancement' is less harsh.
997 % The format of the ContrastStretchImage method is:
999 % MagickBooleanType ContrastStretchImage(Image *image,
1000 % const char *levels,ExceptionInfo *exception)
1002 % A description of each parameter follows:
1004 % o image: the image.
1006 % o black_point: the black point.
1008 % o white_point: the white point.
1010 % o levels: Specify the levels where the black and white points have the
1011 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1013 % o exception: return any errors or warnings in this structure.
1016 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1017 const double black_point,const double white_point,ExceptionInfo *exception)
1019 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1020 #define ContrastStretchImageTag "ContrastStretch/Image"
1044 Allocate histogram and stretch map.
1046 assert(image != (Image *) NULL);
1047 assert(image->signature == MagickCoreSignature);
1048 if (image->debug != MagickFalse)
1049 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1050 if (SetImageGray(image,exception) != MagickFalse)
1051 (void) SetImageColorspace(image,GRAYColorspace,exception);
1052 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1053 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1054 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1055 sizeof(*histogram));
1056 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1057 GetPixelChannels(image)*sizeof(*stretch_map));
1058 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1059 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1061 if (stretch_map != (double *) NULL)
1062 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1063 if (histogram != (double *) NULL)
1064 histogram=(double *) RelinquishMagickMemory(histogram);
1065 if (white != (double *) NULL)
1066 white=(double *) RelinquishMagickMemory(white);
1067 if (black != (double *) NULL)
1068 black=(double *) RelinquishMagickMemory(black);
1069 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1076 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1077 sizeof(*histogram));
1078 image_view=AcquireVirtualCacheView(image,exception);
1079 for (y=0; y < (ssize_t) image->rows; y++)
1081 register const Quantum
1087 if (status == MagickFalse)
1089 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1090 if (p == (const Quantum *) NULL)
1095 for (x=0; x < (ssize_t) image->columns; x++)
1100 pixel=GetPixelIntensity(image,p);
1101 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1103 if (image->channel_mask != DefaultChannels)
1104 pixel=(double) p[i];
1105 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1106 ClampToQuantum(pixel))+i]++;
1108 p+=GetPixelChannels(image);
1111 image_view=DestroyCacheView(image_view);
1113 Find the histogram boundaries by locating the black/white levels.
1115 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1124 white[i]=MaxRange(QuantumRange);
1126 for (j=0; j <= (ssize_t) MaxMap; j++)
1128 intensity+=histogram[GetPixelChannels(image)*j+i];
1129 if (intensity > black_point)
1132 black[i]=(double) j;
1134 for (j=(ssize_t) MaxMap; j != 0; j--)
1136 intensity+=histogram[GetPixelChannels(image)*j+i];
1137 if (intensity > ((double) image->columns*image->rows-white_point))
1140 white[i]=(double) j;
1142 histogram=(double *) RelinquishMagickMemory(histogram);
1144 Stretch the histogram to create the stretched image mapping.
1146 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1147 sizeof(*stretch_map));
1148 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1153 for (j=0; j <= (ssize_t) MaxMap; j++)
1158 gamma=PerceptibleReciprocal(white[i]-black[i]);
1159 if (j < (ssize_t) black[i])
1160 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1162 if (j > (ssize_t) white[i])
1163 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1165 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1166 (double) (MaxMap*gamma*(j-black[i])));
1169 if (image->storage_class == PseudoClass)
1175 Stretch-contrast colormap.
1177 for (j=0; j < (ssize_t) image->colors; j++)
1179 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1181 i=GetPixelChannelOffset(image,RedPixelChannel);
1182 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1183 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1185 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1187 i=GetPixelChannelOffset(image,GreenPixelChannel);
1188 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1189 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1191 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1193 i=GetPixelChannelOffset(image,BluePixelChannel);
1194 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1195 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1197 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1199 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1200 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1201 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1206 Stretch-contrast image.
1210 image_view=AcquireAuthenticCacheView(image,exception);
1211 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1212 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1213 magick_threads(image,image,image->rows,1)
1215 for (y=0; y < (ssize_t) image->rows; y++)
1223 if (status == MagickFalse)
1225 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1226 if (q == (Quantum *) NULL)
1231 for (x=0; x < (ssize_t) image->columns; x++)
1236 if (GetPixelWriteMask(image,q) == 0)
1238 q+=GetPixelChannels(image);
1241 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1243 PixelChannel channel=GetPixelChannelChannel(image,j);
1244 PixelTrait traits=GetPixelChannelTraits(image,channel);
1245 if ((traits & UpdatePixelTrait) == 0)
1247 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1248 ScaleQuantumToMap(q[j])+j]);
1250 q+=GetPixelChannels(image);
1252 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1254 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1259 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1260 #pragma omp critical (MagickCore_ContrastStretchImage)
1262 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1264 if (proceed == MagickFalse)
1268 image_view=DestroyCacheView(image_view);
1269 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1270 white=(double *) RelinquishMagickMemory(white);
1271 black=(double *) RelinquishMagickMemory(black);
1276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1280 % E n h a n c e I m a g e %
1284 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1286 % EnhanceImage() applies a digital filter that improves the quality of a
1289 % The format of the EnhanceImage method is:
1291 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1293 % A description of each parameter follows:
1295 % o image: the image.
1297 % o exception: return any errors or warnings in this structure.
1300 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1302 #define EnhanceImageTag "Enhance/Image"
1303 #define EnhancePixel(weight) \
1304 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1305 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1306 distance_squared=(4.0+mean)*distance*distance; \
1307 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1308 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1309 distance_squared+=(7.0-mean)*distance*distance; \
1310 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1311 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1312 distance_squared+=(5.0-mean)*distance*distance; \
1313 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1314 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1315 distance_squared+=(5.0-mean)*distance*distance; \
1316 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1317 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1318 distance_squared+=(5.0-mean)*distance*distance; \
1319 if (distance_squared < 0.069) \
1321 aggregate.red+=(weight)*GetPixelRed(image,r); \
1322 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1323 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1324 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1325 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1326 total_weight+=(weight); \
1328 r+=GetPixelChannels(image);
1347 Initialize enhanced image attributes.
1349 assert(image != (const Image *) NULL);
1350 assert(image->signature == MagickCoreSignature);
1351 if (image->debug != MagickFalse)
1352 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1353 assert(exception != (ExceptionInfo *) NULL);
1354 assert(exception->signature == MagickCoreSignature);
1355 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1357 if (enhance_image == (Image *) NULL)
1358 return((Image *) NULL);
1359 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1361 enhance_image=DestroyImage(enhance_image);
1362 return((Image *) NULL);
1369 image_view=AcquireVirtualCacheView(image,exception);
1370 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1371 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1372 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1373 magick_threads(image,enhance_image,image->rows,1)
1375 for (y=0; y < (ssize_t) image->rows; y++)
1380 register const Quantum
1392 if (status == MagickFalse)
1394 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1395 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1397 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1402 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1403 GetPixelInfo(image,&pixel);
1404 for (x=0; x < (ssize_t) image->columns; x++)
1415 register const Quantum
1418 if (GetPixelWriteMask(image,p) == 0)
1420 SetPixelBackgoundColor(enhance_image,q);
1421 p+=GetPixelChannels(image);
1422 q+=GetPixelChannels(enhance_image);
1425 GetPixelInfo(image,&aggregate);
1427 GetPixelInfoPixel(image,p+center,&pixel);
1429 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1430 EnhancePixel(8.0); EnhancePixel(5.0);
1431 r=p+GetPixelChannels(image)*(image->columns+4);
1432 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1433 EnhancePixel(20.0); EnhancePixel(8.0);
1434 r=p+2*GetPixelChannels(image)*(image->columns+4);
1435 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1436 EnhancePixel(40.0); EnhancePixel(10.0);
1437 r=p+3*GetPixelChannels(image)*(image->columns+4);
1438 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1439 EnhancePixel(20.0); EnhancePixel(8.0);
1440 r=p+4*GetPixelChannels(image)*(image->columns+4);
1441 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1442 EnhancePixel(8.0); EnhancePixel(5.0);
1443 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1444 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1445 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1446 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1447 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1448 SetPixelViaPixelInfo(image,&pixel,q);
1449 p+=GetPixelChannels(image);
1450 q+=GetPixelChannels(enhance_image);
1452 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1454 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1459 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1460 #pragma omp critical (MagickCore_EnhanceImage)
1462 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1463 if (proceed == MagickFalse)
1467 enhance_view=DestroyCacheView(enhance_view);
1468 image_view=DestroyCacheView(image_view);
1469 if (status == MagickFalse)
1470 enhance_image=DestroyImage(enhance_image);
1471 return(enhance_image);
1475 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1479 % E q u a l i z e I m a g e %
1483 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1485 % EqualizeImage() applies a histogram equalization to the image.
1487 % The format of the EqualizeImage method is:
1489 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1491 % A description of each parameter follows:
1493 % o image: the image.
1495 % o exception: return any errors or warnings in this structure.
1498 MagickExport MagickBooleanType EqualizeImage(Image *image,
1499 ExceptionInfo *exception)
1501 #define EqualizeImageTag "Equalize/Image"
1507 black[CompositePixelChannel+1],
1511 white[CompositePixelChannel+1];
1526 Allocate and initialize histogram arrays.
1528 assert(image != (Image *) NULL);
1529 assert(image->signature == MagickCoreSignature);
1530 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1531 if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1534 if (image->debug != MagickFalse)
1535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1536 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1537 GetPixelChannels(image)*sizeof(*equalize_map));
1538 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1539 sizeof(*histogram));
1540 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1542 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1543 (map == (double *) NULL))
1545 if (map != (double *) NULL)
1546 map=(double *) RelinquishMagickMemory(map);
1547 if (histogram != (double *) NULL)
1548 histogram=(double *) RelinquishMagickMemory(histogram);
1549 if (equalize_map != (double *) NULL)
1550 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1551 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1558 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1559 sizeof(*histogram));
1560 image_view=AcquireVirtualCacheView(image,exception);
1561 for (y=0; y < (ssize_t) image->rows; y++)
1563 register const Quantum
1569 if (status == MagickFalse)
1571 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1572 if (p == (const Quantum *) NULL)
1577 for (x=0; x < (ssize_t) image->columns; x++)
1579 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1585 if ((image->channel_mask & SyncChannels) != 0)
1586 intensity=GetPixelIntensity(image,p);
1587 histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1589 p+=GetPixelChannels(image);
1592 image_view=DestroyCacheView(image_view);
1594 Integrate the histogram to get the equalization map.
1596 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1605 for (j=0; j <= (ssize_t) MaxMap; j++)
1607 intensity+=histogram[GetPixelChannels(image)*j+i];
1608 map[GetPixelChannels(image)*j+i]=intensity;
1611 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1612 sizeof(*equalize_map));
1613 (void) ResetMagickMemory(black,0,sizeof(*black));
1614 (void) ResetMagickMemory(white,0,sizeof(*white));
1615 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1621 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1622 if (black[i] != white[i])
1623 for (j=0; j <= (ssize_t) MaxMap; j++)
1624 equalize_map[GetPixelChannels(image)*j+i]=(double)
1625 ScaleMapToQuantum((double) ((MaxMap*(map[
1626 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1628 histogram=(double *) RelinquishMagickMemory(histogram);
1629 map=(double *) RelinquishMagickMemory(map);
1630 if (image->storage_class == PseudoClass)
1638 for (j=0; j < (ssize_t) image->colors; j++)
1640 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1642 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1643 if (black[channel] != white[channel])
1644 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1645 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1648 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1650 PixelChannel channel=GetPixelChannelChannel(image,
1652 if (black[channel] != white[channel])
1653 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1654 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1657 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1659 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1660 if (black[channel] != white[channel])
1661 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1662 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1665 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1667 PixelChannel channel=GetPixelChannelChannel(image,
1669 if (black[channel] != white[channel])
1670 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1671 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1680 image_view=AcquireAuthenticCacheView(image,exception);
1681 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1682 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1683 magick_threads(image,image,image->rows,1)
1685 for (y=0; y < (ssize_t) image->rows; y++)
1693 if (status == MagickFalse)
1695 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1696 if (q == (Quantum *) NULL)
1701 for (x=0; x < (ssize_t) image->columns; x++)
1706 if (GetPixelWriteMask(image,q) == 0)
1708 q+=GetPixelChannels(image);
1711 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1713 PixelChannel channel=GetPixelChannelChannel(image,j);
1714 PixelTrait traits=GetPixelChannelTraits(image,channel);
1715 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1717 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1718 ScaleQuantumToMap(q[j])+j]);
1720 q+=GetPixelChannels(image);
1722 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1724 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1729 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1730 #pragma omp critical (MagickCore_EqualizeImage)
1732 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1733 if (proceed == MagickFalse)
1737 image_view=DestroyCacheView(image_view);
1738 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1743 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1747 % G a m m a I m a g e %
1751 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1753 % GammaImage() gamma-corrects a particular image channel. The same
1754 % image viewed on different devices will have perceptual differences in the
1755 % way the image's intensities are represented on the screen. Specify
1756 % individual gamma levels for the red, green, and blue channels, or adjust
1757 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1759 % You can also reduce the influence of a particular channel with a gamma
1762 % The format of the GammaImage method is:
1764 % MagickBooleanType GammaImage(Image *image,const double gamma,
1765 % ExceptionInfo *exception)
1767 % A description of each parameter follows:
1769 % o image: the image.
1771 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1773 % o gamma: the image gamma.
1777 static inline double gamma_pow(const double value,const double gamma)
1779 return(value < 0.0 ? value : pow(value,gamma));
1782 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1783 ExceptionInfo *exception)
1785 #define GammaCorrectImageTag "GammaCorrect/Image"
1806 Allocate and initialize gamma maps.
1808 assert(image != (Image *) NULL);
1809 assert(image->signature == MagickCoreSignature);
1810 if (image->debug != MagickFalse)
1811 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1814 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1815 if (gamma_map == (Quantum *) NULL)
1816 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1818 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1820 for (i=0; i <= (ssize_t) MaxMap; i++)
1821 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1822 MaxMap,1.0/gamma)));
1823 if (image->storage_class == PseudoClass)
1824 for (i=0; i < (ssize_t) image->colors; i++)
1827 Gamma-correct colormap.
1829 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1830 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1831 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1832 ClampToQuantum(image->colormap[i].red))];
1833 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1834 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1835 ClampToQuantum(image->colormap[i].green))];
1836 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1837 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1838 ClampToQuantum(image->colormap[i].blue))];
1839 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1840 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1841 ClampToQuantum(image->colormap[i].alpha))];
1843 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1844 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1845 image->colormap[i].red,1.0/gamma);
1846 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1847 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1848 image->colormap[i].green,1.0/gamma);
1849 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1850 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1851 image->colormap[i].blue,1.0/gamma);
1852 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1853 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1854 image->colormap[i].alpha,1.0/gamma);
1858 Gamma-correct image.
1862 image_view=AcquireAuthenticCacheView(image,exception);
1863 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1864 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1865 magick_threads(image,image,image->rows,1)
1867 for (y=0; y < (ssize_t) image->rows; y++)
1875 if (status == MagickFalse)
1877 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1878 if (q == (Quantum *) NULL)
1883 for (x=0; x < (ssize_t) image->columns; x++)
1888 if (GetPixelWriteMask(image,q) == 0)
1890 q+=GetPixelChannels(image);
1893 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1895 PixelChannel channel=GetPixelChannelChannel(image,j);
1896 PixelTrait traits=GetPixelChannelTraits(image,channel);
1897 if ((traits & UpdatePixelTrait) == 0)
1899 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1900 q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1902 q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1905 q+=GetPixelChannels(image);
1907 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1909 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1914 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1915 #pragma omp critical (MagickCore_GammaImage)
1917 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1919 if (proceed == MagickFalse)
1923 image_view=DestroyCacheView(image_view);
1924 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1925 if (image->gamma != 0.0)
1926 image->gamma*=gamma;
1931 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935 % G r a y s c a l e I m a g e %
1939 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1941 % GrayscaleImage() converts the image to grayscale.
1943 % The format of the GrayscaleImage method is:
1945 % MagickBooleanType GrayscaleImage(Image *image,
1946 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1948 % A description of each parameter follows:
1950 % o image: the image.
1952 % o method: the pixel intensity method.
1954 % o exception: return any errors or warnings in this structure.
1957 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1958 const PixelIntensityMethod method,ExceptionInfo *exception)
1960 #define GrayscaleImageTag "Grayscale/Image"
1974 assert(image != (Image *) NULL);
1975 assert(image->signature == MagickCoreSignature);
1976 if (image->debug != MagickFalse)
1977 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1978 if (image->storage_class == PseudoClass)
1980 if (SyncImage(image,exception) == MagickFalse)
1981 return(MagickFalse);
1982 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1983 return(MagickFalse);
1985 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1986 if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1988 image->intensity=method;
1989 image->type=GrayscaleType;
1990 return(SetImageColorspace(image,GRAYColorspace,exception));
1998 image_view=AcquireAuthenticCacheView(image,exception);
1999 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2000 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2001 magick_threads(image,image,image->rows,1)
2003 for (y=0; y < (ssize_t) image->rows; y++)
2011 if (status == MagickFalse)
2013 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2014 if (q == (Quantum *) NULL)
2019 for (x=0; x < (ssize_t) image->columns; x++)
2027 if (GetPixelWriteMask(image,q) == 0)
2029 q+=GetPixelChannels(image);
2032 red=(MagickRealType) GetPixelRed(image,q);
2033 green=(MagickRealType) GetPixelGreen(image,q);
2034 blue=(MagickRealType) GetPixelBlue(image,q);
2038 case AveragePixelIntensityMethod:
2040 intensity=(red+green+blue)/3.0;
2043 case BrightnessPixelIntensityMethod:
2045 intensity=MagickMax(MagickMax(red,green),blue);
2048 case LightnessPixelIntensityMethod:
2050 intensity=(MagickMin(MagickMin(red,green),blue)+
2051 MagickMax(MagickMax(red,green),blue))/2.0;
2054 case MSPixelIntensityMethod:
2056 intensity=(MagickRealType) (((double) red*red+green*green+
2060 case Rec601LumaPixelIntensityMethod:
2062 if (image->colorspace == RGBColorspace)
2064 red=EncodePixelGamma(red);
2065 green=EncodePixelGamma(green);
2066 blue=EncodePixelGamma(blue);
2068 intensity=0.298839*red+0.586811*green+0.114350*blue;
2071 case Rec601LuminancePixelIntensityMethod:
2073 if (image->colorspace == sRGBColorspace)
2075 red=DecodePixelGamma(red);
2076 green=DecodePixelGamma(green);
2077 blue=DecodePixelGamma(blue);
2079 intensity=0.298839*red+0.586811*green+0.114350*blue;
2082 case Rec709LumaPixelIntensityMethod:
2085 if (image->colorspace == RGBColorspace)
2087 red=EncodePixelGamma(red);
2088 green=EncodePixelGamma(green);
2089 blue=EncodePixelGamma(blue);
2091 intensity=0.212656*red+0.715158*green+0.072186*blue;
2094 case Rec709LuminancePixelIntensityMethod:
2096 if (image->colorspace == sRGBColorspace)
2098 red=DecodePixelGamma(red);
2099 green=DecodePixelGamma(green);
2100 blue=DecodePixelGamma(blue);
2102 intensity=0.212656*red+0.715158*green+0.072186*blue;
2105 case RMSPixelIntensityMethod:
2107 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2108 blue*blue)/sqrt(3.0));
2112 SetPixelGray(image,ClampToQuantum(intensity),q);
2113 q+=GetPixelChannels(image);
2115 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2117 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2122 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2123 #pragma omp critical (MagickCore_GrayscaleImage)
2125 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2127 if (proceed == MagickFalse)
2131 image_view=DestroyCacheView(image_view);
2132 image->intensity=method;
2133 image->type=GrayscaleType;
2134 return(SetImageColorspace(image,GRAYColorspace,exception));
2138 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2142 % H a l d C l u t I m a g e %
2146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2148 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2149 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2150 % Create it with the HALD coder. You can apply any color transformation to
2151 % the Hald image and then use this method to apply the transform to the
2154 % The format of the HaldClutImage method is:
2156 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2157 % ExceptionInfo *exception)
2159 % A description of each parameter follows:
2161 % o image: the image, which is replaced by indexed CLUT values
2163 % o hald_image: the color lookup table image for replacement color values.
2165 % o exception: return any errors or warnings in this structure.
2168 MagickExport MagickBooleanType HaldClutImage(Image *image,
2169 const Image *hald_image,ExceptionInfo *exception)
2171 #define HaldClutImageTag "Clut/Image"
2173 typedef struct _HaldInfo
2205 assert(image != (Image *) NULL);
2206 assert(image->signature == MagickCoreSignature);
2207 if (image->debug != MagickFalse)
2208 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2209 assert(hald_image != (Image *) NULL);
2210 assert(hald_image->signature == MagickCoreSignature);
2211 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2212 return(MagickFalse);
2213 if (image->alpha_trait == UndefinedPixelTrait)
2214 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2220 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2221 (MagickRealType) hald_image->rows);
2222 for (level=2; (level*level*level) < length; level++) ;
2224 cube_size=level*level;
2225 width=(double) hald_image->columns;
2226 GetPixelInfo(hald_image,&zero);
2227 hald_view=AcquireVirtualCacheView(hald_image,exception);
2228 image_view=AcquireAuthenticCacheView(image,exception);
2229 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2230 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2231 magick_threads(image,image,image->rows,1)
2233 for (y=0; y < (ssize_t) image->rows; y++)
2241 if (status == MagickFalse)
2243 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2244 if (q == (Quantum *) NULL)
2249 for (x=0; x < (ssize_t) image->columns; x++)
2264 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2265 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2266 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2267 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2268 point.x-=floor(point.x);
2269 point.y-=floor(point.y);
2270 point.z-=floor(point.z);
2272 (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2273 fmod(offset,width),floor(offset/width),&pixel1,exception);
2275 (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2276 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2278 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2281 (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2282 fmod(offset,width),floor(offset/width),&pixel1,exception);
2283 (void) InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2284 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2286 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2289 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2291 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2292 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2293 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2294 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2295 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2296 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2297 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2298 (image->colorspace == CMYKColorspace))
2299 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2300 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2301 (image->alpha_trait != UndefinedPixelTrait))
2302 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2303 q+=GetPixelChannels(image);
2305 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2307 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2312 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2313 #pragma omp critical (MagickCore_HaldClutImage)
2315 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2316 if (proceed == MagickFalse)
2320 hald_view=DestroyCacheView(hald_view);
2321 image_view=DestroyCacheView(image_view);
2326 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2330 % L e v e l I m a g e %
2334 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2336 % LevelImage() adjusts the levels of a particular image channel by
2337 % scaling the colors falling between specified white and black points to
2338 % the full available quantum range.
2340 % The parameters provided represent the black, and white points. The black
2341 % point specifies the darkest color in the image. Colors darker than the
2342 % black point are set to zero. White point specifies the lightest color in
2343 % the image. Colors brighter than the white point are set to the maximum
2346 % If a '!' flag is given, map black and white colors to the given levels
2347 % rather than mapping those levels to black and white. See
2348 % LevelizeImage() below.
2350 % Gamma specifies a gamma correction to apply to the image.
2352 % The format of the LevelImage method is:
2354 % MagickBooleanType LevelImage(Image *image,const double black_point,
2355 % const double white_point,const double gamma,ExceptionInfo *exception)
2357 % A description of each parameter follows:
2359 % o image: the image.
2361 % o black_point: The level to map zero (black) to.
2363 % o white_point: The level to map QuantumRange (white) to.
2365 % o exception: return any errors or warnings in this structure.
2369 static inline double LevelPixel(const double black_point,
2370 const double white_point,const double gamma,const double pixel)
2376 if (fabs(white_point-black_point) < MagickEpsilon)
2378 scale=1.0/(white_point-black_point);
2379 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2381 return(level_pixel);
2384 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2385 const double white_point,const double gamma,ExceptionInfo *exception)
2387 #define LevelImageTag "Level/Image"
2405 Allocate and initialize levels map.
2407 assert(image != (Image *) NULL);
2408 assert(image->signature == MagickCoreSignature);
2409 if (image->debug != MagickFalse)
2410 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2411 if (image->storage_class == PseudoClass)
2412 for (i=0; i < (ssize_t) image->colors; i++)
2417 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2418 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2419 white_point,gamma,image->colormap[i].red));
2420 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2421 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2422 white_point,gamma,image->colormap[i].green));
2423 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2424 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2425 white_point,gamma,image->colormap[i].blue));
2426 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2427 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2428 white_point,gamma,image->colormap[i].alpha));
2435 image_view=AcquireAuthenticCacheView(image,exception);
2436 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2437 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2438 magick_threads(image,image,image->rows,1)
2440 for (y=0; y < (ssize_t) image->rows; y++)
2448 if (status == MagickFalse)
2450 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2451 if (q == (Quantum *) NULL)
2456 for (x=0; x < (ssize_t) image->columns; x++)
2461 if (GetPixelWriteMask(image,q) == 0)
2463 q+=GetPixelChannels(image);
2466 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2468 PixelChannel channel=GetPixelChannelChannel(image,j);
2469 PixelTrait traits=GetPixelChannelTraits(image,channel);
2470 if ((traits & UpdatePixelTrait) == 0)
2472 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2475 q+=GetPixelChannels(image);
2477 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2479 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2484 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2485 #pragma omp critical (MagickCore_LevelImage)
2487 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2488 if (proceed == MagickFalse)
2492 image_view=DestroyCacheView(image_view);
2493 (void) ClampImage(image,exception);
2498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2502 % L e v e l i z e I m a g e %
2506 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2508 % LevelizeImage() applies the reversed LevelImage() operation to just
2509 % the specific channels specified. It compresses the full range of color
2510 % values, so that they lie between the given black and white points. Gamma is
2511 % applied before the values are mapped.
2513 % LevelizeImage() can be called with by using a +level command line
2514 % API option, or using a '!' on a -level or LevelImage() geometry string.
2516 % It can be used to de-contrast a greyscale image to the exact levels
2517 % specified. Or by using specific levels for each channel of an image you
2518 % can convert a gray-scale image to any linear color gradient, according to
2521 % The format of the LevelizeImage method is:
2523 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2524 % const double white_point,const double gamma,ExceptionInfo *exception)
2526 % A description of each parameter follows:
2528 % o image: the image.
2530 % o black_point: The level to map zero (black) to.
2532 % o white_point: The level to map QuantumRange (white) to.
2534 % o gamma: adjust gamma by this factor before mapping values.
2536 % o exception: return any errors or warnings in this structure.
2539 MagickExport MagickBooleanType LevelizeImage(Image *image,
2540 const double black_point,const double white_point,const double gamma,
2541 ExceptionInfo *exception)
2543 #define LevelizeImageTag "Levelize/Image"
2544 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2545 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2563 Allocate and initialize levels map.
2565 assert(image != (Image *) NULL);
2566 assert(image->signature == MagickCoreSignature);
2567 if (image->debug != MagickFalse)
2568 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2569 if (image->storage_class == PseudoClass)
2570 for (i=0; i < (ssize_t) image->colors; i++)
2575 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2576 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2577 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2578 image->colormap[i].green=(double) LevelizeValue(
2579 image->colormap[i].green);
2580 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2581 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2582 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2583 image->colormap[i].alpha=(double) LevelizeValue(
2584 image->colormap[i].alpha);
2591 image_view=AcquireAuthenticCacheView(image,exception);
2592 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2593 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2594 magick_threads(image,image,image->rows,1)
2596 for (y=0; y < (ssize_t) image->rows; y++)
2604 if (status == MagickFalse)
2606 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2607 if (q == (Quantum *) NULL)
2612 for (x=0; x < (ssize_t) image->columns; x++)
2617 if (GetPixelWriteMask(image,q) == 0)
2619 q+=GetPixelChannels(image);
2622 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2624 PixelChannel channel=GetPixelChannelChannel(image,j);
2625 PixelTrait traits=GetPixelChannelTraits(image,channel);
2626 if ((traits & UpdatePixelTrait) == 0)
2628 q[j]=LevelizeValue(q[j]);
2630 q+=GetPixelChannels(image);
2632 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2634 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2639 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2640 #pragma omp critical (MagickCore_LevelizeImage)
2642 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2643 if (proceed == MagickFalse)
2647 image_view=DestroyCacheView(image_view);
2652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2656 % L e v e l I m a g e C o l o r s %
2660 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2662 % LevelImageColors() maps the given color to "black" and "white" values,
2663 % linearly spreading out the colors, and level values on a channel by channel
2664 % bases, as per LevelImage(). The given colors allows you to specify
2665 % different level ranges for each of the color channels separately.
2667 % If the boolean 'invert' is set true the image values will modifyed in the
2668 % reverse direction. That is any existing "black" and "white" colors in the
2669 % image will become the color values given, with all other values compressed
2670 % appropriatally. This effectivally maps a greyscale gradient into the given
2673 % The format of the LevelImageColors method is:
2675 % MagickBooleanType LevelImageColors(Image *image,
2676 % const PixelInfo *black_color,const PixelInfo *white_color,
2677 % const MagickBooleanType invert,ExceptionInfo *exception)
2679 % A description of each parameter follows:
2681 % o image: the image.
2683 % o black_color: The color to map black to/from
2685 % o white_point: The color to map white to/from
2687 % o invert: if true map the colors (levelize), rather than from (level)
2689 % o exception: return any errors or warnings in this structure.
2692 MagickExport MagickBooleanType LevelImageColors(Image *image,
2693 const PixelInfo *black_color,const PixelInfo *white_color,
2694 const MagickBooleanType invert,ExceptionInfo *exception)
2703 Allocate and initialize levels map.
2705 assert(image != (Image *) NULL);
2706 assert(image->signature == MagickCoreSignature);
2707 if (image->debug != MagickFalse)
2708 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2709 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2710 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2711 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2712 (void) SetImageColorspace(image,sRGBColorspace,exception);
2714 if (invert == MagickFalse)
2716 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2718 channel_mask=SetImageChannelMask(image,RedChannel);
2719 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2721 (void) SetImageChannelMask(image,channel_mask);
2723 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2725 channel_mask=SetImageChannelMask(image,GreenChannel);
2726 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2728 (void) SetImageChannelMask(image,channel_mask);
2730 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2732 channel_mask=SetImageChannelMask(image,BlueChannel);
2733 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2735 (void) SetImageChannelMask(image,channel_mask);
2737 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2738 (image->colorspace == CMYKColorspace))
2740 channel_mask=SetImageChannelMask(image,BlackChannel);
2741 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2743 (void) SetImageChannelMask(image,channel_mask);
2745 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2746 (image->alpha_trait != UndefinedPixelTrait))
2748 channel_mask=SetImageChannelMask(image,AlphaChannel);
2749 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2751 (void) SetImageChannelMask(image,channel_mask);
2756 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2758 channel_mask=SetImageChannelMask(image,RedChannel);
2759 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2761 (void) SetImageChannelMask(image,channel_mask);
2763 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2765 channel_mask=SetImageChannelMask(image,GreenChannel);
2766 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2768 (void) SetImageChannelMask(image,channel_mask);
2770 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2772 channel_mask=SetImageChannelMask(image,BlueChannel);
2773 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2775 (void) SetImageChannelMask(image,channel_mask);
2777 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2778 (image->colorspace == CMYKColorspace))
2780 channel_mask=SetImageChannelMask(image,BlackChannel);
2781 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2783 (void) SetImageChannelMask(image,channel_mask);
2785 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2786 (image->alpha_trait != UndefinedPixelTrait))
2788 channel_mask=SetImageChannelMask(image,AlphaChannel);
2789 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2791 (void) SetImageChannelMask(image,channel_mask);
2794 return(status != 0 ? MagickTrue : MagickFalse);
2798 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2802 % L i n e a r S t r e t c h I m a g e %
2806 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2808 % LinearStretchImage() discards any pixels below the black point and above
2809 % the white point and levels the remaining pixels.
2811 % The format of the LinearStretchImage method is:
2813 % MagickBooleanType LinearStretchImage(Image *image,
2814 % const double black_point,const double white_point,
2815 % ExceptionInfo *exception)
2817 % A description of each parameter follows:
2819 % o image: the image.
2821 % o black_point: the black point.
2823 % o white_point: the white point.
2825 % o exception: return any errors or warnings in this structure.
2828 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2829 const double black_point,const double white_point,ExceptionInfo *exception)
2831 #define LinearStretchImageTag "LinearStretch/Image"
2849 Allocate histogram and linear map.
2851 assert(image != (Image *) NULL);
2852 assert(image->signature == MagickCoreSignature);
2853 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2854 if (histogram == (double *) NULL)
2855 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2860 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2861 image_view=AcquireVirtualCacheView(image,exception);
2862 for (y=0; y < (ssize_t) image->rows; y++)
2864 register const Quantum
2870 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2871 if (p == (const Quantum *) NULL)
2873 for (x=0; x < (ssize_t) image->columns; x++)
2875 intensity=GetPixelIntensity(image,p);
2876 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2877 p+=GetPixelChannels(image);
2880 image_view=DestroyCacheView(image_view);
2882 Find the histogram boundaries by locating the black and white point levels.
2885 for (black=0; black < (ssize_t) MaxMap; black++)
2887 intensity+=histogram[black];
2888 if (intensity >= black_point)
2892 for (white=(ssize_t) MaxMap; white != 0; white--)
2894 intensity+=histogram[white];
2895 if (intensity >= white_point)
2898 histogram=(double *) RelinquishMagickMemory(histogram);
2899 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2900 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2906 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2910 % M o d u l a t e I m a g e %
2914 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2916 % ModulateImage() lets you control the brightness, saturation, and hue
2917 % of an image. Modulate represents the brightness, saturation, and hue
2918 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2919 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2920 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2922 % The format of the ModulateImage method is:
2924 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2925 % ExceptionInfo *exception)
2927 % A description of each parameter follows:
2929 % o image: the image.
2931 % o modulate: Define the percent change in brightness, saturation, and hue.
2933 % o exception: return any errors or warnings in this structure.
2937 static inline void ModulateHCL(const double percent_hue,
2938 const double percent_chroma,const double percent_luma,double *red,
2939 double *green,double *blue)
2947 Increase or decrease color luma, chroma, or hue.
2949 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2950 hue+=0.5*(0.01*percent_hue-1.0);
2955 chroma*=0.01*percent_chroma;
2956 luma*=0.01*percent_luma;
2957 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2960 static inline void ModulateHCLp(const double percent_hue,
2961 const double percent_chroma,const double percent_luma,double *red,
2962 double *green,double *blue)
2970 Increase or decrease color luma, chroma, or hue.
2972 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2973 hue+=0.5*(0.01*percent_hue-1.0);
2978 chroma*=0.01*percent_chroma;
2979 luma*=0.01*percent_luma;
2980 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2983 static inline void ModulateHSB(const double percent_hue,
2984 const double percent_saturation,const double percent_brightness,double *red,
2985 double *green,double *blue)
2993 Increase or decrease color brightness, saturation, or hue.
2995 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2996 hue+=0.5*(0.01*percent_hue-1.0);
3001 saturation*=0.01*percent_saturation;
3002 brightness*=0.01*percent_brightness;
3003 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3006 static inline void ModulateHSI(const double percent_hue,
3007 const double percent_saturation,const double percent_intensity,double *red,
3008 double *green,double *blue)
3016 Increase or decrease color intensity, saturation, or hue.
3018 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3019 hue+=0.5*(0.01*percent_hue-1.0);
3024 saturation*=0.01*percent_saturation;
3025 intensity*=0.01*percent_intensity;
3026 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3029 static inline void ModulateHSL(const double percent_hue,
3030 const double percent_saturation,const double percent_lightness,double *red,
3031 double *green,double *blue)
3039 Increase or decrease color lightness, saturation, or hue.
3041 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3042 hue+=0.5*(0.01*percent_hue-1.0);
3047 saturation*=0.01*percent_saturation;
3048 lightness*=0.01*percent_lightness;
3049 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3052 static inline void ModulateHSV(const double percent_hue,
3053 const double percent_saturation,const double percent_value,double *red,
3054 double *green,double *blue)
3062 Increase or decrease color value, saturation, or hue.
3064 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3065 hue+=0.5*(0.01*percent_hue-1.0);
3070 saturation*=0.01*percent_saturation;
3071 value*=0.01*percent_value;
3072 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3075 static inline void ModulateHWB(const double percent_hue,
3076 const double percent_whiteness,const double percent_blackness,double *red,
3077 double *green,double *blue)
3085 Increase or decrease color blackness, whiteness, or hue.
3087 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3088 hue+=0.5*(0.01*percent_hue-1.0);
3093 blackness*=0.01*percent_blackness;
3094 whiteness*=0.01*percent_whiteness;
3095 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3098 static inline void ModulateLCHab(const double percent_luma,
3099 const double percent_chroma,const double percent_hue,double *red,
3100 double *green,double *blue)
3108 Increase or decrease color luma, chroma, or hue.
3110 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3111 luma*=0.01*percent_luma;
3112 chroma*=0.01*percent_chroma;
3113 hue+=0.5*(0.01*percent_hue-1.0);
3118 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3121 static inline void ModulateLCHuv(const double percent_luma,
3122 const double percent_chroma,const double percent_hue,double *red,
3123 double *green,double *blue)
3131 Increase or decrease color luma, chroma, or hue.
3133 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3134 luma*=0.01*percent_luma;
3135 chroma*=0.01*percent_chroma;
3136 hue+=0.5*(0.01*percent_hue-1.0);
3141 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3144 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3145 ExceptionInfo *exception)
3147 #define ModulateImageTag "Modulate/Image"
3182 Initialize modulate table.
3184 assert(image != (Image *) NULL);
3185 assert(image->signature == MagickCoreSignature);
3186 if (image->debug != MagickFalse)
3187 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3188 if (modulate == (char *) NULL)
3189 return(MagickFalse);
3190 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3191 (void) SetImageColorspace(image,sRGBColorspace,exception);
3192 flags=ParseGeometry(modulate,&geometry_info);
3193 percent_brightness=geometry_info.rho;
3194 percent_saturation=geometry_info.sigma;
3195 if ((flags & SigmaValue) == 0)
3196 percent_saturation=100.0;
3197 percent_hue=geometry_info.xi;
3198 if ((flags & XiValue) == 0)
3200 colorspace=UndefinedColorspace;
3201 artifact=GetImageArtifact(image,"modulate:colorspace");
3202 if (artifact != (const char *) NULL)
3203 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3204 MagickFalse,artifact);
3205 if (image->storage_class == PseudoClass)
3206 for (i=0; i < (ssize_t) image->colors; i++)
3214 Modulate image colormap.
3216 red=(double) image->colormap[i].red;
3217 green=(double) image->colormap[i].green;
3218 blue=(double) image->colormap[i].blue;
3223 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3227 case HCLpColorspace:
3229 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3235 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3241 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3248 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3254 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3260 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3265 case LCHabColorspace:
3267 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3271 case LCHuvColorspace:
3273 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3278 image->colormap[i].red=red;
3279 image->colormap[i].green=green;
3280 image->colormap[i].blue=blue;
3285 #if defined(MAGICKCORE_OPENCL_SUPPORT)
3286 if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3287 percent_saturation,colorspace,exception) != MagickFalse)
3292 image_view=AcquireAuthenticCacheView(image,exception);
3293 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3294 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3295 magick_threads(image,image,image->rows,1)
3297 for (y=0; y < (ssize_t) image->rows; y++)
3305 if (status == MagickFalse)
3307 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3308 if (q == (Quantum *) NULL)
3313 for (x=0; x < (ssize_t) image->columns; x++)
3320 if (GetPixelWriteMask(image,q) == 0)
3322 q+=GetPixelChannels(image);
3325 red=(double) GetPixelRed(image,q);
3326 green=(double) GetPixelGreen(image,q);
3327 blue=(double) GetPixelBlue(image,q);
3332 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3336 case HCLpColorspace:
3338 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3344 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3351 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3357 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3363 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3367 case LCHabColorspace:
3369 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3374 case LCHuvColorspace:
3376 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3381 SetPixelRed(image,ClampToQuantum(red),q);
3382 SetPixelGreen(image,ClampToQuantum(green),q);
3383 SetPixelBlue(image,ClampToQuantum(blue),q);
3384 q+=GetPixelChannels(image);
3386 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3388 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3393 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3394 #pragma omp critical (MagickCore_ModulateImage)
3396 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3397 if (proceed == MagickFalse)
3401 image_view=DestroyCacheView(image_view);
3406 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3410 % N e g a t e I m a g e %
3414 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3416 % NegateImage() negates the colors in the reference image. The grayscale
3417 % option means that only grayscale values within the image are negated.
3419 % The format of the NegateImage method is:
3421 % MagickBooleanType NegateImage(Image *image,
3422 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3424 % A description of each parameter follows:
3426 % o image: the image.
3428 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3430 % o exception: return any errors or warnings in this structure.
3433 MagickExport MagickBooleanType NegateImage(Image *image,
3434 const MagickBooleanType grayscale,ExceptionInfo *exception)
3436 #define NegateImageTag "Negate/Image"
3453 assert(image != (Image *) NULL);
3454 assert(image->signature == MagickCoreSignature);
3455 if (image->debug != MagickFalse)
3456 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3457 if (image->storage_class == PseudoClass)
3458 for (i=0; i < (ssize_t) image->colors; i++)
3463 if( grayscale != MagickFalse )
3464 if ((image->colormap[i].red != image->colormap[i].green) ||
3465 (image->colormap[i].green != image->colormap[i].blue))
3467 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3468 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3469 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3470 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3471 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3472 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3479 image_view=AcquireAuthenticCacheView(image,exception);
3480 if( grayscale != MagickFalse )
3482 for (y=0; y < (ssize_t) image->rows; y++)
3493 if (status == MagickFalse)
3495 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3497 if (q == (Quantum *) NULL)
3502 for (x=0; x < (ssize_t) image->columns; x++)
3507 if ((GetPixelWriteMask(image,q) == 0) ||
3508 IsPixelGray(image,q) != MagickFalse)
3510 q+=GetPixelChannels(image);
3513 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3515 PixelChannel channel=GetPixelChannelChannel(image,j);
3516 PixelTrait traits=GetPixelChannelTraits(image,channel);
3517 if ((traits & UpdatePixelTrait) == 0)
3519 q[j]=QuantumRange-q[j];
3521 q+=GetPixelChannels(image);
3523 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3524 if (sync == MagickFalse)
3526 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3531 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3532 #pragma omp critical (MagickCore_NegateImage)
3534 proceed=SetImageProgress(image,NegateImageTag,progress++,
3536 if (proceed == MagickFalse)
3540 image_view=DestroyCacheView(image_view);
3546 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3547 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3548 magick_threads(image,image,image->rows,1)
3550 for (y=0; y < (ssize_t) image->rows; y++)
3558 if (status == MagickFalse)
3560 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3561 if (q == (Quantum *) NULL)
3566 for (x=0; x < (ssize_t) image->columns; x++)
3571 if (GetPixelWriteMask(image,q) == 0)
3573 q+=GetPixelChannels(image);
3576 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3578 PixelChannel channel=GetPixelChannelChannel(image,j);
3579 PixelTrait traits=GetPixelChannelTraits(image,channel);
3580 if ((traits & UpdatePixelTrait) == 0)
3582 q[j]=QuantumRange-q[j];
3584 q+=GetPixelChannels(image);
3586 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3588 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3593 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3594 #pragma omp critical (MagickCore_NegateImage)
3596 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3597 if (proceed == MagickFalse)
3601 image_view=DestroyCacheView(image_view);
3606 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3610 % N o r m a l i z e I m a g e %
3614 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3616 % The NormalizeImage() method enhances the contrast of a color image by
3617 % mapping the darkest 2 percent of all pixel to black and the brightest
3618 % 1 percent to white.
3620 % The format of the NormalizeImage method is:
3622 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3624 % A description of each parameter follows:
3626 % o image: the image.
3628 % o exception: return any errors or warnings in this structure.
3631 MagickExport MagickBooleanType NormalizeImage(Image *image,
3632 ExceptionInfo *exception)
3638 black_point=(double) image->columns*image->rows*0.0015;
3639 white_point=(double) image->columns*image->rows*0.9995;
3640 return(ContrastStretchImage(image,black_point,white_point,exception));
3644 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3648 % S i g m o i d a l C o n t r a s t I m a g e %
3652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3654 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3655 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3656 % sigmoidal transfer function without saturating highlights or shadows.
3657 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3658 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3659 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3660 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3663 % The format of the SigmoidalContrastImage method is:
3665 % MagickBooleanType SigmoidalContrastImage(Image *image,
3666 % const MagickBooleanType sharpen,const char *levels,
3667 % ExceptionInfo *exception)
3669 % A description of each parameter follows:
3671 % o image: the image.
3673 % o sharpen: Increase or decrease image contrast.
3675 % o contrast: strength of the contrast, the larger the number the more
3676 % 'threshold-like' it becomes.
3678 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3680 % o exception: return any errors or warnings in this structure.
3685 ImageMagick 6 has a version of this function which uses LUTs.
3689 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3692 The first version, based on the hyperbolic tangent tanh, when combined with
3693 the scaling step, is an exact arithmetic clone of the the sigmoid function
3694 based on the logistic curve. The equivalence is based on the identity
3696 1/(1+exp(-t)) = (1+tanh(t/2))/2
3698 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3699 scaled sigmoidal derivation is invariant under affine transformations of
3702 The tanh version is almost certainly more accurate and cheaper. The 0.5
3703 factor in the argument is to clone the legacy ImageMagick behavior. The
3704 reason for making the define depend on atanh even though it only uses tanh
3705 has to do with the construction of the inverse of the scaled sigmoidal.
3707 #if defined(MAGICKCORE_HAVE_ATANH)
3708 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3710 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3713 Scaled sigmoidal function:
3715 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3716 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3718 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3719 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3720 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3721 zero. This is fixed below by exiting immediately when contrast is small,
3722 leaving the image (or colormap) unmodified. This appears to be safe because
3723 the series expansion of the logistic sigmoidal function around x=b is
3727 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3729 #define ScaledSigmoidal(a,b,x) ( \
3730 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3731 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3733 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3734 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3735 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3736 when creating a LUT from in gamut values, hence the branching. In
3737 addition, HDRI may have out of gamut values.
3738 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3739 It is only a right inverse. This is unavoidable.
3741 static inline double InverseScaledSigmoidal(const double a,const double b,
3744 const double sig0=Sigmoidal(a,b,0.0);
3745 const double sig1=Sigmoidal(a,b,1.0);
3746 const double argument=(sig1-sig0)*x+sig0;
3747 const double clamped=
3749 #if defined(MAGICKCORE_HAVE_ATANH)
3750 argument < -1+MagickEpsilon
3754 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3756 return(b+(2.0/a)*atanh(clamped));
3758 argument < MagickEpsilon
3762 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3764 return(b-log(1.0/clamped-1.0)/a);
3768 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3769 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3770 ExceptionInfo *exception)
3772 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3773 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3774 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3775 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3776 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3793 assert(image != (Image *) NULL);
3794 assert(image->signature == MagickCoreSignature);
3795 if (image->debug != MagickFalse)
3796 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3798 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3799 case nothing is done.
3801 if (contrast < MagickEpsilon)
3804 Sigmoidal-contrast enhance colormap.
3806 if (image->storage_class == PseudoClass)
3811 if( sharpen != MagickFalse )
3812 for (i=0; i < (ssize_t) image->colors; i++)
3814 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3815 image->colormap[i].red=(MagickRealType) ScaledSig(
3816 image->colormap[i].red);
3817 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3818 image->colormap[i].green=(MagickRealType) ScaledSig(
3819 image->colormap[i].green);
3820 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3821 image->colormap[i].blue=(MagickRealType) ScaledSig(
3822 image->colormap[i].blue);
3823 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3824 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3825 image->colormap[i].alpha);
3828 for (i=0; i < (ssize_t) image->colors; i++)
3830 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3831 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3832 image->colormap[i].red);
3833 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3834 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3835 image->colormap[i].green);
3836 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3837 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3838 image->colormap[i].blue);
3839 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3840 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3841 image->colormap[i].alpha);
3845 Sigmoidal-contrast enhance image.
3849 image_view=AcquireAuthenticCacheView(image,exception);
3850 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3851 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3852 magick_threads(image,image,image->rows,1)
3854 for (y=0; y < (ssize_t) image->rows; y++)
3862 if (status == MagickFalse)
3864 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3865 if (q == (Quantum *) NULL)
3870 for (x=0; x < (ssize_t) image->columns; x++)
3875 if (GetPixelWriteMask(image,q) == 0)
3877 q+=GetPixelChannels(image);
3880 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3882 PixelChannel channel=GetPixelChannelChannel(image,i);
3883 PixelTrait traits=GetPixelChannelTraits(image,channel);
3884 if ((traits & UpdatePixelTrait) == 0)
3886 if( sharpen != MagickFalse )
3887 q[i]=ScaledSig(q[i]);
3889 q[i]=InverseScaledSig(q[i]);
3891 q+=GetPixelChannels(image);
3893 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3895 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3900 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3901 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3903 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3905 if (proceed == MagickFalse)
3909 image_view=DestroyCacheView(image_view);