]> granicus.if.org Git - imagemagick/blobdiff - magick/morphology.c
(no commit message)
[imagemagick] / magick / morphology.c
index 77b7cf7d298c4f01b83cf996276f7fef75558987..982325a75b4a1348c5ee160a935c43b2a564bf35 100644 (file)
@@ -850,19 +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:type
-%       Find single pixel ridges or thin lines
-%    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: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
 %
@@ -943,10 +959,10 @@ 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 ConvexHullKernel:
     case SkeletonKernel:
@@ -1276,7 +1292,9 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         break;
       }
     case SobelKernel:
-      { switch ( (int) args->rho ) {
+#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");
@@ -1299,8 +1317,24 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType 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);
         break;
       }
+#endif
     case RobertsKernel:
       {
         kernel=ParseKernelArray("3: 0,0,0  1,-1,0  0,0,0");
@@ -1653,40 +1687,78 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         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;
-        ExpandRotateKernelInfo(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;
-        ExpandRotateKernelInfo(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:
-      {
-        KernelInfo
-          *new_kernel;
-        /* first set of 4 kernels */
-        kernel=ParseKernelArray("3: -,1,-  -,1,-  1,-,1");
-        if (kernel == (KernelInfo *) NULL)
-          return(kernel);
-        kernel->type = type;
-        ExpandRotateKernelInfo(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;
-        ExpandRotateKernelInfo(new_kernel, 90.0);
-        LastKernelInfo(kernel)->next = new_kernel;
+      { /* 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:
@@ -1708,16 +1780,8 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
               return(kernel);
             kernel->type = type;
             ExpandRotateKernelInfo(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;
-            ExpandRotateKernelInfo(new_kernel, 90.0);  /* 4 rotated kernels */
-            LastKernelInfo(kernel)->next = new_kernel;
-#endif
-            /* kernels to find a stepped 'thick' line, 4 rotates + mirrors */
+
+            /* 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");
@@ -1773,13 +1837,13 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel == (KernelInfo *) NULL)
           return(kernel);
         kernel->type = type;
-        ExpandRotateKernelInfo(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;
-        ExpandRotateKernelInfo(new_kernel, 45.0);
+        ExpandRotateKernelInfo(new_kernel, 90.0);
         LastKernelInfo(kernel)->next = new_kernel;
         break;
       }
@@ -1880,6 +1944,13 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
             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;
@@ -1948,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;
@@ -2386,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);
@@ -2964,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;
@@ -3233,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 )
@@ -3375,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");