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-2011 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/cache.h"
46 #include "MagickCore/cache-view.h"
47 #include "MagickCore/color.h"
48 #include "MagickCore/color-private.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/composite-private.h"
51 #include "MagickCore/enhance.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/fx.h"
55 #include "MagickCore/gem.h"
56 #include "MagickCore/geometry.h"
57 #include "MagickCore/histogram.h"
58 #include "MagickCore/image.h"
59 #include "MagickCore/image-private.h"
60 #include "MagickCore/memory_.h"
61 #include "MagickCore/monitor.h"
62 #include "MagickCore/monitor-private.h"
63 #include "MagickCore/option.h"
64 #include "MagickCore/pixel-accessor.h"
65 #include "MagickCore/quantum.h"
66 #include "MagickCore/quantum-private.h"
67 #include "MagickCore/resample.h"
68 #include "MagickCore/resample-private.h"
69 #include "MagickCore/statistic.h"
70 #include "MagickCore/string_.h"
71 #include "MagickCore/string-private.h"
72 #include "MagickCore/thread-private.h"
73 #include "MagickCore/token.h"
74 #include "MagickCore/xml-tree.h"
77 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81 % A u t o G a m m a I m a g e %
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 % AutoGammaImage() extract the 'mean' from the image and adjust the image
88 % to try make set its gamma appropriatally.
90 % The format of the AutoGammaImage method is:
92 % MagickBooleanType AutoGammaImage(Image *image)
94 % A description of each parameter follows:
96 % o image: The image to auto-level
99 MagickExport MagickBooleanType AutoGammaImage(Image *image)
111 if (image->sync != MagickFalse)
114 Apply gamma correction equally accross all given channels
116 (void) GetImageChannelMean(image,DefaultChannels,&mean,&sans,
118 gamma=log(mean*QuantumScale)/log_mean;
119 return(LevelImageChannel(image,DefaultChannels,0.0,(double) QuantumRange,gamma));
122 Auto-gamma each channel separately.
125 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
127 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
129 gamma=log(mean*QuantumScale)/log_mean;
130 status=status && LevelImageChannel(image,RedChannel,0.0,(double)
133 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
135 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
137 gamma=log(mean*QuantumScale)/log_mean;
138 status=status && LevelImageChannel(image,GreenChannel,0.0,(double)
141 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
143 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
145 gamma=log(mean*QuantumScale)/log_mean;
146 status=status && LevelImageChannel(image,BlueChannel,0.0,(double)
149 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
150 (image->colorspace == CMYKColorspace))
152 (void) GetImageChannelMean(image,BlackChannel,&mean,&sans,
154 gamma=log(mean*QuantumScale)/log_mean;
155 status=status && LevelImageChannel(image,BlackChannel,0.0,(double)
158 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
159 (image->matte == MagickTrue))
161 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
163 gamma=log(mean*QuantumScale)/log_mean;
164 status=status && LevelImageChannel(image,OpacityChannel,0.0,(double)
167 return(status != 0 ? MagickTrue : MagickFalse);
171 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
175 % A u t o L e v e l I m a g e %
179 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
181 % AutoLevelImage() adjusts the levels of a particular image channel by
182 % scaling the minimum and maximum values to the full quantum range.
184 % The format of the LevelImage method is:
186 % MagickBooleanType AutoLevelImage(Image *image)
188 % A description of each parameter follows:
190 % o image: The image to auto-level
193 MagickExport MagickBooleanType AutoLevelImage(Image *image)
195 return(MinMaxStretchImage(image,DefaultChannels,0.0,0.0));
199 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
203 % B r i g h t n e s s C o n t r a s t I m a g e %
207 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
209 % Use BrightnessContrastImage() to change the brightness and/or contrast of
210 % an image. It converts the brightness and contrast parameters into slope
211 % and intercept and calls a polynomical function to apply to the image.
213 % The format of the BrightnessContrastImage method is:
215 % MagickBooleanType BrightnessContrastImage(Image *image,
216 % const double brightness,const double contrast)
217 % MagickBooleanType BrightnessContrastImageChannel(Image *image,
218 % const ChannelType channel,const double brightness,
219 % const double contrast)
221 % A description of each parameter follows:
223 % o image: the image.
225 % o channel: the channel.
227 % o brightness: the brightness percent (-100 .. 100).
229 % o contrast: the contrast percent (-100 .. 100).
233 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
234 const double brightness,const double contrast)
239 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
244 MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
245 const ChannelType channel,const double brightness,const double contrast)
247 #define BrightnessContastImageTag "BrightnessContast/Image"
259 Compute slope and intercept.
261 assert(image != (Image *) NULL);
262 assert(image->signature == MagickSignature);
263 if (image->debug != MagickFalse)
264 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
266 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
269 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
270 coefficients[0]=slope;
271 coefficients[1]=intercept;
272 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
278 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282 % C o l o r D e c i s i o n L i s t I m a g e %
286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
288 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
289 % (CCC) file which solely contains one or more color corrections and applies
290 % the correction to the image. Here is a sample CCC file:
292 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
293 % <ColorCorrection id="cc03345">
295 % <Slope> 0.9 1.2 0.5 </Slope>
296 % <Offset> 0.4 -0.5 0.6 </Offset>
297 % <Power> 1.0 0.8 1.5 </Power>
300 % <Saturation> 0.85 </Saturation>
303 % </ColorCorrectionCollection>
305 % which includes the slop, offset, and power for each of the RGB channels
306 % as well as the saturation.
308 % The format of the ColorDecisionListImage method is:
310 % MagickBooleanType ColorDecisionListImage(Image *image,
311 % const char *color_correction_collection)
313 % A description of each parameter follows:
315 % o image: the image.
317 % o color_correction_collection: the color correction collection in XML.
320 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
321 const char *color_correction_collection)
323 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
325 typedef struct _Correction
333 typedef struct _ColorCorrection
348 token[MaxTextExtent];
382 Allocate and initialize cdl maps.
384 assert(image != (Image *) NULL);
385 assert(image->signature == MagickSignature);
386 if (image->debug != MagickFalse)
387 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
388 if (color_correction_collection == (const char *) NULL)
390 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
391 if (ccc == (XMLTreeInfo *) NULL)
393 cc=GetXMLTreeChild(ccc,"ColorCorrection");
394 if (cc == (XMLTreeInfo *) NULL)
396 ccc=DestroyXMLTree(ccc);
399 color_correction.red.slope=1.0;
400 color_correction.red.offset=0.0;
401 color_correction.red.power=1.0;
402 color_correction.green.slope=1.0;
403 color_correction.green.offset=0.0;
404 color_correction.green.power=1.0;
405 color_correction.blue.slope=1.0;
406 color_correction.blue.offset=0.0;
407 color_correction.blue.power=1.0;
408 color_correction.saturation=0.0;
409 sop=GetXMLTreeChild(cc,"SOPNode");
410 if (sop != (XMLTreeInfo *) NULL)
417 slope=GetXMLTreeChild(sop,"Slope");
418 if (slope != (XMLTreeInfo *) NULL)
420 content=GetXMLTreeContent(slope);
421 p=(const char *) content;
422 for (i=0; (*p != '\0') && (i < 3); i++)
424 GetMagickToken(p,&p,token);
426 GetMagickToken(p,&p,token);
431 color_correction.red.slope=InterpretLocaleValue(token,
437 color_correction.green.slope=InterpretLocaleValue(token,
443 color_correction.blue.slope=InterpretLocaleValue(token,
450 offset=GetXMLTreeChild(sop,"Offset");
451 if (offset != (XMLTreeInfo *) NULL)
453 content=GetXMLTreeContent(offset);
454 p=(const char *) content;
455 for (i=0; (*p != '\0') && (i < 3); i++)
457 GetMagickToken(p,&p,token);
459 GetMagickToken(p,&p,token);
464 color_correction.red.offset=InterpretLocaleValue(token,
470 color_correction.green.offset=InterpretLocaleValue(token,
476 color_correction.blue.offset=InterpretLocaleValue(token,
483 power=GetXMLTreeChild(sop,"Power");
484 if (power != (XMLTreeInfo *) NULL)
486 content=GetXMLTreeContent(power);
487 p=(const char *) content;
488 for (i=0; (*p != '\0') && (i < 3); i++)
490 GetMagickToken(p,&p,token);
492 GetMagickToken(p,&p,token);
497 color_correction.red.power=InterpretLocaleValue(token,
503 color_correction.green.power=InterpretLocaleValue(token,
509 color_correction.blue.power=InterpretLocaleValue(token,
517 sat=GetXMLTreeChild(cc,"SATNode");
518 if (sat != (XMLTreeInfo *) NULL)
523 saturation=GetXMLTreeChild(sat,"Saturation");
524 if (saturation != (XMLTreeInfo *) NULL)
526 content=GetXMLTreeContent(saturation);
527 p=(const char *) content;
528 GetMagickToken(p,&p,token);
529 color_correction.saturation=InterpretLocaleValue(token,
533 ccc=DestroyXMLTree(ccc);
534 if (image->debug != MagickFalse)
536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
537 " Color Correction Collection:");
538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
539 " color_correction.red.slope: %g",color_correction.red.slope);
540 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
541 " color_correction.red.offset: %g",color_correction.red.offset);
542 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
543 " color_correction.red.power: %g",color_correction.red.power);
544 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
545 " color_correction.green.slope: %g",color_correction.green.slope);
546 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
547 " color_correction.green.offset: %g",color_correction.green.offset);
548 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
549 " color_correction.green.power: %g",color_correction.green.power);
550 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
551 " color_correction.blue.slope: %g",color_correction.blue.slope);
552 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
553 " color_correction.blue.offset: %g",color_correction.blue.offset);
554 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
555 " color_correction.blue.power: %g",color_correction.blue.power);
556 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
557 " color_correction.saturation: %g",color_correction.saturation);
559 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
560 if (cdl_map == (PixelPacket *) NULL)
561 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
563 #if defined(MAGICKCORE_OPENMP_SUPPORT)
564 #pragma omp parallel for schedule(dynamic,4)
566 for (i=0; i <= (ssize_t) MaxMap; i++)
568 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
569 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
570 color_correction.red.offset,color_correction.red.power)))));
571 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
572 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
573 color_correction.green.offset,color_correction.green.power)))));
574 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
575 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
576 color_correction.blue.offset,color_correction.blue.power)))));
578 if (image->storage_class == PseudoClass)
581 Apply transfer function to colormap.
583 #if defined(MAGICKCORE_OPENMP_SUPPORT)
584 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
586 for (i=0; i < (ssize_t) image->colors; i++)
591 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
592 0.0722*image->colormap[i].blue;
593 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
594 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
595 image->colormap[i].green=ClampToQuantum(luma+
596 color_correction.saturation*cdl_map[ScaleQuantumToMap(
597 image->colormap[i].green)].green-luma);
598 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
599 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
603 Apply transfer function to image.
607 exception=(&image->exception);
608 image_view=AcquireCacheView(image);
609 #if defined(MAGICKCORE_OPENMP_SUPPORT)
610 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
612 for (y=0; y < (ssize_t) image->rows; y++)
623 if (status == MagickFalse)
625 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
626 if (q == (const Quantum *) NULL)
631 for (x=0; x < (ssize_t) image->columns; x++)
633 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
634 GetPixelBlue(image,q);
635 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
636 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
637 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
638 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
639 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
640 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
641 q+=GetPixelChannels(image);
643 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
645 if (image->progress_monitor != (MagickProgressMonitor) NULL)
650 #if defined(MAGICKCORE_OPENMP_SUPPORT)
651 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
653 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
654 progress++,image->rows);
655 if (proceed == MagickFalse)
659 image_view=DestroyCacheView(image_view);
660 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
665 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
669 % C l u t I m a g e %
673 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
675 % ClutImage() replaces each color value in the given image, by using it as an
676 % index to lookup a replacement color value in a Color Look UP Table in the
677 % form of an image. The values are extracted along a diagonal of the CLUT
678 % image so either a horizontal or vertial gradient image can be used.
680 % Typically this is used to either re-color a gray-scale image according to a
681 % color gradient in the CLUT image, or to perform a freeform histogram
682 % (level) adjustment according to the (typically gray-scale) gradient in the
685 % When the 'channel' mask includes the matte/alpha transparency channel but
686 % one image has no such channel it is assumed that that image is a simple
687 % gray-scale image that will effect the alpha channel values, either for
688 % gray-scale coloring (with transparent or semi-transparent colors), or
689 % a histogram adjustment of existing alpha channel values. If both images
690 % have matte channels, direct and normal indexing is applied, which is rarely
693 % The format of the ClutImage method is:
695 % MagickBooleanType ClutImage(Image *image,Image *clut_image)
696 % MagickBooleanType ClutImageChannel(Image *image,
697 % const ChannelType channel,Image *clut_image)
699 % A description of each parameter follows:
701 % o image: the image, which is replaced by indexed CLUT values
703 % o clut_image: the color lookup table image for replacement color values.
705 % o channel: the channel.
709 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
711 return(ClutImageChannel(image,DefaultChannels,clut_image));
714 MagickExport MagickBooleanType ClutImageChannel(Image *image,
715 const ChannelType channel,const Image *clut_image)
717 #define ClampAlphaPixelComponent(pixel) ClampToQuantum((pixel)->alpha)
718 #define ClampBlackPixelComponent(pixel) ClampToQuantum((pixel)->black)
719 #define ClampBluePixelComponent(pixel) ClampToQuantum((pixel)->blue)
720 #define ClampGreenPixelComponent(pixel) ClampToQuantum((pixel)->green)
721 #define ClampRedPixelComponent(pixel) ClampToQuantum((pixel)->red)
722 #define ClutImageTag "Clut/Image"
747 assert(image != (Image *) NULL);
748 assert(image->signature == MagickSignature);
749 if (image->debug != MagickFalse)
750 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
751 assert(clut_image != (Image *) NULL);
752 assert(clut_image->signature == MagickSignature);
753 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
755 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
757 if (clut_map == (PixelInfo *) NULL)
758 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
765 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
766 exception=(&image->exception);
767 clut_view=AcquireCacheView(clut_image);
768 #if defined(MAGICKCORE_OPENMP_SUPPORT)
769 #pragma omp parallel for schedule(dynamic,4)
771 for (i=0; i <= (ssize_t) MaxMap; i++)
773 GetPixelInfo(clut_image,clut_map+i);
774 (void) InterpolatePixelInfo(clut_image,clut_view,
775 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
776 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
778 clut_view=DestroyCacheView(clut_view);
779 image_view=AcquireCacheView(image);
780 #if defined(MAGICKCORE_OPENMP_SUPPORT)
781 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
783 for (y=0; y < (ssize_t) image->rows; y++)
794 if (status == MagickFalse)
796 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
797 if (q == (const Quantum *) NULL)
802 GetPixelInfo(image,&pixel);
803 for (x=0; x < (ssize_t) image->columns; x++)
805 SetPixelInfo(image,q,&pixel);
806 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
807 SetPixelRed(image,ClampRedPixelComponent(clut_map+
808 ScaleQuantumToMap(GetPixelRed(image,q))),q);
809 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
810 SetPixelGreen(image,ClampGreenPixelComponent(clut_map+
811 ScaleQuantumToMap(GetPixelGreen(image,q))),q);
812 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
813 SetPixelBlue(image,ClampBluePixelComponent(clut_map+
814 ScaleQuantumToMap(GetPixelBlue(image,q))),q);
815 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
816 (image->colorspace == CMYKColorspace))
817 SetPixelBlack(image,ClampBlackPixelComponent(clut_map+
818 ScaleQuantumToMap(GetPixelBlack(image,q))),q);
819 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
821 if (clut_image->matte == MagickFalse)
822 SetPixelAlpha(image,GetPixelInfoIntensity(clut_map+
823 ScaleQuantumToMap((Quantum) GetPixelAlpha(image,q))),q);
825 if (image->matte == MagickFalse)
826 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
827 ScaleQuantumToMap((Quantum) GetPixelInfoIntensity(&pixel))),q);
829 SetPixelAlpha(image,ClampAlphaPixelComponent(clut_map+
830 ScaleQuantumToMap(GetPixelAlpha(image,q))),q);
832 q+=GetPixelChannels(image);
834 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
836 if (image->progress_monitor != (MagickProgressMonitor) NULL)
841 #if defined(MAGICKCORE_OPENMP_SUPPORT)
842 #pragma omp critical (MagickCore_ClutImageChannel)
844 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
845 if (proceed == MagickFalse)
849 image_view=DestroyCacheView(image_view);
850 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
851 if ((clut_image->matte != MagickFalse) &&
852 ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0))
853 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
858 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
862 % C o n t r a s t I m a g e %
866 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
868 % ContrastImage() enhances the intensity differences between the lighter and
869 % darker elements of the image. Set sharpen to a MagickTrue to increase the
870 % image contrast otherwise the contrast is reduced.
872 % The format of the ContrastImage method is:
874 % MagickBooleanType ContrastImage(Image *image,
875 % const MagickBooleanType sharpen)
877 % A description of each parameter follows:
879 % o image: the image.
881 % o sharpen: Increase or decrease image contrast.
885 static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
893 Enhance contrast: dark color become darker, light color become lighter.
895 assert(red != (Quantum *) NULL);
896 assert(green != (Quantum *) NULL);
897 assert(blue != (Quantum *) NULL);
901 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
902 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
904 if (brightness > 1.0)
907 if (brightness < 0.0)
909 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
912 MagickExport MagickBooleanType ContrastImage(Image *image,
913 const MagickBooleanType sharpen)
915 #define ContrastImageTag "Contrast/Image"
938 assert(image != (Image *) NULL);
939 assert(image->signature == MagickSignature);
940 if (image->debug != MagickFalse)
941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
942 sign=sharpen != MagickFalse ? 1 : -1;
943 if (image->storage_class == PseudoClass)
946 Contrast enhance colormap.
948 for (i=0; i < (ssize_t) image->colors; i++)
949 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
950 &image->colormap[i].blue);
953 Contrast enhance image.
957 exception=(&image->exception);
958 image_view=AcquireCacheView(image);
959 #if defined(MAGICKCORE_OPENMP_SUPPORT)
960 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
962 for (y=0; y < (ssize_t) image->rows; y++)
975 if (status == MagickFalse)
977 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
978 if (q == (const Quantum *) NULL)
983 for (x=0; x < (ssize_t) image->columns; x++)
985 red=GetPixelRed(image,q);
986 green=GetPixelGreen(image,q);
987 blue=GetPixelBlue(image,q);
988 Contrast(sign,&red,&green,&blue);
989 SetPixelRed(image,red,q);
990 SetPixelGreen(image,green,q);
991 SetPixelBlue(image,blue,q);
992 q+=GetPixelChannels(image);
994 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
996 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1001 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1002 #pragma omp critical (MagickCore_ContrastImage)
1004 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1005 if (proceed == MagickFalse)
1009 image_view=DestroyCacheView(image_view);
1014 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1018 % C o n t r a s t S t r e t c h I m a g e %
1022 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1024 % The ContrastStretchImage() is a simple image enhancement technique that
1025 % attempts to improve the contrast in an image by `stretching' the range of
1026 % intensity values it contains to span a desired range of values. It differs
1027 % from the more sophisticated histogram equalization in that it can only
1028 % apply % a linear scaling function to the image pixel values. As a result
1029 % the `enhancement' is less harsh.
1031 % The format of the ContrastStretchImage method is:
1033 % MagickBooleanType ContrastStretchImage(Image *image,
1034 % const char *levels)
1035 % MagickBooleanType ContrastStretchImageChannel(Image *image,
1036 % const size_t channel,const double black_point,
1037 % const double white_point)
1039 % A description of each parameter follows:
1041 % o image: the image.
1043 % o channel: the channel.
1045 % o black_point: the black point.
1047 % o white_point: the white point.
1049 % o levels: Specify the levels where the black and white points have the
1050 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1054 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1073 if (levels == (char *) NULL)
1074 return(MagickFalse);
1075 flags=ParseGeometry(levels,&geometry_info);
1076 black_point=geometry_info.rho;
1077 white_point=(double) image->columns*image->rows;
1078 if ((flags & SigmaValue) != 0)
1079 white_point=geometry_info.sigma;
1080 if ((flags & PercentValue) != 0)
1082 black_point*=(double) QuantumRange/100.0;
1083 white_point*=(double) QuantumRange/100.0;
1085 if ((flags & SigmaValue) == 0)
1086 white_point=(double) image->columns*image->rows-black_point;
1087 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1092 MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1093 const ChannelType channel,const double black_point,const double white_point)
1095 #define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1096 #define ContrastStretchImageTag "ContrastStretch/Image"
1126 Allocate histogram and stretch map.
1128 assert(image != (Image *) NULL);
1129 assert(image->signature == MagickSignature);
1130 if (image->debug != MagickFalse)
1131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1132 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
1133 sizeof(*histogram));
1134 stretch_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
1135 sizeof(*stretch_map));
1136 if ((histogram == (PixelInfo *) NULL) ||
1137 (stretch_map == (PixelInfo *) NULL))
1138 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1144 exception=(&image->exception);
1145 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1146 image_view=AcquireCacheView(image);
1147 for (y=0; y < (ssize_t) image->rows; y++)
1149 register const Quantum
1155 if (status == MagickFalse)
1157 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1158 if (p == (const Quantum *) NULL)
1163 if (channel == DefaultChannels)
1164 for (x=0; x < (ssize_t) image->columns; x++)
1169 intensity=GetPixelIntensity(image,p);
1170 histogram[ScaleQuantumToMap(intensity)].red++;
1171 histogram[ScaleQuantumToMap(intensity)].green++;
1172 histogram[ScaleQuantumToMap(intensity)].blue++;
1173 histogram[ScaleQuantumToMap(intensity)].black++;
1174 p+=GetPixelChannels(image);
1177 for (x=0; x < (ssize_t) image->columns; x++)
1179 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1180 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
1181 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1182 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
1183 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1184 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
1185 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1186 (image->colorspace == CMYKColorspace))
1187 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
1188 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1189 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1190 p+=GetPixelChannels(image);
1194 Find the histogram boundaries by locating the black/white levels.
1197 white.red=MaxRange(QuantumRange);
1198 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1201 for (i=0; i <= (ssize_t) MaxMap; i++)
1203 intensity+=histogram[i].red;
1204 if (intensity > black_point)
1207 black.red=(MagickRealType) i;
1209 for (i=(ssize_t) MaxMap; i != 0; i--)
1211 intensity+=histogram[i].red;
1212 if (intensity > ((double) image->columns*image->rows-white_point))
1215 white.red=(MagickRealType) i;
1218 white.green=MaxRange(QuantumRange);
1219 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1222 for (i=0; i <= (ssize_t) MaxMap; i++)
1224 intensity+=histogram[i].green;
1225 if (intensity > black_point)
1228 black.green=(MagickRealType) i;
1230 for (i=(ssize_t) MaxMap; i != 0; i--)
1232 intensity+=histogram[i].green;
1233 if (intensity > ((double) image->columns*image->rows-white_point))
1236 white.green=(MagickRealType) i;
1239 white.blue=MaxRange(QuantumRange);
1240 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1243 for (i=0; i <= (ssize_t) MaxMap; i++)
1245 intensity+=histogram[i].blue;
1246 if (intensity > black_point)
1249 black.blue=(MagickRealType) i;
1251 for (i=(ssize_t) MaxMap; i != 0; i--)
1253 intensity+=histogram[i].blue;
1254 if (intensity > ((double) image->columns*image->rows-white_point))
1257 white.blue=(MagickRealType) i;
1260 white.alpha=MaxRange(QuantumRange);
1261 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1264 for (i=0; i <= (ssize_t) MaxMap; i++)
1266 intensity+=histogram[i].alpha;
1267 if (intensity > black_point)
1270 black.alpha=(MagickRealType) i;
1272 for (i=(ssize_t) MaxMap; i != 0; i--)
1274 intensity+=histogram[i].alpha;
1275 if (intensity > ((double) image->columns*image->rows-white_point))
1278 white.alpha=(MagickRealType) i;
1281 white.black=MaxRange(QuantumRange);
1282 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) && (image->colorspace == CMYKColorspace))
1285 for (i=0; i <= (ssize_t) MaxMap; i++)
1287 intensity+=histogram[i].black;
1288 if (intensity > black_point)
1291 black.black=(MagickRealType) i;
1293 for (i=(ssize_t) MaxMap; i != 0; i--)
1295 intensity+=histogram[i].black;
1296 if (intensity > ((double) image->columns*image->rows-white_point))
1299 white.black=(MagickRealType) i;
1301 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1303 Stretch the histogram to create the stretched image mapping.
1305 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
1306 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1307 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1309 for (i=0; i <= (ssize_t) MaxMap; i++)
1311 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1313 if (i < (ssize_t) black.red)
1314 stretch_map[i].red=0.0;
1316 if (i > (ssize_t) white.red)
1317 stretch_map[i].red=(MagickRealType) QuantumRange;
1319 if (black.red != white.red)
1320 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1321 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1323 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1325 if (i < (ssize_t) black.green)
1326 stretch_map[i].green=0.0;
1328 if (i > (ssize_t) white.green)
1329 stretch_map[i].green=(MagickRealType) QuantumRange;
1331 if (black.green != white.green)
1332 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1333 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1336 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1338 if (i < (ssize_t) black.blue)
1339 stretch_map[i].blue=0.0;
1341 if (i > (ssize_t) white.blue)
1342 stretch_map[i].blue=(MagickRealType) QuantumRange;
1344 if (black.blue != white.blue)
1345 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1346 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1349 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1351 if (i < (ssize_t) black.alpha)
1352 stretch_map[i].alpha=0.0;
1354 if (i > (ssize_t) white.alpha)
1355 stretch_map[i].alpha=(MagickRealType) QuantumRange;
1357 if (black.alpha != white.alpha)
1358 stretch_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1359 (MagickRealType) (MaxMap*(i-black.alpha)/(white.alpha-
1362 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1363 (image->colorspace == CMYKColorspace))
1365 if (i < (ssize_t) black.black)
1366 stretch_map[i].black=0.0;
1368 if (i > (ssize_t) white.black)
1369 stretch_map[i].black=(MagickRealType) QuantumRange;
1371 if (black.black != white.black)
1372 stretch_map[i].black=(MagickRealType) ScaleMapToQuantum(
1373 (MagickRealType) (MaxMap*(i-black.black)/(white.black-
1380 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) || (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1381 (image->colorspace == CMYKColorspace)))
1382 image->storage_class=DirectClass;
1383 if (image->storage_class == PseudoClass)
1388 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1389 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1391 for (i=0; i < (ssize_t) image->colors; i++)
1393 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1395 if (black.red != white.red)
1396 image->colormap[i].red=ClampToQuantum(stretch_map[
1397 ScaleQuantumToMap(image->colormap[i].red)].red);
1399 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1401 if (black.green != white.green)
1402 image->colormap[i].green=ClampToQuantum(stretch_map[
1403 ScaleQuantumToMap(image->colormap[i].green)].green);
1405 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1407 if (black.blue != white.blue)
1408 image->colormap[i].blue=ClampToQuantum(stretch_map[
1409 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1411 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1413 if (black.alpha != white.alpha)
1414 image->colormap[i].alpha=ClampToQuantum(stretch_map[
1415 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
1424 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1425 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1427 for (y=0; y < (ssize_t) image->rows; y++)
1435 if (status == MagickFalse)
1437 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1438 if (q == (const Quantum *) NULL)
1443 for (x=0; x < (ssize_t) image->columns; x++)
1445 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1447 if (black.red != white.red)
1448 SetPixelRed(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1449 GetPixelRed(image,q))].red),q);
1451 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1453 if (black.green != white.green)
1454 SetPixelGreen(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1455 GetPixelGreen(image,q))].green),q);
1457 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1459 if (black.blue != white.blue)
1460 SetPixelBlue(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1461 GetPixelBlue(image,q))].blue),q);
1463 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1464 (image->colorspace == CMYKColorspace))
1466 if (black.black != white.black)
1467 SetPixelBlack(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1468 GetPixelBlack(image,q))].black),q);
1470 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1472 if (black.alpha != white.alpha)
1473 SetPixelAlpha(image,ClampToQuantum(stretch_map[ScaleQuantumToMap(
1474 GetPixelAlpha(image,q))].alpha),q);
1476 q+=GetPixelChannels(image);
1478 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1480 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1485 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1486 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1488 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1490 if (proceed == MagickFalse)
1494 image_view=DestroyCacheView(image_view);
1495 stretch_map=(PixelInfo *) RelinquishMagickMemory(stretch_map);
1500 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1504 % E n h a n c e I m a g e %
1508 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1510 % EnhanceImage() applies a digital filter that improves the quality of a
1513 % The format of the EnhanceImage method is:
1515 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1517 % A description of each parameter follows:
1519 % o image: the image.
1521 % o exception: return any errors or warnings in this structure.
1524 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1526 #define Enhance(weight) \
1527 mean=((MagickRealType) GetPixelRed(image,r)+pixel.red)/2; \
1528 distance=(MagickRealType) GetPixelRed(image,r)-(MagickRealType) pixel.red; \
1529 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1530 mean)*distance*distance; \
1531 mean=((MagickRealType) GetPixelGreen(image,r)+pixel.green)/2; \
1532 distance=(MagickRealType) GetPixelGreen(image,r)- \
1533 (MagickRealType) pixel.green; \
1534 distance_squared+=4.0*distance*distance; \
1535 mean=((MagickRealType) GetPixelBlue(image,r)+pixel.blue)/2; \
1536 distance=(MagickRealType) GetPixelBlue(image,r)- \
1537 (MagickRealType) pixel.blue; \
1538 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1539 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1540 mean=((MagickRealType) GetPixelAlpha(image,r)+pixel.alpha)/2; \
1541 distance=(MagickRealType) GetPixelAlpha(image,r)-(MagickRealType) pixel.alpha; \
1542 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1543 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1544 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1545 QuantumRange/25.0f)) \
1547 aggregate.red+=(weight)*GetPixelRed(image,r); \
1548 aggregate.green+=(weight)*GetPixelGreen(image,r); \
1549 aggregate.blue+=(weight)*GetPixelBlue(image,r); \
1550 aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
1551 total_weight+=(weight); \
1554 #define EnhanceImageTag "Enhance/Image"
1576 Initialize enhanced image attributes.
1578 assert(image != (const Image *) NULL);
1579 assert(image->signature == MagickSignature);
1580 if (image->debug != MagickFalse)
1581 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1582 assert(exception != (ExceptionInfo *) NULL);
1583 assert(exception->signature == MagickSignature);
1584 if ((image->columns < 5) || (image->rows < 5))
1585 return((Image *) NULL);
1586 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1588 if (enhance_image == (Image *) NULL)
1589 return((Image *) NULL);
1590 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1592 InheritException(exception,&enhance_image->exception);
1593 enhance_image=DestroyImage(enhance_image);
1594 return((Image *) NULL);
1601 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1602 image_view=AcquireCacheView(image);
1603 enhance_view=AcquireCacheView(enhance_image);
1604 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1607 for (y=0; y < (ssize_t) image->rows; y++)
1609 register const Quantum
1619 Read another scan line.
1621 if (status == MagickFalse)
1623 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1624 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1626 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1631 for (x=0; x < (ssize_t) image->columns; x++)
1645 register const Quantum
1649 Compute weighted average of target pixel color components.
1653 r=p+2*(image->columns+4)+2;
1654 GetPixelPacket(image,r,&pixel);
1656 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1657 r=p+(image->columns+4);
1658 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1659 r=p+2*(image->columns+4);
1660 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1661 r=p+3*(image->columns+4);
1662 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1663 r=p+4*(image->columns+4);
1664 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1665 SetPixelRed(enhance_image,(Quantum) ((aggregate.red+
1666 (total_weight/2)-1)/total_weight),q);
1667 SetPixelGreen(enhance_image,(Quantum) ((aggregate.green+
1668 (total_weight/2)-1)/total_weight),q);
1669 SetPixelBlue(enhance_image,(Quantum) ((aggregate.blue+
1670 (total_weight/2)-1)/total_weight),q);
1671 SetPixelAlpha(enhance_image,(Quantum) ((aggregate.alpha+
1672 (total_weight/2)-1)/total_weight),q);
1673 p+=GetPixelChannels(image);
1674 q+=GetPixelChannels(enhance_image);
1676 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1678 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1683 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1684 #pragma omp critical (MagickCore_EnhanceImage)
1686 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1687 if (proceed == MagickFalse)
1691 enhance_view=DestroyCacheView(enhance_view);
1692 image_view=DestroyCacheView(image_view);
1693 return(enhance_image);
1697 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1701 % E q u a l i z e I m a g e %
1705 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1707 % EqualizeImage() applies a histogram equalization to the image.
1709 % The format of the EqualizeImage method is:
1711 % MagickBooleanType EqualizeImage(Image *image)
1712 % MagickBooleanType EqualizeImageChannel(Image *image,
1713 % const ChannelType channel)
1715 % A description of each parameter follows:
1717 % o image: the image.
1719 % o channel: the channel.
1723 MagickExport MagickBooleanType EqualizeImage(Image *image)
1725 return(EqualizeImageChannel(image,DefaultChannels));
1728 MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1729 const ChannelType channel)
1731 #define EqualizeImageTag "Equalize/Image"
1760 Allocate and initialize histogram arrays.
1762 assert(image != (Image *) NULL);
1763 assert(image->signature == MagickSignature);
1764 if (image->debug != MagickFalse)
1765 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1766 equalize_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
1767 sizeof(*equalize_map));
1768 histogram=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,
1769 sizeof(*histogram));
1770 map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1771 if ((equalize_map == (PixelInfo *) NULL) ||
1772 (histogram == (PixelInfo *) NULL) ||
1773 (map == (PixelInfo *) NULL))
1775 if (map != (PixelInfo *) NULL)
1776 map=(PixelInfo *) RelinquishMagickMemory(map);
1777 if (histogram != (PixelInfo *) NULL)
1778 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1779 if (equalize_map != (PixelInfo *) NULL)
1780 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
1781 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1787 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1788 exception=(&image->exception);
1789 for (y=0; y < (ssize_t) image->rows; y++)
1791 register const Quantum
1797 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1798 if (p == (const Quantum *) NULL)
1800 for (x=0; x < (ssize_t) image->columns; x++)
1802 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1803 histogram[ScaleQuantumToMap(GetPixelRed(image,p))].red++;
1804 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1805 histogram[ScaleQuantumToMap(GetPixelGreen(image,p))].green++;
1806 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1807 histogram[ScaleQuantumToMap(GetPixelBlue(image,p))].blue++;
1808 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1809 (image->colorspace == CMYKColorspace))
1810 histogram[ScaleQuantumToMap(GetPixelBlack(image,p))].black++;
1811 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1812 histogram[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha++;
1813 p+=GetPixelChannels(image);
1817 Integrate the histogram to get the equalization map.
1819 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1820 for (i=0; i <= (ssize_t) MaxMap; i++)
1822 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
1823 intensity.red+=histogram[i].red;
1824 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
1825 intensity.green+=histogram[i].green;
1826 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
1827 intensity.blue+=histogram[i].blue;
1828 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1829 (image->colorspace == CMYKColorspace))
1830 intensity.black+=histogram[i].black;
1831 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
1832 intensity.alpha+=histogram[i].alpha;
1836 white=map[(int) MaxMap];
1837 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
1838 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1841 for (i=0; i <= (ssize_t) MaxMap; i++)
1843 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1844 (white.red != black.red))
1845 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1846 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1847 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1848 (white.green != black.green))
1849 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1850 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1851 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1852 (white.blue != black.blue))
1853 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1854 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1855 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1856 (image->colorspace == CMYKColorspace)) &&
1857 (white.black != black.black))
1858 equalize_map[i].black=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].black-black.black))/(white.black-black.black)));
1860 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1861 (white.alpha != black.alpha))
1862 equalize_map[i].alpha=(MagickRealType) ScaleMapToQuantum(
1863 (MagickRealType) ((MaxMap*(map[i].alpha-black.alpha))/
1864 (white.alpha-black.alpha)));
1866 histogram=(PixelInfo *) RelinquishMagickMemory(histogram);
1867 map=(PixelInfo *) RelinquishMagickMemory(map);
1868 if (image->storage_class == PseudoClass)
1873 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1874 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1876 for (i=0; i < (ssize_t) image->colors; i++)
1878 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1879 (white.red != black.red))
1880 image->colormap[i].red=ClampToQuantum(equalize_map[
1881 ScaleQuantumToMap(image->colormap[i].red)].red);
1882 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1883 (white.green != black.green))
1884 image->colormap[i].green=ClampToQuantum(equalize_map[
1885 ScaleQuantumToMap(image->colormap[i].green)].green);
1886 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1887 (white.blue != black.blue))
1888 image->colormap[i].blue=ClampToQuantum(equalize_map[
1889 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1890 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1891 (white.alpha != black.alpha))
1892 image->colormap[i].alpha=ClampToQuantum(equalize_map[
1893 ScaleQuantumToMap(image->colormap[i].alpha)].alpha);
1901 exception=(&image->exception);
1902 image_view=AcquireCacheView(image);
1903 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1904 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1906 for (y=0; y < (ssize_t) image->rows; y++)
1914 if (status == MagickFalse)
1916 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1917 if (q == (const Quantum *) NULL)
1922 for (x=0; x < (ssize_t) image->columns; x++)
1924 if (((GetPixelRedTraits(image) & ActivePixelTrait) != 0) &&
1925 (white.red != black.red))
1926 SetPixelRed(image,ClampToQuantum(equalize_map[
1927 ScaleQuantumToMap(GetPixelRed(image,q))].red),q);
1928 if (((GetPixelGreenTraits(image) & ActivePixelTrait) != 0) &&
1929 (white.green != black.green))
1930 SetPixelGreen(image,ClampToQuantum(equalize_map[
1931 ScaleQuantumToMap(GetPixelGreen(image,q))].green),q);
1932 if (((GetPixelBlueTraits(image) & ActivePixelTrait) != 0) &&
1933 (white.blue != black.blue))
1934 SetPixelBlue(image,ClampToQuantum(equalize_map[
1935 ScaleQuantumToMap(GetPixelBlue(image,q))].blue),q);
1936 if ((((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
1937 (image->colorspace == CMYKColorspace)) &&
1938 (white.black != black.black))
1939 SetPixelBlack(image,ClampToQuantum(equalize_map[
1940 ScaleQuantumToMap(GetPixelBlack(image,q))].black),q);
1941 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
1942 (white.alpha != black.alpha))
1943 SetPixelAlpha(image,ClampToQuantum(equalize_map[
1944 ScaleQuantumToMap(GetPixelAlpha(image,q))].alpha),q);
1945 q+=GetPixelChannels(image);
1947 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1949 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1954 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1955 #pragma omp critical (MagickCore_EqualizeImageChannel)
1957 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1958 if (proceed == MagickFalse)
1962 image_view=DestroyCacheView(image_view);
1963 equalize_map=(PixelInfo *) RelinquishMagickMemory(equalize_map);
1968 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1972 % G a m m a I m a g e %
1976 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1978 % GammaImage() gamma-corrects a particular image channel. The same
1979 % image viewed on different devices will have perceptual differences in the
1980 % way the image's intensities are represented on the screen. Specify
1981 % individual gamma levels for the red, green, and blue channels, or adjust
1982 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1984 % You can also reduce the influence of a particular channel with a gamma
1987 % The format of the GammaImage method is:
1989 % MagickBooleanType GammaImage(Image *image,const char *level)
1990 % MagickBooleanType GammaImageChannel(Image *image,
1991 % const ChannelType channel,const double gamma)
1993 % A description of each parameter follows:
1995 % o image: the image.
1997 % o channel: the channel.
1999 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2001 % o gamma: the image gamma.
2004 MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2016 assert(image != (Image *) NULL);
2017 assert(image->signature == MagickSignature);
2018 if (image->debug != MagickFalse)
2019 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2020 if (level == (char *) NULL)
2021 return(MagickFalse);
2022 flags=ParseGeometry(level,&geometry_info);
2023 gamma.red=geometry_info.rho;
2024 gamma.green=geometry_info.sigma;
2025 if ((flags & SigmaValue) == 0)
2026 gamma.green=gamma.red;
2027 gamma.blue=geometry_info.xi;
2028 if ((flags & XiValue) == 0)
2029 gamma.blue=gamma.red;
2030 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2032 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2033 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2034 GreenChannel | BlueChannel),(double) gamma.red);
2037 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2038 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2039 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2041 return(status != 0 ? MagickTrue : MagickFalse);
2044 MagickExport MagickBooleanType GammaImageChannel(Image *image,
2045 const ChannelType channel,const double gamma)
2047 #define GammaCorrectImageTag "GammaCorrect/Image"
2071 Allocate and initialize gamma maps.
2073 assert(image != (Image *) NULL);
2074 assert(image->signature == MagickSignature);
2075 if (image->debug != MagickFalse)
2076 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2079 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2080 if (gamma_map == (Quantum *) NULL)
2081 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2083 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2085 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2086 #pragma omp parallel for schedule(dynamic,4)
2088 for (i=0; i <= (ssize_t) MaxMap; i++)
2089 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
2090 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2091 if (image->storage_class == PseudoClass)
2094 Gamma-correct colormap.
2096 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2097 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2099 for (i=0; i < (ssize_t) image->colors; i++)
2101 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2102 image->colormap[i].red=gamma_map[
2103 ScaleQuantumToMap(image->colormap[i].red)];
2104 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2105 image->colormap[i].green=gamma_map[
2106 ScaleQuantumToMap(image->colormap[i].green)];
2107 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2108 image->colormap[i].blue=gamma_map[
2109 ScaleQuantumToMap(image->colormap[i].blue)];
2110 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
2111 image->colormap[i].alpha=gamma_map[
2112 ScaleQuantumToMap(image->colormap[i].alpha)];
2116 Gamma-correct image.
2120 exception=(&image->exception);
2121 image_view=AcquireCacheView(image);
2122 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2123 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2125 for (y=0; y < (ssize_t) image->rows; y++)
2133 if (status == MagickFalse)
2135 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2136 if (q == (const Quantum *) NULL)
2141 for (x=0; x < (ssize_t) image->columns; x++)
2143 if (channel == DefaultChannels)
2145 SetPixelRed(image,gamma_map[
2146 ScaleQuantumToMap(GetPixelRed(image,q))],q);
2147 SetPixelGreen(image,gamma_map[
2148 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2149 SetPixelBlue(image,gamma_map[ScaleQuantumToMap(
2150 GetPixelBlue(image,q))],q);
2154 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2155 SetPixelRed(image,gamma_map[ScaleQuantumToMap(
2156 GetPixelRed(image,q))],q);
2157 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2158 SetPixelGreen(image,gamma_map[
2159 ScaleQuantumToMap(GetPixelGreen(image,q))],q);
2160 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2161 SetPixelBlue(image,gamma_map[
2162 ScaleQuantumToMap(GetPixelBlue(image,q))],q);
2163 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
2165 if (image->matte == MagickFalse)
2166 SetPixelAlpha(image,gamma_map[
2167 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
2169 SetPixelAlpha(image,gamma_map[
2170 ScaleQuantumToMap(GetPixelAlpha(image,q))],q);
2173 q+=GetPixelChannels(image);
2175 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2176 (image->colorspace == CMYKColorspace))
2177 for (x=0; x < (ssize_t) image->columns; x++)
2178 SetPixelBlack(image,gamma_map[ScaleQuantumToMap(
2179 GetPixelBlack(image,q))],q);
2180 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2182 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2187 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2188 #pragma omp critical (MagickCore_GammaImageChannel)
2190 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2192 if (proceed == MagickFalse)
2196 image_view=DestroyCacheView(image_view);
2197 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2198 if (image->gamma != 0.0)
2199 image->gamma*=gamma;
2204 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2208 % H a l d C l u t I m a g e %
2212 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2214 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2215 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2216 % Create it with the HALD coder. You can apply any color transformation to
2217 % the Hald image and then use this method to apply the transform to the
2220 % The format of the HaldClutImage method is:
2222 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2223 % MagickBooleanType HaldClutImageChannel(Image *image,
2224 % const ChannelType channel,Image *hald_image)
2226 % A description of each parameter follows:
2228 % o image: the image, which is replaced by indexed CLUT values
2230 % o hald_image: the color lookup table image for replacement color values.
2232 % o channel: the channel.
2236 static inline size_t MagickMin(const size_t x,const size_t y)
2243 MagickExport MagickBooleanType HaldClutImage(Image *image,
2244 const Image *hald_image)
2246 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2249 MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2250 const ChannelType channel,const Image *hald_image)
2252 #define HaldClutImageTag "Clut/Image"
2254 typedef struct _HaldInfo
2289 assert(image != (Image *) NULL);
2290 assert(image->signature == MagickSignature);
2291 if (image->debug != MagickFalse)
2292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2293 assert(hald_image != (Image *) NULL);
2294 assert(hald_image->signature == MagickSignature);
2295 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2296 return(MagickFalse);
2297 if (image->matte == MagickFalse)
2298 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2304 length=MagickMin(hald_image->columns,hald_image->rows);
2305 for (level=2; (level*level*level) < length; level++) ;
2307 cube_size=level*level;
2308 width=(double) hald_image->columns;
2309 GetPixelInfo(hald_image,&zero);
2310 exception=(&image->exception);
2311 image_view=AcquireCacheView(image);
2312 hald_view=AcquireCacheView(hald_image);
2313 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2314 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2316 for (y=0; y < (ssize_t) image->rows; y++)
2337 if (status == MagickFalse)
2339 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2340 if (q == (const Quantum *) NULL)
2350 for (x=0; x < (ssize_t) image->columns; x++)
2352 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2353 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2354 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
2355 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2356 point.x-=floor(point.x);
2357 point.y-=floor(point.y);
2358 point.z-=floor(point.z);
2359 (void) InterpolatePixelInfo(image,hald_view,
2360 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2362 (void) InterpolatePixelInfo(image,hald_view,
2363 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2364 width),&pixel2,exception);
2365 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2366 pixel2.alpha,point.y,&pixel3);
2368 (void) InterpolatePixelInfo(image,hald_view,
2369 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2371 (void) InterpolatePixelInfo(image,hald_view,
2372 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2373 width),&pixel2,exception);
2374 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,
2375 pixel2.alpha,point.y,&pixel4);
2376 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,
2377 pixel4.alpha,point.z,&pixel);
2378 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2380 ClampToQuantum(pixel.red),q);
2381 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2382 SetPixelGreen(image,
2383 ClampToQuantum(pixel.green),q);
2384 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2386 ClampToQuantum(pixel.blue),q);
2387 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2388 (image->colorspace == CMYKColorspace))
2389 SetPixelBlack(image,
2390 ClampToQuantum(pixel.black),q);
2391 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) && (image->matte != MagickFalse))
2392 SetPixelAlpha(image,
2393 ClampToQuantum(pixel.alpha),q);
2394 q+=GetPixelChannels(image);
2396 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2403 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2404 #pragma omp critical (MagickCore_HaldClutImageChannel)
2406 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2407 if (proceed == MagickFalse)
2411 hald_view=DestroyCacheView(hald_view);
2412 image_view=DestroyCacheView(image_view);
2417 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2421 % L e v e l I m a g e %
2425 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2427 % LevelImage() adjusts the levels of a particular image channel by
2428 % scaling the colors falling between specified white and black points to
2429 % the full available quantum range.
2431 % The parameters provided represent the black, and white points. The black
2432 % point specifies the darkest color in the image. Colors darker than the
2433 % black point are set to zero. White point specifies the lightest color in
2434 % the image. Colors brighter than the white point are set to the maximum
2437 % If a '!' flag is given, map black and white colors to the given levels
2438 % rather than mapping those levels to black and white. See
2439 % LevelizeImageChannel() and LevelizeImageChannel(), below.
2441 % Gamma specifies a gamma correction to apply to the image.
2443 % The format of the LevelImage method is:
2445 % MagickBooleanType LevelImage(Image *image,const char *levels)
2447 % A description of each parameter follows:
2449 % o image: the image.
2451 % o levels: Specify the levels where the black and white points have the
2452 % range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2453 % A '!' flag inverts the re-mapping.
2457 MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2476 if (levels == (char *) NULL)
2477 return(MagickFalse);
2478 flags=ParseGeometry(levels,&geometry_info);
2479 black_point=geometry_info.rho;
2480 white_point=(double) QuantumRange;
2481 if ((flags & SigmaValue) != 0)
2482 white_point=geometry_info.sigma;
2484 if ((flags & XiValue) != 0)
2485 gamma=geometry_info.xi;
2486 if ((flags & PercentValue) != 0)
2488 black_point*=(double) image->columns*image->rows/100.0;
2489 white_point*=(double) image->columns*image->rows/100.0;
2491 if ((flags & SigmaValue) == 0)
2492 white_point=(double) QuantumRange-black_point;
2493 if ((flags & AspectValue ) == 0)
2494 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2497 status=LevelizeImage(image,black_point,white_point,gamma);
2502 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2506 % L e v e l i z e I m a g e %
2510 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2512 % LevelizeImage() applies the normal level operation to the image, spreading
2513 % out the values between the black and white points over the entire range of
2514 % values. Gamma correction is also applied after the values has been mapped.
2516 % It is typically used to improve image contrast, or to provide a controlled
2517 % linear threshold for the image. If the black and white points are set to
2518 % the minimum and maximum values found in the image, the image can be
2519 % normalized. or by swapping black and white values, negate the image.
2521 % The format of the LevelizeImage method is:
2523 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2524 % const double white_point,const double gamma)
2525 % MagickBooleanType LevelizeImageChannel(Image *image,
2526 % const ChannelType channel,const double black_point,
2527 % const double white_point,const double gamma)
2529 % A description of each parameter follows:
2531 % o image: the image.
2533 % o channel: the channel.
2535 % o black_point: The level which is to be mapped to zero (black)
2537 % o white_point: The level which is to be mapped to QuantiumRange (white)
2539 % o gamma: adjust gamma by this factor before mapping values.
2540 % use 1.0 for purely linear stretching of image color values
2543 MagickExport MagickBooleanType LevelImageChannel(Image *image,
2544 const ChannelType channel,const double black_point,const double white_point,
2547 #define LevelImageTag "Level/Image"
2548 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2549 pow(scale*((double) (x)-black_point),1.0/gamma)))
2573 Allocate and initialize levels map.
2575 assert(image != (Image *) NULL);
2576 assert(image->signature == MagickSignature);
2577 if (image->debug != MagickFalse)
2578 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2579 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2580 if (image->storage_class == PseudoClass)
2581 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2582 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2584 for (i=0; i < (ssize_t) image->colors; i++)
2589 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2590 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
2591 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2592 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
2593 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2594 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
2595 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
2596 image->colormap[i].alpha=LevelQuantum(image->colormap[i].alpha);
2603 exception=(&image->exception);
2604 image_view=AcquireCacheView(image);
2605 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2606 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2608 for (y=0; y < (ssize_t) image->rows; y++)
2616 if (status == MagickFalse)
2618 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2619 if (q == (const Quantum *) NULL)
2624 for (x=0; x < (ssize_t) image->columns; x++)
2626 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2627 SetPixelRed(image,LevelQuantum(
2628 GetPixelRed(image,q)),q);
2629 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2630 SetPixelGreen(image,
2631 LevelQuantum(GetPixelGreen(image,q)),q);
2632 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2634 LevelQuantum(GetPixelBlue(image,q)),q);
2635 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
2636 (image->matte == MagickTrue))
2637 SetPixelAlpha(image,
2638 LevelQuantum(GetPixelAlpha(image,q)),q);
2639 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2640 (image->colorspace == CMYKColorspace))
2641 SetPixelBlack(image,
2642 LevelQuantum(GetPixelBlack(image,q)),q);
2643 q+=GetPixelChannels(image);
2645 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2647 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2652 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2653 #pragma omp critical (MagickCore_LevelImageChannel)
2655 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2656 if (proceed == MagickFalse)
2660 image_view=DestroyCacheView(image_view);
2665 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2669 % L e v e l i z e I m a g e C h a n n e l %
2673 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2675 % LevelizeImageChannel() applies the reversed LevelImage() operation to just
2676 % the specific channels specified. It compresses the full range of color
2677 % values, so that they lie between the given black and white points. Gamma is
2678 % applied before the values are mapped.
2680 % LevelizeImageChannel() can be called with by using a +level command line
2681 % API option, or using a '!' on a -level or LevelImage() geometry string.
2683 % It can be used for example de-contrast a greyscale image to the exact
2684 % levels specified. Or by using specific levels for each channel of an image
2685 % you can convert a gray-scale image to any linear color gradient, according
2688 % The format of the LevelizeImageChannel method is:
2690 % MagickBooleanType LevelizeImageChannel(Image *image,
2691 % const ChannelType channel,const char *levels)
2693 % A description of each parameter follows:
2695 % o image: the image.
2697 % o channel: the channel.
2699 % o black_point: The level to map zero (black) to.
2701 % o white_point: The level to map QuantiumRange (white) to.
2703 % o gamma: adjust gamma by this factor before mapping values.
2707 MagickExport MagickBooleanType LevelizeImage(Image *image,
2708 const double black_point,const double white_point,const double gamma)
2713 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2718 MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2719 const ChannelType channel,const double black_point,const double white_point,
2722 #define LevelizeImageTag "Levelize/Image"
2723 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2724 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2746 Allocate and initialize levels map.
2748 assert(image != (Image *) NULL);
2749 assert(image->signature == MagickSignature);
2750 if (image->debug != MagickFalse)
2751 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2752 if (image->storage_class == PseudoClass)
2753 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2754 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2756 for (i=0; i < (ssize_t) image->colors; i++)
2761 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2762 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2763 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2764 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2765 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2766 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2767 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
2768 image->colormap[i].alpha=LevelizeValue(image->colormap[i].alpha);
2775 exception=(&image->exception);
2776 image_view=AcquireCacheView(image);
2777 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2778 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2780 for (y=0; y < (ssize_t) image->rows; y++)
2788 if (status == MagickFalse)
2790 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2791 if (q == (const Quantum *) NULL)
2796 for (x=0; x < (ssize_t) image->columns; x++)
2798 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2799 SetPixelRed(image,LevelizeValue(GetPixelRed(image,q)),q);
2800 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2801 SetPixelGreen(image,LevelizeValue(GetPixelGreen(image,q)),q);
2802 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2803 SetPixelBlue(image,LevelizeValue(GetPixelBlue(image,q)),q);
2804 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2805 (image->colorspace == CMYKColorspace))
2806 SetPixelBlack(image,LevelizeValue(GetPixelBlack(image,q)),q);
2807 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
2808 (image->matte == MagickTrue))
2809 SetPixelAlpha(image,LevelizeValue(GetPixelAlpha(image,q)),q);
2810 q+=GetPixelChannels(image);
2812 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2814 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2819 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2820 #pragma omp critical (MagickCore_LevelizeImageChannel)
2822 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2823 if (proceed == MagickFalse)
2827 image_view=DestroyCacheView(image_view);
2832 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2836 % L e v e l I m a g e C o l o r s %
2840 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2842 % LevelImageColor() maps the given color to "black" and "white" values,
2843 % linearly spreading out the colors, and level values on a channel by channel
2844 % bases, as per LevelImage(). The given colors allows you to specify
2845 % different level ranges for each of the color channels separately.
2847 % If the boolean 'invert' is set true the image values will modifyed in the
2848 % reverse direction. That is any existing "black" and "white" colors in the
2849 % image will become the color values given, with all other values compressed
2850 % appropriatally. This effectivally maps a greyscale gradient into the given
2853 % The format of the LevelColorsImageChannel method is:
2855 % MagickBooleanType LevelColorsImage(Image *image,
2856 % const PixelInfo *black_color,
2857 % const PixelInfo *white_color,const MagickBooleanType invert)
2858 % MagickBooleanType LevelColorsImageChannel(Image *image,
2859 % const ChannelType channel,const PixelInfo *black_color,
2860 % const PixelInfo *white_color,const MagickBooleanType invert)
2862 % A description of each parameter follows:
2864 % o image: the image.
2866 % o channel: the channel.
2868 % o black_color: The color to map black to/from
2870 % o white_point: The color to map white to/from
2872 % o invert: if true map the colors (levelize), rather than from (level)
2876 MagickExport MagickBooleanType LevelColorsImage(Image *image,
2877 const PixelInfo *black_color,const PixelInfo *white_color,
2878 const MagickBooleanType invert)
2883 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2888 MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2889 const ChannelType channel,const PixelInfo *black_color,
2890 const PixelInfo *white_color,const MagickBooleanType invert)
2896 Allocate and initialize levels map.
2898 assert(image != (Image *) NULL);
2899 assert(image->signature == MagickSignature);
2900 if (image->debug != MagickFalse)
2901 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2903 if (invert == MagickFalse)
2905 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2906 status|=LevelImageChannel(image,RedChannel,
2907 black_color->red,white_color->red,(double) 1.0);
2908 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2909 status|=LevelImageChannel(image,GreenChannel,
2910 black_color->green,white_color->green,(double) 1.0);
2911 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2912 status|=LevelImageChannel(image,BlueChannel,
2913 black_color->blue,white_color->blue,(double) 1.0);
2914 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2915 (image->colorspace == CMYKColorspace))
2916 status|=LevelImageChannel(image,BlackChannel,
2917 black_color->black,white_color->black,(double) 1.0);
2918 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
2919 (image->matte == MagickTrue))
2920 status|=LevelImageChannel(image,OpacityChannel,
2921 black_color->alpha,white_color->alpha,(double) 1.0);
2925 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
2926 status|=LevelizeImageChannel(image,RedChannel,
2927 black_color->red,white_color->red,(double) 1.0);
2928 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
2929 status|=LevelizeImageChannel(image,GreenChannel,
2930 black_color->green,white_color->green,(double) 1.0);
2931 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
2932 status|=LevelizeImageChannel(image,BlueChannel,
2933 black_color->blue,white_color->blue,(double) 1.0);
2934 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
2935 (image->colorspace == CMYKColorspace))
2936 status|=LevelizeImageChannel(image,BlackChannel,
2937 black_color->black,white_color->black,(double) 1.0);
2938 if (((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0) &&
2939 (image->matte == MagickTrue))
2940 status|=LevelizeImageChannel(image,OpacityChannel,
2941 black_color->alpha,white_color->alpha,(double) 1.0);
2943 return(status == 0 ? MagickFalse : MagickTrue);
2947 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2951 % L i n e a r S t r e t c h I m a g e %
2955 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2957 % The LinearStretchImage() discards any pixels below the black point and
2958 % above the white point and levels the remaining pixels.
2960 % The format of the LinearStretchImage method is:
2962 % MagickBooleanType LinearStretchImage(Image *image,
2963 % const double black_point,const double white_point)
2965 % A description of each parameter follows:
2967 % o image: the image.
2969 % o black_point: the black point.
2971 % o white_point: the white point.
2974 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2975 const double black_point,const double white_point)
2977 #define LinearStretchImageTag "LinearStretch/Image"
2995 Allocate histogram and linear map.
2997 assert(image != (Image *) NULL);
2998 assert(image->signature == MagickSignature);
2999 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3000 sizeof(*histogram));
3001 if (histogram == (MagickRealType *) NULL)
3002 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3007 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3008 exception=(&image->exception);
3009 for (y=0; y < (ssize_t) image->rows; y++)
3011 register const Quantum
3017 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3018 if (p == (const Quantum *) NULL)
3020 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3022 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
3023 p+=GetPixelChannels(image);
3027 Find the histogram boundaries by locating the black and white point levels.
3030 for (black=0; black < (ssize_t) MaxMap; black++)
3032 intensity+=histogram[black];
3033 if (intensity >= black_point)
3037 for (white=(ssize_t) MaxMap; white != 0; white--)
3039 intensity+=histogram[white];
3040 if (intensity >= white_point)
3043 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3044 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3050 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3054 % M o d u l a t e I m a g e %
3058 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3060 % ModulateImage() lets you control the brightness, saturation, and hue
3061 % of an image. Modulate represents the brightness, saturation, and hue
3062 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3063 % modulation is lightness, saturation, and hue. And if the colorspace is
3064 % HWB, use blackness, whiteness, and hue.
3066 % The format of the ModulateImage method is:
3068 % MagickBooleanType ModulateImage(Image *image,const char *modulate)
3070 % A description of each parameter follows:
3072 % o image: the image.
3074 % o modulate: Define the percent change in brightness, saturation, and
3079 static void ModulateHSB(const double percent_hue,
3080 const double percent_saturation,const double percent_brightness,
3081 Quantum *red,Quantum *green,Quantum *blue)
3089 Increase or decrease color brightness, saturation, or hue.
3091 assert(red != (Quantum *) NULL);
3092 assert(green != (Quantum *) NULL);
3093 assert(blue != (Quantum *) NULL);
3094 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3095 hue+=0.5*(0.01*percent_hue-1.0);
3100 saturation*=0.01*percent_saturation;
3101 brightness*=0.01*percent_brightness;
3102 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3105 static void ModulateHSL(const double percent_hue,
3106 const double percent_saturation,const double percent_lightness,
3107 Quantum *red,Quantum *green,Quantum *blue)
3115 Increase or decrease color lightness, saturation, or hue.
3117 assert(red != (Quantum *) NULL);
3118 assert(green != (Quantum *) NULL);
3119 assert(blue != (Quantum *) NULL);
3120 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3121 hue+=0.5*(0.01*percent_hue-1.0);
3126 saturation*=0.01*percent_saturation;
3127 lightness*=0.01*percent_lightness;
3128 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3131 static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3139 Increase or decrease color blackness, whiteness, or hue.
3141 assert(red != (Quantum *) NULL);
3142 assert(green != (Quantum *) NULL);
3143 assert(blue != (Quantum *) NULL);
3144 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3145 hue+=0.5*(0.01*percent_hue-1.0);
3150 blackness*=0.01*percent_blackness;
3151 whiteness*=0.01*percent_whiteness;
3152 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3155 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3157 #define ModulateImageTag "Modulate/Image"
3195 Initialize modulate table.
3197 assert(image != (Image *) NULL);
3198 assert(image->signature == MagickSignature);
3199 if (image->debug != MagickFalse)
3200 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3201 if (modulate == (char *) NULL)
3202 return(MagickFalse);
3203 flags=ParseGeometry(modulate,&geometry_info);
3204 percent_brightness=geometry_info.rho;
3205 percent_saturation=geometry_info.sigma;
3206 if ((flags & SigmaValue) == 0)
3207 percent_saturation=100.0;
3208 percent_hue=geometry_info.xi;
3209 if ((flags & XiValue) == 0)
3211 colorspace=UndefinedColorspace;
3212 artifact=GetImageArtifact(image,"modulate:colorspace");
3213 if (artifact != (const char *) NULL)
3214 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3215 MagickFalse,artifact);
3216 if (image->storage_class == PseudoClass)
3221 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3222 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3224 for (i=0; i < (ssize_t) image->colors; i++)
3229 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3230 &image->colormap[i].red,&image->colormap[i].green,
3231 &image->colormap[i].blue);
3237 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3238 &image->colormap[i].red,&image->colormap[i].green,
3239 &image->colormap[i].blue);
3244 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3245 &image->colormap[i].red,&image->colormap[i].green,
3246 &image->colormap[i].blue);
3256 exception=(&image->exception);
3257 image_view=AcquireCacheView(image);
3258 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3259 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3261 for (y=0; y < (ssize_t) image->rows; y++)
3274 if (status == MagickFalse)
3276 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3277 if (q == (const Quantum *) NULL)
3282 for (x=0; x < (ssize_t) image->columns; x++)
3284 red=GetPixelRed(image,q);
3285 green=GetPixelGreen(image,q);
3286 blue=GetPixelBlue(image,q);
3291 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3298 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3304 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3309 SetPixelRed(image,red,q);
3310 SetPixelGreen(image,green,q);
3311 SetPixelBlue(image,blue,q);
3312 q+=GetPixelChannels(image);
3314 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3316 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3321 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3322 #pragma omp critical (MagickCore_ModulateImage)
3324 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3325 if (proceed == MagickFalse)
3329 image_view=DestroyCacheView(image_view);
3334 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3338 % N e g a t e I m a g e %
3342 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3344 % NegateImage() negates the colors in the reference image. The grayscale
3345 % option means that only grayscale values within the image are negated.
3347 % The format of the NegateImageChannel method is:
3349 % MagickBooleanType NegateImage(Image *image,
3350 % const MagickBooleanType grayscale)
3351 % MagickBooleanType NegateImageChannel(Image *image,
3352 % const ChannelType channel,const MagickBooleanType grayscale)
3354 % A description of each parameter follows:
3356 % o image: the image.
3358 % o channel: the channel.
3360 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3364 MagickExport MagickBooleanType NegateImage(Image *image,
3365 const MagickBooleanType grayscale)
3370 status=NegateImageChannel(image,DefaultChannels,grayscale);
3374 MagickExport MagickBooleanType NegateImageChannel(Image *image,
3375 const ChannelType channel,const MagickBooleanType grayscale)
3377 #define NegateImageTag "Negate/Image"
3397 assert(image != (Image *) NULL);
3398 assert(image->signature == MagickSignature);
3399 if (image->debug != MagickFalse)
3400 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3401 if (image->storage_class == PseudoClass)
3406 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3407 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3409 for (i=0; i < (ssize_t) image->colors; i++)
3411 if (grayscale != MagickFalse)
3412 if ((image->colormap[i].red != image->colormap[i].green) ||
3413 (image->colormap[i].green != image->colormap[i].blue))
3415 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
3416 image->colormap[i].red=(Quantum) QuantumRange-
3417 image->colormap[i].red;
3418 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
3419 image->colormap[i].green=(Quantum) QuantumRange-
3420 image->colormap[i].green;
3421 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
3422 image->colormap[i].blue=(Quantum) QuantumRange-
3423 image->colormap[i].blue;
3431 exception=(&image->exception);
3432 image_view=AcquireCacheView(image);
3433 if (grayscale != MagickFalse)
3435 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3436 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3438 for (y=0; y < (ssize_t) image->rows; y++)
3449 if (status == MagickFalse)
3451 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3453 if (q == (const Quantum *) NULL)
3458 for (x=0; x < (ssize_t) image->columns; x++)
3460 if ((GetPixelRed(image,q) != GetPixelGreen(image,q)) ||
3461 (GetPixelGreen(image,q) != GetPixelBlue(image,q)))
3463 q+=GetPixelChannels(image);
3466 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
3467 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
3468 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
3469 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
3470 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
3471 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
3472 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
3473 (image->colorspace == CMYKColorspace))
3474 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
3475 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
3476 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3477 q+=GetPixelChannels(image);
3479 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3480 if (sync == MagickFalse)
3482 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3487 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3488 #pragma omp critical (MagickCore_NegateImageChannel)
3490 proceed=SetImageProgress(image,NegateImageTag,progress++,
3492 if (proceed == MagickFalse)
3496 image_view=DestroyCacheView(image_view);
3502 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3503 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3505 for (y=0; y < (ssize_t) image->rows; y++)
3513 if (status == MagickFalse)
3515 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3516 if (q == (const Quantum *) NULL)
3521 for (x=0; x < (ssize_t) image->columns; x++)
3523 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
3524 SetPixelRed(image,QuantumRange-GetPixelRed(image,q),q);
3525 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
3526 SetPixelGreen(image,QuantumRange-GetPixelGreen(image,q),q);
3527 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
3528 SetPixelBlue(image,QuantumRange-GetPixelBlue(image,q),q);
3529 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
3530 (image->colorspace == CMYKColorspace))
3531 SetPixelBlack(image,QuantumRange-GetPixelBlack(image,q),q);
3532 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
3533 SetPixelAlpha(image,QuantumRange-GetPixelAlpha(image,q),q);
3534 q+=GetPixelChannels(image);
3536 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3538 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3543 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3544 #pragma omp critical (MagickCore_NegateImageChannel)
3546 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3547 if (proceed == MagickFalse)
3551 image_view=DestroyCacheView(image_view);
3556 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3560 % N o r m a l i z e I m a g e %
3564 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3566 % The NormalizeImage() method enhances the contrast of a color image by
3567 % mapping the darkest 2 percent of all pixel to black and the brightest
3568 % 1 percent to white.
3570 % The format of the NormalizeImage method is:
3572 % MagickBooleanType NormalizeImage(Image *image)
3573 % MagickBooleanType NormalizeImageChannel(Image *image,
3574 % const ChannelType channel)
3576 % A description of each parameter follows:
3578 % o image: the image.
3580 % o channel: the channel.
3584 MagickExport MagickBooleanType NormalizeImage(Image *image)
3589 status=NormalizeImageChannel(image,DefaultChannels);
3593 MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3594 const ChannelType channel)
3600 black_point=(double) image->columns*image->rows*0.0015;
3601 white_point=(double) image->columns*image->rows*0.9995;
3602 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3606 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3610 % S i g m o i d a l C o n t r a s t I m a g e %
3614 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3616 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3617 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3618 % sigmoidal transfer function without saturating highlights or shadows.
3619 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3620 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3621 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3622 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3625 % The format of the SigmoidalContrastImage method is:
3627 % MagickBooleanType SigmoidalContrastImage(Image *image,
3628 % const MagickBooleanType sharpen,const char *levels)
3629 % MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3630 % const ChannelType channel,const MagickBooleanType sharpen,
3631 % const double contrast,const double midpoint)
3633 % A description of each parameter follows:
3635 % o image: the image.
3637 % o channel: the channel.
3639 % o sharpen: Increase or decrease image contrast.
3641 % o alpha: strength of the contrast, the larger the number the more
3642 % 'threshold-like' it becomes.
3644 % o beta: midpoint of the function as a color value 0 to QuantumRange.
3648 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3649 const MagickBooleanType sharpen,const char *levels)
3660 flags=ParseGeometry(levels,&geometry_info);
3661 if ((flags & SigmaValue) == 0)
3662 geometry_info.sigma=1.0*QuantumRange/2.0;
3663 if ((flags & PercentValue) != 0)
3664 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3665 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3666 geometry_info.rho,geometry_info.sigma);
3670 MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3671 const ChannelType channel,const MagickBooleanType sharpen,
3672 const double contrast,const double midpoint)
3674 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3698 Allocate and initialize sigmoidal maps.
3700 assert(image != (Image *) NULL);
3701 assert(image->signature == MagickSignature);
3702 if (image->debug != MagickFalse)
3703 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3704 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3705 sizeof(*sigmoidal_map));
3706 if (sigmoidal_map == (MagickRealType *) NULL)
3707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3709 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3710 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3711 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3713 for (i=0; i <= (ssize_t) MaxMap; i++)
3715 if (sharpen != MagickFalse)
3717 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3718 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3719 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3720 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3721 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3722 (double) QuantumRange)))))+0.5));
3725 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3726 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3727 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3728 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3729 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3730 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3731 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3732 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3733 (double) QuantumRange*contrast))))))/contrast)));
3735 if (image->storage_class == PseudoClass)
3738 Sigmoidal-contrast enhance colormap.
3740 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3741 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3743 for (i=0; i < (ssize_t) image->colors; i++)
3745 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
3746 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
3747 ScaleQuantumToMap(image->colormap[i].red)]);
3748 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
3749 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
3750 ScaleQuantumToMap(image->colormap[i].green)]);
3751 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
3752 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
3753 ScaleQuantumToMap(image->colormap[i].blue)]);
3754 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
3755 image->colormap[i].alpha=ClampToQuantum(sigmoidal_map[
3756 ScaleQuantumToMap(image->colormap[i].alpha)]);
3760 Sigmoidal-contrast enhance image.
3764 exception=(&image->exception);
3765 image_view=AcquireCacheView(image);
3766 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3767 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3769 for (y=0; y < (ssize_t) image->rows; y++)
3777 if (status == MagickFalse)
3779 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3780 if (q == (const Quantum *) NULL)
3785 for (x=0; x < (ssize_t) image->columns; x++)
3787 if ((GetPixelRedTraits(image) & ActivePixelTrait) != 0)
3788 SetPixelRed(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3789 GetPixelRed(image,q))]),q);
3790 if ((GetPixelGreenTraits(image) & ActivePixelTrait) != 0)
3791 SetPixelGreen(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3792 GetPixelGreen(image,q))]),q);
3793 if ((GetPixelBlueTraits(image) & ActivePixelTrait) != 0)
3794 SetPixelBlue(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3795 GetPixelBlue(image,q))]),q);
3796 if (((GetPixelBlackTraits(image) & ActivePixelTrait) != 0) &&
3797 (image->colorspace == CMYKColorspace))
3798 SetPixelBlack(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3799 GetPixelBlack(image,q))]),q);
3800 if ((GetPixelAlphaTraits(image) & ActivePixelTrait) != 0)
3801 SetPixelAlpha(image,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3802 GetPixelAlpha(image,q))]),q);
3803 q+=GetPixelChannels(image);
3805 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3807 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3812 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3813 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3815 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3817 if (proceed == MagickFalse)
3821 image_view=DestroyCacheView(image_view);
3822 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);