--- /dev/null
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% EEEEE N N H H AAA N N CCCC EEEEE %
+% E NN N H H A A NN N C E %
+% EEE N N N HHHHH AAAAA N N N C EEE %
+% E N NN H H A A N NN C E %
+% EEEEE N N H H A A N N CCCC EEEEE %
+% %
+% %
+% MagickCore Image Enhancement Methods %
+% %
+% Software Design %
+% Cristy %
+% July 1992 %
+% %
+% %
+% Copyright 1999-2016 ImageMagick Studio LLC, a non-profit organization %
+% dedicated to making software imaging solutions freely available. %
+% %
+% You may not use this file except in compliance with the License. You may %
+% obtain a copy of the License at %
+% %
+% http://www.imagemagick.org/script/license.php %
+% %
+% Unless required by applicable law or agreed to in writing, software %
+% distributed under the License is distributed on an "AS IS" BASIS, %
+% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
+% See the License for the specific language governing permissions and %
+% limitations under the License. %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%
+%
+*/
+\f
+/*
+ Include declarations.
+*/
+#include "MagickCore/studio.h"
+#include "MagickCore/accelerate.h"
+#include "MagickCore/artifact.h"
+#include "MagickCore/attribute.h"
+#include "MagickCore/cache.h"
+#include "MagickCore/cache-view.h"
+#include "MagickCore/channel.h"
+#include "MagickCore/color.h"
+#include "MagickCore/color-private.h"
+#include "MagickCore/colorspace.h"
+#include "MagickCore/colorspace-private.h"
+#include "MagickCore/composite-private.h"
+#include "MagickCore/enhance.h"
+#include "MagickCore/exception.h"
+#include "MagickCore/exception-private.h"
+#include "MagickCore/fx.h"
+#include "MagickCore/gem.h"
+#include "MagickCore/gem-private.h"
+#include "MagickCore/geometry.h"
+#include "MagickCore/histogram.h"
+#include "MagickCore/image.h"
+#include "MagickCore/image-private.h"
+#include "MagickCore/memory_.h"
+#include "MagickCore/monitor.h"
+#include "MagickCore/monitor-private.h"
+#include "MagickCore/option.h"
+#include "MagickCore/pixel.h"
+#include "MagickCore/pixel-accessor.h"
+#include "MagickCore/quantum.h"
+#include "MagickCore/quantum-private.h"
+#include "MagickCore/resample.h"
+#include "MagickCore/resample-private.h"
+#include "MagickCore/resource_.h"
+#include "MagickCore/statistic.h"
+#include "MagickCore/string_.h"
+#include "MagickCore/string-private.h"
+#include "MagickCore/thread-private.h"
+#include "MagickCore/threshold.h"
+#include "MagickCore/token.h"
+#include "MagickCore/xml-tree.h"
+#include "MagickCore/xml-tree-private.h"
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% A u t o G a m m a I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% AutoGammaImage() extract the 'mean' from the image and adjust the image
+% to try make set its gamma appropriatally.
+%
+% The format of the AutoGammaImage method is:
+%
+% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: The image to auto-level
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType AutoGammaImage(Image *image,
+ ExceptionInfo *exception)
+{
+ double
+ gamma,
+ log_mean,
+ mean,
+ sans;
+
+ MagickStatusType
+ status;
+
+ register ssize_t
+ i;
+
+ log_mean=log(0.5);
+ if (image->channel_mask == DefaultChannels)
+ {
+ /*
+ Apply gamma correction equally across all given channels.
+ */
+ (void) GetImageMean(image,&mean,&sans,exception);
+ gamma=log(mean*QuantumScale)/log_mean;
+ return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
+ }
+ /*
+ Auto-gamma each channel separately.
+ */
+ status=MagickTrue;
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ ChannelType
+ channel_mask;
+
+ PixelChannel channel=GetPixelChannelChannel(image,i);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
+ status=GetImageMean(image,&mean,&sans,exception);
+ gamma=log(mean*QuantumScale)/log_mean;
+ status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ if (status == MagickFalse)
+ break;
+ }
+ return(status != 0 ? MagickTrue : MagickFalse);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% A u t o L e v e l I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% AutoLevelImage() adjusts the levels of a particular image channel by
+% scaling the minimum and maximum values to the full quantum range.
+%
+% The format of the LevelImage method is:
+%
+% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: The image to auto-level
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType AutoLevelImage(Image *image,
+ ExceptionInfo *exception)
+{
+ return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% B r i g h t n e s s C o n t r a s t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% BrightnessContrastImage() changes the brightness and/or contrast of an
+% image. It converts the brightness and contrast parameters into slope and
+% intercept and calls a polynomical function to apply to the image.
+%
+% The format of the BrightnessContrastImage method is:
+%
+% MagickBooleanType BrightnessContrastImage(Image *image,
+% const double brightness,const double contrast,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o brightness: the brightness percent (-100 .. 100).
+%
+% o contrast: the contrast percent (-100 .. 100).
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
+ const double brightness,const double contrast,ExceptionInfo *exception)
+{
+#define BrightnessContastImageTag "BrightnessContast/Image"
+
+ double
+ alpha,
+ coefficients[2],
+ intercept,
+ slope;
+
+ MagickBooleanType
+ status;
+
+ /*
+ Compute slope and intercept.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ alpha=contrast;
+ slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
+ if (slope < 0.0)
+ slope=0.0;
+ intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
+ coefficients[0]=slope;
+ coefficients[1]=intercept;
+ status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% C l u t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ClutImage() replaces each color value in the given image, by using it as an
+% index to lookup a replacement color value in a Color Look UP Table in the
+% form of an image. The values are extracted along a diagonal of the CLUT
+% image so either a horizontal or vertial gradient image can be used.
+%
+% Typically this is used to either re-color a gray-scale image according to a
+% color gradient in the CLUT image, or to perform a freeform histogram
+% (level) adjustment according to the (typically gray-scale) gradient in the
+% CLUT image.
+%
+% When the 'channel' mask includes the matte/alpha transparency channel but
+% one image has no such channel it is assumed that that image is a simple
+% gray-scale image that will effect the alpha channel values, either for
+% gray-scale coloring (with transparent or semi-transparent colors), or
+% a histogram adjustment of existing alpha channel values. If both images
+% have matte channels, direct and normal indexing is applied, which is rarely
+% used.
+%
+% The format of the ClutImage method is:
+%
+% MagickBooleanType ClutImage(Image *image,Image *clut_image,
+% const PixelInterpolateMethod method,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image, which is replaced by indexed CLUT values
+%
+% o clut_image: the color lookup table image for replacement color values.
+%
+% o method: the pixel interpolation method.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
+ const PixelInterpolateMethod method,ExceptionInfo *exception)
+{
+#define ClutImageTag "Clut/Image"
+
+ CacheView
+ *clut_view,
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ PixelInfo
+ *clut_map;
+
+ register ssize_t
+ i;
+
+ ssize_t adjust,
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ assert(clut_image != (Image *) NULL);
+ assert(clut_image->signature == MagickCoreSignature);
+ if( SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
+ return(MagickFalse);
+ if( (IsGrayColorspace(image->colorspace) != MagickFalse) &&
+ (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
+ (void) SetImageColorspace(image,sRGBColorspace,exception);
+ clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
+ if (clut_map == (PixelInfo *) NULL)
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ /*
+ Clut image.
+ */
+ status=MagickTrue;
+ progress=0;
+ adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
+ clut_view=AcquireVirtualCacheView(clut_image,exception);
+ for (i=0; i <= (ssize_t) MaxMap; i++)
+ {
+ GetPixelInfo(clut_image,clut_map+i);
+ (void) InterpolatePixelInfo(clut_image,clut_view,method,
+ (double) i*(clut_image->columns-adjust)/MaxMap,(double) i*
+ (clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
+ }
+ clut_view=DestroyCacheView(clut_view);
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ PixelInfo
+ pixel;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ GetPixelInfo(image,&pixel);
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ PixelTrait
+ traits;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ GetPixelInfoPixel(image,q,&pixel);
+ traits=GetPixelChannelTraits(image,RedPixelChannel);
+ if ((traits & UpdatePixelTrait) != 0)
+ pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
+ pixel.red))].red;
+ traits=GetPixelChannelTraits(image,GreenPixelChannel);
+ if ((traits & UpdatePixelTrait) != 0)
+ pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
+ pixel.green))].green;
+ traits=GetPixelChannelTraits(image,BluePixelChannel);
+ if ((traits & UpdatePixelTrait) != 0)
+ pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
+ pixel.blue))].blue;
+ traits=GetPixelChannelTraits(image,BlackPixelChannel);
+ if ((traits & UpdatePixelTrait) != 0)
+ pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
+ pixel.black))].black;
+ traits=GetPixelChannelTraits(image,AlphaPixelChannel);
+ if ((traits & UpdatePixelTrait) != 0)
+ pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
+ pixel.alpha))].alpha;
+ SetPixelViaPixelInfo(image,&pixel,q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ClutImage)
+#endif
+ proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
+ if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
+ ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
+ (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% C o l o r D e c i s i o n L i s t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ColorDecisionListImage() accepts a lightweight Color Correction Collection
+% (CCC) file which solely contains one or more color corrections and applies
+% the correction to the image. Here is a sample CCC file:
+%
+% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
+% <ColorCorrection id="cc03345">
+% <SOPNode>
+% <Slope> 0.9 1.2 0.5 </Slope>
+% <Offset> 0.4 -0.5 0.6 </Offset>
+% <Power> 1.0 0.8 1.5 </Power>
+% </SOPNode>
+% <SATNode>
+% <Saturation> 0.85 </Saturation>
+% </SATNode>
+% </ColorCorrection>
+% </ColorCorrectionCollection>
+%
+% which includes the slop, offset, and power for each of the RGB channels
+% as well as the saturation.
+%
+% The format of the ColorDecisionListImage method is:
+%
+% MagickBooleanType ColorDecisionListImage(Image *image,
+% const char *color_correction_collection,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o color_correction_collection: the color correction collection in XML.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
+ const char *color_correction_collection,ExceptionInfo *exception)
+{
+#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
+
+ typedef struct _Correction
+ {
+ double
+ slope,
+ offset,
+ power;
+ } Correction;
+
+ typedef struct _ColorCorrection
+ {
+ Correction
+ red,
+ green,
+ blue;
+
+ double
+ saturation;
+ } ColorCorrection;
+
+ CacheView
+ *image_view;
+
+ char
+ token[MagickPathExtent];
+
+ ColorCorrection
+ color_correction;
+
+ const char
+ *content,
+ *p;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ PixelInfo
+ *cdl_map;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ XMLTreeInfo
+ *cc,
+ *ccc,
+ *sat,
+ *sop;
+
+ /*
+ Allocate and initialize cdl maps.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (color_correction_collection == (const char *) NULL)
+ return(MagickFalse);
+ ccc=NewXMLTree((const char *) color_correction_collection,exception);
+ if (ccc == (XMLTreeInfo *) NULL)
+ return(MagickFalse);
+ cc=GetXMLTreeChild(ccc,"ColorCorrection");
+ if (cc == (XMLTreeInfo *) NULL)
+ {
+ ccc=DestroyXMLTree(ccc);
+ return(MagickFalse);
+ }
+ color_correction.red.slope=1.0;
+ color_correction.red.offset=0.0;
+ color_correction.red.power=1.0;
+ color_correction.green.slope=1.0;
+ color_correction.green.offset=0.0;
+ color_correction.green.power=1.0;
+ color_correction.blue.slope=1.0;
+ color_correction.blue.offset=0.0;
+ color_correction.blue.power=1.0;
+ color_correction.saturation=0.0;
+ sop=GetXMLTreeChild(cc,"SOPNode");
+ if (sop != (XMLTreeInfo *) NULL)
+ {
+ XMLTreeInfo
+ *offset,
+ *power,
+ *slope;
+
+ slope=GetXMLTreeChild(sop,"Slope");
+ if (slope != (XMLTreeInfo *) NULL)
+ {
+ content=GetXMLTreeContent(slope);
+ p=(const char *) content;
+ for (i=0; (*p != '\0') && (i < 3); i++)
+ {
+ GetMagickToken(p,&p,token);
+ if (*token == ',')
+ GetMagickToken(p,&p,token);
+ switch (i)
+ {
+ case 0:
+ {
+ color_correction.red.slope=StringToDouble(token,(char **) NULL);
+ break;
+ }
+ case 1:
+ {
+ color_correction.green.slope=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ case 2:
+ {
+ color_correction.blue.slope=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ }
+ }
+ }
+ offset=GetXMLTreeChild(sop,"Offset");
+ if (offset != (XMLTreeInfo *) NULL)
+ {
+ content=GetXMLTreeContent(offset);
+ p=(const char *) content;
+ for (i=0; (*p != '\0') && (i < 3); i++)
+ {
+ GetMagickToken(p,&p,token);
+ if (*token == ',')
+ GetMagickToken(p,&p,token);
+ switch (i)
+ {
+ case 0:
+ {
+ color_correction.red.offset=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ case 1:
+ {
+ color_correction.green.offset=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ case 2:
+ {
+ color_correction.blue.offset=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ }
+ }
+ }
+ power=GetXMLTreeChild(sop,"Power");
+ if (power != (XMLTreeInfo *) NULL)
+ {
+ content=GetXMLTreeContent(power);
+ p=(const char *) content;
+ for (i=0; (*p != '\0') && (i < 3); i++)
+ {
+ GetMagickToken(p,&p,token);
+ if (*token == ',')
+ GetMagickToken(p,&p,token);
+ switch (i)
+ {
+ case 0:
+ {
+ color_correction.red.power=StringToDouble(token,(char **) NULL);
+ break;
+ }
+ case 1:
+ {
+ color_correction.green.power=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ case 2:
+ {
+ color_correction.blue.power=StringToDouble(token,
+ (char **) NULL);
+ break;
+ }
+ }
+ }
+ }
+ }
+ sat=GetXMLTreeChild(cc,"SATNode");
+ if (sat != (XMLTreeInfo *) NULL)
+ {
+ XMLTreeInfo
+ *saturation;
+
+ saturation=GetXMLTreeChild(sat,"Saturation");
+ if (saturation != (XMLTreeInfo *) NULL)
+ {
+ content=GetXMLTreeContent(saturation);
+ p=(const char *) content;
+ GetMagickToken(p,&p,token);
+ color_correction.saturation=StringToDouble(token,(char **) NULL);
+ }
+ }
+ ccc=DestroyXMLTree(ccc);
+ if (image->debug != MagickFalse)
+ {
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " Color Correction Collection:");
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.red.slope: %g",color_correction.red.slope);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.red.offset: %g",color_correction.red.offset);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.red.power: %g",color_correction.red.power);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.green.slope: %g",color_correction.green.slope);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.green.offset: %g",color_correction.green.offset);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.green.power: %g",color_correction.green.power);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.blue.slope: %g",color_correction.blue.slope);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.blue.offset: %g",color_correction.blue.offset);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.blue.power: %g",color_correction.blue.power);
+ (void) LogMagickEvent(TransformEvent,GetMagickModule(),
+ " color_correction.saturation: %g",color_correction.saturation);
+ }
+ cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
+ if (cdl_map == (PixelInfo *) NULL)
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ for (i=0; i <= (ssize_t) MaxMap; i++)
+ {
+ cdl_map[i].red=(double) ScaleMapToQuantum((double)
+ (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
+ color_correction.red.offset,color_correction.red.power))));
+ cdl_map[i].green=(double) ScaleMapToQuantum((double)
+ (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
+ color_correction.green.offset,color_correction.green.power))));
+ cdl_map[i].blue=(double) ScaleMapToQuantum((double)
+ (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
+ color_correction.blue.offset,color_correction.blue.power))));
+ }
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ /*
+ Apply transfer function to colormap.
+ */
+ double
+ luma;
+
+ luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
+ 0.07217f*image->colormap[i].blue;
+ image->colormap[i].red=luma+color_correction.saturation*cdl_map[
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
+ image->colormap[i].green=luma+color_correction.saturation*cdl_map[
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
+ image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
+ }
+ /*
+ Apply transfer function to image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ double
+ luma;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
+ 0.07217f*GetPixelBlue(image,q);
+ SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
+ (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
+ SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
+ (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
+ SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
+ (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
+#endif
+ proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
+ progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% C o n t r a s t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ContrastImage() enhances the intensity differences between the lighter and
+% darker elements of the image. Set sharpen to a MagickTrue to increase the
+% image contrast otherwise the contrast is reduced.
+%
+% The format of the ContrastImage method is:
+%
+% MagickBooleanType ContrastImage(Image *image,
+% const MagickBooleanType sharpen,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o sharpen: Increase or decrease image contrast.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+static void Contrast(const int sign,double *red,double *green,double *blue)
+{
+ double
+ brightness,
+ hue,
+ saturation;
+
+ /*
+ Enhance contrast: dark color become darker, light color become lighter.
+ */
+ assert(red != (double *) NULL);
+ assert(green != (double *) NULL);
+ assert(blue != (double *) NULL);
+ hue=0.0;
+ saturation=0.0;
+ brightness=0.0;
+ ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
+ brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
+ brightness);
+ if (brightness > 1.0)
+ brightness=1.0;
+ else
+ if (brightness < 0.0)
+ brightness=0.0;
+ ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
+}
+
+MagickExport MagickBooleanType ContrastImage(Image *image,
+ const MagickBooleanType sharpen,ExceptionInfo *exception)
+{
+#define ContrastImageTag "Contrast/Image"
+
+ CacheView
+ *image_view;
+
+ int
+ sign;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
+ return(MagickTrue);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ sign=sharpen != MagickFalse ? 1 : -1;
+ if (image->storage_class == PseudoClass)
+ {
+ /*
+ Contrast enhance colormap.
+ */
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ double
+ blue,
+ green,
+ red;
+
+ red=(double) image->colormap[i].red;
+ green=(double) image->colormap[i].green;
+ blue=(double) image->colormap[i].blue;
+ Contrast(sign,&red,&green,&blue);
+ image->colormap[i].red=(MagickRealType) red;
+ image->colormap[i].green=(MagickRealType) green;
+ image->colormap[i].blue=(MagickRealType) blue;
+ }
+ }
+ /*
+ Contrast enhance image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ double
+ blue,
+ green,
+ red;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ red=(double) GetPixelRed(image,q);
+ green=(double) GetPixelGreen(image,q);
+ blue=(double) GetPixelBlue(image,q);
+ Contrast(sign,&red,&green,&blue);
+ SetPixelRed(image,ClampToQuantum(red),q);
+ SetPixelGreen(image,ClampToQuantum(green),q);
+ SetPixelBlue(image,ClampToQuantum(blue),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ContrastImage)
+#endif
+ proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% C o n t r a s t S t r e t c h I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ContrastStretchImage() is a simple image enhancement technique that attempts
+% to improve the contrast in an image by 'stretching' the range of intensity
+% values it contains to span a desired range of values. It differs from the
+% more sophisticated histogram equalization in that it can only apply a
+% linear scaling function to the image pixel values. As a result the
+% 'enhancement' is less harsh.
+%
+% The format of the ContrastStretchImage method is:
+%
+% MagickBooleanType ContrastStretchImage(Image *image,
+% const char *levels,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o black_point: the black point.
+%
+% o white_point: the white point.
+%
+% o levels: Specify the levels where the black and white points have the
+% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType ContrastStretchImage(Image *image,
+ const double black_point,const double white_point,ExceptionInfo *exception)
+{
+#define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
+#define ContrastStretchImageTag "ContrastStretch/Image"
+
+ CacheView
+ *image_view;
+
+ double
+ *black,
+ *histogram,
+ *stretch_map,
+ *white;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Allocate histogram and stretch map.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (SetImageGray(image,exception) != MagickFalse)
+ (void) SetImageColorspace(image,GRAYColorspace,exception);
+ black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
+ white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
+ histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
+ sizeof(*histogram));
+ stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
+ GetPixelChannels(image)*sizeof(*stretch_map));
+ if ((black == (double *) NULL) || (white == (double *) NULL) ||
+ (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
+ {
+ if (stretch_map != (double *) NULL)
+ stretch_map=(double *) RelinquishMagickMemory(stretch_map);
+ if (histogram != (double *) NULL)
+ histogram=(double *) RelinquishMagickMemory(histogram);
+ if (white != (double *) NULL)
+ white=(double *) RelinquishMagickMemory(white);
+ if (black != (double *) NULL)
+ black=(double *) RelinquishMagickMemory(black);
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ }
+ /*
+ Form histogram.
+ */
+ status=MagickTrue;
+ (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
+ sizeof(*histogram));
+ image_view=AcquireVirtualCacheView(image,exception);
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
+ if (p == (const Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ double
+ pixel;
+
+ pixel=GetPixelIntensity(image,p);
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ if (image->channel_mask != DefaultChannels)
+ pixel=(double) p[i];
+ histogram[GetPixelChannels(image)*ScaleQuantumToMap(
+ ClampToQuantum(pixel))+i]++;
+ }
+ p+=GetPixelChannels(image);
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ /*
+ Find the histogram boundaries by locating the black/white levels.
+ */
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ intensity;
+
+ register ssize_t
+ j;
+
+ black[i]=0.0;
+ white[i]=MaxRange(QuantumRange);
+ intensity=0.0;
+ for (j=0; j <= (ssize_t) MaxMap; j++)
+ {
+ intensity+=histogram[GetPixelChannels(image)*j+i];
+ if (intensity > black_point)
+ break;
+ }
+ black[i]=(double) j;
+ intensity=0.0;
+ for (j=(ssize_t) MaxMap; j != 0; j--)
+ {
+ intensity+=histogram[GetPixelChannels(image)*j+i];
+ if (intensity > ((double) image->columns*image->rows-white_point))
+ break;
+ }
+ white[i]=(double) j;
+ }
+ histogram=(double *) RelinquishMagickMemory(histogram);
+ /*
+ Stretch the histogram to create the stretched image mapping.
+ */
+ (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
+ sizeof(*stretch_map));
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ register ssize_t
+ j;
+
+ for (j=0; j <= (ssize_t) MaxMap; j++)
+ {
+ double
+ gamma;
+
+ gamma=PerceptibleReciprocal(white[i]-black[i]);
+ if (j < (ssize_t) black[i])
+ stretch_map[GetPixelChannels(image)*j+i]=0.0;
+ else
+ if (j > (ssize_t) white[i])
+ stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
+ else
+ stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
+ (double) (MaxMap*gamma*(j-black[i])));
+ }
+ }
+ if (image->storage_class == PseudoClass)
+ {
+ register ssize_t
+ j;
+
+ /*
+ Stretch-contrast colormap.
+ */
+ for (j=0; j < (ssize_t) image->colors; j++)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ {
+ i=GetPixelChannelOffset(image,RedPixelChannel);
+ image->colormap[j].red=stretch_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i];
+ }
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ {
+ i=GetPixelChannelOffset(image,GreenPixelChannel);
+ image->colormap[j].green=stretch_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i];
+ }
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ {
+ i=GetPixelChannelOffset(image,BluePixelChannel);
+ image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i];
+ }
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ {
+ i=GetPixelChannelOffset(image,AlphaPixelChannel);
+ image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i];
+ }
+ }
+ }
+ /*
+ Stretch-contrast image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(q[j])+j]);
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ContrastStretchImage)
+#endif
+ proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
+ image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ stretch_map=(double *) RelinquishMagickMemory(stretch_map);
+ white=(double *) RelinquishMagickMemory(white);
+ black=(double *) RelinquishMagickMemory(black);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% E n h a n c e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% EnhanceImage() applies a digital filter that improves the quality of a
+% noisy image.
+%
+% The format of the EnhanceImage method is:
+%
+% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
+{
+#define EnhanceImageTag "Enhance/Image"
+#define EnhancePixel(weight) \
+ mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
+ distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
+ distance_squared=(4.0+mean)*distance*distance; \
+ mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
+ distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
+ distance_squared+=(7.0-mean)*distance*distance; \
+ mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
+ distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
+ distance_squared+=(5.0-mean)*distance*distance; \
+ mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
+ distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
+ distance_squared+=(5.0-mean)*distance*distance; \
+ mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
+ distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
+ distance_squared+=(5.0-mean)*distance*distance; \
+ if (distance_squared < 0.069) \
+ { \
+ aggregate.red+=(weight)*GetPixelRed(image,r); \
+ aggregate.green+=(weight)*GetPixelGreen(image,r); \
+ aggregate.blue+=(weight)*GetPixelBlue(image,r); \
+ aggregate.black+=(weight)*GetPixelBlack(image,r); \
+ aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \
+ total_weight+=(weight); \
+ } \
+ r+=GetPixelChannels(image);
+
+ CacheView
+ *enhance_view,
+ *image_view;
+
+ Image
+ *enhance_image;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ ssize_t
+ y;
+
+ /*
+ Initialize enhanced image attributes.
+ */
+ assert(image != (const Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ assert(exception != (ExceptionInfo *) NULL);
+ assert(exception->signature == MagickCoreSignature);
+ enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
+ exception);
+ if (enhance_image == (Image *) NULL)
+ return((Image *) NULL);
+ if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
+ {
+ enhance_image=DestroyImage(enhance_image);
+ return((Image *) NULL);
+ }
+ /*
+ Enhance image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireVirtualCacheView(image,exception);
+ enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,enhance_image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ PixelInfo
+ pixel;
+
+ register const Quantum
+ *magick_restrict p;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ ssize_t
+ center;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
+ q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
+ exception);
+ if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+ {
+ status=MagickFalse;
+ continue;
+ }
+ center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
+ GetPixelInfo(image,&pixel);
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ double
+ distance,
+ distance_squared,
+ mean,
+ total_weight;
+
+ PixelInfo
+ aggregate;
+
+ register const Quantum
+ *magick_restrict r;
+
+ if (GetPixelReadMask(image,p) == 0)
+ {
+ SetPixelBackgoundColor(enhance_image,q);
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(enhance_image);
+ continue;
+ }
+ GetPixelInfo(image,&aggregate);
+ total_weight=0.0;
+ GetPixelInfoPixel(image,p+center,&pixel);
+ r=p;
+ EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
+ EnhancePixel(8.0); EnhancePixel(5.0);
+ r=p+GetPixelChannels(image)*(image->columns+4);
+ EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
+ EnhancePixel(20.0); EnhancePixel(8.0);
+ r=p+2*GetPixelChannels(image)*(image->columns+4);
+ EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
+ EnhancePixel(40.0); EnhancePixel(10.0);
+ r=p+3*GetPixelChannels(image)*(image->columns+4);
+ EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
+ EnhancePixel(20.0); EnhancePixel(8.0);
+ r=p+4*GetPixelChannels(image)*(image->columns+4);
+ EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
+ EnhancePixel(8.0); EnhancePixel(5.0);
+ pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
+ pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
+ pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
+ pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
+ pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
+ SetPixelViaPixelInfo(image,&pixel,q);
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(enhance_image);
+ }
+ if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_EnhanceImage)
+#endif
+ proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ enhance_view=DestroyCacheView(enhance_view);
+ image_view=DestroyCacheView(image_view);
+ if (status == MagickFalse)
+ enhance_image=DestroyImage(enhance_image);
+ return(enhance_image);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% E q u a l i z e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% EqualizeImage() applies a histogram equalization to the image.
+%
+% The format of the EqualizeImage method is:
+%
+% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType EqualizeImage(Image *image,
+ ExceptionInfo *exception)
+{
+#define EqualizeImageTag "Equalize/Image"
+
+ CacheView
+ *image_view;
+
+ double
+ black[CompositePixelChannel+1],
+ *equalize_map,
+ *histogram,
+ *map,
+ white[CompositePixelChannel+1];
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Allocate and initialize histogram arrays.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (AccelerateEqualizeImage(image,exception) != MagickFalse)
+ return(MagickTrue);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
+ GetPixelChannels(image)*sizeof(*equalize_map));
+ histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
+ sizeof(*histogram));
+ map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
+ sizeof(*map));
+ if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
+ (map == (double *) NULL))
+ {
+ if (map != (double *) NULL)
+ map=(double *) RelinquishMagickMemory(map);
+ if (histogram != (double *) NULL)
+ histogram=(double *) RelinquishMagickMemory(histogram);
+ if (equalize_map != (double *) NULL)
+ equalize_map=(double *) RelinquishMagickMemory(equalize_map);
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ }
+ /*
+ Form histogram.
+ */
+ status=MagickTrue;
+ (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
+ sizeof(*histogram));
+ image_view=AcquireVirtualCacheView(image,exception);
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
+ if (p == (const Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ double intensity=GetPixelIntensity(image,p);
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ histogram[GetPixelChannels(image)*ScaleQuantumToMap(intensity)+i]++;
+ p+=GetPixelChannels(image);
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ /*
+ Integrate the histogram to get the equalization map.
+ */
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ intensity;
+
+ register ssize_t
+ j;
+
+ intensity=0.0;
+ for (j=0; j <= (ssize_t) MaxMap; j++)
+ {
+ intensity+=histogram[GetPixelChannels(image)*j+i];
+ map[GetPixelChannels(image)*j+i]=intensity;
+ }
+ }
+ (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
+ sizeof(*equalize_map));
+ (void) ResetMagickMemory(black,0,sizeof(*black));
+ (void) ResetMagickMemory(white,0,sizeof(*white));
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ register ssize_t
+ j;
+
+ black[i]=map[i];
+ white[i]=map[GetPixelChannels(image)*MaxMap+i];
+ if (black[i] != white[i])
+ for (j=0; j <= (ssize_t) MaxMap; j++)
+ equalize_map[GetPixelChannels(image)*j+i]=(double)
+ ScaleMapToQuantum((double) ((MaxMap*(map[
+ GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
+ }
+ histogram=(double *) RelinquishMagickMemory(histogram);
+ map=(double *) RelinquishMagickMemory(map);
+ if (image->storage_class == PseudoClass)
+ {
+ register ssize_t
+ j;
+
+ /*
+ Equalize colormap.
+ */
+ for (j=0; j < (ssize_t) image->colors; j++)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
+ if (black[channel] != white[channel])
+ image->colormap[j].red=equalize_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+
+ channel];
+ }
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,
+ GreenPixelChannel);
+ if (black[channel] != white[channel])
+ image->colormap[j].green=equalize_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+
+ channel];
+
+ }
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
+ if (black[channel] != white[channel])
+ image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+
+ channel];
+ }
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,
+ AlphaPixelChannel);
+ if (black[channel] != white[channel])
+ image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+
+ channel];
+ }
+ }
+ }
+ /*
+ Equalize image.
+ */
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
+ continue;
+ q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
+ ScaleQuantumToMap(q[j])+j]);
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_EqualizeImage)
+#endif
+ proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ equalize_map=(double *) RelinquishMagickMemory(equalize_map);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% G a m m a I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% GammaImage() gamma-corrects a particular image channel. The same
+% image viewed on different devices will have perceptual differences in the
+% way the image's intensities are represented on the screen. Specify
+% individual gamma levels for the red, green, and blue channels, or adjust
+% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
+%
+% You can also reduce the influence of a particular channel with a gamma
+% value of 0.
+%
+% The format of the GammaImage method is:
+%
+% MagickBooleanType GammaImage(Image *image,const double gamma,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
+%
+% o gamma: the image gamma.
+%
+*/
+
+static inline double gamma_pow(const double value,const double gamma)
+{
+ return(value < 0.0 ? value : pow(value,gamma));
+}
+
+MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
+ ExceptionInfo *exception)
+{
+#define GammaCorrectImageTag "GammaCorrect/Image"
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ Quantum
+ *gamma_map;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Allocate and initialize gamma maps.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (gamma == 1.0)
+ return(MagickTrue);
+ gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
+ if (gamma_map == (Quantum *) NULL)
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
+ if (gamma != 0.0)
+ for (i=0; i <= (ssize_t) MaxMap; i++)
+ gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
+ MaxMap,1.0/gamma)));
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ /*
+ Gamma-correct colormap.
+ */
+#if !defined(MAGICKCORE_HDRI_SUPPORT)
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
+ ClampToQuantum(image->colormap[i].red))];
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
+ ClampToQuantum(image->colormap[i].green))];
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
+ ClampToQuantum(image->colormap[i].blue))];
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
+ ClampToQuantum(image->colormap[i].alpha))];
+#else
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
+ image->colormap[i].red,1.0/gamma);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
+ image->colormap[i].green,1.0/gamma);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
+ image->colormap[i].blue,1.0/gamma);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
+ image->colormap[i].alpha,1.0/gamma);
+#endif
+ }
+ /*
+ Gamma-correct image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+#if !defined(MAGICKCORE_HDRI_SUPPORT)
+ q[j]=gamma_map[ScaleQuantumToMap(q[j])];
+#else
+ q[j]=QuantumRange*gamma_pow(QuantumScale*q[j],1.0/gamma);
+#endif
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_GammaImage)
+#endif
+ proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
+ image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
+ if (image->gamma != 0.0)
+ image->gamma*=gamma;
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% G r a y s c a l e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% GrayscaleImage() converts the image to grayscale.
+%
+% The format of the GrayscaleImage method is:
+%
+% MagickBooleanType GrayscaleImage(Image *image,
+% const PixelIntensityMethod method ,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o method: the pixel intensity method.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType GrayscaleImage(Image *image,
+ const PixelIntensityMethod method,ExceptionInfo *exception)
+{
+#define GrayscaleImageTag "Grayscale/Image"
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ ssize_t
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
+ return(MagickTrue);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (image->storage_class == PseudoClass)
+ {
+ if (SyncImage(image,exception) == MagickFalse)
+ return(MagickFalse);
+ if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
+ return(MagickFalse);
+ }
+ /*
+ Grayscale image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ MagickRealType
+ blue,
+ green,
+ red,
+ intensity;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ red=(MagickRealType) GetPixelRed(image,q);
+ green=(MagickRealType) GetPixelGreen(image,q);
+ blue=(MagickRealType) GetPixelBlue(image,q);
+ intensity=0.0;
+ switch (method)
+ {
+ case AveragePixelIntensityMethod:
+ {
+ intensity=(red+green+blue)/3.0;
+ break;
+ }
+ case BrightnessPixelIntensityMethod:
+ {
+ intensity=MagickMax(MagickMax(red,green),blue);
+ break;
+ }
+ case LightnessPixelIntensityMethod:
+ {
+ intensity=(MagickMin(MagickMin(red,green),blue)+
+ MagickMax(MagickMax(red,green),blue))/2.0;
+ break;
+ }
+ case MSPixelIntensityMethod:
+ {
+ intensity=(MagickRealType) (((double) red*red+green*green+
+ blue*blue)/3.0);
+ break;
+ }
+ case Rec601LumaPixelIntensityMethod:
+ {
+ if (image->colorspace == RGBColorspace)
+ {
+ red=EncodePixelGamma(red);
+ green=EncodePixelGamma(green);
+ blue=EncodePixelGamma(blue);
+ }
+ intensity=0.298839*red+0.586811*green+0.114350*blue;
+ break;
+ }
+ case Rec601LuminancePixelIntensityMethod:
+ {
+ if (image->colorspace == sRGBColorspace)
+ {
+ red=DecodePixelGamma(red);
+ green=DecodePixelGamma(green);
+ blue=DecodePixelGamma(blue);
+ }
+ intensity=0.298839*red+0.586811*green+0.114350*blue;
+ break;
+ }
+ case Rec709LumaPixelIntensityMethod:
+ default:
+ {
+ if (image->colorspace == RGBColorspace)
+ {
+ red=EncodePixelGamma(red);
+ green=EncodePixelGamma(green);
+ blue=EncodePixelGamma(blue);
+ }
+ intensity=0.212656*red+0.715158*green+0.072186*blue;
+ break;
+ }
+ case Rec709LuminancePixelIntensityMethod:
+ {
+ if (image->colorspace == sRGBColorspace)
+ {
+ red=DecodePixelGamma(red);
+ green=DecodePixelGamma(green);
+ blue=DecodePixelGamma(blue);
+ }
+ intensity=0.212656*red+0.715158*green+0.072186*blue;
+ break;
+ }
+ case RMSPixelIntensityMethod:
+ {
+ intensity=(MagickRealType) (sqrt((double) red*red+green*green+
+ blue*blue)/sqrt(3.0));
+ break;
+ }
+ }
+ SetPixelGray(image,ClampToQuantum(intensity),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_GrayscaleImage)
+#endif
+ proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
+ image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ image->intensity=method;
+ image->type=GrayscaleType;
+ return(SetImageColorspace(image,GRAYColorspace,exception));
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% H a l d C l u t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% HaldClutImage() applies a Hald color lookup table to the image. A Hald
+% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
+% Create it with the HALD coder. You can apply any color transformation to
+% the Hald image and then use this method to apply the transform to the
+% image.
+%
+% The format of the HaldClutImage method is:
+%
+% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image, which is replaced by indexed CLUT values
+%
+% o hald_image: the color lookup table image for replacement color values.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType HaldClutImage(Image *image,
+ const Image *hald_image,ExceptionInfo *exception)
+{
+#define HaldClutImageTag "Clut/Image"
+
+ typedef struct _HaldInfo
+ {
+ double
+ x,
+ y,
+ z;
+ } HaldInfo;
+
+ CacheView
+ *hald_view,
+ *image_view;
+
+ double
+ width;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ PixelInfo
+ zero;
+
+ size_t
+ cube_size,
+ length,
+ level;
+
+ ssize_t
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ assert(hald_image != (Image *) NULL);
+ assert(hald_image->signature == MagickCoreSignature);
+ if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
+ return(MagickFalse);
+ if (image->alpha_trait == UndefinedPixelTrait)
+ (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
+ /*
+ Hald clut image.
+ */
+ status=MagickTrue;
+ progress=0;
+ length=(size_t) MagickMin((MagickRealType) hald_image->columns,
+ (MagickRealType) hald_image->rows);
+ for (level=2; (level*level*level) < length; level++) ;
+ level*=level;
+ cube_size=level*level;
+ width=(double) hald_image->columns;
+ GetPixelInfo(hald_image,&zero);
+ hald_view=AcquireVirtualCacheView(hald_image,exception);
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ double
+ offset;
+
+ HaldInfo
+ point;
+
+ PixelInfo
+ pixel,
+ pixel1,
+ pixel2,
+ pixel3,
+ pixel4;
+
+ point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
+ point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
+ point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
+ offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
+ point.x-=floor(point.x);
+ point.y-=floor(point.y);
+ point.z-=floor(point.z);
+ pixel1=zero;
+ (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
+ fmod(offset,width),floor(offset/width),&pixel1,exception);
+ pixel2=zero;
+ (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
+ fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
+ pixel3=zero;
+ CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
+ point.y,&pixel3);
+ offset+=cube_size;
+ (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
+ fmod(offset,width),floor(offset/width),&pixel1,exception);
+ (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
+ fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
+ pixel4=zero;
+ CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
+ point.y,&pixel4);
+ pixel=zero;
+ CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
+ point.z,&pixel);
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ SetPixelRed(image,ClampToQuantum(pixel.red),q);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ SetPixelGreen(image,ClampToQuantum(pixel.green),q);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
+ if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->colorspace == CMYKColorspace))
+ SetPixelBlack(image,ClampToQuantum(pixel.black),q);
+ if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->alpha_trait != UndefinedPixelTrait))
+ SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_HaldClutImage)
+#endif
+ proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ hald_view=DestroyCacheView(hald_view);
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% L e v e l I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% LevelImage() adjusts the levels of a particular image channel by
+% scaling the colors falling between specified white and black points to
+% the full available quantum range.
+%
+% The parameters provided represent the black, and white points. The black
+% point specifies the darkest color in the image. Colors darker than the
+% black point are set to zero. White point specifies the lightest color in
+% the image. Colors brighter than the white point are set to the maximum
+% quantum value.
+%
+% If a '!' flag is given, map black and white colors to the given levels
+% rather than mapping those levels to black and white. See
+% LevelizeImage() below.
+%
+% Gamma specifies a gamma correction to apply to the image.
+%
+% The format of the LevelImage method is:
+%
+% MagickBooleanType LevelImage(Image *image,const double black_point,
+% const double white_point,const double gamma,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o black_point: The level to map zero (black) to.
+%
+% o white_point: The level to map QuantumRange (white) to.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+static inline double LevelPixel(const double black_point,
+ const double white_point,const double gamma,const double pixel)
+{
+ double
+ level_pixel,
+ scale;
+
+ scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
+ level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
+ 1.0/gamma);
+ return(level_pixel);
+}
+
+MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
+ const double white_point,const double gamma,ExceptionInfo *exception)
+{
+#define LevelImageTag "Level/Image"
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Allocate and initialize levels map.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ /*
+ Level colormap.
+ */
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
+ white_point,gamma,image->colormap[i].red));
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
+ white_point,gamma,image->colormap[i].green));
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
+ white_point,gamma,image->colormap[i].blue));
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
+ white_point,gamma,image->colormap[i].alpha));
+ }
+ /*
+ Level image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
+ (double) q[j]));
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_LevelImage)
+#endif
+ proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ (void) ClampImage(image,exception);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% L e v e l i z e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% LevelizeImage() applies the reversed LevelImage() operation to just
+% the specific channels specified. It compresses the full range of color
+% values, so that they lie between the given black and white points. Gamma is
+% applied before the values are mapped.
+%
+% LevelizeImage() can be called with by using a +level command line
+% API option, or using a '!' on a -level or LevelImage() geometry string.
+%
+% It can be used to de-contrast a greyscale image to the exact levels
+% specified. Or by using specific levels for each channel of an image you
+% can convert a gray-scale image to any linear color gradient, according to
+% those levels.
+%
+% The format of the LevelizeImage method is:
+%
+% MagickBooleanType LevelizeImage(Image *image,const double black_point,
+% const double white_point,const double gamma,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o black_point: The level to map zero (black) to.
+%
+% o white_point: The level to map QuantumRange (white) to.
+%
+% o gamma: adjust gamma by this factor before mapping values.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType LevelizeImage(Image *image,
+ const double black_point,const double white_point,const double gamma,
+ ExceptionInfo *exception)
+{
+#define LevelizeImageTag "Levelize/Image"
+#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
+ (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Allocate and initialize levels map.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ /*
+ Level colormap.
+ */
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=(double) LevelizeValue(
+ image->colormap[i].green);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=(double) LevelizeValue(
+ image->colormap[i].alpha);
+ }
+ /*
+ Level image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ q[j]=LevelizeValue(q[j]);
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_LevelizeImage)
+#endif
+ proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% L e v e l I m a g e C o l o r s %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% LevelImageColors() maps the given color to "black" and "white" values,
+% linearly spreading out the colors, and level values on a channel by channel
+% bases, as per LevelImage(). The given colors allows you to specify
+% different level ranges for each of the color channels separately.
+%
+% If the boolean 'invert' is set true the image values will modifyed in the
+% reverse direction. That is any existing "black" and "white" colors in the
+% image will become the color values given, with all other values compressed
+% appropriatally. This effectivally maps a greyscale gradient into the given
+% color gradient.
+%
+% The format of the LevelImageColors method is:
+%
+% MagickBooleanType LevelImageColors(Image *image,
+% const PixelInfo *black_color,const PixelInfo *white_color,
+% const MagickBooleanType invert,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o black_color: The color to map black to/from
+%
+% o white_point: The color to map white to/from
+%
+% o invert: if true map the colors (levelize), rather than from (level)
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType LevelImageColors(Image *image,
+ const PixelInfo *black_color,const PixelInfo *white_color,
+ const MagickBooleanType invert,ExceptionInfo *exception)
+{
+ ChannelType
+ channel_mask;
+
+ MagickStatusType
+ status;
+
+ /*
+ Allocate and initialize levels map.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
+ ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
+ (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
+ (void) SetImageColorspace(image,sRGBColorspace,exception);
+ status=MagickTrue;
+ if (invert == MagickFalse)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,RedChannel);
+ status&=LevelImage(image,black_color->red,white_color->red,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,GreenChannel);
+ status&=LevelImage(image,black_color->green,white_color->green,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,BlueChannel);
+ status&=LevelImage(image,black_color->blue,white_color->blue,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->colorspace == CMYKColorspace))
+ {
+ channel_mask=SetImageChannelMask(image,BlackChannel);
+ status&=LevelImage(image,black_color->black,white_color->black,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->alpha_trait != UndefinedPixelTrait))
+ {
+ channel_mask=SetImageChannelMask(image,AlphaChannel);
+ status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ }
+ else
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,RedChannel);
+ status&=LevelizeImage(image,black_color->red,white_color->red,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,GreenChannel);
+ status&=LevelizeImage(image,black_color->green,white_color->green,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ {
+ channel_mask=SetImageChannelMask(image,BlueChannel);
+ status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->colorspace == CMYKColorspace))
+ {
+ channel_mask=SetImageChannelMask(image,BlackChannel);
+ status&=LevelizeImage(image,black_color->black,white_color->black,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
+ (image->alpha_trait != UndefinedPixelTrait))
+ {
+ channel_mask=SetImageChannelMask(image,AlphaChannel);
+ status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
+ exception);
+ (void) SetImageChannelMask(image,channel_mask);
+ }
+ }
+ return(status != 0 ? MagickTrue : MagickFalse);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% L i n e a r S t r e t c h I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% LinearStretchImage() discards any pixels below the black point and above
+% the white point and levels the remaining pixels.
+%
+% The format of the LinearStretchImage method is:
+%
+% MagickBooleanType LinearStretchImage(Image *image,
+% const double black_point,const double white_point,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o black_point: the black point.
+%
+% o white_point: the white point.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType LinearStretchImage(Image *image,
+ const double black_point,const double white_point,ExceptionInfo *exception)
+{
+#define LinearStretchImageTag "LinearStretch/Image"
+
+ CacheView
+ *image_view;
+
+ double
+ *histogram,
+ intensity;
+
+ MagickBooleanType
+ status;
+
+ ssize_t
+ black,
+ white,
+ y;
+
+ /*
+ Allocate histogram and linear map.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
+ if (histogram == (double *) NULL)
+ ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
+ image->filename);
+ /*
+ Form histogram.
+ */
+ (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
+ image_view=AcquireVirtualCacheView(image,exception);
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p;
+
+ register ssize_t
+ x;
+
+ p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
+ if (p == (const Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ intensity=GetPixelIntensity(image,p);
+ histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
+ p+=GetPixelChannels(image);
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ /*
+ Find the histogram boundaries by locating the black and white point levels.
+ */
+ intensity=0.0;
+ for (black=0; black < (ssize_t) MaxMap; black++)
+ {
+ intensity+=histogram[black];
+ if (intensity >= black_point)
+ break;
+ }
+ intensity=0.0;
+ for (white=(ssize_t) MaxMap; white != 0; white--)
+ {
+ intensity+=histogram[white];
+ if (intensity >= white_point)
+ break;
+ }
+ histogram=(double *) RelinquishMagickMemory(histogram);
+ status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
+ (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
+ return(status);
+}
+
+
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% M o d u l a t e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% ModulateImage() lets you control the brightness, saturation, and hue
+% of an image. Modulate represents the brightness, saturation, and hue
+% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
+% modulation is lightness, saturation, and hue. For HWB, use blackness,
+% whiteness, and hue. And for HCL, use chrome, luma, and hue.
+%
+% The format of the ModulateImage method is:
+%
+% MagickBooleanType ModulateImage(Image *image,const char *modulate,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o modulate: Define the percent change in brightness, saturation, and hue.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+static inline void ModulateHCL(const double percent_hue,
+ const double percent_chroma,const double percent_luma,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ luma,
+ chroma;
+
+ /*
+ Increase or decrease color luma, chroma, or hue.
+ */
+ ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue > 1.0)
+ hue-=1.0;
+ chroma*=0.01*percent_chroma;
+ luma*=0.01*percent_luma;
+ ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
+}
+
+static inline void ModulateHCLp(const double percent_hue,
+ const double percent_chroma,const double percent_luma,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ luma,
+ chroma;
+
+ /*
+ Increase or decrease color luma, chroma, or hue.
+ */
+ ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue > 1.0)
+ hue-=1.0;
+ chroma*=0.01*percent_chroma;
+ luma*=0.01*percent_luma;
+ ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
+}
+
+static inline void ModulateHSB(const double percent_hue,
+ const double percent_saturation,const double percent_brightness,double *red,
+ double *green,double *blue)
+{
+ double
+ brightness,
+ hue,
+ saturation;
+
+ /*
+ Increase or decrease color brightness, saturation, or hue.
+ */
+ ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue > 1.0)
+ hue-=1.0;
+ saturation*=0.01*percent_saturation;
+ brightness*=0.01*percent_brightness;
+ ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
+}
+
+static inline void ModulateHSI(const double percent_hue,
+ const double percent_saturation,const double percent_intensity,double *red,
+ double *green,double *blue)
+{
+ double
+ intensity,
+ hue,
+ saturation;
+
+ /*
+ Increase or decrease color intensity, saturation, or hue.
+ */
+ ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue > 1.0)
+ hue-=1.0;
+ saturation*=0.01*percent_saturation;
+ intensity*=0.01*percent_intensity;
+ ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
+}
+
+static inline void ModulateHSL(const double percent_hue,
+ const double percent_saturation,const double percent_lightness,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ lightness,
+ saturation;
+
+ /*
+ Increase or decrease color lightness, saturation, or hue.
+ */
+ ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue >= 1.0)
+ hue-=1.0;
+ saturation*=0.01*percent_saturation;
+ lightness*=0.01*percent_lightness;
+ ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
+}
+
+static inline void ModulateHSV(const double percent_hue,
+ const double percent_saturation,const double percent_value,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ saturation,
+ value;
+
+ /*
+ Increase or decrease color value, saturation, or hue.
+ */
+ ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue >= 1.0)
+ hue-=1.0;
+ saturation*=0.01*percent_saturation;
+ value*=0.01*percent_value;
+ ConvertHSVToRGB(hue,saturation,value,red,green,blue);
+}
+
+static inline void ModulateHWB(const double percent_hue,
+ const double percent_whiteness,const double percent_blackness,double *red,
+ double *green,double *blue)
+{
+ double
+ blackness,
+ hue,
+ whiteness;
+
+ /*
+ Increase or decrease color blackness, whiteness, or hue.
+ */
+ ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue >= 1.0)
+ hue-=1.0;
+ blackness*=0.01*percent_blackness;
+ whiteness*=0.01*percent_whiteness;
+ ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
+}
+
+static inline void ModulateLCHab(const double percent_luma,
+ const double percent_chroma,const double percent_hue,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ luma,
+ chroma;
+
+ /*
+ Increase or decrease color luma, chroma, or hue.
+ */
+ ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
+ luma*=0.01*percent_luma;
+ chroma*=0.01*percent_chroma;
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue >= 1.0)
+ hue-=1.0;
+ ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
+}
+
+static inline void ModulateLCHuv(const double percent_luma,
+ const double percent_chroma,const double percent_hue,double *red,
+ double *green,double *blue)
+{
+ double
+ hue,
+ luma,
+ chroma;
+
+ /*
+ Increase or decrease color luma, chroma, or hue.
+ */
+ ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
+ luma*=0.01*percent_luma;
+ chroma*=0.01*percent_chroma;
+ hue+=0.5*(0.01*percent_hue-1.0);
+ while (hue < 0.0)
+ hue+=1.0;
+ while (hue >= 1.0)
+ hue-=1.0;
+ ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
+}
+
+MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
+ ExceptionInfo *exception)
+{
+#define ModulateImageTag "Modulate/Image"
+
+ CacheView
+ *image_view;
+
+ ColorspaceType
+ colorspace;
+
+ const char
+ *artifact;
+
+ double
+ percent_brightness,
+ percent_hue,
+ percent_saturation;
+
+ GeometryInfo
+ geometry_info;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ MagickStatusType
+ flags;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ /*
+ Initialize modulate table.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (modulate == (char *) NULL)
+ return(MagickFalse);
+ if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
+ (void) SetImageColorspace(image,sRGBColorspace,exception);
+ flags=ParseGeometry(modulate,&geometry_info);
+ percent_brightness=geometry_info.rho;
+ percent_saturation=geometry_info.sigma;
+ if ((flags & SigmaValue) == 0)
+ percent_saturation=100.0;
+ percent_hue=geometry_info.xi;
+ if ((flags & XiValue) == 0)
+ percent_hue=100.0;
+ colorspace=UndefinedColorspace;
+ artifact=GetImageArtifact(image,"modulate:colorspace");
+ if (artifact != (const char *) NULL)
+ colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
+ MagickFalse,artifact);
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ double
+ blue,
+ green,
+ red;
+
+ /*
+ Modulate image colormap.
+ */
+ red=(double) image->colormap[i].red;
+ green=(double) image->colormap[i].green;
+ blue=(double) image->colormap[i].blue;
+ switch (colorspace)
+ {
+ case HCLColorspace:
+ {
+ ModulateHCL(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HCLpColorspace:
+ {
+ ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSBColorspace:
+ {
+ ModulateHSB(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSIColorspace:
+ {
+ ModulateHSI(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSLColorspace:
+ default:
+ {
+ ModulateHSL(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSVColorspace:
+ {
+ ModulateHSV(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HWBColorspace:
+ {
+ ModulateHWB(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case LCHColorspace:
+ case LCHabColorspace:
+ {
+ ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
+ &red,&green,&blue);
+ break;
+ }
+ case LCHuvColorspace:
+ {
+ ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
+ &red,&green,&blue);
+ break;
+ }
+ }
+ image->colormap[i].red=red;
+ image->colormap[i].green=green;
+ image->colormap[i].blue=blue;
+ }
+ /*
+ Modulate image.
+ */
+ if(AccelerateModulateImage(image,percent_brightness,percent_hue,
+ percent_saturation,colorspace,exception) != MagickFalse)
+ return(MagickTrue);
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ double
+ blue,
+ green,
+ red;
+
+ red=(double) GetPixelRed(image,q);
+ green=(double) GetPixelGreen(image,q);
+ blue=(double) GetPixelBlue(image,q);
+ switch (colorspace)
+ {
+ case HCLColorspace:
+ {
+ ModulateHCL(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HCLpColorspace:
+ {
+ ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSBColorspace:
+ {
+ ModulateHSB(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSLColorspace:
+ default:
+ {
+ ModulateHSL(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HSVColorspace:
+ {
+ ModulateHSV(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case HWBColorspace:
+ {
+ ModulateHWB(percent_hue,percent_saturation,percent_brightness,
+ &red,&green,&blue);
+ break;
+ }
+ case LCHabColorspace:
+ {
+ ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
+ &red,&green,&blue);
+ break;
+ }
+ case LCHColorspace:
+ case LCHuvColorspace:
+ {
+ ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
+ &red,&green,&blue);
+ break;
+ }
+ }
+ SetPixelRed(image,ClampToQuantum(red),q);
+ SetPixelGreen(image,ClampToQuantum(green),q);
+ SetPixelBlue(image,ClampToQuantum(blue),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_ModulateImage)
+#endif
+ proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% N e g a t e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% NegateImage() negates the colors in the reference image. The grayscale
+% option means that only grayscale values within the image are negated.
+%
+% The format of the NegateImage method is:
+%
+% MagickBooleanType NegateImage(Image *image,
+% const MagickBooleanType grayscale,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType NegateImage(Image *image,
+ const MagickBooleanType grayscale,ExceptionInfo *exception)
+{
+#define NegateImageTag "Negate/Image"
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ register ssize_t
+ i;
+
+ ssize_t
+ y;
+
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ if (image->storage_class == PseudoClass)
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ /*
+ Negate colormap.
+ */
+ if( grayscale != MagickFalse )
+ if ((image->colormap[i].red != image->colormap[i].green) ||
+ (image->colormap[i].green != image->colormap[i].blue))
+ continue;
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=QuantumRange-image->colormap[i].red;
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=QuantumRange-image->colormap[i].green;
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
+ }
+ /*
+ Negate image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+ if( grayscale != MagickFalse )
+ {
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ MagickBooleanType
+ sync;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
+ exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if ((GetPixelReadMask(image,q) == 0) ||
+ IsPixelGray(image,q) != MagickFalse)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ q[j]=QuantumRange-q[j];
+ }
+ q+=GetPixelChannels(image);
+ }
+ sync=SyncCacheViewAuthenticPixels(image_view,exception);
+ if (sync == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_NegateImage)
+#endif
+ proceed=SetImageProgress(image,NegateImageTag,progress++,
+ image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(MagickTrue);
+ }
+ /*
+ Negate image.
+ */
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ j;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,j);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ q[j]=QuantumRange-q[j];
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_NegateImage)
+#endif
+ proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% N o r m a l i z e I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% The NormalizeImage() method enhances the contrast of a color image by
+% mapping the darkest 2 percent of all pixel to black and the brightest
+% 1 percent to white.
+%
+% The format of the NormalizeImage method is:
+%
+% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType NormalizeImage(Image *image,
+ ExceptionInfo *exception)
+{
+ double
+ black_point,
+ white_point;
+
+ black_point=(double) image->columns*image->rows*0.0015;
+ white_point=(double) image->columns*image->rows*0.9995;
+ return(ContrastStretchImage(image,black_point,white_point,exception));
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
+% S i g m o i d a l C o n t r a s t I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
+% sigmoidal contrast algorithm. Increase the contrast of the image using a
+% sigmoidal transfer function without saturating highlights or shadows.
+% Contrast indicates how much to increase the contrast (0 is none; 3 is
+% typical; 20 is pushing it); mid-point indicates where midtones fall in the
+% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
+% sharpen to MagickTrue to increase the image contrast otherwise the contrast
+% is reduced.
+%
+% The format of the SigmoidalContrastImage method is:
+%
+% MagickBooleanType SigmoidalContrastImage(Image *image,
+% const MagickBooleanType sharpen,const char *levels,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o sharpen: Increase or decrease image contrast.
+%
+% o contrast: strength of the contrast, the larger the number the more
+% 'threshold-like' it becomes.
+%
+% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+
+/*
+ ImageMagick 6 has a version of this function which uses LUTs.
+*/
+
+/*
+ Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
+ constant" set to a.
+
+ The first version, based on the hyperbolic tangent tanh, when combined with
+ the scaling step, is an exact arithmetic clone of the the sigmoid function
+ based on the logistic curve. The equivalence is based on the identity
+
+ 1/(1+exp(-t)) = (1+tanh(t/2))/2
+
+ (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
+ scaled sigmoidal derivation is invariant under affine transformations of
+ the ordinate.
+
+ The tanh version is almost certainly more accurate and cheaper. The 0.5
+ factor in the argument is to clone the legacy ImageMagick behavior. The
+ reason for making the define depend on atanh even though it only uses tanh
+ has to do with the construction of the inverse of the scaled sigmoidal.
+*/
+#if defined(MAGICKCORE_HAVE_ATANH)
+#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
+#else
+#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
+#endif
+/*
+ Scaled sigmoidal function:
+
+ ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
+ ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
+
+ See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
+ http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
+ of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
+ zero. This is fixed below by exiting immediately when contrast is small,
+ leaving the image (or colormap) unmodified. This appears to be safe because
+ the series expansion of the logistic sigmoidal function around x=b is
+
+ 1/2-a*(b-x)/4+...
+
+ so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
+*/
+#define ScaledSigmoidal(a,b,x) ( \
+ (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
+ (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
+/*
+ Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
+ may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
+ sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
+ when creating a LUT from in gamut values, hence the branching. In
+ addition, HDRI may have out of gamut values.
+ InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
+ It is only a right inverse. This is unavoidable.
+*/
+static inline double InverseScaledSigmoidal(const double a,const double b,
+ const double x)
+{
+ const double sig0=Sigmoidal(a,b,0.0);
+ const double sig1=Sigmoidal(a,b,1.0);
+ const double argument=(sig1-sig0)*x+sig0;
+ const double clamped=
+ (
+#if defined(MAGICKCORE_HAVE_ATANH)
+ argument < -1+MagickEpsilon
+ ?
+ -1+MagickEpsilon
+ :
+ ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
+ );
+ return(b+(2.0/a)*atanh(clamped));
+#else
+ argument < MagickEpsilon
+ ?
+ MagickEpsilon
+ :
+ ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
+ );
+ return(b-log(1.0/clamped-1.0)/a);
+#endif
+}
+
+MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
+ const MagickBooleanType sharpen,const double contrast,const double midpoint,
+ ExceptionInfo *exception)
+{
+#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
+#define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
+ ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
+#define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
+ InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
+
+ CacheView
+ *image_view;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ ssize_t
+ y;
+
+ /*
+ Convenience macros.
+ */
+ assert(image != (Image *) NULL);
+ assert(image->signature == MagickCoreSignature);
+ if (image->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+ /*
+ Side effect: may clamp values unless contrast<MagickEpsilon, in which
+ case nothing is done.
+ */
+ if (contrast < MagickEpsilon)
+ return(MagickTrue);
+ /*
+ Sigmoidal-contrast enhance colormap.
+ */
+ if (image->storage_class == PseudoClass)
+ {
+ register ssize_t
+ i;
+
+ if( sharpen != MagickFalse )
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=(MagickRealType) ScaledSig(
+ image->colormap[i].red);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=(MagickRealType) ScaledSig(
+ image->colormap[i].green);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=(MagickRealType) ScaledSig(
+ image->colormap[i].blue);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=(MagickRealType) ScaledSig(
+ image->colormap[i].alpha);
+ }
+ else
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=(MagickRealType) InverseScaledSig(
+ image->colormap[i].red);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=(MagickRealType) InverseScaledSig(
+ image->colormap[i].green);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=(MagickRealType) InverseScaledSig(
+ image->colormap[i].blue);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
+ image->colormap[i].alpha);
+ }
+ }
+ /*
+ Sigmoidal-contrast enhance image.
+ */
+ status=MagickTrue;
+ progress=0;
+ image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static,4) shared(progress,status) \
+ magick_threads(image,image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ x;
+
+ if (status == MagickFalse)
+ continue;
+ q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ register ssize_t
+ i;
+
+ if (GetPixelReadMask(image,q) == 0)
+ {
+ q+=GetPixelChannels(image);
+ continue;
+ }
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ PixelChannel channel=GetPixelChannelChannel(image,i);
+ PixelTrait traits=GetPixelChannelTraits(image,channel);
+ if ((traits & UpdatePixelTrait) == 0)
+ continue;
+ if( sharpen != MagickFalse )
+ q[i]=ScaledSig(q[i]);
+ else
+ q[i]=InverseScaledSig(q[i]);
+ }
+ q+=GetPixelChannels(image);
+ }
+ if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp critical (MagickCore_SigmoidalContrastImage)
+#endif
+ proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
+ image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ image_view=DestroyCacheView(image_view);
+ return(status);
+}