]> granicus.if.org Git - imagemagick/commitdiff
Major re-write #3 of MorphologyApply()
authoranthony <anthony@git.imagemagick.org>
Sun, 23 May 2010 07:47:50 +0000 (07:47 +0000)
committeranthony <anthony@git.imagemagick.org>
Sun, 23 May 2010 07:47:50 +0000 (07:47 +0000)
ChangeLog
magick/morphology.c
magick/morphology.h
magick/option.c

index b5a9b242faef149126de5fba8913856329b66561..4c471ca8c386a79136e88a0c4eeca82655825d05 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,18 @@
+2010-05-23  6.6.2.0 Anthony Thyssen <A.Thyssen@griffith...>
+  * Third Re-write of MorphologyApply() to better handle compound methods.
+  * Implemented  -set option:morphology:compose for merging results of
+    multiple kernels. "None" means re-iterate results with next kernel.
+    While "Undefined" means to use the internal default for a method.
+    Thinning, Thickening, HitAndMiss, Convolve, Correlate defaults to "None"
+    while HitAndMiss defaults to "Lighten" (union of all kernel results).
+    Other morphology methods defaults still to be decided.
+  * Added HitAndMiss Kernel: Ridges  (find ridges and pixel width lines)
+  * ExpandKernelInfo() (rotation expand) now groks symmetrical kernels.
+
 2010-05-18  6.6.2.0 Anthony Thyssen <A.Thyssen@griffith...>
   * Separation of internal function MorphologyAppy() from
     MorphologyImageChannel() to calls to convolve without user settings.
-  * Rewrite of MorphologyApply to output better 'verbose' messages
+  * Rewrite of MorphologyApply() to output better 'verbose' messages
   * Better handling of Gaussian tyle filters (bug fixes)
   * Bug fix and optimization of kernel size calculations in "gem.c"
   * Allow kernel rotation flags in 'user defined' kernels
index 9dae6be8887abb5647090cf51b6e69a635064e80..0eb5191b36f5e69fad7df85e6a6ed9455ef6dcb4 100644 (file)
@@ -775,6 +775,8 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       Find Edges of a binary shape
 %    Corners
 %       Find corners of a binary shape
+%    Ridges
+%       Find Ridges or Thin lines
 %    LineEnds
 %       Find end points of lines (for pruning a skeletion)
 %    LineJunctions
@@ -863,7 +865,6 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
     case CornersKernel:    /* Hit and Miss kernels */
     case LineEndsKernel:
     case LineJunctionsKernel:
-    case ThinningKernel:
     case ConvexHullKernel:
     case SkeletonKernel:
       /* A pre-generated kernel is not needed */
@@ -1539,6 +1540,15 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
         break;
       }
+    case RidgesKernel:
+      {
+        kernel=ParseKernelArray("3: -,-,-  0,1,0  -,-,-");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 45.0); /* 4 rotated kernels (symmetrical) */
+        break;
+      }
     case LineEndsKernel:
       {
         KernelInfo
@@ -1595,18 +1605,22 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         LastKernelInfo(kernel)->next = new_kernel;
         break;
       }
-    case ThinningKernel:
-      { /* Thinning Kernel ??  -- filled corner and edge */
+    case SkeletonKernel:
+      { /* what is the best form for medial axis skeletonization? */
+#if 0
+#  if 0
+        kernel=AcquireKernelInfo("Corners;Edges");
+#  else
+        kernel=AcquireKernelInfo("Edges;Corners");
+#  endif
+#else
         kernel=ParseKernelArray("3: 0,0,-  0,1,1  -,1,1");
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
         ExpandKernelInfo(kernel, 45);
         break;
-      }
-    case SkeletonKernel:
-      {
-        kernel=AcquireKernelInfo("Edges;Corners");
+#endif
         break;
       }
     /* Distance Measuring Kernels */
@@ -1809,22 +1823,47 @@ MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
 % especially with regard to non-orthogonal angles, and rotation of larger
 % 2D kernels.
 */
-static void ExpandKernelInfo(KernelInfo *kernel, double angle)
+
+/* Internal Routine - Return true if two kernels are the same */
+static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
+     const KernelInfo *kernel2)
+{
+  register unsigned long
+    i;
+  if ( kernel1->width != kernel2->width )
+    return MagickFalse;
+  if ( kernel1->height != kernel2->height )
+    return MagickFalse;
+  for (i=0; i < (kernel1->width*kernel1->height); i++) {
+    /* Test Nan */
+    if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
+      return MagickFalse;
+    if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
+      return MagickFalse;
+    /* Test actual value */
+    if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
+      return MagickFalse;
+  }
+  return MagickTrue;
+}
+
+static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
 {
   KernelInfo
-    *clone,
+    *new,
     *last;
 
-  double
-    a;
-
   last = kernel;
-  for (a=angle; a<355.0; a+=angle) {
-    clone = CloneKernelInfo(last);
-    RotateKernelInfo(clone, angle);
-    last->next = clone;
-    last = clone;
+  while(1) {
+    new = CloneKernelInfo(last);
+    RotateKernelInfo(new, angle);
+    if ( SameKernelInfo(kernel, new) == MagickTrue )
+      break;
+    last->next = new;
+    last = new;
   }
+  new = DestroyKernelInfo(new); /* This was the same as the first - junk */
+  return;
 }
 \f
 /*
@@ -1907,10 +1946,11 @@ static void CalcKernelMetaData(KernelInfo *kernel)
 %  (-bias setting or image->bias) is passed directly to this function,
 %  and not extracted from an image.
 %
-%  The format of the MorphologyImage method is:
+%  The format of the MorphologyApply method is:
 %
 %      Image *MorphologyApply(const Image *image,MorphologyMethod method,
-%        const long iterations,const KernelInfo *kernel,const double bias,
+%        const long iterations,const KernelInfo *kernel,
+%        const CompositeMethod compose, const double bias,
 %        ExceptionInfo *exception)
 %
 %  A description of each parameter follows:
@@ -1929,7 +1969,13 @@ static void CalcKernelMetaData(KernelInfo *kernel)
 %    o kernel: An array of double representing the morphology kernel.
 %              Warning: kernel may be normalized for the Convolve method.
 %
-%    o bias: Convolution Bias to use.
+%    o compose: How to handle or merge multi-kernel results.
+%               If 'Undefined' use default of the Morphology method.
+%               If 'No' force image to be re-iterated by each kernel.
+%               Otherwise merge the results using the mathematical compose
+%               method given.
+%
+%    o bias: Convolution Output Bias.
 %
 %    o exception: return any errors or warnings in this structure.
 %
@@ -2476,35 +2522,45 @@ static unsigned long MorphologyPrimitive(const Image *image, Image
 
 MagickExport Image *MorphologyApply(const Image *image, const ChannelType
      channel,const MorphologyMethod method, const long iterations,
-     const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
+     const KernelInfo *kernel, const CompositeOperator compose,
+     const double bias, ExceptionInfo *exception)
 {
   Image
-    *curr_image,   /* Image we are working with */
-    *work_image,   /* secondary working image */
-    *save_image;   /* save image for later use */
+    *curr_image,    /* Image we are working with or iterating */
+    *work_image,    /* secondary image for primative iteration */
+    *save_image,    /* saved image - for 'edge' method only */
+    *rslt_image;    /* resultant image - after multi-kernel handling */
 
   KernelInfo
-    *curr_kernel,  /* current kernel list to apply */
-    *this_kernel;  /* current individual kernel to apply */
+    *reflected_kernel, /* A reflected copy of the kernel (if needed) */
+    *norm_kernel,      /* the current normal un-reflected kernel */
+    *rflt_kernel,      /* the current reflected kernel (if needed) */
+    *this_kernel;      /* the kernel being applied */
 
   MorphologyMethod
-    primitive;     /* the current morphology primitive being applied */
-
-  MagickBooleanType
-    verbose;           /* verbose output of results */
+    primative;      /* the current morphology primative being applied */
 
   CompositeOperator
-    kernel_compose;  /* Handling the result of multiple kernels*/
+    rslt_compose;   /* multi-kernel compose method for results to use */
+
+  MagickBooleanType
+    verbose;        /* verbose output of results */
 
   unsigned long
-    count,         /* count of primitive steps applied */
-    loop,          /* number of times though kernel list (iterations) */
-    loop_limit,    /* finish looping after this many times */
-    stage,         /* stage number for compound morphology */
-    changed,       /* number pixels changed by one primitive operation */
-    loop_changed,  /* changes made over loop though of kernels */
-    total_changed, /* total count of all changes to image */
-    kernel_number; /* kernel number being applied */
+    method_loop,    /* Loop 1: number of compound method iterations */
+    method_limit,   /*         maximum number of compound method iterations */
+    kernel_number,  /* Loop 2: the kernel number being applied */
+    stage_loop,     /* Loop 3: primative loop for compound morphology */
+    stage_limit,    /*         how many primatives in this compound */
+    kernel_loop,    /* Loop 4: iterate the kernel (basic morphology) */
+    kernel_limit,   /*         number of times to iterate kernel */
+    count,          /* total count of primative steps applied */
+    changed,        /* number pixels changed by last primative operation */
+    kernel_changed, /* total count of changed using iterated kernel */
+    method_changed; /* total count of changed over method iteration */
+
+  char
+    v_info[80];
 
   assert(image != (Image *) NULL);
   assert(image->signature == MagickSignature);
@@ -2513,144 +2569,196 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
   assert(exception != (ExceptionInfo *) NULL);
   assert(exception->signature == MagickSignature);
 
-  loop_limit = (unsigned long) iterations;
-  if ( iterations < 0 )
-     loop_limit = image->columns > image->rows ? image->columns : image->rows;
   if ( iterations == 0 )
-    return((Image *)NULL); /* null operation - nothing to do! */
+    return((Image *)NULL);   /* null operation - nothing to do! */
 
-  /* kernel must be valid at this point
-   * (except maybe for posible future morphology methods like "Prune"
-   */
-  assert(kernel != (KernelInfo *)NULL);
+  kernel_limit = (unsigned long) iterations;
+  if ( iterations < 0 )  /* negative interations = infinite (well alomst) */
+     kernel_limit = image->columns > image->rows ? image->columns : image->rows;
 
   verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
     MagickTrue : MagickFalse;
 
   /* initialise for cleanup */
-  curr_image = (Image *) image;    /* result of morpholgy primitive */
-  work_image = (Image *) NULL;     /* secondary working image */
-  save_image = (Image *) NULL;     /* save image for some compound methods */
-  curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
+  curr_image = (Image *) image;
+  work_image = save_image = rslt_image = (Image *) NULL;
+  reflected_kernel = (KernelInfo *) NULL;
+
 
-  kernel_compose = NoCompositeOp;  /* iterated over all kernels */
+  count = 0;      /* number of low-level morphology primatives performed */
 
-  /* Select initial primitive morphology to apply */
-  primitive = UndefinedMorphology;
+  /* Initialize specific methods
+   * + which loop should use the given iteratations
+   * + how many primatives make up the compound morphology
+   * + multi-kernel compose method to use (by default)
+   */
+  method_limit = 1;       /* just do method once, unless otherwise set */
+  stage_limit = 1;        /* assume method is not a compount */
+  rslt_compose = compose; /* and we are composing multi-kernels as given */
   switch( method ) {
-    case CorrelateMorphology:
-      /* A Correlation is actually a Convolution with a reflected kernel.
-      ** However a Convolution is a weighted sum using a reflected kernel.
-      ** It may seem stange to convert a Correlation into a Convolution
-      ** as the Correleation is the simplier method, but Convolution is
-      ** much more commonly used, and it makes sense to implement it directly
-      ** so as to avoid the need to duplicate the kernel when it is not
-      ** required (which is typically the default).
-      */
-      curr_kernel = CloneKernelInfo(kernel);
-      if (curr_kernel == (KernelInfo *) NULL)
-        goto error_cleanup;
-      RotateKernelInfo(curr_kernel,180);
-      /* FALL THRU to Convolve */
-    case ConvolveMorphology:
-      primitive = ConvolveMorphology;
-      kernel_compose = NoCompositeOp;
+    case SmoothMorphology:  /* 4 primative compound morphology */
+      stage_limit = 4;
       break;
-    case ErodeMorphology:      /* just erode */
-    case OpenMorphology:       /* erode then dialate */
-    case EdgeInMorphology:     /* erode and image difference */
-    case TopHatMorphology:     /* erode, dilate and image difference */
-    case SmoothMorphology:     /* erode, dilate, dilate, erode */
-      primitive = ErodeMorphology;
-      break;
-    case ErodeIntensityMorphology:
+    case OpenMorphology:    /* 2 primative compound morphology */
     case OpenIntensityMorphology:
-      primitive = ErodeIntensityMorphology;
-      break;
-    case DilateMorphology:     /* just dilate */
-    case EdgeOutMorphology:    /* dilate and image difference */
-    case EdgeMorphology:       /* dilate and erode difference */
-      primitive = DilateMorphology;
-      break;
-    case CloseMorphology:      /* dilate, then erode */
-    case BottomHatMorphology:  /* dilate and image difference */
-      curr_kernel = CloneKernelInfo(kernel);
-      if (curr_kernel == (KernelInfo *) NULL)
-        goto error_cleanup;
-      RotateKernelInfo(curr_kernel,180);
-      primitive = DilateMorphology;
-      break;
-    case DilateIntensityMorphology:
+    case TopHatMorphology:
+    case CloseMorphology:
     case CloseIntensityMorphology:
-      curr_kernel = CloneKernelInfo(kernel);
-      if (curr_kernel == (KernelInfo *) NULL)
-        goto error_cleanup;
-      RotateKernelInfo(curr_kernel,180);
-      primitive = DilateIntensityMorphology;
+    case BottomHatMorphology:
+    case EdgeMorphology:
+      stage_limit = 2;
       break;
     case HitAndMissMorphology:
-      primitive = HitAndMissMorphology;
-      loop_limit = 1;                       /* iterate only once */
-      kernel_compose = LightenCompositeOp;  /* Union of Hit-And-Miss */
+      kernel_limit = 1;            /* Only apply each kernel once to image */
+      rslt_compose = LightenCompositeOp;  /* Union of multi-kernel results */
       break;
-    case ThinningMorphology:     /* iterative morphology */
+    case ThinningMorphology:  /* don't interate each kernel, iterate method */
     case ThickenMorphology:
-    case DistanceMorphology:     /* Distance should never use multple kernels */
-    case UndefinedMorphology:
-      primitive = method;
+      method_limit = kernel_limit; /* iterate method with each kernel */
+      kernel_limit = 1;            /* do not do kernel iteration  */
+      break;
+    default:
       break;
   }
 
-#if 0
-  { /* User override of results handling  -- Experimental */
-    const char
-       *artifact = GetImageArtifact(image,"morphology:style");
-    if ( artifact != (const char *) NULL ) {
-      if (LocaleCompare("union",artifact) == 0)
-        kernel_compose = LightenCompositeOp;
-      if (LocaleCompare("iterate",artifact) == 0)
-        kernel_compose = NoCompositeOp;
-      else
-        kernel_compose = (CompositeOperator) ParseMagickOption(
-                                 MagickComposeOptions,MagickFalse,artifact);
-      if ( kernel_compose == UndefinedCompositeOp )
-        perror("Invalid \"morphology:compose\" setting\n");
-    }
-  }
-#endif
-
-  /* Initialize compound morphology stages  */
-  count = 0;          /* number of low-level morphology primitives performed */
-  total_changed = 0;  /* total number of pixels changed thoughout */
-  stage = 1;          /* the compound morphology stage number */
+  if ( compose != UndefinedCompositeOp )
+    rslt_compose = compose;  /* override default composition for method */
+  if ( rslt_compose == UndefinedCompositeOp )
+    rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
 
-  /* compount morphology staging loop */
-  while ( 1 ) {
+  /* Some methods require a refltected kernel to use with primatives
+   * create the reflected kernel for the methods that need it */
+  switch ( method ) {
+    case CorrelateMorphology:
+    case CloseMorphology:
+    case CloseIntensityMorphology:
+    case BottomHatMorphology:
+    case SmoothMorphology:
+      reflected_kernel = CloneKernelInfo(kernel);
+      if (reflected_kernel == (KernelInfo *) NULL)
+        goto error_cleanup;
+      RotateKernelInfo(reflected_kernel,180);
+      break;
+    default:
+      break;
+  }
 
-#if 1
-    /* Extra information for debugging compound operations */
-    if ( verbose == MagickTrue && primitive != method )
-      fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
-        MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
-        MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
-        ( curr_kernel == kernel) ? "" : "*",
-        ( kernel_compose == NoCompositeOp ) ? "iterate"
-          : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
-#endif
+  /* Loop 1:  iterate the compound method */
+  method_loop = 0;
+  method_changed = 1;
+  while ( method_loop < method_limit && method_changed > 0 ) {
+    method_loop++;
+    method_changed = 0;
+
+    /* Loop 2:  iterate over each kernel in a multi-kernel list */
+    norm_kernel = (KernelInfo *) kernel;
+    rflt_kernel = reflected_kernel;
+    kernel_number = 0;
+    while ( norm_kernel != NULL ) {
+
+      /* Loop 3: Compound Morphology Staging - Select Primative to apply */
+      stage_loop = 0;          /* the compound morphology stage number */
+      while ( stage_loop < stage_limit ) {
+        stage_loop++;   /* The stage of the compound morphology */
+
+        /* Select primative morphology for this stage of compound method */
+        this_kernel = norm_kernel; /* default use unreflected kernel */
+        switch( method ) {
+          case ErodeMorphology:      /* just erode */
+          case EdgeInMorphology:     /* erode and image difference */
+            primative = ErodeMorphology;
+            break;
+          case DilateMorphology:     /* just dilate */
+          case EdgeOutMorphology:    /* dilate and image difference */
+            primative = DilateMorphology;
+            break;
+          case OpenMorphology:       /* erode then dialate */
+          case TopHatMorphology:     /* open and image difference */
+            primative = ErodeMorphology;
+            if ( stage_loop == 2 )
+              primative = DilateMorphology;
+            break;
+          case OpenIntensityMorphology:
+            primative = ErodeIntensityMorphology;
+            if ( stage_loop == 2 )
+              primative = DilateIntensityMorphology;
+          case CloseMorphology:      /* dilate, then erode */
+          case BottomHatMorphology:  /* close and image difference */
+            this_kernel = rflt_kernel; /* use the reflected kernel */
+            primative = DilateMorphology;
+            if ( stage_loop == 2 )
+              primative = ErodeMorphology;
+            break;
+          case CloseIntensityMorphology:
+            this_kernel = rflt_kernel; /* use the reflected kernel */
+            primative = DilateIntensityMorphology;
+            if ( stage_loop == 2 )
+              primative = ErodeIntensityMorphology;
+            break;
+          case SmoothMorphology:         /* open, close */
+            switch ( stage_loop ) {
+              case 1: /* start an open method, which starts with Erode */
+                primative = ErodeMorphology;
+                break;
+              case 2:  /* now Dilate the Erode */
+                primative = DilateMorphology;
+                break;
+              case 3:  /* Reflect kernel a close */
+                this_kernel = rflt_kernel; /* use the reflected kernel */
+                primative = DilateMorphology;
+                break;
+              case 4:  /* Finish the Close */
+                this_kernel = rflt_kernel; /* use the reflected kernel */
+                primative = ErodeMorphology;
+                break;
+            }
+            break;
+          case EdgeMorphology:        /* dilate and erode difference */
+            primative = DilateMorphology;
+            if ( stage_loop == 2 ) {
+              save_image = curr_image;      /* save the image difference */
+              curr_image = (Image *) image;
+              primative = ErodeMorphology;
+            }
+            break;
+          case CorrelateMorphology:
+            /* A Correlation is a Convolution with a reflected kernel.
+            ** However a Convolution is a weighted sum using a reflected
+            ** kernel.  It may seem stange to convert a Correlation into a
+            ** Convolution as the Correlation is the simplier method, but
+            ** Convolution is much more commonly used, and it makes sense to
+            ** implement it directly so as to avoid the need to duplicate the
+            ** kernel when it is not required (which is typically the
+            ** default).
+            */
+            this_kernel = rflt_kernel; /* use the reflected kernel */
+            primative = ConvolveMorphology;
+            break;
+          default:
+            primative = method;       /* method is a primative */
+            break;
+        }
 
-    if ( kernel_compose == NoCompositeOp ) {
-      /******************************
-       ** Iterate over all Kernels **
-       ******************************/
-      loop = 0;
-      loop_changed = 1;
-      while ( loop < loop_limit && loop_changed > 0 ) {
-        loop++;       /* the iteration of this kernel */
+        /* Extra information for debugging compound operations */
+        if ( verbose == MagickTrue ) {
+          if ( stage_limit > 1 )
+            sprintf(v_info, "%s:%lu.%lu -> ",
+                 MagickOptionToMnemonic(MagickMorphologyOptions, method),
+                 method_loop, stage_loop );
+          else if ( primative != method )
+            sprintf(v_info, "%s:%lu -> ",
+                 MagickOptionToMnemonic(MagickMorphologyOptions, method),
+                 method_loop );
+          else
+            v_info[0] = '\0';
+        }
 
-        loop_changed = 0;
-        this_kernel = curr_kernel;
-        kernel_number = 0;
-        while ( this_kernel != NULL ) {
+        /* Loop 4: Iterate the kernel with primative */
+        kernel_loop = 0;
+        kernel_changed = 0;
+        changed = 1;
+        while ( kernel_loop < kernel_limit && changed > 0 ) {
+          kernel_loop++;     /* the iteration of this kernel */
 
           /* Create a destination image, if not yet defined */
           if ( work_image == (Image *) NULL )
@@ -2665,237 +2773,143 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
                 }
             }
 
-          /* morphological primitive  curr -> work */
+          /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
           count++;
-          changed = MorphologyPrimitive(curr_image, work_image, primitive,
+          changed = MorphologyPrimitive(curr_image, work_image, primative,
                         channel, this_kernel, bias, exception);
-          loop_changed += changed;
-          total_changed += changed;
-
-          if ( verbose == MagickTrue )
-            fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
-                MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
-                loop, kernel_number, count, changed);
-
+          kernel_changed += changed;
+          method_changed += changed;
+
+          if ( verbose == MagickTrue ) {
+            if ( kernel_loop > 1 )
+              fprintf(stderr, "\n"); /* add end-of-line from previous */
+            fprintf(stderr, "%s%s%s:%lu.%lu #%lu => Changed %lu", v_info,
+                MagickOptionToMnemonic(MagickMorphologyOptions, primative),
+                 ( this_kernel == rflt_kernel ) ? "*" : "",
+               method_loop+kernel_loop-1, kernel_number, count, changed);
+          }
           /* prepare next loop */
           { Image *tmp = work_image;   /* swap images for iteration */
             work_image = curr_image;
             curr_image = tmp;
           }
           if ( work_image == image )
-            work_image = (Image *) NULL;  /* never assign image to 'work' */
-          this_kernel = this_kernel->next;  /* prepare next kernel (if any) */
-          kernel_number++;
-        }
+            work_image = (Image *) NULL; /* replace input 'image' */
 
-        if ( verbose == MagickTrue && kernel->next != NULL )
-          fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu   Total %lu\n",
-                MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
-                loop, count, loop_changed, total_changed );
-      }
-    }
+        } /* End Loop 4: Iterate the kernel with primative */
 
-    else {
-      /*************************************
-       ** Composition of Iterated Kernels **
-       *************************************/
-      Image
-        *input_image,  /* starting point for kernels */
-        *union_image;
-      input_image = curr_image;
-      union_image = (Image *) NULL;
-
-      this_kernel = curr_kernel;
-      kernel_number = 0;
-      while ( this_kernel != NULL ) {
-
-        if( curr_image != (Image *) NULL && curr_image != input_image )
-          curr_image=DestroyImage(curr_image);
-        curr_image = input_image;  /* always start with the original image */
-
-        loop = 0;
-        changed = 1;
-        loop_changed = 0;
-        while ( loop < loop_limit && changed > 0 ) {
-          loop++;       /* the iteration of this kernel */
+        if ( verbose == MagickTrue && kernel_changed != changed )
+          fprintf(stderr, "   Total %lu", kernel_changed);
+        if ( verbose == MagickTrue && stage_loop < stage_limit )
+          fprintf(stderr, "\n"); /* add end-of-line before looping */
 
-          /* Create a destination image, if not defined */
-          if ( work_image == (Image *) NULL )
-            {
-              work_image=CloneImage(image,0,0,MagickTrue,exception);
-              if (work_image == (Image *) NULL)
-                goto error_cleanup;
-              if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
-                {
-                  InheritException(exception,&curr_image->exception);
-                  if( union_image != (Image *) NULL )
-                    union_image=DestroyImage(union_image);
-                  if( curr_image != input_image )
-                    curr_image = DestroyImage(curr_image);
-                  curr_image = (Image *) input_image;
-                  goto error_cleanup;
-                }
-            }
+#if 0
+    fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
+    fprintf(stderr, "      curr =0x%lx\n", (unsigned long)curr_image);
+    fprintf(stderr, "      work =0x%lx\n", (unsigned long)work_image);
+    fprintf(stderr, "      save =0x%lx\n", (unsigned long)save_image);
+    fprintf(stderr, "      union=0x%lx\n", (unsigned long)rslt_image);
+#endif
 
-          /* morphological primitive  curr -> work */
-          count++;
-          changed = MorphologyPrimitive(curr_image,work_image,primitive,
-                        channel, this_kernel, bias, exception);
-          loop_changed += changed;
-          total_changed += changed;
+      } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
 
+      /*  Final Post-processing for some Compound Methods
+      **
+      ** The removal of any 'Sync' channel flag in the Image Compositon
+      ** below ensures the methematical compose method is applied in a
+      ** purely mathematical way, and only to the selected channels.
+      ** Turn off SVG composition 'alpha blending'.
+      */
+      switch( method ) {
+        case EdgeOutMorphology:
+        case EdgeInMorphology:
+        case TopHatMorphology:
+        case BottomHatMorphology:
           if ( verbose == MagickTrue )
-            fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
-                MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
-                loop, kernel_number, count, changed);
-
-          /* prepare next loop */
-          { Image *tmp = work_image;   /* swap images for iteration */
-            work_image = curr_image;   /* curr_image is now the results */
-            curr_image = tmp;
-          }
-          if ( work_image == input_image )
-            work_image = (Image *) NULL;  /* clear work of the input_image */
-
-        } /* end kernel iteration */
-
-        /* make a union of the iterated kernel */
-        if ( union_image == (Image *) NULL)   /* start the union? */
-          union_image = curr_image, curr_image = (Image *)NULL;
-        else
-          (void) CompositeImageChannel(union_image,
-            (ChannelType) (channel & ~SyncChannels), kernel_compose,
-            curr_image, 0, 0);
-
-        this_kernel = this_kernel->next;  /* next kernel (if any) */
-        kernel_number++;
+            fprintf(stderr, "\n%s: Difference with original image",
+                 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
+          (void) CompositeImageChannel(curr_image,
+                  (ChannelType) (channel & ~SyncChannels),
+                  DifferenceCompositeOp, image, 0, 0);
+          break;
+        case EdgeMorphology:
+          if ( verbose == MagickTrue )
+            fprintf(stderr, "\n%s: Difference of Dilate and Erode",
+                 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
+          (void) CompositeImageChannel(curr_image,
+                  (ChannelType) (channel & ~SyncChannels),
+                  DifferenceCompositeOp, save_image, 0, 0);
+          save_image = DestroyImage(save_image); /* finished with save image */
+          break;
+        default:
+          break;
       }
 
-      if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
-        fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu   Total %lu\n",
-              MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
-              loop, count, loop_changed, total_changed );
-
-#if 0
-fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
-fprintf(stderr, "      input=0x%lx\n", (unsigned long)input_image);
-fprintf(stderr, "      union=0x%lx\n", (unsigned long)union_image);
-fprintf(stderr, "      curr =0x%lx\n", (unsigned long)curr_image);
-fprintf(stderr, "      work =0x%lx\n", (unsigned long)work_image);
-fprintf(stderr, "      save =0x%lx\n", (unsigned long)save_image);
-#endif
-
-      /* Finish up - return the union of results */
-      if( curr_image != (Image *) NULL && curr_image != input_image )
-          curr_image=DestroyImage(curr_image);
-      if( input_image != input_image )
-        input_image = DestroyImage(input_image);
-      curr_image = union_image;
-    }
-
-    /* Compound Morphology Operations
-     *   set next 'primitive' iteration, and continue
-     *   or break when all operations are complete.
-     */
-    stage++;   /* what is the next stage number to do */
-    switch( method ) {
-      case SmoothMorphology:           /* open, close */
-        switch ( stage ) {
-        /* case 1:  initialized above */
-        case 2:  /* open part 2 */
-          primitive = DilateMorphology;
-          continue;
-        case 3:  /* close part 1 */
-          curr_kernel = CloneKernelInfo(kernel);
-          if (curr_kernel == (KernelInfo *) NULL)
-            goto error_cleanup;
-          RotateKernelInfo(curr_kernel,180);
-          continue;
-        case 4:  /* close part 2 */
-          primitive = ErodeMorphology;
-          continue;
+      /* multi-kernel handling:  re-iterate, or compose results */
+      if ( kernel->next == (KernelInfo *) NULL )
+        rslt_image = curr_image;   /* not multi-kernel nothing to do */
+      else if ( rslt_compose == NoCompositeOp )
+        { if ( verbose == MagickTrue )
+            fprintf(stderr, " (re-iterate)");
+          rslt_image = curr_image;
+          /* original image is no longer actually needed! */
         }
-        break;
-      case OpenMorphology:      /* erode, dilate */
-      case TopHatMorphology:
-        primitive = DilateMorphology;
-        if ( stage <= 2 ) continue;
-        break;
-      case OpenIntensityMorphology:
-        primitive = DilateIntensityMorphology;
-        if ( stage <= 2 ) continue;
-        break;
-      case CloseMorphology:       /* dilate, erode */
-      case BottomHatMorphology:
-        primitive = ErodeMorphology;
-        if ( stage <= 2 ) continue;
-        break;
-      case CloseIntensityMorphology:
-        primitive = ErodeIntensityMorphology;
-        if ( stage <= 2 ) continue;
-        break;
-      case EdgeMorphology:        /* dilate and erode difference */
-        if (stage <= 2) {
-          save_image = curr_image;
-          curr_image = (Image *) image;
-          primitive = ErodeMorphology;
-          continue;
+      else if ( rslt_image == (Image *) NULL)
+        { if ( verbose == MagickTrue )
+            fprintf(stderr, " (save for compose)");
+          rslt_image = curr_image;
+          curr_image = (Image *) image;  /* continue with original image */
         }
-        break;
-      default:  /* Primitive Morphology is just finished! */
-        break;
-    }
-
-    if ( verbose == MagickTrue && count > 1 )
-      fprintf(stderr, "Morphology %s: ======> Total %lu\n",
-           MagickOptionToMnemonic(MagickMorphologyOptions, method),
-           total_changed );
+      else
+        { /* add the new 'current' result to the composition
+          **
+          ** The removal of any 'Sync' channel flag in the Image Compositon
+          ** below ensures the methematical compose method is applied in a
+          ** purely mathematical way, and only to the selected channels.
+          ** Turn off SVG composition 'alpha blending'.
+          */
+          if ( verbose == MagickTrue )
+            fprintf(stderr, " (compose \"%s\")",
+                 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
+          (void) CompositeImageChannel(rslt_image,
+               (ChannelType) (channel & ~SyncChannels), rslt_compose,
+               curr_image, 0, 0);
+          curr_image = (Image *) image;  /* continue with original image */
+        }
+      if ( verbose == MagickTrue )
+        fprintf(stderr, "\n");
 
-    /* If we reach this point we are finished! - Break the Loop */
-    break;
-  }
+      /* loop to the next kernel in a multi-kernel list */
+      norm_kernel = norm_kernel->next;
+      if ( rflt_kernel != (KernelInfo *) NULL )
+        rflt_kernel = rflt_kernel->next;
+      kernel_number++;
+    } /* End Loop 2: Loop over each kernel */
 
-  /*  Final Post-processing for some Compound Methods
-  **
-  ** The removal of any 'Sync' channel flag in the Image Compositon below
-  ** ensures the compose method is applied in a purely mathematical way, only
-  ** the selected channels, without any normal 'alpha blending' normally
-  ** associated with the compose method.
-  **
-  ** Note "method" here is the 'original' morphological method, and not the
-  ** 'current' morphological method used above to generate "new_image".
-  */
-  switch( method ) {
-    case EdgeOutMorphology:
-    case EdgeInMorphology:
-    case TopHatMorphology:
-    case BottomHatMorphology:
-      (void) CompositeImageChannel(curr_image,
-               (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
-               image, 0, 0);
-      break;
-    case EdgeMorphology:
-      /* Difference the Eroded Image with a Dilate image */
-      (void) CompositeImageChannel(curr_image,
-               (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
-               save_image, 0, 0);
-      break;
-    default:
-      break;
-  }
+  } /* End Loop 1: compound method interation */
 
   goto exit_cleanup;
 
-  /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
+  /* Yes goto's are bad, but it makes cleanup lot more efficient */
 error_cleanup:
-  if ( curr_image != (Image *) NULL && curr_image != image )
-    (void) DestroyImage(curr_image);
+  if ( curr_image != (Image *) NULL &&
+       curr_image != rslt_image &&
+       curr_image != image )
+    curr_image = DestroyImage(curr_image);
+  if ( rslt_image != (Image *) NULL )
+    rslt_image = DestroyImage(rslt_image);
 exit_cleanup:
+  if ( curr_image != (Image *) NULL &&
+       curr_image != rslt_image &&
+       curr_image != image )
+    curr_image = DestroyImage(curr_image);
   if ( work_image != (Image *) NULL )
-    (void) DestroyImage(work_image);
+    work_image = DestroyImage(work_image);
   if ( save_image != (Image *) NULL )
-    (void) DestroyImage(save_image);
-  return(curr_image);
+    save_image = DestroyImage(save_image);
+  if ( reflected_kernel != (KernelInfo *) NULL )
+    reflected_kernel = DestroyKernelInfo(reflected_kernel);
+  return(rslt_image);
 }
 \f
 /*
@@ -2960,6 +2974,9 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
   KernelInfo
     *curr_kernel;
 
+  CompositeOperator
+    compose;
+
   Image
     *morphology_image;
 
@@ -2985,12 +3002,27 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
 
   /* display the (normalized) kernel via stderr */
   artifact = GetImageArtifact(image,"showkernel");
+  if ( artifact == (const char *) NULL)
+    artifact = GetImageArtifact(image,"convolve:showkernel");
+  if ( artifact == (const char *) NULL)
+    artifact = GetImageArtifact(image,"morphology:showkernel");
   if ( artifact != (const char *) NULL)
     ShowKernelInfo(curr_kernel);
 
+  /* override the default handling of multi-kernel morphology results
+   * if 'Undefined' use the default method
+   * if 'None' (default for 'Convolve') re-iterate previous result
+   * otherwise merge resulting images using compose method given
+   */
+  compose = UndefinedCompositeOp;  /* use default for method */
+  artifact = GetImageArtifact(image,"morphology:compose");
+  if ( artifact != (const char *) NULL)
+    compose = (CompositeOperator) ParseMagickOption(
+                             MagickComposeOptions,MagickFalse,artifact);
+
   /* Apply the Morphology */
   morphology_image = MorphologyApply(image, channel, method, iterations,
-                         curr_kernel, image->bias, exception);
+                         curr_kernel, compose, image->bias, exception);
 
   /* Cleanup and Exit */
   if ( curr_kernel != kernel )
@@ -2998,7 +3030,6 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
   return(morphology_image);
 }
 
-
 MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
   method, const long iterations,const KernelInfo *kernel, ExceptionInfo
   *exception)
index af6b924375c7e5df5cf44a64116f04abcfad79b6..b2753815c58437ee1a7dc9eb03e63ae40ac2023e 100644 (file)
@@ -51,9 +51,9 @@ typedef enum
   PeaksKernel,         /* Hit And Miss Kernels */
   EdgesKernel,
   CornersKernel,
+  RidgesKernel,
   LineEndsKernel,
   LineJunctionsKernel,
-  ThinningKernel,
   ConvexHullKernel,
   SkeletonKernel,
   ChebyshevKernel,    /* Distance Measuring Kernels */
index 493431213eae450dd3ccf958ee67b2c869cc5e4a..b67fcbe7056b031224897ca10ad0523461663fed 100644 (file)
@@ -1077,9 +1077,9 @@ static const OptionInfo
     { "Peaks", (long) PeaksKernel,MagickFalse },
     { "Edges", (long) EdgesKernel,MagickFalse },
     { "Corners", (long) CornersKernel,MagickFalse },
+    { "Ridges", (long) RidgesKernel,MagickFalse },
     { "LineEnds", (long) LineEndsKernel,MagickFalse },
     { "LineJunctions", (long) LineJunctionsKernel,MagickFalse },
-    { "Thinning", (long) ThinningKernel,MagickTrue },     /* under trail */
     { "ConvexHull", (long) ConvexHullKernel,MagickFalse },
     { "Skeleton", (long) SkeletonKernel,MagickFalse },
     { "Chebyshev", (long) ChebyshevKernel, MagickFalse },