]> granicus.if.org Git - imagemagick/blobdiff - magick/morphology.c
(no commit message)
[imagemagick] / magick / morphology.c
index 99737d771d49485bc35cd5b99a57a3279212fe4e..982325a75b4a1348c5ee160a935c43b2a564bf35 100644 (file)
@@ -112,7 +112,8 @@ static inline double MagickMax(const double x,const double y)
 /* Currently these are only internal to this module */
 static void
   CalcKernelMetaData(KernelInfo *),
-  ExpandKernelInfo(KernelInfo *, const double),
+  ExpandMirrorKernelInfo(KernelInfo *),
+  ExpandRotateKernelInfo(KernelInfo *, const double),
   RotateKernelInfo(KernelInfo *, double);
 \f
 
@@ -163,21 +164,17 @@ static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
 %
 %  Input kernel defintion strings can consist of any of three types.
 %
-%    "name:args"
+%    "name:args[[@><]"
 %         Select from one of the built in kernels, using the name and
 %         geometry arguments supplied.  See AcquireKernelBuiltIn()
 %
-%    "WxH[+X+Y][^@]:num, num, num ..."
+%    "WxH[+X+Y][@><]:num, num, num ..."
 %         a kernel of size W by H, with W*H floating point numbers following.
 %         the 'center' can be optionally be defined at +X+Y (such that +0+0
 %         is top left corner). If not defined the pixel in the center, for
 %         odd sizes, or to the immediate top or left of center for even sizes
 %         is automatically selected.
 %
-%         If a '^' is included the kernel expanded with 90-degree rotations,
-%         While a '@' will allow you to expand a 3x3 kernel using 45-degree
-%         circular rotates.
-%
 %    "num, num, num, num, ..."
 %         list of floating point numbers defining an 'old style' odd sized
 %         square kernel.  At least 9 values should be provided for a 3x3
@@ -192,6 +189,13 @@ static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
 %  Any extra ';' characters, at start, end or between kernel defintions are
 %  simply ignored.
 %
+%  The special flags will expand a single kernel, into a list of rotated
+%  kernels. A '@' flag will expand a 3x3 kernel into a list of 45-degree
+%  cyclic rotations, while a '>' will generate a list of 90-degree rotations.
+%  The '<' also exands using 90-degree rotates, but giving a 180-degree
+%  reflected kernel before the +/- 90-degree rotations, which can be important
+%  for Thinning operations.
+%
 %  Note that 'name' kernels will start with an alphabetic character while the
 %  new kernel specification has a ':' character in its specification string.
 %  If neither is the case, it is assumed an old style of a simple list of
@@ -358,9 +362,11 @@ static KernelInfo *ParseKernelArray(const char *kernel_string)
     return(DestroyKernelInfo(kernel));
 
   if ( (flags & AreaValue) != 0 )         /* '@' symbol in kernel size */
-    ExpandKernelInfo(kernel, 45.0);
-  else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel size */
-    ExpandKernelInfo(kernel, 90.0);
+    ExpandRotateKernelInfo(kernel, 45.0); /* cyclic rotate 3x3 kernels */
+  else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
+    ExpandRotateKernelInfo(kernel, 90.0); /* 90 degree rotate of kernel */
+  else if ( (flags & LessValue) != 0 )    /* '<' symbol in kernel args */
+    ExpandMirrorKernelInfo(kernel);       /* 90 degree mirror rotate */
 
   return(kernel);
 }
@@ -440,7 +446,7 @@ static KernelInfo *ParseKernelName(const char *kernel_string)
         args.xi = 1.0;
       break;
     case ChebyshevKernel:
-    case ManhattenKernel:
+    case ManhattanKernel:
     case EuclideanKernel:
       if ( (flags & HeightValue) == 0 )           /* no distance scale */
         args.sigma = 100.0;                       /* default distance scaling */
@@ -458,9 +464,11 @@ static KernelInfo *ParseKernelName(const char *kernel_string)
   /* global expand to rotated kernel list - only for single kernels */
   if ( kernel->next == (KernelInfo *) NULL ) {
     if ( (flags & AreaValue) != 0 )         /* '@' symbol in kernel args */
-      ExpandKernelInfo(kernel, 45.0);
-    else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel args */
-      ExpandKernelInfo(kernel, 90.0);
+      ExpandRotateKernelInfo(kernel, 45.0);
+    else if ( (flags & GreaterValue) != 0 ) /* '>' symbol in kernel args */
+      ExpandRotateKernelInfo(kernel, 90.0);
+    else if ( (flags & LessValue) != 0 )    /* '<' symbol in kernel args */
+      ExpandMirrorKernelInfo(kernel);
   }
 
   return(kernel);
@@ -499,7 +507,7 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 
       /* Error handling -- this is not proper error handling! */
       if ( new_kernel == (KernelInfo *) NULL ) {
-        fprintf(stderr, "Failed to parse kernel number #%lu\n",(unsigned long)
+        fprintf(stderr, "Failed to parse kernel number #%.20g\n",(double)
           kernel_number);
         if ( kernel != (KernelInfo *) NULL )
           kernel=DestroyKernelInfo(kernel);
@@ -640,39 +648,71 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %
 %    Sobel:{angle}
 %      Sobel 'Edge' convolution kernel (3x3)
-%           -1, 0, 1
-%           -2, 0,-2
-%           -1, 0, 1
+%          | -1, 0, 1 |
+%          | -2, 0,-2 |
+%          | -1, 0, 1 |
+%
+%    Sobel:{type},{angle}
+%      Type 0:  default un-nomalized version shown above.
+%
+%      Type 1:  As default but pre-normalized
+%          | 1, 0, -1 |
+%          | 2, 0, -2 |  / 4
+%          | 1, 0, -1 |
+%
+%      Type 2:  Diagonal version with same normalization as 1
+%          | 1, 0, -1 |
+%          | 2, 0, -2 |  / 4
+%          | 1, 0, -1 |
 %
 %    Roberts:{angle}
 %      Roberts convolution kernel (3x3)
-%            0, 0, 0
-%           -1, 1, 0
-%            0, 0, 0
+%          |  0, 0, 0 |
+%          | -1, 1, 0 |
+%          |  0, 0, 0 |
+%
 %    Prewitt:{angle}
 %      Prewitt Edge convolution kernel (3x3)
-%           -1, 0, 1
-%           -1, 0, 1
-%           -1, 0, 1
+%          | -1, 0, 1 |
+%          | -1, 0, 1 |
+%          | -1, 0, 1 |
+%
 %    Compass:{angle}
 %      Prewitt's "Compass" convolution kernel (3x3)
-%           -1, 1, 1
-%           -1,-2, 1
-%           -1, 1, 1
+%          | -1, 1, 1 |
+%          | -1,-2, 1 |
+%          | -1, 1, 1 |
+%
 %    Kirsch:{angle}
 %      Kirsch's "Compass" convolution kernel (3x3)
-%           -3,-3, 5
-%           -3, 0, 5
-%           -3,-3, 5
+%          | -3,-3, 5 |
+%          | -3, 0, 5 |
+%          | -3,-3, 5 |
 %
-%    FreiChen:{type},{angle}
+%    FreiChen:{angle}
 %      Frei-Chen Edge Detector is based on a kernel that is similar to
 %      the Sobel Kernel, but is designed to be isotropic. That is it takes
 %      into account the distance of the diagonal in the kernel.
 %
-%        Type 0: |   1,     0,   -1     |
-%                | sqrt(2), 0, -sqrt(2) |
-%                |   1,     0,   -1     |
+%          |   1,     0,   -1     |
+%          | sqrt(2), 0, -sqrt(2) |
+%          |   1,     0,   -1     |
+%
+%    FreiChen:{type},{angle}
+%
+%      Frei-Chen Pre-weighted kernels...
+%
+%        Type 0:  default un-nomalized version shown above.
+%
+%        Type 1: Orthogonal Kernel (same as type 11 below)
+%          |   1,     0,   -1     |
+%          | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
+%          |   1,     0,   -1     |
+%
+%        Type 2: Diagonal form of Kernel...
+%          |   1,     sqrt(2),    0     |
+%          | sqrt(2),   0,     -sqrt(2) | / 2*sqrt(2)
+%          |   0,    -sqrt(2)    -1     |
 %
 %      However this kernel is als at the heart of the FreiChen Edge Detection
 %      Process which uses a set of 9 specially weighted kernel.  These 9
@@ -683,41 +723,43 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %      from each other, both the direction and the strength of the edge can be
 %      determined.
 %
-%        Type 1: |   1,     0,   -1     |
-%                | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
-%                |   1,     0,   -1     |
+%        Type 10: All 9 of the following pre-weighted kernels...
+%
+%        Type 11: |   1,     0,   -1     |
+%                 | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
+%                 |   1,     0,   -1     |
 %
-%        Type 2: | 1, sqrt(2), 1 |
-%                | 0,   0,     0 | / 2*sqrt(2)
-%                | 1, sqrt(2), 1 |
+%        Type 12: | 1, sqrt(2), 1 |
+%                 | 0,   0,     0 | / 2*sqrt(2)
+%                 | 1, sqrt(2), 1 |
 %
-%        Type 3: | sqrt(2), -1,    0     |
-%                |  -1,      0,    1     | / 2*sqrt(2)
-%                |   0,      1, -sqrt(2) |
+%        Type 13: | sqrt(2), -1,    0     |
+%                 |  -1,      0,    1     | / 2*sqrt(2)
+%                 |   0,      1, -sqrt(2) |
 %
-%        Type 4: |    0,     1, -sqrt(2) |
-%                |   -1,     0,     1    | / 2*sqrt(2)
-%                | sqrt(2), -1,     0    |
+%        Type 14: |    0,     1, -sqrt(2) |
+%                 |   -1,     0,     1    | / 2*sqrt(2)
+%                 | sqrt(2), -1,     0    |
 %
-%        Type 5: | 0, -1, 0 |
-%                | 1,  0, 1 | / 2
-%                | 0, -1, 0 |
+%        Type 15: | 0, -1, 0 |
+%                 | 1,  0, 1 | / 2
+%                 | 0, -1, 0 |
 %
-%        Type 6: |  1, 0, -1 |
-%                |  0, 0,  0 | / 2
-%                | -1, 0,  1 |
+%        Type 16: |  1, 0, -1 |
+%                 |  0, 0,  0 | / 2
+%                 | -1, 0,  1 |
 %
-%        Type 7: |  1, -2,  1 |
-%                | -2,  4, -2 | / 6
-%                | -1, -2,  1 |
+%        Type 17: |  1, -2,  1 |
+%                 | -2,  4, -2 | / 6
+%                 | -1, -2,  1 |
 %
-%        Type 8: | -2, 1, -2 |
-%                |  1, 4,  1 | / 6
-%                | -2, 1, -2 |
+%        Type 18: | -2, 1, -2 |
+%                 |  1, 4,  1 | / 6
+%                 | -2, 1, -2 |
 %
-%        Type 9: | 1, 1, 1 |
-%                | 1, 1, 1 | / 3
-%                | 1, 1, 1 |
+%        Type 19: | 1, 1, 1 |
+%                 | 1, 1, 1 | / 3
+%                 | 1, 1, 1 |
 %
 %      The first 4 are for edge detection, the next 4 are for line detection
 %      and the last is to add a average component to the results.
@@ -808,23 +850,35 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       Find any peak larger than the pixels the fall between the two radii.
 %       The default ring of pixels is as per "Ring".
 %    Edges
-%       Find edges of a binary shape
+%       Find flat orthogonal edges of a binary shape
 %    Corners
-%       Find corners of a binary shape
-%    Ridges
-%       Find single pixel ridges or thin lines
-%    Ridges2
-%       Find 2 pixel thick ridges or lines
-%    Ridges3
-%       Find 2 pixel thick diagonal ridges (experimental)
-%    LineEnds
+%       Find 90 degree corners of a binary shape
+%    LineEnds:type
 %       Find end points of lines (for pruning a skeletion)
+%       Two types of lines ends (default to both) can be searched for
+%         Type 0: All line ends
+%         Type 1: single kernel for 4-conneected line ends
+%         Type 2: single kernel for simple line ends
 %    LineJunctions
 %       Find three line junctions (within a skeletion)
+%         Type 0: all line junctions
+%         Type 1: Y Junction kernel
+%         Type 2: Diagonal T Junction kernel
+%         Type 3: Orthogonal T Junction kernel
+%         Type 4: Diagonal X Junction kernel
+%         Type 5: Orthogonal + Junction kernel
+%    Ridges:type
+%       Find single pixel ridges or thin lines
+%         Type 1: Fine single pixel thick lines and ridges
+%         Type 2: Find two pixel thick lines and ridges
 %    ConvexHull
 %       Octagonal thicken kernel, to generate convex hulls of 45 degrees
-%    Skeleton
-%       Thinning kernel, which leaves behind a skeletion of a shape
+%    Skeleton:type
+%       Traditional skeleton generating kernels.
+%         Type 1: Tradional Skeleton kernel (4 connected skeleton)
+%         Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
+%         Type 3: Experimental Variation to try to present left-right symmetry
+%         Type 4: Experimental Variation to preserve left-right symmetry
 %
 %  Distance Measuring Kernels
 %
@@ -844,8 +898,8 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       'square' like distance function, but one where diagonals are closer
 %       than expected.
 %
-%    Manhatten:[{radius}][x{scale}[%!]]
-%       Manhatten Distance (also known as Rectilinear Distance, or the Taxi
+%    Manhattan:[{radius}][x{scale}[%!]]
+%       Manhattan Distance (also known as Rectilinear Distance, or the Taxi
 %       Cab metric), is the distance needed when you can only travel in
 %       orthogonal (horizontal or vertical) only.  It is the distance a 'Rook'
 %       in chess would travel. It results in a diamond like distances, where
@@ -866,7 +920,7 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       To allow the use of fractional distances that you get with diagonals
 %       the actual distance is scaled by a fixed value which the user can
 %       provide.  This is not actually nessary for either ""Chebyshev" or
-%       "Manhatten" distance kernels, but is done for all three distance
+%       "Manhattan" distance kernels, but is done for all three distance
 %       kernels.  If no scale is provided it is set to a value of 100,
 %       allowing for a maximum distance measurement of 655 pixels using a Q16
 %       version of IM, from any edge.  However for small images this can
@@ -905,18 +959,16 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
     case CompassKernel:
     case KirschKernel:
     case FreiChenKernel:
-    case CornersKernel:    /* Hit and Miss kernels */
+    case EdgesKernel:       /* Hit and Miss kernels */
+    case CornersKernel:
     case LineEndsKernel:
     case LineJunctionsKernel:
-    case EdgesKernel:
     case RidgesKernel:
-    case Ridges2Kernel:
     case ConvexHullKernel:
     case SkeletonKernel:
-    case MatKernel:
-      /* A pre-generated kernel is not needed */
-      break;
-#if 0 /* set to 1 to do a compile-time check that we haven't missed anything */
+      break;               /* A pre-generated kernel is not needed */
+#if 0
+    /* set to 1 to do a compile-time check that we haven't missed anything */
     case GaussianKernel:
     case DoGKernel:
     case LoGKernel:
@@ -931,7 +983,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
     case RingKernel:
     case PeaksKernel:
     case ChebyshevKernel:
-    case ManhattenKernel:
+    case ManhattanKernel:
     case EuclideanKernel:
 #else
     default:
@@ -1231,7 +1283,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
           case 19:  /* a 9x9 LoG (sigma approx 1.4) */
             /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
             kernel=ParseKernelArray(
-              "9: 0,-1,-1,-2,-2,-2,-1,-1,0  -1,-2,-4,-5,-5,-5,-4,-2,-1  -1,-4,-5,-3,-0,-3,-5,-4,-1  -2,-5,-3,@12,@24,@12,-3,-5,-2  -2,-5,-0,@24,@40,@24,-0,-5,-2  -2,-5,-3,@12,@24,@12,-3,-5,-2  -1,-4,-5,-3,-0,-3,-5,-4,-1  -1,-2,-4,-5,-5,-5,-4,-2,-1  0,-1,-1,-2,-2,-2,-1,-1,0");
+              "9: 0,-1,-1,-2,-2,-2,-1,-1,0  -1,-2,-4,-5,-5,-5,-4,-2,-1  -1,-4,-5,-3,-0,-3,-5,-4,-1  -2,-5,-3,12,24,12,-3,-5,-2  -2,-5,-0,24,40,24,-0,-5,-2  -2,-5,-3,12,24,12,-3,-5,-2  -1,-4,-5,-3,-0,-3,-5,-4,-1  -1,-2,-4,-5,-5,-5,-4,-2,-1  0,-1,-1,-2,-2,-2,-1,-1,0");
             break;
         }
         if (kernel == (KernelInfo *) NULL)
@@ -1240,14 +1292,49 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         break;
       }
     case SobelKernel:
-      {
+#if 0
+      { /* Sobel with optional 'sub-types' */
+        switch ( (int) args->rho ) {
+          default:
+          case 0:
+            kernel=ParseKernelArray("3: 1,0,-1  2,0,-2  1,0,-1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+          case 1:
+            kernel=ParseKernelArray("3: 1,0,-1  2,0,-2  1,0,-1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            ScaleKernelInfo(kernel, 0.25, NoValue);
+            break;
+          case 2:
+            kernel=ParseKernelArray("3: 1,2,0  2,0,-2  0,-2,-1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            ScaleKernelInfo(kernel, 0.25, NoValue);
+            break;
+        }
+        if ( fabs(args->sigma) > MagickEpsilon )
+          /* Rotate by correctly supplied 'angle' */
+          RotateKernelInfo(kernel, args->sigma);
+        else if ( args->rho > 30.0 || args->rho < -30.0 )
+          /* Rotate by out of bounds 'type' */
+          RotateKernelInfo(kernel, args->rho);
+        break;
+      }
+#else
+      { /* Simple Sobel Kernel */
         kernel=ParseKernelArray("3: 1,0,-1  2,0,-2  1,0,-1");
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
-        RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
+        RotateKernelInfo(kernel, args->rho);
         break;
       }
+#endif
     case RobertsKernel:
       {
         kernel=ParseKernelArray("3: 0,0,0  1,-1,0  0,0,0");
@@ -1294,11 +1381,28 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             kernel=ParseKernelArray("3: 1,0,-1  2,0,-2  1,0,-1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
+            kernel->type = type;
             kernel->values[3] = +MagickSQ2;
             kernel->values[5] = -MagickSQ2;
             CalcKernelMetaData(kernel);     /* recalculate meta-data */
             break;
+          case 2:
+            kernel=ParseKernelArray("3: 1,2,0  2,0,-2  0,-2,-1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            kernel->values[1] = kernel->values[3] = +MagickSQ2;
+            kernel->values[5] = kernel->values[7] = -MagickSQ2;
+            CalcKernelMetaData(kernel);     /* recalculate meta-data */
+            ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
+            break;
+          case 10:
+            kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            break;
           case 1:
+          case 11:
             kernel=ParseKernelArray("3: 1,0,-1  2,0,-2  1,0,-1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
@@ -1308,7 +1412,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             CalcKernelMetaData(kernel);     /* recalculate meta-data */
             ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
             break;
-          case 2:
+          case 12:
             kernel=ParseKernelArray("3: 1,2,1  0,0,0  1,2,1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
@@ -1318,7 +1422,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             CalcKernelMetaData(kernel);
             ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
             break;
-          case 3:
+          case 13:
             kernel=ParseKernelArray("3: 2,-1,0  -1,0,1  0,1,-2");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
@@ -1328,7 +1432,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             CalcKernelMetaData(kernel);
             ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
             break;
-          case 4:
+          case 14:
             kernel=ParseKernelArray("3: 0,1,-2  -1,0,1  2,-1,0");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
@@ -1338,46 +1442,41 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             CalcKernelMetaData(kernel);
             ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
             break;
-          case 5:
+          case 15:
             kernel=ParseKernelArray("3: 0,-1,0  1,0,1  0,-1,0");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
             kernel->type = type;
             ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
             break;
-          case 6:
+          case 16:
             kernel=ParseKernelArray("3: 1,0,-1  0,0,0  -1,0,1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
             kernel->type = type;
             ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
             break;
-          case 7:
+          case 17:
             kernel=ParseKernelArray("3: 1,-2,1  -2,4,-2  -1,-2,1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
             kernel->type = type;
             ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
             break;
-          case 8:
+          case 18:
             kernel=ParseKernelArray("3: -2,1,-2  1,4,1  -2,1,-2");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
             kernel->type = type;
             ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
             break;
-          case 9:
+          case 19:
             kernel=ParseKernelArray("3: 1,1,1  1,1,1  1,1,1");
             if (kernel == (KernelInfo *) NULL)
               return(kernel);
             kernel->type = type;
             ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
             break;
-          case -1:
-            kernel=AcquireKernelInfo("FreiChen:1;FreiChen:2;FreiChen:3;FreiChen:4;FreiChen:5;FreiChen:6;FreiChen:7;FreiChen:8;FreiChen:9");
-            if (kernel == (KernelInfo *) NULL)
-              return(kernel);
-            break;
         }
         if ( fabs(args->sigma) > MagickEpsilon )
           /* Rotate by correctly supplied 'angle' */
@@ -1575,7 +1674,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
+        ExpandMirrorKernelInfo(kernel); /* mirror expansion of other kernels */
         break;
       }
     case CornersKernel:
@@ -1584,116 +1683,149 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
-        break;
-      }
-    case RidgesKernel:
-      {
-        kernel=ParseKernelArray("3x1:0,1,0");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
-        break;
-      }
-    case Ridges2Kernel:
-      {
-        KernelInfo
-          *new_kernel;
-        kernel=ParseKernelArray("4x1:0,1,1,0");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0); /* 4 rotated kernels */
-#if 0
-        /* 2 pixel diagonaly thick - 4 rotates - not needed? */
-        new_kernel=ParseKernelArray("4x4^:0,-,-,- -,1,-,- -,-,1,- -,-,-,0'");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        ExpandKernelInfo(new_kernel, 90.0);  /* 4 rotated kernels */
-        LastKernelInfo(kernel)->next = new_kernel;
-#endif
-        /* kernels to find a stepped 'thick' line - 4 rotates * mirror */
-        /* Unfortunatally we can not yet rotate a non-square kernel */
-        /* But then we can't flip a non-symetrical kernel either */
-        new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
-        new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        LastKernelInfo(kernel)->next = new_kernel;
+        ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
         break;
       }
     case LineEndsKernel:
-      {
-        KernelInfo
-          *new_kernel;
-        kernel=ParseKernelArray("3: 0,0,0  0,1,0  -,1,-");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0);
-        /* append second set of 4 kernels */
-        new_kernel=ParseKernelArray("3: 0,0,0  0,1,0  0,0,1");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        ExpandKernelInfo(new_kernel, 90.0);
-        LastKernelInfo(kernel)->next = new_kernel;
+      { /* Kernels for finding the end of thin lines */
+        switch ( (int) args->rho ) {
+          case 0:
+          default:
+            /* set of kernels to find all end of lines */
+            kernel=AcquireKernelInfo("LineEnds:1>;LineEnds:2>");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            break;
+          case 1:
+            /* kernel for 4-connected line ends - no rotation */
+            kernel=ParseKernelArray("3: 0,0,0  0,1,0  -,1,-");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+         case 2:
+            /* kernel to add for 8-connected lines - no rotation */
+            kernel=ParseKernelArray("3: 0,0,0  0,1,0  0,0,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+        }
         break;
       }
     case LineJunctionsKernel:
-      {
+      { /* kernels for finding the junctions of multiple lines */
+        switch ( (int) args->rho ) {
+          case 0:
+          default:
+            /* set of kernels to find all line junctions */
+            kernel=AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            break;
+          case 1:
+            /* Y Junction */
+            kernel=ParseKernelArray("3: 1,-,1  -,1,-  -,1,-");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+          case 2:
+            /* Diagonal T Junctions */
+            kernel=ParseKernelArray("3: 1,-,-  -,1,-  1,-,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+          case 3:
+            /* Orthogonal T Junctions */
+            kernel=ParseKernelArray("3: -,-,-  1,1,1  -,1,-");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+          case 4:
+            /* Diagonal X Junctions */
+            kernel=ParseKernelArray("3: 1,-,1  -,1,-  1,-,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+          case 5:
+            /* Orthogonal X Junctions - minimal diamond kernel */
+            kernel=ParseKernelArray("3: -,1,-  1,1,1  -,1,-");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            break;
+        }
+        break;
+      }
+    case RidgesKernel:
+      { /* Ridges - Ridge finding kernels */
         KernelInfo
           *new_kernel;
-        /* first set of 4 kernels */
-        kernel=ParseKernelArray("3: -,1,-  -,1,-  1,-,1");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 45.0);
-        /* append second set of 4 kernels */
-        new_kernel=ParseKernelArray("3: 1,-,-  -,1,-  1,-,1");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        ExpandKernelInfo(new_kernel, 90.0);
-        LastKernelInfo(kernel)->next = new_kernel;
+        switch ( (int) args->rho ) {
+          case 1:
+          default:
+            kernel=ParseKernelArray("3x1:0,1,0");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
+            break;
+          case 2:
+            kernel=ParseKernelArray("4x1:0,1,1,0");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
+
+            /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
+            /* Unfortunatally we can not yet rotate a non-square kernel */
+            /* But then we can't flip a non-symetrical kernel either */
+            new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            break;
+        }
         break;
       }
     case ConvexHullKernel:
@@ -1705,65 +1837,122 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
-        ExpandKernelInfo(kernel, 45.0);
-        /* append the mirror versions too */
+        ExpandRotateKernelInfo(kernel, 90.0);
+        /* append the mirror versions too - no flip function yet */
         new_kernel=ParseKernelArray("3: 1,1,1  1,0,-  -,-,0");
         if (new_kernel == (KernelInfo *) NULL)
           return(DestroyKernelInfo(kernel));
         new_kernel->type = type;
-        ExpandKernelInfo(new_kernel, 45.0);
+        ExpandRotateKernelInfo(new_kernel, 90.0);
         LastKernelInfo(kernel)->next = new_kernel;
         break;
       }
     case SkeletonKernel:
-      { /* what is the best form for skeletonization by thinning? */
-#if 1
-        /* Full Corner rotated to form edges too */
-        kernel=ParseKernelArray("3: 0,0,-  0,1,1  -,1,1");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 45);
-#endif
-#if 0
-        /* As last but thin the edges before looking for corners */
-        KernelInfo
-          *new_kernel;
-        kernel=ParseKernelArray("3: 0,0,0  -,1,-  1,1,1");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandKernelInfo(kernel, 90.0);
-        new_kernel=ParseKernelArray("3: 0,0,-  0,1,1  -,1,1");
-        if (new_kernel == (KernelInfo *) NULL)
-          return(DestroyKernelInfo(kernel));
-        new_kernel->type = type;
-        ExpandKernelInfo(new_kernel, 90.0);
-        LastKernelInfo(kernel)->next = new_kernel;
-#endif
-#if 0
-        kernel=AcquireKernelInfo("Edges;Corners");
-#endif
-#if 0
-        kernel=AcquireKernelInfo("Corners;Edges");
-#endif
-        break;
-      }
-    case MatKernel: /* experimental - MAT from a Distance Gradient */
       {
         KernelInfo
           *new_kernel;
-        /* Ridge Kernel but without the diagonal */
-        kernel=ParseKernelArray("3x1: 0,1,0");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = RidgesKernel;
-        ExpandKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
-        /* Plus the 2 pixel ridges kernel - no diagonal */
-        new_kernel=AcquireKernelBuiltIn(Ridges2Kernel,args);
-        if (new_kernel == (KernelInfo *) NULL)
-          return(kernel);
-        LastKernelInfo(kernel)->next = new_kernel;
+        switch ( (int) args->rho ) {
+          case 1:
+          default:
+            /* Traditional Skeleton...
+            ** A cyclically rotated single kernel
+            */
+            kernel=ParseKernelArray("3: 0,0,0  -,1,-  1,1,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
+            break;
+          case 2:
+            /* HIPR Variation of the cyclic skeleton
+            ** Corners of the traditional method made more forgiving,
+            ** but the retain the same cyclic order.
+            */
+            kernel=ParseKernelArray("3: 0,0,0  -,1,-  1,1,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            new_kernel=ParseKernelArray("3: -,0,0  1,1,0  -,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
+            break;
+          case 3:
+            /* Jittered Skeleton: do top, then bottom, then each sides */
+            /* Do top edge */
+            kernel=ParseKernelArray("3: 0,0,0  -,1,-  1,1,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            new_kernel=ParseKernelArray("3: 0,0,-  0,1,1  -,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3: -,0,0  1,1,0  -,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            /* Do Bottom edge */
+            new_kernel=ParseKernelArray("3: 1,1,1  -,1,-  0,0,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3: -,1,-  1,1,0  -,0,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3: -,1,-  0,1,1  0,0,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            /* Last the two sides */
+            new_kernel=ParseKernelArray("3: 0,-,1  0,1,1  0,-,1");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3: 1,-,0  1,1,0  1,-,0");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(new_kernel);
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            break;
+          case 4:
+            /* Just a simple 'Edge' kernel, but with a extra two kernels
+            ** to finish off diagonal lines,  top then bottom then sides.
+            ** Works well for test case but fails for general case.
+            */
+            kernel=ParseKernelArray("3: 0,0,0  -,1,-  1,1,1");
+            if (kernel == (KernelInfo *) NULL)
+              return(kernel);
+            kernel->type = type;
+            new_kernel=ParseKernelArray("3: 0,0,0  0,1,1  1,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            new_kernel=ParseKernelArray("3: 0,0,0  1,1,0  -,1,1");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            LastKernelInfo(kernel)->next = new_kernel;
+            ExpandMirrorKernelInfo(kernel);
+            /* Append a set of corner kernels */
+            new_kernel=ParseKernelArray("3: 0,0,-  0,1,1  -,1,-");
+            if (new_kernel == (KernelInfo *) NULL)
+              return(DestroyKernelInfo(kernel));
+            new_kernel->type = type;
+            ExpandRotateKernelInfo(new_kernel, 90.0);
+            LastKernelInfo(kernel)->next = new_kernel;
+            break;
+        }
         break;
       }
     /* Distance Measuring Kernels */
@@ -1787,7 +1976,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         kernel->maximum = kernel->values[0];
         break;
       }
-    case ManhattenKernel:
+    case ManhattanKernel:
       {
         if (args->rho < 1.0)
           kernel->width = kernel->height = 3;  /* default radius = 1 */
@@ -1830,8 +2019,8 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
     case UnityKernel:
     default:
       {
-        /* Unity or No-Op Kernel - 3x3 with 1 in center */
-        kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
+        /* Unity or No-Op Kernel - Basically just a single pixel on its own */
+        kernel=ParseKernelArray("1:1");
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
@@ -1922,7 +2111,6 @@ MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
 %    o kernel: the Morphology/Convolution kernel to be destroyed
 %
 */
-
 MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
 {
   assert(kernel != (KernelInfo *) NULL);
@@ -1940,21 +2128,96 @@ MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
 %                                                                             %
 %                                                                             %
 %                                                                             %
-%     E x p a n d K e r n e l I n f o                                         %
+%     E x p a n d M i r r o r K e r n e l I n f o                             %
 %                                                                             %
 %                                                                             %
 %                                                                             %
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %
-%  ExpandKernelInfo() takes a single kernel, and expands it into a list
-%  of kernels each incrementally rotated the angle given.
+%  ExpandMirrorKernelInfo() takes a single kernel, and expands it into a
+%  sequence of 90-degree rotated kernels but providing a reflected 180
+%  rotatation, before the -/+ 90-degree rotations.
+%
+%  This special rotation order produces a better, more symetrical thinning of
+%  objects.
+%
+%  The format of the ExpandMirrorKernelInfo method is:
+%
+%      void ExpandMirrorKernelInfo(KernelInfo *kernel)
+%
+%  A description of each parameter follows:
+%
+%    o kernel: the Morphology/Convolution kernel
+%
+% This function is only internel to this module, as it is not finalized,
+% especially with regard to non-orthogonal angles, and rotation of larger
+% 2D kernels.
+*/
+
+#if 0
+static void FlopKernelInfo(KernelInfo *kernel)
+    { /* Do a Flop by reversing each row. */
+      size_t
+        y;
+      register ssize_t
+        x,r;
+      register double
+        *k,t;
+
+      for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
+        for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
+          t=k[x],  k[x]=k[r],  k[r]=t;
+
+      kernel->x = kernel->width - kernel->x - 1;
+      angle = fmod(angle+180.0, 360.0);
+    }
+#endif
+
+static void ExpandMirrorKernelInfo(KernelInfo *kernel)
+{
+  KernelInfo
+    *clone,
+    *last;
+
+  last = kernel;
+
+  clone = CloneKernelInfo(last);
+  RotateKernelInfo(clone, 180);   /* flip */
+  LastKernelInfo(last)->next = clone;
+  last = clone;
+
+  clone = CloneKernelInfo(last);
+  RotateKernelInfo(clone, 90);   /* transpose */
+  LastKernelInfo(last)->next = clone;
+  last = clone;
+
+  clone = CloneKernelInfo(last);
+  RotateKernelInfo(clone, 180);  /* flop */
+  LastKernelInfo(last)->next = clone;
+
+  return;
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%     E x p a n d R o t a t e K e r n e l I n f o                             %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  ExpandRotateKernelInfo() takes a kernel list, and expands it by rotating
+%  incrementally by the angle given, until the first kernel repeats.
 %
 %  WARNING: 45 degree rotations only works for 3x3 kernels.
 %  While 90 degree roatations only works for linear and square kernels
 %
-%  The format of the RotateKernelInfo method is:
+%  The format of the ExpandRotateKernelInfo method is:
 %
-%      void ExpandKernelInfo(KernelInfo *kernel, double angle)
+%      void ExpandRotateKernelInfo(KernelInfo *kernel, double angle)
 %
 %  A description of each parameter follows:
 %
@@ -1996,7 +2259,7 @@ static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
   return MagickTrue;
 }
 
-static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
+static void ExpandRotateKernelInfo(KernelInfo *kernel, const double angle)
 {
   KernelInfo
     *clone,
@@ -2008,10 +2271,10 @@ static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
     RotateKernelInfo(clone, angle);
     if ( SameKernelInfo(kernel, clone) == MagickTrue )
       break;
-    last->next = clone;
+    LastKernelInfo(last)->next = clone;
     last = clone;
   }
-  clone = DestroyKernelInfo(clone); /* This was the same as the first - junk */
+  clone = DestroyKernelInfo(clone); /* kernel has repeated - junk the clone */
   return;
 }
 \f
@@ -2087,13 +2350,17 @@ static void CalcKernelMetaData(KernelInfo *kernel)
 %  a list of multiple kernels.
 %
 %  It is basically equivelent to as MorphologyImageChannel() (see below) but
-%  without user controls, that that function extracts and applies to kernels
-%  and morphology methods.
+%  without any user controls.  This allows internel programs to use this
+%  function, to actually perform a specific task without posible interference
+%  by any API user supplied settings.
+%
+%  It is MorphologyImageChannel() task to extract any such user controls, and
+%  pass them to this function for processing.
 %
 %  More specifically kernels are not normalized/scaled/blended by the
-%  'convolve:scale' Image Artifact (-set setting), and the convolve bias
-%  (-bias setting or image->bias) is passed directly to this function,
-%  and not extracted from an image.
+%  'convolve:scale' Image Artifact (setting), nor is the convolve bias
+%  (-bias setting or image->bias) loooked at, but must be supplied from the
+%  function arguments.
 %
 %  The format of the MorphologyApply method is:
 %
@@ -2190,7 +2457,7 @@ static size_t MorphologyPrimitive(const Image *image, Image
     case HitAndMissMorphology:
     case ThinningMorphology:
     case ThickenMorphology:
-      /* kernel is user as is, without reflection */
+      /* kernel is used as is, without reflection */
       break;
     default:
       assert("Not a Primitive Morphology Method" != (char *) NULL);
@@ -2456,8 +2723,9 @@ static size_t MorphologyPrimitive(const Image *image, Image
             ** neighbourhoods, 0.0 for background, and 1.0 for foreground
             ** with either Nan or 0.5 values for don't care.
             **
-            ** Note that this can produce negative results, though really
-            ** only a positive match has any real value.
+            ** Note that this will never produce a meaningless negative
+            ** result.  Such results can cause Thinning/Thicken to not work
+            ** correctly when used against a greyscale image.
             */
             k = kernel->values;
             k_pixels = p;
@@ -2489,7 +2757,7 @@ static size_t MorphologyPrimitive(const Image *image, Image
               k_pixels += image->columns+kernel->width;
               k_indexes += image->columns+kernel->width;
             }
-            /* Pattern Match  only if min fg larger than min bg pixels */
+            /* Pattern Match if difference is positive */
             min.red     -= max.red;     Maximize( min.red,     0.0 );
             min.green   -= max.green;   Maximize( min.green,   0.0 );
             min.blue    -= max.blue;    Maximize( min.blue,    0.0 );
@@ -2614,12 +2882,12 @@ static size_t MorphologyPrimitive(const Image *image, Image
           result.index   -= min.index;
           break;
         case ThickenMorphology:
-          /* Union with original image (maximize) - or should this be + */
-          Maximize( result.red,     min.red );
-          Maximize( result.green,   min.green );
-          Maximize( result.blue,    min.blue );
-          Maximize( result.opacity, min.opacity );
-          Maximize( result.index,   min.index );
+          /* Add the pattern matchs to the original */
+          result.red     += min.red;
+          result.green   += min.green;
+          result.blue    += min.blue;
+          result.opacity += min.opacity;
+          result.index   += min.index;
           break;
         default:
           /* result directly calculated or assigned */
@@ -2767,15 +3035,12 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
       stage_limit = 2;
       break;
     case HitAndMissMorphology:
-      kernel_limit = 1;          /* no method or kernel iteration */
       rslt_compose = LightenCompositeOp;  /* Union of multi-kernel results */
-      break;
+      /* FALL THUR */
     case ThinningMorphology:
     case ThickenMorphology:
-      method_limit = kernel_limit;  /* iterate method with each kernel */
+      method_limit = kernel_limit;  /* iterate the whole method */
       kernel_limit = 1;             /* do not do kernel iteration  */
-    case DistanceMorphology:
-      rslt_compose = NoCompositeOp; /* Re-iterate with multiple kernels */
       break;
     default:
       break;
@@ -2907,13 +3172,13 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
         /* Extra information for debugging compound operations */
         if ( verbose == MagickTrue ) {
           if ( stage_limit > 1 )
-            (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu.%lu -> ",
-             MagickOptionToMnemonic(MagickMorphologyOptions, method),
-             (unsigned long) method_loop,(unsigned long) stage_loop);
+            (void) FormatMagickString(v_info,MaxTextExtent,"%s:%.20g.%.20g -> ",
+             MagickOptionToMnemonic(MagickMorphologyOptions,method),(double)
+             method_loop,(double) stage_loop);
           else if ( primative != method )
-            (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu -> ",
-               MagickOptionToMnemonic(MagickMorphologyOptions, method),
-               (unsigned long) method_loop);
+            (void) FormatMagickString(v_info, MaxTextExtent, "%s:%.20g -> ",
+              MagickOptionToMnemonic(MagickMorphologyOptions, method),(double)
+              method_loop);
           else
             v_info[0] = '\0';
         }
@@ -2948,11 +3213,11 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
           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 ) ? "*" : "",
-               (unsigned long) method_loop+kernel_loop-1,(unsigned long)
-               kernel_number,(unsigned long) count,(unsigned long) changed);
+            (void) fprintf(stderr, "%s%s%s:%.20g.%.20g #%.20g => Changed %.20g",
+              v_info,MagickOptionToMnemonic(MagickMorphologyOptions,
+              primative),(this_kernel == rflt_kernel ) ? "*" : "",
+              (double) (method_loop+kernel_loop-1),(double) kernel_number,
+              (double) count,(double) changed);
           }
           /* prepare next loop */
           { Image *tmp = work_image;   /* swap images for iteration */
@@ -2965,7 +3230,7 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
         } /* End Loop 4: Iterate the kernel with primative */
 
         if ( verbose == MagickTrue && kernel_changed != changed )
-          fprintf(stderr, "   Total %lu",(unsigned long) kernel_changed);
+          fprintf(stderr, "   Total %.20g",(double) kernel_changed);
         if ( verbose == MagickTrue && stage_loop < stage_limit )
           fprintf(stderr, "\n"); /* add end-of-line before looping */
 
@@ -3036,13 +3301,19 @@ MagickExport Image *MorphologyApply(const Image *image, const ChannelType
           ** 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'.
+          **
+          ** The compose image order is specifically so that the new image can
+          ** be subtarcted 'Minus' from the collected result, to allow you to
+          ** convert a HitAndMiss methd into a Thinning method.
           */
           if ( verbose == MagickTrue )
             fprintf(stderr, " (compose \"%s\")",
                  MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
-          (void) CompositeImageChannel(rslt_image,
+          (void) CompositeImageChannel(curr_image,
                (ChannelType) (channel & ~SyncChannels), rslt_compose,
-               curr_image, 0, 0);
+               rslt_image, 0, 0);
+          rslt_image = DestroyImage(rslt_image);
+          rslt_image = curr_image;
           curr_image = (Image *) image;  /* continue with original image */
         }
       if ( verbose == MagickTrue )
@@ -3158,7 +3429,7 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
   if ( method == ConvolveMorphology ||  method == CorrelateMorphology )
     {
       artifact = GetImageArtifact(image,"convolve:scale");
-      if ( artifact != (char *)NULL ) {
+      if ( artifact != (const char *)NULL ) {
         if ( curr_kernel == kernel )
           curr_kernel = CloneKernelInfo(kernel);
         if (curr_kernel == (KernelInfo *) NULL) {
@@ -3178,10 +3449,11 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
   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
+  /* 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.
+   * Default for 'HitAndMiss' is 'Lighten'.
    */
   compose = UndefinedCompositeOp;  /* use default for method */
   artifact = GetImageArtifact(image,"morphology:compose");
@@ -3270,7 +3542,7 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
     case PeaksKernel:
     case LaplacianKernel:
     case ChebyshevKernel:
-    case ManhattenKernel:
+    case ManhattanKernel:
     case EuclideanKernel:
       return;
 
@@ -3330,8 +3602,8 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
   if ( 45.0 < fmod(angle, 180.0)  && fmod(angle,180.0) <= 135.0 )
     {
       if ( kernel->width == 1 || kernel->height == 1 )
-        { /* Do a transpose of the image, which results in a 90
-          ** degree rotation of a 1 dimentional kernel
+        { /* Do a transpose of a 1 dimentional kernel,
+          ** which results in a fast 90 degree rotation of some type.
           */
           ssize_t
             t;
@@ -3404,24 +3676,6 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
    * performed here, posibily with a linear kernel restriction.
    */
 
-#if 0
-    { /* Do a Flop by reversing each row.
-       */
-      size_t
-        y;
-      register ssize_t
-        x,r;
-      register double
-        *k,t;
-
-      for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
-        for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
-          t=k[x],  k[x]=k[r],  k[r]=t;
-
-      kernel->x = kernel->width - kernel->x - 1;
-      angle = fmod(angle+180.0, 360.0);
-    }
-#endif
   return;
 }
 \f