Skip to content

Commit

Permalink
Merge pull request opencv#23076 from inayd:22012-bugfixFillPoly
Browse files Browse the repository at this point in the history
  • Loading branch information
alalek committed Jan 28, 2023
2 parents bd9d60c + 54449b6 commit c67d4fc
Show file tree
Hide file tree
Showing 2 changed files with 277 additions and 15 deletions.
59 changes: 44 additions & 15 deletions modules/imgproc/src/drawing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ CollectPolyEdges( Mat& img, const Point2l* v, int npts,
int shift, Point offset=Point() );

static void
FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color );
FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color, int line_type);

static void
PolyLine( Mat& img, const Point2l* v, int npts, bool closed,
Expand Down Expand Up @@ -1031,7 +1031,7 @@ EllipseEx( Mat& img, Point2l center, Size2l axes,
v.push_back(center);
std::vector<PolyEdge> edges;
CollectPolyEdges( img, &v[0], (int)v.size(), edges, color, line_type, XY_SHIFT );
FillEdgeCollection( img, edges, color );
FillEdgeCollection( img, edges, color, line_type );
}
}

Expand Down Expand Up @@ -1259,37 +1259,60 @@ CollectPolyEdges( Mat& img, const Point2l* v, int count, std::vector<PolyEdge>&
pt1.x = (pt1.x + offset.x) << (XY_SHIFT - shift);
pt1.y = (pt1.y + delta) >> shift;

if( line_type < CV_AA )
Point2l pt0c(pt0), pt1c(pt1);

if (line_type < CV_AA)
{
t0.y = pt0.y; t1.y = pt1.y;
t0.x = (pt0.x + (XY_ONE >> 1)) >> XY_SHIFT;
t1.x = (pt1.x + (XY_ONE >> 1)) >> XY_SHIFT;
Line( img, t0, t1, color, line_type );
Line(img, t0, t1, color, line_type);

// use clipped endpoints to create a more accurate PolyEdge
if ((unsigned)t0.x >= (unsigned)(img.cols) ||
(unsigned)t1.x >= (unsigned)(img.cols) ||
(unsigned)t0.y >= (unsigned)(img.rows) ||
(unsigned)t1.y >= (unsigned)(img.rows))
{
clipLine(img.size(), t0, t1);

if (t0.y != t1.y)
{
pt0c.y = t0.y; pt1c.y = t1.y;
pt0c.x = (int64)(t0.x) << XY_SHIFT;
pt1c.x = (int64)(t1.x) << XY_SHIFT;
}
}
else
{
pt0c.x += XY_ONE >> 1;
pt1c.x += XY_ONE >> 1;
}
}
else
{
t0.x = pt0.x; t1.x = pt1.x;
t0.y = pt0.y << XY_SHIFT;
t1.y = pt1.y << XY_SHIFT;
LineAA( img, t0, t1, color );
LineAA(img, t0, t1, color);
}

if( pt0.y == pt1.y )
if (pt0.y == pt1.y)
continue;

if( pt0.y < pt1.y )
edge.dx = (pt1c.x - pt0c.x) / (pt1c.y - pt0c.y);
if (pt0.y < pt1.y)
{
edge.y0 = (int)(pt0.y);
edge.y1 = (int)(pt1.y);
edge.x = pt0.x;
edge.x = pt0c.x + (pt0.y - pt0c.y) * edge.dx; // correct starting point for clipped lines
}
else
{
edge.y0 = (int)(pt1.y);
edge.y1 = (int)(pt0.y);
edge.x = pt1.x;
edge.x = pt1c.x + (pt1.y - pt1c.y) * edge.dx; // correct starting point for clipped lines
}
edge.dx = (pt1.x - pt0.x) / (pt1.y - pt0.y);
edges.push_back(edge);
}
}
Expand All @@ -1306,7 +1329,7 @@ struct CmpEdges
/**************** helper macros and functions for sequence/contour processing ***********/

static void
FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color )
FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color, int line_type)
{
PolyEdge tmp;
int i, y, total = (int)edges.size();
Expand All @@ -1315,6 +1338,12 @@ FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color )
int y_max = INT_MIN, y_min = INT_MAX;
int64 x_max = 0xFFFFFFFFFFFFFFFF, x_min = 0x7FFFFFFFFFFFFFFF;
int pix_size = (int)img.elemSize();
int delta;

if (line_type < CV_AA)
delta = 0;
else
delta = XY_ONE - 1;

if( total < 2 )
return;
Expand Down Expand Up @@ -1394,12 +1423,12 @@ FillEdgeCollection( Mat& img, std::vector<PolyEdge>& edges, const void* color )

if (keep_prelast->x > prelast->x)
{
x1 = (int)((prelast->x + XY_ONE - 1) >> XY_SHIFT);
x1 = (int)((prelast->x + delta) >> XY_SHIFT);
x2 = (int)(keep_prelast->x >> XY_SHIFT);
}
else
{
x1 = (int)((keep_prelast->x + XY_ONE - 1) >> XY_SHIFT);
x1 = (int)((keep_prelast->x + delta) >> XY_SHIFT);
x2 = (int)(prelast->x >> XY_SHIFT);
}

Expand Down Expand Up @@ -1995,7 +2024,7 @@ void fillPoly( Mat& img, const Point** pts, const int* npts, int ncontours,
CollectPolyEdges(img, _pts.data(), npts[i], edges, buf, line_type, shift, offset);
}

FillEdgeCollection(img, edges, buf);
FillEdgeCollection(img, edges, buf, line_type);
}


Expand Down Expand Up @@ -2654,7 +2683,7 @@ cvDrawContours( void* _img, CvSeq* contour,
}

if( thickness < 0 )
cv::FillEdgeCollection( img, edges, ext_buf );
cv::FillEdgeCollection( img, edges, ext_buf, line_type);

if( h_next && contour0 )
contour0->h_next = h_next;
Expand Down
233 changes: 233 additions & 0 deletions modules/imgproc/test/test_drawing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -632,4 +632,237 @@ TEST(Drawing, fillpoly_circle)
EXPECT_LT(diff_fp3, 1.);
}

TEST(Drawing, fillpoly_fully)
{
unsigned imageWidth = 256;
unsigned imageHeight = 256;
int type = CV_8UC1;
int shift = 0;
Point offset(0, 0);
cv::LineTypes lineType = LINE_4;

int imageSizeOffset = 15;

cv::Mat img(imageHeight, imageWidth, type);
img = 0;

std::vector<cv::Point> polygonPoints;
polygonPoints.push_back(cv::Point(100, -50));
polygonPoints.push_back(cv::Point(imageSizeOffset, imageHeight - imageSizeOffset));
polygonPoints.push_back(cv::Point(imageSizeOffset, imageSizeOffset));

// convert data
std::vector<const cv::Point*> polygonPointPointers(polygonPoints.size());
for (size_t i = 0; i < polygonPoints.size(); i++)
{
polygonPointPointers[i] = &polygonPoints[i];
}

const cv::Point** data = &polygonPointPointers.front();
int size = (int)polygonPoints.size();
const int* npts = &size;
int ncontours = 1;

// generate image
cv::fillPoly(img, data, npts, ncontours, 255, lineType, shift, offset);

// check for artifacts
{
cv::Mat binary = img < 128;
cv::Mat labelImage(binary.size(), CV_32S);
cv::Mat labelCentroids;
int labels = cv::connectedComponents(binary, labelImage, 4);
EXPECT_EQ(2, labels) << "artifacts occured";
}

// check if filling went over border
{
int xy_shift = 16, delta = offset.y + ((1 << shift) >> 1);
int xy_one = 1 << xy_shift;

Point pt0(polygonPoints[polygonPoints.size() - 1]), pt1;
for (size_t i = 0; i < polygonPoints.size(); i++, pt0 = pt1)
{
pt1 = polygonPoints[i];

// offset/shift treated like in fillPoly
Point t0(pt0), t1(pt1);

t0.x = (t0.x + offset.x) << (xy_shift - shift);
t0.y = (t0.y + delta) >> shift;

t1.x = (t1.x + offset.x) << (xy_shift - shift);
t1.y = (t1.y + delta) >> shift;

if (lineType < CV_AA)
{
t0.x = (t0.x + (xy_one >> 1)) >> xy_shift;
t1.x = (t1.x + (xy_one >> 1)) >> xy_shift;

// LINE_4 to use the same type of line which is used in fillPoly
line(img, t0, t1, 0, 1, LINE_4, 0);
}
else
{
t0.x >>= (xy_shift);
t1.x >>= (xy_shift);
line(img, t0, t1, 0, 1, lineType, 0);
}

}
cv::Mat binary = img < 254;
cv::Mat labelImage(binary.size(), CV_32S);
int labels = cv::connectedComponents(binary, labelImage, 4);
EXPECT_EQ(2, labels) << "filling went over the border";
}
}

PARAM_TEST_CASE(FillPolyFully, unsigned, unsigned, int, int, Point, cv::LineTypes)
{
unsigned imageWidth;
unsigned imageHeight;
int type;
int shift;
Point offset;
cv::LineTypes lineType;

virtual void SetUp()
{
imageWidth = GET_PARAM(0);
imageHeight = GET_PARAM(1);
type = GET_PARAM(2);
shift = GET_PARAM(3);
offset = GET_PARAM(4);
lineType = GET_PARAM(5);
}

void draw_polygon(cv::Mat& img, const std::vector<cv::Point>& polygonPoints)
{
// convert data
std::vector<const cv::Point*> polygonPointPointers(polygonPoints.size());
for (size_t i = 0; i < polygonPoints.size(); i++)
{
polygonPointPointers[i] = &polygonPoints[i];
}

const cv::Point** data = &polygonPointPointers.front();
int size = (int)polygonPoints.size();
const int* npts = &size;
int ncontours = 1;

// generate image
cv::fillPoly(img, data, npts, ncontours, 255, lineType, shift, offset);
}

void check_artifacts(cv::Mat& img)
{
// check for artifacts
cv::Mat binary = img < 128;
cv::Mat labelImage(binary.size(), CV_32S);
cv::Mat labelCentroids;
int labels = cv::connectedComponents(binary, labelImage, 4);
EXPECT_EQ(2, labels) << "artifacts occured";
}

void check_filling_over_border(cv::Mat& img, const std::vector<cv::Point>& polygonPoints)
{
int xy_shift = 16, delta = offset.y + ((1 << shift) >> 1);
int xy_one = 1 << xy_shift;

Point pt0(polygonPoints[polygonPoints.size() - 1]), pt1;
for (size_t i = 0; i < polygonPoints.size(); i++, pt0 = pt1)
{
pt1 = polygonPoints[i];

// offset/shift treated like in fillPoly
Point t0(pt0), t1(pt1);

t0.x = (t0.x + offset.x) << (xy_shift - shift);
t0.y = (t0.y + delta) >> shift;

t1.x = (t1.x + offset.x) << (xy_shift - shift);
t1.y = (t1.y + delta) >> shift;

if (lineType < CV_AA)
{
t0.x = (t0.x + (xy_one >> 1)) >> xy_shift;
t1.x = (t1.x + (xy_one >> 1)) >> xy_shift;

// LINE_4 to use the same type of line which is used in fillPoly
line(img, t0, t1, 0, 1, LINE_4, 0);
}
else
{
t0.x >>= (xy_shift);
t1.x >>= (xy_shift);
line(img, t0, t1, 0, 1, lineType, 0);
}

}
cv::Mat binary = img < 254;
cv::Mat labelImage(binary.size(), CV_32S);
int labels = cv::connectedComponents(binary, labelImage, 4);
EXPECT_EQ(2, labels) << "filling went over the border";
}

void run_test(const std::vector<cv::Point>& polygonPoints)
{
cv::Mat img(imageHeight, imageWidth, type);
img = 0;

draw_polygon(img, polygonPoints);
check_artifacts(img);
check_filling_over_border(img, polygonPoints);
}
};

TEST_P(FillPolyFully, DISABLED_fillpoly_fully)
{
int imageSizeOffset = 15;

// testing for polygon with straight edge at left/right side
int positions1[2] = { imageSizeOffset, (int)imageWidth - imageSizeOffset };
for (size_t i = 0; i < 2; i++)
{
for (int y = imageHeight + 50; y > -50; y -= 1)
{
// define polygon
std::vector<cv::Point> polygonPoints;
polygonPoints.push_back(cv::Point(100, imageHeight - y));
polygonPoints.push_back(cv::Point(positions1[i], positions1[1]));
polygonPoints.push_back(cv::Point(positions1[i], positions1[0]));

run_test(polygonPoints);
}
}

// testing for polygon with straight edge at top/bottom side
int positions2[2] = { imageSizeOffset, (int)imageHeight - imageSizeOffset };
for (size_t i = 0; i < 2; i++)
{
for (int x = imageWidth + 50; x > -50; x -= 1)
{
// define polygon
std::vector<cv::Point> polygonPoints;
polygonPoints.push_back(cv::Point(imageWidth - x, 100));
polygonPoints.push_back(cv::Point(positions2[1], positions2[i]));
polygonPoints.push_back(cv::Point(positions2[0], positions2[i]));

run_test(polygonPoints);
}
}
}

INSTANTIATE_TEST_CASE_P(
FillPolyTest, FillPolyFully,
testing::Combine(
testing::Values(256),
testing::Values(256),
testing::Values(CV_8UC1),
testing::Values(0, 1, 2),
testing::Values(cv::Point(0, 0), cv::Point(10, 10)),
testing::Values(LINE_4, LINE_8, LINE_AA)
)
);

}} // namespace

0 comments on commit c67d4fc

Please sign in to comment.