1005 lines
34 KiB
C++
1005 lines
34 KiB
C++
//
|
|
// draw.cpp
|
|
// MNN
|
|
//
|
|
// Created by MNN on 2021/08/26.
|
|
// Copyright © 2018, Alibaba Group Holding Limited
|
|
//
|
|
|
|
#include <MNN/ImageProcess.hpp>
|
|
#include "cv/imgproc/draw.hpp"
|
|
#include <MNN/expr/NeuralNetWorkOp.hpp>
|
|
#include <MNN/expr/MathOp.hpp>
|
|
#include <cmath>
|
|
#include <limits>
|
|
|
|
namespace MNN {
|
|
namespace CV {
|
|
#ifdef MNN_IMGPROC_DRAW
|
|
// help functions
|
|
#define MIN(a,b) ((a) > (b) ? (b) : (a))
|
|
#define MAX(a,b) ((a) < (b) ? (b) : (a))
|
|
|
|
struct Region {
|
|
public:
|
|
Region(int _y, int _xl, int _xr) : y(_y), xl(_xl), xr(_xr) {}
|
|
Region(int _y, int _xl) : y(_y), xl(_xl), xr(_xl) {}
|
|
int y;
|
|
int xl;
|
|
int xr;
|
|
};
|
|
|
|
bool clipLine(Size2l img_size, Point2l& pt1, Point2l& pt2) {
|
|
int c1, c2;
|
|
int64_t right = img_size.width-1, bottom = img_size.height-1;
|
|
if (img_size.width <= 0 || img_size.height <= 0) return false;
|
|
|
|
int64_t &x1 = pt1.x, &y1 = pt1.y, &x2 = pt2.x, &y2 = pt2.y;
|
|
c1 = (x1 < 0) + (x1 > right) * 2 + (y1 < 0) * 4 + (y1 > bottom) * 8;
|
|
c2 = (x2 < 0) + (x2 > right) * 2 + (y2 < 0) * 4 + (y2 > bottom) * 8;
|
|
|
|
if ((c1 & c2) == 0 && (c1 | c2) != 0) {
|
|
int64_t a;
|
|
if (c1 & 12) {
|
|
a = c1 < 8 ? 0 : bottom;
|
|
x1 += (int64_t)((double)(a - y1) * (x2 - x1) / (y2 - y1));
|
|
y1 = a;
|
|
c1 = (x1 < 0) + (x1 > right) * 2;
|
|
}
|
|
if (c2 & 12) {
|
|
a = c2 < 8 ? 0 : bottom;
|
|
x2 += (int64_t)((double)(a - y2) * (x2 - x1) / (y2 - y1));
|
|
y2 = a;
|
|
c2 = (x2 < 0) + (x2 > right) * 2;
|
|
}
|
|
if ((c1 & c2) == 0 && (c1 | c2) != 0) {
|
|
if (c1) {
|
|
a = c1 == 1 ? 0 : right;
|
|
y1 += (int64_t)((double)(a - x1) * (y2 - y1) / (x2 - x1));
|
|
x1 = a;
|
|
c1 = 0;
|
|
}
|
|
if (c2) {
|
|
a = c2 == 1 ? 0 : right;
|
|
y2 += (int64_t)((double)(a - x2) * (y2 - y1) / (x2 - x1));
|
|
x2 = a;
|
|
c2 = 0;
|
|
}
|
|
}
|
|
MNN_ASSERT((c1 & c2) != 0 || (x1 | y1 | x2 | y2) >= 0);
|
|
}
|
|
return (c1 | c2) == 0;
|
|
}
|
|
bool clipLine(Size img_size, Point2i& pt1, Point2i& pt2) {
|
|
Point2l p1(pt1.x, pt1.y);
|
|
Point2l p2(pt2.x, pt2.y);
|
|
bool inside = clipLine(Size2l(img_size.width, img_size.height), p1, p2);
|
|
pt1.x = (int)p1.x;
|
|
pt1.y = (int)p1.y;
|
|
pt2.x = (int)p2.x;
|
|
pt2.y = (int)p2.y;
|
|
return inside;
|
|
}
|
|
|
|
enum { XY_SHIFT = 16, XY_ONE = 1 << XY_SHIFT, DRAWING_STORAGE_BLOCK = (1<<12) - 256 };
|
|
|
|
static inline void PutPoint(std::vector<Region>& regions, Size size, int x, int y) {
|
|
if( 0 <= x && x < size.width && 0 <= y && y < size.height) {
|
|
regions.emplace_back(Region{y, x});
|
|
}
|
|
}
|
|
|
|
static void Line(std::vector<Region>& regions, Size size, Point2i pt1_, Point2i pt2_, int connectivity = 8) {
|
|
if (connectivity == 0) {
|
|
connectivity = 8;
|
|
} else if (connectivity == 1) {
|
|
connectivity = 4;
|
|
}
|
|
int count = -1, err, minusDelta, plusDelta, minusStep, plusStep, minusShift, plusShift;
|
|
Point2i p = Point2i(0, 0);
|
|
Rect2i rect(0, 0, size.width, size.height);
|
|
Point2i pt1 = pt1_ - rect.tl();
|
|
Point2i pt2 = pt2_ - rect.tl();
|
|
|
|
if ((unsigned)pt1.x >= (unsigned)(rect.width) || (unsigned)pt2.x >= (unsigned)(rect.width) ||
|
|
(unsigned)pt1.y >= (unsigned)(rect.height) || (unsigned)pt2.y >= (unsigned)(rect.height)) {
|
|
if (!clipLine(Size(rect.width, rect.height), pt1, pt2)) {
|
|
err = plusDelta = minusDelta = plusStep = minusStep = plusShift = minusShift = count = 0;
|
|
}
|
|
}
|
|
|
|
pt1 += rect.tl();
|
|
pt2 += rect.tl();
|
|
|
|
int delta_x = 1, delta_y = 1;
|
|
int dx = pt2.x - pt1.x;
|
|
int dy = pt2.y - pt1.y;
|
|
|
|
if (dx < 0) {
|
|
dx = -dx;
|
|
dy = -dy;
|
|
pt1 = pt2;
|
|
}
|
|
|
|
if (dy < 0) {
|
|
dy = -dy;
|
|
delta_y = -1;
|
|
}
|
|
|
|
bool vert = dy > dx;
|
|
if (vert) {
|
|
std::swap(dx, dy);
|
|
std::swap(delta_x, delta_y);
|
|
}
|
|
|
|
MNN_ASSERT(dx >= 0 && dy >= 0);
|
|
|
|
if (connectivity == 8) {
|
|
err = dx - (dy + dy);
|
|
plusDelta = dx + dx;
|
|
minusDelta = -(dy + dy);
|
|
minusShift = delta_x;
|
|
plusShift = 0;
|
|
minusStep = 0;
|
|
plusStep = delta_y;
|
|
count = dx + 1;
|
|
} else /* connectivity == 4 */ {
|
|
err = 0;
|
|
plusDelta = (dx + dx) + (dy + dy);
|
|
minusDelta = -(dy + dy);
|
|
minusShift = delta_x;
|
|
plusShift = -delta_x;
|
|
minusStep = 0;
|
|
plusStep = delta_y;
|
|
count = dx + dy + 1;
|
|
}
|
|
|
|
if (vert) {
|
|
std::swap(plusStep, plusShift);
|
|
std::swap(minusStep, minusShift);
|
|
}
|
|
p = pt1;
|
|
PutPoint(regions, size, p.x, p.y);
|
|
for(int i = 1; i < count; i++) {
|
|
int mask = err < 0 ? -1 : 0;
|
|
err += minusDelta + (plusDelta & mask);
|
|
p.y += minusStep + (plusStep & mask);
|
|
p.x += minusShift + (plusShift & mask);
|
|
PutPoint(regions, size, p.x, p.y);
|
|
}
|
|
}
|
|
|
|
static void Line2(std::vector<Region>& regions, Size size, Point2l pt1, Point2l pt2) {
|
|
int64_t dx, dy;
|
|
int ecount;
|
|
int64_t ax, ay;
|
|
int64_t i, j;
|
|
int x, y;
|
|
int64_t x_step, y_step;
|
|
Size2l sizeScaled(((int64_t)size.width) << XY_SHIFT, ((int64_t)size.height) << XY_SHIFT);
|
|
if(!clipLine(sizeScaled, pt1, pt2)) {
|
|
return;
|
|
}
|
|
dx = pt2.x - pt1.x;
|
|
dy = pt2.y - pt1.y;
|
|
j = dx < 0 ? -1 : 0;
|
|
ax = (dx ^ j) - j;
|
|
i = dy < 0 ? -1 : 0;
|
|
ay = (dy ^ i) - i;
|
|
|
|
if (ax > ay) {
|
|
dy = (dy ^ j) - j;
|
|
pt1.x ^= pt2.x & j;
|
|
pt2.x ^= pt1.x & j;
|
|
pt1.x ^= pt2.x & j;
|
|
pt1.y ^= pt2.y & j;
|
|
pt2.y ^= pt1.y & j;
|
|
pt1.y ^= pt2.y & j;
|
|
|
|
x_step = XY_ONE;
|
|
y_step = (dy << XY_SHIFT) / (ax | 1);
|
|
ecount = (int)((pt2.x - pt1.x) >> XY_SHIFT);
|
|
} else {
|
|
dx = (dx ^ i) - i;
|
|
pt1.x ^= pt2.x & i;
|
|
pt2.x ^= pt1.x & i;
|
|
pt1.x ^= pt2.x & i;
|
|
pt1.y ^= pt2.y & i;
|
|
pt2.y ^= pt1.y & i;
|
|
pt1.y ^= pt2.y & i;
|
|
|
|
x_step = (dx << XY_SHIFT) / (ay | 1);
|
|
y_step = XY_ONE;
|
|
ecount = (int)((pt2.y - pt1.y) >> XY_SHIFT);
|
|
}
|
|
pt1.x += (XY_ONE >> 1);
|
|
pt1.y += (XY_ONE >> 1);
|
|
PutPoint(regions, size, (pt2.x + (XY_ONE >> 1)) >> XY_SHIFT, (pt2.y + (XY_ONE >> 1)) >> XY_SHIFT);
|
|
if (ax > ay) {
|
|
pt1.x >>= XY_SHIFT;
|
|
while(ecount >= 0) {
|
|
PutPoint(regions, size, pt1.x, pt1.y >> XY_SHIFT);
|
|
pt1.x++;
|
|
pt1.y += y_step;
|
|
ecount--;
|
|
}
|
|
} else {
|
|
pt1.y >>= XY_SHIFT;
|
|
while(ecount >= 0) {
|
|
PutPoint(regions, size, pt1.x >> XY_SHIFT, pt1.y);
|
|
pt1.x += x_step;
|
|
pt1.y++;
|
|
ecount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void FillConvexPoly(std::vector<Region>& regions, Size size, const Point2l* v, int npts, int line_type, int shift) {
|
|
struct {
|
|
int idx, di;
|
|
int64_t x, dx;
|
|
int ye;
|
|
} edge[2];
|
|
|
|
int delta = 1 << shift >> 1;
|
|
int i, y, imin = 0;
|
|
int edges = npts;
|
|
int64_t xmin, xmax, ymin, ymax;
|
|
Point2l p0;
|
|
int delta1, delta2;
|
|
|
|
delta1 = delta2 = XY_ONE >> 1;
|
|
|
|
p0 = v[npts - 1];
|
|
p0.x <<= XY_SHIFT - shift;
|
|
p0.y <<= XY_SHIFT - shift;
|
|
|
|
MNN_ASSERT(0 <= shift && shift <= XY_SHIFT);
|
|
xmin = xmax = v[0].x;
|
|
ymin = ymax = v[0].y;
|
|
|
|
for (i = 0; i < npts; i++) {
|
|
Point2l p = v[i];
|
|
if (p.y < ymin) {
|
|
ymin = p.y;
|
|
imin = i;
|
|
}
|
|
|
|
ymax = std::max(ymax, p.y);
|
|
xmax = std::max(xmax, p.x);
|
|
xmin = MIN(xmin, p.x);
|
|
|
|
p.x <<= XY_SHIFT - shift;
|
|
p.y <<= XY_SHIFT - shift;
|
|
|
|
if(!shift) {
|
|
Point2i pt0, pt1;
|
|
pt0.x = (int)(p0.x >> XY_SHIFT);
|
|
pt0.y = (int)(p0.y >> XY_SHIFT);
|
|
pt1.x = (int)(p.x >> XY_SHIFT);
|
|
pt1.y = (int)(p.y >> XY_SHIFT);
|
|
Line(regions, size, pt0, pt1, line_type);
|
|
} else {
|
|
Line2(regions, size, p0, p);
|
|
}
|
|
p0 = p;
|
|
}
|
|
|
|
xmin = (xmin + delta) >> shift;
|
|
xmax = (xmax + delta) >> shift;
|
|
ymin = (ymin + delta) >> shift;
|
|
ymax = (ymax + delta) >> shift;
|
|
|
|
if(npts < 3 || (int)xmax < 0 || (int)ymax < 0 || (int)xmin >= size.width || (int)ymin >= size.height) {
|
|
return;
|
|
}
|
|
ymax = MIN(ymax, size.height - 1);
|
|
edge[0].idx = edge[1].idx = imin;
|
|
edge[0].ye = edge[1].ye = y = (int)ymin;
|
|
edge[0].di = 1;
|
|
edge[1].di = npts - 1;
|
|
edge[0].x = edge[1].x = -XY_ONE;
|
|
edge[0].dx = edge[1].dx = 0;
|
|
int region_y = y;
|
|
do {
|
|
if (y < (int)ymax || y == (int)ymin) {
|
|
for (i = 0; i < 2; i++) {
|
|
if (y >= edge[i].ye) {
|
|
int idx0 = edge[i].idx, di = edge[i].di;
|
|
int idx = idx0 + di;
|
|
if (idx >= npts) idx -= npts;
|
|
int ty = 0;
|
|
|
|
for (; edges-- > 0; ) {
|
|
ty = (int)((v[idx].y + delta) >> shift);
|
|
if (ty > y) {
|
|
int64_t xs = v[idx0].x;
|
|
int64_t xe = v[idx].x;
|
|
if (shift != XY_SHIFT)
|
|
{
|
|
xs <<= XY_SHIFT - shift;
|
|
xe <<= XY_SHIFT - shift;
|
|
}
|
|
|
|
edge[i].ye = ty;
|
|
edge[i].dx = ((xe - xs)*2 + (ty - y)) / (2 * (ty - y));
|
|
edge[i].x = xs;
|
|
edge[i].idx = idx;
|
|
break;
|
|
}
|
|
idx0 = idx;
|
|
idx += di;
|
|
if (idx >= npts) idx -= npts;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (edges < 0)
|
|
break;
|
|
|
|
if (y >= 0) {
|
|
int left = 0, right = 1;
|
|
if (edge[0].x > edge[1].x)
|
|
{
|
|
left = 1, right = 0;
|
|
}
|
|
|
|
int xx1 = (int)((edge[left].x + delta1) >> XY_SHIFT);
|
|
int xx2 = (int)((edge[right].x + delta2) >> XY_SHIFT);
|
|
|
|
if(xx2 >= 0 && xx1 < size.width)
|
|
{
|
|
if(xx1 < 0) {
|
|
xx1 = 0;
|
|
}
|
|
if(xx2 >= size.width) {
|
|
xx2 = size.width - 1;
|
|
}
|
|
if (xx2 - xx1 > 0) regions.emplace_back(Region{region_y, xx1, xx2});
|
|
}
|
|
}
|
|
|
|
edge[0].x += edge[0].dx;
|
|
edge[1].x += edge[1].dx;
|
|
region_y++;
|
|
} while(++y <= (int)ymax);
|
|
}
|
|
|
|
static void sincos(int angle, float& cosval, float& sinval) {
|
|
angle += (angle < 0 ? 360 : 0);
|
|
float radian = angle * MNN_PI / 180;
|
|
sinval = sin(radian);
|
|
cosval = cos(radian);
|
|
}
|
|
|
|
void ellipse2Poly(Point2d center, Size2d axes, int angle, int arc_start, int arc_end, int delta, std::vector<Point2d>& pts) {
|
|
MNN_ASSERT(0 < delta && delta <= 180);
|
|
|
|
float alpha, beta;
|
|
int i;
|
|
|
|
while(angle < 0) angle += 360;
|
|
while(angle > 360) angle -= 360;
|
|
|
|
if (arc_start > arc_end) {
|
|
i = arc_start;
|
|
arc_start = arc_end;
|
|
arc_end = i;
|
|
}
|
|
while (arc_start < 0) {
|
|
arc_start += 360;
|
|
arc_end += 360;
|
|
}
|
|
while (arc_end > 360) {
|
|
arc_end -= 360;
|
|
arc_start -= 360;
|
|
}
|
|
if (arc_end - arc_start > 360) {
|
|
arc_start = 0;
|
|
arc_end = 360;
|
|
}
|
|
sincos(angle, alpha, beta);
|
|
pts.resize(0);
|
|
|
|
for (i = arc_start; i < arc_end + delta; i += delta) {
|
|
double x, y;
|
|
angle = i;
|
|
if (angle > arc_end) angle = arc_end;
|
|
float sinv, cosv;
|
|
sincos(angle, sinv, cosv);
|
|
x = axes.width * cosv;
|
|
y = axes.height * sinv;
|
|
Point2d pt;
|
|
pt.x = center.x + x * alpha - y * beta;
|
|
pt.y = center.y + x * beta + y * alpha;
|
|
pts.push_back(pt);
|
|
}
|
|
|
|
// If there are no points, it's a zero-size polygon
|
|
if( pts.size() == 1) {
|
|
pts.assign(2,center);
|
|
}
|
|
}
|
|
static void ThickLine(std::vector<Region>& regions, Size size, Point2l p0, Point2l p1, int thickness, int line_type, int flags, int shift);
|
|
static void PolyLine(std::vector<Region>& regions, Size size, const Point2l* v, int count, bool is_closed, int thickness, int line_type, int shift) {
|
|
if (!v || count <= 0) {
|
|
return;
|
|
}
|
|
|
|
int i = is_closed ? count - 1 : 0;
|
|
int flags = 2 + !is_closed;
|
|
Point2l p0;
|
|
MNN_ASSERT(0 <= shift && shift <= XY_SHIFT && thickness >= 0);
|
|
|
|
p0 = v[i];
|
|
for (i = !is_closed; i < count; i++) {
|
|
Point2l p = v[i];
|
|
ThickLine(regions, size, p0, p, thickness, line_type, flags, shift );
|
|
p0 = p;
|
|
flags = 2;
|
|
}
|
|
}
|
|
|
|
struct PolyEdge {
|
|
PolyEdge() : y0(0), y1(0), x(0), dx(0), next(0) {}
|
|
|
|
int y0, y1;
|
|
int64_t x, dx;
|
|
PolyEdge *next;
|
|
};
|
|
|
|
static void CollectPolyEdges(std::vector<Region>& regions, Size size, const Point2l* v, int count, std::vector<PolyEdge>& edges, int line_type, int shift, Point2i offset = Point2i()) {
|
|
int delta = offset.y + ((1 << shift) >> 1);
|
|
Point2l pt0 = v[count-1], pt1;
|
|
pt0.x = (pt0.x + offset.x) << (XY_SHIFT - shift);
|
|
pt0.y = (pt0.y + delta) >> shift;
|
|
|
|
edges.reserve(edges.size() + count);
|
|
|
|
for (int i = 0; i < count; i++, pt0 = pt1) {
|
|
Point2l t0, t1;
|
|
PolyEdge edge;
|
|
|
|
pt1 = v[i];
|
|
pt1.x = (pt1.x + offset.x) << (XY_SHIFT - shift);
|
|
pt1.y = (pt1.y + delta) >> shift;
|
|
|
|
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(regions, size, t0, t1, line_type);
|
|
|
|
if (pt0.y == pt1.y) continue;
|
|
|
|
if (pt0.y < pt1.y) {
|
|
edge.y0 = (int)(pt0.y);
|
|
edge.y1 = (int)(pt1.y);
|
|
edge.x = pt0.x;
|
|
} else {
|
|
edge.y0 = (int)(pt1.y);
|
|
edge.y1 = (int)(pt0.y);
|
|
edge.x = pt1.x;
|
|
}
|
|
edge.dx = (pt1.x - pt0.x) / (pt1.y - pt0.y);
|
|
edges.push_back(edge);
|
|
}
|
|
}
|
|
|
|
static void FillEdgeCollection(std::vector<Region>& regions, Size size, std::vector<PolyEdge>& edges) {
|
|
PolyEdge tmp;
|
|
int i, y, total = (int)edges.size();
|
|
PolyEdge* e;
|
|
int y_max = std::numeric_limits<int>::min(), y_min = std::numeric_limits<int>::max();
|
|
int64_t x_max = 0xFFFFFFFFFFFFFFFF, x_min = 0x7FFFFFFFFFFFFFFF;
|
|
|
|
if (total < 2) return;
|
|
|
|
for (i = 0; i < total; i++) {
|
|
PolyEdge& e1 = edges[i];
|
|
MNN_ASSERT(e1.y0 < e1.y1);
|
|
// Determine x-coordinate of the end of the edge.
|
|
// (This is not necessary x-coordinate of any vertex in the array.)
|
|
int64_t x1 = e1.x + (e1.y1 - e1.y0) * e1.dx;
|
|
y_min = std::min( y_min, e1.y0 );
|
|
y_max = std::max( y_max, e1.y1 );
|
|
x_min = std::min( x_min, e1.x );
|
|
x_max = std::max( x_max, e1.x );
|
|
x_min = std::min( x_min, x1 );
|
|
x_max = std::max( x_max, x1 );
|
|
}
|
|
|
|
if (y_max < 0 || y_min >= size.height || x_max < 0 || x_min >= ((int64_t)size.width<<XY_SHIFT)) return;
|
|
|
|
std::sort( edges.begin(), edges.end(), [](const PolyEdge& e1, const PolyEdge& e2) {
|
|
return e1.y0 - e2.y0 ? e1.y0 < e2.y0 : e1.x - e2.x ? e1.x < e2.x : e1.dx < e2.dx;
|
|
});
|
|
|
|
// start drawing
|
|
tmp.y0 = std::numeric_limits<int>::max();
|
|
edges.push_back(tmp); // after this point we do not add
|
|
// any elements to edges, thus we can use pointers
|
|
i = 0;
|
|
tmp.next = 0;
|
|
e = &edges[i];
|
|
y_max = MIN(y_max, size.height);
|
|
|
|
for (y = e->y0; y < y_max; y++) {
|
|
PolyEdge *last, *prelast, *keep_prelast;
|
|
int sort_flag = 0;
|
|
int draw = 0;
|
|
int clipline = y < 0;
|
|
|
|
prelast = &tmp;
|
|
last = tmp.next;
|
|
while (last || e->y0 == y) {
|
|
if (last && last->y1 == y) {
|
|
// exclude edge if y reaches its lower point
|
|
prelast->next = last->next;
|
|
last = last->next;
|
|
continue;
|
|
}
|
|
keep_prelast = prelast;
|
|
if (last && (e->y0 > y || last->x < e->x)) {
|
|
// go to the next edge in active list
|
|
prelast = last;
|
|
last = last->next;
|
|
} else if(i < total) {
|
|
// insert new edge into active list if y reaches its upper point
|
|
prelast->next = e;
|
|
e->next = last;
|
|
prelast = e;
|
|
e = &edges[++i];
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (draw) {
|
|
if(!clipline) {
|
|
// convert x's from fixed-point to image coordinates
|
|
// uchar *timg = const_cast<uchar*>(img->readMap<uchar>()) + (y * pix_size * w);
|
|
int x1, x2;
|
|
|
|
if (keep_prelast->x > prelast->x) {
|
|
x1 = (int)((prelast->x + XY_ONE - 1) >> XY_SHIFT);
|
|
x2 = (int)(keep_prelast->x >> XY_SHIFT);
|
|
} else {
|
|
x1 = (int)((keep_prelast->x + XY_ONE - 1) >> XY_SHIFT);
|
|
x2 = (int)(prelast->x >> XY_SHIFT);
|
|
}
|
|
|
|
// clip and draw the line
|
|
if( x1 < size.width && x2 >= 0 )
|
|
{
|
|
if (x1 < 0) x1 = 0;
|
|
if (x2 >= size.width) x2 = size.width - 1;
|
|
regions.emplace_back(Region{y, x1, x2});
|
|
}
|
|
}
|
|
keep_prelast->x += keep_prelast->dx;
|
|
prelast->x += prelast->dx;
|
|
}
|
|
draw ^= 1;
|
|
}
|
|
|
|
// sort edges (using bubble sort)
|
|
keep_prelast = 0;
|
|
do {
|
|
prelast = &tmp;
|
|
last = tmp.next;
|
|
|
|
while (last != keep_prelast && last->next != 0) {
|
|
PolyEdge *te = last->next;
|
|
// swap edges
|
|
if (last->x > te->x) {
|
|
prelast->next = te;
|
|
last->next = te->next;
|
|
te->next = last;
|
|
prelast = te;
|
|
sort_flag = 1;
|
|
} else {
|
|
prelast = last;
|
|
last = te;
|
|
}
|
|
}
|
|
keep_prelast = prelast;
|
|
} while(sort_flag && keep_prelast != tmp.next && keep_prelast != &tmp);
|
|
}
|
|
}
|
|
|
|
static void EllipseEx(std::vector<Region>& regions, Size size, Point2l center, Size2l axes, int angle, int arc_start, int arc_end, int thickness, int line_type) {
|
|
axes.width = std::abs(axes.width), axes.height = std::abs(axes.height);
|
|
int delta = (int)((std::max(axes.width,axes.height)+(XY_ONE>>1))>>XY_SHIFT);
|
|
delta = delta < 3 ? 90 : delta < 10 ? 30 : delta < 15 ? 18 : 5;
|
|
|
|
std::vector<Point2d> _v;
|
|
ellipse2Poly(Point2d((double)center.x, (double)center.y), Size2d((double)axes.width, (double)axes.height), angle, arc_start, arc_end, delta, _v);
|
|
|
|
std::vector<Point2l> v;
|
|
Point2l prevPt(0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF);
|
|
v.resize(0);
|
|
for (unsigned int i = 0; i < _v.size(); ++i)
|
|
{
|
|
Point2l pt;
|
|
pt.x = (int64_t)std::round(_v[i].x / static_cast<double>(XY_ONE)) << XY_SHIFT;
|
|
pt.y = (int64_t)std::round(_v[i].y / static_cast<double>(XY_ONE)) << XY_SHIFT;
|
|
pt.x += std::round(_v[i].x - pt.x);
|
|
pt.y += std::round(_v[i].y - pt.y);
|
|
if (pt != prevPt) {
|
|
v.push_back(pt);
|
|
prevPt = pt;
|
|
}
|
|
}
|
|
|
|
// If there are no points, it's a zero-size polygon
|
|
if (v.size() == 1) {
|
|
v.assign(2, center);
|
|
}
|
|
|
|
if (thickness >= 0) {
|
|
PolyLine(regions, size, &v[0], (int)v.size(), false, thickness, line_type, XY_SHIFT);
|
|
} else if( arc_end - arc_start >= 360 ) {
|
|
FillConvexPoly(regions, size, &v[0], (int)v.size(), line_type, XY_SHIFT);
|
|
} else {
|
|
v.push_back(center);
|
|
std::vector<PolyEdge> edges;
|
|
CollectPolyEdges(regions, size, &v[0], (int)v.size(), edges, line_type, XY_SHIFT);
|
|
FillEdgeCollection(regions, size, edges);
|
|
}
|
|
}
|
|
|
|
static void Circle(std::vector<Region>& regions, Size size, Point2i center, int radius, int fill) {
|
|
int err = 0, dx = radius, dy = 0, plus = 1, minus = (radius << 1) - 1;
|
|
int inside = center.x >= radius && center.x < size.width - radius &&
|
|
center.y >= radius && center.y < size.height - radius;
|
|
|
|
while (dx >= dy) {
|
|
int mask;
|
|
int y11 = center.y - dy, y12 = center.y + dy, y21 = center.y - dx, y22 = center.y + dx;
|
|
int x11 = center.x - dx, x12 = center.x + dx, x21 = center.x - dy, x22 = center.x + dy;
|
|
|
|
if (inside) {
|
|
if(!fill) {
|
|
regions.emplace_back(Region{y11, x11});
|
|
regions.emplace_back(Region{y12, x11});
|
|
regions.emplace_back(Region{y11, x12});
|
|
regions.emplace_back(Region{y12, x12});
|
|
regions.emplace_back(Region{y21, x21});
|
|
regions.emplace_back(Region{y22, x21});
|
|
regions.emplace_back(Region{y21, x22});
|
|
regions.emplace_back(Region{y22, x22});
|
|
} else {
|
|
regions.emplace_back(Region{y11, x11, x12});
|
|
regions.emplace_back(Region{y12, x11, x12});
|
|
regions.emplace_back(Region{y21, x21, x22});
|
|
regions.emplace_back(Region{y22, x21, x22});
|
|
}
|
|
} else if (x11 < size.width && x12 >= 0 && y21 < size.height && y22 >= 0) {
|
|
if (fill) {
|
|
x11 = std::max(x11, 0);
|
|
x12 = MIN(x12, size.width - 1);
|
|
}
|
|
if ((unsigned)y11 < (unsigned)size.height) {
|
|
if (!fill) {
|
|
if(x11 >= 0) regions.emplace_back(Region{y11, x11});
|
|
if(x12 < size.width) regions.emplace_back(Region{y11, x12});
|
|
} else {
|
|
regions.emplace_back(Region{y11, x11, x12});
|
|
}
|
|
}
|
|
if ((unsigned)y12 < (unsigned)size.height) {
|
|
if(!fill) {
|
|
if(x11 >= 0) regions.emplace_back(Region{y12, x11});
|
|
if(x12 < size.width) regions.emplace_back(Region{y12, x12});
|
|
} else {
|
|
regions.emplace_back(Region{y12, x11, x12});
|
|
}
|
|
}
|
|
|
|
if (x21 < size.width && x22 >= 0) {
|
|
if (fill) {
|
|
x21 = std::max(x21, 0);
|
|
x22 = MIN(x22, size.width - 1);
|
|
}
|
|
if ((unsigned)y21 < (unsigned)size.height) {
|
|
if(!fill) {
|
|
if(x21 >= 0) regions.emplace_back(Region{y21, x21});
|
|
if(x22 < size.width) regions.emplace_back(Region{y21, x22});
|
|
} else {
|
|
regions.emplace_back(Region{y21, x21, x22});
|
|
}
|
|
}
|
|
if ((unsigned)y22 < (unsigned)size.height) {
|
|
if(!fill) {
|
|
if(x21 >= 0) regions.emplace_back(Region{y22, x21});
|
|
if(x22 < size.width) regions.emplace_back(Region{y22, x22});
|
|
} else {
|
|
regions.emplace_back(Region{y22, x21, x22});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dy++;
|
|
err += plus;
|
|
plus += 2;
|
|
mask = (err <= 0) - 1;
|
|
err -= minus & mask;
|
|
dx += mask;
|
|
minus -= mask & 2;
|
|
}
|
|
}
|
|
|
|
static void ThickLine(std::vector<Region>& regions, Size size, Point2l p0, Point2l p1, int thickness, int line_type, int flags, int shift) {
|
|
constexpr double INV_XY_ONE = 1./XY_ONE;
|
|
p0.x <<= XY_SHIFT - shift;
|
|
p0.y <<= XY_SHIFT - shift;
|
|
p1.x <<= XY_SHIFT - shift;
|
|
p1.y <<= XY_SHIFT - shift;
|
|
|
|
if(thickness <= 1) {
|
|
if (line_type == 1 || line_type == 4 || shift == 0) {
|
|
p0.x = (p0.x + (XY_ONE>>1)) >> XY_SHIFT;
|
|
p0.y = (p0.y + (XY_ONE>>1)) >> XY_SHIFT;
|
|
p1.x = (p1.x + (XY_ONE>>1)) >> XY_SHIFT;
|
|
p1.y = (p1.y + (XY_ONE>>1)) >> XY_SHIFT;
|
|
Line(regions, size, p0, p1, line_type);
|
|
} else {
|
|
Line2(regions, size, p0, p1);
|
|
}
|
|
} else {
|
|
Point2l pt[4], dp = Point2i(0,0);
|
|
double dx = (p0.x - p1.x)*INV_XY_ONE, dy = (p1.y - p0.y)*INV_XY_ONE;
|
|
double r = dx * dx + dy * dy;
|
|
int i, oddThickness = thickness & 1;
|
|
thickness <<= XY_SHIFT - 1;
|
|
|
|
if( fabs(r) > 2.2e-16 ) {
|
|
r = (thickness + oddThickness * XY_ONE * 0.5) / std::sqrt(r);
|
|
dp.x = std::round( dy * r );
|
|
dp.y = std::round( dx * r );
|
|
|
|
pt[0].x = p0.x + dp.x;
|
|
pt[0].y = p0.y + dp.y;
|
|
pt[1].x = p0.x - dp.x;
|
|
pt[1].y = p0.y - dp.y;
|
|
pt[2].x = p1.x - dp.x;
|
|
pt[2].y = p1.y - dp.y;
|
|
pt[3].x = p1.x + dp.x;
|
|
pt[3].y = p1.y + dp.y;
|
|
FillConvexPoly(regions, size, pt, 4, line_type, XY_SHIFT);
|
|
}
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
if(flags & (i+1)) {
|
|
Point2i center;
|
|
center.x = (int)((p0.x + (XY_ONE>>1)) >> XY_SHIFT);
|
|
center.y = (int)((p0.y + (XY_ONE>>1)) >> XY_SHIFT);
|
|
Circle(regions, size, center, (thickness + (XY_ONE>>1)) >> XY_SHIFT, 1);
|
|
}
|
|
p0 = p1;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T> static inline
|
|
void scalarToRawData_(const Scalar& s, T * const buf, const int cn) {
|
|
for(int i = 0; i < cn; i++) {
|
|
buf[i] = static_cast<T>(s.val[i]);
|
|
}
|
|
}
|
|
|
|
void scalarToRawData(const Scalar& s, void* buf, VARP img) {
|
|
auto type = img->getInfo()->type;
|
|
int cn = getVARPChannel(img);
|
|
if (type == halide_type_of<uint8_t>()) {
|
|
scalarToRawData_<uchar>(s, (uchar*)buf, cn);
|
|
} else if (type == halide_type_of<float>()) {
|
|
scalarToRawData_<float>(s, (float*)buf, cn);
|
|
} else if (type == halide_type_of<double>()) {
|
|
scalarToRawData_<double>(s, (double*)buf, cn);
|
|
} else if (type == halide_type_of<int>()) {
|
|
scalarToRawData_<int>(s, (int*)buf, cn);
|
|
}
|
|
}
|
|
|
|
std::vector<Region> mergeRegions(std::vector<Region> regions) {
|
|
std::vector<Region> res;
|
|
// 1. get line's region
|
|
std::map<int, std::vector<std::pair<int, int>>> lines;
|
|
for (auto region : regions) {
|
|
if (lines.find(region.y) != lines.end()) {
|
|
lines[region.y].push_back({region.xl, region.xr});
|
|
} else {
|
|
lines[region.y] = std::vector<std::pair<int, int>>();
|
|
lines[region.y].push_back({region.xl, region.xr});
|
|
}
|
|
}
|
|
// 2. merge line's region
|
|
for (auto line : lines) {
|
|
auto liner = line.second;
|
|
// sort line regions
|
|
std::sort(liner.begin(), liner.end(), [](const std::pair<int, int>& a, const std::pair<int, int>& b){return a.first < b.first;});
|
|
// merge
|
|
res.emplace_back(Region{line.first, liner[0].first, liner[0].second});
|
|
for (int i = 1; i < liner.size(); i++) {
|
|
if (res.back().xr >= liner[i].first) {
|
|
res.back().xr = MAX(res.back().xr, liner[i].second);
|
|
} else {
|
|
res.emplace_back(Region{line.first, liner[i].first, liner[i].second});
|
|
}
|
|
}
|
|
}
|
|
return res;
|
|
}
|
|
|
|
void doDraw(VARP& img, const std::vector<Region>& regions, const Scalar& color) {
|
|
double buf[4];
|
|
scalarToRawData(color, buf, img);
|
|
auto mergeRegs = mergeRegions(regions);
|
|
ImageProcess::Config config;
|
|
std::unique_ptr<ImageProcess> process(ImageProcess::create(config));
|
|
process->setDraw();
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
auto ptr = const_cast<uint8_t*>(img->readMap<uint8_t>());
|
|
int num = (int)mergeRegs.size();
|
|
process->draw(ptr, w, h, c, reinterpret_cast<const int*>(mergeRegs.data()), num, (uint8_t*)buf);
|
|
}
|
|
|
|
void arrowedLine(VARP& img, Point pt1, Point pt2, const Scalar& color,
|
|
int thickness, int line_type, int shift, double tipLength) {
|
|
// line
|
|
line(img, pt1, pt2, color, thickness, line_type, shift);
|
|
float deltaX = pt1.fX - pt2.fX, deltaY = pt1.fY - pt2.fY;
|
|
const double tipSize = std::sqrt(deltaX * deltaX + deltaY * deltaY) * tipLength;
|
|
const double angle = atan2(pt1.fY - pt2.fY, pt1.fX - pt2.fX);
|
|
// arrawed edge 1
|
|
Point p;
|
|
p.fX = std::round(pt2.fX + tipSize * cos(angle + MNN_PI / 4));
|
|
p.fY = std::round(pt2.fY + tipSize * sin(angle + MNN_PI / 4));
|
|
line(img, p, pt2, color, thickness, line_type, shift);
|
|
// arrawed edge 2
|
|
p.fX = std::round(pt2.fX + tipSize * cos(angle - MNN_PI / 4));
|
|
p.fY = std::round(pt2.fY + tipSize * sin(angle - MNN_PI / 4));
|
|
line(img, p, pt2, color, thickness, line_type, shift);
|
|
}
|
|
|
|
void circle(VARP& img, Point center, int radius, const Scalar& color, int thickness, int line_type, int shift) {
|
|
Point2i center_(static_cast<int>(center.fX), static_cast<int>(center.fY));
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
Size size(w, h);
|
|
std::vector<Region> regions;
|
|
if( thickness > 1 || line_type != LINE_8 || shift > 0 ) {
|
|
Point2l _center(center_);
|
|
int64_t _radius(radius);
|
|
_center.x <<= XY_SHIFT - shift;
|
|
_center.y <<= XY_SHIFT - shift;
|
|
_radius <<= XY_SHIFT - shift;
|
|
EllipseEx(regions, size, _center, Size2l(_radius, _radius), 0, 0, 360, thickness, line_type);
|
|
} else {
|
|
Circle(regions, size, center_, radius, thickness < 0);
|
|
}
|
|
doDraw(img, regions, color);
|
|
}
|
|
|
|
void ellipse(VARP& img, Point center, Size axes, double angle,
|
|
double start_angle, double end_angle, const Scalar& color,
|
|
int thickness, int line_type, int shift){
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
Size size(w, h);
|
|
std::vector<Region> regions;
|
|
auto _angle = static_cast<int>(std::round(angle));
|
|
auto _start_angle = static_cast<int>(std::round(start_angle));
|
|
auto _end_angle = static_cast<int>(std::round(end_angle));
|
|
Point2l _center(static_cast<int64_t>(center.fX), static_cast<int64_t>(center.fY));
|
|
Size2l _axes(axes);
|
|
_center.x <<= XY_SHIFT - shift;
|
|
_center.y <<= XY_SHIFT - shift;
|
|
_axes.width <<= XY_SHIFT - shift;
|
|
_axes.height <<= XY_SHIFT - shift;
|
|
EllipseEx(regions, size, _center, _axes, _angle, _start_angle, _end_angle, thickness, line_type);
|
|
doDraw(img, regions, color);
|
|
}
|
|
|
|
void line(VARP& img, Point pt1, Point pt2, const Scalar& color,
|
|
int thickness, int lineType, int shift) {
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
Point2i p1(static_cast<int>(pt1.fX), static_cast<int>(pt1.fY));
|
|
Point2i p2(static_cast<int>(pt2.fX), static_cast<int>(pt2.fY));
|
|
std::vector<Region> regions;
|
|
ThickLine(regions, Size{w, h}, p1, p2, thickness, lineType, 3, shift);
|
|
doDraw(img, regions, color);
|
|
}
|
|
|
|
void rectangle(VARP& img, Point pt1, Point pt2, const Scalar& color,
|
|
int thickness, int lineType, int shift) {
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
Size size(w, h);
|
|
std::vector<Point2l> pt(4);
|
|
pt[0] = {static_cast<int64_t>(pt1.fX), static_cast<int64_t>(pt1.fY)};
|
|
pt[1] = {static_cast<int64_t>(pt2.fX), static_cast<int64_t>(pt1.fY)};
|
|
pt[2] = {static_cast<int64_t>(pt2.fX), static_cast<int64_t>(pt2.fY)};
|
|
pt[3] = {static_cast<int64_t>(pt1.fX), static_cast<int64_t>(pt2.fY)};
|
|
std::vector<Region> regions;
|
|
if (thickness >= 0) {
|
|
PolyLine(regions, size, pt.data(), 4, true, thickness, lineType, shift);
|
|
} else {
|
|
FillConvexPoly(regions, size, pt.data(), 4, lineType, shift);
|
|
}
|
|
doDraw(img, regions, color);
|
|
}
|
|
|
|
void drawContours(VARP& img, std::vector<std::vector<Point>> _contours, int contourIdx, const Scalar& color, int thickness, int lineType) {
|
|
size_t ncontours = _contours.size();
|
|
if (!ncontours) return;
|
|
int h, w, c; getVARPSize(img, &h, &w, &c);
|
|
Size size(w, h);
|
|
std::vector<Region> regions;
|
|
size_t i = 0, first = 0, last = ncontours;
|
|
if (contourIdx >= 0) {
|
|
first = contourIdx;
|
|
last = first + 1;
|
|
}
|
|
std::vector<PolyEdge> edges;
|
|
for (i = first; i < last; i++) {
|
|
const auto& contour = _contours[i];
|
|
if (contour.empty()) continue;
|
|
std::vector<Point2l> pts;
|
|
for (int j = 0; j < contour.size(); j++) {
|
|
int nextj = j + 1 == contour.size() ? 0 : j + 1;
|
|
Point2l pt1(contour[j].fX, contour[j].fY), pt2(contour[nextj].fX, contour[nextj].fY);
|
|
if(thickness >= 0) {
|
|
ThickLine(regions, size, pt1, pt2, thickness, lineType, 2, 0);
|
|
} else {
|
|
if (!j) pts.push_back(pt1);
|
|
pts.push_back(pt2);
|
|
}
|
|
}
|
|
if (thickness < 0) {
|
|
CollectPolyEdges(regions, size, &pts[0], (int)pts.size(), edges, lineType, 0);
|
|
}
|
|
}
|
|
if (thickness < 0) {
|
|
FillEdgeCollection(regions, size, edges);
|
|
}
|
|
doDraw(img, regions, color);
|
|
}
|
|
|
|
void fillPoly(VARP& img, std::vector<std::vector<Point>> _pts, const Scalar& color, int line_type, int shift, Point _offset) {
|
|
int ncontours = _pts.size();
|
|
if (!ncontours) return;
|
|
int h, w, c;
|
|
getVARPSize(img, &h, &w, &c);
|
|
Size size(w, h);
|
|
std::vector<Region> regions;
|
|
std::vector<std::vector<Point2i>> pts(ncontours);
|
|
std::vector<Point2i*> _ptsptr(ncontours);
|
|
std::vector<int> _npts(ncontours);
|
|
Point2i** ptsptr = _ptsptr.data();
|
|
int *npts = _npts.data(), total = 0;
|
|
for(int i = 0; i < ncontours; i++ ) {
|
|
int num = _pts[i].size();
|
|
pts[i].resize(num);
|
|
for (int j = 0; j < num; j++) {
|
|
pts[i][j].x = _pts[i][j].fX;
|
|
pts[i][j].y = _pts[i][j].fY;
|
|
}
|
|
ptsptr[i] = pts[i].data();
|
|
npts[i] = num;
|
|
total += num;
|
|
}
|
|
if(line_type == LINE_AA && img->getInfo()->type == halide_type_of<uint8_t>()) line_type = 8;
|
|
MNN_ASSERT(ptsptr && npts && ncontours >= 0 && 0 <= shift && shift <= XY_SHIFT);
|
|
std::vector<PolyEdge> edges;
|
|
Point2i offset(_offset.fX, _offset.fY);
|
|
edges.reserve( total + 1 );
|
|
for (int i = 0; i < ncontours; i++) {
|
|
std::vector<Point2l> _pts(ptsptr[i], ptsptr[i] + npts[i]);
|
|
CollectPolyEdges(regions, size, _pts.data(), npts[i], edges, line_type, shift, offset);
|
|
}
|
|
FillEdgeCollection(regions, size, edges);
|
|
doDraw(img, regions, color);
|
|
}
|
|
#endif
|
|
} // CV
|
|
} // MNN
|