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 "magick/studio.h"
44 #include "magick/artifact.h"
45 #include "magick/cache.h"
46 #include "magick/cache-view.h"
47 #include "magick/color.h"
48 #include "magick/color-private.h"
49 #include "magick/colorspace.h"
50 #include "magick/composite-private.h"
51 #include "magick/enhance.h"
52 #include "magick/exception.h"
53 #include "magick/exception-private.h"
54 #include "magick/fx.h"
55 #include "magick/gem.h"
56 #include "magick/geometry.h"
57 #include "magick/histogram.h"
58 #include "magick/image.h"
59 #include "magick/image-private.h"
60 #include "magick/memory_.h"
61 #include "magick/monitor.h"
62 #include "magick/monitor-private.h"
63 #include "magick/option.h"
64 #include "magick/pixel-private.h"
65 #include "magick/quantum.h"
66 #include "magick/quantum-private.h"
67 #include "magick/resample.h"
68 #include "magick/resample-private.h"
69 #include "magick/statistic.h"
70 #include "magick/string_.h"
71 #include "magick/string-private.h"
72 #include "magick/thread-private.h"
73 #include "magick/token.h"
74 #include "magick/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)
93 % MagickBooleanType AutoGammaImageChannel(Image *image,
94 % const ChannelType channel)
96 % A description of each parameter follows:
98 % o image: The image to auto-level
100 % o channel: The channels to auto-level. If the special 'SyncChannels'
101 % flag is set all given channels is adjusted in the same way using the
102 % mean average of those channels.
106 MagickExport MagickBooleanType AutoGammaImage(Image *image)
108 return(AutoGammaImageChannel(image,DefaultChannels));
111 MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
112 const ChannelType channel)
118 mean,sans,gamma,logmean;
122 if ((channel & SyncChannels) != 0 )
125 Apply gamma correction equally accross all given channels
127 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
128 gamma=log(mean*QuantumScale)/logmean;
129 return LevelImageChannel(image, channel,
130 0.0, (double)QuantumRange, gamma);
134 auto-gamma each channel separateally
137 if ((channel & RedChannel) != 0)
139 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
141 gamma=log(mean*QuantumScale)/logmean;
142 status = status && LevelImageChannel(image, RedChannel,
143 0.0, (double)QuantumRange, gamma);
145 if ((channel & GreenChannel) != 0)
147 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
149 gamma=log(mean*QuantumScale)/logmean;
150 status = status && LevelImageChannel(image, GreenChannel,
151 0.0, (double)QuantumRange, gamma);
153 if ((channel & BlueChannel) != 0)
155 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
157 gamma=log(mean*QuantumScale)/logmean;
158 status = status && LevelImageChannel(image, BlueChannel,
159 0.0, (double)QuantumRange, gamma);
161 if (((channel & OpacityChannel) != 0) &&
162 (image->matte == MagickTrue))
164 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
166 gamma=log(mean*QuantumScale)/logmean;
167 status = status && LevelImageChannel(image, OpacityChannel,
168 0.0, (double)QuantumRange, gamma);
170 if (((channel & IndexChannel) != 0) &&
171 (image->colorspace == CMYKColorspace))
173 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
175 gamma=log(mean*QuantumScale)/logmean;
176 status = status && LevelImageChannel(image, IndexChannel,
177 0.0, (double)QuantumRange, gamma);
179 return(status != 0 ? MagickTrue : MagickFalse);
183 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 % A u t o L e v e l I m a g e %
191 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
193 % AutoLevelImage() adjusts the levels of a particular image channel by
194 % scaling the minimum and maximum values to the full quantum range.
196 % The format of the LevelImage method is:
198 % MagickBooleanType AutoLevelImage(Image *image)
199 % MagickBooleanType AutoLevelImageChannel(Image *image,
200 % const ChannelType channel)
202 % A description of each parameter follows:
204 % o image: The image to auto-level
206 % o channel: The channels to auto-level. If the special 'SyncChannels'
207 % flag is set the min/max/mean value of all given channels is used for
208 % all given channels, to all channels in the same way.
212 MagickExport MagickBooleanType AutoLevelImage(Image *image)
214 return(AutoLevelImageChannel(image,DefaultChannels));
217 MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
218 const ChannelType channel)
221 This is simply a convenience function around a Min/Max Histogram Stretch
223 return MinMaxStretchImage(image, channel, 0.0, 0.0);
227 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
231 % B r i g h t n e s s C o n t r a s t I m a g e %
235 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
237 % Use BrightnessContrastImage() to change the brightness and/or contrast of
238 % an image. It converts the brightness and contrast parameters into slope
239 % and intercept and calls a polynomical function to apply to the image.
241 % The format of the BrightnessContrastImage method is:
243 % MagickBooleanType BrightnessContrastImage(Image *image,
244 % const double brightness,const double contrast)
245 % MagickBooleanType BrightnessContrastImageChannel(Image *image,
246 % const ChannelType channel,const double brightness,
247 % const double contrast)
249 % A description of each parameter follows:
251 % o image: the image.
253 % o channel: the channel.
255 % o brightness: the brightness percent (-100 .. 100).
257 % o contrast: the contrast percent (-100 .. 100).
261 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
262 const double brightness,const double contrast)
267 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
272 MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
273 const ChannelType channel,const double brightness,const double contrast)
275 #define BrightnessContastImageTag "BrightnessContast/Image"
287 Compute slope and intercept.
289 assert(image != (Image *) NULL);
290 assert(image->signature == MagickSignature);
291 if (image->debug != MagickFalse)
292 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
294 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
297 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
298 coefficients[0]=slope;
299 coefficients[1]=intercept;
300 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
306 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
310 % C o l o r D e c i s i o n L i s t I m a g e %
314 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
316 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
317 % (CCC) file which solely contains one or more color corrections and applies
318 % the correction to the image. Here is a sample CCC file:
320 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
321 % <ColorCorrection id="cc03345">
323 % <Slope> 0.9 1.2 0.5 </Slope>
324 % <Offset> 0.4 -0.5 0.6 </Offset>
325 % <Power> 1.0 0.8 1.5 </Power>
328 % <Saturation> 0.85 </Saturation>
331 % </ColorCorrectionCollection>
333 % which includes the slop, offset, and power for each of the RGB channels
334 % as well as the saturation.
336 % The format of the ColorDecisionListImage method is:
338 % MagickBooleanType ColorDecisionListImage(Image *image,
339 % const char *color_correction_collection)
341 % A description of each parameter follows:
343 % o image: the image.
345 % o color_correction_collection: the color correction collection in XML.
348 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
349 const char *color_correction_collection)
351 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
353 typedef struct _Correction
361 typedef struct _ColorCorrection
376 token[MaxTextExtent];
410 Allocate and initialize cdl maps.
412 assert(image != (Image *) NULL);
413 assert(image->signature == MagickSignature);
414 if (image->debug != MagickFalse)
415 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
416 if (color_correction_collection == (const char *) NULL)
418 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
419 if (ccc == (XMLTreeInfo *) NULL)
421 cc=GetXMLTreeChild(ccc,"ColorCorrection");
422 if (cc == (XMLTreeInfo *) NULL)
424 ccc=DestroyXMLTree(ccc);
427 color_correction.red.slope=1.0;
428 color_correction.red.offset=0.0;
429 color_correction.red.power=1.0;
430 color_correction.green.slope=1.0;
431 color_correction.green.offset=0.0;
432 color_correction.green.power=1.0;
433 color_correction.blue.slope=1.0;
434 color_correction.blue.offset=0.0;
435 color_correction.blue.power=1.0;
436 color_correction.saturation=0.0;
437 sop=GetXMLTreeChild(cc,"SOPNode");
438 if (sop != (XMLTreeInfo *) NULL)
445 slope=GetXMLTreeChild(sop,"Slope");
446 if (slope != (XMLTreeInfo *) NULL)
448 content=GetXMLTreeContent(slope);
449 p=(const char *) content;
450 for (i=0; (*p != '\0') && (i < 3); i++)
452 GetMagickToken(p,&p,token);
454 GetMagickToken(p,&p,token);
457 case 0: color_correction.red.slope=StringToDouble(token); break;
458 case 1: color_correction.green.slope=StringToDouble(token); break;
459 case 2: color_correction.blue.slope=StringToDouble(token); break;
463 offset=GetXMLTreeChild(sop,"Offset");
464 if (offset != (XMLTreeInfo *) NULL)
466 content=GetXMLTreeContent(offset);
467 p=(const char *) content;
468 for (i=0; (*p != '\0') && (i < 3); i++)
470 GetMagickToken(p,&p,token);
472 GetMagickToken(p,&p,token);
475 case 0: color_correction.red.offset=StringToDouble(token); break;
476 case 1: color_correction.green.offset=StringToDouble(token); break;
477 case 2: color_correction.blue.offset=StringToDouble(token); break;
481 power=GetXMLTreeChild(sop,"Power");
482 if (power != (XMLTreeInfo *) NULL)
484 content=GetXMLTreeContent(power);
485 p=(const char *) content;
486 for (i=0; (*p != '\0') && (i < 3); i++)
488 GetMagickToken(p,&p,token);
490 GetMagickToken(p,&p,token);
493 case 0: color_correction.red.power=StringToDouble(token); break;
494 case 1: color_correction.green.power=StringToDouble(token); break;
495 case 2: color_correction.blue.power=StringToDouble(token); break;
500 sat=GetXMLTreeChild(cc,"SATNode");
501 if (sat != (XMLTreeInfo *) NULL)
506 saturation=GetXMLTreeChild(sat,"Saturation");
507 if (saturation != (XMLTreeInfo *) NULL)
509 content=GetXMLTreeContent(saturation);
510 p=(const char *) content;
511 GetMagickToken(p,&p,token);
512 color_correction.saturation=StringToDouble(token);
515 ccc=DestroyXMLTree(ccc);
516 if (image->debug != MagickFalse)
518 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
519 " Color Correction Collection:");
520 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
521 " color_correction.red.slope: %g",color_correction.red.slope);
522 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
523 " color_correction.red.offset: %g",color_correction.red.offset);
524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
525 " color_correction.red.power: %g",color_correction.red.power);
526 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
527 " color_correction.green.slope: %g",color_correction.green.slope);
528 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
529 " color_correction.green.offset: %g",color_correction.green.offset);
530 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
531 " color_correction.green.power: %g",color_correction.green.power);
532 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
533 " color_correction.blue.slope: %g",color_correction.blue.slope);
534 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
535 " color_correction.blue.offset: %g",color_correction.blue.offset);
536 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
537 " color_correction.blue.power: %g",color_correction.blue.power);
538 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
539 " color_correction.saturation: %g",color_correction.saturation);
541 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
542 if (cdl_map == (PixelPacket *) NULL)
543 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
545 #if defined(MAGICKCORE_OPENMP_SUPPORT)
546 #pragma omp parallel for schedule(dynamic,4)
548 for (i=0; i <= (ssize_t) MaxMap; i++)
550 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
551 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
552 color_correction.red.offset,color_correction.red.power)))));
553 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
554 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
555 color_correction.green.offset,color_correction.green.power)))));
556 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
557 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
558 color_correction.blue.offset,color_correction.blue.power)))));
560 if (image->storage_class == PseudoClass)
563 Apply transfer function to colormap.
565 #if defined(MAGICKCORE_OPENMP_SUPPORT)
566 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
568 for (i=0; i < (ssize_t) image->colors; i++)
573 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
574 0.0722*image->colormap[i].blue;
575 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
576 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
577 image->colormap[i].green=ClampToQuantum(luma+
578 color_correction.saturation*cdl_map[ScaleQuantumToMap(
579 image->colormap[i].green)].green-luma);
580 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
581 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
585 Apply transfer function to image.
589 exception=(&image->exception);
590 image_view=AcquireCacheView(image);
591 #if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
594 for (y=0; y < (ssize_t) image->rows; y++)
605 if (status == MagickFalse)
607 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
608 if (q == (PixelPacket *) NULL)
613 for (x=0; x < (ssize_t) image->columns; x++)
615 luma=0.2126*GetRedPixelComponent(q)+0.7152*GetGreenPixelComponent(q)+
616 0.0722*GetBluePixelComponent(q);
617 SetRedPixelComponent(q,ClampToQuantum(luma+color_correction.saturation*
618 (cdl_map[ScaleQuantumToMap(GetRedPixelComponent(q))].red-luma)));
619 SetGreenPixelComponent(q,ClampToQuantum(luma+color_correction.saturation*
620 (cdl_map[ScaleQuantumToMap(GetGreenPixelComponent(q))].green-luma)));
621 SetBluePixelComponent(q,ClampToQuantum(luma+color_correction.saturation*
622 (cdl_map[ScaleQuantumToMap(GetBluePixelComponent(q))].blue-luma)));
625 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
627 if (image->progress_monitor != (MagickProgressMonitor) NULL)
632 #if defined(MAGICKCORE_OPENMP_SUPPORT)
633 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
635 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
636 progress++,image->rows);
637 if (proceed == MagickFalse)
641 image_view=DestroyCacheView(image_view);
642 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
647 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
651 % C l u t I m a g e %
655 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
657 % ClutImage() replaces each color value in the given image, by using it as an
658 % index to lookup a replacement color value in a Color Look UP Table in the
659 % form of an image. The values are extracted along a diagonal of the CLUT
660 % image so either a horizontal or vertial gradient image can be used.
662 % Typically this is used to either re-color a gray-scale image according to a
663 % color gradient in the CLUT image, or to perform a freeform histogram
664 % (level) adjustment according to the (typically gray-scale) gradient in the
667 % When the 'channel' mask includes the matte/alpha transparency channel but
668 % one image has no such channel it is assumed that that image is a simple
669 % gray-scale image that will effect the alpha channel values, either for
670 % gray-scale coloring (with transparent or semi-transparent colors), or
671 % a histogram adjustment of existing alpha channel values. If both images
672 % have matte channels, direct and normal indexing is applied, which is rarely
675 % The format of the ClutImage method is:
677 % MagickBooleanType ClutImage(Image *image,Image *clut_image)
678 % MagickBooleanType ClutImageChannel(Image *image,
679 % const ChannelType channel,Image *clut_image)
681 % A description of each parameter follows:
683 % o image: the image, which is replaced by indexed CLUT values
685 % o clut_image: the color lookup table image for replacement color values.
687 % o channel: the channel.
691 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
693 return(ClutImageChannel(image,DefaultChannels,clut_image));
696 MagickExport MagickBooleanType ClutImageChannel(Image *image,
697 const ChannelType channel,const Image *clut_image)
699 #define ClutImageTag "Clut/Image"
724 assert(image != (Image *) NULL);
725 assert(image->signature == MagickSignature);
726 if (image->debug != MagickFalse)
727 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
728 assert(clut_image != (Image *) NULL);
729 assert(clut_image->signature == MagickSignature);
730 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
732 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
734 if (clut_map == (MagickPixelPacket *) NULL)
735 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
742 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
743 exception=(&image->exception);
744 clut_view=AcquireCacheView(clut_image);
745 #if defined(MAGICKCORE_OPENMP_SUPPORT)
746 #pragma omp parallel for schedule(dynamic,4)
748 for (i=0; i <= (ssize_t) MaxMap; i++)
750 GetMagickPixelPacket(clut_image,clut_map+i);
751 (void) InterpolateMagickPixelPacket(clut_image,clut_view,
752 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
753 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
755 clut_view=DestroyCacheView(clut_view);
756 image_view=AcquireCacheView(image);
757 #if defined(MAGICKCORE_OPENMP_SUPPORT)
758 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
760 for (y=0; y < (ssize_t) image->rows; y++)
774 if (status == MagickFalse)
776 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
777 if (q == (PixelPacket *) NULL)
782 indexes=GetCacheViewAuthenticIndexQueue(image_view);
783 GetMagickPixelPacket(image,&pixel);
784 for (x=0; x < (ssize_t) image->columns; x++)
786 SetMagickPixelPacket(image,q,indexes+x,&pixel);
787 if ((channel & RedChannel) != 0)
788 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
789 ScaleQuantumToMap(GetRedPixelComponent(q))));
790 if ((channel & GreenChannel) != 0)
791 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
792 ScaleQuantumToMap(GetGreenPixelComponent(q))));
793 if ((channel & BlueChannel) != 0)
794 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
795 ScaleQuantumToMap(GetBluePixelComponent(q))));
796 if ((channel & OpacityChannel) != 0)
798 if (clut_image->matte == MagickFalse)
799 SetOpacityPixelComponent(q,(QuantumRange-
800 MagickPixelIntensityToQuantum(clut_map+ScaleQuantumToMap(
801 (Quantum) GetAlphaPixelComponent(q)))));
803 if (image->matte == MagickFalse)
804 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
805 ScaleQuantumToMap((Quantum) MagickPixelIntensity(&pixel))));
807 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
808 clut_map+ScaleQuantumToMap(GetOpacityPixelComponent(q))));
810 if (((channel & IndexChannel) != 0) &&
811 (image->colorspace == CMYKColorspace))
812 SetIndexPixelComponent(indexes+x,ClampToQuantum((clut_map+(ssize_t)
813 GetIndexPixelComponent(indexes+x))->index));
816 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
818 if (image->progress_monitor != (MagickProgressMonitor) NULL)
823 #if defined(MAGICKCORE_OPENMP_SUPPORT)
824 #pragma omp critical (MagickCore_ClutImageChannel)
826 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
827 if (proceed == MagickFalse)
831 image_view=DestroyCacheView(image_view);
832 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
833 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
834 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
839 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
843 % C o n t r a s t I m a g e %
847 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
849 % ContrastImage() enhances the intensity differences between the lighter and
850 % darker elements of the image. Set sharpen to a MagickTrue to increase the
851 % image contrast otherwise the contrast is reduced.
853 % The format of the ContrastImage method is:
855 % MagickBooleanType ContrastImage(Image *image,
856 % const MagickBooleanType sharpen)
858 % A description of each parameter follows:
860 % o image: the image.
862 % o sharpen: Increase or decrease image contrast.
866 static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
874 Enhance contrast: dark color become darker, light color become lighter.
876 assert(red != (Quantum *) NULL);
877 assert(green != (Quantum *) NULL);
878 assert(blue != (Quantum *) NULL);
882 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
883 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
885 if (brightness > 1.0)
888 if (brightness < 0.0)
890 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
893 MagickExport MagickBooleanType ContrastImage(Image *image,
894 const MagickBooleanType sharpen)
896 #define ContrastImageTag "Contrast/Image"
919 assert(image != (Image *) NULL);
920 assert(image->signature == MagickSignature);
921 if (image->debug != MagickFalse)
922 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
923 sign=sharpen != MagickFalse ? 1 : -1;
924 if (image->storage_class == PseudoClass)
927 Contrast enhance colormap.
929 for (i=0; i < (ssize_t) image->colors; i++)
930 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
931 &image->colormap[i].blue);
934 Contrast enhance image.
938 exception=(&image->exception);
939 image_view=AcquireCacheView(image);
940 #if defined(MAGICKCORE_OPENMP_SUPPORT)
941 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
943 for (y=0; y < (ssize_t) image->rows; y++)
956 if (status == MagickFalse)
958 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
959 if (q == (PixelPacket *) NULL)
964 for (x=0; x < (ssize_t) image->columns; x++)
966 red=GetRedPixelComponent(q);
967 green=GetGreenPixelComponent(q);
968 blue=GetBluePixelComponent(q);
969 Contrast(sign,&red,&green,&blue);
970 SetRedPixelComponent(q,red);
971 SetGreenPixelComponent(q,green);
972 SetBluePixelComponent(q,blue);
975 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
977 if (image->progress_monitor != (MagickProgressMonitor) NULL)
982 #if defined(MAGICKCORE_OPENMP_SUPPORT)
983 #pragma omp critical (MagickCore_ContrastImage)
985 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
986 if (proceed == MagickFalse)
990 image_view=DestroyCacheView(image_view);
995 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
999 % C o n t r a s t S t r e t c h I m a g e %
1003 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1005 % The ContrastStretchImage() is a simple image enhancement technique that
1006 % attempts to improve the contrast in an image by `stretching' the range of
1007 % intensity values it contains to span a desired range of values. It differs
1008 % from the more sophisticated histogram equalization in that it can only
1009 % apply % a linear scaling function to the image pixel values. As a result
1010 % the `enhancement' is less harsh.
1012 % The format of the ContrastStretchImage method is:
1014 % MagickBooleanType ContrastStretchImage(Image *image,
1015 % const char *levels)
1016 % MagickBooleanType ContrastStretchImageChannel(Image *image,
1017 % const size_t channel,const double black_point,
1018 % const double white_point)
1020 % A description of each parameter follows:
1022 % o image: the image.
1024 % o channel: the channel.
1026 % o black_point: the black point.
1028 % o white_point: the white point.
1030 % o levels: Specify the levels where the black and white points have the
1031 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1035 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1054 if (levels == (char *) NULL)
1055 return(MagickFalse);
1056 flags=ParseGeometry(levels,&geometry_info);
1057 black_point=geometry_info.rho;
1058 white_point=(double) image->columns*image->rows;
1059 if ((flags & SigmaValue) != 0)
1060 white_point=geometry_info.sigma;
1061 if ((flags & PercentValue) != 0)
1063 black_point*=(double) QuantumRange/100.0;
1064 white_point*=(double) QuantumRange/100.0;
1066 if ((flags & SigmaValue) == 0)
1067 white_point=(double) image->columns*image->rows-black_point;
1068 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1073 MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1074 const ChannelType channel,const double black_point,const double white_point)
1076 #define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1077 #define ContrastStretchImageTag "ContrastStretch/Image"
1107 Allocate histogram and stretch map.
1109 assert(image != (Image *) NULL);
1110 assert(image->signature == MagickSignature);
1111 if (image->debug != MagickFalse)
1112 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1113 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1114 sizeof(*histogram));
1115 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1116 sizeof(*stretch_map));
1117 if ((histogram == (MagickPixelPacket *) NULL) ||
1118 (stretch_map == (MagickPixelPacket *) NULL))
1119 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1125 exception=(&image->exception);
1126 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1127 image_view=AcquireCacheView(image);
1128 for (y=0; y < (ssize_t) image->rows; y++)
1130 register const PixelPacket
1133 register IndexPacket
1139 if (status == MagickFalse)
1141 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1142 if (p == (const PixelPacket *) NULL)
1147 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1148 if (channel == DefaultChannels)
1149 for (x=0; x < (ssize_t) image->columns; x++)
1154 intensity=PixelIntensityToQuantum(p);
1155 histogram[ScaleQuantumToMap(intensity)].red++;
1156 histogram[ScaleQuantumToMap(intensity)].green++;
1157 histogram[ScaleQuantumToMap(intensity)].blue++;
1158 histogram[ScaleQuantumToMap(intensity)].index++;
1162 for (x=0; x < (ssize_t) image->columns; x++)
1164 if ((channel & RedChannel) != 0)
1165 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1166 if ((channel & GreenChannel) != 0)
1167 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1168 if ((channel & BlueChannel) != 0)
1169 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1170 if ((channel & OpacityChannel) != 0)
1171 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1172 if (((channel & IndexChannel) != 0) &&
1173 (image->colorspace == CMYKColorspace))
1174 histogram[ScaleQuantumToMap(GetIndexPixelComponent(
1175 indexes+x))].index++;
1180 Find the histogram boundaries by locating the black/white levels.
1183 white.red=MaxRange(QuantumRange);
1184 if ((channel & RedChannel) != 0)
1187 for (i=0; i <= (ssize_t) MaxMap; i++)
1189 intensity+=histogram[i].red;
1190 if (intensity > black_point)
1193 black.red=(MagickRealType) i;
1195 for (i=(ssize_t) MaxMap; i != 0; i--)
1197 intensity+=histogram[i].red;
1198 if (intensity > ((double) image->columns*image->rows-white_point))
1201 white.red=(MagickRealType) i;
1204 white.green=MaxRange(QuantumRange);
1205 if ((channel & GreenChannel) != 0)
1208 for (i=0; i <= (ssize_t) MaxMap; i++)
1210 intensity+=histogram[i].green;
1211 if (intensity > black_point)
1214 black.green=(MagickRealType) i;
1216 for (i=(ssize_t) MaxMap; i != 0; i--)
1218 intensity+=histogram[i].green;
1219 if (intensity > ((double) image->columns*image->rows-white_point))
1222 white.green=(MagickRealType) i;
1225 white.blue=MaxRange(QuantumRange);
1226 if ((channel & BlueChannel) != 0)
1229 for (i=0; i <= (ssize_t) MaxMap; i++)
1231 intensity+=histogram[i].blue;
1232 if (intensity > black_point)
1235 black.blue=(MagickRealType) i;
1237 for (i=(ssize_t) MaxMap; i != 0; i--)
1239 intensity+=histogram[i].blue;
1240 if (intensity > ((double) image->columns*image->rows-white_point))
1243 white.blue=(MagickRealType) i;
1246 white.opacity=MaxRange(QuantumRange);
1247 if ((channel & OpacityChannel) != 0)
1250 for (i=0; i <= (ssize_t) MaxMap; i++)
1252 intensity+=histogram[i].opacity;
1253 if (intensity > black_point)
1256 black.opacity=(MagickRealType) i;
1258 for (i=(ssize_t) MaxMap; i != 0; i--)
1260 intensity+=histogram[i].opacity;
1261 if (intensity > ((double) image->columns*image->rows-white_point))
1264 white.opacity=(MagickRealType) i;
1267 white.index=MaxRange(QuantumRange);
1268 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1271 for (i=0; i <= (ssize_t) MaxMap; i++)
1273 intensity+=histogram[i].index;
1274 if (intensity > black_point)
1277 black.index=(MagickRealType) i;
1279 for (i=(ssize_t) MaxMap; i != 0; i--)
1281 intensity+=histogram[i].index;
1282 if (intensity > ((double) image->columns*image->rows-white_point))
1285 white.index=(MagickRealType) i;
1287 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1289 Stretch the histogram to create the stretched image mapping.
1291 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
1292 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1293 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1295 for (i=0; i <= (ssize_t) MaxMap; i++)
1297 if ((channel & RedChannel) != 0)
1299 if (i < (ssize_t) black.red)
1300 stretch_map[i].red=0.0;
1302 if (i > (ssize_t) white.red)
1303 stretch_map[i].red=(MagickRealType) QuantumRange;
1305 if (black.red != white.red)
1306 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1307 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1309 if ((channel & GreenChannel) != 0)
1311 if (i < (ssize_t) black.green)
1312 stretch_map[i].green=0.0;
1314 if (i > (ssize_t) white.green)
1315 stretch_map[i].green=(MagickRealType) QuantumRange;
1317 if (black.green != white.green)
1318 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1319 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1322 if ((channel & BlueChannel) != 0)
1324 if (i < (ssize_t) black.blue)
1325 stretch_map[i].blue=0.0;
1327 if (i > (ssize_t) white.blue)
1328 stretch_map[i].blue=(MagickRealType) QuantumRange;
1330 if (black.blue != white.blue)
1331 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1332 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1335 if ((channel & OpacityChannel) != 0)
1337 if (i < (ssize_t) black.opacity)
1338 stretch_map[i].opacity=0.0;
1340 if (i > (ssize_t) white.opacity)
1341 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1343 if (black.opacity != white.opacity)
1344 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1345 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1348 if (((channel & IndexChannel) != 0) &&
1349 (image->colorspace == CMYKColorspace))
1351 if (i < (ssize_t) black.index)
1352 stretch_map[i].index=0.0;
1354 if (i > (ssize_t) white.index)
1355 stretch_map[i].index=(MagickRealType) QuantumRange;
1357 if (black.index != white.index)
1358 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1359 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1366 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1367 (image->colorspace == CMYKColorspace)))
1368 image->storage_class=DirectClass;
1369 if (image->storage_class == PseudoClass)
1374 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1375 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1377 for (i=0; i < (ssize_t) image->colors; i++)
1379 if ((channel & RedChannel) != 0)
1381 if (black.red != white.red)
1382 image->colormap[i].red=ClampToQuantum(stretch_map[
1383 ScaleQuantumToMap(image->colormap[i].red)].red);
1385 if ((channel & GreenChannel) != 0)
1387 if (black.green != white.green)
1388 image->colormap[i].green=ClampToQuantum(stretch_map[
1389 ScaleQuantumToMap(image->colormap[i].green)].green);
1391 if ((channel & BlueChannel) != 0)
1393 if (black.blue != white.blue)
1394 image->colormap[i].blue=ClampToQuantum(stretch_map[
1395 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1397 if ((channel & OpacityChannel) != 0)
1399 if (black.opacity != white.opacity)
1400 image->colormap[i].opacity=ClampToQuantum(stretch_map[
1401 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1410 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1411 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1413 for (y=0; y < (ssize_t) image->rows; y++)
1415 register IndexPacket
1418 register PixelPacket
1424 if (status == MagickFalse)
1426 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1427 if (q == (PixelPacket *) NULL)
1432 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1433 for (x=0; x < (ssize_t) image->columns; x++)
1435 if ((channel & RedChannel) != 0)
1437 if (black.red != white.red)
1438 SetRedPixelComponent(q,ClampToQuantum(stretch_map[
1439 ScaleQuantumToMap(GetRedPixelComponent(q))].red));
1441 if ((channel & GreenChannel) != 0)
1443 if (black.green != white.green)
1444 SetGreenPixelComponent(q,ClampToQuantum(stretch_map[
1445 ScaleQuantumToMap(GetGreenPixelComponent(q))].green));
1447 if ((channel & BlueChannel) != 0)
1449 if (black.blue != white.blue)
1450 SetBluePixelComponent(q,ClampToQuantum(stretch_map[
1451 ScaleQuantumToMap(GetBluePixelComponent(q))].blue));
1453 if ((channel & OpacityChannel) != 0)
1455 if (black.opacity != white.opacity)
1456 SetOpacityPixelComponent(q,ClampToQuantum(stretch_map[
1457 ScaleQuantumToMap(GetOpacityPixelComponent(q))].opacity));
1459 if (((channel & IndexChannel) != 0) &&
1460 (image->colorspace == CMYKColorspace))
1462 if (black.index != white.index)
1463 SetIndexPixelComponent(indexes+x,ClampToQuantum(stretch_map[
1464 ScaleQuantumToMap(GetIndexPixelComponent(indexes+x))].index));
1468 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1470 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1475 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1476 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1478 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1480 if (proceed == MagickFalse)
1484 image_view=DestroyCacheView(image_view);
1485 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1490 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1494 % E n h a n c e I m a g e %
1498 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1500 % EnhanceImage() applies a digital filter that improves the quality of a
1503 % The format of the EnhanceImage method is:
1505 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1507 % A description of each parameter follows:
1509 % o image: the image.
1511 % o exception: return any errors or warnings in this structure.
1514 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1516 #define Enhance(weight) \
1517 mean=((MagickRealType) GetRedPixelComponent(r)+pixel.red)/2; \
1518 distance=(MagickRealType) GetRedPixelComponent(r)-(MagickRealType) pixel.red; \
1519 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1520 mean)*distance*distance; \
1521 mean=((MagickRealType) GetGreenPixelComponent(r)+pixel.green)/2; \
1522 distance=(MagickRealType) GetGreenPixelComponent(r)-(MagickRealType) \
1524 distance_squared+=4.0*distance*distance; \
1525 mean=((MagickRealType) GetBluePixelComponent(r)+pixel.blue)/2; \
1526 distance=(MagickRealType) GetBluePixelComponent(r)-(MagickRealType) \
1528 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1529 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1530 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1531 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1532 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1533 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1534 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1535 QuantumRange/25.0f)) \
1537 aggregate.red+=(weight)*GetRedPixelComponent(r); \
1538 aggregate.green+=(weight)*GetGreenPixelComponent(r); \
1539 aggregate.blue+=(weight)*GetBluePixelComponent(r); \
1540 aggregate.opacity+=(weight)*GetOpacityPixelComponent(r); \
1541 total_weight+=(weight); \
1544 #define EnhanceImageTag "Enhance/Image"
1566 Initialize enhanced image attributes.
1568 assert(image != (const Image *) NULL);
1569 assert(image->signature == MagickSignature);
1570 if (image->debug != MagickFalse)
1571 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1572 assert(exception != (ExceptionInfo *) NULL);
1573 assert(exception->signature == MagickSignature);
1574 if ((image->columns < 5) || (image->rows < 5))
1575 return((Image *) NULL);
1576 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1578 if (enhance_image == (Image *) NULL)
1579 return((Image *) NULL);
1580 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1582 InheritException(exception,&enhance_image->exception);
1583 enhance_image=DestroyImage(enhance_image);
1584 return((Image *) NULL);
1591 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1592 image_view=AcquireCacheView(image);
1593 enhance_view=AcquireCacheView(enhance_image);
1594 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1595 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1597 for (y=0; y < (ssize_t) image->rows; y++)
1599 register const PixelPacket
1602 register PixelPacket
1609 Read another scan line.
1611 if (status == MagickFalse)
1613 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1614 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1616 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1621 for (x=0; x < (ssize_t) image->columns; x++)
1635 register const PixelPacket
1639 Compute weighted average of target pixel color components.
1643 r=p+2*(image->columns+4)+2;
1646 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1647 r=p+(image->columns+4);
1648 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1649 r=p+2*(image->columns+4);
1650 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1651 r=p+3*(image->columns+4);
1652 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1653 r=p+4*(image->columns+4);
1654 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1655 SetRedPixelComponent(q,(aggregate.red+(total_weight/2)-1)/total_weight);
1656 SetGreenPixelComponent(q,(aggregate.green+(total_weight/2)-1)/
1658 SetBluePixelComponent(q,(aggregate.blue+(total_weight/2)-1)/total_weight);
1659 SetOpacityPixelComponent(q,(aggregate.opacity+(total_weight/2)-1)/
1664 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1666 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1671 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1672 #pragma omp critical (MagickCore_EnhanceImage)
1674 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1675 if (proceed == MagickFalse)
1679 enhance_view=DestroyCacheView(enhance_view);
1680 image_view=DestroyCacheView(image_view);
1681 return(enhance_image);
1685 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1689 % E q u a l i z e I m a g e %
1693 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1695 % EqualizeImage() applies a histogram equalization to the image.
1697 % The format of the EqualizeImage method is:
1699 % MagickBooleanType EqualizeImage(Image *image)
1700 % MagickBooleanType EqualizeImageChannel(Image *image,
1701 % const ChannelType channel)
1703 % A description of each parameter follows:
1705 % o image: the image.
1707 % o channel: the channel.
1711 MagickExport MagickBooleanType EqualizeImage(Image *image)
1713 return(EqualizeImageChannel(image,DefaultChannels));
1716 MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1717 const ChannelType channel)
1719 #define EqualizeImageTag "Equalize/Image"
1748 Allocate and initialize histogram arrays.
1750 assert(image != (Image *) NULL);
1751 assert(image->signature == MagickSignature);
1752 if (image->debug != MagickFalse)
1753 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1754 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1755 sizeof(*equalize_map));
1756 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1757 sizeof(*histogram));
1758 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1759 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1760 (histogram == (MagickPixelPacket *) NULL) ||
1761 (map == (MagickPixelPacket *) NULL))
1763 if (map != (MagickPixelPacket *) NULL)
1764 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1765 if (histogram != (MagickPixelPacket *) NULL)
1766 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1767 if (equalize_map != (MagickPixelPacket *) NULL)
1768 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1769 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1775 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1776 exception=(&image->exception);
1777 for (y=0; y < (ssize_t) image->rows; y++)
1779 register const IndexPacket
1782 register const PixelPacket
1788 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1789 if (p == (const PixelPacket *) NULL)
1791 indexes=GetVirtualIndexQueue(image);
1792 for (x=0; x < (ssize_t) image->columns; x++)
1794 if ((channel & RedChannel) != 0)
1795 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1796 if ((channel & GreenChannel) != 0)
1797 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1798 if ((channel & BlueChannel) != 0)
1799 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1800 if ((channel & OpacityChannel) != 0)
1801 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1802 if (((channel & IndexChannel) != 0) &&
1803 (image->colorspace == CMYKColorspace))
1804 histogram[ScaleQuantumToMap(GetIndexPixelComponent(indexes+x))].index++;
1809 Integrate the histogram to get the equalization map.
1811 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1812 for (i=0; i <= (ssize_t) MaxMap; i++)
1814 if ((channel & RedChannel) != 0)
1815 intensity.red+=histogram[i].red;
1816 if ((channel & GreenChannel) != 0)
1817 intensity.green+=histogram[i].green;
1818 if ((channel & BlueChannel) != 0)
1819 intensity.blue+=histogram[i].blue;
1820 if ((channel & OpacityChannel) != 0)
1821 intensity.opacity+=histogram[i].opacity;
1822 if (((channel & IndexChannel) != 0) &&
1823 (image->colorspace == CMYKColorspace))
1824 intensity.index+=histogram[i].index;
1828 white=map[(int) MaxMap];
1829 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
1830 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1831 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1833 for (i=0; i <= (ssize_t) MaxMap; i++)
1835 if (((channel & RedChannel) != 0) && (white.red != black.red))
1836 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1837 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1838 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1839 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1840 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1841 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1842 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1843 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1844 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1845 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1846 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1847 (white.opacity-black.opacity)));
1848 if ((((channel & IndexChannel) != 0) &&
1849 (image->colorspace == CMYKColorspace)) &&
1850 (white.index != black.index))
1851 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1852 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1854 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1855 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1856 if (image->storage_class == PseudoClass)
1861 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1862 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1864 for (i=0; i < (ssize_t) image->colors; i++)
1866 if (((channel & RedChannel) != 0) && (white.red != black.red))
1867 image->colormap[i].red=ClampToQuantum(equalize_map[
1868 ScaleQuantumToMap(image->colormap[i].red)].red);
1869 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1870 image->colormap[i].green=ClampToQuantum(equalize_map[
1871 ScaleQuantumToMap(image->colormap[i].green)].green);
1872 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1873 image->colormap[i].blue=ClampToQuantum(equalize_map[
1874 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1875 if (((channel & OpacityChannel) != 0) &&
1876 (white.opacity != black.opacity))
1877 image->colormap[i].opacity=ClampToQuantum(equalize_map[
1878 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1886 exception=(&image->exception);
1887 image_view=AcquireCacheView(image);
1888 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1889 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1891 for (y=0; y < (ssize_t) image->rows; y++)
1893 register IndexPacket
1896 register PixelPacket
1902 if (status == MagickFalse)
1904 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1905 if (q == (PixelPacket *) NULL)
1910 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1911 for (x=0; x < (ssize_t) image->columns; x++)
1913 if (((channel & RedChannel) != 0) && (white.red != black.red))
1914 SetRedPixelComponent(q,ClampToQuantum(equalize_map[
1915 ScaleQuantumToMap(GetRedPixelComponent(q))].red));
1916 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1917 SetGreenPixelComponent(q,ClampToQuantum(equalize_map[
1918 ScaleQuantumToMap(GetGreenPixelComponent(q))].green));
1919 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1920 SetBluePixelComponent(q,ClampToQuantum(equalize_map[
1921 ScaleQuantumToMap(GetBluePixelComponent(q))].blue));
1922 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1923 SetOpacityPixelComponent(q,ClampToQuantum(equalize_map[
1924 ScaleQuantumToMap(GetOpacityPixelComponent(q))].opacity));
1925 if ((((channel & IndexChannel) != 0) &&
1926 (image->colorspace == CMYKColorspace)) &&
1927 (white.index != black.index))
1928 SetIndexPixelComponent(indexes+x,ClampToQuantum(equalize_map[
1929 ScaleQuantumToMap(GetIndexPixelComponent(indexes+x))].index));
1932 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1934 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1939 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1940 #pragma omp critical (MagickCore_EqualizeImageChannel)
1942 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1943 if (proceed == MagickFalse)
1947 image_view=DestroyCacheView(image_view);
1948 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1953 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1957 % G a m m a I m a g e %
1961 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1963 % GammaImage() gamma-corrects a particular image channel. The same
1964 % image viewed on different devices will have perceptual differences in the
1965 % way the image's intensities are represented on the screen. Specify
1966 % individual gamma levels for the red, green, and blue channels, or adjust
1967 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1969 % You can also reduce the influence of a particular channel with a gamma
1972 % The format of the GammaImage method is:
1974 % MagickBooleanType GammaImage(Image *image,const char *level)
1975 % MagickBooleanType GammaImageChannel(Image *image,
1976 % const ChannelType channel,const double gamma)
1978 % A description of each parameter follows:
1980 % o image: the image.
1982 % o channel: the channel.
1984 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1986 % o gamma: the image gamma.
1989 MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2001 assert(image != (Image *) NULL);
2002 assert(image->signature == MagickSignature);
2003 if (image->debug != MagickFalse)
2004 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2005 if (level == (char *) NULL)
2006 return(MagickFalse);
2007 flags=ParseGeometry(level,&geometry_info);
2008 gamma.red=geometry_info.rho;
2009 gamma.green=geometry_info.sigma;
2010 if ((flags & SigmaValue) == 0)
2011 gamma.green=gamma.red;
2012 gamma.blue=geometry_info.xi;
2013 if ((flags & XiValue) == 0)
2014 gamma.blue=gamma.red;
2015 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2017 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2018 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2019 GreenChannel | BlueChannel),(double) gamma.red);
2022 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2023 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2024 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2026 return(status != 0 ? MagickTrue : MagickFalse);
2029 MagickExport MagickBooleanType GammaImageChannel(Image *image,
2030 const ChannelType channel,const double gamma)
2032 #define GammaCorrectImageTag "GammaCorrect/Image"
2056 Allocate and initialize gamma maps.
2058 assert(image != (Image *) NULL);
2059 assert(image->signature == MagickSignature);
2060 if (image->debug != MagickFalse)
2061 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2064 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2065 if (gamma_map == (Quantum *) NULL)
2066 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2068 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2070 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2071 #pragma omp parallel for schedule(dynamic,4)
2073 for (i=0; i <= (ssize_t) MaxMap; i++)
2074 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
2075 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2076 if (image->storage_class == PseudoClass)
2079 Gamma-correct colormap.
2081 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2082 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2084 for (i=0; i < (ssize_t) image->colors; i++)
2086 if ((channel & RedChannel) != 0)
2087 image->colormap[i].red=gamma_map[
2088 ScaleQuantumToMap(image->colormap[i].red)];
2089 if ((channel & GreenChannel) != 0)
2090 image->colormap[i].green=gamma_map[
2091 ScaleQuantumToMap(image->colormap[i].green)];
2092 if ((channel & BlueChannel) != 0)
2093 image->colormap[i].blue=gamma_map[
2094 ScaleQuantumToMap(image->colormap[i].blue)];
2095 if ((channel & OpacityChannel) != 0)
2097 if (image->matte == MagickFalse)
2098 image->colormap[i].opacity=gamma_map[
2099 ScaleQuantumToMap(image->colormap[i].opacity)];
2101 image->colormap[i].opacity=(Quantum) QuantumRange-
2102 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2103 image->colormap[i].opacity))];
2108 Gamma-correct image.
2112 exception=(&image->exception);
2113 image_view=AcquireCacheView(image);
2114 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2115 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2117 for (y=0; y < (ssize_t) image->rows; y++)
2119 register IndexPacket
2122 register PixelPacket
2128 if (status == MagickFalse)
2130 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2131 if (q == (PixelPacket *) NULL)
2136 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2137 for (x=0; x < (ssize_t) image->columns; x++)
2139 if (channel == DefaultChannels)
2141 SetRedPixelComponent(q,gamma_map[ScaleQuantumToMap(
2142 GetRedPixelComponent(q))]);
2143 SetGreenPixelComponent(q,gamma_map[ScaleQuantumToMap(
2144 GetGreenPixelComponent(q))]);
2145 SetBluePixelComponent(q,gamma_map[ScaleQuantumToMap(
2146 GetBluePixelComponent(q))]);
2150 if ((channel & RedChannel) != 0)
2151 SetRedPixelComponent(q,gamma_map[ScaleQuantumToMap(
2152 GetRedPixelComponent(q))]);
2153 if ((channel & GreenChannel) != 0)
2154 SetGreenPixelComponent(q,gamma_map[ScaleQuantumToMap(
2155 GetGreenPixelComponent(q))]);
2156 if ((channel & BlueChannel) != 0)
2157 SetBluePixelComponent(q,gamma_map[ScaleQuantumToMap(
2158 GetBluePixelComponent(q))]);
2159 if ((channel & OpacityChannel) != 0)
2161 if (image->matte == MagickFalse)
2162 SetOpacityPixelComponent(q,gamma_map[ScaleQuantumToMap(
2163 GetOpacityPixelComponent(q))]);
2165 SetOpacityPixelComponent(q,(Quantum) QuantumRange-gamma_map[
2166 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))]);
2171 if (((channel & IndexChannel) != 0) &&
2172 (image->colorspace == CMYKColorspace))
2173 for (x=0; x < (ssize_t) image->columns; x++)
2174 SetIndexPixelComponent(indexes+x,gamma_map[ScaleQuantumToMap(
2175 GetIndexPixelComponent(indexes+x))]);
2176 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2178 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2183 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2184 #pragma omp critical (MagickCore_GammaImageChannel)
2186 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2188 if (proceed == MagickFalse)
2192 image_view=DestroyCacheView(image_view);
2193 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2194 if (image->gamma != 0.0)
2195 image->gamma*=gamma;
2200 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2204 % H a l d C l u t I m a g e %
2208 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2210 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2211 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2212 % Create it with the HALD coder. You can apply any color transformation to
2213 % the Hald image and then use this method to apply the transform to the
2216 % The format of the HaldClutImage method is:
2218 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2219 % MagickBooleanType HaldClutImageChannel(Image *image,
2220 % const ChannelType channel,Image *hald_image)
2222 % A description of each parameter follows:
2224 % o image: the image, which is replaced by indexed CLUT values
2226 % o hald_image: the color lookup table image for replacement color values.
2228 % o channel: the channel.
2232 static inline size_t MagickMin(const size_t x,const size_t y)
2239 MagickExport MagickBooleanType HaldClutImage(Image *image,
2240 const Image *hald_image)
2242 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2245 MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2246 const ChannelType channel,const Image *hald_image)
2248 #define HaldClutImageTag "Clut/Image"
2250 typedef struct _HaldInfo
2285 assert(image != (Image *) NULL);
2286 assert(image->signature == MagickSignature);
2287 if (image->debug != MagickFalse)
2288 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2289 assert(hald_image != (Image *) NULL);
2290 assert(hald_image->signature == MagickSignature);
2291 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2292 return(MagickFalse);
2293 if (image->matte == MagickFalse)
2294 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2300 length=MagickMin(hald_image->columns,hald_image->rows);
2301 for (level=2; (level*level*level) < length; level++) ;
2303 cube_size=level*level;
2304 width=(double) hald_image->columns;
2305 GetMagickPixelPacket(hald_image,&zero);
2306 exception=(&image->exception);
2307 image_view=AcquireCacheView(image);
2308 hald_view=AcquireCacheView(hald_image);
2309 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2310 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2312 for (y=0; y < (ssize_t) image->rows; y++)
2327 register IndexPacket
2330 register PixelPacket
2336 if (status == MagickFalse)
2338 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2339 if (q == (PixelPacket *) NULL)
2344 indexes=GetCacheViewAuthenticIndexQueue(hald_view);
2350 for (x=0; x < (ssize_t) image->columns; x++)
2352 point.x=QuantumScale*(level-1.0)*GetRedPixelComponent(q);
2353 point.y=QuantumScale*(level-1.0)*GetGreenPixelComponent(q);
2354 point.z=QuantumScale*(level-1.0)*GetBluePixelComponent(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) InterpolateMagickPixelPacket(image,hald_view,
2360 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2362 (void) InterpolateMagickPixelPacket(image,hald_view,
2363 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2364 width),&pixel2,exception);
2365 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2366 pixel2.opacity,point.y,&pixel3);
2368 (void) InterpolateMagickPixelPacket(image,hald_view,
2369 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2371 (void) InterpolateMagickPixelPacket(image,hald_view,
2372 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2373 width),&pixel2,exception);
2374 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2375 pixel2.opacity,point.y,&pixel4);
2376 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2377 pixel4.opacity,point.z,&pixel);
2378 if ((channel & RedChannel) != 0)
2379 SetRedPixelComponent(q,ClampToQuantum(pixel.red));
2380 if ((channel & GreenChannel) != 0)
2381 SetGreenPixelComponent(q,ClampToQuantum(pixel.green));
2382 if ((channel & BlueChannel) != 0)
2383 SetBluePixelComponent(q,ClampToQuantum(pixel.blue));
2384 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2385 SetOpacityPixelComponent(q,ClampToQuantum(pixel.opacity));
2386 if (((channel & IndexChannel) != 0) &&
2387 (image->colorspace == CMYKColorspace))
2388 SetIndexPixelComponent(indexes+x,ClampToQuantum(pixel.index));
2391 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2393 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2398 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2399 #pragma omp critical (MagickCore_HaldClutImageChannel)
2401 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2402 if (proceed == MagickFalse)
2406 hald_view=DestroyCacheView(hald_view);
2407 image_view=DestroyCacheView(image_view);
2412 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2416 % L e v e l I m a g e %
2420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2422 % LevelImage() adjusts the levels of a particular image channel by
2423 % scaling the colors falling between specified white and black points to
2424 % the full available quantum range.
2426 % The parameters provided represent the black, and white points. The black
2427 % point specifies the darkest color in the image. Colors darker than the
2428 % black point are set to zero. White point specifies the lightest color in
2429 % the image. Colors brighter than the white point are set to the maximum
2432 % If a '!' flag is given, map black and white colors to the given levels
2433 % rather than mapping those levels to black and white. See
2434 % LevelizeImageChannel() and LevelizeImageChannel(), below.
2436 % Gamma specifies a gamma correction to apply to the image.
2438 % The format of the LevelImage method is:
2440 % MagickBooleanType LevelImage(Image *image,const char *levels)
2442 % A description of each parameter follows:
2444 % o image: the image.
2446 % o levels: Specify the levels where the black and white points have the
2447 % range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2448 % A '!' flag inverts the re-mapping.
2452 MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2471 if (levels == (char *) NULL)
2472 return(MagickFalse);
2473 flags=ParseGeometry(levels,&geometry_info);
2474 black_point=geometry_info.rho;
2475 white_point=(double) QuantumRange;
2476 if ((flags & SigmaValue) != 0)
2477 white_point=geometry_info.sigma;
2479 if ((flags & XiValue) != 0)
2480 gamma=geometry_info.xi;
2481 if ((flags & PercentValue) != 0)
2483 black_point*=(double) image->columns*image->rows/100.0;
2484 white_point*=(double) image->columns*image->rows/100.0;
2486 if ((flags & SigmaValue) == 0)
2487 white_point=(double) QuantumRange-black_point;
2488 if ((flags & AspectValue ) == 0)
2489 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2492 status=LevelizeImage(image,black_point,white_point,gamma);
2497 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2501 % L e v e l i z e I m a g e %
2505 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2507 % LevelizeImage() applies the normal level operation to the image, spreading
2508 % out the values between the black and white points over the entire range of
2509 % values. Gamma correction is also applied after the values has been mapped.
2511 % It is typically used to improve image contrast, or to provide a controlled
2512 % linear threshold for the image. If the black and white points are set to
2513 % the minimum and maximum values found in the image, the image can be
2514 % normalized. or by swapping black and white values, negate the image.
2516 % The format of the LevelizeImage method is:
2518 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2519 % const double white_point,const double gamma)
2520 % MagickBooleanType LevelizeImageChannel(Image *image,
2521 % const ChannelType channel,const double black_point,
2522 % const double white_point,const double gamma)
2524 % A description of each parameter follows:
2526 % o image: the image.
2528 % o channel: the channel.
2530 % o black_point: The level which is to be mapped to zero (black)
2532 % o white_point: The level which is to be mapped to QuantiumRange (white)
2534 % o gamma: adjust gamma by this factor before mapping values.
2535 % use 1.0 for purely linear stretching of image color values
2538 MagickExport MagickBooleanType LevelImageChannel(Image *image,
2539 const ChannelType channel,const double black_point,const double white_point,
2542 #define LevelImageTag "Level/Image"
2543 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2544 pow(scale*((double) (x)-black_point),1.0/gamma)))
2568 Allocate and initialize levels map.
2570 assert(image != (Image *) NULL);
2571 assert(image->signature == MagickSignature);
2572 if (image->debug != MagickFalse)
2573 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2574 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2575 if (image->storage_class == PseudoClass)
2576 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2577 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2579 for (i=0; i < (ssize_t) image->colors; i++)
2584 if ((channel & RedChannel) != 0)
2585 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
2586 if ((channel & GreenChannel) != 0)
2587 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
2588 if ((channel & BlueChannel) != 0)
2589 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
2590 if ((channel & OpacityChannel) != 0)
2591 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
2598 exception=(&image->exception);
2599 image_view=AcquireCacheView(image);
2600 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2601 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2603 for (y=0; y < (ssize_t) image->rows; y++)
2605 register IndexPacket
2608 register PixelPacket
2614 if (status == MagickFalse)
2616 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2617 if (q == (PixelPacket *) NULL)
2622 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2623 for (x=0; x < (ssize_t) image->columns; x++)
2625 if ((channel & RedChannel) != 0)
2626 SetRedPixelComponent(q,LevelQuantum(GetRedPixelComponent(q)));
2627 if ((channel & GreenChannel) != 0)
2628 SetGreenPixelComponent(q,LevelQuantum(GetGreenPixelComponent(q)));
2629 if ((channel & BlueChannel) != 0)
2630 SetBluePixelComponent(q,LevelQuantum(GetBluePixelComponent(q)));
2631 if (((channel & OpacityChannel) != 0) &&
2632 (image->matte == MagickTrue))
2633 SetOpacityPixelComponent(q,QuantumRange-LevelQuantum(QuantumRange-
2635 if (((channel & IndexChannel) != 0) &&
2636 (image->colorspace == CMYKColorspace))
2637 SetIndexPixelComponent(indexes+x,LevelQuantum(
2638 GetIndexPixelComponent(indexes+x)));
2641 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2643 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2648 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2649 #pragma omp critical (MagickCore_LevelImageChannel)
2651 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2652 if (proceed == MagickFalse)
2656 image_view=DestroyCacheView(image_view);
2661 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2665 % L e v e l i z e I m a g e C h a n n e l %
2669 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2671 % LevelizeImageChannel() applies the reversed LevelImage() operation to just
2672 % the specific channels specified. It compresses the full range of color
2673 % values, so that they lie between the given black and white points. Gamma is
2674 % applied before the values are mapped.
2676 % LevelizeImageChannel() can be called with by using a +level command line
2677 % API option, or using a '!' on a -level or LevelImage() geometry string.
2679 % It can be used for example de-contrast a greyscale image to the exact
2680 % levels specified. Or by using specific levels for each channel of an image
2681 % you can convert a gray-scale image to any linear color gradient, according
2684 % The format of the LevelizeImageChannel method is:
2686 % MagickBooleanType LevelizeImageChannel(Image *image,
2687 % const ChannelType channel,const char *levels)
2689 % A description of each parameter follows:
2691 % o image: the image.
2693 % o channel: the channel.
2695 % o black_point: The level to map zero (black) to.
2697 % o white_point: The level to map QuantiumRange (white) to.
2699 % o gamma: adjust gamma by this factor before mapping values.
2703 MagickExport MagickBooleanType LevelizeImage(Image *image,
2704 const double black_point,const double white_point,const double gamma)
2709 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2714 MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2715 const ChannelType channel,const double black_point,const double white_point,
2718 #define LevelizeImageTag "Levelize/Image"
2719 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2720 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2742 Allocate and initialize levels map.
2744 assert(image != (Image *) NULL);
2745 assert(image->signature == MagickSignature);
2746 if (image->debug != MagickFalse)
2747 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2748 if (image->storage_class == PseudoClass)
2749 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2750 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2752 for (i=0; i < (ssize_t) image->colors; i++)
2757 if ((channel & RedChannel) != 0)
2758 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2759 if ((channel & GreenChannel) != 0)
2760 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2761 if ((channel & BlueChannel) != 0)
2762 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2763 if ((channel & OpacityChannel) != 0)
2764 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2771 exception=(&image->exception);
2772 image_view=AcquireCacheView(image);
2773 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2774 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2776 for (y=0; y < (ssize_t) image->rows; y++)
2778 register IndexPacket
2781 register PixelPacket
2787 if (status == MagickFalse)
2789 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2790 if (q == (PixelPacket *) NULL)
2795 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2796 for (x=0; x < (ssize_t) image->columns; x++)
2798 if ((channel & RedChannel) != 0)
2799 SetRedPixelComponent(q,LevelizeValue(GetRedPixelComponent(q)));
2800 if ((channel & GreenChannel) != 0)
2801 SetGreenPixelComponent(q,LevelizeValue(GetGreenPixelComponent(q)));
2802 if ((channel & BlueChannel) != 0)
2803 SetBluePixelComponent(q,LevelizeValue(GetBluePixelComponent(q)));
2804 if (((channel & OpacityChannel) != 0) &&
2805 (image->matte == MagickTrue))
2806 SetOpacityPixelComponent(q,LevelizeValue(GetOpacityPixelComponent(q)));
2807 if (((channel & IndexChannel) != 0) &&
2808 (image->colorspace == CMYKColorspace))
2809 SetIndexPixelComponent(indexes+x,LevelizeValue(
2810 GetIndexPixelComponent(indexes+x)));
2813 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2815 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2820 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2821 #pragma omp critical (MagickCore_LevelizeImageChannel)
2823 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2824 if (proceed == MagickFalse)
2828 image_view=DestroyCacheView(image_view);
2833 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2837 % L e v e l I m a g e C o l o r s %
2841 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2843 % LevelImageColor() maps the given color to "black" and "white" values,
2844 % linearly spreading out the colors, and level values on a channel by channel
2845 % bases, as per LevelImage(). The given colors allows you to specify
2846 % different level ranges for each of the color channels separately.
2848 % If the boolean 'invert' is set true the image values will modifyed in the
2849 % reverse direction. That is any existing "black" and "white" colors in the
2850 % image will become the color values given, with all other values compressed
2851 % appropriatally. This effectivally maps a greyscale gradient into the given
2854 % The format of the LevelColorsImageChannel method is:
2856 % MagickBooleanType LevelColorsImage(Image *image,
2857 % const MagickPixelPacket *black_color,
2858 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2859 % MagickBooleanType LevelColorsImageChannel(Image *image,
2860 % const ChannelType channel,const MagickPixelPacket *black_color,
2861 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2863 % A description of each parameter follows:
2865 % o image: the image.
2867 % o channel: the channel.
2869 % o black_color: The color to map black to/from
2871 % o white_point: The color to map white to/from
2873 % o invert: if true map the colors (levelize), rather than from (level)
2877 MagickExport MagickBooleanType LevelColorsImage(Image *image,
2878 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2879 const MagickBooleanType invert)
2884 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2889 MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2890 const ChannelType channel,const MagickPixelPacket *black_color,
2891 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2897 Allocate and initialize levels map.
2899 assert(image != (Image *) NULL);
2900 assert(image->signature == MagickSignature);
2901 if (image->debug != MagickFalse)
2902 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2904 if (invert == MagickFalse)
2906 if ((channel & RedChannel) != 0)
2907 status|=LevelImageChannel(image,RedChannel,
2908 black_color->red,white_color->red,(double) 1.0);
2909 if ((channel & GreenChannel) != 0)
2910 status|=LevelImageChannel(image,GreenChannel,
2911 black_color->green,white_color->green,(double) 1.0);
2912 if ((channel & BlueChannel) != 0)
2913 status|=LevelImageChannel(image,BlueChannel,
2914 black_color->blue,white_color->blue,(double) 1.0);
2915 if (((channel & OpacityChannel) != 0) &&
2916 (image->matte == MagickTrue))
2917 status|=LevelImageChannel(image,OpacityChannel,
2918 black_color->opacity,white_color->opacity,(double) 1.0);
2919 if (((channel & IndexChannel) != 0) &&
2920 (image->colorspace == CMYKColorspace))
2921 status|=LevelImageChannel(image,IndexChannel,
2922 black_color->index,white_color->index,(double) 1.0);
2926 if ((channel & RedChannel) != 0)
2927 status|=LevelizeImageChannel(image,RedChannel,
2928 black_color->red,white_color->red,(double) 1.0);
2929 if ((channel & GreenChannel) != 0)
2930 status|=LevelizeImageChannel(image,GreenChannel,
2931 black_color->green,white_color->green,(double) 1.0);
2932 if ((channel & BlueChannel) != 0)
2933 status|=LevelizeImageChannel(image,BlueChannel,
2934 black_color->blue,white_color->blue,(double) 1.0);
2935 if (((channel & OpacityChannel) != 0) &&
2936 (image->matte == MagickTrue))
2937 status|=LevelizeImageChannel(image,OpacityChannel,
2938 black_color->opacity,white_color->opacity,(double) 1.0);
2939 if (((channel & IndexChannel) != 0) &&
2940 (image->colorspace == CMYKColorspace))
2941 status|=LevelizeImageChannel(image,IndexChannel,
2942 black_color->index,white_color->index,(double) 1.0);
2944 return(status == 0 ? MagickFalse : MagickTrue);
2948 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2952 % L i n e a r S t r e t c h I m a g e %
2956 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2958 % The LinearStretchImage() discards any pixels below the black point and
2959 % above the white point and levels the remaining pixels.
2961 % The format of the LinearStretchImage method is:
2963 % MagickBooleanType LinearStretchImage(Image *image,
2964 % const double black_point,const double white_point)
2966 % A description of each parameter follows:
2968 % o image: the image.
2970 % o black_point: the black point.
2972 % o white_point: the white point.
2975 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2976 const double black_point,const double white_point)
2978 #define LinearStretchImageTag "LinearStretch/Image"
2996 Allocate histogram and linear map.
2998 assert(image != (Image *) NULL);
2999 assert(image->signature == MagickSignature);
3000 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3001 sizeof(*histogram));
3002 if (histogram == (MagickRealType *) NULL)
3003 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3008 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
3009 exception=(&image->exception);
3010 for (y=0; y < (ssize_t) image->rows; y++)
3012 register const PixelPacket
3018 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3019 if (p == (const PixelPacket *) NULL)
3021 for (x=(ssize_t) image->columns-1; x >= 0; x--)
3023 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3028 Find the histogram boundaries by locating the black and white point levels.
3031 for (black=0; black < (ssize_t) MaxMap; black++)
3033 intensity+=histogram[black];
3034 if (intensity >= black_point)
3038 for (white=(ssize_t) MaxMap; white != 0; white--)
3040 intensity+=histogram[white];
3041 if (intensity >= white_point)
3044 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3045 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3051 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3055 % M o d u l a t e I m a g e %
3059 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3061 % ModulateImage() lets you control the brightness, saturation, and hue
3062 % of an image. Modulate represents the brightness, saturation, and hue
3063 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3064 % modulation is lightness, saturation, and hue. And if the colorspace is
3065 % HWB, use blackness, whiteness, and hue.
3067 % The format of the ModulateImage method is:
3069 % MagickBooleanType ModulateImage(Image *image,const char *modulate)
3071 % A description of each parameter follows:
3073 % o image: the image.
3075 % o modulate: Define the percent change in brightness, saturation, and
3080 static void ModulateHSB(const double percent_hue,
3081 const double percent_saturation,const double percent_brightness,
3082 Quantum *red,Quantum *green,Quantum *blue)
3090 Increase or decrease color brightness, saturation, or hue.
3092 assert(red != (Quantum *) NULL);
3093 assert(green != (Quantum *) NULL);
3094 assert(blue != (Quantum *) NULL);
3095 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3096 hue+=0.5*(0.01*percent_hue-1.0);
3101 saturation*=0.01*percent_saturation;
3102 brightness*=0.01*percent_brightness;
3103 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3106 static void ModulateHSL(const double percent_hue,
3107 const double percent_saturation,const double percent_lightness,
3108 Quantum *red,Quantum *green,Quantum *blue)
3116 Increase or decrease color lightness, saturation, or hue.
3118 assert(red != (Quantum *) NULL);
3119 assert(green != (Quantum *) NULL);
3120 assert(blue != (Quantum *) NULL);
3121 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3122 hue+=0.5*(0.01*percent_hue-1.0);
3127 saturation*=0.01*percent_saturation;
3128 lightness*=0.01*percent_lightness;
3129 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3132 static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3140 Increase or decrease color blackness, whiteness, or hue.
3142 assert(red != (Quantum *) NULL);
3143 assert(green != (Quantum *) NULL);
3144 assert(blue != (Quantum *) NULL);
3145 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3146 hue+=0.5*(0.01*percent_hue-1.0);
3151 blackness*=0.01*percent_blackness;
3152 whiteness*=0.01*percent_whiteness;
3153 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3156 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3158 #define ModulateImageTag "Modulate/Image"
3196 Initialize modulate table.
3198 assert(image != (Image *) NULL);
3199 assert(image->signature == MagickSignature);
3200 if (image->debug != MagickFalse)
3201 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3202 if (modulate == (char *) NULL)
3203 return(MagickFalse);
3204 flags=ParseGeometry(modulate,&geometry_info);
3205 percent_brightness=geometry_info.rho;
3206 percent_saturation=geometry_info.sigma;
3207 if ((flags & SigmaValue) == 0)
3208 percent_saturation=100.0;
3209 percent_hue=geometry_info.xi;
3210 if ((flags & XiValue) == 0)
3212 colorspace=UndefinedColorspace;
3213 artifact=GetImageArtifact(image,"modulate:colorspace");
3214 if (artifact != (const char *) NULL)
3215 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3216 MagickFalse,artifact);
3217 if (image->storage_class == PseudoClass)
3222 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3223 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3225 for (i=0; i < (ssize_t) image->colors; i++)
3230 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3231 &image->colormap[i].red,&image->colormap[i].green,
3232 &image->colormap[i].blue);
3238 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3239 &image->colormap[i].red,&image->colormap[i].green,
3240 &image->colormap[i].blue);
3245 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3246 &image->colormap[i].red,&image->colormap[i].green,
3247 &image->colormap[i].blue);
3257 exception=(&image->exception);
3258 image_view=AcquireCacheView(image);
3259 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3260 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3262 for (y=0; y < (ssize_t) image->rows; y++)
3269 register PixelPacket
3275 if (status == MagickFalse)
3277 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3278 if (q == (PixelPacket *) NULL)
3283 for (x=0; x < (ssize_t) image->columns; x++)
3285 red=GetRedPixelComponent(q);
3286 green=GetGreenPixelComponent(q);
3287 blue=GetBluePixelComponent(q);
3292 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3299 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3305 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3310 SetRedPixelComponent(q,red);
3311 SetGreenPixelComponent(q,green);
3312 SetBluePixelComponent(q,blue);
3315 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3317 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3322 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3323 #pragma omp critical (MagickCore_ModulateImage)
3325 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3326 if (proceed == MagickFalse)
3330 image_view=DestroyCacheView(image_view);
3335 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3339 % N e g a t e I m a g e %
3343 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3345 % NegateImage() negates the colors in the reference image. The grayscale
3346 % option means that only grayscale values within the image are negated.
3348 % The format of the NegateImageChannel method is:
3350 % MagickBooleanType NegateImage(Image *image,
3351 % const MagickBooleanType grayscale)
3352 % MagickBooleanType NegateImageChannel(Image *image,
3353 % const ChannelType channel,const MagickBooleanType grayscale)
3355 % A description of each parameter follows:
3357 % o image: the image.
3359 % o channel: the channel.
3361 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3365 MagickExport MagickBooleanType NegateImage(Image *image,
3366 const MagickBooleanType grayscale)
3371 status=NegateImageChannel(image,DefaultChannels,grayscale);
3375 MagickExport MagickBooleanType NegateImageChannel(Image *image,
3376 const ChannelType channel,const MagickBooleanType grayscale)
3378 #define NegateImageTag "Negate/Image"
3398 assert(image != (Image *) NULL);
3399 assert(image->signature == MagickSignature);
3400 if (image->debug != MagickFalse)
3401 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3402 if (image->storage_class == PseudoClass)
3407 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3408 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3410 for (i=0; i < (ssize_t) image->colors; i++)
3412 if (grayscale != MagickFalse)
3413 if ((image->colormap[i].red != image->colormap[i].green) ||
3414 (image->colormap[i].green != image->colormap[i].blue))
3416 if ((channel & RedChannel) != 0)
3417 image->colormap[i].red=(Quantum) QuantumRange-
3418 image->colormap[i].red;
3419 if ((channel & GreenChannel) != 0)
3420 image->colormap[i].green=(Quantum) QuantumRange-
3421 image->colormap[i].green;
3422 if ((channel & BlueChannel) != 0)
3423 image->colormap[i].blue=(Quantum) QuantumRange-
3424 image->colormap[i].blue;
3432 exception=(&image->exception);
3433 image_view=AcquireCacheView(image);
3434 if (grayscale != MagickFalse)
3436 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3437 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3439 for (y=0; y < (ssize_t) image->rows; y++)
3444 register IndexPacket
3447 register PixelPacket
3453 if (status == MagickFalse)
3455 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3457 if (q == (PixelPacket *) NULL)
3462 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3463 for (x=0; x < (ssize_t) image->columns; x++)
3465 if ((GetRedPixelComponent(q) != GetGreenPixelComponent(q)) ||
3466 (GetGreenPixelComponent(q) != GetBluePixelComponent(q)))
3471 if ((channel & RedChannel) != 0)
3472 SetRedPixelComponent(q,QuantumRange-GetRedPixelComponent(q));
3473 if ((channel & GreenChannel) != 0)
3474 SetGreenPixelComponent(q,QuantumRange-GetGreenPixelComponent(q));
3475 if ((channel & BlueChannel) != 0)
3476 SetBluePixelComponent(q,QuantumRange-GetBluePixelComponent(q));
3477 if ((channel & OpacityChannel) != 0)
3478 SetOpacityPixelComponent(q,QuantumRange-
3479 GetOpacityPixelComponent(q));
3480 if (((channel & IndexChannel) != 0) &&
3481 (image->colorspace == CMYKColorspace))
3482 SetIndexPixelComponent(indexes+x,QuantumRange-
3483 GetIndexPixelComponent(indexes+x));
3486 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3487 if (sync == MagickFalse)
3489 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3494 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3495 #pragma omp critical (MagickCore_NegateImageChannel)
3497 proceed=SetImageProgress(image,NegateImageTag,progress++,
3499 if (proceed == MagickFalse)
3503 image_view=DestroyCacheView(image_view);
3509 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3510 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3512 for (y=0; y < (ssize_t) image->rows; y++)
3514 register IndexPacket
3517 register PixelPacket
3523 if (status == MagickFalse)
3525 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3526 if (q == (PixelPacket *) NULL)
3531 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3532 for (x=0; x < (ssize_t) image->columns; x++)
3534 if ((channel & RedChannel) != 0)
3535 SetRedPixelComponent(q,QuantumRange-GetRedPixelComponent(q));
3536 if ((channel & GreenChannel) != 0)
3537 SetGreenPixelComponent(q,QuantumRange-GetGreenPixelComponent(q));
3538 if ((channel & BlueChannel) != 0)
3539 SetBluePixelComponent(q,QuantumRange-GetBluePixelComponent(q));
3540 if ((channel & OpacityChannel) != 0)
3541 SetOpacityPixelComponent(q,QuantumRange-GetOpacityPixelComponent(q));
3542 if (((channel & IndexChannel) != 0) &&
3543 (image->colorspace == CMYKColorspace))
3544 SetIndexPixelComponent(indexes+x,QuantumRange-
3545 GetIndexPixelComponent(indexes+x));
3548 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3550 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3555 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3556 #pragma omp critical (MagickCore_NegateImageChannel)
3558 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3559 if (proceed == MagickFalse)
3563 image_view=DestroyCacheView(image_view);
3568 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3572 % N o r m a l i z e I m a g e %
3576 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3578 % The NormalizeImage() method enhances the contrast of a color image by
3579 % mapping the darkest 2 percent of all pixel to black and the brightest
3580 % 1 percent to white.
3582 % The format of the NormalizeImage method is:
3584 % MagickBooleanType NormalizeImage(Image *image)
3585 % MagickBooleanType NormalizeImageChannel(Image *image,
3586 % const ChannelType channel)
3588 % A description of each parameter follows:
3590 % o image: the image.
3592 % o channel: the channel.
3596 MagickExport MagickBooleanType NormalizeImage(Image *image)
3601 status=NormalizeImageChannel(image,DefaultChannels);
3605 MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3606 const ChannelType channel)
3612 black_point=(double) image->columns*image->rows*0.0015;
3613 white_point=(double) image->columns*image->rows*0.9995;
3614 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3618 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3622 % S i g m o i d a l C o n t r a s t I m a g e %
3626 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3628 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3629 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3630 % sigmoidal transfer function without saturating highlights or shadows.
3631 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3632 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3633 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3634 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3637 % The format of the SigmoidalContrastImage method is:
3639 % MagickBooleanType SigmoidalContrastImage(Image *image,
3640 % const MagickBooleanType sharpen,const char *levels)
3641 % MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3642 % const ChannelType channel,const MagickBooleanType sharpen,
3643 % const double contrast,const double midpoint)
3645 % A description of each parameter follows:
3647 % o image: the image.
3649 % o channel: the channel.
3651 % o sharpen: Increase or decrease image contrast.
3653 % o alpha: strength of the contrast, the larger the number the more
3654 % 'threshold-like' it becomes.
3656 % o beta: midpoint of the function as a color value 0 to QuantumRange.
3660 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3661 const MagickBooleanType sharpen,const char *levels)
3672 flags=ParseGeometry(levels,&geometry_info);
3673 if ((flags & SigmaValue) == 0)
3674 geometry_info.sigma=1.0*QuantumRange/2.0;
3675 if ((flags & PercentValue) != 0)
3676 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3677 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3678 geometry_info.rho,geometry_info.sigma);
3682 MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3683 const ChannelType channel,const MagickBooleanType sharpen,
3684 const double contrast,const double midpoint)
3686 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3710 Allocate and initialize sigmoidal maps.
3712 assert(image != (Image *) NULL);
3713 assert(image->signature == MagickSignature);
3714 if (image->debug != MagickFalse)
3715 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3716 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3717 sizeof(*sigmoidal_map));
3718 if (sigmoidal_map == (MagickRealType *) NULL)
3719 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3721 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3722 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3723 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3725 for (i=0; i <= (ssize_t) MaxMap; i++)
3727 if (sharpen != MagickFalse)
3729 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3730 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3731 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3732 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3733 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3734 (double) QuantumRange)))))+0.5));
3737 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3738 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3739 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3740 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3741 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3742 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3743 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3744 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3745 (double) QuantumRange*contrast))))))/contrast)));
3747 if (image->storage_class == PseudoClass)
3750 Sigmoidal-contrast enhance colormap.
3752 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3753 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3755 for (i=0; i < (ssize_t) image->colors; i++)
3757 if ((channel & RedChannel) != 0)
3758 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
3759 ScaleQuantumToMap(image->colormap[i].red)]);
3760 if ((channel & GreenChannel) != 0)
3761 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
3762 ScaleQuantumToMap(image->colormap[i].green)]);
3763 if ((channel & BlueChannel) != 0)
3764 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
3765 ScaleQuantumToMap(image->colormap[i].blue)]);
3766 if ((channel & OpacityChannel) != 0)
3767 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
3768 ScaleQuantumToMap(image->colormap[i].opacity)]);
3772 Sigmoidal-contrast enhance image.
3776 exception=(&image->exception);
3777 image_view=AcquireCacheView(image);
3778 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3779 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3781 for (y=0; y < (ssize_t) image->rows; y++)
3783 register IndexPacket
3786 register PixelPacket
3792 if (status == MagickFalse)
3794 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3795 if (q == (PixelPacket *) NULL)
3800 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3801 for (x=0; x < (ssize_t) image->columns; x++)
3803 if ((channel & RedChannel) != 0)
3804 SetRedPixelComponent(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3805 GetRedPixelComponent(q))]));
3806 if ((channel & GreenChannel) != 0)
3807 SetGreenPixelComponent(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3808 GetGreenPixelComponent(q))]));
3809 if ((channel & BlueChannel) != 0)
3810 SetBluePixelComponent(q,ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(
3811 GetBluePixelComponent(q))]));
3812 if ((channel & OpacityChannel) != 0)
3813 SetOpacityPixelComponent(q,ClampToQuantum(sigmoidal_map[
3814 ScaleQuantumToMap(GetOpacityPixelComponent(q))]));
3815 if (((channel & IndexChannel) != 0) &&
3816 (image->colorspace == CMYKColorspace))
3817 SetIndexPixelComponent(indexes+x,ClampToQuantum(sigmoidal_map[
3818 ScaleQuantumToMap(GetIndexPixelComponent(indexes+x))]));
3821 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3823 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3828 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3829 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3831 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3833 if (proceed == MagickFalse)
3837 image_view=DestroyCacheView(image_view);
3838 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);