progress=0;
adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
clut_view=AcquireVirtualCacheView(clut_image,exception);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i <= (ssize_t) MaxMap; i++)
{
GetPixelInfo(clut_image,clut_map+i);
}
image_view=DestroyCacheView(image_view);
clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
- if ((clut_image->matte != MagickFalse) &&
+ if ((clut_image->alpha_trait == BlendPixelTrait) &&
((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
(void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
return(status);
if (cdl_map == (PixelInfo *) NULL)
ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
image->filename);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i <= (ssize_t) MaxMap; i++)
{
cdl_map[i].red=(double) ScaleMapToQuantum((double)
for (i=0; i < (ssize_t) image->colors; i++)
{
/*
- Apply transfer function to colormap.
+ Apply transfer function to colormap.
*/
double
- luma;
+ luma;
luma=0.21267*image->colormap[i].red+0.71526*image->colormap[i].green+
- 0.07217*image->colormap[i].blue;
+ 0.07217*image->colormap[i].blue;
image->colormap[i].red=luma+color_correction.saturation*cdl_map[
- ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
+ 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[
Find the histogram boundaries by locating the black/white levels.
*/
number_channels=GetPixelChannels(image);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) shared(progress,status) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i < (ssize_t) number_channels; i++)
{
double
(void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
sizeof(*stretch_map));
number_channels=GetPixelChannels(image);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) shared(progress,status) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i < (ssize_t) number_channels; i++)
{
register ssize_t
Integrate the histogram to get the equalization map.
*/
number_channels=GetPixelChannels(image);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) shared(progress,status) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i < (ssize_t) number_channels; i++)
{
double
(void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
sizeof(*equalize_map));
number_channels=GetPixelChannels(image);
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static,4) shared(progress,status) \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i < (ssize_t) number_channels; i++)
{
register ssize_t
image->filename);
(void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
if (gamma != 0.0)
-#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
- #pragma omp parallel for \
- dynamic_number_threads(image,image->columns,1,1)
-#endif
for (i=0; i <= (ssize_t) MaxMap; i++)
gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
MaxMap,1.0/gamma)));
for (i=0; i < (ssize_t) image->colors; i++)
{
/*
- Gamma-correct colormap.
+ Gamma-correct colormap.
*/
if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].red=(double) gamma_map[
+ 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[
return(MagickFalse);
if (IsGrayColorspace(image->colorspace) != MagickFalse)
(void) TransformImageColorspace(image,RGBColorspace,exception);
- if (image->matte == MagickFalse)
+ if (image->alpha_trait != BlendPixelTrait)
(void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
/*
Hald clut image.
(image->colorspace == CMYKColorspace))
SetPixelBlack(image,ClampToQuantum(pixel.black),q);
if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
- (image->matte != MagickFalse))
+ (image->alpha_trait == BlendPixelTrait))
SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
q+=GetPixelChannels(image);
}
(void) SetImageChannelMask(image,channel_mask);
}
if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
- (image->matte == MagickTrue))
+ (image->alpha_trait == BlendPixelTrait))
{
channel_mask=SetImageChannelMask(image,AlphaChannel);
status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
(void) SetImageChannelMask(image,channel_mask);
}
if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
- (image->matte == MagickTrue))
+ (image->alpha_trait == BlendPixelTrait))
{
channel_mask=SetImageChannelMask(image,AlphaChannel);
status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
for (i=0; i < (ssize_t) image->colors; i++)
{
/*
- Negate colormap.
+ Negate colormap.
*/
if (grayscale != MagickFalse)
- if ((image->colormap[i].red != image->colormap[i].green) ||
- (image->colormap[i].green != image->colormap[i].blue))
- continue;
+ 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;
+ image->colormap[i].red=QuantumRange-image->colormap[i].red;
if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].green=QuantumRange-image->colormap[i].green;
+ image->colormap[i].green=QuantumRange-image->colormap[i].green;
if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
+ image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
}
/*
Negate image.
image_view=AcquireAuthenticCacheView(image,exception);
if (grayscale != MagickFalse)
{
-#if defined(MAGICKCORE_OPENMP_SUPPORT)
- #pragma omp parallel for schedule(static) shared(progress,status) \
- dynamic_number_threads(image,image->columns,image->rows,1)
-#endif
for (y=0; y < (ssize_t) image->rows; y++)
{
MagickBooleanType
%
% o sharpen: Increase or decrease image contrast.
%
-% o alpha: strength of the contrast, the larger the number the more
+% o contrast: strength of the contrast, the larger the number the more
% 'threshold-like' it becomes.
%
-% o beta: midpoint of the function as a color value 0 to QuantumRange.
+% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
%
% o exception: return any errors or warnings in this structure.
%
*/
+
+/*
+ Sigmoidal function 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 its 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 above 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-side 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 argument=(Sigmoidal(a,b,1.0)-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+(-1.0/a)*log(1.0/clamped+-1.0));
+#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;
MagickOffsetType
progress;
- Quantum
- *sigmoidal_map;
-
- register ssize_t
- i;
-
ssize_t
y;
/*
- Sigmoidal with inflexion point moved to b and "slope constant" set to a.
- */
-#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
- /*
- Scaled sigmoidal formula: (1/(1+exp(a*(b-x))) - 1/(1+exp(a*b)))
- /
- (1/(1+exp(a*(b-1))) - 1/(1+exp(a*b))).
- See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
- http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf.
- */
-#define ScaledSigmoidal(a,b,x) ( \
- (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
- (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
-#define InverseScaledSigmoidal(a,b,x) ( \
- (b) - log( -1.0+1.0/((Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0))*(x)+ \
- Sigmoidal((a),(b),0.0)) ) / (a) )
- /*
- The limit of ScaledSigmoidal as a->0 is the identity, but a=0 gives a
- division by zero. This is fixed below by hardwiring the identity when a is
- small. This would appear to be safe because the series expansion of the
- sigmoidal function around x=b is 1/2-a*(b-x)/4+... so that s(1)-s(0) is
- about a/4.
- */
-
- /*
- Allocate and initialize sigmoidal maps.
+ Convenience macros.
*/
assert(image != (Image *) NULL);
assert(image->signature == MagickSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
- sigmoidal_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,
- sizeof(*sigmoidal_map));
- if (sigmoidal_map == (Quantum *) NULL)
- ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
- image->filename);
- (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
- if (contrast<4.0*MagickEpsilon)
- for (i=0; i <= (ssize_t) MaxMap; i++)
- sigmoidal_map[i]=ScaleMapToQuantum((double) i);
- else if (sharpen != MagickFalse)
- for (i=0; i <= (ssize_t) MaxMap; i++)
- sigmoidal_map[i]=ScaleMapToQuantum( (double) (MaxMap*
- ScaledSigmoidal(contrast,QuantumScale*midpoint,(double) i/MaxMap)));
- else
- for (i=0; i <= (ssize_t) MaxMap; i++)
- sigmoidal_map[i]=ScaleMapToQuantum((double) (MaxMap*
- InverseScaledSigmoidal(contrast,QuantumScale*midpoint,
- (double) i/MaxMap)));
+ /*
+ 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)
- for (i=0; i < (ssize_t) image->colors; i++)
{
- /*
- Sigmoidal-contrast enhance colormap.
- */
- if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].red=(double) ClampToQuantum((double) sigmoidal_map[
- ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))]);
- if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].green=(double) ClampToQuantum((double) sigmoidal_map[
- ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))]);
- if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].blue=(double) ClampToQuantum((double) sigmoidal_map[
- ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))]);
- if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
- image->colormap[i].alpha=(double) ClampToQuantum((double) sigmoidal_map[
- ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))]);
+ 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=ScaledSig(image->colormap[i].red);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=ScaledSig(image->colormap[i].green);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=ScaledSig(image->colormap[i].blue);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=ScaledSig(image->colormap[i].alpha);
+ }
+ else
+ for (i=0; i < (ssize_t) image->colors; i++)
+ {
+ if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].red=InverseScaledSig(image->colormap[i].red);
+ if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].green=InverseScaledSig(image->colormap[i].green);
+ if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].blue=InverseScaledSig(image->colormap[i].blue);
+ if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
+ image->colormap[i].alpha=InverseScaledSig(image->colormap[i].alpha);
+ }
}
/*
Sigmoidal-contrast enhance image.
traits=GetPixelChannelTraits(image,channel);
if ((traits & UpdatePixelTrait) == 0)
continue;
- q[i]=ClampToQuantum((double) sigmoidal_map[ScaleQuantumToMap(q[i])]);
+ if (sharpen != MagickFalse)
+ q[i]=ScaledSig(q[i]);
+ else
+ q[i]=InverseScaledSig(q[i]);
}
q+=GetPixelChannels(image);
}
}
}
image_view=DestroyCacheView(image_view);
- sigmoidal_map=(Quantum *) RelinquishMagickMemory(sigmoidal_map);
return(status);
}