changeset 3258:6f652c7bfc85

ImageProcessing::FillPolygon
author Alain Mazy <alain@mazy.be>
date Mon, 18 Feb 2019 18:43:40 +0100
parents 5d1f998b0b18
children 6f9398eb902d
files Core/Images/ImageProcessing.cpp Core/Images/ImageProcessing.h UnitTestsSources/ImageProcessingTests.cpp
diffstat 3 files changed, 188 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Core/Images/ImageProcessing.cpp	Mon Feb 18 12:18:15 2019 +0100
+++ b/Core/Images/ImageProcessing.cpp	Mon Feb 18 18:43:40 2019 +0100
@@ -43,6 +43,7 @@
 #include <string.h>
 #include <limits>
 #include <stdint.h>
+#include <algorithm>
 
 namespace Orthanc
 {
@@ -1364,4 +1365,144 @@
       throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
   }
+
+  void ComputePolygonExtent(int32_t& left, int32_t& right, int32_t& top, int32_t& bottom, const std::vector<ImageProcessing::ImagePoint>& points)
+  {
+    left = std::numeric_limits<int32_t>::max();
+    right = std::numeric_limits<int32_t>::min();
+    top = std::numeric_limits<int32_t>::max();
+    bottom = std::numeric_limits<int32_t>::min();
+
+    for (size_t i = 0; i < points.size(); i++)
+    {
+      const ImageProcessing::ImagePoint& p = points[i];
+      left = std::min(p.GetX(), left);
+      right = std::max(p.GetX(), right);
+      bottom = std::max(p.GetY(), bottom);
+      top = std::min(p.GetY(), top);
+    }
+  }
+
+  template <PixelFormat TargetFormat>
+  void FillPolygon_(ImageAccessor& image,
+              const std::vector<ImageProcessing::ImagePoint>& points,
+              int64_t value_)
+  {
+    typedef typename PixelTraits<TargetFormat>::PixelType  TargetType;
+
+    TargetType value = PixelTraits<TargetFormat>::IntegerToPixel(value_);
+    int32_t left;
+    int32_t right;
+    int32_t top;
+    int32_t bottom;
+
+    ComputePolygonExtent(left, right, top, bottom, points);
+
+    // from http://alienryderflex.com/polygon_fill/
+
+    // convert all "corner"  points to double only once
+    std::vector<double> cpx;
+    std::vector<double> cpy;
+    size_t cpSize = points.size();
+    for (size_t i = 0; i < points.size(); i++)
+    {
+      cpx.push_back((double)points[i].GetX());
+      cpy.push_back((double)points[i].GetY());
+    }
+
+    std::vector<int32_t> nodeX;
+    nodeX.resize(cpSize);
+    int  nodes, pixelX, pixelY, i, j, swap ;
+
+    //  Loop through the rows of the image.
+    for (pixelY = top; pixelY < bottom; pixelY++)
+    {
+      double y = (double)pixelY;
+      //  Build a list of nodes.
+      nodes = 0;
+      j = static_cast<int>(cpSize) - 1;
+
+      for (i = 0; i < cpSize; i++)
+      {
+        if ((cpy[i] < y && cpy[j] >=  y) || (cpy[j] < y && cpy[i] >= y))
+        {
+          nodeX[nodes++] = (int32_t)(cpx[i] + (y - cpy[i])/(cpy[j] - cpy[i]) * (cpx[j] - cpx[i]));
+        }
+        j=i;
+      }
+
+      //  Sort the nodes, via a simple “Bubble” sort.
+      i=0;
+      while (i < nodes-1)
+      {
+        if (nodeX[i] > nodeX[i+1])
+        {
+          swap = nodeX[i];
+          nodeX[i] = nodeX[i+1];
+          nodeX[i+1] = swap;
+          if (i > 0)
+          {
+            i--;
+          }
+        }
+        else
+        {
+          i++;
+        }
+      }
+
+      TargetType* row = reinterpret_cast<TargetType*>(image.GetRow(pixelY));
+      //  Fill the pixels between node pairs.
+      for (i=0; i<nodes; i+=2)
+      {
+        if (nodeX[i] >= right)
+          break;
+
+        if (nodeX[i+1] >= left)
+        {
+          if (nodeX[i]< left)
+          {
+            nodeX[i] = left;
+          }
+
+          if (nodeX[i+1] > right)
+          {
+            nodeX[i+1] = right;
+          }
+
+          for (pixelX = nodeX[i]; pixelX <= nodeX[i+1]; pixelX++)
+          {
+            *(row + pixelX) = value;
+          }
+        }
+      }
+    }
+  }
+
+  void ImageProcessing::FillPolygon(ImageAccessor& image,
+                                    const std::vector<ImagePoint>& points,
+                                    int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+    case Orthanc::PixelFormat_Grayscale8:
+    {
+      FillPolygon_<Orthanc::PixelFormat_Grayscale8>(image, points, value);
+      break;
+    }
+    case Orthanc::PixelFormat_Grayscale16:
+    {
+      FillPolygon_<Orthanc::PixelFormat_Grayscale16>(image, points, value);
+      break;
+    }
+    case Orthanc::PixelFormat_SignedGrayscale16:
+    {
+      FillPolygon_<Orthanc::PixelFormat_SignedGrayscale16>(image, points, value);
+      break;
+    }
+    default:
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
 }
--- a/Core/Images/ImageProcessing.h	Mon Feb 18 12:18:15 2019 +0100
+++ b/Core/Images/ImageProcessing.h	Mon Feb 18 18:43:40 2019 +0100
@@ -34,6 +34,7 @@
 #pragma once
 
 #include "ImageAccessor.h"
+#include <vector>
 
 #include <stdint.h>
 
@@ -41,6 +42,22 @@
 {
   namespace ImageProcessing
   {
+    class ImagePoint
+    {
+      int32_t x_;
+      int32_t y_;
+      
+     public:
+      ImagePoint(int32_t x, int32_t y)
+        : x_(x),
+          y_(y)
+      {
+      }
+
+      int32_t GetX() const {return x_;}
+      int32_t GetY() const {return y_;}
+    };
+
     void Copy(ImageAccessor& target,
               const ImageAccessor& source);
 
@@ -101,5 +118,9 @@
                          uint8_t green,
                          uint8_t blue,
                          uint8_t alpha);
+
+    void FillPolygon(ImageAccessor& image,
+                     const std::vector<ImagePoint>& points,
+                     int64_t value);
   }
 }
--- a/UnitTestsSources/ImageProcessingTests.cpp	Mon Feb 18 12:18:15 2019 +0100
+++ b/UnitTestsSources/ImageProcessingTests.cpp	Mon Feb 18 18:43:40 2019 +0100
@@ -180,7 +180,7 @@
   
   memset(image.GetBuffer(), 128, image.GetHeight() * image.GetWidth());
 
-  unsigned int c = 0;
+  float c = 0.0f;
   for (unsigned int y = 0; y < image.GetHeight(); y++)
   {
     for (unsigned int x = 0; x < image.GetWidth(); x++, c++)
@@ -189,7 +189,6 @@
     }
   }
 
-  c = 0;
   for (unsigned int y = 0; y < image.GetHeight(); y++)
   {
     for (unsigned int x = 0; x < image.GetWidth(); x++, c++)
@@ -198,3 +197,28 @@
     }
   }
 }
+
+TYPED_TEST(TestIntegerImageTraits, FillPolygon)
+{
+  ImageAccessor& image = this->GetImage();
+
+  ImageProcessing::Set(image, 128);
+
+  // draw a triangle
+  std::vector<ImageProcessing::ImagePoint> points;
+  points.push_back(ImageProcessing::ImagePoint(1,1));
+  points.push_back(ImageProcessing::ImagePoint(1,5));
+  points.push_back(ImageProcessing::ImagePoint(5,5));
+
+  Orthanc::ImageProcessing::FillPolygon(image, points, 255);
+
+  // outside polygon
+  ASSERT_FLOAT_EQ(128, TestFixture::ImageTraits::GetFloatPixel(image, 0, 0));
+  ASSERT_FLOAT_EQ(128, TestFixture::ImageTraits::GetFloatPixel(image, 0, 6));
+  ASSERT_FLOAT_EQ(128, TestFixture::ImageTraits::GetFloatPixel(image, 6, 6));
+  ASSERT_FLOAT_EQ(128, TestFixture::ImageTraits::GetFloatPixel(image, 6, 0));
+
+  // inside polygon (note: we don't test too close from the edges since the current algo is taking some margin from the edges and might be improved in that sense)
+  ASSERT_FLOAT_EQ(255, TestFixture::ImageTraits::GetFloatPixel(image, 1, 2));
+  ASSERT_FLOAT_EQ(255, TestFixture::ImageTraits::GetFloatPixel(image, 2, 4));
+}