#ifndef VECTORIZER_H #define VECTORIZER_H #include "gpu_offset.h" #include #include #include #include #include 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& next_edge_buf, std::vector& active_edges_buf, std::vector& visited_buf, std::vector& 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 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& next_edge_buf, std::vector& active_edges_buf, std::vector& visited_buf, std::vector& 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 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