]> granicus.if.org Git - imagemagick/commitdiff
Minimal Error reporting for badly defined kernels
authoranthony <anthony@git.imagemagick.org>
Tue, 11 May 2010 06:51:12 +0000 (06:51 +0000)
committeranthony <anthony@git.imagemagick.org>
Tue, 11 May 2010 06:51:12 +0000 (06:51 +0000)
Pattern Matching Morphology: HitAndMiss Thicken Thinning

ChangeLog
config/english.xml
config/francais.xml
magick/morphology.c
magick/morphology.h
magick/option.c
wand/mogrify.c

index 3c93f54904f0ca1509159385f94263d3e6a8ed95..4fd8eebfd9a6bbaa9580cdf21ada2f2c18e804fd 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2010-05-11  6.6.1-8 Anthony Thyssen <A.Thyssen@griffith...>
+  * Implementation of Pattern Matching Morphology methods
+    HitAndMiss, Thicken, Thinning
+  * Minimal Error reporting of badly defined Kernel Strings
+
 2010-05-08  6.6.1-8 Glenn Randers-Pehrson <glennrp@image...>
   * Use proper background.index when writing indexed PNG, if the background
     color is in the palette.
index ad56c31c034fc03e7b7cdf1dbf5ea29028a2cd26..b066737c1f51df065c6771e7297f1ef9948d48f8 100644 (file)
         <message name="UnableToParseExpression">
           unable to parse expression
         </message>
+        <message name="UnableToParseKernel">
+          unable to parse kernel string
+        </message>
         <message name="UnbalancedParenthesis">
           unbalanced parenthesis
         </message>
         <message name="UnrecognizedInterpolateMethod">
           unrecognized interpolate method
         </message>
+        <message name="UnrecognizedKernelType">
+          unrecognized kernel type
+        </message>
         <message name="UnrecognizedListType">
           unrecognized list type
         </message>
index 009133963ea26f98cc1b2d151b7cf40caf25da33..4f4139ce42a963d3ba9cfeffb3b7f9a036561c32 100644 (file)
         <message name="UnableToParseExpression">
           impossible d'analyser l'expression
         </message>
+        <message name="UnableToParseKernel">
+          impossible d'analyser l'kernel string
+        </message>
         <message name="UnbalancedParenthesis">
           parenthèse non fermée
         </message>
         <message name="UnrecognizedInterpolateMethod">
           méthode d'interpolation non reconnnue
         </message>
+        <message name="UnrecognizedKernelType">
+          type de kernel non reconnnue
+        </message>
         <message name="UnrecognizedListType">
           type de liste non reconnu
         </message>
index 6bbbcd728408d6838ba6e7576d3d088405f95d7c..8b804d45e2d09ec11ffcc5c2517efed348a8997f 100644 (file)
@@ -180,9 +180,9 @@ static void
 */
 
 /* This was separated so that it could be used as a separate
-** array input handling function.
+** array input handling function, such as for -color-matrix
 */
-static KernelInfo *ParseArray(const char *kernel_string)
+static KernelInfo *ParseKernelArray(const char *kernel_string)
 {
   KernelInfo
     *kernel;
@@ -191,7 +191,8 @@ static KernelInfo *ParseArray(const char *kernel_string)
     token[MaxTextExtent];
 
   const char
-    *p;
+    *p,
+    *end;
 
   register long
     i;
@@ -206,9 +207,14 @@ static KernelInfo *ParseArray(const char *kernel_string)
   kernel->type = UserDefinedKernel;
   kernel->signature = MagickSignature;
 
+  /* find end of this specific kernel definition string */
+  end = strchr(kernel_string, ';');
+  if ( end == (char *) NULL )
+    end = strchr(kernel_string, '\0');
+
   /* Has a ':' in argument - New user kernel specification */
   p = strchr(kernel_string, ':');
-  if ( p != (char *) NULL)
+  if ( p != (char *) NULL && p < end)
     {
       MagickStatusType
         flags;
@@ -251,7 +257,7 @@ static KernelInfo *ParseArray(const char *kernel_string)
       p=(const char *) kernel_string;
       while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
         p++;  /* ignore "'" chars for convolve filter usage - Cristy */
-      for (i=0; *p != '\0'; i++)
+      for (i=0; p < end; i++)
       {
         GetMagickToken(p,&p,token);
         if (*token == ',')
@@ -275,7 +281,7 @@ static KernelInfo *ParseArray(const char *kernel_string)
   kernel->maximum = -MagickHuge;
   kernel->negative_range = kernel->positive_range = 0.0;
 
-  for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
+  for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
   {
     GetMagickToken(p,&p,token);
     if (*token == ',')
@@ -294,6 +300,11 @@ static KernelInfo *ParseArray(const char *kernel_string)
     }
   }
 
+  /* sanity check -- no more values in kernel definition */
+  GetMagickToken(p,&p,token);
+  if ( *token != '\0' && *token != ';' && *token != '\'' )
+    return(DestroyKernelInfo(kernel));
+
 #if 0
   /* this was the old method of handling a incomplete kernel */
   if ( i < (long) (kernel->width*kernel->height) ) {
@@ -315,12 +326,14 @@ static KernelInfo *ParseArray(const char *kernel_string)
   return(kernel);
 }
 
-
-MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
+static KernelInfo *ParseNamedKernel(const char *kernel_string)
 {
   char
     token[MaxTextExtent];
 
+  long
+    type;
+
   const char
     *p;
 
@@ -330,64 +343,70 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
   GeometryInfo
     args;
 
-  long
-    type;
-
-  /* If it does not start with an alpha - user defined kernel */
-  GetMagickToken(kernel_string,&p,token);
-  if (isalpha((int) ((unsigned char) *token)) == 0)
-    return(ParseArray(kernel_string));
-
   /* Parse special 'named' kernel */
+  GetMagickToken(kernel_string,&p,token);
   type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
   if ( type < 0 || type == UserDefinedKernel )
-    return((KernelInfo *)NULL);
+    return((KernelInfo *)NULL);  /* not a valid named kernel */
 
   while (((isspace((int) ((unsigned char) *p)) != 0) ||
-          (*p == ',') || (*p == ':' )) && (*p != '\0'))
+          (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
     p++;
   SetGeometryInfo(&args);
   flags = ParseGeometry(p, &args);
 
   /* special handling of missing values in input string */
   switch( type ) {
-  case RectangleKernel:
-    if ( (flags & WidthValue) == 0 ) /* if no width then */
-      args.rho = args.sigma;         /* then  width = height */
-    if ( args.rho < 1.0 )            /* if width too small */
-        args.rho = 3;                 /* then  width = 3 */
-    if ( args.sigma < 1.0 )          /* if height too small */
-      args.sigma = args.rho;         /* then  height = width */
-    if ( (flags & XValue) == 0 )     /* center offset if not defined */
-      args.xi = (double)(((long)args.rho-1)/2);
-    if ( (flags & YValue) == 0 )
-      args.psi = (double)(((long)args.sigma-1)/2);
-    break;
-  case SquareKernel:
-  case DiamondKernel:
-  case DiskKernel:
-  case PlusKernel:
-    /* If no scale given (a 0 scale is valid! - set it to 1.0 */
-    if ( (flags & HeightValue) == 0 )
-      args.sigma = 1.0;
-    break;
-  case ChebyshevKernel:
-  case ManhattenKernel:
-  case EuclideanKernel:
-    if ( (flags & HeightValue) == 0 )
-      args.sigma = 100.0;                    /* default distance scaling */
-    else if ( (flags & AspectValue ) != 0 )  /* '!' flag */
-      args.sigma = QuantumRange/args.sigma;  /* maximum pixel distance */
-    else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
-      args.sigma *= QuantumRange/100.0;      /* percentage of color range */
-    break;
-  default:
-    break;
+    case RectangleKernel:
+      if ( (flags & WidthValue) == 0 ) /* if no width then */
+        args.rho = args.sigma;         /* then  width = height */
+      if ( args.rho < 1.0 )            /* if width too small */
+          args.rho = 3;                 /* then  width = 3 */
+      if ( args.sigma < 1.0 )          /* if height too small */
+        args.sigma = args.rho;         /* then  height = width */
+      if ( (flags & XValue) == 0 )     /* center offset if not defined */
+        args.xi = (double)(((long)args.rho-1)/2);
+      if ( (flags & YValue) == 0 )
+        args.psi = (double)(((long)args.sigma-1)/2);
+      break;
+    case SquareKernel:
+    case DiamondKernel:
+    case DiskKernel:
+    case PlusKernel:
+      /* If no scale given (a 0 scale is valid! - set it to 1.0 */
+      if ( (flags & HeightValue) == 0 )
+        args.sigma = 1.0;
+      break;
+    case ChebyshevKernel:
+    case ManhattenKernel:
+    case EuclideanKernel:
+      if ( (flags & HeightValue) == 0 )
+        args.sigma = 100.0;                    /* default distance scaling */
+      else if ( (flags & AspectValue ) != 0 )  /* '!' flag */
+        args.sigma = QuantumRange/args.sigma;  /* maximum pixel distance */
+      else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
+        args.sigma *= QuantumRange/100.0;      /* percentage of color range */
+      break;
+    default:
+      break;
   }
 
   return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
 }
 
+MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
+{
+  char
+    token[MaxTextExtent];
+
+  /* If it does not start with an alpha - Its is a user defined kernel array */
+  GetMagickToken(kernel_string,NULL,token);
+  if (isalpha((int) ((unsigned char) *token)) == 0)
+    return(ParseKernelArray(kernel_string));
+
+  return(ParseNamedKernel(kernel_string));
+}
+
 \f
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1120,20 +1139,23 @@ static unsigned long MorphologyApply(const Image *image, Image
   offx = kernel->x;
   offy = kernel->y;
   switch(method) {
-    case ErodeMorphology:
-    case ErodeIntensityMorphology:
-      /* kernel is user as is, without reflection */
-      break;
     case ConvolveMorphology:
     case DilateMorphology:
     case DilateIntensityMorphology:
     case DistanceMorphology:
-      /* kernel needs to used with reflection */
+      /* kernel needs to used with reflection about origin */
       offx = (long) kernel->width-offx-1;
       offy = (long) kernel->height-offy-1;
       break;
+    case ErodeMorphology:
+    case ErodeIntensityMorphology:
+    case HitAndMissMorphology:
+    case ThinningMorphology:
+    case ThickenMorphology:
+      /* kernel is user as is, without reflection */
+      break;
     default:
-      perror("Not a low level Morpholgy Method");
+      perror("Not a low level Morphology Method");
       break;
   }
 
@@ -1196,7 +1218,9 @@ static unsigned long MorphologyApply(const Image *image, Image
         *restrict k_indexes;
 
       MagickPixelPacket
-        result;
+        result,
+        min,
+        max;
 
       /* Copy input to ouput image for unused channels
        * This removes need for 'cloning' a new image every iteration
@@ -1205,45 +1229,39 @@ static unsigned long MorphologyApply(const Image *image, Image
       if (image->colorspace == CMYKColorspace)
         q_indexes[x] = p_indexes[r];
 
-      result.green=(MagickRealType) 0;
-      result.blue=(MagickRealType) 0;
-      result.opacity=(MagickRealType) 0;
-      result.index=(MagickRealType) 0;
+      /* Defaults */
+      min.red     =
+      min.green   =
+      min.blue    =
+      min.opacity =
+      min.index   = (MagickRealType) QuantumRange;
+      max.red     =
+      max.green   =
+      max.blue    =
+      max.opacity =
+      max.index   = (MagickRealType) 0;
+      /* original pixel value */
+      result.red     = (MagickRealType) p[r].red;
+      result.green   = (MagickRealType) p[r].green;
+      result.blue    = (MagickRealType) p[r].blue;
+      result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
+      if ( image->colorspace == CMYKColorspace)
+         result.index   = (MagickRealType) p_indexes[r];
+
       switch (method) {
         case ConvolveMorphology:
           /* Set the user defined bias of the weighted average output
           **
           ** FUTURE: provide some way for internal functions to disable
-          ** user defined bias and scaling effects.
+          ** user provided bias and scaling effects.
           */
           result=bias;
           break;
-        case DilateMorphology:
-          result.red     =
-          result.green   =
-          result.blue    =
-          result.opacity =
-          result.index   = -MagickHuge;
-          break;
-        case ErodeMorphology:
-          result.red     =
-          result.green   =
-          result.blue    =
-          result.opacity =
-          result.index   = +MagickHuge;
-          break;
         case DilateIntensityMorphology:
         case ErodeIntensityMorphology:
-          result.red = 0.0;  /* flag indicating first match found */
+          result.red = 0.0;  /* flag indicating when first match found */
           break;
         default:
-          /* Otherwise just start with the original pixel value */
-          result.red     = (MagickRealType) p[r].red;
-          result.green   = (MagickRealType) p[r].green;
-          result.blue    = (MagickRealType) p[r].blue;
-          result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
-          if ( image->colorspace == CMYKColorspace)
-             result.index   = (MagickRealType) p_indexes[r];
           break;
       }
 
@@ -1259,7 +1277,7 @@ static unsigned long MorphologyApply(const Image *image, Image
             ** the kernel, and thus 'lower-level' that Convolution.  However
             ** as Convolution is the more common method used, and it does not
             ** really cost us much in terms of processing to use a reflected
-            ** kernel it is Convolution that is implemented.
+            ** kernel, so it is Convolution that is implemented.
             **
             ** Correlation will have its kernel reflected before calling
             ** this function to do a Convolve.
@@ -1267,32 +1285,15 @@ static unsigned long MorphologyApply(const Image *image, Image
             ** For more details of Correlation vs Convolution see
             **   http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
             */
-            if (((channel & OpacityChannel) == 0) ||
-                      (image->matte == MagickFalse))
-              {
-                /* Convolution without transparency effects */
-                k = &kernel->values[ kernel->width*kernel->height-1 ];
-                k_pixels = p;
-                k_indexes = p_indexes;
-                for (v=0; v < (long) kernel->height; v++) {
-                  for (u=0; u < (long) kernel->width; u++, k--) {
-                    if ( IsNan(*k) ) continue;
-                    result.red     += (*k)*k_pixels[u].red;
-                    result.green   += (*k)*k_pixels[u].green;
-                    result.blue    += (*k)*k_pixels[u].blue;
-                    /* result.opacity += not involved here */
-                    if ( image->colorspace == CMYKColorspace)
-                      result.index   += (*k)*k_indexes[u];
-                  }
-                  k_pixels += image->columns+kernel->width;
-                  k_indexes += image->columns+kernel->width;
-                }
-              }
-            else
-              { /* Kernel & Alpha weighted Convolution */
+            if (((channel & SyncChannels) != 0 ) &&
+                      (image->matte == MagickTrue))
+              { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
+                ** Weight the color channels with Alpha Channel so that
+                ** transparent pixels are not part of the results.
+                */
                 MagickRealType
-                  alpha,  /* alpha value * kernel weighting */
-                  gamma;  /* weighting divisor */
+                  alpha,  /* color channel weighting : kernel*alpha  */
+                  gamma;  /* divisor, sum of weighting values */
 
                 gamma=0.0;
                 k = &kernel->values[ kernel->width*kernel->height-1 ];
@@ -1321,10 +1322,32 @@ static unsigned long MorphologyApply(const Image *image, Image
                 result.opacity *= gamma;
                 result.index *= gamma;
               }
+            else
+              {
+                /* No 'Sync' flag, or no Alpha involved.
+                ** Convolution is simple individual channel weigthed sum.
+                */
+                k = &kernel->values[ kernel->width*kernel->height-1 ];
+                k_pixels = p;
+                k_indexes = p_indexes;
+                for (v=0; v < (long) kernel->height; v++) {
+                  for (u=0; u < (long) kernel->width; u++, k--) {
+                    if ( IsNan(*k) ) continue;
+                    result.red     += (*k)*k_pixels[u].red;
+                    result.green   += (*k)*k_pixels[u].green;
+                    result.blue    += (*k)*k_pixels[u].blue;
+                    result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
+                    if ( image->colorspace == CMYKColorspace)
+                      result.index   += (*k)*k_indexes[u];
+                  }
+                  k_pixels += image->columns+kernel->width;
+                  k_indexes += image->columns+kernel->width;
+                }
+              }
             break;
 
         case ErodeMorphology:
-            /* Minimize Value within kernel neighbourhood
+            /* Minimum Value within kernel neighbourhood
             **
             ** NOTE that the kernel is not reflected for this operation!
             **
@@ -1338,21 +1361,22 @@ static unsigned long MorphologyApply(const Image *image, Image
             for (v=0; v < (long) kernel->height; v++) {
               for (u=0; u < (long) kernel->width; u++, k++) {
                 if ( IsNan(*k) || (*k) < 0.5 ) continue;
-                Minimize(result.red,     (double) k_pixels[u].red);
-                Minimize(result.green,   (double) k_pixels[u].green);
-                Minimize(result.blue,    (double) k_pixels[u].blue);
-                Minimize(result.opacity,
+                Minimize(min.red,     (double) k_pixels[u].red);
+                Minimize(min.green,   (double) k_pixels[u].green);
+                Minimize(min.blue,    (double) k_pixels[u].blue);
+                Minimize(min.opacity,
                             QuantumRange-(double) k_pixels[u].opacity);
                 if ( image->colorspace == CMYKColorspace)
-                  Minimize(result.index,   (double) k_indexes[u]);
+                  Minimize(min.index,   (double) k_indexes[u]);
               }
               k_pixels += image->columns+kernel->width;
               k_indexes += image->columns+kernel->width;
             }
             break;
 
+
         case DilateMorphology:
-            /* Maximize Value within kernel neighbourhood
+            /* Maximum Value within kernel neighbourhood
             **
             ** NOTE for correct working of this operation for asymetrical
             ** kernels, the kernel needs to be applied in its reflected form.
@@ -1369,19 +1393,70 @@ static unsigned long MorphologyApply(const Image *image, Image
             for (v=0; v < (long) kernel->height; v++) {
               for (u=0; u < (long) kernel->width; u++, k--) {
                 if ( IsNan(*k) || (*k) < 0.5 ) continue;
-                Maximize(result.red,     (double) k_pixels[u].red);
-                Maximize(result.green,   (double) k_pixels[u].green);
-                Maximize(result.blue,    (double) k_pixels[u].blue);
-                Maximize(result.opacity,
+                Maximize(max.red,     (double) k_pixels[u].red);
+                Maximize(max.green,   (double) k_pixels[u].green);
+                Maximize(max.blue,    (double) k_pixels[u].blue);
+                Maximize(max.opacity,
                             QuantumRange-(double) k_pixels[u].opacity);
                 if ( image->colorspace == CMYKColorspace)
-                  Maximize(result.index,   (double) k_indexes[u]);
+                  Maximize(max.index,   (double) k_indexes[u]);
               }
               k_pixels += image->columns+kernel->width;
               k_indexes += image->columns+kernel->width;
             }
             break;
 
+        case HitAndMissMorphology:
+        case ThinningMorphology:
+        case ThickenMorphology:
+            /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
+            **
+            ** NOTE that the kernel is not reflected for this operation,
+            ** and consists of both foreground and background pixel
+            ** 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.
+            */
+            k = kernel->values;
+            k_pixels = p;
+            k_indexes = p_indexes;
+            for (v=0; v < (long) kernel->height; v++) {
+              for (u=0; u < (long) kernel->width; u++, k++) {
+                if ( IsNan(*k) ) continue;
+                if ( (*k) > 0.7 )
+                { /* minimim of foreground pixels */
+                  Minimize(min.red,     (double) k_pixels[u].red);
+                  Minimize(min.green,   (double) k_pixels[u].green);
+                  Minimize(min.blue,    (double) k_pixels[u].blue);
+                  Minimize(min.opacity,
+                              QuantumRange-(double) k_pixels[u].opacity);
+                  if ( image->colorspace == CMYKColorspace)
+                    Minimize(min.index,   (double) k_indexes[u]);
+                }
+                else if ( (*k) < 0.3 )
+                { /* maximum of background pixels */
+                  Maximize(max.red,     (double) k_pixels[u].red);
+                  Maximize(max.green,   (double) k_pixels[u].green);
+                  Maximize(max.blue,    (double) k_pixels[u].blue);
+                  Maximize(max.opacity,
+                              QuantumRange-(double) k_pixels[u].opacity);
+                  if ( image->colorspace == CMYKColorspace)
+                    Maximize(max.index,   (double) k_indexes[u]);
+                }
+              }
+              k_pixels += image->columns+kernel->width;
+              k_indexes += image->columns+kernel->width;
+            }
+            /* Pattern Match  only if min fg larger than min bg pixels */
+            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 );
+            min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
+            min.index   -= max.index;   Maximize( min.index,   0.0 );
+            break;
+
         case ErodeIntensityMorphology:
             /* Select Pixel with Minimum Intensity within kernel neighbourhood
             **
@@ -1440,6 +1515,7 @@ static unsigned long MorphologyApply(const Image *image, Image
             }
             break;
 
+
         case DistanceMorphology:
             /* Add kernel Value and select the minimum value found.
             ** The result is a iterative distance from edge of image shape.
@@ -1448,6 +1524,9 @@ static unsigned long MorphologyApply(const Image *image, Image
             ** be the case. For example how about a distance from left edges?
             ** To work correctly with asymetrical kernels the reflected kernel
             ** needs to be applied.
+            **
+            ** Actually this is really a GreyErode with a negative kernel!
+            **
             */
 #if 0
             /* No need to do distance morphology if original value is zero
@@ -1486,13 +1565,48 @@ static unsigned long MorphologyApply(const Image *image, Image
         default:
             break; /* Do nothing */
       }
+      /* Final mathematics of results (combine with original image?)
+      **
+      ** NOTE: Difference Morphology operators Edge* and *Hat could also
+      ** be done here but works better with iteration as a image difference
+      ** in the controling function (below).  Thicken and Thinning however
+      ** should be done here so thay can be iterated correctly.
+      */
+      switch ( method ) {
+        case HitAndMissMorphology:
+        case ErodeMorphology:
+          result = min;    /* minimum of neighbourhood */
+          break;
+        case DilateMorphology:
+          result = max;    /* maximum of neighbourhood */
+          break;
+        case ThinningMorphology:
+          /* subtract pattern match from original */
+          result.red     -= min.red;
+          result.green   -= min.green;
+          result.blue    -= min.blue;
+          result.opacity -= min.opacity;
+          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 );
+          break;
+        default:
+          /* result directly calculated or assigned */
+          break;
+      }
+      /* Assign the resulting pixel values - Clamping Result */
       switch ( method ) {
         case UndefinedMorphology:
         case DilateIntensityMorphology:
         case ErodeIntensityMorphology:
           break;  /* full pixel was directly assigned - not a channel method */
         default:
-          /* Assign the results */
           if ((channel & RedChannel) != 0)
             q->red = ClampToQuantum(result.red);
           if ((channel & GreenChannel) != 0)
@@ -1507,6 +1621,7 @@ static unsigned long MorphologyApply(const Image *image, Image
             q_indexes[x] = ClampToQuantum(result.index);
           break;
       }
+      /* Count up changed pixels */
       if (   ( p[r].red != q->red )
           || ( p[r].green != q->green )
           || ( p[r].blue != q->blue )
index 09710886609958a24e34621575a19028e55892b8..0129273bc0ba7db3552dbb5eea6c5dcfe24fbfda 100644 (file)
@@ -61,12 +61,16 @@ typedef enum
   CloseMorphology,             /* Erode then Dilate */
   OpenIntensityMorphology,     /* Pixel Pick using GreyScale Open */
   CloseIntensityMorphology,    /* Pixel Pick using GreyScale Close */
-/* Third-level Morphology methods */
+/* Difference Morphology methods */
   EdgeInMorphology,            /* Dilate difference from Original */
   EdgeOutMorphology,           /* Erode difference from Original */
   EdgeMorphology,              /* Dilate difference with Erode */
   TopHatMorphology,            /* Close difference from Original */
-  BottomHatMorphology          /* Open difference from Original */
+  BottomHatMorphology,         /* Open difference from Original */
+/* Recursive Morphology methods */
+  HitAndMissMorphology,        /* Foreground/Background pattern matching */
+  ThinningMorphology,          /* Remove matching pixels from image */
+  ThickenMorphology            /* Add matching pixels from image */
 } MorphologyMethod;
 
 typedef struct
index a4c171b75b5c67ee38415a71974fe2dab6ede674..4cbfdbc2dc8afbaaa019f00bcf0faa298fee1e14 100644 (file)
@@ -1254,6 +1254,9 @@ static const OptionInfo
     { "TopHat", (long) TopHatMorphology, MagickFalse },
     { "BottomHat", (long) BottomHatMorphology, MagickFalse },
     { "Distance", (long) DistanceMorphology, MagickFalse },
+    { "HitAndMiss", (long) HitAndMissMorphology, MagickFalse },
+    { "Thinning", (long) ThinningMorphology, MagickFalse },
+    { "Thicken", (long) ThickenMorphology, MagickFalse },
     { (char *) NULL, (long) UndefinedMorphology, MagickFalse }
   },
   NoiseOptions[] =
index 76f6e374b7a1cc01a71120562ed40a95f9f35d74..c1f8bfa0b0370add2b764e4351dc472998a1b3d0 100644 (file)
@@ -2245,7 +2245,12 @@ WandExport MagickBooleanType MogrifyImage(ImageInfo *image_info,const int argc,
               iterations=StringToLong(p);
             kernel=AcquireKernelInfo(argv[i+2]);
             if (kernel == (KernelInfo *) NULL)
+            {
+              (void) ThrowMagickException(exception,GetMagickModule(),
+                OptionError,"UnabletoParseKernel","morphology");
+              status=MagickFalse;
               break;
+            }
             if ( GetImageArtifact(*image,"showkernel") != (const char *) NULL)
               ShowKernelInfo(kernel);  /* display the kernel to stderr */
             morphology_image=MorphologyImageChannel(*image,channel,method,