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*q->red+0.7152*q->green+0.0722*q->blue;
616 q->red=ClampToQuantum(luma+color_correction.saturation*
617 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
618 q->green=ClampToQuantum(luma+color_correction.saturation*
619 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
620 q->blue=ClampToQuantum(luma+color_correction.saturation*
621 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
624 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
626 if (image->progress_monitor != (MagickProgressMonitor) NULL)
631 #if defined(MAGICKCORE_OPENMP_SUPPORT)
632 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
634 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
635 progress++,image->rows);
636 if (proceed == MagickFalse)
640 image_view=DestroyCacheView(image_view);
641 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
646 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
650 % C l u t I m a g e %
654 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
656 % ClutImage() replaces each color value in the given image, by using it as an
657 % index to lookup a replacement color value in a Color Look UP Table in the
658 % form of an image. The values are extracted along a diagonal of the CLUT
659 % image so either a horizontal or vertial gradient image can be used.
661 % Typically this is used to either re-color a gray-scale image according to a
662 % color gradient in the CLUT image, or to perform a freeform histogram
663 % (level) adjustment according to the (typically gray-scale) gradient in the
666 % When the 'channel' mask includes the matte/alpha transparency channel but
667 % one image has no such channel it is assumed that that image is a simple
668 % gray-scale image that will effect the alpha channel values, either for
669 % gray-scale coloring (with transparent or semi-transparent colors), or
670 % a histogram adjustment of existing alpha channel values. If both images
671 % have matte channels, direct and normal indexing is applied, which is rarely
674 % The format of the ClutImage method is:
676 % MagickBooleanType ClutImage(Image *image,Image *clut_image)
677 % MagickBooleanType ClutImageChannel(Image *image,
678 % const ChannelType channel,Image *clut_image)
680 % A description of each parameter follows:
682 % o image: the image, which is replaced by indexed CLUT values
684 % o clut_image: the color lookup table image for replacement color values.
686 % o channel: the channel.
690 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
692 return(ClutImageChannel(image,DefaultChannels,clut_image));
695 MagickExport MagickBooleanType ClutImageChannel(Image *image,
696 const ChannelType channel,const Image *clut_image)
698 #define ClutImageTag "Clut/Image"
723 assert(image != (Image *) NULL);
724 assert(image->signature == MagickSignature);
725 if (image->debug != MagickFalse)
726 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
727 assert(clut_image != (Image *) NULL);
728 assert(clut_image->signature == MagickSignature);
729 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
731 clut_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
733 if (clut_map == (MagickPixelPacket *) NULL)
734 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
741 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
742 exception=(&image->exception);
743 clut_view=AcquireCacheView(clut_image);
744 #if defined(MAGICKCORE_OPENMP_SUPPORT)
745 #pragma omp parallel for schedule(dynamic,4)
747 for (i=0; i <= (ssize_t) MaxMap; i++)
749 GetMagickPixelPacket(clut_image,clut_map+i);
750 (void) InterpolateMagickPixelPacket(clut_image,clut_view,
751 UndefinedInterpolatePixel,QuantumScale*i*(clut_image->columns-adjust),
752 QuantumScale*i*(clut_image->rows-adjust),clut_map+i,exception);
754 clut_view=DestroyCacheView(clut_view);
755 image_view=AcquireCacheView(image);
756 #if defined(MAGICKCORE_OPENMP_SUPPORT)
757 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
759 for (y=0; y < (ssize_t) image->rows; y++)
773 if (status == MagickFalse)
775 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
776 if (q == (PixelPacket *) NULL)
781 indexes=GetCacheViewAuthenticIndexQueue(image_view);
782 GetMagickPixelPacket(image,&pixel);
783 for (x=0; x < (ssize_t) image->columns; x++)
785 SetMagickPixelPacket(image,q,indexes+x,&pixel);
786 if ((channel & RedChannel) != 0)
787 SetRedPixelComponent(q,ClampRedPixelComponent(clut_map+
788 ScaleQuantumToMap(q->red)));
789 if ((channel & GreenChannel) != 0)
790 SetGreenPixelComponent(q,ClampGreenPixelComponent(clut_map+
791 ScaleQuantumToMap(q->green)));
792 if ((channel & BlueChannel) != 0)
793 SetBluePixelComponent(q,ClampBluePixelComponent(clut_map+
794 ScaleQuantumToMap(q->blue)));
795 if ((channel & OpacityChannel) != 0)
797 if (clut_image->matte == MagickFalse)
798 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
799 clut_map+ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))));
801 if (image->matte == MagickFalse)
802 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(clut_map+
803 ScaleQuantumToMap((Quantum) MagickPixelIntensity(&pixel))));
805 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(
806 clut_map+ScaleQuantumToMap(q->opacity)));
808 if (((channel & IndexChannel) != 0) &&
809 (image->colorspace == CMYKColorspace))
810 indexes[x]=ClampToQuantum((clut_map+(ssize_t) indexes[x])->index);
813 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
815 if (image->progress_monitor != (MagickProgressMonitor) NULL)
820 #if defined(MAGICKCORE_OPENMP_SUPPORT)
821 #pragma omp critical (MagickCore_ClutImageChannel)
823 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
824 if (proceed == MagickFalse)
828 image_view=DestroyCacheView(image_view);
829 clut_map=(MagickPixelPacket *) RelinquishMagickMemory(clut_map);
830 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
831 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
836 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
840 % C o n t r a s t I m a g e %
844 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
846 % ContrastImage() enhances the intensity differences between the lighter and
847 % darker elements of the image. Set sharpen to a MagickTrue to increase the
848 % image contrast otherwise the contrast is reduced.
850 % The format of the ContrastImage method is:
852 % MagickBooleanType ContrastImage(Image *image,
853 % const MagickBooleanType sharpen)
855 % A description of each parameter follows:
857 % o image: the image.
859 % o sharpen: Increase or decrease image contrast.
863 static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
871 Enhance contrast: dark color become darker, light color become lighter.
873 assert(red != (Quantum *) NULL);
874 assert(green != (Quantum *) NULL);
875 assert(blue != (Quantum *) NULL);
879 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
880 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
882 if (brightness > 1.0)
885 if (brightness < 0.0)
887 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
890 MagickExport MagickBooleanType ContrastImage(Image *image,
891 const MagickBooleanType sharpen)
893 #define ContrastImageTag "Contrast/Image"
916 assert(image != (Image *) NULL);
917 assert(image->signature == MagickSignature);
918 if (image->debug != MagickFalse)
919 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
920 sign=sharpen != MagickFalse ? 1 : -1;
921 if (image->storage_class == PseudoClass)
924 Contrast enhance colormap.
926 for (i=0; i < (ssize_t) image->colors; i++)
927 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
928 &image->colormap[i].blue);
931 Contrast enhance image.
935 exception=(&image->exception);
936 image_view=AcquireCacheView(image);
937 #if defined(MAGICKCORE_OPENMP_SUPPORT)
938 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
940 for (y=0; y < (ssize_t) image->rows; y++)
948 if (status == MagickFalse)
950 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
951 if (q == (PixelPacket *) NULL)
956 for (x=0; x < (ssize_t) image->columns; x++)
958 Contrast(sign,&q->red,&q->green,&q->blue);
961 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
963 if (image->progress_monitor != (MagickProgressMonitor) NULL)
968 #if defined(MAGICKCORE_OPENMP_SUPPORT)
969 #pragma omp critical (MagickCore_ContrastImage)
971 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
972 if (proceed == MagickFalse)
976 image_view=DestroyCacheView(image_view);
981 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985 % C o n t r a s t S t r e t c h I m a g e %
989 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
991 % The ContrastStretchImage() is a simple image enhancement technique that
992 % attempts to improve the contrast in an image by `stretching' the range of
993 % intensity values it contains to span a desired range of values. It differs
994 % from the more sophisticated histogram equalization in that it can only
995 % apply % a linear scaling function to the image pixel values. As a result
996 % the `enhancement' is less harsh.
998 % The format of the ContrastStretchImage method is:
1000 % MagickBooleanType ContrastStretchImage(Image *image,
1001 % const char *levels)
1002 % MagickBooleanType ContrastStretchImageChannel(Image *image,
1003 % const size_t channel,const double black_point,
1004 % const double white_point)
1006 % A description of each parameter follows:
1008 % o image: the image.
1010 % o channel: the channel.
1012 % o black_point: the black point.
1014 % o white_point: the white point.
1016 % o levels: Specify the levels where the black and white points have the
1017 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1021 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1040 if (levels == (char *) NULL)
1041 return(MagickFalse);
1042 flags=ParseGeometry(levels,&geometry_info);
1043 black_point=geometry_info.rho;
1044 white_point=(double) image->columns*image->rows;
1045 if ((flags & SigmaValue) != 0)
1046 white_point=geometry_info.sigma;
1047 if ((flags & PercentValue) != 0)
1049 black_point*=(double) QuantumRange/100.0;
1050 white_point*=(double) QuantumRange/100.0;
1052 if ((flags & SigmaValue) == 0)
1053 white_point=(double) image->columns*image->rows-black_point;
1054 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1059 MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1060 const ChannelType channel,const double black_point,const double white_point)
1062 #define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1063 #define ContrastStretchImageTag "ContrastStretch/Image"
1093 Allocate histogram and stretch map.
1095 assert(image != (Image *) NULL);
1096 assert(image->signature == MagickSignature);
1097 if (image->debug != MagickFalse)
1098 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1099 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1100 sizeof(*histogram));
1101 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1102 sizeof(*stretch_map));
1103 if ((histogram == (MagickPixelPacket *) NULL) ||
1104 (stretch_map == (MagickPixelPacket *) NULL))
1105 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1111 exception=(&image->exception);
1112 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1113 image_view=AcquireCacheView(image);
1114 for (y=0; y < (ssize_t) image->rows; y++)
1116 register const PixelPacket
1119 register IndexPacket
1125 if (status == MagickFalse)
1127 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1128 if (p == (const PixelPacket *) NULL)
1133 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1134 if (channel == DefaultChannels)
1135 for (x=0; x < (ssize_t) image->columns; x++)
1140 intensity=PixelIntensityToQuantum(p);
1141 histogram[ScaleQuantumToMap(intensity)].red++;
1142 histogram[ScaleQuantumToMap(intensity)].green++;
1143 histogram[ScaleQuantumToMap(intensity)].blue++;
1144 histogram[ScaleQuantumToMap(intensity)].index++;
1148 for (x=0; x < (ssize_t) image->columns; x++)
1150 if ((channel & RedChannel) != 0)
1151 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1152 if ((channel & GreenChannel) != 0)
1153 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1154 if ((channel & BlueChannel) != 0)
1155 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1156 if ((channel & OpacityChannel) != 0)
1157 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1158 if (((channel & IndexChannel) != 0) &&
1159 (image->colorspace == CMYKColorspace))
1160 histogram[ScaleQuantumToMap(indexes[x])].index++;
1165 Find the histogram boundaries by locating the black/white levels.
1168 white.red=MaxRange(QuantumRange);
1169 if ((channel & RedChannel) != 0)
1172 for (i=0; i <= (ssize_t) MaxMap; i++)
1174 intensity+=histogram[i].red;
1175 if (intensity > black_point)
1178 black.red=(MagickRealType) i;
1180 for (i=(ssize_t) MaxMap; i != 0; i--)
1182 intensity+=histogram[i].red;
1183 if (intensity > ((double) image->columns*image->rows-white_point))
1186 white.red=(MagickRealType) i;
1189 white.green=MaxRange(QuantumRange);
1190 if ((channel & GreenChannel) != 0)
1193 for (i=0; i <= (ssize_t) MaxMap; i++)
1195 intensity+=histogram[i].green;
1196 if (intensity > black_point)
1199 black.green=(MagickRealType) i;
1201 for (i=(ssize_t) MaxMap; i != 0; i--)
1203 intensity+=histogram[i].green;
1204 if (intensity > ((double) image->columns*image->rows-white_point))
1207 white.green=(MagickRealType) i;
1210 white.blue=MaxRange(QuantumRange);
1211 if ((channel & BlueChannel) != 0)
1214 for (i=0; i <= (ssize_t) MaxMap; i++)
1216 intensity+=histogram[i].blue;
1217 if (intensity > black_point)
1220 black.blue=(MagickRealType) i;
1222 for (i=(ssize_t) MaxMap; i != 0; i--)
1224 intensity+=histogram[i].blue;
1225 if (intensity > ((double) image->columns*image->rows-white_point))
1228 white.blue=(MagickRealType) i;
1231 white.opacity=MaxRange(QuantumRange);
1232 if ((channel & OpacityChannel) != 0)
1235 for (i=0; i <= (ssize_t) MaxMap; i++)
1237 intensity+=histogram[i].opacity;
1238 if (intensity > black_point)
1241 black.opacity=(MagickRealType) i;
1243 for (i=(ssize_t) MaxMap; i != 0; i--)
1245 intensity+=histogram[i].opacity;
1246 if (intensity > ((double) image->columns*image->rows-white_point))
1249 white.opacity=(MagickRealType) i;
1252 white.index=MaxRange(QuantumRange);
1253 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1256 for (i=0; i <= (ssize_t) MaxMap; i++)
1258 intensity+=histogram[i].index;
1259 if (intensity > black_point)
1262 black.index=(MagickRealType) i;
1264 for (i=(ssize_t) MaxMap; i != 0; i--)
1266 intensity+=histogram[i].index;
1267 if (intensity > ((double) image->columns*image->rows-white_point))
1270 white.index=(MagickRealType) i;
1272 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1274 Stretch the histogram to create the stretched image mapping.
1276 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
1277 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1278 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1280 for (i=0; i <= (ssize_t) MaxMap; i++)
1282 if ((channel & RedChannel) != 0)
1284 if (i < (ssize_t) black.red)
1285 stretch_map[i].red=0.0;
1287 if (i > (ssize_t) white.red)
1288 stretch_map[i].red=(MagickRealType) QuantumRange;
1290 if (black.red != white.red)
1291 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1292 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1294 if ((channel & GreenChannel) != 0)
1296 if (i < (ssize_t) black.green)
1297 stretch_map[i].green=0.0;
1299 if (i > (ssize_t) white.green)
1300 stretch_map[i].green=(MagickRealType) QuantumRange;
1302 if (black.green != white.green)
1303 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1304 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1307 if ((channel & BlueChannel) != 0)
1309 if (i < (ssize_t) black.blue)
1310 stretch_map[i].blue=0.0;
1312 if (i > (ssize_t) white.blue)
1313 stretch_map[i].blue=(MagickRealType) QuantumRange;
1315 if (black.blue != white.blue)
1316 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1317 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1320 if ((channel & OpacityChannel) != 0)
1322 if (i < (ssize_t) black.opacity)
1323 stretch_map[i].opacity=0.0;
1325 if (i > (ssize_t) white.opacity)
1326 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1328 if (black.opacity != white.opacity)
1329 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1330 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1333 if (((channel & IndexChannel) != 0) &&
1334 (image->colorspace == CMYKColorspace))
1336 if (i < (ssize_t) black.index)
1337 stretch_map[i].index=0.0;
1339 if (i > (ssize_t) white.index)
1340 stretch_map[i].index=(MagickRealType) QuantumRange;
1342 if (black.index != white.index)
1343 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1344 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1351 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1352 (image->colorspace == CMYKColorspace)))
1353 image->storage_class=DirectClass;
1354 if (image->storage_class == PseudoClass)
1359 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1360 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1362 for (i=0; i < (ssize_t) image->colors; i++)
1364 if ((channel & RedChannel) != 0)
1366 if (black.red != white.red)
1367 image->colormap[i].red=ClampToQuantum(stretch_map[
1368 ScaleQuantumToMap(image->colormap[i].red)].red);
1370 if ((channel & GreenChannel) != 0)
1372 if (black.green != white.green)
1373 image->colormap[i].green=ClampToQuantum(stretch_map[
1374 ScaleQuantumToMap(image->colormap[i].green)].green);
1376 if ((channel & BlueChannel) != 0)
1378 if (black.blue != white.blue)
1379 image->colormap[i].blue=ClampToQuantum(stretch_map[
1380 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1382 if ((channel & OpacityChannel) != 0)
1384 if (black.opacity != white.opacity)
1385 image->colormap[i].opacity=ClampToQuantum(stretch_map[
1386 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1395 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1396 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1398 for (y=0; y < (ssize_t) image->rows; y++)
1400 register IndexPacket
1403 register PixelPacket
1409 if (status == MagickFalse)
1411 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1412 if (q == (PixelPacket *) NULL)
1417 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1418 for (x=0; x < (ssize_t) image->columns; x++)
1420 if ((channel & RedChannel) != 0)
1422 if (black.red != white.red)
1423 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
1425 if ((channel & GreenChannel) != 0)
1427 if (black.green != white.green)
1428 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1431 if ((channel & BlueChannel) != 0)
1433 if (black.blue != white.blue)
1434 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1437 if ((channel & OpacityChannel) != 0)
1439 if (black.opacity != white.opacity)
1440 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1441 q->opacity)].opacity);
1443 if (((channel & IndexChannel) != 0) &&
1444 (image->colorspace == CMYKColorspace))
1446 if (black.index != white.index)
1447 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
1448 ScaleQuantumToMap(indexes[x])].index);
1452 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1454 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1459 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1460 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1462 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1464 if (proceed == MagickFalse)
1468 image_view=DestroyCacheView(image_view);
1469 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1474 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1478 % E n h a n c e I m a g e %
1482 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1484 % EnhanceImage() applies a digital filter that improves the quality of a
1487 % The format of the EnhanceImage method is:
1489 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1491 % A description of each parameter follows:
1493 % o image: the image.
1495 % o exception: return any errors or warnings in this structure.
1498 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1500 #define Enhance(weight) \
1501 mean=((MagickRealType) r->red+pixel.red)/2; \
1502 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1503 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1504 mean)*distance*distance; \
1505 mean=((MagickRealType) r->green+pixel.green)/2; \
1506 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1507 distance_squared+=4.0*distance*distance; \
1508 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1509 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1510 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1511 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1512 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1513 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1514 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1515 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1516 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1517 QuantumRange/25.0f)) \
1519 aggregate.red+=(weight)*r->red; \
1520 aggregate.green+=(weight)*r->green; \
1521 aggregate.blue+=(weight)*r->blue; \
1522 aggregate.opacity+=(weight)*r->opacity; \
1523 total_weight+=(weight); \
1526 #define EnhanceImageTag "Enhance/Image"
1548 Initialize enhanced image attributes.
1550 assert(image != (const Image *) NULL);
1551 assert(image->signature == MagickSignature);
1552 if (image->debug != MagickFalse)
1553 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1554 assert(exception != (ExceptionInfo *) NULL);
1555 assert(exception->signature == MagickSignature);
1556 if ((image->columns < 5) || (image->rows < 5))
1557 return((Image *) NULL);
1558 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1560 if (enhance_image == (Image *) NULL)
1561 return((Image *) NULL);
1562 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1564 InheritException(exception,&enhance_image->exception);
1565 enhance_image=DestroyImage(enhance_image);
1566 return((Image *) NULL);
1573 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1574 image_view=AcquireCacheView(image);
1575 enhance_view=AcquireCacheView(enhance_image);
1576 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1577 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1579 for (y=0; y < (ssize_t) image->rows; y++)
1581 register const PixelPacket
1584 register PixelPacket
1591 Read another scan line.
1593 if (status == MagickFalse)
1595 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1596 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1598 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1603 for (x=0; x < (ssize_t) image->columns; x++)
1617 register const PixelPacket
1621 Compute weighted average of target pixel color components.
1625 r=p+2*(image->columns+4)+2;
1628 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1629 r=p+(image->columns+4);
1630 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1631 r=p+2*(image->columns+4);
1632 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1633 r=p+3*(image->columns+4);
1634 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1635 r=p+4*(image->columns+4);
1636 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1637 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1638 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1639 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1640 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1645 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1647 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1652 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1653 #pragma omp critical (MagickCore_EnhanceImage)
1655 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1656 if (proceed == MagickFalse)
1660 enhance_view=DestroyCacheView(enhance_view);
1661 image_view=DestroyCacheView(image_view);
1662 return(enhance_image);
1666 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1670 % E q u a l i z e I m a g e %
1674 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1676 % EqualizeImage() applies a histogram equalization to the image.
1678 % The format of the EqualizeImage method is:
1680 % MagickBooleanType EqualizeImage(Image *image)
1681 % MagickBooleanType EqualizeImageChannel(Image *image,
1682 % const ChannelType channel)
1684 % A description of each parameter follows:
1686 % o image: the image.
1688 % o channel: the channel.
1692 MagickExport MagickBooleanType EqualizeImage(Image *image)
1694 return(EqualizeImageChannel(image,DefaultChannels));
1697 MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1698 const ChannelType channel)
1700 #define EqualizeImageTag "Equalize/Image"
1729 Allocate and initialize histogram arrays.
1731 assert(image != (Image *) NULL);
1732 assert(image->signature == MagickSignature);
1733 if (image->debug != MagickFalse)
1734 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1735 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1736 sizeof(*equalize_map));
1737 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1738 sizeof(*histogram));
1739 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1740 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1741 (histogram == (MagickPixelPacket *) NULL) ||
1742 (map == (MagickPixelPacket *) NULL))
1744 if (map != (MagickPixelPacket *) NULL)
1745 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1746 if (histogram != (MagickPixelPacket *) NULL)
1747 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1748 if (equalize_map != (MagickPixelPacket *) NULL)
1749 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1750 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1756 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1757 exception=(&image->exception);
1758 for (y=0; y < (ssize_t) image->rows; y++)
1760 register const IndexPacket
1763 register const PixelPacket
1769 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1770 if (p == (const PixelPacket *) NULL)
1772 indexes=GetVirtualIndexQueue(image);
1773 for (x=0; x < (ssize_t) image->columns; x++)
1775 if ((channel & RedChannel) != 0)
1776 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1777 if ((channel & GreenChannel) != 0)
1778 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1779 if ((channel & BlueChannel) != 0)
1780 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1781 if ((channel & OpacityChannel) != 0)
1782 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1783 if (((channel & IndexChannel) != 0) &&
1784 (image->colorspace == CMYKColorspace))
1785 histogram[ScaleQuantumToMap(indexes[x])].index++;
1790 Integrate the histogram to get the equalization map.
1792 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1793 for (i=0; i <= (ssize_t) MaxMap; i++)
1795 if ((channel & RedChannel) != 0)
1796 intensity.red+=histogram[i].red;
1797 if ((channel & GreenChannel) != 0)
1798 intensity.green+=histogram[i].green;
1799 if ((channel & BlueChannel) != 0)
1800 intensity.blue+=histogram[i].blue;
1801 if ((channel & OpacityChannel) != 0)
1802 intensity.opacity+=histogram[i].opacity;
1803 if (((channel & IndexChannel) != 0) &&
1804 (image->colorspace == CMYKColorspace))
1805 intensity.index+=histogram[i].index;
1809 white=map[(int) MaxMap];
1810 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
1811 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1812 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1814 for (i=0; i <= (ssize_t) MaxMap; i++)
1816 if (((channel & RedChannel) != 0) && (white.red != black.red))
1817 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1818 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1819 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1820 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1821 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1822 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1823 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1824 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1825 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1826 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1827 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1828 (white.opacity-black.opacity)));
1829 if ((((channel & IndexChannel) != 0) &&
1830 (image->colorspace == CMYKColorspace)) &&
1831 (white.index != black.index))
1832 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1833 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1835 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1836 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1837 if (image->storage_class == PseudoClass)
1842 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1843 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1845 for (i=0; i < (ssize_t) image->colors; i++)
1847 if (((channel & RedChannel) != 0) && (white.red != black.red))
1848 image->colormap[i].red=ClampToQuantum(equalize_map[
1849 ScaleQuantumToMap(image->colormap[i].red)].red);
1850 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1851 image->colormap[i].green=ClampToQuantum(equalize_map[
1852 ScaleQuantumToMap(image->colormap[i].green)].green);
1853 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1854 image->colormap[i].blue=ClampToQuantum(equalize_map[
1855 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1856 if (((channel & OpacityChannel) != 0) &&
1857 (white.opacity != black.opacity))
1858 image->colormap[i].opacity=ClampToQuantum(equalize_map[
1859 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1867 exception=(&image->exception);
1868 image_view=AcquireCacheView(image);
1869 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1870 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1872 for (y=0; y < (ssize_t) image->rows; y++)
1874 register IndexPacket
1877 register PixelPacket
1883 if (status == MagickFalse)
1885 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1886 if (q == (PixelPacket *) NULL)
1891 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1892 for (x=0; x < (ssize_t) image->columns; x++)
1894 if (((channel & RedChannel) != 0) && (white.red != black.red))
1895 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
1896 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1897 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1899 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1900 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
1901 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1902 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1903 q->opacity)].opacity);
1904 if ((((channel & IndexChannel) != 0) &&
1905 (image->colorspace == CMYKColorspace)) &&
1906 (white.index != black.index))
1907 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1908 indexes[x])].index);
1911 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1913 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1918 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1919 #pragma omp critical (MagickCore_EqualizeImageChannel)
1921 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1922 if (proceed == MagickFalse)
1926 image_view=DestroyCacheView(image_view);
1927 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1932 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936 % G a m m a I m a g e %
1940 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1942 % GammaImage() gamma-corrects a particular image channel. The same
1943 % image viewed on different devices will have perceptual differences in the
1944 % way the image's intensities are represented on the screen. Specify
1945 % individual gamma levels for the red, green, and blue channels, or adjust
1946 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1948 % You can also reduce the influence of a particular channel with a gamma
1951 % The format of the GammaImage method is:
1953 % MagickBooleanType GammaImage(Image *image,const char *level)
1954 % MagickBooleanType GammaImageChannel(Image *image,
1955 % const ChannelType channel,const double gamma)
1957 % A description of each parameter follows:
1959 % o image: the image.
1961 % o channel: the channel.
1963 % o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1965 % o gamma: the image gamma.
1968 MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
1980 assert(image != (Image *) NULL);
1981 assert(image->signature == MagickSignature);
1982 if (image->debug != MagickFalse)
1983 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1984 if (level == (char *) NULL)
1985 return(MagickFalse);
1986 flags=ParseGeometry(level,&geometry_info);
1987 gamma.red=geometry_info.rho;
1988 gamma.green=geometry_info.sigma;
1989 if ((flags & SigmaValue) == 0)
1990 gamma.green=gamma.red;
1991 gamma.blue=geometry_info.xi;
1992 if ((flags & XiValue) == 0)
1993 gamma.blue=gamma.red;
1994 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
1996 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
1997 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
1998 GreenChannel | BlueChannel),(double) gamma.red);
2001 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2002 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2003 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2005 return(status != 0 ? MagickTrue : MagickFalse);
2008 MagickExport MagickBooleanType GammaImageChannel(Image *image,
2009 const ChannelType channel,const double gamma)
2011 #define GammaCorrectImageTag "GammaCorrect/Image"
2035 Allocate and initialize gamma maps.
2037 assert(image != (Image *) NULL);
2038 assert(image->signature == MagickSignature);
2039 if (image->debug != MagickFalse)
2040 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2043 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2044 if (gamma_map == (Quantum *) NULL)
2045 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2047 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2049 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2050 #pragma omp parallel for schedule(dynamic,4)
2052 for (i=0; i <= (ssize_t) MaxMap; i++)
2053 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
2054 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2055 if (image->storage_class == PseudoClass)
2058 Gamma-correct colormap.
2060 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2061 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2063 for (i=0; i < (ssize_t) image->colors; i++)
2065 if ((channel & RedChannel) != 0)
2066 image->colormap[i].red=gamma_map[
2067 ScaleQuantumToMap(image->colormap[i].red)];
2068 if ((channel & GreenChannel) != 0)
2069 image->colormap[i].green=gamma_map[
2070 ScaleQuantumToMap(image->colormap[i].green)];
2071 if ((channel & BlueChannel) != 0)
2072 image->colormap[i].blue=gamma_map[
2073 ScaleQuantumToMap(image->colormap[i].blue)];
2074 if ((channel & OpacityChannel) != 0)
2076 if (image->matte == MagickFalse)
2077 image->colormap[i].opacity=gamma_map[
2078 ScaleQuantumToMap(image->colormap[i].opacity)];
2080 image->colormap[i].opacity=(Quantum) QuantumRange-
2081 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2082 image->colormap[i].opacity))];
2087 Gamma-correct image.
2091 exception=(&image->exception);
2092 image_view=AcquireCacheView(image);
2093 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2094 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2096 for (y=0; y < (ssize_t) image->rows; y++)
2098 register IndexPacket
2101 register PixelPacket
2107 if (status == MagickFalse)
2109 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2110 if (q == (PixelPacket *) NULL)
2115 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2116 for (x=0; x < (ssize_t) image->columns; x++)
2118 if (channel == DefaultChannels)
2120 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2121 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2122 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2126 if ((channel & RedChannel) != 0)
2127 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2128 if ((channel & GreenChannel) != 0)
2129 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2130 if ((channel & BlueChannel) != 0)
2131 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2132 if ((channel & OpacityChannel) != 0)
2134 if (image->matte == MagickFalse)
2135 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2137 q->opacity=(Quantum) QuantumRange-gamma_map[
2138 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
2143 if (((channel & IndexChannel) != 0) &&
2144 (image->colorspace == CMYKColorspace))
2145 for (x=0; x < (ssize_t) image->columns; x++)
2146 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2147 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2149 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2154 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2155 #pragma omp critical (MagickCore_GammaImageChannel)
2157 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2159 if (proceed == MagickFalse)
2163 image_view=DestroyCacheView(image_view);
2164 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2165 if (image->gamma != 0.0)
2166 image->gamma*=gamma;
2171 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2175 % H a l d C l u t I m a g e %
2179 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2181 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2182 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2183 % Create it with the HALD coder. You can apply any color transformation to
2184 % the Hald image and then use this method to apply the transform to the
2187 % The format of the HaldClutImage method is:
2189 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2190 % MagickBooleanType HaldClutImageChannel(Image *image,
2191 % const ChannelType channel,Image *hald_image)
2193 % A description of each parameter follows:
2195 % o image: the image, which is replaced by indexed CLUT values
2197 % o hald_image: the color lookup table image for replacement color values.
2199 % o channel: the channel.
2203 static inline size_t MagickMin(const size_t x,const size_t y)
2210 MagickExport MagickBooleanType HaldClutImage(Image *image,
2211 const Image *hald_image)
2213 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2216 MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2217 const ChannelType channel,const Image *hald_image)
2219 #define HaldClutImageTag "Clut/Image"
2221 typedef struct _HaldInfo
2256 assert(image != (Image *) NULL);
2257 assert(image->signature == MagickSignature);
2258 if (image->debug != MagickFalse)
2259 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2260 assert(hald_image != (Image *) NULL);
2261 assert(hald_image->signature == MagickSignature);
2262 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2263 return(MagickFalse);
2264 if (image->matte == MagickFalse)
2265 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2271 length=MagickMin(hald_image->columns,hald_image->rows);
2272 for (level=2; (level*level*level) < length; level++) ;
2274 cube_size=level*level;
2275 width=(double) hald_image->columns;
2276 GetMagickPixelPacket(hald_image,&zero);
2277 exception=(&image->exception);
2278 image_view=AcquireCacheView(image);
2279 hald_view=AcquireCacheView(hald_image);
2280 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2281 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2283 for (y=0; y < (ssize_t) image->rows; y++)
2298 register IndexPacket
2301 register PixelPacket
2307 if (status == MagickFalse)
2309 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2310 if (q == (PixelPacket *) NULL)
2315 indexes=GetCacheViewAuthenticIndexQueue(hald_view);
2321 for (x=0; x < (ssize_t) image->columns; x++)
2323 point.x=QuantumScale*(level-1.0)*q->red;
2324 point.y=QuantumScale*(level-1.0)*q->green;
2325 point.z=QuantumScale*(level-1.0)*q->blue;
2326 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2327 point.x-=floor(point.x);
2328 point.y-=floor(point.y);
2329 point.z-=floor(point.z);
2330 (void) InterpolateMagickPixelPacket(image,hald_view,
2331 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2333 (void) InterpolateMagickPixelPacket(image,hald_view,
2334 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2335 width),&pixel2,exception);
2336 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2337 pixel2.opacity,point.y,&pixel3);
2339 (void) InterpolateMagickPixelPacket(image,hald_view,
2340 UndefinedInterpolatePixel,fmod(offset,width),floor(offset/width),
2342 (void) InterpolateMagickPixelPacket(image,hald_view,
2343 UndefinedInterpolatePixel,fmod(offset+level,width),floor((offset+level)/
2344 width),&pixel2,exception);
2345 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2346 pixel2.opacity,point.y,&pixel4);
2347 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2348 pixel4.opacity,point.z,&pixel);
2349 if ((channel & RedChannel) != 0)
2350 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2351 if ((channel & GreenChannel) != 0)
2352 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2353 if ((channel & BlueChannel) != 0)
2354 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2355 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2356 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
2357 if (((channel & IndexChannel) != 0) &&
2358 (image->colorspace == CMYKColorspace))
2359 indexes[x]=ClampToQuantum(pixel.index);
2362 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2364 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2369 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2370 #pragma omp critical (MagickCore_HaldClutImageChannel)
2372 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2373 if (proceed == MagickFalse)
2377 hald_view=DestroyCacheView(hald_view);
2378 image_view=DestroyCacheView(image_view);
2383 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2387 % L e v e l I m a g e %
2391 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2393 % LevelImage() adjusts the levels of a particular image channel by
2394 % scaling the colors falling between specified white and black points to
2395 % the full available quantum range.
2397 % The parameters provided represent the black, and white points. The black
2398 % point specifies the darkest color in the image. Colors darker than the
2399 % black point are set to zero. White point specifies the lightest color in
2400 % the image. Colors brighter than the white point are set to the maximum
2403 % If a '!' flag is given, map black and white colors to the given levels
2404 % rather than mapping those levels to black and white. See
2405 % LevelizeImageChannel() and LevelizeImageChannel(), below.
2407 % Gamma specifies a gamma correction to apply to the image.
2409 % The format of the LevelImage method is:
2411 % MagickBooleanType LevelImage(Image *image,const char *levels)
2413 % A description of each parameter follows:
2415 % o image: the image.
2417 % o levels: Specify the levels where the black and white points have the
2418 % range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2419 % A '!' flag inverts the re-mapping.
2423 MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2442 if (levels == (char *) NULL)
2443 return(MagickFalse);
2444 flags=ParseGeometry(levels,&geometry_info);
2445 black_point=geometry_info.rho;
2446 white_point=(double) QuantumRange;
2447 if ((flags & SigmaValue) != 0)
2448 white_point=geometry_info.sigma;
2450 if ((flags & XiValue) != 0)
2451 gamma=geometry_info.xi;
2452 if ((flags & PercentValue) != 0)
2454 black_point*=(double) image->columns*image->rows/100.0;
2455 white_point*=(double) image->columns*image->rows/100.0;
2457 if ((flags & SigmaValue) == 0)
2458 white_point=(double) QuantumRange-black_point;
2459 if ((flags & AspectValue ) == 0)
2460 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2463 status=LevelizeImage(image,black_point,white_point,gamma);
2468 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2472 % L e v e l i z e I m a g e %
2476 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2478 % LevelizeImage() applies the normal level operation to the image, spreading
2479 % out the values between the black and white points over the entire range of
2480 % values. Gamma correction is also applied after the values has been mapped.
2482 % It is typically used to improve image contrast, or to provide a controlled
2483 % linear threshold for the image. If the black and white points are set to
2484 % the minimum and maximum values found in the image, the image can be
2485 % normalized. or by swapping black and white values, negate the image.
2487 % The format of the LevelizeImage method is:
2489 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2490 % const double white_point,const double gamma)
2491 % MagickBooleanType LevelizeImageChannel(Image *image,
2492 % const ChannelType channel,const double black_point,
2493 % const double white_point,const double gamma)
2495 % A description of each parameter follows:
2497 % o image: the image.
2499 % o channel: the channel.
2501 % o black_point: The level which is to be mapped to zero (black)
2503 % o white_point: The level which is to be mapped to QuantiumRange (white)
2505 % o gamma: adjust gamma by this factor before mapping values.
2506 % use 1.0 for purely linear stretching of image color values
2509 MagickExport MagickBooleanType LevelImageChannel(Image *image,
2510 const ChannelType channel,const double black_point,const double white_point,
2513 #define LevelImageTag "Level/Image"
2514 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2515 pow(scale*((double) (x)-black_point),1.0/gamma)))
2539 Allocate and initialize levels map.
2541 assert(image != (Image *) NULL);
2542 assert(image->signature == MagickSignature);
2543 if (image->debug != MagickFalse)
2544 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2545 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2546 if (image->storage_class == PseudoClass)
2547 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2548 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2550 for (i=0; i < (ssize_t) image->colors; i++)
2555 if ((channel & RedChannel) != 0)
2556 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
2557 if ((channel & GreenChannel) != 0)
2558 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
2559 if ((channel & BlueChannel) != 0)
2560 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
2561 if ((channel & OpacityChannel) != 0)
2562 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
2569 exception=(&image->exception);
2570 image_view=AcquireCacheView(image);
2571 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2572 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2574 for (y=0; y < (ssize_t) image->rows; y++)
2576 register IndexPacket
2579 register PixelPacket
2585 if (status == MagickFalse)
2587 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2588 if (q == (PixelPacket *) NULL)
2593 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2594 for (x=0; x < (ssize_t) image->columns; x++)
2596 if ((channel & RedChannel) != 0)
2597 q->red=LevelQuantum(q->red);
2598 if ((channel & GreenChannel) != 0)
2599 q->green=LevelQuantum(q->green);
2600 if ((channel & BlueChannel) != 0)
2601 q->blue=LevelQuantum(q->blue);
2602 if (((channel & OpacityChannel) != 0) &&
2603 (image->matte == MagickTrue))
2604 q->opacity=(Quantum) (QuantumRange-LevelQuantum(QuantumRange-
2606 if (((channel & IndexChannel) != 0) &&
2607 (image->colorspace == CMYKColorspace))
2608 indexes[x]=LevelQuantum(indexes[x]);
2611 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2613 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2618 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2619 #pragma omp critical (MagickCore_LevelImageChannel)
2621 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2622 if (proceed == MagickFalse)
2626 image_view=DestroyCacheView(image_view);
2631 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2635 % L e v e l i z e I m a g e C h a n n e l %
2639 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2641 % LevelizeImageChannel() applies the reversed LevelImage() operation to just
2642 % the specific channels specified. It compresses the full range of color
2643 % values, so that they lie between the given black and white points. Gamma is
2644 % applied before the values are mapped.
2646 % LevelizeImageChannel() can be called with by using a +level command line
2647 % API option, or using a '!' on a -level or LevelImage() geometry string.
2649 % It can be used for example de-contrast a greyscale image to the exact
2650 % levels specified. Or by using specific levels for each channel of an image
2651 % you can convert a gray-scale image to any linear color gradient, according
2654 % The format of the LevelizeImageChannel method is:
2656 % MagickBooleanType LevelizeImageChannel(Image *image,
2657 % const ChannelType channel,const char *levels)
2659 % A description of each parameter follows:
2661 % o image: the image.
2663 % o channel: the channel.
2665 % o black_point: The level to map zero (black) to.
2667 % o white_point: The level to map QuantiumRange (white) to.
2669 % o gamma: adjust gamma by this factor before mapping values.
2673 MagickExport MagickBooleanType LevelizeImage(Image *image,
2674 const double black_point,const double white_point,const double gamma)
2679 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2684 MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2685 const ChannelType channel,const double black_point,const double white_point,
2688 #define LevelizeImageTag "Levelize/Image"
2689 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2690 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2712 Allocate and initialize levels map.
2714 assert(image != (Image *) NULL);
2715 assert(image->signature == MagickSignature);
2716 if (image->debug != MagickFalse)
2717 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2718 if (image->storage_class == PseudoClass)
2719 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2720 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2722 for (i=0; i < (ssize_t) image->colors; i++)
2727 if ((channel & RedChannel) != 0)
2728 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2729 if ((channel & GreenChannel) != 0)
2730 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2731 if ((channel & BlueChannel) != 0)
2732 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2733 if ((channel & OpacityChannel) != 0)
2734 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2741 exception=(&image->exception);
2742 image_view=AcquireCacheView(image);
2743 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2744 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2746 for (y=0; y < (ssize_t) image->rows; y++)
2748 register IndexPacket
2751 register PixelPacket
2757 if (status == MagickFalse)
2759 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2760 if (q == (PixelPacket *) NULL)
2765 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2766 for (x=0; x < (ssize_t) image->columns; x++)
2768 if ((channel & RedChannel) != 0)
2769 q->red=LevelizeValue(q->red);
2770 if ((channel & GreenChannel) != 0)
2771 q->green=LevelizeValue(q->green);
2772 if ((channel & BlueChannel) != 0)
2773 q->blue=LevelizeValue(q->blue);
2774 if (((channel & OpacityChannel) != 0) &&
2775 (image->matte == MagickTrue))
2776 q->opacity=LevelizeValue(q->opacity);
2777 if (((channel & IndexChannel) != 0) &&
2778 (image->colorspace == CMYKColorspace))
2779 indexes[x]=LevelizeValue(indexes[x]);
2782 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2784 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2789 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2790 #pragma omp critical (MagickCore_LevelizeImageChannel)
2792 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2793 if (proceed == MagickFalse)
2797 image_view=DestroyCacheView(image_view);
2802 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2806 % L e v e l I m a g e C o l o r s %
2810 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2812 % LevelImageColor() maps the given color to "black" and "white" values,
2813 % linearly spreading out the colors, and level values on a channel by channel
2814 % bases, as per LevelImage(). The given colors allows you to specify
2815 % different level ranges for each of the color channels separately.
2817 % If the boolean 'invert' is set true the image values will modifyed in the
2818 % reverse direction. That is any existing "black" and "white" colors in the
2819 % image will become the color values given, with all other values compressed
2820 % appropriatally. This effectivally maps a greyscale gradient into the given
2823 % The format of the LevelColorsImageChannel method is:
2825 % MagickBooleanType LevelColorsImage(Image *image,
2826 % const MagickPixelPacket *black_color,
2827 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2828 % MagickBooleanType LevelColorsImageChannel(Image *image,
2829 % const ChannelType channel,const MagickPixelPacket *black_color,
2830 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2832 % A description of each parameter follows:
2834 % o image: the image.
2836 % o channel: the channel.
2838 % o black_color: The color to map black to/from
2840 % o white_point: The color to map white to/from
2842 % o invert: if true map the colors (levelize), rather than from (level)
2846 MagickExport MagickBooleanType LevelColorsImage(Image *image,
2847 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2848 const MagickBooleanType invert)
2853 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2858 MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2859 const ChannelType channel,const MagickPixelPacket *black_color,
2860 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2866 Allocate and initialize levels map.
2868 assert(image != (Image *) NULL);
2869 assert(image->signature == MagickSignature);
2870 if (image->debug != MagickFalse)
2871 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2873 if (invert == MagickFalse)
2875 if ((channel & RedChannel) != 0)
2876 status|=LevelImageChannel(image,RedChannel,
2877 black_color->red,white_color->red,(double) 1.0);
2878 if ((channel & GreenChannel) != 0)
2879 status|=LevelImageChannel(image,GreenChannel,
2880 black_color->green,white_color->green,(double) 1.0);
2881 if ((channel & BlueChannel) != 0)
2882 status|=LevelImageChannel(image,BlueChannel,
2883 black_color->blue,white_color->blue,(double) 1.0);
2884 if (((channel & OpacityChannel) != 0) &&
2885 (image->matte == MagickTrue))
2886 status|=LevelImageChannel(image,OpacityChannel,
2887 black_color->opacity,white_color->opacity,(double) 1.0);
2888 if (((channel & IndexChannel) != 0) &&
2889 (image->colorspace == CMYKColorspace))
2890 status|=LevelImageChannel(image,IndexChannel,
2891 black_color->index,white_color->index,(double) 1.0);
2895 if ((channel & RedChannel) != 0)
2896 status|=LevelizeImageChannel(image,RedChannel,
2897 black_color->red,white_color->red,(double) 1.0);
2898 if ((channel & GreenChannel) != 0)
2899 status|=LevelizeImageChannel(image,GreenChannel,
2900 black_color->green,white_color->green,(double) 1.0);
2901 if ((channel & BlueChannel) != 0)
2902 status|=LevelizeImageChannel(image,BlueChannel,
2903 black_color->blue,white_color->blue,(double) 1.0);
2904 if (((channel & OpacityChannel) != 0) &&
2905 (image->matte == MagickTrue))
2906 status|=LevelizeImageChannel(image,OpacityChannel,
2907 black_color->opacity,white_color->opacity,(double) 1.0);
2908 if (((channel & IndexChannel) != 0) &&
2909 (image->colorspace == CMYKColorspace))
2910 status|=LevelizeImageChannel(image,IndexChannel,
2911 black_color->index,white_color->index,(double) 1.0);
2913 return(status == 0 ? MagickFalse : MagickTrue);
2917 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2921 % L i n e a r S t r e t c h I m a g e %
2925 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2927 % The LinearStretchImage() discards any pixels below the black point and
2928 % above the white point and levels the remaining pixels.
2930 % The format of the LinearStretchImage method is:
2932 % MagickBooleanType LinearStretchImage(Image *image,
2933 % const double black_point,const double white_point)
2935 % A description of each parameter follows:
2937 % o image: the image.
2939 % o black_point: the black point.
2941 % o white_point: the white point.
2944 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2945 const double black_point,const double white_point)
2947 #define LinearStretchImageTag "LinearStretch/Image"
2965 Allocate histogram and linear map.
2967 assert(image != (Image *) NULL);
2968 assert(image->signature == MagickSignature);
2969 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2970 sizeof(*histogram));
2971 if (histogram == (MagickRealType *) NULL)
2972 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2977 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2978 exception=(&image->exception);
2979 for (y=0; y < (ssize_t) image->rows; y++)
2981 register const PixelPacket
2987 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
2988 if (p == (const PixelPacket *) NULL)
2990 for (x=(ssize_t) image->columns-1; x >= 0; x--)
2992 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
2997 Find the histogram boundaries by locating the black and white point levels.
3000 for (black=0; black < (ssize_t) MaxMap; black++)
3002 intensity+=histogram[black];
3003 if (intensity >= black_point)
3007 for (white=(ssize_t) MaxMap; white != 0; white--)
3009 intensity+=histogram[white];
3010 if (intensity >= white_point)
3013 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3014 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3020 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3024 % M o d u l a t e I m a g e %
3028 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3030 % ModulateImage() lets you control the brightness, saturation, and hue
3031 % of an image. Modulate represents the brightness, saturation, and hue
3032 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3033 % modulation is lightness, saturation, and hue. And if the colorspace is
3034 % HWB, use blackness, whiteness, and hue.
3036 % The format of the ModulateImage method is:
3038 % MagickBooleanType ModulateImage(Image *image,const char *modulate)
3040 % A description of each parameter follows:
3042 % o image: the image.
3044 % o modulate: Define the percent change in brightness, saturation, and
3049 static void ModulateHSB(const double percent_hue,
3050 const double percent_saturation,const double percent_brightness,
3051 Quantum *red,Quantum *green,Quantum *blue)
3059 Increase or decrease color brightness, saturation, or hue.
3061 assert(red != (Quantum *) NULL);
3062 assert(green != (Quantum *) NULL);
3063 assert(blue != (Quantum *) NULL);
3064 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3065 hue+=0.5*(0.01*percent_hue-1.0);
3070 saturation*=0.01*percent_saturation;
3071 brightness*=0.01*percent_brightness;
3072 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3075 static void ModulateHSL(const double percent_hue,
3076 const double percent_saturation,const double percent_lightness,
3077 Quantum *red,Quantum *green,Quantum *blue)
3085 Increase or decrease color lightness, saturation, or hue.
3087 assert(red != (Quantum *) NULL);
3088 assert(green != (Quantum *) NULL);
3089 assert(blue != (Quantum *) NULL);
3090 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3091 hue+=0.5*(0.01*percent_hue-1.0);
3096 saturation*=0.01*percent_saturation;
3097 lightness*=0.01*percent_lightness;
3098 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3101 static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3109 Increase or decrease color blackness, whiteness, or hue.
3111 assert(red != (Quantum *) NULL);
3112 assert(green != (Quantum *) NULL);
3113 assert(blue != (Quantum *) NULL);
3114 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3115 hue+=0.5*(0.01*percent_hue-1.0);
3120 blackness*=0.01*percent_blackness;
3121 whiteness*=0.01*percent_whiteness;
3122 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3125 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3127 #define ModulateImageTag "Modulate/Image"
3165 Initialize modulate table.
3167 assert(image != (Image *) NULL);
3168 assert(image->signature == MagickSignature);
3169 if (image->debug != MagickFalse)
3170 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3171 if (modulate == (char *) NULL)
3172 return(MagickFalse);
3173 flags=ParseGeometry(modulate,&geometry_info);
3174 percent_brightness=geometry_info.rho;
3175 percent_saturation=geometry_info.sigma;
3176 if ((flags & SigmaValue) == 0)
3177 percent_saturation=100.0;
3178 percent_hue=geometry_info.xi;
3179 if ((flags & XiValue) == 0)
3181 colorspace=UndefinedColorspace;
3182 artifact=GetImageArtifact(image,"modulate:colorspace");
3183 if (artifact != (const char *) NULL)
3184 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3185 MagickFalse,artifact);
3186 if (image->storage_class == PseudoClass)
3191 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3192 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3194 for (i=0; i < (ssize_t) image->colors; i++)
3199 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3200 &image->colormap[i].red,&image->colormap[i].green,
3201 &image->colormap[i].blue);
3207 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3208 &image->colormap[i].red,&image->colormap[i].green,
3209 &image->colormap[i].blue);
3214 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3215 &image->colormap[i].red,&image->colormap[i].green,
3216 &image->colormap[i].blue);
3226 exception=(&image->exception);
3227 image_view=AcquireCacheView(image);
3228 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3229 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3231 for (y=0; y < (ssize_t) image->rows; y++)
3233 register PixelPacket
3239 if (status == MagickFalse)
3241 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3242 if (q == (PixelPacket *) NULL)
3247 for (x=0; x < (ssize_t) image->columns; x++)
3253 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3254 &q->red,&q->green,&q->blue);
3260 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3261 &q->red,&q->green,&q->blue);
3266 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3267 &q->red,&q->green,&q->blue);
3273 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3275 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3280 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3281 #pragma omp critical (MagickCore_ModulateImage)
3283 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3284 if (proceed == MagickFalse)
3288 image_view=DestroyCacheView(image_view);
3293 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3297 % N e g a t e I m a g e %
3301 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3303 % NegateImage() negates the colors in the reference image. The grayscale
3304 % option means that only grayscale values within the image are negated.
3306 % The format of the NegateImageChannel method is:
3308 % MagickBooleanType NegateImage(Image *image,
3309 % const MagickBooleanType grayscale)
3310 % MagickBooleanType NegateImageChannel(Image *image,
3311 % const ChannelType channel,const MagickBooleanType grayscale)
3313 % A description of each parameter follows:
3315 % o image: the image.
3317 % o channel: the channel.
3319 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3323 MagickExport MagickBooleanType NegateImage(Image *image,
3324 const MagickBooleanType grayscale)
3329 status=NegateImageChannel(image,DefaultChannels,grayscale);
3333 MagickExport MagickBooleanType NegateImageChannel(Image *image,
3334 const ChannelType channel,const MagickBooleanType grayscale)
3336 #define NegateImageTag "Negate/Image"
3356 assert(image != (Image *) NULL);
3357 assert(image->signature == MagickSignature);
3358 if (image->debug != MagickFalse)
3359 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3360 if (image->storage_class == PseudoClass)
3365 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3366 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3368 for (i=0; i < (ssize_t) image->colors; i++)
3370 if (grayscale != MagickFalse)
3371 if ((image->colormap[i].red != image->colormap[i].green) ||
3372 (image->colormap[i].green != image->colormap[i].blue))
3374 if ((channel & RedChannel) != 0)
3375 image->colormap[i].red=(Quantum) QuantumRange-
3376 image->colormap[i].red;
3377 if ((channel & GreenChannel) != 0)
3378 image->colormap[i].green=(Quantum) QuantumRange-
3379 image->colormap[i].green;
3380 if ((channel & BlueChannel) != 0)
3381 image->colormap[i].blue=(Quantum) QuantumRange-
3382 image->colormap[i].blue;
3390 exception=(&image->exception);
3391 image_view=AcquireCacheView(image);
3392 if (grayscale != MagickFalse)
3394 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3395 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3397 for (y=0; y < (ssize_t) image->rows; y++)
3402 register IndexPacket
3405 register PixelPacket
3411 if (status == MagickFalse)
3413 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3415 if (q == (PixelPacket *) NULL)
3420 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3421 for (x=0; x < (ssize_t) image->columns; x++)
3423 if ((q->red != q->green) || (q->green != q->blue))
3428 if ((channel & RedChannel) != 0)
3429 q->red=(Quantum) QuantumRange-q->red;
3430 if ((channel & GreenChannel) != 0)
3431 q->green=(Quantum) QuantumRange-q->green;
3432 if ((channel & BlueChannel) != 0)
3433 q->blue=(Quantum) QuantumRange-q->blue;
3434 if ((channel & OpacityChannel) != 0)
3435 q->opacity=(Quantum) QuantumRange-q->opacity;
3436 if (((channel & IndexChannel) != 0) &&
3437 (image->colorspace == CMYKColorspace))
3438 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3441 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3442 if (sync == MagickFalse)
3444 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3449 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3450 #pragma omp critical (MagickCore_NegateImageChannel)
3452 proceed=SetImageProgress(image,NegateImageTag,progress++,
3454 if (proceed == MagickFalse)
3458 image_view=DestroyCacheView(image_view);
3464 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3465 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3467 for (y=0; y < (ssize_t) image->rows; y++)
3469 register IndexPacket
3472 register PixelPacket
3478 if (status == MagickFalse)
3480 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3481 if (q == (PixelPacket *) NULL)
3486 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3487 for (x=0; x < (ssize_t) image->columns; x++)
3489 if ((channel & RedChannel) != 0)
3490 q->red=(Quantum) QuantumRange-q->red;
3491 if ((channel & GreenChannel) != 0)
3492 q->green=(Quantum) QuantumRange-q->green;
3493 if ((channel & BlueChannel) != 0)
3494 q->blue=(Quantum) QuantumRange-q->blue;
3495 if ((channel & OpacityChannel) != 0)
3496 q->opacity=(Quantum) QuantumRange-q->opacity;
3497 if (((channel & IndexChannel) != 0) &&
3498 (image->colorspace == CMYKColorspace))
3499 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3502 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3504 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3509 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3510 #pragma omp critical (MagickCore_NegateImageChannel)
3512 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3513 if (proceed == MagickFalse)
3517 image_view=DestroyCacheView(image_view);
3522 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3526 % N o r m a l i z e I m a g e %
3530 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3532 % The NormalizeImage() method enhances the contrast of a color image by
3533 % mapping the darkest 2 percent of all pixel to black and the brightest
3534 % 1 percent to white.
3536 % The format of the NormalizeImage method is:
3538 % MagickBooleanType NormalizeImage(Image *image)
3539 % MagickBooleanType NormalizeImageChannel(Image *image,
3540 % const ChannelType channel)
3542 % A description of each parameter follows:
3544 % o image: the image.
3546 % o channel: the channel.
3550 MagickExport MagickBooleanType NormalizeImage(Image *image)
3555 status=NormalizeImageChannel(image,DefaultChannels);
3559 MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3560 const ChannelType channel)
3566 black_point=(double) image->columns*image->rows*0.0015;
3567 white_point=(double) image->columns*image->rows*0.9995;
3568 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3572 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3576 % S i g m o i d a l C o n t r a s t I m a g e %
3580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3582 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3583 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3584 % sigmoidal transfer function without saturating highlights or shadows.
3585 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3586 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3587 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3588 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3591 % The format of the SigmoidalContrastImage method is:
3593 % MagickBooleanType SigmoidalContrastImage(Image *image,
3594 % const MagickBooleanType sharpen,const char *levels)
3595 % MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3596 % const ChannelType channel,const MagickBooleanType sharpen,
3597 % const double contrast,const double midpoint)
3599 % A description of each parameter follows:
3601 % o image: the image.
3603 % o channel: the channel.
3605 % o sharpen: Increase or decrease image contrast.
3607 % o alpha: strength of the contrast, the larger the number the more
3608 % 'threshold-like' it becomes.
3610 % o beta: midpoint of the function as a color value 0 to QuantumRange.
3614 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3615 const MagickBooleanType sharpen,const char *levels)
3626 flags=ParseGeometry(levels,&geometry_info);
3627 if ((flags & SigmaValue) == 0)
3628 geometry_info.sigma=1.0*QuantumRange/2.0;
3629 if ((flags & PercentValue) != 0)
3630 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3631 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3632 geometry_info.rho,geometry_info.sigma);
3636 MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3637 const ChannelType channel,const MagickBooleanType sharpen,
3638 const double contrast,const double midpoint)
3640 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3664 Allocate and initialize sigmoidal maps.
3666 assert(image != (Image *) NULL);
3667 assert(image->signature == MagickSignature);
3668 if (image->debug != MagickFalse)
3669 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3670 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3671 sizeof(*sigmoidal_map));
3672 if (sigmoidal_map == (MagickRealType *) NULL)
3673 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3675 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3676 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3677 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3679 for (i=0; i <= (ssize_t) MaxMap; i++)
3681 if (sharpen != MagickFalse)
3683 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3684 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3685 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3686 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3687 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3688 (double) QuantumRange)))))+0.5));
3691 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3692 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3693 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3694 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3695 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3696 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3697 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3698 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3699 (double) QuantumRange*contrast))))))/contrast)));
3701 if (image->storage_class == PseudoClass)
3704 Sigmoidal-contrast enhance colormap.
3706 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3707 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3709 for (i=0; i < (ssize_t) image->colors; i++)
3711 if ((channel & RedChannel) != 0)
3712 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
3713 ScaleQuantumToMap(image->colormap[i].red)]);
3714 if ((channel & GreenChannel) != 0)
3715 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
3716 ScaleQuantumToMap(image->colormap[i].green)]);
3717 if ((channel & BlueChannel) != 0)
3718 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
3719 ScaleQuantumToMap(image->colormap[i].blue)]);
3720 if ((channel & OpacityChannel) != 0)
3721 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
3722 ScaleQuantumToMap(image->colormap[i].opacity)]);
3726 Sigmoidal-contrast enhance image.
3730 exception=(&image->exception);
3731 image_view=AcquireCacheView(image);
3732 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3733 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3735 for (y=0; y < (ssize_t) image->rows; y++)
3737 register IndexPacket
3740 register PixelPacket
3746 if (status == MagickFalse)
3748 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3749 if (q == (PixelPacket *) NULL)
3754 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3755 for (x=0; x < (ssize_t) image->columns; x++)
3757 if ((channel & RedChannel) != 0)
3758 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
3759 if ((channel & GreenChannel) != 0)
3760 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
3761 if ((channel & BlueChannel) != 0)
3762 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
3763 if ((channel & OpacityChannel) != 0)
3764 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
3765 if (((channel & IndexChannel) != 0) &&
3766 (image->colorspace == CMYKColorspace))
3767 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
3768 ScaleQuantumToMap(indexes[x])]);
3771 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3773 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3778 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3779 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3781 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3783 if (proceed == MagickFalse)
3787 image_view=DestroyCacheView(image_view);
3788 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);