2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % EEEEE N N H H AAA N N CCCC EEEEE %
7 % E NN N H H A A NN N C E %
8 % EEE N N N HHHHH AAAAA N N N C EEE %
9 % E N NN H H A A N NN C E %
10 % EEEEE N N H H A A N N CCCC EEEEE %
13 % MagickCore Image Enhancement Methods %
20 % Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/accelerate.h"
45 #include "MagickCore/artifact.h"
46 #include "MagickCore/attribute.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/cache-view.h"
49 #include "MagickCore/channel.h"
50 #include "MagickCore/color.h"
51 #include "MagickCore/color-private.h"
52 #include "MagickCore/colorspace.h"
53 #include "MagickCore/colorspace-private.h"
54 #include "MagickCore/composite-private.h"
55 #include "MagickCore/enhance.h"
56 #include "MagickCore/exception.h"
57 #include "MagickCore/exception-private.h"
58 #include "MagickCore/fx.h"
59 #include "MagickCore/gem.h"
60 #include "MagickCore/gem-private.h"
61 #include "MagickCore/geometry.h"
62 #include "MagickCore/histogram.h"
63 #include "MagickCore/image.h"
64 #include "MagickCore/image-private.h"
65 #include "MagickCore/memory_.h"
66 #include "MagickCore/monitor.h"
67 #include "MagickCore/monitor-private.h"
68 #include "MagickCore/option.h"
69 #include "MagickCore/pixel.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/quantum.h"
72 #include "MagickCore/quantum-private.h"
73 #include "MagickCore/resample.h"
74 #include "MagickCore/resample-private.h"
75 #include "MagickCore/resource_.h"
76 #include "MagickCore/statistic.h"
77 #include "MagickCore/string_.h"
78 #include "MagickCore/string-private.h"
79 #include "MagickCore/thread-private.h"
80 #include "MagickCore/threshold.h"
81 #include "MagickCore/token.h"
82 #include "MagickCore/xml-tree.h"
83 #include "MagickCore/xml-tree-private.h"
86 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90 % A u t o G a m m a I m a g e %
94 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96 % AutoGammaImage() extract the 'mean' from the image and adjust the image
97 % to try make set its gamma appropriatally.
99 % The format of the AutoGammaImage method is:
101 % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
103 % A description of each parameter follows:
105 % o image: The image to auto-level
107 % o exception: return any errors or warnings in this structure.
110 MagickExport MagickBooleanType AutoGammaImage(Image *image,
111 ExceptionInfo *exception)
126 if (image->channel_mask == DefaultChannels)
129 Apply gamma correction equally across all given channels.
131 (void) GetImageMean(image,&mean,&sans,exception);
132 gamma=log(mean*QuantumScale)/log_mean;
133 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
136 Auto-gamma each channel separately.
139 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
144 PixelChannel channel=GetPixelChannelChannel(image,i);
145 PixelTrait traits=GetPixelChannelTraits(image,channel);
146 if ((traits & UpdatePixelTrait) == 0)
148 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
149 status=GetImageMean(image,&mean,&sans,exception);
150 gamma=log(mean*QuantumScale)/log_mean;
151 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
152 (void) SetImageChannelMask(image,channel_mask);
153 if (status == MagickFalse)
156 return(status != 0 ? MagickTrue : MagickFalse);
160 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164 % A u t o L e v e l I m a g e %
168 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
170 % AutoLevelImage() adjusts the levels of a particular image channel by
171 % scaling the minimum and maximum values to the full quantum range.
173 % The format of the LevelImage method is:
175 % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
177 % A description of each parameter follows:
179 % o image: The image to auto-level
181 % o exception: return any errors or warnings in this structure.
184 MagickExport MagickBooleanType AutoLevelImage(Image *image,
185 ExceptionInfo *exception)
187 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195 % B r i g h t n e s s C o n t r a s t I m a g e %
199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
201 % BrightnessContrastImage() changes the brightness and/or contrast of an
202 % image. It converts the brightness and contrast parameters into slope and
203 % intercept and calls a polynomical function to apply to the image.
205 % The format of the BrightnessContrastImage method is:
207 % MagickBooleanType BrightnessContrastImage(Image *image,
208 % const double brightness,const double contrast,ExceptionInfo *exception)
210 % A description of each parameter follows:
212 % o image: the image.
214 % o brightness: the brightness percent (-100 .. 100).
216 % o contrast: the contrast percent (-100 .. 100).
218 % o exception: return any errors or warnings in this structure.
221 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
222 const double brightness,const double contrast,ExceptionInfo *exception)
224 #define BrightnessContastImageTag "BrightnessContast/Image"
236 Compute slope and intercept.
238 assert(image != (Image *) NULL);
239 assert(image->signature == MagickCoreSignature);
240 if (image->debug != MagickFalse)
241 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
243 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
246 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
247 coefficients[0]=slope;
248 coefficients[1]=intercept;
249 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
254 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258 % C l u t I m a g e %
262 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
264 % ClutImage() replaces each color value in the given image, by using it as an
265 % index to lookup a replacement color value in a Color Look UP Table in the
266 % form of an image. The values are extracted along a diagonal of the CLUT
267 % image so either a horizontal or vertial gradient image can be used.
269 % Typically this is used to either re-color a gray-scale image according to a
270 % color gradient in the CLUT image, or to perform a freeform histogram
271 % (level) adjustment according to the (typically gray-scale) gradient in the
274 % When the 'channel' mask includes the matte/alpha transparency channel but
275 % one image has no such channel it is assumed that that image is a simple
276 % gray-scale image that will effect the alpha channel values, either for
277 % gray-scale coloring (with transparent or semi-transparent colors), or
278 % a histogram adjustment of existing alpha channel values. If both images
279 % have matte channels, direct and normal indexing is applied, which is rarely
282 % The format of the ClutImage method is:
284 % MagickBooleanType ClutImage(Image *image,Image *clut_image,
285 % const PixelInterpolateMethod method,ExceptionInfo *exception)
287 % A description of each parameter follows:
289 % o image: the image, which is replaced by indexed CLUT values
291 % o clut_image: the color lookup table image for replacement color values.
293 % o method: the pixel interpolation method.
295 % o exception: return any errors or warnings in this structure.
298 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
299 const PixelInterpolateMethod method,ExceptionInfo *exception)
301 #define ClutImageTag "Clut/Image"
322 assert(image != (Image *) NULL);
323 assert(image->signature == MagickCoreSignature);
324 if (image->debug != MagickFalse)
325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
326 assert(clut_image != (Image *) NULL);
327 assert(clut_image->signature == MagickCoreSignature);
328 if( SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
330 if( (IsGrayColorspace(image->colorspace) != MagickFalse) &&
331 (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
332 (void) SetImageColorspace(image,sRGBColorspace,exception);
333 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
334 if (clut_map == (PixelInfo *) NULL)
335 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
342 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
343 clut_view=AcquireVirtualCacheView(clut_image,exception);
344 for (i=0; i <= (ssize_t) MaxMap; i++)
346 GetPixelInfo(clut_image,clut_map+i);
347 (void) InterpolatePixelInfo(clut_image,clut_view,method,
348 (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
349 (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
351 clut_view=DestroyCacheView(clut_view);
352 image_view=AcquireAuthenticCacheView(image,exception);
353 #if defined(MAGICKCORE_OPENMP_SUPPORT)
354 #pragma omp parallel for schedule(static,4) shared(progress,status) \
355 magick_threads(image,image,image->rows,1)
357 for (y=0; y < (ssize_t) image->rows; y++)
368 if (status == MagickFalse)
370 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
371 if (q == (Quantum *) NULL)
376 GetPixelInfo(image,&pixel);
377 for (x=0; x < (ssize_t) image->columns; x++)
382 if (GetPixelReadMask(image,q) == 0)
384 q+=GetPixelChannels(image);
387 GetPixelInfoPixel(image,q,&pixel);
388 traits=GetPixelChannelTraits(image,RedPixelChannel);
389 if ((traits & UpdatePixelTrait) != 0)
390 pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
392 traits=GetPixelChannelTraits(image,GreenPixelChannel);
393 if ((traits & UpdatePixelTrait) != 0)
394 pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
395 pixel.green))].green;
396 traits=GetPixelChannelTraits(image,BluePixelChannel);
397 if ((traits & UpdatePixelTrait) != 0)
398 pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
400 traits=GetPixelChannelTraits(image,BlackPixelChannel);
401 if ((traits & UpdatePixelTrait) != 0)
402 pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
403 pixel.black))].black;
404 traits=GetPixelChannelTraits(image,AlphaPixelChannel);
405 if ((traits & UpdatePixelTrait) != 0)
406 pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
407 pixel.alpha))].alpha;
408 SetPixelViaPixelInfo(image,&pixel,q);
409 q+=GetPixelChannels(image);
411 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
413 if (image->progress_monitor != (MagickProgressMonitor) NULL)
418 #if defined(MAGICKCORE_OPENMP_SUPPORT)
419 #pragma omp critical (MagickCore_ClutImage)
421 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
422 if (proceed == MagickFalse)
426 image_view=DestroyCacheView(image_view);
427 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
428 if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
429 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
430 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
435 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
439 % C o l o r D e c i s i o n L i s t I m a g e %
443 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
445 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
446 % (CCC) file which solely contains one or more color corrections and applies
447 % the correction to the image. Here is a sample CCC file:
449 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
450 % <ColorCorrection id="cc03345">
452 % <Slope> 0.9 1.2 0.5 </Slope>
453 % <Offset> 0.4 -0.5 0.6 </Offset>
454 % <Power> 1.0 0.8 1.5 </Power>
457 % <Saturation> 0.85 </Saturation>
460 % </ColorCorrectionCollection>
462 % which includes the slop, offset, and power for each of the RGB channels
463 % as well as the saturation.
465 % The format of the ColorDecisionListImage method is:
467 % MagickBooleanType ColorDecisionListImage(Image *image,
468 % const char *color_correction_collection,ExceptionInfo *exception)
470 % A description of each parameter follows:
472 % o image: the image.
474 % o color_correction_collection: the color correction collection in XML.
476 % o exception: return any errors or warnings in this structure.
479 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
480 const char *color_correction_collection,ExceptionInfo *exception)
482 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
484 typedef struct _Correction
492 typedef struct _ColorCorrection
507 token[MagickPathExtent];
538 Allocate and initialize cdl maps.
540 assert(image != (Image *) NULL);
541 assert(image->signature == MagickCoreSignature);
542 if (image->debug != MagickFalse)
543 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
544 if (color_correction_collection == (const char *) NULL)
546 ccc=NewXMLTree((const char *) color_correction_collection,exception);
547 if (ccc == (XMLTreeInfo *) NULL)
549 cc=GetXMLTreeChild(ccc,"ColorCorrection");
550 if (cc == (XMLTreeInfo *) NULL)
552 ccc=DestroyXMLTree(ccc);
555 color_correction.red.slope=1.0;
556 color_correction.red.offset=0.0;
557 color_correction.red.power=1.0;
558 color_correction.green.slope=1.0;
559 color_correction.green.offset=0.0;
560 color_correction.green.power=1.0;
561 color_correction.blue.slope=1.0;
562 color_correction.blue.offset=0.0;
563 color_correction.blue.power=1.0;
564 color_correction.saturation=0.0;
565 sop=GetXMLTreeChild(cc,"SOPNode");
566 if (sop != (XMLTreeInfo *) NULL)
573 slope=GetXMLTreeChild(sop,"Slope");
574 if (slope != (XMLTreeInfo *) NULL)
576 content=GetXMLTreeContent(slope);
577 p=(const char *) content;
578 for (i=0; (*p != '\0') && (i < 3); i++)
580 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 (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
892 if (image->debug != MagickFalse)
893 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
894 sign=sharpen != MagickFalse ? 1 : -1;
895 if (image->storage_class == PseudoClass)
898 Contrast enhance colormap.
900 for (i=0; i < (ssize_t) image->colors; i++)
907 red=(double) image->colormap[i].red;
908 green=(double) image->colormap[i].green;
909 blue=(double) image->colormap[i].blue;
910 Contrast(sign,&red,&green,&blue);
911 image->colormap[i].red=(MagickRealType) red;
912 image->colormap[i].green=(MagickRealType) green;
913 image->colormap[i].blue=(MagickRealType) blue;
917 Contrast enhance image.
921 image_view=AcquireAuthenticCacheView(image,exception);
922 #if defined(MAGICKCORE_OPENMP_SUPPORT)
923 #pragma omp parallel for schedule(static,4) shared(progress,status) \
924 magick_threads(image,image,image->rows,1)
926 for (y=0; y < (ssize_t) image->rows; y++)
939 if (status == MagickFalse)
941 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
942 if (q == (Quantum *) NULL)
947 for (x=0; x < (ssize_t) image->columns; x++)
949 red=(double) GetPixelRed(image,q);
950 green=(double) GetPixelGreen(image,q);
951 blue=(double) GetPixelBlue(image,q);
952 Contrast(sign,&red,&green,&blue);
953 SetPixelRed(image,ClampToQuantum(red),q);
954 SetPixelGreen(image,ClampToQuantum(green),q);
955 SetPixelBlue(image,ClampToQuantum(blue),q);
956 q+=GetPixelChannels(image);
958 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
960 if (image->progress_monitor != (MagickProgressMonitor) NULL)
965 #if defined(MAGICKCORE_OPENMP_SUPPORT)
966 #pragma omp critical (MagickCore_ContrastImage)
968 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
969 if (proceed == MagickFalse)
973 image_view=DestroyCacheView(image_view);
978 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
982 % C o n t r a s t S t r e t c h I m a g e %
986 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
988 % ContrastStretchImage() is a simple image enhancement technique that attempts
989 % to improve the contrast in an image by 'stretching' the range of intensity
990 % values it contains to span a desired range of values. It differs from the
991 % more sophisticated histogram equalization in that it can only apply a
992 % linear scaling function to the image pixel values. As a result the
993 % 'enhancement' is less harsh.
995 % The format of the ContrastStretchImage method is:
997 % MagickBooleanType ContrastStretchImage(Image *image,
998 % const char *levels,ExceptionInfo *exception)
1000 % A description of each parameter follows:
1002 % o image: the image.
1004 % o black_point: the black point.
1006 % o white_point: the white point.
1008 % o levels: Specify the levels where the black and white points have the
1009 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1011 % o exception: return any errors or warnings in this structure.
1014 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1015 const double black_point,const double white_point,ExceptionInfo *exception)
1017 #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
1018 #define ContrastStretchImageTag "ContrastStretch/Image"
1042 Allocate histogram and stretch map.
1044 assert(image != (Image *) NULL);
1045 assert(image->signature == MagickCoreSignature);
1046 if (image->debug != MagickFalse)
1047 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1048 if (SetImageGray(image,exception) != MagickFalse)
1049 (void) SetImageColorspace(image,GRAYColorspace,exception);
1050 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1051 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
1052 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1053 sizeof(*histogram));
1054 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1055 GetPixelChannels(image)*sizeof(*stretch_map));
1056 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1057 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
1059 if (stretch_map != (double *) NULL)
1060 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1061 if (histogram != (double *) NULL)
1062 histogram=(double *) RelinquishMagickMemory(histogram);
1063 if (white != (double *) NULL)
1064 white=(double *) RelinquishMagickMemory(white);
1065 if (black != (double *) NULL)
1066 black=(double *) RelinquishMagickMemory(black);
1067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1074 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1075 sizeof(*histogram));
1076 image_view=AcquireVirtualCacheView(image,exception);
1077 for (y=0; y < (ssize_t) image->rows; y++)
1079 register const Quantum
1085 if (status == MagickFalse)
1087 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1088 if (p == (const Quantum *) NULL)
1093 for (x=0; x < (ssize_t) image->columns; x++)
1098 pixel=GetPixelIntensity(image,p);
1099 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1101 if (image->channel_mask != DefaultChannels)
1102 pixel=(double) p[i];
1103 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1104 ClampToQuantum(pixel))+i]++;
1106 p+=GetPixelChannels(image);
1109 image_view=DestroyCacheView(image_view);
1111 Find the histogram boundaries by locating the black/white levels.
1113 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1122 white[i]=MaxRange(QuantumRange);
1124 for (j=0; j <= (ssize_t) MaxMap; j++)
1126 intensity+=histogram[GetPixelChannels(image)*j+i];
1127 if (intensity > black_point)
1130 black[i]=(double) j;
1132 for (j=(ssize_t) MaxMap; j != 0; j--)
1134 intensity+=histogram[GetPixelChannels(image)*j+i];
1135 if (intensity > ((double) image->columns*image->rows-white_point))
1138 white[i]=(double) j;
1140 histogram=(double *) RelinquishMagickMemory(histogram);
1142 Stretch the histogram to create the stretched image mapping.
1144 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1145 sizeof(*stretch_map));
1146 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1151 for (j=0; j <= (ssize_t) MaxMap; j++)
1156 gamma=PerceptibleReciprocal(white[i]-black[i]);
1157 if (j < (ssize_t) black[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1160 if (j > (ssize_t) white[i])
1161 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
1163 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1164 (double) (MaxMap*gamma*(j-black[i])));
1167 if (image->storage_class == PseudoClass)
1173 Stretch-contrast colormap.
1175 for (j=0; j < (ssize_t) image->colors; j++)
1177 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1179 i=GetPixelChannelOffset(image,RedPixelChannel);
1180 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1181 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
1183 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1185 i=GetPixelChannelOffset(image,GreenPixelChannel);
1186 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1187 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
1189 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1191 i=GetPixelChannelOffset(image,BluePixelChannel);
1192 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1193 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
1195 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1197 i=GetPixelChannelOffset(image,AlphaPixelChannel);
1198 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1199 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
1204 Stretch-contrast image.
1208 image_view=AcquireAuthenticCacheView(image,exception);
1209 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1211 magick_threads(image,image,image->rows,1)
1213 for (y=0; y < (ssize_t) image->rows; y++)
1221 if (status == MagickFalse)
1223 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1224 if (q == (Quantum *) NULL)
1229 for (x=0; x < (ssize_t) image->columns; x++)
1234 if (GetPixelReadMask(image,q) == 0)
1236 q+=GetPixelChannels(image);
1239 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1241 PixelChannel channel=GetPixelChannelChannel(image,j);
1242 PixelTrait traits=GetPixelChannelTraits(image,channel);
1243 if ((traits & UpdatePixelTrait) == 0)
1245 q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1246 ScaleQuantumToMap(q[j])+j]);
1248 q+=GetPixelChannels(image);
1250 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1252 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1257 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1258 #pragma omp critical (MagickCore_ContrastStretchImage)
1260 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1262 if (proceed == MagickFalse)
1266 image_view=DestroyCacheView(image_view);
1267 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1268 white=(double *) RelinquishMagickMemory(white);
1269 black=(double *) RelinquishMagickMemory(black);
1274 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1278 % E n h a n c e I m a g e %
1282 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284 % EnhanceImage() applies a digital filter that improves the quality of a
1287 % The format of the EnhanceImage method is:
1289 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1291 % A description of each parameter follows:
1293 % o image: the image.
1295 % o exception: return any errors or warnings in this structure.
1298 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1300 #define EnhanceImageTag "Enhance/Image"
1301 #define EnhancePixel(weight) \
1302 mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1303 distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1304 distance_squared=(4.0+mean)*distance*distance; \
1305 mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1306 distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1307 distance_squared+=(7.0-mean)*distance*distance; \
1308 mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1309 distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1310 distance_squared+=(5.0-mean)*distance*distance; \
1311 mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1312 distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1313 distance_squared+=(5.0-mean)*distance*distance; \
1314 mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1315 distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1316 distance_squared+=(5.0-mean)*distance*distance; \
1317 if (distance_squared < 0.069) \
1319 aggregate.red+=(weight)*GetPixelRed(image,r); \
1320 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1321 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1322 aggregate.black+=(weight)*GetPixelBlack(image,r); \
1323 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1324 total_weight+=(weight); \
1326 r+=GetPixelChannels(image);
1345 Initialize enhanced image attributes.
1347 assert(image != (const Image *) NULL);
1348 assert(image->signature == MagickCoreSignature);
1349 if (image->debug != MagickFalse)
1350 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1351 assert(exception != (ExceptionInfo *) NULL);
1352 assert(exception->signature == MagickCoreSignature);
1353 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1355 if (enhance_image == (Image *) NULL)
1356 return((Image *) NULL);
1357 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1359 enhance_image=DestroyImage(enhance_image);
1360 return((Image *) NULL);
1367 image_view=AcquireVirtualCacheView(image,exception);
1368 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1370 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1371 magick_threads(image,enhance_image,image->rows,1)
1373 for (y=0; y < (ssize_t) image->rows; y++)
1378 register const Quantum
1390 if (status == MagickFalse)
1392 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1393 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1395 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1400 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
1401 GetPixelInfo(image,&pixel);
1402 for (x=0; x < (ssize_t) image->columns; x++)
1413 register const Quantum
1416 if (GetPixelReadMask(image,p) == 0)
1418 SetPixelBackgoundColor(enhance_image,q);
1419 p+=GetPixelChannels(image);
1420 q+=GetPixelChannels(enhance_image);
1423 GetPixelInfo(image,&aggregate);
1425 GetPixelInfoPixel(image,p+center,&pixel);
1427 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1428 EnhancePixel(8.0); EnhancePixel(5.0);
1429 r=p+GetPixelChannels(image)*(image->columns+4);
1430 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1431 EnhancePixel(20.0); EnhancePixel(8.0);
1432 r=p+2*GetPixelChannels(image)*(image->columns+4);
1433 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1434 EnhancePixel(40.0); EnhancePixel(10.0);
1435 r=p+3*GetPixelChannels(image)*(image->columns+4);
1436 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1437 EnhancePixel(20.0); EnhancePixel(8.0);
1438 r=p+4*GetPixelChannels(image)*(image->columns+4);
1439 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1440 EnhancePixel(8.0); EnhancePixel(5.0);
1441 pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1442 pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1443 pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1444 pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1445 pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1446 SetPixelViaPixelInfo(image,&pixel,q);
1447 p+=GetPixelChannels(image);
1448 q+=GetPixelChannels(enhance_image);
1450 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1452 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1457 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1458 #pragma omp critical (MagickCore_EnhanceImage)
1460 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1461 if (proceed == MagickFalse)
1465 enhance_view=DestroyCacheView(enhance_view);
1466 image_view=DestroyCacheView(image_view);
1467 if (status == MagickFalse)
1468 enhance_image=DestroyImage(enhance_image);
1469 return(enhance_image);
1473 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477 % E q u a l i z e I m a g e %
1481 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1483 % EqualizeImage() applies a histogram equalization to the image.
1485 % The format of the EqualizeImage method is:
1487 % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
1489 % A description of each parameter follows:
1491 % o image: the image.
1493 % o exception: return any errors or warnings in this structure.
1496 MagickExport MagickBooleanType EqualizeImage(Image *image,
1497 ExceptionInfo *exception)
1499 #define EqualizeImageTag "Equalize/Image"
1505 black[CompositePixelChannel+1],
1509 white[CompositePixelChannel+1];
1524 Allocate and initialize histogram arrays.
1526 assert(image != (Image *) NULL);
1527 assert(image->signature == MagickCoreSignature);
1528 if (AccelerateEqualizeImage(image,exception) != MagickFalse)
1530 if (image->debug != MagickFalse)
1531 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1532 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
1533 GetPixelChannels(image)*sizeof(*equalize_map));
1534 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1535 sizeof(*histogram));
1536 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1538 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
1539 (map == (double *) NULL))
1541 if (map != (double *) NULL)
1542 map=(double *) RelinquishMagickMemory(map);
1543 if (histogram != (double *) NULL)
1544 histogram=(double *) RelinquishMagickMemory(histogram);
1545 if (equalize_map != (double *) NULL)
1546 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1547 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1554 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1555 sizeof(*histogram));
1556 image_view=AcquireVirtualCacheView(image,exception);
1557 for (y=0; y < (ssize_t) image->rows; y++)
1559 register const Quantum
1565 if (status == MagickFalse)
1567 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1568 if (p == (const Quantum *) NULL)
1573 for (x=0; x < (ssize_t) image->columns; x++)
1575 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1581 if ((image->channel_mask & SyncChannels) != 0)
1582 intensity=GetPixelIntensity(image,p);
1583 histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
1585 p+=GetPixelChannels(image);
1588 image_view=DestroyCacheView(image_view);
1590 Integrate the histogram to get the equalization map.
1592 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1601 for (j=0; j <= (ssize_t) MaxMap; j++)
1603 intensity+=histogram[GetPixelChannels(image)*j+i];
1604 map[GetPixelChannels(image)*j+i]=intensity;
1607 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1608 sizeof(*equalize_map));
1609 (void) ResetMagickMemory(black,0,sizeof(*black));
1610 (void) ResetMagickMemory(white,0,sizeof(*white));
1611 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1617 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1618 if (black[i] != white[i])
1619 for (j=0; j <= (ssize_t) MaxMap; j++)
1620 equalize_map[GetPixelChannels(image)*j+i]=(double)
1621 ScaleMapToQuantum((double) ((MaxMap*(map[
1622 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1624 histogram=(double *) RelinquishMagickMemory(histogram);
1625 map=(double *) RelinquishMagickMemory(map);
1626 if (image->storage_class == PseudoClass)
1634 for (j=0; j < (ssize_t) image->colors; j++)
1636 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1638 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
1639 if (black[channel] != white[channel])
1640 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
1641 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
1644 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1646 PixelChannel channel=GetPixelChannelChannel(image,
1648 if (black[channel] != white[channel])
1649 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
1650 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
1653 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1655 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
1656 if (black[channel] != white[channel])
1657 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
1658 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
1661 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1663 PixelChannel channel=GetPixelChannelChannel(image,
1665 if (black[channel] != white[channel])
1666 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
1667 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
1676 image_view=AcquireAuthenticCacheView(image,exception);
1677 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1678 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1679 magick_threads(image,image,image->rows,1)
1681 for (y=0; y < (ssize_t) image->rows; y++)
1689 if (status == MagickFalse)
1691 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1692 if (q == (Quantum *) NULL)
1697 for (x=0; x < (ssize_t) image->columns; x++)
1702 if (GetPixelReadMask(image,q) == 0)
1704 q+=GetPixelChannels(image);
1707 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1709 PixelChannel channel=GetPixelChannelChannel(image,j);
1710 PixelTrait traits=GetPixelChannelTraits(image,channel);
1711 if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
1713 q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1714 ScaleQuantumToMap(q[j])+j]);
1716 q+=GetPixelChannels(image);
1718 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1720 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1725 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1726 #pragma omp critical (MagickCore_EqualizeImage)
1728 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1729 if (proceed == MagickFalse)
1733 image_view=DestroyCacheView(image_view);
1734 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
1739 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1743 % G a m m a I m a g e %
1747 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1749 % GammaImage() gamma-corrects a particular image channel. The same
1750 % image viewed on different devices will have perceptual differences in the
1751 % way the image's intensities are represented on the screen. Specify
1752 % individual gamma levels for the red, green, and blue channels, or adjust
1753 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1755 % You can also reduce the influence of a particular channel with a gamma
1758 % The format of the GammaImage method is:
1760 % MagickBooleanType GammaImage(Image *image,const double gamma,
1761 % ExceptionInfo *exception)
1763 % A description of each parameter follows:
1765 % o image: the image.
1767 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1769 % o gamma: the image gamma.
1773 static inline double gamma_pow(const double value,const double gamma)
1775 return(value < 0.0 ? value : pow(value,gamma));
1778 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1779 ExceptionInfo *exception)
1781 #define GammaCorrectImageTag "GammaCorrect/Image"
1802 Allocate and initialize gamma maps.
1804 assert(image != (Image *) NULL);
1805 assert(image->signature == MagickCoreSignature);
1806 if (image->debug != MagickFalse)
1807 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1810 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1811 if (gamma_map == (Quantum *) NULL)
1812 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1814 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1816 for (i=0; i <= (ssize_t) MaxMap; i++)
1817 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
1818 MaxMap,1.0/gamma)));
1819 if (image->storage_class == PseudoClass)
1820 for (i=0; i < (ssize_t) image->colors; i++)
1823 Gamma-correct colormap.
1825 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1826 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1827 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1828 ClampToQuantum(image->colormap[i].red))];
1829 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1830 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1831 ClampToQuantum(image->colormap[i].green))];
1832 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1833 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1834 ClampToQuantum(image->colormap[i].blue))];
1835 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1836 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1837 ClampToQuantum(image->colormap[i].alpha))];
1839 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1840 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1841 image->colormap[i].red,1.0/gamma);
1842 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1843 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1844 image->colormap[i].green,1.0/gamma);
1845 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1846 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1847 image->colormap[i].blue,1.0/gamma);
1848 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1849 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1850 image->colormap[i].alpha,1.0/gamma);
1854 Gamma-correct image.
1858 image_view=AcquireAuthenticCacheView(image,exception);
1859 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1860 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1861 magick_threads(image,image,image->rows,1)
1863 for (y=0; y < (ssize_t) image->rows; y++)
1871 if (status == MagickFalse)
1873 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1874 if (q == (Quantum *) NULL)
1879 for (x=0; x < (ssize_t) image->columns; x++)
1884 if (GetPixelReadMask(image,q) == 0)
1886 q+=GetPixelChannels(image);
1889 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1891 PixelChannel channel=GetPixelChannelChannel(image,j);
1892 PixelTrait traits=GetPixelChannelTraits(image,channel);
1893 if ((traits & UpdatePixelTrait) == 0)
1895 #if !defined(MAGICKCORE_HDRI_SUPPORT)
1896 q[j]=gamma_map[ScaleQuantumToMap(q[j])];
1898 q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
1901 q+=GetPixelChannels(image);
1903 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1905 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1910 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1911 #pragma omp critical (MagickCore_GammaImage)
1913 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1915 if (proceed == MagickFalse)
1919 image_view=DestroyCacheView(image_view);
1920 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
1921 if (image->gamma != 0.0)
1922 image->gamma*=gamma;
1927 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1931 % G r a y s c a l e I m a g e %
1935 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1937 % GrayscaleImage() converts the image to grayscale.
1939 % The format of the GrayscaleImage method is:
1941 % MagickBooleanType GrayscaleImage(Image *image,
1942 % const PixelIntensityMethod method ,ExceptionInfo *exception)
1944 % A description of each parameter follows:
1946 % o image: the image.
1948 % o method: the pixel intensity method.
1950 % o exception: return any errors or warnings in this structure.
1953 MagickExport MagickBooleanType GrayscaleImage(Image *image,
1954 const PixelIntensityMethod method,ExceptionInfo *exception)
1956 #define GrayscaleImageTag "Grayscale/Image"
1970 assert(image != (Image *) NULL);
1971 assert(image->signature == MagickCoreSignature);
1972 if (image->debug != MagickFalse)
1973 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1974 if (image->storage_class == PseudoClass)
1976 if (SyncImage(image,exception) == MagickFalse)
1977 return(MagickFalse);
1978 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
1979 return(MagickFalse);
1981 if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
1983 image->intensity=method;
1984 image->type=GrayscaleType;
1985 return(SetImageColorspace(image,GRAYColorspace,exception));
1992 image_view=AcquireAuthenticCacheView(image,exception);
1993 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1994 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1995 magick_threads(image,image,image->rows,1)
1997 for (y=0; y < (ssize_t) image->rows; y++)
2005 if (status == MagickFalse)
2007 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2008 if (q == (Quantum *) NULL)
2013 for (x=0; x < (ssize_t) image->columns; x++)
2021 if (GetPixelReadMask(image,q) == 0)
2023 q+=GetPixelChannels(image);
2026 red=(MagickRealType) GetPixelRed(image,q);
2027 green=(MagickRealType) GetPixelGreen(image,q);
2028 blue=(MagickRealType) GetPixelBlue(image,q);
2032 case AveragePixelIntensityMethod:
2034 intensity=(red+green+blue)/3.0;
2037 case BrightnessPixelIntensityMethod:
2039 intensity=MagickMax(MagickMax(red,green),blue);
2042 case LightnessPixelIntensityMethod:
2044 intensity=(MagickMin(MagickMin(red,green),blue)+
2045 MagickMax(MagickMax(red,green),blue))/2.0;
2048 case MSPixelIntensityMethod:
2050 intensity=(MagickRealType) (((double) red*red+green*green+
2054 case Rec601LumaPixelIntensityMethod:
2056 if (image->colorspace == RGBColorspace)
2058 red=EncodePixelGamma(red);
2059 green=EncodePixelGamma(green);
2060 blue=EncodePixelGamma(blue);
2062 intensity=0.298839*red+0.586811*green+0.114350*blue;
2065 case Rec601LuminancePixelIntensityMethod:
2067 if (image->colorspace == sRGBColorspace)
2069 red=DecodePixelGamma(red);
2070 green=DecodePixelGamma(green);
2071 blue=DecodePixelGamma(blue);
2073 intensity=0.298839*red+0.586811*green+0.114350*blue;
2076 case Rec709LumaPixelIntensityMethod:
2079 if (image->colorspace == RGBColorspace)
2081 red=EncodePixelGamma(red);
2082 green=EncodePixelGamma(green);
2083 blue=EncodePixelGamma(blue);
2085 intensity=0.212656*red+0.715158*green+0.072186*blue;
2088 case Rec709LuminancePixelIntensityMethod:
2090 if (image->colorspace == sRGBColorspace)
2092 red=DecodePixelGamma(red);
2093 green=DecodePixelGamma(green);
2094 blue=DecodePixelGamma(blue);
2096 intensity=0.212656*red+0.715158*green+0.072186*blue;
2099 case RMSPixelIntensityMethod:
2101 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2102 blue*blue)/sqrt(3.0));
2106 SetPixelGray(image,ClampToQuantum(intensity),q);
2107 q+=GetPixelChannels(image);
2109 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2111 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2116 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2117 #pragma omp critical (MagickCore_GrayscaleImage)
2119 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2121 if (proceed == MagickFalse)
2125 image_view=DestroyCacheView(image_view);
2126 image->intensity=method;
2127 image->type=GrayscaleType;
2128 return(SetImageColorspace(image,GRAYColorspace,exception));
2132 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2136 % H a l d C l u t I m a g e %
2140 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2142 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2143 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2144 % Create it with the HALD coder. You can apply any color transformation to
2145 % the Hald image and then use this method to apply the transform to the
2148 % The format of the HaldClutImage method is:
2150 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2151 % ExceptionInfo *exception)
2153 % A description of each parameter follows:
2155 % o image: the image, which is replaced by indexed CLUT values
2157 % o hald_image: the color lookup table image for replacement color values.
2159 % o exception: return any errors or warnings in this structure.
2162 MagickExport MagickBooleanType HaldClutImage(Image *image,
2163 const Image *hald_image,ExceptionInfo *exception)
2165 #define HaldClutImageTag "Clut/Image"
2167 typedef struct _HaldInfo
2199 assert(image != (Image *) NULL);
2200 assert(image->signature == MagickCoreSignature);
2201 if (image->debug != MagickFalse)
2202 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2203 assert(hald_image != (Image *) NULL);
2204 assert(hald_image->signature == MagickCoreSignature);
2205 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2206 return(MagickFalse);
2207 if (image->alpha_trait == UndefinedPixelTrait)
2208 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2214 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2215 (MagickRealType) hald_image->rows);
2216 for (level=2; (level*level*level) < length; level++) ;
2218 cube_size=level*level;
2219 width=(double) hald_image->columns;
2220 GetPixelInfo(hald_image,&zero);
2221 hald_view=AcquireVirtualCacheView(hald_image,exception);
2222 image_view=AcquireAuthenticCacheView(image,exception);
2223 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2224 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2225 magick_threads(image,image,image->rows,1)
2227 for (y=0; y < (ssize_t) image->rows; y++)
2235 if (status == MagickFalse)
2237 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2238 if (q == (Quantum *) NULL)
2243 for (x=0; x < (ssize_t) image->columns; x++)
2258 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2259 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2260 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2261 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2262 point.x-=floor(point.x);
2263 point.y-=floor(point.y);
2264 point.z-=floor(point.z);
2266 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2267 fmod(offset,width),floor(offset/width),&pixel1,exception);
2269 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2270 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2272 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2275 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2276 fmod(offset,width),floor(offset/width),&pixel1,exception);
2277 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2278 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2280 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2283 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2285 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2286 SetPixelRed(image,ClampToQuantum(pixel.red),q);
2287 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2288 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2289 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2290 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2291 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2292 (image->colorspace == CMYKColorspace))
2293 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2294 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2295 (image->alpha_trait != UndefinedPixelTrait))
2296 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2297 q+=GetPixelChannels(image);
2299 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2301 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2306 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2307 #pragma omp critical (MagickCore_HaldClutImage)
2309 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2310 if (proceed == MagickFalse)
2314 hald_view=DestroyCacheView(hald_view);
2315 image_view=DestroyCacheView(image_view);
2320 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2324 % L e v e l I m a g e %
2328 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2330 % LevelImage() adjusts the levels of a particular image channel by
2331 % scaling the colors falling between specified white and black points to
2332 % the full available quantum range.
2334 % The parameters provided represent the black, and white points. The black
2335 % point specifies the darkest color in the image. Colors darker than the
2336 % black point are set to zero. White point specifies the lightest color in
2337 % the image. Colors brighter than the white point are set to the maximum
2340 % If a '!' flag is given, map black and white colors to the given levels
2341 % rather than mapping those levels to black and white. See
2342 % LevelizeImage() below.
2344 % Gamma specifies a gamma correction to apply to the image.
2346 % The format of the LevelImage method is:
2348 % MagickBooleanType LevelImage(Image *image,const double black_point,
2349 % const double white_point,const double gamma,ExceptionInfo *exception)
2351 % A description of each parameter follows:
2353 % o image: the image.
2355 % o black_point: The level to map zero (black) to.
2357 % o white_point: The level to map QuantumRange (white) to.
2359 % o exception: return any errors or warnings in this structure.
2363 static inline double LevelPixel(const double black_point,
2364 const double white_point,const double gamma,const double pixel)
2370 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2371 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2373 return(level_pixel);
2376 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2377 const double white_point,const double gamma,ExceptionInfo *exception)
2379 #define LevelImageTag "Level/Image"
2397 Allocate and initialize levels map.
2399 assert(image != (Image *) NULL);
2400 assert(image->signature == MagickCoreSignature);
2401 if (image->debug != MagickFalse)
2402 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2403 if (image->storage_class == PseudoClass)
2404 for (i=0; i < (ssize_t) image->colors; i++)
2409 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2410 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2411 white_point,gamma,image->colormap[i].red));
2412 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2413 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2414 white_point,gamma,image->colormap[i].green));
2415 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2416 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2417 white_point,gamma,image->colormap[i].blue));
2418 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2419 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2420 white_point,gamma,image->colormap[i].alpha));
2427 image_view=AcquireAuthenticCacheView(image,exception);
2428 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2429 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2430 magick_threads(image,image,image->rows,1)
2432 for (y=0; y < (ssize_t) image->rows; y++)
2440 if (status == MagickFalse)
2442 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2443 if (q == (Quantum *) NULL)
2448 for (x=0; x < (ssize_t) image->columns; x++)
2453 if (GetPixelReadMask(image,q) == 0)
2455 q+=GetPixelChannels(image);
2458 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2460 PixelChannel channel=GetPixelChannelChannel(image,j);
2461 PixelTrait traits=GetPixelChannelTraits(image,channel);
2462 if ((traits & UpdatePixelTrait) == 0)
2464 q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2467 q+=GetPixelChannels(image);
2469 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2471 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2476 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2477 #pragma omp critical (MagickCore_LevelImage)
2479 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2480 if (proceed == MagickFalse)
2484 image_view=DestroyCacheView(image_view);
2485 (void) ClampImage(image,exception);
2490 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2494 % L e v e l i z e I m a g e %
2498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2500 % LevelizeImage() applies the reversed LevelImage() operation to just
2501 % the specific channels specified. It compresses the full range of color
2502 % values, so that they lie between the given black and white points. Gamma is
2503 % applied before the values are mapped.
2505 % LevelizeImage() can be called with by using a +level command line
2506 % API option, or using a '!' on a -level or LevelImage() geometry string.
2508 % It can be used to de-contrast a greyscale image to the exact levels
2509 % specified. Or by using specific levels for each channel of an image you
2510 % can convert a gray-scale image to any linear color gradient, according to
2513 % The format of the LevelizeImage method is:
2515 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2516 % const double white_point,const double gamma,ExceptionInfo *exception)
2518 % A description of each parameter follows:
2520 % o image: the image.
2522 % o black_point: The level to map zero (black) to.
2524 % o white_point: The level to map QuantumRange (white) to.
2526 % o gamma: adjust gamma by this factor before mapping values.
2528 % o exception: return any errors or warnings in this structure.
2531 MagickExport MagickBooleanType LevelizeImage(Image *image,
2532 const double black_point,const double white_point,const double gamma,
2533 ExceptionInfo *exception)
2535 #define LevelizeImageTag "Levelize/Image"
2536 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2537 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
2555 Allocate and initialize levels map.
2557 assert(image != (Image *) NULL);
2558 assert(image->signature == MagickCoreSignature);
2559 if (image->debug != MagickFalse)
2560 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2561 if (image->storage_class == PseudoClass)
2562 for (i=0; i < (ssize_t) image->colors; i++)
2567 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2568 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
2569 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2570 image->colormap[i].green=(double) LevelizeValue(
2571 image->colormap[i].green);
2572 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2573 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
2574 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2575 image->colormap[i].alpha=(double) LevelizeValue(
2576 image->colormap[i].alpha);
2583 image_view=AcquireAuthenticCacheView(image,exception);
2584 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2585 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2586 magick_threads(image,image,image->rows,1)
2588 for (y=0; y < (ssize_t) image->rows; y++)
2596 if (status == MagickFalse)
2598 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2599 if (q == (Quantum *) NULL)
2604 for (x=0; x < (ssize_t) image->columns; x++)
2609 if (GetPixelReadMask(image,q) == 0)
2611 q+=GetPixelChannels(image);
2614 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2616 PixelChannel channel=GetPixelChannelChannel(image,j);
2617 PixelTrait traits=GetPixelChannelTraits(image,channel);
2618 if ((traits & UpdatePixelTrait) == 0)
2620 q[j]=LevelizeValue(q[j]);
2622 q+=GetPixelChannels(image);
2624 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2626 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2631 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2632 #pragma omp critical (MagickCore_LevelizeImage)
2634 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2635 if (proceed == MagickFalse)
2639 image_view=DestroyCacheView(image_view);
2644 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2648 % L e v e l I m a g e C o l o r s %
2652 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2654 % LevelImageColors() maps the given color to "black" and "white" values,
2655 % linearly spreading out the colors, and level values on a channel by channel
2656 % bases, as per LevelImage(). The given colors allows you to specify
2657 % different level ranges for each of the color channels separately.
2659 % If the boolean 'invert' is set true the image values will modifyed in the
2660 % reverse direction. That is any existing "black" and "white" colors in the
2661 % image will become the color values given, with all other values compressed
2662 % appropriatally. This effectivally maps a greyscale gradient into the given
2665 % The format of the LevelImageColors method is:
2667 % MagickBooleanType LevelImageColors(Image *image,
2668 % const PixelInfo *black_color,const PixelInfo *white_color,
2669 % const MagickBooleanType invert,ExceptionInfo *exception)
2671 % A description of each parameter follows:
2673 % o image: the image.
2675 % o black_color: The color to map black to/from
2677 % o white_point: The color to map white to/from
2679 % o invert: if true map the colors (levelize), rather than from (level)
2681 % o exception: return any errors or warnings in this structure.
2684 MagickExport MagickBooleanType LevelImageColors(Image *image,
2685 const PixelInfo *black_color,const PixelInfo *white_color,
2686 const MagickBooleanType invert,ExceptionInfo *exception)
2695 Allocate and initialize levels map.
2697 assert(image != (Image *) NULL);
2698 assert(image->signature == MagickCoreSignature);
2699 if (image->debug != MagickFalse)
2700 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2701 if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
2702 ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
2703 (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
2704 (void) SetImageColorspace(image,sRGBColorspace,exception);
2706 if (invert == MagickFalse)
2708 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2710 channel_mask=SetImageChannelMask(image,RedChannel);
2711 status&=LevelImage(image,black_color->red,white_color->red,1.0,
2713 (void) SetImageChannelMask(image,channel_mask);
2715 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2717 channel_mask=SetImageChannelMask(image,GreenChannel);
2718 status&=LevelImage(image,black_color->green,white_color->green,1.0,
2720 (void) SetImageChannelMask(image,channel_mask);
2722 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2724 channel_mask=SetImageChannelMask(image,BlueChannel);
2725 status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
2727 (void) SetImageChannelMask(image,channel_mask);
2729 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2730 (image->colorspace == CMYKColorspace))
2732 channel_mask=SetImageChannelMask(image,BlackChannel);
2733 status&=LevelImage(image,black_color->black,white_color->black,1.0,
2735 (void) SetImageChannelMask(image,channel_mask);
2737 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2738 (image->alpha_trait != UndefinedPixelTrait))
2740 channel_mask=SetImageChannelMask(image,AlphaChannel);
2741 status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
2743 (void) SetImageChannelMask(image,channel_mask);
2748 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2750 channel_mask=SetImageChannelMask(image,RedChannel);
2751 status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
2753 (void) SetImageChannelMask(image,channel_mask);
2755 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2757 channel_mask=SetImageChannelMask(image,GreenChannel);
2758 status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
2760 (void) SetImageChannelMask(image,channel_mask);
2762 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2764 channel_mask=SetImageChannelMask(image,BlueChannel);
2765 status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2767 (void) SetImageChannelMask(image,channel_mask);
2769 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2770 (image->colorspace == CMYKColorspace))
2772 channel_mask=SetImageChannelMask(image,BlackChannel);
2773 status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
2775 (void) SetImageChannelMask(image,channel_mask);
2777 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2778 (image->alpha_trait != UndefinedPixelTrait))
2780 channel_mask=SetImageChannelMask(image,AlphaChannel);
2781 status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2783 (void) SetImageChannelMask(image,channel_mask);
2786 return(status != 0 ? MagickTrue : MagickFalse);
2790 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2794 % L i n e a r S t r e t c h I m a g e %
2798 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2800 % LinearStretchImage() discards any pixels below the black point and above
2801 % the white point and levels the remaining pixels.
2803 % The format of the LinearStretchImage method is:
2805 % MagickBooleanType LinearStretchImage(Image *image,
2806 % const double black_point,const double white_point,
2807 % ExceptionInfo *exception)
2809 % A description of each parameter follows:
2811 % o image: the image.
2813 % o black_point: the black point.
2815 % o white_point: the white point.
2817 % o exception: return any errors or warnings in this structure.
2820 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2821 const double black_point,const double white_point,ExceptionInfo *exception)
2823 #define LinearStretchImageTag "LinearStretch/Image"
2841 Allocate histogram and linear map.
2843 assert(image != (Image *) NULL);
2844 assert(image->signature == MagickCoreSignature);
2845 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
2846 if (histogram == (double *) NULL)
2847 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2852 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2853 image_view=AcquireVirtualCacheView(image,exception);
2854 for (y=0; y < (ssize_t) image->rows; y++)
2856 register const Quantum
2862 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2863 if (p == (const Quantum *) NULL)
2865 for (x=0; x < (ssize_t) image->columns; x++)
2867 intensity=GetPixelIntensity(image,p);
2868 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
2869 p+=GetPixelChannels(image);
2872 image_view=DestroyCacheView(image_view);
2874 Find the histogram boundaries by locating the black and white point levels.
2877 for (black=0; black < (ssize_t) MaxMap; black++)
2879 intensity+=histogram[black];
2880 if (intensity >= black_point)
2884 for (white=(ssize_t) MaxMap; white != 0; white--)
2886 intensity+=histogram[white];
2887 if (intensity >= white_point)
2890 histogram=(double *) RelinquishMagickMemory(histogram);
2891 status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
2892 (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
2898 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2902 % M o d u l a t e I m a g e %
2906 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2908 % ModulateImage() lets you control the brightness, saturation, and hue
2909 % of an image. Modulate represents the brightness, saturation, and hue
2910 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2911 % modulation is lightness, saturation, and hue. For HWB, use blackness,
2912 % whiteness, and hue. And for HCL, use chrome, luma, and hue.
2914 % The format of the ModulateImage method is:
2916 % MagickBooleanType ModulateImage(Image *image,const char *modulate,
2917 % ExceptionInfo *exception)
2919 % A description of each parameter follows:
2921 % o image: the image.
2923 % o modulate: Define the percent change in brightness, saturation, and hue.
2925 % o exception: return any errors or warnings in this structure.
2929 static inline void ModulateHCL(const double percent_hue,
2930 const double percent_chroma,const double percent_luma,double *red,
2931 double *green,double *blue)
2939 Increase or decrease color luma, chroma, or hue.
2941 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2942 hue+=0.5*(0.01*percent_hue-1.0);
2947 chroma*=0.01*percent_chroma;
2948 luma*=0.01*percent_luma;
2949 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2952 static inline void ModulateHCLp(const double percent_hue,
2953 const double percent_chroma,const double percent_luma,double *red,
2954 double *green,double *blue)
2962 Increase or decrease color luma, chroma, or hue.
2964 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2965 hue+=0.5*(0.01*percent_hue-1.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+=0.5*(0.01*percent_hue-1.0);
2993 saturation*=0.01*percent_saturation;
2994 brightness*=0.01*percent_brightness;
2995 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2998 static inline void ModulateHSI(const double percent_hue,
2999 const double percent_saturation,const double percent_intensity,double *red,
3000 double *green,double *blue)
3008 Increase or decrease color intensity, saturation, or hue.
3010 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3011 hue+=0.5*(0.01*percent_hue-1.0);
3016 saturation*=0.01*percent_saturation;
3017 intensity*=0.01*percent_intensity;
3018 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3021 static inline void ModulateHSL(const double percent_hue,
3022 const double percent_saturation,const double percent_lightness,double *red,
3023 double *green,double *blue)
3031 Increase or decrease color lightness, saturation, or hue.
3033 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3034 hue+=0.5*(0.01*percent_hue-1.0);
3039 saturation*=0.01*percent_saturation;
3040 lightness*=0.01*percent_lightness;
3041 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3044 static inline void ModulateHSV(const double percent_hue,
3045 const double percent_saturation,const double percent_value,double *red,
3046 double *green,double *blue)
3054 Increase or decrease color value, saturation, or hue.
3056 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3057 hue+=0.5*(0.01*percent_hue-1.0);
3062 saturation*=0.01*percent_saturation;
3063 value*=0.01*percent_value;
3064 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3067 static inline void ModulateHWB(const double percent_hue,
3068 const double percent_whiteness,const double percent_blackness,double *red,
3069 double *green,double *blue)
3077 Increase or decrease color blackness, whiteness, or hue.
3079 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3080 hue+=0.5*(0.01*percent_hue-1.0);
3085 blackness*=0.01*percent_blackness;
3086 whiteness*=0.01*percent_whiteness;
3087 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3090 static inline void ModulateLCHab(const double percent_luma,
3091 const double percent_chroma,const double percent_hue,double *red,
3092 double *green,double *blue)
3100 Increase or decrease color luma, chroma, or hue.
3102 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3103 luma*=0.01*percent_luma;
3104 chroma*=0.01*percent_chroma;
3105 hue+=0.5*(0.01*percent_hue-1.0);
3110 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3113 static inline void ModulateLCHuv(const double percent_luma,
3114 const double percent_chroma,const double percent_hue,double *red,
3115 double *green,double *blue)
3123 Increase or decrease color luma, chroma, or hue.
3125 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
3126 luma*=0.01*percent_luma;
3127 chroma*=0.01*percent_chroma;
3128 hue+=0.5*(0.01*percent_hue-1.0);
3133 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
3136 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3137 ExceptionInfo *exception)
3139 #define ModulateImageTag "Modulate/Image"
3174 Initialize modulate table.
3176 assert(image != (Image *) NULL);
3177 assert(image->signature == MagickCoreSignature);
3178 if (image->debug != MagickFalse)
3179 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3180 if (modulate == (char *) NULL)
3181 return(MagickFalse);
3182 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3183 (void) SetImageColorspace(image,sRGBColorspace,exception);
3184 flags=ParseGeometry(modulate,&geometry_info);
3185 percent_brightness=geometry_info.rho;
3186 percent_saturation=geometry_info.sigma;
3187 if ((flags & SigmaValue) == 0)
3188 percent_saturation=100.0;
3189 percent_hue=geometry_info.xi;
3190 if ((flags & XiValue) == 0)
3192 colorspace=UndefinedColorspace;
3193 artifact=GetImageArtifact(image,"modulate:colorspace");
3194 if (artifact != (const char *) NULL)
3195 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3196 MagickFalse,artifact);
3197 if (image->storage_class == PseudoClass)
3198 for (i=0; i < (ssize_t) image->colors; i++)
3206 Modulate image colormap.
3208 red=(double) image->colormap[i].red;
3209 green=(double) image->colormap[i].green;
3210 blue=(double) image->colormap[i].blue;
3215 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3219 case HCLpColorspace:
3221 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3227 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3233 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3240 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3246 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3252 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3257 case LCHabColorspace:
3259 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3263 case LCHuvColorspace:
3265 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3270 image->colormap[i].red=red;
3271 image->colormap[i].green=green;
3272 image->colormap[i].blue=blue;
3277 if(AccelerateModulateImage(image,percent_brightness,percent_hue,
3278 percent_saturation,colorspace,exception) != MagickFalse)
3282 image_view=AcquireAuthenticCacheView(image,exception);
3283 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3284 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3285 magick_threads(image,image,image->rows,1)
3287 for (y=0; y < (ssize_t) image->rows; y++)
3295 if (status == MagickFalse)
3297 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3298 if (q == (Quantum *) NULL)
3303 for (x=0; x < (ssize_t) image->columns; x++)
3310 red=(double) GetPixelRed(image,q);
3311 green=(double) GetPixelGreen(image,q);
3312 blue=(double) GetPixelBlue(image,q);
3317 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3321 case HCLpColorspace:
3323 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3329 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3336 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3342 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3348 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3352 case LCHabColorspace:
3354 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3359 case LCHuvColorspace:
3361 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3366 SetPixelRed(image,ClampToQuantum(red),q);
3367 SetPixelGreen(image,ClampToQuantum(green),q);
3368 SetPixelBlue(image,ClampToQuantum(blue),q);
3369 q+=GetPixelChannels(image);
3371 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3373 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3378 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3379 #pragma omp critical (MagickCore_ModulateImage)
3381 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3382 if (proceed == MagickFalse)
3386 image_view=DestroyCacheView(image_view);
3391 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3395 % N e g a t e I m a g e %
3399 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3401 % NegateImage() negates the colors in the reference image. The grayscale
3402 % option means that only grayscale values within the image are negated.
3404 % The format of the NegateImage method is:
3406 % MagickBooleanType NegateImage(Image *image,
3407 % const MagickBooleanType grayscale,ExceptionInfo *exception)
3409 % A description of each parameter follows:
3411 % o image: the image.
3413 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3415 % o exception: return any errors or warnings in this structure.
3418 MagickExport MagickBooleanType NegateImage(Image *image,
3419 const MagickBooleanType grayscale,ExceptionInfo *exception)
3421 #define NegateImageTag "Negate/Image"
3438 assert(image != (Image *) NULL);
3439 assert(image->signature == MagickCoreSignature);
3440 if (image->debug != MagickFalse)
3441 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3442 if (image->storage_class == PseudoClass)
3443 for (i=0; i < (ssize_t) image->colors; i++)
3448 if( grayscale != MagickFalse )
3449 if ((image->colormap[i].red != image->colormap[i].green) ||
3450 (image->colormap[i].green != image->colormap[i].blue))
3452 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3453 image->colormap[i].red=QuantumRange-image->colormap[i].red;
3454 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3455 image->colormap[i].green=QuantumRange-image->colormap[i].green;
3456 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3457 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
3464 image_view=AcquireAuthenticCacheView(image,exception);
3465 if( grayscale != MagickFalse )
3467 for (y=0; y < (ssize_t) image->rows; y++)
3478 if (status == MagickFalse)
3480 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3482 if (q == (Quantum *) NULL)
3487 for (x=0; x < (ssize_t) image->columns; x++)
3492 if ((GetPixelReadMask(image,q) == 0) ||
3493 IsPixelGray(image,q) != MagickFalse)
3495 q+=GetPixelChannels(image);
3498 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3500 PixelChannel channel=GetPixelChannelChannel(image,j);
3501 PixelTrait traits=GetPixelChannelTraits(image,channel);
3502 if ((traits & UpdatePixelTrait) == 0)
3504 q[j]=QuantumRange-q[j];
3506 q+=GetPixelChannels(image);
3508 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3509 if (sync == MagickFalse)
3511 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3516 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3517 #pragma omp critical (MagickCore_NegateImage)
3519 proceed=SetImageProgress(image,NegateImageTag,progress++,
3521 if (proceed == MagickFalse)
3525 image_view=DestroyCacheView(image_view);
3531 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3532 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3533 magick_threads(image,image,image->rows,1)
3535 for (y=0; y < (ssize_t) image->rows; y++)
3543 if (status == MagickFalse)
3545 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3546 if (q == (Quantum *) NULL)
3551 for (x=0; x < (ssize_t) image->columns; x++)
3556 if (GetPixelReadMask(image,q) == 0)
3558 q+=GetPixelChannels(image);
3561 for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3563 PixelChannel channel=GetPixelChannelChannel(image,j);
3564 PixelTrait traits=GetPixelChannelTraits(image,channel);
3565 if ((traits & UpdatePixelTrait) == 0)
3567 q[j]=QuantumRange-q[j];
3569 q+=GetPixelChannels(image);
3571 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3573 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3578 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3579 #pragma omp critical (MagickCore_NegateImage)
3581 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3582 if (proceed == MagickFalse)
3586 image_view=DestroyCacheView(image_view);
3591 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3595 % N o r m a l i z e I m a g e %
3599 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3601 % The NormalizeImage() method enhances the contrast of a color image by
3602 % mapping the darkest 2 percent of all pixel to black and the brightest
3603 % 1 percent to white.
3605 % The format of the NormalizeImage method is:
3607 % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
3609 % A description of each parameter follows:
3611 % o image: the image.
3613 % o exception: return any errors or warnings in this structure.
3616 MagickExport MagickBooleanType NormalizeImage(Image *image,
3617 ExceptionInfo *exception)
3623 black_point=(double) image->columns*image->rows*0.0015;
3624 white_point=(double) image->columns*image->rows*0.9995;
3625 return(ContrastStretchImage(image,black_point,white_point,exception));
3629 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3633 % S i g m o i d a l C o n t r a s t I m a g e %
3637 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3639 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3640 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3641 % sigmoidal transfer function without saturating highlights or shadows.
3642 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3643 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3644 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3645 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3648 % The format of the SigmoidalContrastImage method is:
3650 % MagickBooleanType SigmoidalContrastImage(Image *image,
3651 % const MagickBooleanType sharpen,const char *levels,
3652 % ExceptionInfo *exception)
3654 % A description of each parameter follows:
3656 % o image: the image.
3658 % o sharpen: Increase or decrease image contrast.
3660 % o contrast: strength of the contrast, the larger the number the more
3661 % 'threshold-like' it becomes.
3663 % o midpoint: midpoint of the function as a color value 0 to QuantumRange.
3665 % o exception: return any errors or warnings in this structure.
3670 ImageMagick 6 has a version of this function which uses LUTs.
3674 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3677 The first version, based on the hyperbolic tangent tanh, when combined with
3678 the scaling step, is an exact arithmetic clone of the the sigmoid function
3679 based on the logistic curve. The equivalence is based on the identity
3681 1/(1+exp(-t)) = (1+tanh(t/2))/2
3683 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3684 scaled sigmoidal derivation is invariant under affine transformations of
3687 The tanh version is almost certainly more accurate and cheaper. The 0.5
3688 factor in the argument is to clone the legacy ImageMagick behavior. The
3689 reason for making the define depend on atanh even though it only uses tanh
3690 has to do with the construction of the inverse of the scaled sigmoidal.
3692 #if defined(MAGICKCORE_HAVE_ATANH)
3693 #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
3695 #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
3698 Scaled sigmoidal function:
3700 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3701 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
3703 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3704 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3705 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
3706 zero. This is fixed below by exiting immediately when contrast is small,
3707 leaving the image (or colormap) unmodified. This appears to be safe because
3708 the series expansion of the logistic sigmoidal function around x=b is
3712 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
3714 #define ScaledSigmoidal(a,b,x) ( \
3715 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
3716 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
3718 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3719 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3720 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3721 when creating a LUT from in gamut values, hence the branching. In
3722 addition, HDRI may have out of gamut values.
3723 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
3724 It is only a right inverse. This is unavoidable.
3726 static inline double InverseScaledSigmoidal(const double a,const double b,
3729 const double sig0=Sigmoidal(a,b,0.0);
3730 const double sig1=Sigmoidal(a,b,1.0);
3731 const double argument=(sig1-sig0)*x+sig0;
3732 const double clamped=
3734 #if defined(MAGICKCORE_HAVE_ATANH)
3735 argument < -1+MagickEpsilon
3739 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3741 return(b+(2.0/a)*atanh(clamped));
3743 argument < MagickEpsilon
3747 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3749 return(b-log(1.0/clamped-1.0)/a);
3753 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3754 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3755 ExceptionInfo *exception)
3757 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3758 #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
3759 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3760 #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
3761 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
3778 assert(image != (Image *) NULL);
3779 assert(image->signature == MagickCoreSignature);
3780 if (image->debug != MagickFalse)
3781 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3783 Side effect: may clamp values unless contrast<MagickEpsilon, in which
3784 case nothing is done.
3786 if (contrast < MagickEpsilon)
3789 Sigmoidal-contrast enhance colormap.
3791 if (image->storage_class == PseudoClass)
3796 if( sharpen != MagickFalse )
3797 for (i=0; i < (ssize_t) image->colors; i++)
3799 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3800 image->colormap[i].red=(MagickRealType) ScaledSig(
3801 image->colormap[i].red);
3802 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3803 image->colormap[i].green=(MagickRealType) ScaledSig(
3804 image->colormap[i].green);
3805 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3806 image->colormap[i].blue=(MagickRealType) ScaledSig(
3807 image->colormap[i].blue);
3808 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3809 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3810 image->colormap[i].alpha);
3813 for (i=0; i < (ssize_t) image->colors; i++)
3815 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3816 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3817 image->colormap[i].red);
3818 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3819 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3820 image->colormap[i].green);
3821 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3822 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3823 image->colormap[i].blue);
3824 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3825 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3826 image->colormap[i].alpha);
3830 Sigmoidal-contrast enhance image.
3834 image_view=AcquireAuthenticCacheView(image,exception);
3835 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3836 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3837 magick_threads(image,image,image->rows,1)
3839 for (y=0; y < (ssize_t) image->rows; y++)
3847 if (status == MagickFalse)
3849 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3850 if (q == (Quantum *) NULL)
3855 for (x=0; x < (ssize_t) image->columns; x++)
3860 if (GetPixelReadMask(image,q) == 0)
3862 q+=GetPixelChannels(image);
3865 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3867 PixelChannel channel=GetPixelChannelChannel(image,i);
3868 PixelTrait traits=GetPixelChannelTraits(image,channel);
3869 if ((traits & UpdatePixelTrait) == 0)
3871 if( sharpen != MagickFalse )
3872 q[i]=ScaledSig(q[i]);
3874 q[i]=InverseScaledSig(q[i]);
3876 q+=GetPixelChannels(image);
3878 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3880 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3885 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3886 #pragma omp critical (MagickCore_SigmoidalContrastImage)
3888 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3890 if (proceed == MagickFalse)
3894 image_view=DestroyCacheView(image_view);