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 % https://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/accelerate-private.h"
45 #include "MagickCore/artifact.h"
46 #include "MagickCore/attribute.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/cache-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+=fmod((percent_hue-100.0),200.0)/200.0;
2951 chroma*=0.01*percent_chroma;
2952 luma*=0.01*percent_luma;
2953 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2956 static inline void ModulateHCLp(const double percent_hue,
2957 const double percent_chroma,const double percent_luma,double *red,
2958 double *green,double *blue)
2966 Increase or decrease color luma, chroma, or hue.
2968 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2969 hue+=fmod((percent_hue-100.0),200.0)/200.0;
2970 chroma*=0.01*percent_chroma;
2971 luma*=0.01*percent_luma;
2972 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2975 static inline void ModulateHSB(const double percent_hue,
2976 const double percent_saturation,const double percent_brightness,double *red,
2977 double *green,double *blue)
2985 Increase or decrease color brightness, saturation, or hue.
2987 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2988 hue+=fmod((percent_hue-100.0),200.0)/200.0;
2989 saturation*=0.01*percent_saturation;
2990 brightness*=0.01*percent_brightness;
2991 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2994 static inline void ModulateHSI(const double percent_hue,
2995 const double percent_saturation,const double percent_intensity,double *red,
2996 double *green,double *blue)
3004 Increase or decrease color intensity, saturation, or hue.
3006 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3007 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3008 saturation*=0.01*percent_saturation;
3009 intensity*=0.01*percent_intensity;
3010 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3013 static inline void ModulateHSL(const double percent_hue,
3014 const double percent_saturation,const double percent_lightness,double *red,
3015 double *green,double *blue)
3023 Increase or decrease color lightness, saturation, or hue.
3025 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3026 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3027 saturation*=0.01*percent_saturation;
3028 lightness*=0.01*percent_lightness;
3029 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3032 static inline void ModulateHSV(const double percent_hue,
3033 const double percent_saturation,const double percent_value,double *red,
3034 double *green,double *blue)
3042 Increase or decrease color value, saturation, or hue.
3044 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3045 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3046 saturation*=0.01*percent_saturation;
3047 value*=0.01*percent_value;
3048 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3051 static inline void ModulateHWB(const double percent_hue,
3052 const double percent_whiteness,const double percent_blackness,double *red,
3053 double *green,double *blue)
3061 Increase or decrease color blackness, whiteness, or hue.
3063 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3064 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3065 blackness*=0.01*percent_blackness;
3066 whiteness*=0.01*percent_whiteness;
3067 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3070 static inline void ModulateLCHab(const double percent_luma,
3071 const double percent_chroma,const double percent_hue,double *red,
3072 double *green,double *blue)
3080 Increase or decrease color luma, chroma, or hue.
3082 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3083 luma*=0.01*percent_luma;
3084 chroma*=0.01*percent_chroma;
3085 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3086 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3089 static inline void ModulateLCHuv(const double percent_luma,
3090 const double percent_chroma,const double percent_hue,double *red,
3091 double *green,double *blue)
3099 Increase or decrease color luma, chroma, or hue.
3101 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3102 luma*=0.01*percent_luma;
3103 chroma*=0.01*percent_chroma;
3104 hue+=fmod((percent_hue-100.0),200.0)/200.0;
3105 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3108 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3109 ExceptionInfo *exception)
3111 #define ModulateImageTag "Modulate/Image"
3146 Initialize modulate table.
3148 assert(image != (Image *) NULL);
3149 assert(image->signature == MagickCoreSignature);
3150 if (image->debug != MagickFalse)
3151 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3152 if (modulate == (char *) NULL)
3153 return(MagickFalse);
3154 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3155 (void) SetImageColorspace(image,sRGBColorspace,exception);
3156 flags=ParseGeometry(modulate,&geometry_info);
3157 percent_brightness=geometry_info.rho;
3158 percent_saturation=geometry_info.sigma;
3159 if ((flags & SigmaValue) == 0)
3160 percent_saturation=100.0;
3161 percent_hue=geometry_info.xi;
3162 if ((flags & XiValue) == 0)
3164 colorspace=UndefinedColorspace;
3165 artifact=GetImageArtifact(image,"modulate:colorspace");
3166 if (artifact != (const char *) NULL)
3167 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3168 MagickFalse,artifact);
3169 if (image->storage_class == PseudoClass)
3170 for (i=0; i < (ssize_t) image->colors; i++)
3178 Modulate image colormap.
3180 red=(double) image->colormap[i].red;
3181 green=(double) image->colormap[i].green;
3182 blue=(double) image->colormap[i].blue;
3187 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3191 case HCLpColorspace:
3193 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3199 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3205 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3212 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3218 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3224 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3229 case LCHabColorspace:
3231 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3235 case LCHuvColorspace:
3237 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3242 image->colormap[i].red=red;
3243 image->colormap[i].green=green;
3244 image->colormap[i].blue=blue;
3249 #if defined(MAGICKCORE_OPENCL_SUPPORT)
3250 if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3251 percent_saturation,colorspace,exception) != MagickFalse)
3256 image_view=AcquireAuthenticCacheView(image,exception);
3257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3258 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3259 magick_threads(image,image,image->rows,1)
3261 for (y=0; y < (ssize_t) image->rows; y++)
3269 if (status == MagickFalse)
3271 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3272 if (q == (Quantum *) NULL)
3277 for (x=0; x < (ssize_t) image->columns; x++)
3284 if (GetPixelWriteMask(image,q) == 0)
3286 q+=GetPixelChannels(image);
3289 red=(double) GetPixelRed(image,q);
3290 green=(double) GetPixelGreen(image,q);
3291 blue=(double) GetPixelBlue(image,q);
3296 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3300 case HCLpColorspace:
3302 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3308 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3315 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3321 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3327 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3331 case LCHabColorspace:
3333 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3338 case LCHuvColorspace:
3340 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3345 SetPixelRed(image,ClampToQuantum(red),q);
3346 SetPixelGreen(image,ClampToQuantum(green),q);
3347 SetPixelBlue(image,ClampToQuantum(blue),q);
3348 q+=GetPixelChannels(image);
3350 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3352 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3357 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3358 #pragma omp critical (MagickCore_ModulateImage)
3360 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3361 if (proceed == MagickFalse)
3365 image_view=DestroyCacheView(image_view);
3370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3374 % N e g a t e I m a g e %
3378 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3380 % NegateImage() negates the colors in the reference image. The grayscale
3381 % option means that only grayscale values within the image are negated.
3383 % The format of the NegateImage method is:
3385 % MagickBooleanType NegateImage(Image *image,
3386 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3388 % A description of each parameter follows:
3390 % o image: the image.
3392 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3394 % o exception: return any errors or warnings in this structure.
3397 MagickExport MagickBooleanType NegateImage(Image *image,
3398 const MagickBooleanType grayscale,ExceptionInfo *exception)
3400 #define NegateImageTag "Negate/Image"
3417 assert(image != (Image *) NULL);
3418 assert(image->signature == MagickCoreSignature);
3419 if (image->debug != MagickFalse)
3420 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3421 if (image->storage_class == PseudoClass)
3422 for (i=0; i < (ssize_t) image->colors; i++)
3427 if( grayscale != MagickFalse )
3428 if ((image->colormap[i].red != image->colormap[i].green) ||
3429 (image->colormap[i].green != image->colormap[i].blue))
3431 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3432 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3433 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3434 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3435 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3436 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3443 image_view=AcquireAuthenticCacheView(image,exception);
3444 if( grayscale != MagickFalse )
3446 for (y=0; y < (ssize_t) image->rows; y++)
3457 if (status == MagickFalse)
3459 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3461 if (q == (Quantum *) NULL)
3466 for (x=0; x < (ssize_t) image->columns; x++)
3471 if ((GetPixelWriteMask(image,q) == 0) ||
3472 IsPixelGray(image,q) != MagickFalse)
3474 q+=GetPixelChannels(image);
3477 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3479 PixelChannel channel=GetPixelChannelChannel(image,j);
3480 PixelTrait traits=GetPixelChannelTraits(image,channel);
3481 if ((traits & UpdatePixelTrait) == 0)
3483 q[j]=QuantumRange-q[j];
3485 q+=GetPixelChannels(image);
3487 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3488 if (sync == MagickFalse)
3490 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3495 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3496 #pragma omp critical (MagickCore_NegateImage)
3498 proceed=SetImageProgress(image,NegateImageTag,progress++,
3500 if (proceed == MagickFalse)
3504 image_view=DestroyCacheView(image_view);
3510 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3511 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3512 magick_threads(image,image,image->rows,1)
3514 for (y=0; y < (ssize_t) image->rows; y++)
3522 if (status == MagickFalse)
3524 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3525 if (q == (Quantum *) NULL)
3530 for (x=0; x < (ssize_t) image->columns; x++)
3535 if (GetPixelWriteMask(image,q) == 0)
3537 q+=GetPixelChannels(image);
3540 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3542 PixelChannel channel=GetPixelChannelChannel(image,j);
3543 PixelTrait traits=GetPixelChannelTraits(image,channel);
3544 if ((traits & UpdatePixelTrait) == 0)
3546 q[j]=QuantumRange-q[j];
3548 q+=GetPixelChannels(image);
3550 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3552 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3557 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3558 #pragma omp critical (MagickCore_NegateImage)
3560 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3561 if (proceed == MagickFalse)
3565 image_view=DestroyCacheView(image_view);
3570 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3574 % N o r m a l i z e I m a g e %
3578 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3580 % The NormalizeImage() method enhances the contrast of a color image by
3581 % mapping the darkest 2 percent of all pixel to black and the brightest
3582 % 1 percent to white.
3584 % The format of the NormalizeImage method is:
3586 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3588 % A description of each parameter follows:
3590 % o image: the image.
3592 % o exception: return any errors or warnings in this structure.
3595 MagickExport MagickBooleanType NormalizeImage(Image *image,
3596 ExceptionInfo *exception)
3602 black_point=(double) image->columns*image->rows*0.0015;
3603 white_point=(double) image->columns*image->rows*0.9995;
3604 return(ContrastStretchImage(image,black_point,white_point,exception));
3608 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3612 % S i g m o i d a l C o n t r a s t I m a g e %
3616 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3618 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3619 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3620 % sigmoidal transfer function without saturating highlights or shadows.
3621 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3622 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3623 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3624 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3627 % The format of the SigmoidalContrastImage method is:
3629 % MagickBooleanType SigmoidalContrastImage(Image *image,
3630 % const MagickBooleanType sharpen,const char *levels,
3631 % ExceptionInfo *exception)
3633 % A description of each parameter follows:
3635 % o image: the image.
3637 % o sharpen: Increase or decrease image contrast.
3639 % o contrast: strength of the contrast, the larger the number the more
3640 % 'threshold-like' it becomes.
3642 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3644 % o exception: return any errors or warnings in this structure.
3649 ImageMagick 6 has a version of this function which uses LUTs.
3653 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3656 The first version, based on the hyperbolic tangent tanh, when combined with
3657 the scaling step, is an exact arithmetic clone of the the sigmoid function
3658 based on the logistic curve. The equivalence is based on the identity
3660 1/(1+exp(-t)) = (1+tanh(t/2))/2
3662 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3663 scaled sigmoidal derivation is invariant under affine transformations of
3666 The tanh version is almost certainly more accurate and cheaper. The 0.5
3667 factor in the argument is to clone the legacy ImageMagick behavior. The
3668 reason for making the define depend on atanh even though it only uses tanh
3669 has to do with the construction of the inverse of the scaled sigmoidal.
3671 #if defined(MAGICKCORE_HAVE_ATANH)
3672 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3674 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3677 Scaled sigmoidal function:
3679 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3680 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3682 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3683 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3684 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3685 zero. This is fixed below by exiting immediately when contrast is small,
3686 leaving the image (or colormap) unmodified. This appears to be safe because
3687 the series expansion of the logistic sigmoidal function around x=b is
3691 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3693 #define ScaledSigmoidal(a,b,x) ( \
3694 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3695 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3697 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3698 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3699 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3700 when creating a LUT from in gamut values, hence the branching. In
3701 addition, HDRI may have out of gamut values.
3702 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3703 It is only a right inverse. This is unavoidable.
3705 static inline double InverseScaledSigmoidal(const double a,const double b,
3708 const double sig0=Sigmoidal(a,b,0.0);
3709 const double sig1=Sigmoidal(a,b,1.0);
3710 const double argument=(sig1-sig0)*x+sig0;
3711 const double clamped=
3713 #if defined(MAGICKCORE_HAVE_ATANH)
3714 argument < -1+MagickEpsilon
3718 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3720 return(b+(2.0/a)*atanh(clamped));
3722 argument < MagickEpsilon
3726 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3728 return(b-log(1.0/clamped-1.0)/a);
3732 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3733 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3734 ExceptionInfo *exception)
3736 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3737 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3738 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3739 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3740 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3757 assert(image != (Image *) NULL);
3758 assert(image->signature == MagickCoreSignature);
3759 if (image->debug != MagickFalse)
3760 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3762 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3763 case nothing is done.
3765 if (contrast < MagickEpsilon)
3768 Sigmoidal-contrast enhance colormap.
3770 if (image->storage_class == PseudoClass)
3775 if( sharpen != MagickFalse )
3776 for (i=0; i < (ssize_t) image->colors; i++)
3778 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3779 image->colormap[i].red=(MagickRealType) ScaledSig(
3780 image->colormap[i].red);
3781 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3782 image->colormap[i].green=(MagickRealType) ScaledSig(
3783 image->colormap[i].green);
3784 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3785 image->colormap[i].blue=(MagickRealType) ScaledSig(
3786 image->colormap[i].blue);
3787 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3788 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3789 image->colormap[i].alpha);
3792 for (i=0; i < (ssize_t) image->colors; i++)
3794 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3795 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3796 image->colormap[i].red);
3797 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3798 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3799 image->colormap[i].green);
3800 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3801 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3802 image->colormap[i].blue);
3803 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3804 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3805 image->colormap[i].alpha);
3809 Sigmoidal-contrast enhance image.
3813 image_view=AcquireAuthenticCacheView(image,exception);
3814 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3815 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3816 magick_threads(image,image,image->rows,1)
3818 for (y=0; y < (ssize_t) image->rows; y++)
3826 if (status == MagickFalse)
3828 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3829 if (q == (Quantum *) NULL)
3834 for (x=0; x < (ssize_t) image->columns; x++)
3839 if (GetPixelWriteMask(image,q) == 0)
3841 q+=GetPixelChannels(image);
3844 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3846 PixelChannel channel=GetPixelChannelChannel(image,i);
3847 PixelTrait traits=GetPixelChannelTraits(image,channel);
3848 if ((traits & UpdatePixelTrait) == 0)
3850 if( sharpen != MagickFalse )
3851 q[i]=ScaledSig(q[i]);
3853 q[i]=InverseScaledSig(q[i]);
3855 q+=GetPixelChannels(image);
3857 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3859 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3864 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3865 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3867 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3869 if (proceed == MagickFalse)
3873 image_view=DestroyCacheView(image_view);