+2018-11-21 7.0.8-15 Cristy <quetzlzacatenango@image...>
+ * added support for the -clahe option: contrast limited adaptive histogram
+ equalization
+
2018-11-13 7.0.8-15 Dirk Lemstra <dirk@lem.....org>
* Added support for GIMP 2.10 files (reference
https://github.com/ImageMagick/ImageMagick/pull/1381).
{ "-charcoal", 1L, SimpleOperatorFlag, MagickFalse },
{ "+chop", 1L, DeprecateOptionFlag, MagickTrue },
{ "-chop", 1L, SimpleOperatorFlag, MagickFalse },
+ { "+clahe", 1L, DeprecateOptionFlag, MagickTrue },
+ { "-clahe", 1L, SimpleOperatorFlag, MagickFalse },
{ "+clamp", 0L, DeprecateOptionFlag, MagickTrue },
{ "-clamp", 0L, SimpleOperatorFlag, MagickFalse },
{ "-clip", 0L, SimpleOperatorFlag, MagickFalse },
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickCoreSignature);
- threshold_image=CloneImage(image,0,0,MagickTrue,
- exception);
+ threshold_image=CloneImage(image,0,0,MagickTrue,exception);
if (threshold_image == (Image *) NULL)
return((Image *) NULL);
status=SetImageStorageClass(threshold_image,DirectClass,exception);
% %
% %
% %
+% C A L H E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% CLAHEImage() is a variant of adaptive histogram equalization in which the
+% contrast amplification is limited, so as to reduce this problem of noise
+% amplification.
+%
+% Image *CLAHEImage(const Image *image,const size_t width,
+% const size_t height,const double bias,const double sans,
+% ExceptionInfo *exception)
+%
+% A description of each parameter follows:
+%
+% o image: the image.
+%
+% o width: the width of the local neighborhood.
+%
+% o height: the height of the local neighborhood.
+%
+% o bias: the mean bias.
+%
+% o sans: not used
+%
+% o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport Image *CLAHEImage(const Image *image,
+ const size_t width,const size_t height,const double bias,const double sans,
+ ExceptionInfo *exception)
+{
+#define CLAHEImageTag "CLAHE/Image"
+
+ CacheView
+ *image_view,
+ *threshold_view;
+
+ Image
+ *threshold_image;
+
+ MagickBooleanType
+ status;
+
+ MagickOffsetType
+ progress;
+
+ MagickSizeType
+ number_pixels;
+
+ ssize_t
+ y;
+
+ /*
+ Initialize threshold image attributes.
+ */
+ assert(image != (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);
+ (void) sans;
+ threshold_image=CloneImage(image,0,0,MagickTrue,exception);
+ if (threshold_image == (Image *) NULL)
+ return((Image *) NULL);
+ status=SetImageStorageClass(threshold_image,DirectClass,exception);
+ if (status == MagickFalse)
+ {
+ threshold_image=DestroyImage(threshold_image);
+ return((Image *) NULL);
+ }
+ /*
+ Threshold image.
+ */
+ status=MagickTrue;
+ progress=0;
+ number_pixels=(MagickSizeType) width*height;
+ image_view=AcquireVirtualCacheView(image,exception);
+ threshold_view=AcquireAuthenticCacheView(threshold_image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp parallel for schedule(static) shared(progress,status) \
+ magick_number_threads(image,threshold_image,image->rows,1)
+#endif
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ double
+ channel_bias[MaxPixelChannels],
+ channel_sum[MaxPixelChannels];
+
+ register const Quantum
+ *magick_restrict p,
+ *magick_restrict pixels;
+
+ register Quantum
+ *magick_restrict q;
+
+ register ssize_t
+ i,
+ x;
+
+ ssize_t
+ center,
+ u,
+ v;
+
+ if (status == MagickFalse)
+ continue;
+ p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
+ (height/2L),image->columns+width,height,exception);
+ q=QueueCacheViewAuthenticPixels(threshold_view,0,y,threshold_image->columns,
+ 1,exception);
+ if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
+ {
+ status=MagickFalse;
+ continue;
+ }
+ center=(ssize_t) GetPixelChannels(image)*(image->columns+width)*(height/2L)+
+ GetPixelChannels(image)*(width/2);
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ PixelChannel channel = GetPixelChannelChannel(image,i);
+ PixelTrait traits = GetPixelChannelTraits(image,channel);
+ PixelTrait threshold_traits=GetPixelChannelTraits(threshold_image,
+ channel);
+ if ((traits == UndefinedPixelTrait) ||
+ (threshold_traits == UndefinedPixelTrait))
+ continue;
+ if ((threshold_traits & CopyPixelTrait) != 0)
+ {
+ SetPixelChannel(threshold_image,channel,p[center+i],q);
+ continue;
+ }
+ pixels=p;
+ channel_bias[channel]=0.0;
+ channel_sum[channel]=0.0;
+ for (v=0; v < (ssize_t) height; v++)
+ {
+ for (u=0; u < (ssize_t) width; u++)
+ {
+ if (u == (ssize_t) (width-1))
+ channel_bias[channel]+=pixels[i];
+ channel_sum[channel]+=pixels[i];
+ pixels+=GetPixelChannels(image);
+ }
+ pixels+=GetPixelChannels(image)*image->columns;
+ }
+ }
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+ {
+ double
+ mean;
+
+ PixelChannel channel = GetPixelChannelChannel(image,i);
+ PixelTrait traits = GetPixelChannelTraits(image,channel);
+ PixelTrait threshold_traits=GetPixelChannelTraits(threshold_image,
+ channel);
+ if ((traits == UndefinedPixelTrait) ||
+ (threshold_traits == UndefinedPixelTrait))
+ continue;
+ if ((threshold_traits & CopyPixelTrait) != 0)
+ {
+ SetPixelChannel(threshold_image,channel,p[center+i],q);
+ continue;
+ }
+ channel_sum[channel]-=channel_bias[channel];
+ channel_bias[channel]=0.0;
+ pixels=p;
+ for (v=0; v < (ssize_t) height; v++)
+ {
+ channel_bias[channel]+=pixels[i];
+ pixels+=(width-1)*GetPixelChannels(image);
+ channel_sum[channel]+=pixels[i];
+ pixels+=GetPixelChannels(image)*(image->columns+1);
+ }
+ mean=(double) (channel_sum[channel]/number_pixels+bias);
+ SetPixelChannel(threshold_image,channel,(Quantum) ((double)
+ p[center+i] <= mean ? 0 : QuantumRange),q);
+ }
+ p+=GetPixelChannels(image);
+ q+=GetPixelChannels(threshold_image);
+ }
+ if (SyncCacheViewAuthenticPixels(threshold_view,exception) == MagickFalse)
+ status=MagickFalse;
+ if (image->progress_monitor != (MagickProgressMonitor) NULL)
+ {
+ MagickBooleanType
+ proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+ #pragma omp atomic
+#endif
+ progress++;
+ proceed=SetImageProgress(image,CLAHEImageTag,progress,image->rows);
+ if (proceed == MagickFalse)
+ status=MagickFalse;
+ }
+ }
+ threshold_image->type=image->type;
+ threshold_view=DestroyCacheView(threshold_view);
+ image_view=DestroyCacheView(image_view);
+ if (status == MagickFalse)
+ threshold_image=DestroyImage(threshold_image);
+ return(threshold_image);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
% C l a m p I m a g e %
% %
% %
extern MagickExport Image
*AdaptiveThresholdImage(const Image *,const size_t,const size_t,const double,
+ ExceptionInfo *),
+ *CLAHEImage(const Image *,const size_t,const size_t,const double,const double,
ExceptionInfo *);
extern MagickExport ThresholdMap
"-channel mask set the image channel mask",
"-charcoal radius simulate a charcoal drawing",
"-chop geometry remove pixels from the image interior",
+ "-clahe geometry contrast limited adaptive histogram equalization",
"-clamp keep pixel values in range (0-QuantumRange)",
"-colorize value colorize the image with the fill color",
"-color-matrix matrix apply color correction to the image",
{
ssize_t
method;
-
+
if (*option == '+')
break;
i++;
ThrowConvertInvalidArgumentException(option,argv[i]);
break;
}
+ if (LocaleCompare("clahe",option+1) == 0)
+ {
+ if (*option == '+')
+ break;
+ i++;
+ if (i == (ssize_t) argc)
+ ThrowConvertException(OptionError,"MissingArgument",option);
+ if (IsGeometry(argv[i]) == MagickFalse)
+ ThrowConvertInvalidArgumentException(option,argv[i]);
+ break;
+ }
if (LocaleCompare("clamp",option+1) == 0)
break;
if (LocaleCompare("clip",option+1) == 0)
Image
*clone_images,
*clone_list;
-
+
clone_list=CloneImageList(image,exception);
if (k != 0)
clone_list=CloneImageList(image_stack[k-1].image,exception);
if (*option == '+')
clone_images=CloneImages(clone_list,"-1",exception);
else
- {
+ {
i++;
if (i == (ssize_t) argc)
ThrowConvertException(OptionError,"MissingArgument",option);
% %
% %
% %
+% M a g i c k C L A H E I m a g e %
+% %
+% %
+% %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% MagickCLAHEImage() selects an individual threshold for each pixel
+% based on the range of intensity values in its local neighborhood. This
+% allows for thresholding of an image whose global intensity histogram
+% doesn't contain distinctive peaks.
+%
+% The format of the CLAHEImage method is:
+%
+% MagickBooleanType MagickCLAHEImage(MagickWand *wand,const size_t width,
+% const size_t height,const double bias,const double sans)
+%
+% A description of each parameter follows:
+%
+% o wand: the magick wand.
+%
+% o width: the width of the local neighborhood.
+%
+% o height: the height of the local neighborhood.
+%
+% o offset: the mean bias.
+%
+% o sans: not used.
+%
+*/
+WandExport MagickBooleanType MagickCLAHEImage(MagickWand *wand,
+ const size_t width,const size_t height,const double bias,const double sans)
+{
+ Image
+ *threshold_image;
+
+ assert(wand != (MagickWand *) NULL);
+ assert(wand->signature == MagickWandSignature);
+ if (wand->debug != MagickFalse)
+ (void) LogMagickEvent(WandEvent,GetMagickModule(),"%s",wand->name);
+ if (wand->images == (Image *) NULL)
+ ThrowWandException(WandError,"ContainsNoImages",wand->name);
+ threshold_image=CLAHEImage(wand->images,width,height,bias,sans,
+ wand->exception);
+ if (threshold_image == (Image *) NULL)
+ return(MagickFalse);
+ ReplaceImageInList(&wand->images,threshold_image);
+ return(MagickTrue);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% %
+% %
+% %
% M a g i c k C l a m p I m a g e %
% %
% %
MagickCharcoalImage(MagickWand *,const double,const double),
MagickChopImage(MagickWand *,const size_t,const size_t,const ssize_t,
const ssize_t),
+ MagickCLAHEImage(MagickWand *,const size_t,const size_t,const double,
+ const double),
MagickClampImage(MagickWand *),
MagickClipImage(MagickWand *),
MagickClipImagePath(MagickWand *,const char *,const MagickBooleanType),
mogrify_image=ChopImage(*image,&geometry,exception);
break;
}
+ if (LocaleCompare("clahe",option+1) == 0)
+ {
+ /*
+ Contrast limited adaptive histogram equalization.
+ */
+ (void) SyncImageSettings(mogrify_info,*image,exception);
+ flags=ParseGeometry(argv[i+1],&geometry_info);
+ mogrify_image=CLAHEImage(*image,(size_t) geometry_info.rho,
+ (size_t) geometry_info.sigma,(double) geometry_info.xi,
+ geometry_info.psi,exception);
+ break;
+ }
if (LocaleCompare("clip",option+1) == 0)
{
(void) SyncImageSettings(mogrify_info,*image,exception);
"-channel mask set the image channel mask",
"-charcoal geometry simulate a charcoal drawing",
"-chop geometry remove pixels from the image interior",
+ "-clahe geometry contrast limited adaptive histogram equalization",
"-clamp keep pixel values in range (0-QuantumRange)",
"-clip clip along the first path from the 8BIM profile",
"-clip-mask filename associate a clip mask with the image",
ThrowMogrifyInvalidArgumentException(option,argv[i]);
break;
}
+ if (LocaleCompare("clahe",option+1) == 0)
+ {
+ if (*option == '+')
+ break;
+ i++;
+ if (i == (ssize_t) argc)
+ ThrowMogrifyException(OptionError,"MissingArgument",option);
+ if (IsGeometry(argv[i]) == MagickFalse)
+ ThrowMogrifyInvalidArgumentException(option,argv[i]);
+ break;
+ }
if (LocaleCompare("clamp",option+1) == 0)
break;
if (LocaleCompare("clip",option+1) == 0)
new_image=ChopImage(_image,&geometry,_exception);
break;
}
+ if (LocaleCompare("clahe",option+1) == 0)
+ {
+ flags=ParseGeometry(arg1,&geometry_info);
+ if ((flags & (RhoValue|SigmaValue)) == 0)
+ CLIWandExceptArgBreak(OptionError,"InvalidArgument",option,arg1);
+ new_image=CLAHEImage(_image,(size_t) geometry_info.rho,
+ (size_t) geometry_info.sigma,geometry_info.xi,geometry_info.xi,
+ _exception);
+ break;
+ }
if (LocaleCompare("clamp",option+1) == 0)
{
(void) ClampImage(_image,_exception);
if (IfNormalOp)
(void) ClipImage(_image,_exception);
else /* "+mask" remove the write mask */
- (void) SetImageMask(_image,WritePixelMask,(Image *) NULL,_exception);
+ (void) SetImageMask(_image,WritePixelMask,(Image *) NULL,
+ _exception);
break;
}
if (LocaleCompare("clip-mask",option+1) == 0)
{"background", StringReference} } },
{ "Difference", { {"image", ImageReference}, {"fuzz", StringReference} } },
{ "AdaptiveThreshold", { {"geometry", StringReference},
- {"width", IntegerReference}, {"height", IntegerReference} } },
+ {"width", IntegerReference}, {"height", IntegerReference},
+ {"bias", RealReference} } },
{ "Resample", { {"density", StringReference}, {"x", RealReference},
{"y", RealReference}, {"filter", MagickFilterOptions},
{"support", RealReference } } },
{"low-black", RealReference}, {"low-white", RealReference},
{"high-white", RealReference}, {"high-black", RealReference},
{"channel", MagickChannelOptions} } },
+ { "CLAHE", { {"geometry", StringReference},
+ {"width", IntegerReference}, {"height", IntegerReference},
+ {"bias", RealReference}, {"sans", RealReference} } },
};
static SplayTreeInfo
AutoThresholdImage = 294
RangeThreshold = 295
RangeThresholdImage= 296
+ CLAHE = 297
+ CLAHEImage = 298
MogrifyRegion = 666
PPCODE:
{
(void) SetImageChannelMask(image,channel_mask);
break;
}
+ case 149: /* CLAHE */
+ {
+ if (attribute_flag[0] != 0)
+ {
+ flags=ParseGeometry(argument_list[0].string_reference,
+ &geometry_info);
+ if ((flags & PercentValue) != 0)
+ geometry_info.xi=QuantumRange*geometry_info.xi/100.0;
+ }
+ if (attribute_flag[1] != 0)
+ geometry_info.rho=argument_list[1].integer_reference;
+ if (attribute_flag[2] != 0)
+ geometry_info.sigma=argument_list[2].integer_reference;
+ if (attribute_flag[3] != 0)
+ geometry_info.xi=argument_list[3].integer_reference;;
+ if (attribute_flag[4] != 0)
+ geometry_info.psi=argument_list[4].integer_reference;;
+ image=CLAHEImage(image,(size_t) geometry_info.rho,
+ (size_t) geometry_info.sigma,geometry_info.xi,geometry_info.psi,
+ exception);
+ break;
+ }
}
if (next != (Image *) NULL)
(void) CatchImageException(next);
{"background", StringReference} } },
{ "Difference", { {"image", ImageReference}, {"fuzz", StringReference} } },
{ "AdaptiveThreshold", { {"geometry", StringReference},
- {"width", IntegerReference}, {"height", IntegerReference} } },
+ {"width", IntegerReference}, {"height", IntegerReference},
+ {"bias", RealReference} } },
{ "Resample", { {"density", StringReference}, {"x", RealReference},
{"y", RealReference}, {"filter", MagickFilterOptions},
{"support", RealReference } } },
{"low-black", RealReference}, {"low-white", RealReference},
{"high-white", RealReference}, {"high-black", RealReference},
{"channel", MagickChannelOptions} } },
+ { "CLAHE", { {"geometry", StringReference},
+ {"width", IntegerReference}, {"height", IntegerReference},
+ {"bias", RealReference}, {"sans", RealReference} } },
};
static SplayTreeInfo
AutoThresholdImage = 294
RangeThreshold = 295
RangeThresholdImage= 296
+ CLAHE = 297
+ CLAHEImage = 298
MogrifyRegion = 666
PPCODE:
{
(void) SetImageChannelMask(image,channel_mask);
break;
}
+ case 149: /* CLAHE */
+ {
+ if (attribute_flag[0] != 0)
+ {
+ flags=ParseGeometry(argument_list[0].string_reference,
+ &geometry_info);
+ if ((flags & PercentValue) != 0)
+ geometry_info.xi=QuantumRange*geometry_info.xi/100.0;
+ }
+ if (attribute_flag[1] != 0)
+ geometry_info.rho=argument_list[1].integer_reference;
+ if (attribute_flag[2] != 0)
+ geometry_info.sigma=argument_list[2].integer_reference;
+ if (attribute_flag[3] != 0)
+ geometry_info.xi=argument_list[3].integer_reference;;
+ if (attribute_flag[4] != 0)
+ geometry_info.psi=argument_list[4].integer_reference;;
+ image=CLAHEImage(image,(size_t) geometry_info.rho,
+ (size_t) geometry_info.sigma,geometry_info.xi,geometry.psi,
+ exception);
+ break;
+ }
}
if (next != (Image *) NULL)
(void) CatchImageException(next);
\-channel mask set the image channel mask
\-charcoal radius simulate a charcoal drawing
\-chop geometry remove pixels from the image interior
+ \-clahe geometry contrast limited adaptive histogram equalization
\-clamp keep pixel values in range (0-QuantumRange)
\-clip clip along the first path from the 8BIM profile
\-clip-mask filename associate a clip mask with the image
\-channel mask set the image channel mask
\-charcoal radius simulate a charcoal drawing
\-chop geometry remove pixels from the image interior
+ \-clahe geometry contrast limited adaptive histogram equalization
\-clamp keep pixel values in range (0-QuantumRange)
\-clip clip along the first path from the 8BIM profile
\-clip-mask filename associate a clip mask with the image