]> granicus.if.org Git - imagemagick/blobdiff - MagickCore/effect.c
(no commit message)
[imagemagick] / MagickCore / effect.c
index 5b16f6348027ecb42bf8d4828491e5c8e6bf431d..57433ba078796b65c571060ffd56c1e72d09d502 100644 (file)
@@ -862,8 +862,8 @@ MagickExport Image *BlurImage(const Image *image,const double radius,
 %  The format of the EdgeImage method is:
 %
 %      Image *CannyEdgeImage(const Image *image,const double radius,
-%        const double sigma,const double low_percent,const double high_percent,
-%        const size_t threshold,ExceptionInfo *exception)
+%        const double sigma,const double lower_precent,
+%        const double upper_percent,ExceptionInfo *exception)
 %
 %  A description of each parameter follows:
 %
@@ -875,9 +875,9 @@ MagickExport Image *BlurImage(const Image *image,const double radius,
 %
 %    o sigma: the sigma of the gaussian smoothing filter.
 %
-%    o low_percent: percentage of pixels in the low threshold.
+%    o lower_precent: percentage of edge pixels in the lower threshold.
 %
-%    o high_percent: percentage of pixels in the high threshold.
+%    o upper_percent: percentage of edge pixels in the upper threshold.
 %
 %    o exception: return any errors or warnings in this structure.
 %
@@ -886,14 +886,19 @@ MagickExport Image *BlurImage(const Image *image,const double radius,
 typedef struct _CannyInfo
 {
   double
-    Dx,
-    Dy,
     magnitude,
     intensity;
+
+  int
+    orientation;
+
+  ssize_t
+    x,
+    y;
 } CannyInfo;
 
-static MagickBooleanType IsAuthenticPixel(const Image *image,const ssize_t x,
-  const ssize_t y)
+static inline MagickBooleanType IsAuthenticPixel(const Image *image,
+  const ssize_t x,const ssize_t y)
 {
   if ((x < 0) || (x >= (ssize_t) image->columns))
     return(MagickFalse);
@@ -902,69 +907,106 @@ static MagickBooleanType IsAuthenticPixel(const Image *image,const ssize_t x,
   return(MagickTrue);
 }
 
-static MagickBooleanType TraceEdge(Image *edge_image,CacheView *edge_view,
+static MagickBooleanType TraceEdges(Image *edge_image,CacheView *trace_view,
   MatrixInfo *pixel_cache,const ssize_t x,const ssize_t y,
-  const double threshold,ExceptionInfo *exception)
+  const double lower_threshold,ExceptionInfo *exception)
 {
   CannyInfo
     pixel;
 
-  Quantum
-    *q;
+  size_t
+    number_edges;
 
-  MagickBooleanType
-    status;
+  if (GetMatrixElement(pixel_cache,0,0,&pixel) == MagickFalse)
+    return(MagickFalse);
+  pixel.x=x;
+  pixel.y=y;
+  if (SetMatrixElement(pixel_cache,0,0,&pixel) == MagickFalse)
+    return(MagickFalse);
+  number_edges=1;
+  do
+  {
+    MagickBooleanType
+      status;
+
+    ssize_t
+      v,
+      x_offset,
+      y_offset;
 
-  q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception);
-  if ((q != (Quantum *) NULL) && (GetPixelIntensity(edge_image,q) == 0))
+    number_edges--;
+    status=GetMatrixElement(pixel_cache,(ssize_t) number_edges,0,&pixel);
+    if (status == MagickFalse)
+      return(MagickFalse);
+    x_offset=pixel.x;
+    y_offset=pixel.y;
+    for (v=(-1); v <= 1; v++)
     {
       ssize_t
-        v;
+        u;
 
-      *q=QuantumRange;
-      status=SyncCacheViewAuthenticPixels(edge_view,exception);
-      if (status != MagickFalse)
-        {
-          for (v=(-1); v <= 1; v++)
-          {
-            ssize_t
-              u;
+      for (u=(-1); u <= 1; u++)
+      {
+        Quantum
+          *q;
 
-            for (u=(-1); u <= 1; u++)
-            {
-              if (((y != 0) || (u != 0)) &&
-                  (IsAuthenticPixel(edge_image,x+u,y+v) != MagickFalse))
-                {
-                  (void) GetMatrixElement(pixel_cache,x+u,y+v,&pixel);
-                  if (pixel.intensity >= threshold)
-                    {
-                      status=TraceEdge(edge_image,edge_view,pixel_cache,x+u,y+v,
-                        threshold,exception);
-                      if (status != MagickFalse)
-                        return(MagickTrue);
-                    }
-                }
-            }
+        if ((u == 0) && (v == 0))
+          continue;
+        if (IsAuthenticPixel(edge_image,x_offset+u,y_offset+v) == MagickFalse)
+          continue;
+        /*
+          Not an edge if gradient value is below the lower threshold.
+        */
+        q=GetCacheViewAuthenticPixels(trace_view,x_offset+u,y_offset+v,1,1,
+          exception);
+        if (q == (Quantum *) NULL)
+          return(MagickFalse);
+        status=GetMatrixElement(pixel_cache,x_offset+u,y_offset+v,&pixel);
+        if (status == MagickFalse)
+          return(MagickFalse);
+        if ((pixel.intensity >= lower_threshold) &&
+            (GetPixelIntensity(edge_image,q) == 0))
+          {
+            *q=QuantumRange;
+            status=SyncCacheViewAuthenticPixels(trace_view,exception);
+            if (status == MagickFalse)
+              return(MagickFalse);
+            status=GetMatrixElement(pixel_cache,(ssize_t) number_edges,0,
+              &pixel);
+            if (status == MagickFalse)
+              return(MagickFalse);
+            pixel.x=x_offset+u;
+            pixel.y=y_offset+v;
+            status=SetMatrixElement(pixel_cache,(ssize_t) number_edges,0,
+              &pixel);
+            if (status == MagickFalse)
+              return(MagickFalse);
+            number_edges++;
           }
-          return(MagickTrue);
-        }
+      }
     }
-  return(MagickFalse);
+  } while (number_edges != 0);
+  return(MagickTrue);
 }
 
+
 MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
-  const double sigma,const double low_percent,const double high_percent,
+  const double sigma,const double lower_precent,const double upper_percent,
   ExceptionInfo *exception)
 {
   CacheView
-    *edge_view;
+    *edge_view,
+    *trace_view;
+
+  CannyInfo
+    pixel;
 
   char
     geometry[MaxTextExtent];
 
   double
-    high_threshold,
-    low_threshold;
+    lower_threshold,
+    upper_threshold;
 
   Image
     *edge_image;
@@ -996,7 +1038,7 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
   assert(exception != (ExceptionInfo *) NULL);
   assert(exception->signature == MagickSignature);
   /*
-    Filter out noise before trying to locate and detect any edges.
+    Filter out noise.
   */
   (void) FormatLocaleString(geometry,MaxTextExtent,
     "blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma);
@@ -1014,7 +1056,7 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
       return((Image *) NULL);
     }
   /*
-    Find the edge strength by taking the gradient of the image.
+    Find the intensity gradient of the image.
   */
   pixel_cache=AcquireMatrixInfo(edge_image->columns,edge_image->rows,
     sizeof(CannyInfo),exception);
@@ -1039,7 +1081,7 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
 
     if (status == MagickFalse)
       continue;
-    p=GetCacheViewVirtualPixels(edge_view,-1,y-1,edge_image->columns+2,3,
+    p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns+1,2,
       exception);
     if (p == (const Quantum *) NULL)
       {
@@ -1051,6 +1093,13 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
       CannyInfo
         pixel;
 
+      double
+        dx,
+        dy;
+
+      int
+        orientation;
+
       register const Quantum
         *restrict kernel_pixels;
 
@@ -1058,38 +1107,66 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
         v;
 
       static double
-        Gx[3][3] =
+        Gx[2][2] =
         {
-          { -1.0,  0.0, +1.0 },
-          { -2.0,  0.0, +2.0 },
-          { -1.0,  0.0, +1.0 }
+          { -1.0,  +1.0 },
+          { -1.0,  +1.0 }
         },
-        Gy[3][3] =
+        Gy[2][2] =
         {
-          { +1.0, +2.0, +1.0 },
-          {  0.0,  0.0,  0.0 },
-          { -1.0, -2.0, -1.0 }
+          { +1.0, +1.0 },
+          { -1.0, -1.0 }
         };
 
       (void) ResetMagickMemory(&pixel,0,sizeof(pixel));
+      dx=0.0;
+      dy=0.0;
       kernel_pixels=p;
-      for (v=0; v < 3; v++)
+      for (v=0; v < 2; v++)
       {
         ssize_t
           u;
 
-        for (u=0; u < 3; u++)
+        for (u=0; u < 2; u++)
         {
           double
             intensity;
 
           intensity=GetPixelIntensity(edge_image,kernel_pixels+u);
-          pixel.Dx+=Gx[v][u]*intensity;
-          pixel.Dy+=Gy[v][u]*intensity;
+          dx+=0.5*Gx[v][u]*intensity;
+          dy+=0.5*Gy[v][u]*intensity;
         }
-        kernel_pixels+=edge_image->columns+2;
+        kernel_pixels+=edge_image->columns+1;
       }
-      pixel.magnitude=sqrt(pixel.Dx*pixel.Dx+pixel.Dy*pixel.Dy);
+      pixel.magnitude=sqrt(dx*dx+dy*dy);
+      pixel.orientation=2;
+      if (fabs(dx) > MagickEpsilon)
+        {
+          double
+            theta;
+
+          theta=dy/dx;
+          if (theta < 0.0)
+            {
+              if (theta < -2.41421356237)
+                pixel.orientation=2;
+              else
+                if (theta < -0.414213562373)
+                  pixel.orientation=1;
+                else
+                  pixel.orientation=0;
+            }
+          else
+            {
+              if (theta > 2.41421356237)
+                pixel.orientation=2;
+              else
+                if (theta > 0.414213562373)
+                  pixel.orientation=3;
+                else
+                  pixel.orientation=0;
+            }
+        }
       if (SetMatrixElement(pixel_cache,x,y,&pixel) == MagickFalse)
         continue;
       p+=GetPixelChannels(edge_image);
@@ -1097,16 +1174,12 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
   }
   edge_view=DestroyCacheView(edge_view);
   /*
-    Non-maxima suppression; reset edge image.
+    Non-maxima suppression, remove pixels that are not considered to be part
+    of an edge.
   */
-  histogram=(size_t *) AcquireQuantumMemory(65536,sizeof(*histogram));
-  if (histogram == (size_t *) NULL)
-    {
-      pixel_cache=DestroyMatrixInfo(pixel_cache);
-      edge_image=DestroyImage(edge_image);
-      return((Image *) NULL);
-    }
-  (void) ResetMagickMemory(histogram,0,65536*sizeof(*histogram));
+  (void) GetMatrixElement(pixel_cache,0,0,&pixel);
+  max=pixel.intensity;
+  min=pixel.intensity;
   edge_view=AcquireAuthenticCacheView(edge_image,exception);
 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   #pragma omp parallel for schedule(static,4) shared(status) \
@@ -1136,47 +1209,16 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
         beta_pixel,
         pixel;
 
-      ssize_t
-        direction;
-
       (void) GetMatrixElement(pixel_cache,x,y,&pixel);
-      direction=2;
-      if (pixel.Dx != 0.0)
-        {
-          double
-            sector;
-
-          sector=pixel.Dy/pixel.Dx;
-          if (sector < 0.0)
-            {
-              if (sector < -2.41421356237)
-                direction=0;
-              else
-                if (sector < -0.414213562373)
-                  direction=1;
-                else
-                  direction=2;
-            }
-          else
-            {
-              if (sector > 2.41421356237)
-                direction=0;
-              else
-                if (sector > 0.414213562373)
-                  direction=3;
-                else
-                  direction=2;
-            }
-        }
-      switch (direction)
+      switch (pixel.orientation)
       {
         case 0:
         {
           /*
             0 degrees.
           */
-          (void) GetMatrixElement(pixel_cache,x,y-1,&alpha_pixel);
-          (void) GetMatrixElement(pixel_cache,x,y+1,&beta_pixel);
+          (void) GetMatrixElement(pixel_cache,x-1,y,&alpha_pixel);
+          (void) GetMatrixElement(pixel_cache,x+1,y,&beta_pixel);
           break;
         }
         case 1:
@@ -1193,30 +1235,34 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
           /*
             90 degrees.
           */
-          (void) GetMatrixElement(pixel_cache,x-1,y,&alpha_pixel);
-          (void) GetMatrixElement(pixel_cache,x+1,y,&beta_pixel);
+          (void) GetMatrixElement(pixel_cache,x,y-1,&alpha_pixel);
+          (void) GetMatrixElement(pixel_cache,x,y+1,&beta_pixel);
+          break;
         }
         case 3:
         {
           /*
             135 degrees.
           */
-          (void) GetMatrixElement(pixel_cache,x+1,y-1,&alpha_pixel);
-          (void) GetMatrixElement(pixel_cache,x-1,y+1,&beta_pixel);
+          (void) GetMatrixElement(pixel_cache,x-1,y+1,&alpha_pixel);
+          (void) GetMatrixElement(pixel_cache,x+1,y-1,&beta_pixel);
+          break;
         }
       }
       pixel.intensity=pixel.magnitude;
       if ((pixel.magnitude < alpha_pixel.magnitude) ||
           (pixel.magnitude < beta_pixel.magnitude))
         pixel.intensity=0;
-      else
-        if (pixel.magnitude > QuantumRange)
-          pixel.intensity=QuantumRange;
       (void) SetMatrixElement(pixel_cache,x,y,&pixel);
 #if defined(MAGICKCORE_OPENMP_SUPPORT)
       #pragma omp critical (MagickCore_CannyEdgeImage)
 #endif
-      histogram[ScaleQuantumToShort(ClampToQuantum(pixel.intensity))]++;
+      {
+        if (pixel.intensity < min)
+          min=pixel.intensity;
+        if (pixel.intensity > max)
+          max=pixel.intensity;
+      }
       *q=0;
       q+=GetPixelChannels(edge_image);
     }
@@ -1227,54 +1273,61 @@ MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
   /*
     Estimate hysteresis threshold.
   */
-  number_pixels=(size_t) (low_percent*(image->columns*image->rows-
-    histogram[0]));
-  count=0;
-  for (i=65535; count < (ssize_t) number_pixels; i--)
-    count+=histogram[i];
-  high_threshold=(double) ScaleShortToQuantum((unsigned short) i);
-  for (i=0; histogram[i] == 0; i++) ;
-  low_threshold=high_percent*(high_threshold+
-    ScaleShortToQuantum((unsigned short) i));
-  histogram=(size_t *) RelinquishMagickMemory(histogram);
+  lower_threshold=lower_percent*(max-min)+min;
+  upper_threshold=upper_percent*(max-min)+min;
   /*
-    Hysteresis thresholding.
+    Hysteresis threshold.
   */
   edge_view=AcquireAuthenticCacheView(edge_image,exception);
+  trace_view=AcquireAuthenticCacheView(edge_image,exception);
 #if defined(MAGICKCORE_OPENMP_SUPPORT)
   #pragma omp parallel for schedule(static,4) shared(status) \
     magick_threads(edge_image,edge_image,edge_image->rows,1)
 #endif
   for (y=0; y < (ssize_t) edge_image->rows; y++)
   {
-    register Quantum
-      *restrict q;
-
     register ssize_t
       x;
 
     if (status == MagickFalse)
       continue;
-    q=GetCacheViewAuthenticPixels(edge_view,0,y,edge_image->columns,1,
-      exception);
-    if (q == (Quantum *) NULL)
-      {
-        status=MagickFalse;
-        continue;
-      }
     for (x=0; x < (ssize_t) edge_image->columns; x++)
     {
       CannyInfo
         pixel;
 
-      (void) GetMatrixElement(pixel_cache,x,y,&pixel);
-      if (pixel.intensity >= high_threshold)
-        (void) TraceEdge(edge_image,edge_view,pixel_cache,x,y,low_threshold,
-          exception);
+      register Quantum
+        *restrict q;
+
+      /*
+        Edge if pixel gradient higher than upper threshold.
+      */
+      status=GetMatrixElement(pixel_cache,x,y,&pixel);
+      if (status == MagickFalse)
+        break;
+      q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception);
+      if (q == (PixelPacket *) NULL)
+        {
+          status=MagickFalse;
+          continue;
+        }
+      if ((pixel.intensity >= upper_threshold) &&
+          (GetPixelIntensity(edge_image,q) == 0))
+        {
+          *q=QuantumRange;
+          status=SyncCacheViewAuthenticPixels(edge_view,exception);
+          if (status == MagickFalse)
+            continue;
+          status=TraceEdges(edge_image,trace_view,pixel_cache,x,y,
+            lower_threshold,exception);
+          if (status == MagickFalse)
+            continue;
+        }
     }
     if (SyncCacheViewAuthenticPixels(edge_view,exception) == MagickFalse)
       status=MagickFalse;
   }
+  trace_view=DestroyCacheView(trace_view);
   edge_view=DestroyCacheView(edge_view);
   pixel_cache=DestroyMatrixInfo(pixel_cache);
   return(edge_image);