]> granicus.if.org Git - imagemagick/commitdiff
add support for the -clahe option: contrast limited adaptive histogram equalization...
authorCristy <urban-warrior@imagemagick.org>
Thu, 22 Nov 2018 00:13:46 +0000 (19:13 -0500)
committerCristy <urban-warrior@imagemagick.org>
Thu, 22 Nov 2018 00:13:46 +0000 (19:13 -0500)
13 files changed:
ChangeLog
MagickCore/option.c
MagickCore/threshold.c
MagickCore/threshold.h
MagickWand/convert.c
MagickWand/magick-image.c
MagickWand/magick-image.h
MagickWand/mogrify.c
MagickWand/operation.c
PerlMagick/Magick.xs
PerlMagick/quantum/quantum.xs.in
utilities/convert.1.in
utilities/mogrify.1.in

index c7019ed71329abf60e795351cd6525e36579fb79..7c7d802f53f0b86c2c3358d1a198fcc3f8a693b0 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+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).
index 1d7a76d8cd567098e8234a5686b66bb91b55c6cd..cb585f2714bd5489404aa93ffd5116481bc34368 100644 (file)
@@ -612,6 +612,8 @@ static const OptionInfo
     { "-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 },
index 4463a232ac078e79e01d3afe6f63f475bfd18e50..1fd5d5360f81baf138a4f52ea3df077b5b356d37 100644 (file)
@@ -209,8 +209,7 @@ MagickExport Image *AdaptiveThresholdImage(const Image *image,
     (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);
@@ -1059,6 +1058,220 @@ MagickExport MagickBooleanType BlackThresholdImage(Image *image,
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%     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                                                     %
 %                                                                             %
 %                                                                             %
index 731ae68aabf7a269ba70200903a0e602410fe8c3..63a76630025bbc94ff9eab4bbb3e8cbbabffacbb 100644 (file)
@@ -35,6 +35,8 @@ typedef struct _ThresholdMap
 
 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
index 7a02c39a5751e09683477f971f18595f48f891c2..cab2a11af7cc99e55b12ab6f0017cb09b338252b 100644 (file)
@@ -192,6 +192,7 @@ static MagickBooleanType ConvertUsage(void)
       "-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",
@@ -758,7 +759,7 @@ WandExport MagickBooleanType ConvertImageCommand(ImageInfo *image_info,
           {
             ssize_t
               method;
-            
+
             if (*option == '+')
               break;
             i++;
@@ -990,6 +991,17 @@ WandExport MagickBooleanType ConvertImageCommand(ImageInfo *image_info,
               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)
@@ -1015,7 +1027,7 @@ WandExport MagickBooleanType ConvertImageCommand(ImageInfo *image_info,
             Image
               *clone_images,
               *clone_list;
-            
+
             clone_list=CloneImageList(image,exception);
             if (k != 0)
               clone_list=CloneImageList(image_stack[k-1].image,exception);
@@ -1025,7 +1037,7 @@ WandExport MagickBooleanType ConvertImageCommand(ImageInfo *image_info,
             if (*option == '+')
               clone_images=CloneImages(clone_list,"-1",exception);
             else
-              { 
+              {
                 i++;
                 if (i == (ssize_t) argc)
                   ThrowConvertException(OptionError,"MissingArgument",option);
index 79cc6ca7b134101435a212f79cf3865b044854bf..d174b3c7f7a4476cf7fa8a5e80918138414e63a7 100644 (file)
@@ -1254,6 +1254,60 @@ WandExport MagickBooleanType MagickChopImage(MagickWand *wand,
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%   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                                           %
 %                                                                             %
 %                                                                             %
index c9000a9099c8a0a8e7979b6c9f49a1442fcb269e..9f57aded96314e127ae10a66fa97a0f7beeefe2e 100644 (file)
@@ -101,6 +101,8 @@ extern WandExport MagickBooleanType
   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),
index 7ffcf8e18f7b59f94e582962e429478fb883e57b..ff3962ed25f713c016ae51068e70c14f7671a261 100644 (file)
@@ -1080,6 +1080,18 @@ WandExport MagickBooleanType MogrifyImage(ImageInfo *image_info,const int argc,
             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);
@@ -3473,6 +3485,7 @@ static MagickBooleanType MogrifyUsage(void)
       "-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",
@@ -4333,6 +4346,17 @@ WandExport MagickBooleanType MogrifyImageCommand(ImageInfo *image_info,
               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)
index cf7bd2ca0e932a64786214310865d9640206cf21..ed675030de1b9af272ec515ba37ac1e6b29efb75 100644 (file)
@@ -1966,6 +1966,16 @@ static MagickBooleanType CLISimpleOperatorImage(MagickCLI *cli_wand,
           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);
@@ -1976,7 +1986,8 @@ static MagickBooleanType CLISimpleOperatorImage(MagickCLI *cli_wand,
           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)
index 6651242dbb1202ae61d941e29b765592dc23b0e5..fdb10a5fe909441fce7d6eb5e390e5c287507975 100644 (file)
@@ -412,7 +412,8 @@ static struct
       {"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 } } },
@@ -570,6 +571,9 @@ static struct
       {"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
@@ -7640,6 +7644,8 @@ Mogrify(ref,...)
     AutoThresholdImage = 294
     RangeThreshold     = 295
     RangeThresholdImage= 296
+    CLAHE              = 297
+    CLAHEImage         = 298
     MogrifyRegion      = 666
   PPCODE:
   {
@@ -11471,6 +11477,28 @@ Mogrify(ref,...)
             (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);
index ba6a3eddf5d3426d774a5d6c1a9a541ffbb50384..a86d6293bdb57f316adbe0036a0bae1d50b9f715 100644 (file)
@@ -412,7 +412,8 @@ static struct
       {"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 } } },
@@ -570,6 +571,9 @@ static struct
       {"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
@@ -7639,6 +7643,8 @@ Mogrify(ref,...)
     AutoThresholdImage = 294
     RangeThreshold     = 295
     RangeThresholdImage= 296
+    CLAHE              = 297
+    CLAHEImage         = 298
     MogrifyRegion      = 666
   PPCODE:
   {
@@ -11476,6 +11482,28 @@ Mogrify(ref,...)
             (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);
index b61ac55b02ea27664f15a747e483dd25a47b6ebe..fc32a964a73b7fdc8370ab20140154d3de9cb3b8 100644 (file)
@@ -141,6 +141,7 @@ Image Operators:
   \-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
index 08ee5c3700ce5a301c44de8e240332231255f1ee..b94a274fefd7c28d5aa30ff1b181c1dea0015fb4 100644 (file)
@@ -139,6 +139,7 @@ Image Operators:
   \-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