289 lines
10 KiB
C++
289 lines
10 KiB
C++
#ifndef VECTORIZER_H
|
|
#define VECTORIZER_H
|
|
|
|
#include "gpu_offset.h"
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <cstdint>
|
|
|
|
inline int get_h_edge_id(int x, int y, int stride_w) {
|
|
return 2 * (y * stride_w + x) + 0;
|
|
}
|
|
inline int get_v_edge_id(int x, int y, int stride_w) {
|
|
return 2 * (y * stride_w + x) + 1;
|
|
}
|
|
|
|
inline GpuPoint2D get_edge_point(int edge_id, int stride_w, float min_x, float min_y, float pitch_mm) {
|
|
int type = edge_id & 1;
|
|
int cell = edge_id >> 1;
|
|
int y = cell / stride_w;
|
|
int x = cell % stride_w;
|
|
|
|
if (type == 0) {
|
|
return GpuPoint2D{ min_x + (x + 0.5f) * pitch_mm, min_y + (float)y * pitch_mm };
|
|
} else {
|
|
return GpuPoint2D{ min_x + (float)x * pitch_mm, min_y + (y + 0.5f) * pitch_mm };
|
|
}
|
|
}
|
|
|
|
static inline void vectorize_mask_layer(
|
|
const uint8_t* mask,
|
|
int width, int height,
|
|
float min_x, float min_y, float pitch_mm,
|
|
std::vector<int>& next_edge_buf,
|
|
std::vector<int>& active_edges_buf,
|
|
std::vector<uint8_t>& visited_buf,
|
|
std::vector<GpuContour>& out_contours
|
|
) {
|
|
int stride_w = width + 1;
|
|
active_edges_buf.clear();
|
|
|
|
auto add_directed_edge = [&](int e0, int e1) {
|
|
next_edge_buf[e0] = e1;
|
|
visited_buf[e0] = 0;
|
|
active_edges_buf.push_back(e0);
|
|
};
|
|
|
|
for (int y = 0; y < height - 1; ++y) {
|
|
const uint8_t* row0 = mask + y * width;
|
|
const uint8_t* row1 = mask + (y + 1) * width;
|
|
|
|
for (int x = 0; x < width - 1; ++x) {
|
|
int c0 = row0[x];
|
|
int c1 = row0[x + 1];
|
|
int c2 = row1[x + 1];
|
|
int c3 = row1[x];
|
|
|
|
int case_idx = (c0) | (c1 << 1) | (c2 << 2) | (c3 << 3);
|
|
if (case_idx == 0 || case_idx == 15) continue;
|
|
|
|
int e_bottom = get_h_edge_id(x, y, stride_w);
|
|
int e_top = get_h_edge_id(x, y + 1, stride_w);
|
|
int e_left = get_v_edge_id(x, y, stride_w);
|
|
int e_right = get_v_edge_id(x + 1, y, stride_w);
|
|
|
|
switch (case_idx) {
|
|
case 1: add_directed_edge(e_left, e_bottom); break;
|
|
case 2: add_directed_edge(e_bottom, e_right); break;
|
|
case 3: add_directed_edge(e_left, e_right); break;
|
|
case 4: add_directed_edge(e_right, e_top); break;
|
|
case 5: add_directed_edge(e_left, e_top); add_directed_edge(e_bottom, e_right); break;
|
|
case 6: add_directed_edge(e_bottom, e_top); break;
|
|
case 7: add_directed_edge(e_left, e_top); break;
|
|
case 8: add_directed_edge(e_top, e_left); break;
|
|
case 9: add_directed_edge(e_top, e_bottom); break;
|
|
case 10: add_directed_edge(e_bottom, e_left); add_directed_edge(e_top, e_right); break;
|
|
case 11: add_directed_edge(e_top, e_right); break;
|
|
case 12: add_directed_edge(e_right, e_left); break;
|
|
case 13: add_directed_edge(e_right, e_bottom); break;
|
|
case 14: add_directed_edge(e_bottom, e_left); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (active_edges_buf.empty()) return;
|
|
|
|
std::vector<GpuPoint2D> loop_pts;
|
|
loop_pts.reserve(1000);
|
|
|
|
for (int start_e : active_edges_buf) {
|
|
if (visited_buf[start_e]) continue;
|
|
|
|
loop_pts.clear();
|
|
int cur = start_e;
|
|
|
|
while (cur != -1 && !visited_buf[cur]) {
|
|
visited_buf[cur] = 1;
|
|
GpuPoint2D pt = get_edge_point(cur, stride_w, min_x, min_y, pitch_mm);
|
|
|
|
size_t n = loop_pts.size();
|
|
if (n >= 2) {
|
|
float dx1 = loop_pts[n-1].x - loop_pts[n-2].x;
|
|
float dy1 = loop_pts[n-1].y - loop_pts[n-2].y;
|
|
float dx2 = pt.x - loop_pts[n-1].x;
|
|
float dy2 = pt.y - loop_pts[n-1].y;
|
|
|
|
float cross = dx1 * dy2 - dy1 * dx2;
|
|
if (std::abs(cross) < 1e-6f) {
|
|
loop_pts[n-1] = pt; // simplify collinear vertex
|
|
} else {
|
|
loop_pts.push_back(pt);
|
|
}
|
|
} else {
|
|
loop_pts.push_back(pt);
|
|
}
|
|
|
|
cur = next_edge_buf[cur];
|
|
if (cur == start_e) break;
|
|
if (loop_pts.size() > 50000) break;
|
|
}
|
|
|
|
if (loop_pts.size() >= 3) {
|
|
size_t n = loop_pts.size();
|
|
float dx1 = loop_pts[0].x - loop_pts[n-1].x;
|
|
float dy1 = loop_pts[0].y - loop_pts[n-1].y;
|
|
float dx2 = loop_pts[1].x - loop_pts[0].x;
|
|
float dy2 = loop_pts[1].y - loop_pts[0].y;
|
|
if (std::abs(dx1 * dy2 - dy1 * dx2) < 1e-6f) {
|
|
loop_pts.erase(loop_pts.begin());
|
|
}
|
|
|
|
if (loop_pts.size() >= 3) {
|
|
GpuContour c;
|
|
c.num_points = loop_pts.size();
|
|
c.points = (GpuPoint2D*)malloc(c.num_points * sizeof(GpuPoint2D));
|
|
memcpy(c.points, loop_pts.data(), c.num_points * sizeof(GpuPoint2D));
|
|
c.is_hole = 0;
|
|
out_contours.push_back(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset thread-local edge table for zero re-allocation overhead
|
|
for (int e : active_edges_buf) {
|
|
next_edge_buf[e] = -1;
|
|
visited_buf[e] = 0;
|
|
}
|
|
}
|
|
|
|
static inline void vectorize_bitpack_layer(
|
|
const unsigned int* bit_words,
|
|
int width, int height, int row_stride_words,
|
|
float min_x, float min_y, float pitch_mm,
|
|
std::vector<int>& next_edge_buf,
|
|
std::vector<int>& active_edges_buf,
|
|
std::vector<uint8_t>& visited_buf,
|
|
std::vector<GpuContour>& out_contours
|
|
) {
|
|
int stride_w = width + 1;
|
|
active_edges_buf.clear();
|
|
|
|
auto add_directed_edge = [&](int e0, int e1) {
|
|
next_edge_buf[e0] = e1;
|
|
visited_buf[e0] = 0;
|
|
active_edges_buf.push_back(e0);
|
|
};
|
|
|
|
for (int y = 0; y < height - 1; ++y) {
|
|
const unsigned int* row0 = bit_words + (size_t)y * row_stride_words;
|
|
const unsigned int* row1 = bit_words + (size_t)(y + 1) * row_stride_words;
|
|
|
|
for (int w = 0; w < row_stride_words; ++w) {
|
|
unsigned int w0 = row0[w];
|
|
unsigned int w1 = row1[w];
|
|
|
|
unsigned int w0_next = (w + 1 < row_stride_words) ? row0[w + 1] : 0;
|
|
unsigned int w1_next = (w + 1 < row_stride_words) ? row1[w + 1] : 0;
|
|
|
|
if ((w0 == 0 && w1 == 0 && w0_next == 0 && w1_next == 0) ||
|
|
(w0 == 0xFFFFFFFF && w1 == 0xFFFFFFFF && (w0_next & 1) && (w1_next & 1))) {
|
|
continue;
|
|
}
|
|
|
|
int x_base = w * 32;
|
|
|
|
for (int b = 0; b < 32; ++b) {
|
|
int x = x_base + b;
|
|
if (x >= width - 1) break;
|
|
|
|
int c0 = (w0 >> b) & 1;
|
|
int c1 = (b < 31) ? ((w0 >> (b + 1)) & 1) : (w0_next & 1);
|
|
int c2 = (b < 31) ? ((w1 >> (b + 1)) & 1) : (w1_next & 1);
|
|
int c3 = (w1 >> b) & 1;
|
|
|
|
int case_idx = c0 | (c1 << 1) | (c2 << 2) | (c3 << 3);
|
|
if (case_idx == 0 || case_idx == 15) continue;
|
|
|
|
int e_bottom = get_h_edge_id(x, y, stride_w);
|
|
int e_top = get_h_edge_id(x, y + 1, stride_w);
|
|
int e_left = get_v_edge_id(x, y, stride_w);
|
|
int e_right = get_v_edge_id(x + 1, y, stride_w);
|
|
|
|
switch (case_idx) {
|
|
case 1: add_directed_edge(e_left, e_bottom); break;
|
|
case 2: add_directed_edge(e_bottom, e_right); break;
|
|
case 3: add_directed_edge(e_left, e_right); break;
|
|
case 4: add_directed_edge(e_right, e_top); break;
|
|
case 5: add_directed_edge(e_left, e_top); add_directed_edge(e_bottom, e_right); break;
|
|
case 6: add_directed_edge(e_bottom, e_top); break;
|
|
case 7: add_directed_edge(e_left, e_top); break;
|
|
case 8: add_directed_edge(e_top, e_left); break;
|
|
case 9: add_directed_edge(e_top, e_bottom); break;
|
|
case 10: add_directed_edge(e_bottom, e_left); add_directed_edge(e_top, e_right); break;
|
|
case 11: add_directed_edge(e_top, e_right); break;
|
|
case 12: add_directed_edge(e_right, e_left); break;
|
|
case 13: add_directed_edge(e_right, e_bottom); break;
|
|
case 14: add_directed_edge(e_bottom, e_left); break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (active_edges_buf.empty()) return;
|
|
|
|
std::vector<GpuPoint2D> loop_pts;
|
|
loop_pts.reserve(1000);
|
|
|
|
for (int start_e : active_edges_buf) {
|
|
if (visited_buf[start_e]) continue;
|
|
|
|
loop_pts.clear();
|
|
int cur = start_e;
|
|
|
|
while (cur != -1 && !visited_buf[cur]) {
|
|
visited_buf[cur] = 1;
|
|
GpuPoint2D pt = get_edge_point(cur, stride_w, min_x, min_y, pitch_mm);
|
|
|
|
size_t n = loop_pts.size();
|
|
if (n >= 2) {
|
|
float dx1 = loop_pts[n-1].x - loop_pts[n-2].x;
|
|
float dy1 = loop_pts[n-1].y - loop_pts[n-2].y;
|
|
float dx2 = pt.x - loop_pts[n-1].x;
|
|
float dy2 = pt.y - loop_pts[n-1].y;
|
|
|
|
float cross = dx1 * dy2 - dy1 * dx2;
|
|
if (std::abs(cross) < 1e-6f) {
|
|
loop_pts[n-1] = pt; // simplify collinear vertex
|
|
} else {
|
|
loop_pts.push_back(pt);
|
|
}
|
|
} else {
|
|
loop_pts.push_back(pt);
|
|
}
|
|
|
|
cur = next_edge_buf[cur];
|
|
if (cur == start_e) break;
|
|
if (loop_pts.size() > 50000) break;
|
|
}
|
|
|
|
if (loop_pts.size() >= 3) {
|
|
size_t n = loop_pts.size();
|
|
float dx1 = loop_pts[0].x - loop_pts[n-1].x;
|
|
float dy1 = loop_pts[0].y - loop_pts[n-1].y;
|
|
float dx2 = loop_pts[1].x - loop_pts[0].x;
|
|
float dy2 = loop_pts[1].y - loop_pts[0].y;
|
|
if (std::abs(dx1 * dy2 - dy1 * dx2) < 1e-6f) {
|
|
loop_pts.erase(loop_pts.begin());
|
|
}
|
|
|
|
if (loop_pts.size() >= 3) {
|
|
GpuContour c;
|
|
c.num_points = loop_pts.size();
|
|
c.points = (GpuPoint2D*)malloc(c.num_points * sizeof(GpuPoint2D));
|
|
memcpy(c.points, loop_pts.data(), c.num_points * sizeof(GpuPoint2D));
|
|
c.is_hole = 0;
|
|
out_contours.push_back(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset thread-local edge table for zero re-allocation overhead
|
|
for (int e : active_edges_buf) {
|
|
next_edge_buf[e] = -1;
|
|
visited_buf[e] = 0;
|
|
}
|
|
}
|
|
|
|
#endif // VECTORIZER_H
|