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-2010 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/quantum.h"
65 #include "magick/quantum-private.h"
66 #include "magick/resample.h"
67 #include "magick/resample-private.h"
68 #include "magick/statistic.h"
69 #include "magick/string_.h"
70 #include "magick/string-private.h"
71 #include "magick/thread-private.h"
72 #include "magick/token.h"
73 #include "magick/xml-tree.h"
76 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80 % A u t o G a m m a I m a g e %
84 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86 % AutoGammaImage() extract the 'mean' from the image and adjust the image
87 % to try make set its gamma appropriatally.
89 % The format of the AutoGammaImage method is:
91 % MagickBooleanType AutoGammaImage(Image *image)
92 % MagickBooleanType AutoGammaImageChannel(Image *image,
93 % const ChannelType channel)
95 % A description of each parameter follows:
97 % o image: The image to auto-level
99 % o channel: The channels to auto-level. If the special 'SyncChannels'
100 % flag is set all given channels is adjusted in the same way using the
101 % mean average of those channels.
105 MagickExport MagickBooleanType AutoGammaImage(Image *image)
107 return(AutoGammaImageChannel(image,DefaultChannels));
110 MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
111 const ChannelType channel)
117 mean,sans,gamma,logmean;
121 if ((channel & SyncChannels) != 0 )
124 Apply gamma correction equally accross all given channels
126 (void) GetImageChannelMean(image,channel,&mean,&sans,&image->exception);
127 gamma=log(mean*QuantumScale)/logmean;
128 return LevelImageChannel(image, channel,
129 0.0, (double)QuantumRange, gamma);
133 auto-gamma each channel separateally
136 if ((channel & RedChannel) != 0)
138 (void) GetImageChannelMean(image,RedChannel,&mean,&sans,
140 gamma=log(mean*QuantumScale)/logmean;
141 status = status && LevelImageChannel(image, RedChannel,
142 0.0, (double)QuantumRange, gamma);
144 if ((channel & GreenChannel) != 0)
146 (void) GetImageChannelMean(image,GreenChannel,&mean,&sans,
148 gamma=log(mean*QuantumScale)/logmean;
149 status = status && LevelImageChannel(image, GreenChannel,
150 0.0, (double)QuantumRange, gamma);
152 if ((channel & BlueChannel) != 0)
154 (void) GetImageChannelMean(image,BlueChannel,&mean,&sans,
156 gamma=log(mean*QuantumScale)/logmean;
157 status = status && LevelImageChannel(image, BlueChannel,
158 0.0, (double)QuantumRange, gamma);
160 if (((channel & OpacityChannel) != 0) &&
161 (image->matte == MagickTrue))
163 (void) GetImageChannelMean(image,OpacityChannel,&mean,&sans,
165 gamma=log(mean*QuantumScale)/logmean;
166 status = status && LevelImageChannel(image, OpacityChannel,
167 0.0, (double)QuantumRange, gamma);
169 if (((channel & IndexChannel) != 0) &&
170 (image->colorspace == CMYKColorspace))
172 (void) GetImageChannelMean(image,IndexChannel,&mean,&sans,
174 gamma=log(mean*QuantumScale)/logmean;
175 status = status && LevelImageChannel(image, IndexChannel,
176 0.0, (double)QuantumRange, gamma);
178 return(status != 0 ? MagickTrue : MagickFalse);
182 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
186 % A u t o L e v e l I m a g e %
190 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
192 % AutoLevelImage() adjusts the levels of a particular image channel by
193 % scaling the minimum and maximum values to the full quantum range.
195 % The format of the LevelImage method is:
197 % MagickBooleanType AutoLevelImage(Image *image)
198 % MagickBooleanType AutoLevelImageChannel(Image *image,
199 % const ChannelType channel)
201 % A description of each parameter follows:
203 % o image: The image to auto-level
205 % o channel: The channels to auto-level. If the special 'SyncChannels'
206 % flag is set the min/max/mean value of all given channels is used for
207 % all given channels, to all channels in the same way.
211 MagickExport MagickBooleanType AutoLevelImage(Image *image)
213 return(AutoLevelImageChannel(image,DefaultChannels));
216 MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
217 const ChannelType channel)
220 This is simply a convenience function around a Min/Max Histogram Stretch
222 return MinMaxStretchImage(image, channel, 0.0, 0.0);
226 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
230 % B r i g h t n e s s C o n t r a s t I m a g e %
234 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
236 % Use BrightnessContrastImage() to change the brightness and/or contrast of
237 % an image. It converts the brightness and contrast parameters into slope
238 % and intercept and calls a polynomical function to apply to the image.
240 % The format of the BrightnessContrastImage method is:
242 % MagickBooleanType BrightnessContrastImage(Image *image,
243 % const double brightness,const double contrast)
244 % MagickBooleanType BrightnessContrastImageChannel(Image *image,
245 % const ChannelType channel,const double brightness,
246 % const double contrast)
248 % A description of each parameter follows:
250 % o image: the image.
252 % o channel: the channel.
254 % o brightness: the brightness percent (-100 .. 100).
256 % o contrast: the contrast percent (-100 .. 100).
260 MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
261 const double brightness,const double contrast)
266 status=BrightnessContrastImageChannel(image,DefaultChannels,brightness,
271 MagickExport MagickBooleanType BrightnessContrastImageChannel(Image *image,
272 const ChannelType channel,const double brightness,const double contrast)
274 #define BrightnessContastImageTag "BrightnessContast/Image"
286 Compute slope and intercept.
288 assert(image != (Image *) NULL);
289 assert(image->signature == MagickSignature);
290 if (image->debug != MagickFalse)
291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
293 slope=tan(MagickPI*(alpha/100.0+1.0)/4.0);
296 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
297 coefficients[0]=slope;
298 coefficients[1]=intercept;
299 status=FunctionImageChannel(image,channel,PolynomialFunction,2,coefficients,
305 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309 % C o l o r D e c i s i o n L i s t I m a g e %
313 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
315 % ColorDecisionListImage() accepts a lightweight Color Correction Collection
316 % (CCC) file which solely contains one or more color corrections and applies
317 % the correction to the image. Here is a sample CCC file:
319 % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
320 % <ColorCorrection id="cc03345">
322 % <Slope> 0.9 1.2 0.5 </Slope>
323 % <Offset> 0.4 -0.5 0.6 </Offset>
324 % <Power> 1.0 0.8 1.5 </Power>
327 % <Saturation> 0.85 </Saturation>
330 % </ColorCorrectionCollection>
332 % which includes the slop, offset, and power for each of the RGB channels
333 % as well as the saturation.
335 % The format of the ColorDecisionListImage method is:
337 % MagickBooleanType ColorDecisionListImage(Image *image,
338 % const char *color_correction_collection)
340 % A description of each parameter follows:
342 % o image: the image.
344 % o color_correction_collection: the color correction collection in XML.
347 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
348 const char *color_correction_collection)
350 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
352 typedef struct _Correction
360 typedef struct _ColorCorrection
375 token[MaxTextExtent];
407 Allocate and initialize cdl maps.
409 assert(image != (Image *) NULL);
410 assert(image->signature == MagickSignature);
411 if (image->debug != MagickFalse)
412 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
413 if (color_correction_collection == (const char *) NULL)
415 ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
416 if (ccc == (XMLTreeInfo *) NULL)
418 cc=GetXMLTreeChild(ccc,"ColorCorrection");
419 if (cc == (XMLTreeInfo *) NULL)
421 ccc=DestroyXMLTree(ccc);
424 color_correction.red.slope=1.0;
425 color_correction.red.offset=0.0;
426 color_correction.red.power=1.0;
427 color_correction.green.slope=1.0;
428 color_correction.green.offset=0.0;
429 color_correction.green.power=1.0;
430 color_correction.blue.slope=1.0;
431 color_correction.blue.offset=0.0;
432 color_correction.blue.power=1.0;
433 color_correction.saturation=0.0;
434 sop=GetXMLTreeChild(cc,"SOPNode");
435 if (sop != (XMLTreeInfo *) NULL)
442 slope=GetXMLTreeChild(sop,"Slope");
443 if (slope != (XMLTreeInfo *) NULL)
445 content=GetXMLTreeContent(slope);
446 p=(const char *) content;
447 for (i=0; (*p != '\0') && (i < 3); i++)
449 GetMagickToken(p,&p,token);
451 GetMagickToken(p,&p,token);
454 case 0: color_correction.red.slope=StringToDouble(token); break;
455 case 1: color_correction.green.slope=StringToDouble(token); break;
456 case 2: color_correction.blue.slope=StringToDouble(token); break;
460 offset=GetXMLTreeChild(sop,"Offset");
461 if (offset != (XMLTreeInfo *) NULL)
463 content=GetXMLTreeContent(offset);
464 p=(const char *) content;
465 for (i=0; (*p != '\0') && (i < 3); i++)
467 GetMagickToken(p,&p,token);
469 GetMagickToken(p,&p,token);
472 case 0: color_correction.red.offset=StringToDouble(token); break;
473 case 1: color_correction.green.offset=StringToDouble(token); break;
474 case 2: color_correction.blue.offset=StringToDouble(token); break;
478 power=GetXMLTreeChild(sop,"Power");
479 if (power != (XMLTreeInfo *) NULL)
481 content=GetXMLTreeContent(power);
482 p=(const char *) content;
483 for (i=0; (*p != '\0') && (i < 3); i++)
485 GetMagickToken(p,&p,token);
487 GetMagickToken(p,&p,token);
490 case 0: color_correction.red.power=StringToDouble(token); break;
491 case 1: color_correction.green.power=StringToDouble(token); break;
492 case 2: color_correction.blue.power=StringToDouble(token); break;
497 sat=GetXMLTreeChild(cc,"SATNode");
498 if (sat != (XMLTreeInfo *) NULL)
503 saturation=GetXMLTreeChild(sat,"Saturation");
504 if (saturation != (XMLTreeInfo *) NULL)
506 content=GetXMLTreeContent(saturation);
507 p=(const char *) content;
508 GetMagickToken(p,&p,token);
509 color_correction.saturation=StringToDouble(token);
512 ccc=DestroyXMLTree(ccc);
513 if (image->debug != MagickFalse)
515 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
516 " Color Correction Collection:");
517 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
518 " color_correction.red.slope: %g",color_correction.red.slope);
519 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
520 " color_correction.red.offset: %g",color_correction.red.offset);
521 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
522 " color_correction.red.power: %g",color_correction.red.power);
523 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
524 " color_correction.green.slope: %g",color_correction.green.slope);
525 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
526 " color_correction.green.offset: %g",color_correction.green.offset);
527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
528 " color_correction.green.power: %g",color_correction.green.power);
529 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
530 " color_correction.blue.slope: %g",color_correction.blue.slope);
531 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
532 " color_correction.blue.offset: %g",color_correction.blue.offset);
533 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
534 " color_correction.blue.power: %g",color_correction.blue.power);
535 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
536 " color_correction.saturation: %g",color_correction.saturation);
538 cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
539 if (cdl_map == (PixelPacket *) NULL)
540 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
542 #if defined(MAGICKCORE_OPENMP_SUPPORT)
543 #pragma omp parallel for schedule(dynamic,4)
545 for (i=0; i <= (long) MaxMap; i++)
547 cdl_map[i].red=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
548 MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
549 color_correction.red.offset,color_correction.red.power)))));
550 cdl_map[i].green=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
551 MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
552 color_correction.green.offset,color_correction.green.power)))));
553 cdl_map[i].blue=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
554 MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
555 color_correction.blue.offset,color_correction.blue.power)))));
557 if (image->storage_class == PseudoClass)
560 Apply transfer function to colormap.
562 #if defined(MAGICKCORE_OPENMP_SUPPORT)
563 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
565 for (i=0; i < (long) image->colors; i++)
570 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
571 0.0722*image->colormap[i].blue;
572 image->colormap[i].red=ClampToQuantum(luma+color_correction.saturation*
573 cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
574 image->colormap[i].green=ClampToQuantum(luma+
575 color_correction.saturation*cdl_map[ScaleQuantumToMap(
576 image->colormap[i].green)].green-luma);
577 image->colormap[i].blue=ClampToQuantum(luma+color_correction.saturation*
578 cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
582 Apply transfer function to image.
586 exception=(&image->exception);
587 image_view=AcquireCacheView(image);
588 #if defined(MAGICKCORE_OPENMP_SUPPORT)
589 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
591 for (y=0; y < (long) image->rows; y++)
602 if (status == MagickFalse)
604 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
605 if (q == (PixelPacket *) NULL)
610 for (x=0; x < (long) image->columns; x++)
612 luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
613 q->red=ClampToQuantum(luma+color_correction.saturation*
614 (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
615 q->green=ClampToQuantum(luma+color_correction.saturation*
616 (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
617 q->blue=ClampToQuantum(luma+color_correction.saturation*
618 (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
621 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
623 if (image->progress_monitor != (MagickProgressMonitor) NULL)
628 #if defined(MAGICKCORE_OPENMP_SUPPORT)
629 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
631 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
632 progress++,image->rows);
633 if (proceed == MagickFalse)
637 image_view=DestroyCacheView(image_view);
638 cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
643 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
647 % C l u t I m a g e %
651 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
653 % ClutImage() replaces each color value in the given image, by using it as an
654 % index to lookup a replacement color value in a Color Look UP Table in the
655 % form of an image. The values are extracted along a diagonal of the CLUT
656 % image so either a horizontal or vertial gradient image can be used.
658 % Typically this is used to either re-color a gray-scale image according to a
659 % color gradient in the CLUT image, or to perform a freeform histogram
660 % (level) adjustment according to the (typically gray-scale) gradient in the
663 % When the 'channel' mask includes the matte/alpha transparency channel but
664 % one image has no such channel it is assumed that that image is a simple
665 % gray-scale image that will effect the alpha channel values, either for
666 % gray-scale coloring (with transparent or semi-transparent colors), or
667 % a histogram adjustment of existing alpha channel values. If both images
668 % have matte channels, direct and normal indexing is applied, which is rarely
671 % The format of the ClutImage method is:
673 % MagickBooleanType ClutImage(Image *image,Image *clut_image)
674 % MagickBooleanType ClutImageChannel(Image *image,
675 % const ChannelType channel,Image *clut_image)
677 % A description of each parameter follows:
679 % o image: the image, which is replaced by indexed CLUT values
681 % o clut_image: the color lookup table image for replacement color values.
683 % o channel: the channel.
687 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
689 return(ClutImageChannel(image,DefaultChannels,clut_image));
692 MagickExport MagickBooleanType ClutImageChannel(Image *image,
693 const ChannelType channel,const Image *clut_image)
695 #define ClutImageTag "Clut/Image"
715 **restrict resample_filter;
717 assert(image != (Image *) NULL);
718 assert(image->signature == MagickSignature);
719 if (image->debug != MagickFalse)
720 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
721 assert(clut_image != (Image *) NULL);
722 assert(clut_image->signature == MagickSignature);
723 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
730 GetMagickPixelPacket(clut_image,&zero);
731 adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
732 exception=(&image->exception);
733 resample_filter=AcquireResampleFilterThreadSet(clut_image,
734 UndefinedVirtualPixelMethod,MagickTrue,exception);
735 image_view=AcquireCacheView(image);
736 #if defined(MAGICKCORE_OPENMP_SUPPORT)
737 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
739 for (y=0; y < (long) image->rows; y++)
754 if (status == MagickFalse)
756 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
757 if (q == (PixelPacket *) NULL)
762 indexes=GetCacheViewAuthenticIndexQueue(image_view);
764 id=GetOpenMPThreadId();
765 for (x=0; x < (long) image->columns; x++)
770 Apply OpacityChannel BEFORE the color channels. Do not re-order.
772 The handling special case 2 (coloring gray-scale), requires access to
773 the unmodified colors of the original image to determine the index
774 value. As such alpha/matte channel handling must be performed BEFORE,
775 any of the color channels are modified.
778 if ((channel & OpacityChannel) != 0)
780 if (clut_image->matte == MagickFalse)
783 A gray-scale LUT replacement for an image alpha channel.
785 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
786 GetAlphaPixelComponent(q)*(clut_image->columns+adjust),
787 QuantumScale*GetAlphaPixelComponent(q)*(clut_image->rows+
789 q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
793 if (image->matte == MagickFalse)
796 A greyscale image being colored by a LUT with transparency.
798 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
799 PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
800 PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
801 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
806 Direct alpha channel lookup.
808 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
809 q->opacity*(clut_image->columns-adjust),QuantumScale*
810 q->opacity* (clut_image->rows-adjust),&pixel);
811 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
814 if ((channel & RedChannel) != 0)
816 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
817 (clut_image->columns-adjust),QuantumScale*q->red*
818 (clut_image->rows-adjust),&pixel);
819 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
821 if ((channel & GreenChannel) != 0)
823 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
824 (clut_image->columns-adjust),QuantumScale*q->green*
825 (clut_image->rows-adjust),&pixel);
826 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
828 if ((channel & BlueChannel) != 0)
830 (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
831 (clut_image->columns-adjust),QuantumScale*q->blue*
832 (clut_image->rows-adjust),&pixel);
833 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
835 if (((channel & IndexChannel) != 0) &&
836 (image->colorspace == CMYKColorspace))
838 (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
839 (clut_image->columns-adjust),QuantumScale*indexes[x]*
840 (clut_image->rows-adjust),&pixel);
841 indexes[x]=ClampToQuantum(pixel.index);
845 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
847 if (image->progress_monitor != (MagickProgressMonitor) NULL)
852 #if defined(MAGICKCORE_OPENMP_SUPPORT)
853 #pragma omp critical (MagickCore_ClutImageChannel)
855 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
856 if (proceed == MagickFalse)
860 image_view=DestroyCacheView(image_view);
861 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
863 Enable alpha channel if CLUT image could enable it.
865 if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
866 (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
871 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
875 % C o n t r a s t I m a g e %
879 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
881 % ContrastImage() enhances the intensity differences between the lighter and
882 % darker elements of the image. Set sharpen to a MagickTrue to increase the
883 % image contrast otherwise the contrast is reduced.
885 % The format of the ContrastImage method is:
887 % MagickBooleanType ContrastImage(Image *image,
888 % const MagickBooleanType sharpen)
890 % A description of each parameter follows:
892 % o image: the image.
894 % o sharpen: Increase or decrease image contrast.
898 static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
906 Enhance contrast: dark color become darker, light color become lighter.
908 assert(red != (Quantum *) NULL);
909 assert(green != (Quantum *) NULL);
910 assert(blue != (Quantum *) NULL);
914 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
915 brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
916 if (brightness > 1.0)
919 if (brightness < 0.0)
921 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
924 MagickExport MagickBooleanType ContrastImage(Image *image,
925 const MagickBooleanType sharpen)
927 #define ContrastImageTag "Contrast/Image"
948 assert(image != (Image *) NULL);
949 assert(image->signature == MagickSignature);
950 if (image->debug != MagickFalse)
951 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
952 sign=sharpen != MagickFalse ? 1 : -1;
953 if (image->storage_class == PseudoClass)
956 Contrast enhance colormap.
958 for (i=0; i < (long) image->colors; i++)
959 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
960 &image->colormap[i].blue);
963 Contrast enhance image.
967 exception=(&image->exception);
968 image_view=AcquireCacheView(image);
969 #if defined(MAGICKCORE_OPENMP_SUPPORT)
970 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
972 for (y=0; y < (long) image->rows; y++)
980 if (status == MagickFalse)
982 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
983 if (q == (PixelPacket *) NULL)
988 for (x=0; x < (long) image->columns; x++)
990 Contrast(sign,&q->red,&q->green,&q->blue);
993 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
995 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1000 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1001 #pragma omp critical (MagickCore_ContrastImage)
1003 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
1004 if (proceed == MagickFalse)
1008 image_view=DestroyCacheView(image_view);
1013 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1017 % C o n t r a s t S t r e t c h I m a g e %
1021 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1023 % The ContrastStretchImage() is a simple image enhancement technique that
1024 % attempts to improve the contrast in an image by `stretching' the range of
1025 % intensity values it contains to span a desired range of values. It differs
1026 % from the more sophisticated histogram equalization in that it can only
1027 % apply % a linear scaling function to the image pixel values. As a result
1028 % the `enhancement' is less harsh.
1030 % The format of the ContrastStretchImage method is:
1032 % MagickBooleanType ContrastStretchImage(Image *image,
1033 % const char *levels)
1034 % MagickBooleanType ContrastStretchImageChannel(Image *image,
1035 % const unsigned long channel,const double black_point,
1036 % const double white_point)
1038 % A description of each parameter follows:
1040 % o image: the image.
1042 % o channel: the channel.
1044 % o black_point: the black point.
1046 % o white_point: the white point.
1048 % o levels: Specify the levels where the black and white points have the
1049 % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1053 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1072 if (levels == (char *) NULL)
1073 return(MagickFalse);
1074 flags=ParseGeometry(levels,&geometry_info);
1075 black_point=geometry_info.rho;
1076 white_point=(double) image->columns*image->rows;
1077 if ((flags & SigmaValue) != 0)
1078 white_point=geometry_info.sigma;
1079 if ((flags & PercentValue) != 0)
1081 black_point*=(double) QuantumRange/100.0;
1082 white_point*=(double) QuantumRange/100.0;
1084 if ((flags & SigmaValue) == 0)
1085 white_point=(double) image->columns*image->rows-black_point;
1086 status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
1091 MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
1092 const ChannelType channel,const double black_point,const double white_point)
1094 #define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1095 #define ContrastStretchImageTag "ContrastStretch/Image"
1123 Allocate histogram and stretch map.
1125 assert(image != (Image *) NULL);
1126 assert(image->signature == MagickSignature);
1127 if (image->debug != MagickFalse)
1128 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1129 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1130 sizeof(*histogram));
1131 stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1132 sizeof(*stretch_map));
1133 if ((histogram == (MagickPixelPacket *) NULL) ||
1134 (stretch_map == (MagickPixelPacket *) NULL))
1135 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1141 exception=(&image->exception);
1142 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1143 image_view=AcquireCacheView(image);
1144 for (y=0; y < (long) image->rows; y++)
1146 register const PixelPacket
1149 register IndexPacket
1155 if (status == MagickFalse)
1157 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1158 if (p == (const PixelPacket *) NULL)
1163 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1164 if (channel == DefaultChannels)
1165 for (x=0; x < (long) image->columns; x++)
1170 intensity=PixelIntensityToQuantum(p);
1171 histogram[ScaleQuantumToMap(intensity)].red++;
1172 histogram[ScaleQuantumToMap(intensity)].green++;
1173 histogram[ScaleQuantumToMap(intensity)].blue++;
1174 histogram[ScaleQuantumToMap(intensity)].index++;
1178 for (x=0; x < (long) image->columns; x++)
1180 if ((channel & RedChannel) != 0)
1181 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1182 if ((channel & GreenChannel) != 0)
1183 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1184 if ((channel & BlueChannel) != 0)
1185 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1186 if ((channel & OpacityChannel) != 0)
1187 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1188 if (((channel & IndexChannel) != 0) &&
1189 (image->colorspace == CMYKColorspace))
1190 histogram[ScaleQuantumToMap(indexes[x])].index++;
1195 Find the histogram boundaries by locating the black/white levels.
1198 white.red=MaxRange(QuantumRange);
1199 if ((channel & RedChannel) != 0)
1202 for (i=0; i <= (long) MaxMap; i++)
1204 intensity+=histogram[i].red;
1205 if (intensity > black_point)
1208 black.red=(MagickRealType) i;
1210 for (i=(long) MaxMap; i != 0; i--)
1212 intensity+=histogram[i].red;
1213 if (intensity > ((double) image->columns*image->rows-white_point))
1216 white.red=(MagickRealType) i;
1219 white.green=MaxRange(QuantumRange);
1220 if ((channel & GreenChannel) != 0)
1223 for (i=0; i <= (long) MaxMap; i++)
1225 intensity+=histogram[i].green;
1226 if (intensity > black_point)
1229 black.green=(MagickRealType) i;
1231 for (i=(long) MaxMap; i != 0; i--)
1233 intensity+=histogram[i].green;
1234 if (intensity > ((double) image->columns*image->rows-white_point))
1237 white.green=(MagickRealType) i;
1240 white.blue=MaxRange(QuantumRange);
1241 if ((channel & BlueChannel) != 0)
1244 for (i=0; i <= (long) MaxMap; i++)
1246 intensity+=histogram[i].blue;
1247 if (intensity > black_point)
1250 black.blue=(MagickRealType) i;
1252 for (i=(long) MaxMap; i != 0; i--)
1254 intensity+=histogram[i].blue;
1255 if (intensity > ((double) image->columns*image->rows-white_point))
1258 white.blue=(MagickRealType) i;
1261 white.opacity=MaxRange(QuantumRange);
1262 if ((channel & OpacityChannel) != 0)
1265 for (i=0; i <= (long) MaxMap; i++)
1267 intensity+=histogram[i].opacity;
1268 if (intensity > black_point)
1271 black.opacity=(MagickRealType) i;
1273 for (i=(long) MaxMap; i != 0; i--)
1275 intensity+=histogram[i].opacity;
1276 if (intensity > ((double) image->columns*image->rows-white_point))
1279 white.opacity=(MagickRealType) i;
1282 white.index=MaxRange(QuantumRange);
1283 if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
1286 for (i=0; i <= (long) MaxMap; i++)
1288 intensity+=histogram[i].index;
1289 if (intensity > black_point)
1292 black.index=(MagickRealType) i;
1294 for (i=(long) MaxMap; i != 0; i--)
1296 intensity+=histogram[i].index;
1297 if (intensity > ((double) image->columns*image->rows-white_point))
1300 white.index=(MagickRealType) i;
1302 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1304 Stretch the histogram to create the stretched image mapping.
1306 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
1307 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1308 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1310 for (i=0; i <= (long) MaxMap; i++)
1312 if ((channel & RedChannel) != 0)
1314 if (i < (long) black.red)
1315 stretch_map[i].red=0.0;
1317 if (i > (long) white.red)
1318 stretch_map[i].red=(MagickRealType) QuantumRange;
1320 if (black.red != white.red)
1321 stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
1322 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
1324 if ((channel & GreenChannel) != 0)
1326 if (i < (long) black.green)
1327 stretch_map[i].green=0.0;
1329 if (i > (long) white.green)
1330 stretch_map[i].green=(MagickRealType) QuantumRange;
1332 if (black.green != white.green)
1333 stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
1334 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
1337 if ((channel & BlueChannel) != 0)
1339 if (i < (long) black.blue)
1340 stretch_map[i].blue=0.0;
1342 if (i > (long) white.blue)
1343 stretch_map[i].blue=(MagickRealType) QuantumRange;
1345 if (black.blue != white.blue)
1346 stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
1347 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
1350 if ((channel & OpacityChannel) != 0)
1352 if (i < (long) black.opacity)
1353 stretch_map[i].opacity=0.0;
1355 if (i > (long) white.opacity)
1356 stretch_map[i].opacity=(MagickRealType) QuantumRange;
1358 if (black.opacity != white.opacity)
1359 stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1360 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
1363 if (((channel & IndexChannel) != 0) &&
1364 (image->colorspace == CMYKColorspace))
1366 if (i < (long) black.index)
1367 stretch_map[i].index=0.0;
1369 if (i > (long) white.index)
1370 stretch_map[i].index=(MagickRealType) QuantumRange;
1372 if (black.index != white.index)
1373 stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
1374 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
1381 if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
1382 (image->colorspace == CMYKColorspace)))
1383 image->storage_class=DirectClass;
1384 if (image->storage_class == PseudoClass)
1389 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1390 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1392 for (i=0; i < (long) image->colors; i++)
1394 if ((channel & RedChannel) != 0)
1396 if (black.red != white.red)
1397 image->colormap[i].red=ClampToQuantum(stretch_map[
1398 ScaleQuantumToMap(image->colormap[i].red)].red);
1400 if ((channel & GreenChannel) != 0)
1402 if (black.green != white.green)
1403 image->colormap[i].green=ClampToQuantum(stretch_map[
1404 ScaleQuantumToMap(image->colormap[i].green)].green);
1406 if ((channel & BlueChannel) != 0)
1408 if (black.blue != white.blue)
1409 image->colormap[i].blue=ClampToQuantum(stretch_map[
1410 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1412 if ((channel & OpacityChannel) != 0)
1414 if (black.opacity != white.opacity)
1415 image->colormap[i].opacity=ClampToQuantum(stretch_map[
1416 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1425 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1426 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1428 for (y=0; y < (long) image->rows; y++)
1430 register IndexPacket
1436 register PixelPacket
1439 if (status == MagickFalse)
1441 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1442 if (q == (PixelPacket *) NULL)
1447 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1448 for (x=0; x < (long) image->columns; x++)
1450 if ((channel & RedChannel) != 0)
1452 if (black.red != white.red)
1453 q->red=ClampToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
1455 if ((channel & GreenChannel) != 0)
1457 if (black.green != white.green)
1458 q->green=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1461 if ((channel & BlueChannel) != 0)
1463 if (black.blue != white.blue)
1464 q->blue=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1467 if ((channel & OpacityChannel) != 0)
1469 if (black.opacity != white.opacity)
1470 q->opacity=ClampToQuantum(stretch_map[ScaleQuantumToMap(
1471 q->opacity)].opacity);
1473 if (((channel & IndexChannel) != 0) &&
1474 (image->colorspace == CMYKColorspace))
1476 if (black.index != white.index)
1477 indexes[x]=(IndexPacket) ClampToQuantum(stretch_map[
1478 ScaleQuantumToMap(indexes[x])].index);
1482 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1484 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1489 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1490 #pragma omp critical (MagickCore_ContrastStretchImageChannel)
1492 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1494 if (proceed == MagickFalse)
1498 image_view=DestroyCacheView(image_view);
1499 stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
1504 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1508 % E n h a n c e I m a g e %
1512 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1514 % EnhanceImage() applies a digital filter that improves the quality of a
1517 % The format of the EnhanceImage method is:
1519 % Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1521 % A description of each parameter follows:
1523 % o image: the image.
1525 % o exception: return any errors or warnings in this structure.
1528 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1530 #define Enhance(weight) \
1531 mean=((MagickRealType) r->red+pixel.red)/2; \
1532 distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
1533 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
1534 mean)*distance*distance; \
1535 mean=((MagickRealType) r->green+pixel.green)/2; \
1536 distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
1537 distance_squared+=4.0*distance*distance; \
1538 mean=((MagickRealType) r->blue+pixel.blue)/2; \
1539 distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
1540 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1541 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1542 mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
1543 distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
1544 distance_squared+=QuantumScale*(3.0*((MagickRealType) \
1545 QuantumRange+1.0)-1.0-mean)*distance*distance; \
1546 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1547 QuantumRange/25.0f)) \
1549 aggregate.red+=(weight)*r->red; \
1550 aggregate.green+=(weight)*r->green; \
1551 aggregate.blue+=(weight)*r->blue; \
1552 aggregate.opacity+=(weight)*r->opacity; \
1553 total_weight+=(weight); \
1556 #define EnhanceImageTag "Enhance/Image"
1576 Initialize enhanced image attributes.
1578 assert(image != (const Image *) NULL);
1579 assert(image->signature == MagickSignature);
1580 if (image->debug != MagickFalse)
1581 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1582 assert(exception != (ExceptionInfo *) NULL);
1583 assert(exception->signature == MagickSignature);
1584 if ((image->columns < 5) || (image->rows < 5))
1585 return((Image *) NULL);
1586 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1588 if (enhance_image == (Image *) NULL)
1589 return((Image *) NULL);
1590 if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
1592 InheritException(exception,&enhance_image->exception);
1593 enhance_image=DestroyImage(enhance_image);
1594 return((Image *) NULL);
1601 (void) ResetMagickMemory(&zero,0,sizeof(zero));
1602 image_view=AcquireCacheView(image);
1603 enhance_view=AcquireCacheView(enhance_image);
1604 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1607 for (y=0; y < (long) image->rows; y++)
1609 register const PixelPacket
1615 register PixelPacket
1619 Read another scan line.
1621 if (status == MagickFalse)
1623 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1624 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1626 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1631 for (x=0; x < (long) image->columns; x++)
1645 register const PixelPacket
1649 Compute weighted average of target pixel color components.
1653 r=p+2*(image->columns+4)+2;
1656 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1657 r=p+(image->columns+4);
1658 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1659 r=p+2*(image->columns+4);
1660 Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
1661 r=p+3*(image->columns+4);
1662 Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
1663 r=p+4*(image->columns+4);
1664 Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1665 q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
1666 q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
1667 q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
1668 q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
1673 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1675 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1680 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1681 #pragma omp critical (MagickCore_EnhanceImage)
1683 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1684 if (proceed == MagickFalse)
1688 enhance_view=DestroyCacheView(enhance_view);
1689 image_view=DestroyCacheView(image_view);
1690 return(enhance_image);
1694 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698 % E q u a l i z e I m a g e %
1702 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1704 % EqualizeImage() applies a histogram equalization to the image.
1706 % The format of the EqualizeImage method is:
1708 % MagickBooleanType EqualizeImage(Image *image)
1709 % MagickBooleanType EqualizeImageChannel(Image *image,
1710 % const ChannelType channel)
1712 % A description of each parameter follows:
1714 % o image: the image.
1716 % o channel: the channel.
1720 MagickExport MagickBooleanType EqualizeImage(Image *image)
1722 return(EqualizeImageChannel(image,DefaultChannels));
1725 MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
1726 const ChannelType channel)
1728 #define EqualizeImageTag "Equalize/Image"
1755 Allocate and initialize histogram arrays.
1757 assert(image != (Image *) NULL);
1758 assert(image->signature == MagickSignature);
1759 if (image->debug != MagickFalse)
1760 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1761 equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1762 sizeof(*equalize_map));
1763 histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
1764 sizeof(*histogram));
1765 map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
1766 if ((equalize_map == (MagickPixelPacket *) NULL) ||
1767 (histogram == (MagickPixelPacket *) NULL) ||
1768 (map == (MagickPixelPacket *) NULL))
1770 if (map != (MagickPixelPacket *) NULL)
1771 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1772 if (histogram != (MagickPixelPacket *) NULL)
1773 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1774 if (equalize_map != (MagickPixelPacket *) NULL)
1775 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1776 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1782 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
1783 exception=(&image->exception);
1784 for (y=0; y < (long) image->rows; y++)
1786 register const IndexPacket
1789 register const PixelPacket
1795 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
1796 if (p == (const PixelPacket *) NULL)
1798 indexes=GetVirtualIndexQueue(image);
1799 for (x=0; x < (long) image->columns; x++)
1801 if ((channel & RedChannel) != 0)
1802 histogram[ScaleQuantumToMap(GetRedPixelComponent(p))].red++;
1803 if ((channel & GreenChannel) != 0)
1804 histogram[ScaleQuantumToMap(GetGreenPixelComponent(p))].green++;
1805 if ((channel & BlueChannel) != 0)
1806 histogram[ScaleQuantumToMap(GetBluePixelComponent(p))].blue++;
1807 if ((channel & OpacityChannel) != 0)
1808 histogram[ScaleQuantumToMap(GetOpacityPixelComponent(p))].opacity++;
1809 if (((channel & IndexChannel) != 0) &&
1810 (image->colorspace == CMYKColorspace))
1811 histogram[ScaleQuantumToMap(indexes[x])].index++;
1816 Integrate the histogram to get the equalization map.
1818 (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
1819 for (i=0; i <= (long) MaxMap; i++)
1821 if ((channel & RedChannel) != 0)
1822 intensity.red+=histogram[i].red;
1823 if ((channel & GreenChannel) != 0)
1824 intensity.green+=histogram[i].green;
1825 if ((channel & BlueChannel) != 0)
1826 intensity.blue+=histogram[i].blue;
1827 if ((channel & OpacityChannel) != 0)
1828 intensity.opacity+=histogram[i].opacity;
1829 if (((channel & IndexChannel) != 0) &&
1830 (image->colorspace == CMYKColorspace))
1831 intensity.index+=histogram[i].index;
1835 white=map[(int) MaxMap];
1836 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
1837 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1838 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1840 for (i=0; i <= (long) MaxMap; i++)
1842 if (((channel & RedChannel) != 0) && (white.red != black.red))
1843 equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1844 ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
1845 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1846 equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1847 ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
1848 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1849 equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1850 ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
1851 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1852 equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
1853 (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
1854 (white.opacity-black.opacity)));
1855 if ((((channel & IndexChannel) != 0) &&
1856 (image->colorspace == CMYKColorspace)) &&
1857 (white.index != black.index))
1858 equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
1859 ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
1861 histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
1862 map=(MagickPixelPacket *) RelinquishMagickMemory(map);
1863 if (image->storage_class == PseudoClass)
1868 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1869 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1871 for (i=0; i < (long) image->colors; i++)
1873 if (((channel & RedChannel) != 0) && (white.red != black.red))
1874 image->colormap[i].red=ClampToQuantum(equalize_map[
1875 ScaleQuantumToMap(image->colormap[i].red)].red);
1876 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1877 image->colormap[i].green=ClampToQuantum(equalize_map[
1878 ScaleQuantumToMap(image->colormap[i].green)].green);
1879 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1880 image->colormap[i].blue=ClampToQuantum(equalize_map[
1881 ScaleQuantumToMap(image->colormap[i].blue)].blue);
1882 if (((channel & OpacityChannel) != 0) &&
1883 (white.opacity != black.opacity))
1884 image->colormap[i].opacity=ClampToQuantum(equalize_map[
1885 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
1893 exception=(&image->exception);
1894 image_view=AcquireCacheView(image);
1895 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1896 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1898 for (y=0; y < (long) image->rows; y++)
1900 register IndexPacket
1906 register PixelPacket
1909 if (status == MagickFalse)
1911 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1912 if (q == (PixelPacket *) NULL)
1917 indexes=GetCacheViewAuthenticIndexQueue(image_view);
1918 for (x=0; x < (long) image->columns; x++)
1920 if (((channel & RedChannel) != 0) && (white.red != black.red))
1921 q->red=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
1922 if (((channel & GreenChannel) != 0) && (white.green != black.green))
1923 q->green=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1925 if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
1926 q->blue=ClampToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
1927 if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
1928 q->opacity=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1929 q->opacity)].opacity);
1930 if ((((channel & IndexChannel) != 0) &&
1931 (image->colorspace == CMYKColorspace)) &&
1932 (white.index != black.index))
1933 indexes[x]=ClampToQuantum(equalize_map[ScaleQuantumToMap(
1934 indexes[x])].index);
1937 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1939 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1944 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1945 #pragma omp critical (MagickCore_EqualizeImageChannel)
1947 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1948 if (proceed == MagickFalse)
1952 image_view=DestroyCacheView(image_view);
1953 equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
1958 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1962 % G a m m a I m a g e %
1966 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1968 % GammaImage() gamma-corrects a particular image channel. The same
1969 % image viewed on different devices will have perceptual differences in the
1970 % way the image's intensities are represented on the screen. Specify
1971 % individual gamma levels for the red, green, and blue channels, or adjust
1972 % all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1974 % You can also reduce the influence of a particular channel with a gamma
1977 % The format of the GammaImage method is:
1979 % MagickBooleanType GammaImage(Image *image,const double gamma)
1980 % MagickBooleanType GammaImageChannel(Image *image,
1981 % const ChannelType channel,const double gamma)
1983 % A description of each parameter follows:
1985 % o image: the image.
1987 % o channel: the channel.
1989 % o gamma: the image gamma.
1992 MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
2004 assert(image != (Image *) NULL);
2005 assert(image->signature == MagickSignature);
2006 if (image->debug != MagickFalse)
2007 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2008 if (level == (char *) NULL)
2009 return(MagickFalse);
2010 flags=ParseGeometry(level,&geometry_info);
2011 gamma.red=geometry_info.rho;
2012 gamma.green=geometry_info.sigma;
2013 if ((flags & SigmaValue) == 0)
2014 gamma.green=gamma.red;
2015 gamma.blue=geometry_info.xi;
2016 if ((flags & XiValue) == 0)
2017 gamma.blue=gamma.red;
2018 if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
2020 if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
2021 status=GammaImageChannel(image,(const ChannelType) (RedChannel |
2022 GreenChannel | BlueChannel),(double) gamma.red);
2025 status=GammaImageChannel(image,RedChannel,(double) gamma.red);
2026 status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
2027 status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
2029 return(status != 0 ? MagickTrue : MagickFalse);
2032 MagickExport MagickBooleanType GammaImageChannel(Image *image,
2033 const ChannelType channel,const double gamma)
2035 #define GammaCorrectImageTag "GammaCorrect/Image"
2057 Allocate and initialize gamma maps.
2059 assert(image != (Image *) NULL);
2060 assert(image->signature == MagickSignature);
2061 if (image->debug != MagickFalse)
2062 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2065 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2066 if (gamma_map == (Quantum *) NULL)
2067 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2069 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2071 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2072 #pragma omp parallel for schedule(dynamic,4)
2074 for (i=0; i <= (long) MaxMap; i++)
2075 gamma_map[i]=ClampToQuantum((MagickRealType) ScaleMapToQuantum((
2076 MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
2077 if (image->storage_class == PseudoClass)
2080 Gamma-correct colormap.
2082 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2083 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2085 for (i=0; i < (long) image->colors; i++)
2087 if ((channel & RedChannel) != 0)
2088 image->colormap[i].red=gamma_map[
2089 ScaleQuantumToMap(image->colormap[i].red)];
2090 if ((channel & GreenChannel) != 0)
2091 image->colormap[i].green=gamma_map[
2092 ScaleQuantumToMap(image->colormap[i].green)];
2093 if ((channel & BlueChannel) != 0)
2094 image->colormap[i].blue=gamma_map[
2095 ScaleQuantumToMap(image->colormap[i].blue)];
2096 if ((channel & OpacityChannel) != 0)
2098 if (image->matte == MagickFalse)
2099 image->colormap[i].opacity=gamma_map[
2100 ScaleQuantumToMap(image->colormap[i].opacity)];
2102 image->colormap[i].opacity=(Quantum) QuantumRange-
2103 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
2104 image->colormap[i].opacity))];
2109 Gamma-correct image.
2113 exception=(&image->exception);
2114 image_view=AcquireCacheView(image);
2115 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2116 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2118 for (y=0; y < (long) image->rows; y++)
2120 register IndexPacket
2126 register PixelPacket
2129 if (status == MagickFalse)
2131 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2132 if (q == (PixelPacket *) NULL)
2137 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2138 for (x=0; x < (long) image->columns; x++)
2140 if (channel == DefaultChannels)
2142 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2143 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2144 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2148 if ((channel & RedChannel) != 0)
2149 q->red=gamma_map[ScaleQuantumToMap(q->red)];
2150 if ((channel & GreenChannel) != 0)
2151 q->green=gamma_map[ScaleQuantumToMap(q->green)];
2152 if ((channel & BlueChannel) != 0)
2153 q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
2154 if ((channel & OpacityChannel) != 0)
2156 if (image->matte == MagickFalse)
2157 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
2159 q->opacity=(Quantum) QuantumRange-gamma_map[
2160 ScaleQuantumToMap((Quantum) GetAlphaPixelComponent(q))];
2165 if (((channel & IndexChannel) != 0) &&
2166 (image->colorspace == CMYKColorspace))
2167 for (x=0; x < (long) image->columns; x++)
2168 indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
2169 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2171 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2176 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2177 #pragma omp critical (MagickCore_GammaImageChannel)
2179 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
2181 if (proceed == MagickFalse)
2185 image_view=DestroyCacheView(image_view);
2186 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2187 if (image->gamma != 0.0)
2188 image->gamma*=gamma;
2193 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2197 % H a l d C l u t I m a g e %
2201 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2203 % HaldClutImage() applies a Hald color lookup table to the image. A Hald
2204 % color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2205 % Create it with the HALD coder. You can apply any color transformation to
2206 % the Hald image and then use this method to apply the transform to the
2209 % The format of the HaldClutImage method is:
2211 % MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
2212 % MagickBooleanType HaldClutImageChannel(Image *image,
2213 % const ChannelType channel,Image *hald_image)
2215 % A description of each parameter follows:
2217 % o image: the image, which is replaced by indexed CLUT values
2219 % o hald_image: the color lookup table image for replacement color values.
2221 % o channel: the channel.
2225 static inline size_t MagickMin(const size_t x,const size_t y)
2232 MagickExport MagickBooleanType HaldClutImage(Image *image,
2233 const Image *hald_image)
2235 return(HaldClutImageChannel(image,DefaultChannels,hald_image));
2238 MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
2239 const ChannelType channel,const Image *hald_image)
2241 #define HaldClutImageTag "Clut/Image"
2243 typedef struct _HaldInfo
2271 **restrict resample_filter;
2278 assert(image != (Image *) NULL);
2279 assert(image->signature == MagickSignature);
2280 if (image->debug != MagickFalse)
2281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2282 assert(hald_image != (Image *) NULL);
2283 assert(hald_image->signature == MagickSignature);
2284 if (SetImageStorageClass(image,DirectClass) == MagickFalse)
2285 return(MagickFalse);
2286 if (image->matte == MagickFalse)
2287 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
2293 length=MagickMin(hald_image->columns,hald_image->rows);
2294 for (level=2; (level*level*level) < length; level++) ;
2296 cube_size=level*level;
2297 width=(double) hald_image->columns;
2298 GetMagickPixelPacket(hald_image,&zero);
2299 exception=(&image->exception);
2300 resample_filter=AcquireResampleFilterThreadSet(hald_image,
2301 UndefinedVirtualPixelMethod,MagickTrue,exception);
2302 image_view=AcquireCacheView(image);
2303 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2304 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2306 for (y=0; y < (long) image->rows; y++)
2321 register IndexPacket
2328 register PixelPacket
2331 if (status == MagickFalse)
2333 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2334 if (q == (PixelPacket *) NULL)
2339 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2345 id=GetOpenMPThreadId();
2346 for (x=0; x < (long) image->columns; x++)
2348 point.x=QuantumScale*(level-1.0)*q->red;
2349 point.y=QuantumScale*(level-1.0)*q->green;
2350 point.z=QuantumScale*(level-1.0)*q->blue;
2351 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2352 point.x-=floor(point.x);
2353 point.y-=floor(point.y);
2354 point.z-=floor(point.z);
2355 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2356 floor(offset/width),&pixel1);
2357 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2358 floor((offset+level)/width),&pixel2);
2359 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2360 pixel2.opacity,point.y,&pixel3);
2362 (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
2363 floor(offset/width),&pixel1);
2364 (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
2365 floor((offset+level)/width),&pixel2);
2366 MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
2367 pixel2.opacity,point.y,&pixel4);
2368 MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
2369 pixel4.opacity,point.z,&pixel);
2370 if ((channel & RedChannel) != 0)
2371 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
2372 if ((channel & GreenChannel) != 0)
2373 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
2374 if ((channel & BlueChannel) != 0)
2375 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
2376 if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
2377 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
2378 if (((channel & IndexChannel) != 0) &&
2379 (image->colorspace == CMYKColorspace))
2380 indexes[x]=ClampToQuantum(pixel.index);
2383 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2385 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2390 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2391 #pragma omp critical (MagickCore_HaldClutImageChannel)
2393 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2394 if (proceed == MagickFalse)
2398 image_view=DestroyCacheView(image_view);
2399 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
2404 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2408 % L e v e l I m a g e %
2412 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2414 % LevelImage() adjusts the levels of a particular image channel by
2415 % scaling the colors falling between specified white and black points to
2416 % the full available quantum range.
2418 % The parameters provided represent the black, and white points. The black
2419 % point specifies the darkest color in the image. Colors darker than the
2420 % black point are set to zero. White point specifies the lightest color in
2421 % the image. Colors brighter than the white point are set to the maximum
2424 % If a '!' flag is given, map black and white colors to the given levels
2425 % rather than mapping those levels to black and white. See
2426 % LevelizeImageChannel() and LevelizeImageChannel(), below.
2428 % Gamma specifies a gamma correction to apply to the image.
2430 % The format of the LevelImage method is:
2432 % MagickBooleanType LevelImage(Image *image,const char *levels)
2434 % A description of each parameter follows:
2436 % o image: the image.
2438 % o levels: Specify the levels where the black and white points have the
2439 % range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
2440 % A '!' flag inverts the re-mapping.
2444 MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
2463 if (levels == (char *) NULL)
2464 return(MagickFalse);
2465 flags=ParseGeometry(levels,&geometry_info);
2466 black_point=geometry_info.rho;
2467 white_point=(double) QuantumRange;
2468 if ((flags & SigmaValue) != 0)
2469 white_point=geometry_info.sigma;
2471 if ((flags & XiValue) != 0)
2472 gamma=geometry_info.xi;
2473 if ((flags & PercentValue) != 0)
2475 black_point*=(double) image->columns*image->rows/100.0;
2476 white_point*=(double) image->columns*image->rows/100.0;
2478 if ((flags & SigmaValue) == 0)
2479 white_point=(double) QuantumRange-black_point;
2480 if ((flags & AspectValue ) == 0)
2481 status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
2484 status=LevelizeImage(image,black_point,white_point,gamma);
2489 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2493 % L e v e l i z e I m a g e %
2497 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2499 % LevelizeImage() applies the normal level operation to the image, spreading
2500 % out the values between the black and white points over the entire range of
2501 % values. Gamma correction is also applied after the values has been mapped.
2503 % It is typically used to improve image contrast, or to provide a controlled
2504 % linear threshold for the image. If the black and white points are set to
2505 % the minimum and maximum values found in the image, the image can be
2506 % normalized. or by swapping black and white values, negate the image.
2508 % The format of the LevelizeImage method is:
2510 % MagickBooleanType LevelizeImage(Image *image,const double black_point,
2511 % const double white_point,const double gamma)
2512 % MagickBooleanType LevelizeImageChannel(Image *image,
2513 % const ChannelType channel,const double black_point,
2514 % const double white_point,const double gamma)
2516 % A description of each parameter follows:
2518 % o image: the image.
2520 % o channel: the channel.
2522 % o black_point: The level which is to be mapped to zero (black)
2524 % o white_point: The level which is to be mapped to QuantiumRange (white)
2526 % o gamma: adjust gamma by this factor before mapping values.
2527 % use 1.0 for purely linear stretching of image color values
2531 MagickExport MagickBooleanType LevelizeImage(Image *image,
2532 const double black_point,const double white_point,const double gamma)
2537 status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
2542 MagickExport MagickBooleanType LevelImageChannel(Image *image,
2543 const ChannelType channel,const double black_point,const double white_point,
2546 #define LevelImageTag "Level/Image"
2547 #define LevelQuantum(x) (ClampToQuantum((MagickRealType) QuantumRange* \
2548 pow(scale*((double) (x)-black_point),1.0/gamma)))
2570 Allocate and initialize levels map.
2572 assert(image != (Image *) NULL);
2573 assert(image->signature == MagickSignature);
2574 if (image->debug != MagickFalse)
2575 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2576 scale = (white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2577 if (image->storage_class == PseudoClass)
2578 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2579 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2581 for (i=0; i < (long) image->colors; i++)
2586 if ((channel & RedChannel) != 0)
2587 image->colormap[i].red=LevelQuantum(image->colormap[i].red);
2588 if ((channel & GreenChannel) != 0)
2589 image->colormap[i].green=LevelQuantum(image->colormap[i].green);
2590 if ((channel & BlueChannel) != 0)
2591 image->colormap[i].blue=LevelQuantum(image->colormap[i].blue);
2592 if ((channel & OpacityChannel) != 0)
2593 image->colormap[i].opacity=LevelQuantum(image->colormap[i].opacity);
2600 exception=(&image->exception);
2601 image_view=AcquireCacheView(image);
2602 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2603 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2605 for (y=0; y < (long) image->rows; y++)
2607 register IndexPacket
2613 register PixelPacket
2616 if (status == MagickFalse)
2618 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2619 if (q == (PixelPacket *) NULL)
2624 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2625 for (x=0; x < (long) image->columns; x++)
2627 if ((channel & RedChannel) != 0)
2628 q->red=LevelQuantum(q->red);
2629 if ((channel & GreenChannel) != 0)
2630 q->green=LevelQuantum(q->green);
2631 if ((channel & BlueChannel) != 0)
2632 q->blue=LevelQuantum(q->blue);
2633 if (((channel & OpacityChannel) != 0) &&
2634 (image->matte == MagickTrue))
2635 q->opacity=QuantumRange-LevelQuantum(QuantumRange-q->opacity);
2636 if (((channel & IndexChannel) != 0) &&
2637 (image->colorspace == CMYKColorspace))
2638 indexes[x]=LevelQuantum(indexes[x]);
2641 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2643 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2648 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2649 #pragma omp critical (MagickCore_LevelImageChannel)
2651 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2652 if (proceed == MagickFalse)
2656 image_view=DestroyCacheView(image_view);
2661 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2665 % L e v e l i z e I m a g e C h a n n e l %
2669 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2671 % LevelizeImageChannel() applies the reversed LevelImage() operation to just
2672 % the specific channels specified. It compresses the full range of color
2673 % values, so that they lie between the given black and white points. Gamma is
2674 % applied before the values are mapped.
2676 % LevelizeImageChannel() can be called with by using a +level command line
2677 % API option, or using a '!' on a -level or LevelImage() geometry string.
2679 % It can be used for example de-contrast a greyscale image to the exact
2680 % levels specified. Or by using specific levels for each channel of an image
2681 % you can convert a gray-scale image to any linear color gradient, according
2684 % The format of the LevelizeImageChannel method is:
2686 % MagickBooleanType LevelizeImageChannel(Image *image,
2687 % const ChannelType channel,const char *levels)
2689 % A description of each parameter follows:
2691 % o image: the image.
2693 % o channel: the channel.
2695 % o black_point: The level to map zero (black) to.
2697 % o white_point: The level to map QuantiumRange (white) to.
2699 % o gamma: adjust gamma by this factor before mapping values.
2702 MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
2703 const ChannelType channel,const double black_point,const double white_point,
2706 #define LevelizeImageTag "Levelize/Image"
2707 #define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
2708 pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
2728 Allocate and initialize levels map.
2730 assert(image != (Image *) NULL);
2731 assert(image->signature == MagickSignature);
2732 if (image->debug != MagickFalse)
2733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2734 if (image->storage_class == PseudoClass)
2735 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2736 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2738 for (i=0; i < (long) image->colors; i++)
2743 if ((channel & RedChannel) != 0)
2744 image->colormap[i].red=LevelizeValue(image->colormap[i].red);
2745 if ((channel & GreenChannel) != 0)
2746 image->colormap[i].green=LevelizeValue(image->colormap[i].green);
2747 if ((channel & BlueChannel) != 0)
2748 image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
2749 if ((channel & OpacityChannel) != 0)
2750 image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
2757 exception=(&image->exception);
2758 image_view=AcquireCacheView(image);
2759 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2760 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2762 for (y=0; y < (long) image->rows; y++)
2764 register IndexPacket
2770 register PixelPacket
2773 if (status == MagickFalse)
2775 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2776 if (q == (PixelPacket *) NULL)
2781 indexes=GetCacheViewAuthenticIndexQueue(image_view);
2782 for (x=0; x < (long) image->columns; x++)
2784 if ((channel & RedChannel) != 0)
2785 q->red=LevelizeValue(q->red);
2786 if ((channel & GreenChannel) != 0)
2787 q->green=LevelizeValue(q->green);
2788 if ((channel & BlueChannel) != 0)
2789 q->blue=LevelizeValue(q->blue);
2790 if (((channel & OpacityChannel) != 0) &&
2791 (image->matte == MagickTrue))
2792 q->opacity=LevelizeValue(q->opacity);
2793 if (((channel & IndexChannel) != 0) &&
2794 (image->colorspace == CMYKColorspace))
2795 indexes[x]=LevelizeValue(indexes[x]);
2798 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2800 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2805 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2806 #pragma omp critical (MagickCore_LevelizeImageChannel)
2808 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2809 if (proceed == MagickFalse)
2817 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2821 % L e v e l I m a g e C o l o r s %
2825 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2827 % LevelImageColor() maps the given color to "black" and "white" values,
2828 % linearly spreading out the colors, and level values on a channel by channel
2829 % bases, as per LevelImage(). The given colors allows you to specify
2830 % different level ranges for each of the color channels seperatally.
2832 % If the boolean 'invert' is set true the image values will modifyed in the
2833 % reverse direction. That is any existing "black" and "white" colors in the
2834 % image will become the color values given, with all other values compressed
2835 % appropriatally. This effectivally maps a greyscale gradient into the given
2838 % The format of the LevelColorsImageChannel method is:
2840 % MagickBooleanType LevelColorsImage(Image *image,
2841 % const MagickPixelPacket *black_color,
2842 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2843 % MagickBooleanType LevelColorsImageChannel(Image *image,
2844 % const ChannelType channel,const MagickPixelPacket *black_color,
2845 % const MagickPixelPacket *white_color,const MagickBooleanType invert)
2847 % A description of each parameter follows:
2849 % o image: the image.
2851 % o channel: the channel.
2853 % o black_color: The color to map black to/from
2855 % o white_point: The color to map white to/from
2857 % o invert: if true map the colors (levelize), rather than from (level)
2861 MagickExport MagickBooleanType LevelColorsImage(Image *image,
2862 const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
2863 const MagickBooleanType invert)
2868 status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
2873 MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
2874 const ChannelType channel,const MagickPixelPacket *black_color,
2875 const MagickPixelPacket *white_color,const MagickBooleanType invert)
2881 Allocate and initialize levels map.
2883 assert(image != (Image *) NULL);
2884 assert(image->signature == MagickSignature);
2885 if (image->debug != MagickFalse)
2886 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2888 if (invert == MagickFalse)
2890 if ((channel & RedChannel) != 0)
2891 status|=LevelImageChannel(image,RedChannel,
2892 black_color->red,white_color->red,(double) 1.0);
2893 if ((channel & GreenChannel) != 0)
2894 status|=LevelImageChannel(image,GreenChannel,
2895 black_color->green,white_color->green,(double) 1.0);
2896 if ((channel & BlueChannel) != 0)
2897 status|=LevelImageChannel(image,BlueChannel,
2898 black_color->blue,white_color->blue,(double) 1.0);
2899 if (((channel & OpacityChannel) != 0) &&
2900 (image->matte == MagickTrue))
2901 status|=LevelImageChannel(image,OpacityChannel,
2902 black_color->opacity,white_color->opacity,(double) 1.0);
2903 if (((channel & IndexChannel) != 0) &&
2904 (image->colorspace == CMYKColorspace))
2905 status|=LevelImageChannel(image,IndexChannel,
2906 black_color->index,white_color->index,(double) 1.0);
2910 if ((channel & RedChannel) != 0)
2911 status|=LevelizeImageChannel(image,RedChannel,
2912 black_color->red,white_color->red,(double) 1.0);
2913 if ((channel & GreenChannel) != 0)
2914 status|=LevelizeImageChannel(image,GreenChannel,
2915 black_color->green,white_color->green,(double) 1.0);
2916 if ((channel & BlueChannel) != 0)
2917 status|=LevelizeImageChannel(image,BlueChannel,
2918 black_color->blue,white_color->blue,(double) 1.0);
2919 if (((channel & OpacityChannel) != 0) &&
2920 (image->matte == MagickTrue))
2921 status|=LevelizeImageChannel(image,OpacityChannel,
2922 black_color->opacity,white_color->opacity,(double) 1.0);
2923 if (((channel & IndexChannel) != 0) &&
2924 (image->colorspace == CMYKColorspace))
2925 status|=LevelizeImageChannel(image,IndexChannel,
2926 black_color->index,white_color->index,(double) 1.0);
2928 return(status == 0 ? MagickFalse : MagickTrue);
2932 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2936 % L i n e a r S t r e t c h I m a g e %
2940 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2942 % The LinearStretchImage() discards any pixels below the black point and
2943 % above the white point and levels the remaining pixels.
2945 % The format of the LinearStretchImage method is:
2947 % MagickBooleanType LinearStretchImage(Image *image,
2948 % const double black_point,const double white_point)
2950 % A description of each parameter follows:
2952 % o image: the image.
2954 % o black_point: the black point.
2956 % o white_point: the white point.
2959 MagickExport MagickBooleanType LinearStretchImage(Image *image,
2960 const double black_point,const double white_point)
2962 #define LinearStretchImageTag "LinearStretch/Image"
2983 Allocate histogram and linear map.
2985 assert(image != (Image *) NULL);
2986 assert(image->signature == MagickSignature);
2987 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2988 sizeof(*histogram));
2989 if (histogram == (MagickRealType *) NULL)
2990 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2995 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
2996 exception=(&image->exception);
2997 for (y=0; y < (long) image->rows; y++)
2999 register const PixelPacket
3005 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
3006 if (p == (const PixelPacket *) NULL)
3008 for (x=(long) image->columns-1; x >= 0; x--)
3010 histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
3015 Find the histogram boundaries by locating the black and white point levels.
3017 number_pixels=(MagickSizeType) image->columns*image->rows;
3019 for (black=0; black < (long) MaxMap; black++)
3021 intensity+=histogram[black];
3022 if (intensity >= black_point)
3026 for (white=(long) MaxMap; white != 0; white--)
3028 intensity+=histogram[white];
3029 if (intensity >= white_point)
3032 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
3033 status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
3039 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3043 % M o d u l a t e I m a g e %
3047 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3049 % ModulateImage() lets you control the brightness, saturation, and hue
3050 % of an image. Modulate represents the brightness, saturation, and hue
3051 % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
3052 % modulation is lightness, saturation, and hue. And if the colorspace is
3053 % HWB, use blackness, whiteness, and hue.
3055 % The format of the ModulateImage method is:
3057 % MagickBooleanType ModulateImage(Image *image,const char *modulate)
3059 % A description of each parameter follows:
3061 % o image: the image.
3063 % o modulate: Define the percent change in brightness, saturation, and
3068 static void ModulateHSB(const double percent_hue,
3069 const double percent_saturation,const double percent_brightness,
3070 Quantum *red,Quantum *green,Quantum *blue)
3078 Increase or decrease color brightness, saturation, or hue.
3080 assert(red != (Quantum *) NULL);
3081 assert(green != (Quantum *) NULL);
3082 assert(blue != (Quantum *) NULL);
3083 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3084 hue+=0.5*(0.01*percent_hue-1.0);
3089 saturation*=0.01*percent_saturation;
3090 brightness*=0.01*percent_brightness;
3091 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3094 static void ModulateHSL(const double percent_hue,
3095 const double percent_saturation,const double percent_lightness,
3096 Quantum *red,Quantum *green,Quantum *blue)
3104 Increase or decrease color lightness, saturation, or hue.
3106 assert(red != (Quantum *) NULL);
3107 assert(green != (Quantum *) NULL);
3108 assert(blue != (Quantum *) NULL);
3109 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3110 hue+=0.5*(0.01*percent_hue-1.0);
3115 saturation*=0.01*percent_saturation;
3116 lightness*=0.01*percent_lightness;
3117 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3120 static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
3128 Increase or decrease color blackness, whiteness, or hue.
3130 assert(red != (Quantum *) NULL);
3131 assert(green != (Quantum *) NULL);
3132 assert(blue != (Quantum *) NULL);
3133 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3134 hue+=0.5*(0.01*percent_hue-1.0);
3139 blackness*=0.01*percent_blackness;
3140 whiteness*=0.01*percent_whiteness;
3141 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3144 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
3146 #define ModulateImageTag "Modulate/Image"
3182 Initialize modulate table.
3184 assert(image != (Image *) NULL);
3185 assert(image->signature == MagickSignature);
3186 if (image->debug != MagickFalse)
3187 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3188 if (modulate == (char *) NULL)
3189 return(MagickFalse);
3190 flags=ParseGeometry(modulate,&geometry_info);
3191 percent_brightness=geometry_info.rho;
3192 percent_saturation=geometry_info.sigma;
3193 if ((flags & SigmaValue) == 0)
3194 percent_saturation=100.0;
3195 percent_hue=geometry_info.xi;
3196 if ((flags & XiValue) == 0)
3198 colorspace=UndefinedColorspace;
3199 artifact=GetImageArtifact(image,"modulate:colorspace");
3200 if (artifact != (const char *) NULL)
3201 colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
3202 MagickFalse,artifact);
3203 if (image->storage_class == PseudoClass)
3208 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3209 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3211 for (i=0; i < (long) image->colors; i++)
3216 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3217 &image->colormap[i].red,&image->colormap[i].green,
3218 &image->colormap[i].blue);
3224 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3225 &image->colormap[i].red,&image->colormap[i].green,
3226 &image->colormap[i].blue);
3231 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3232 &image->colormap[i].red,&image->colormap[i].green,
3233 &image->colormap[i].blue);
3243 exception=(&image->exception);
3244 image_view=AcquireCacheView(image);
3245 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3246 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3248 for (y=0; y < (long) image->rows; y++)
3253 register PixelPacket
3256 if (status == MagickFalse)
3258 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3259 if (q == (PixelPacket *) NULL)
3264 for (x=0; x < (long) image->columns; x++)
3270 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3271 &q->red,&q->green,&q->blue);
3277 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3278 &q->red,&q->green,&q->blue);
3283 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3284 &q->red,&q->green,&q->blue);
3290 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3292 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3297 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3298 #pragma omp critical (MagickCore_ModulateImage)
3300 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3301 if (proceed == MagickFalse)
3305 image_view=DestroyCacheView(image_view);
3310 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3314 % N e g a t e I m a g e %
3318 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3320 % NegateImage() negates the colors in the reference image. The grayscale
3321 % option means that only grayscale values within the image are negated.
3323 % The format of the NegateImageChannel method is:
3325 % MagickBooleanType NegateImage(Image *image,
3326 % const MagickBooleanType grayscale)
3327 % MagickBooleanType NegateImageChannel(Image *image,
3328 % const ChannelType channel,const MagickBooleanType grayscale)
3330 % A description of each parameter follows:
3332 % o image: the image.
3334 % o channel: the channel.
3336 % o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3340 MagickExport MagickBooleanType NegateImage(Image *image,
3341 const MagickBooleanType grayscale)
3346 status=NegateImageChannel(image,DefaultChannels,grayscale);
3350 MagickExport MagickBooleanType NegateImageChannel(Image *image,
3351 const ChannelType channel,const MagickBooleanType grayscale)
3353 #define NegateImageTag "Negate/Image"
3371 assert(image != (Image *) NULL);
3372 assert(image->signature == MagickSignature);
3373 if (image->debug != MagickFalse)
3374 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3375 if (image->storage_class == PseudoClass)
3380 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3381 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3383 for (i=0; i < (long) image->colors; i++)
3385 if (grayscale != MagickFalse)
3386 if ((image->colormap[i].red != image->colormap[i].green) ||
3387 (image->colormap[i].green != image->colormap[i].blue))
3389 if ((channel & RedChannel) != 0)
3390 image->colormap[i].red=(Quantum) QuantumRange-
3391 image->colormap[i].red;
3392 if ((channel & GreenChannel) != 0)
3393 image->colormap[i].green=(Quantum) QuantumRange-
3394 image->colormap[i].green;
3395 if ((channel & BlueChannel) != 0)
3396 image->colormap[i].blue=(Quantum) QuantumRange-
3397 image->colormap[i].blue;
3405 exception=(&image->exception);
3406 image_view=AcquireCacheView(image);
3407 if (grayscale != MagickFalse)
3409 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3410 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3412 for (y=0; y < (long) image->rows; y++)
3417 register IndexPacket
3423 register PixelPacket
3426 if (status == MagickFalse)
3428 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3430 if (q == (PixelPacket *) NULL)
3435 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3436 for (x=0; x < (long) image->columns; x++)
3438 if ((q->red != q->green) || (q->green != q->blue))
3443 if ((channel & RedChannel) != 0)
3444 q->red=(Quantum) QuantumRange-q->red;
3445 if ((channel & GreenChannel) != 0)
3446 q->green=(Quantum) QuantumRange-q->green;
3447 if ((channel & BlueChannel) != 0)
3448 q->blue=(Quantum) QuantumRange-q->blue;
3449 if ((channel & OpacityChannel) != 0)
3450 q->opacity=(Quantum) QuantumRange-q->opacity;
3451 if (((channel & IndexChannel) != 0) &&
3452 (image->colorspace == CMYKColorspace))
3453 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3456 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3457 if (sync == MagickFalse)
3459 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3464 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3465 #pragma omp critical (MagickCore_NegateImageChannel)
3467 proceed=SetImageProgress(image,NegateImageTag,progress++,
3469 if (proceed == MagickFalse)
3473 image_view=DestroyCacheView(image_view);
3479 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3480 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3482 for (y=0; y < (long) image->rows; y++)
3484 register IndexPacket
3490 register PixelPacket
3493 if (status == MagickFalse)
3495 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3496 if (q == (PixelPacket *) NULL)
3501 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3502 for (x=0; x < (long) image->columns; x++)
3504 if ((channel & RedChannel) != 0)
3505 q->red=(Quantum) QuantumRange-q->red;
3506 if ((channel & GreenChannel) != 0)
3507 q->green=(Quantum) QuantumRange-q->green;
3508 if ((channel & BlueChannel) != 0)
3509 q->blue=(Quantum) QuantumRange-q->blue;
3510 if ((channel & OpacityChannel) != 0)
3511 q->opacity=(Quantum) QuantumRange-q->opacity;
3512 if (((channel & IndexChannel) != 0) &&
3513 (image->colorspace == CMYKColorspace))
3514 indexes[x]=(IndexPacket) QuantumRange-indexes[x];
3517 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3519 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3524 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3525 #pragma omp critical (MagickCore_NegateImageChannel)
3527 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3528 if (proceed == MagickFalse)
3532 image_view=DestroyCacheView(image_view);
3537 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3541 % N o r m a l i z e I m a g e %
3545 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3547 % The NormalizeImage() method enhances the contrast of a color image by
3548 % mapping the darkest 2 percent of all pixel to black and the brightest
3549 % 1 percent to white.
3551 % The format of the NormalizeImage method is:
3553 % MagickBooleanType NormalizeImage(Image *image)
3554 % MagickBooleanType NormalizeImageChannel(Image *image,
3555 % const ChannelType channel)
3557 % A description of each parameter follows:
3559 % o image: the image.
3561 % o channel: the channel.
3565 MagickExport MagickBooleanType NormalizeImage(Image *image)
3570 status=NormalizeImageChannel(image,DefaultChannels);
3574 MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
3575 const ChannelType channel)
3581 black_point=(double) image->columns*image->rows*0.02;
3582 white_point=(double) image->columns*image->rows*0.99;
3583 return(ContrastStretchImageChannel(image,channel,black_point,white_point));
3587 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3591 % S i g m o i d a l C o n t r a s t I m a g e %
3595 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3597 % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3598 % sigmoidal contrast algorithm. Increase the contrast of the image using a
3599 % sigmoidal transfer function without saturating highlights or shadows.
3600 % Contrast indicates how much to increase the contrast (0 is none; 3 is
3601 % typical; 20 is pushing it); mid-point indicates where midtones fall in the
3602 % resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3603 % sharpen to MagickTrue to increase the image contrast otherwise the contrast
3606 % The format of the SigmoidalContrastImage method is:
3608 % MagickBooleanType SigmoidalContrastImage(Image *image,
3609 % const MagickBooleanType sharpen,const char *levels)
3610 % MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3611 % const ChannelType channel,const MagickBooleanType sharpen,
3612 % const double contrast,const double midpoint)
3614 % A description of each parameter follows:
3616 % o image: the image.
3618 % o channel: the channel.
3620 % o sharpen: Increase or decrease image contrast.
3622 % o contrast: control the "shoulder" of the contast curve.
3624 % o midpoint: control the "toe" of the contast curve.
3628 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3629 const MagickBooleanType sharpen,const char *levels)
3640 flags=ParseGeometry(levels,&geometry_info);
3641 if ((flags & SigmaValue) == 0)
3642 geometry_info.sigma=1.0*QuantumRange/2.0;
3643 if ((flags & PercentValue) != 0)
3644 geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
3645 status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
3646 geometry_info.rho,geometry_info.sigma);
3650 MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
3651 const ChannelType channel,const MagickBooleanType sharpen,
3652 const double contrast,const double midpoint)
3654 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3676 Allocate and initialize sigmoidal maps.
3678 assert(image != (Image *) NULL);
3679 assert(image->signature == MagickSignature);
3680 if (image->debug != MagickFalse)
3681 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3682 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3683 sizeof(*sigmoidal_map));
3684 if (sigmoidal_map == (MagickRealType *) NULL)
3685 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3687 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
3688 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3689 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3691 for (i=0; i <= (long) MaxMap; i++)
3693 if (sharpen != MagickFalse)
3695 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3696 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
3697 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3698 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3699 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3700 (double) QuantumRange)))))+0.5));
3703 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3704 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3705 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3706 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3707 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3708 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3709 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3710 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3711 (double) QuantumRange*contrast))))))/contrast)));
3713 if (image->storage_class == PseudoClass)
3716 Sigmoidal-contrast enhance colormap.
3718 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3719 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3721 for (i=0; i < (long) image->colors; i++)
3723 if ((channel & RedChannel) != 0)
3724 image->colormap[i].red=ClampToQuantum(sigmoidal_map[
3725 ScaleQuantumToMap(image->colormap[i].red)]);
3726 if ((channel & GreenChannel) != 0)
3727 image->colormap[i].green=ClampToQuantum(sigmoidal_map[
3728 ScaleQuantumToMap(image->colormap[i].green)]);
3729 if ((channel & BlueChannel) != 0)
3730 image->colormap[i].blue=ClampToQuantum(sigmoidal_map[
3731 ScaleQuantumToMap(image->colormap[i].blue)]);
3732 if ((channel & OpacityChannel) != 0)
3733 image->colormap[i].opacity=ClampToQuantum(sigmoidal_map[
3734 ScaleQuantumToMap(image->colormap[i].opacity)]);
3738 Sigmoidal-contrast enhance image.
3742 exception=(&image->exception);
3743 image_view=AcquireCacheView(image);
3744 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3745 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
3747 for (y=0; y < (long) image->rows; y++)
3749 register IndexPacket
3755 register PixelPacket
3758 if (status == MagickFalse)
3760 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3761 if (q == (PixelPacket *) NULL)
3766 indexes=GetCacheViewAuthenticIndexQueue(image_view);
3767 for (x=0; x < (long) image->columns; x++)
3769 if ((channel & RedChannel) != 0)
3770 q->red=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
3771 if ((channel & GreenChannel) != 0)
3772 q->green=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
3773 if ((channel & BlueChannel) != 0)
3774 q->blue=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
3775 if ((channel & OpacityChannel) != 0)
3776 q->opacity=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
3777 if (((channel & IndexChannel) != 0) &&
3778 (image->colorspace == CMYKColorspace))
3779 indexes[x]=(IndexPacket) ClampToQuantum(sigmoidal_map[
3780 ScaleQuantumToMap(indexes[x])]);
3783 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3785 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3790 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3791 #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
3793 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3795 if (proceed == MagickFalse)
3799 image_view=DestroyCacheView(image_view);
3800 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);