feat(wordcloud): 收口在途开发(布局/存储/前端)+ R4 WCD 生产任务(jobs wcd_file)与生产订单列表

This commit is contained in:
2026-08-13 14:22:48 +08:00
parent 1d17b5e20d
commit e518540235
32 changed files with 3525 additions and 592 deletions
@@ -22,9 +22,9 @@
#include <future>
#include <atomic>
#include <random>
#include <functional>
#include <numeric>
#include <limits>
#include <functional>
#include <numeric>
#include <limits>
// ==========================================
// Thread Pool (avoid per-query thread creation)
@@ -115,13 +115,13 @@ public:
int w;
};
// Original mask-free coordinates. Sorting is lazy because the active
// query_direct/query_near_center paths do not need the O(A log A) order.
std::vector<std::pair<int, int>> valid_coords;
bool valid_coords_center_sorted = false;
double free_center_y = 0.0;
double free_center_x = 0.0;
int spiral_cursor = 1;
// Original mask-free coordinates. Sorting is lazy because the active
// query_direct/query_near_center paths do not need the O(A log A) order.
std::vector<std::pair<int, int>> valid_coords;
bool valid_coords_center_sorted = false;
double free_center_y = 0.0;
double free_center_x = 0.0;
int spiral_cursor = 1;
// Lazy update buffers
std::vector<int32_t> diff;
@@ -139,9 +139,9 @@ public:
}
void init_from_buffer(unsigned char* raw_mask, int h, int w) {
valid_coords.reserve(h * w / 2);
uint64_t free_y_sum = 0;
uint64_t free_x_sum = 0;
valid_coords.reserve(h * w / 2);
uint64_t free_y_sum = 0;
uint64_t free_x_sum = 0;
// Initialize canvas from mask
ensure_canvas();
@@ -157,48 +157,48 @@ public:
if (is_blocked) {
canvas[i * width + j] = 1;
}
if (!is_blocked) {
valid_coords.push_back({i, j});
free_y_sum += (uint64_t)i;
free_x_sum += (uint64_t)j;
}
}
}
if (!valid_coords.empty()) {
free_center_y = (double)free_y_sum / (double)valid_coords.size();
free_center_x = (double)free_x_sum / (double)valid_coords.size();
} else {
free_center_y = (double)h * 0.5;
free_center_x = (double)w * 0.5;
}
valid_coords_center_sorted = false;
spiral_cursor = 1;
if (!is_blocked) {
valid_coords.push_back({i, j});
free_y_sum += (uint64_t)i;
free_x_sum += (uint64_t)j;
}
}
}
if (!valid_coords.empty()) {
free_center_y = (double)free_y_sum / (double)valid_coords.size();
free_center_x = (double)free_x_sum / (double)valid_coords.size();
} else {
free_center_y = (double)h * 0.5;
free_center_x = (double)w * 0.5;
}
valid_coords_center_sorted = false;
spiral_cursor = 1;
std::fill(diff.begin(), diff.end(), 0);
recent_rects.clear();
dirty_count = 0;
}
void ensure_valid_coords_center_sorted() {
if (valid_coords_center_sorted) return;
const double center_y = free_center_y;
const double center_x = free_center_x;
std::sort(valid_coords.begin(), valid_coords.end(),
[center_y, center_x](const std::pair<int, int>& a, const std::pair<int, int>& b) {
double ay = (double)a.first - center_y;
double ax = (double)a.second - center_x;
double by = (double)b.first - center_y;
double bx = (double)b.second - center_x;
return ay * ay + ax * ax < by * by + bx * bx;
}
);
valid_coords_center_sorted = true;
}
// Legacy coordinate ordering remains available to compatibility callers.
void reorder_stratified(int bands) {
std::unique_lock<std::shared_mutex> lock(mutex_);
ensure_valid_coords_center_sorted();
const size_t len = valid_coords.size();
dirty_count = 0;
}
void ensure_valid_coords_center_sorted() {
if (valid_coords_center_sorted) return;
const double center_y = free_center_y;
const double center_x = free_center_x;
std::sort(valid_coords.begin(), valid_coords.end(),
[center_y, center_x](const std::pair<int, int>& a, const std::pair<int, int>& b) {
double ay = (double)a.first - center_y;
double ax = (double)a.second - center_x;
double by = (double)b.first - center_y;
double bx = (double)b.second - center_x;
return ay * ay + ax * ax < by * by + bx * bx;
}
);
valid_coords_center_sorted = true;
}
// Legacy coordinate ordering remains available to compatibility callers.
void reorder_stratified(int bands) {
std::unique_lock<std::shared_mutex> lock(mutex_);
ensure_valid_coords_center_sorted();
const size_t len = valid_coords.size();
if (bands <= 1 || len == 0) return;
if ((size_t)bands > len) bands = (int)len;
@@ -305,7 +305,7 @@ public:
std::fill(diff.begin(), diff.end(), 0);
recent_rects.clear();
dirty_count = 0;
for (int i = 0; i < height; ++i) {
for (int i = 0; i < height; ++i) {
uint32_t row_sum = 0;
for (int j = 0; j < width; ++j) {
uint32_t val = (raw_pixels[i * width + j] > 0) ? 1 : 0;
@@ -343,10 +343,10 @@ public:
}
// Rebuild integral from the internal canvas (partial from pos_r, pos_c)
void rebuild_from_canvas(int pos_r, int pos_c) {
if (canvas.empty()) return;
rebuild_from_bitmap_partial(canvas.data(), pos_r, pos_c);
}
void rebuild_from_canvas(int pos_r, int pos_c) {
if (canvas.empty()) return;
rebuild_from_bitmap_partial(canvas.data(), pos_r, pos_c);
}
// v4: Partial integral rebuild from position (pos_r, pos_c) downward
// Optimized: use row-sum approach (like rebuild_from_bitmap) for the partial region
@@ -450,9 +450,9 @@ public:
return {-1, -1};
}
DirectResult query_direct(int box_h, int box_w, uint32_t seed) {
// Apply pending rectangle updates before reading the integral image.
// Exact-glyph placement uses its own canvas-only path.
DirectResult query_direct(int box_h, int box_w, uint32_t seed) {
// Apply pending rectangle updates before reading the integral image.
// Exact-glyph placement uses its own canvas-only path.
flush();
int max_row = height - box_h;
@@ -473,12 +473,12 @@ public:
return {true, y, x};
}
// Random probes avoid an O(H*W) scan during early and middle packing.
// Increase the budget as occupancy rises.
{
const double occ_ratio = (double)total_occupied / (double)(width * height);
const int probe_budget = (occ_ratio < 0.15) ? 32 : (occ_ratio < 0.40) ? 96 : 192;
const int n_probes = (int)std::min<int64_t>(probe_budget, total_positions);
// Random probes avoid an O(H*W) scan during early and middle packing.
// Increase the budget as occupancy rises.
{
const double occ_ratio = (double)total_occupied / (double)(width * height);
const int probe_budget = (occ_ratio < 0.15) ? 32 : (occ_ratio < 0.40) ? 96 : 192;
const int n_probes = (int)std::min<int64_t>(probe_budget, total_positions);
for (int p = 0; p < n_probes; ++p) {
int y = std::uniform_int_distribution<int>(0, max_row)(rng);
int x = std::uniform_int_distribution<int>(0, max_col)(rng);
@@ -496,28 +496,28 @@ public:
}
if (nt <= 1) {
// Single-pass reservoir halves the work of count-then-pick. A
// small LCG avoids invoking mt19937 for every valid position.
uint64_t count = 0;
int best_y = -1, best_x = -1;
uint32_t state = seed ? seed : 1u;
// Single-pass reservoir halves the work of count-then-pick. A
// small LCG avoids invoking mt19937 for every valid position.
uint64_t count = 0;
int best_y = -1, best_x = -1;
uint32_t state = seed ? seed : 1u;
for (int i = 0; i <= max_row; ++i) {
for (int j = 0; j <= max_col; ++j) {
if (get_area_sum_fast(i, j, box_h, box_w) == 0) {
++count;
// Replace the current result with probability 1/count.
state = state * 1664525u + 1013904223u;
const bool replace = (state % count) == 0;
if (replace) {
best_y = i;
best_x = j;
}
// Replace the current result with probability 1/count.
state = state * 1664525u + 1013904223u;
const bool replace = (state % count) == 0;
if (replace) {
best_y = i;
best_x = j;
}
}
}
}
return count == 0
? DirectResult{false, -1, -1}
: DirectResult{true, best_y, best_x};
return count == 0
? DirectResult{false, -1, -1}
: DirectResult{true, best_y, best_x};
}
// Multi-thread path: parallel count then pick
@@ -559,193 +559,257 @@ public:
}
cum += chunk_counts[t];
}
return {false, -1, -1};
}
// Sample legal positions and prefer the one whose box center is closest
// to the free-mask centroid. This gives the visually important large
// words a stable focal region without paying for a full spiral scan.
DirectResult query_near_center(int box_h, int box_w, uint32_t seed, int probes) {
flush();
int max_row = height - box_h;
int max_col = width - box_w;
if (max_row < 0 || max_col < 0) return {false, -1, -1};
std::mt19937 rng(seed);
probes = std::max(16, std::min(probes, 1024));
int best_y = -1;
int best_x = -1;
double best_score = std::numeric_limits<double>::infinity();
std::uniform_int_distribution<int> row_dist(0, max_row);
std::uniform_int_distribution<int> col_dist(0, max_col);
std::uniform_real_distribution<double> jitter(0.0, 1e-4);
// Test the centroid-aligned position first.
int center_y = std::max(0, std::min(max_row, (int)std::lround(free_center_y - box_h * 0.5)));
int center_x = std::max(0, std::min(max_col, (int)std::lround(free_center_x - box_w * 0.5)));
if (get_area_sum_fast(center_y, center_x, box_h, box_w) == 0) {
return {true, center_y, center_x};
}
const double norm_y = std::max(1.0, (double)height);
const double norm_x = std::max(1.0, (double)width);
for (int p = 0; p < probes; ++p) {
int y = row_dist(rng);
int x = col_dist(rng);
if (get_area_sum_fast(y, x, box_h, box_w) != 0) continue;
double cy = (double)y + box_h * 0.5;
double cx = (double)x + box_w * 0.5;
double dy = (cy - free_center_y) / norm_y;
double dx = (cx - free_center_x) / norm_x;
double score = dy * dy + dx * dx + jitter(rng);
if (score < best_score) {
best_score = score;
best_y = y;
best_x = x;
}
}
if (best_y >= 0) return {true, best_y, best_x};
return query_direct(box_h, box_w, seed ^ 0x9E3779B9u);
}
inline bool glyph_fits_exact(
const unsigned char* glyph, int glyph_h, int glyph_w, int y, int x
) const {
for (int row = 0; row < glyph_h; ++row) {
const unsigned char* glyph_row = glyph + row * glyph_w;
const uint8_t* canvas_row = canvas.data() + (y + row) * width + x;
for (int col = 0; col < glyph_w; ++col) {
if (glyph_row[col] > 0 && canvas_row[col] != 0) return false;
}
}
return true;
}
inline void reserve_glyph_exact(
const unsigned char* glyph, int glyph_h, int glyph_w, int y, int x
) {
stamp_glyph(glyph, glyph_h, glyph_w, y, x);
}
// Find and reserve a position using the actual (optionally dilated) glyph
// bitmap. Unlike rectangle queries, transparent corners and gaps between
// strokes may overlap safely. Search and reservation stay in one C++ call,
// so no integral-image rebuild is needed between words.
DirectResult place_glyph_exact(
const unsigned char* collision_glyph,
const unsigned char* stamp_glyph_data,
int glyph_h,
int glyph_w,
uint32_t seed,
int probes,
int placement_mode
) {
ensure_canvas();
const int max_row = height - glyph_h;
const int max_col = width - glyph_w;
if (max_row < 0 || max_col < 0) return {false, -1, -1};
std::mt19937 rng(seed);
probes = std::max(32, std::min(probes, 2048));
std::uniform_int_distribution<int> row_dist(0, max_row);
std::uniform_int_distribution<int> col_dist(0, max_col);
int best_y = -1;
int best_x = -1;
double best_score = std::numeric_limits<double>::infinity();
if (placement_mode != 0) {
const int center_y = std::max(
0, std::min(max_row, (int)std::lround(free_center_y - glyph_h * 0.5))
);
const int center_x = std::max(
0, std::min(max_col, (int)std::lround(free_center_x - glyph_w * 0.5))
);
if (glyph_fits_exact(collision_glyph, glyph_h, glyph_w, center_y, center_x)) {
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, center_y, center_x);
return {true, center_y, center_x};
}
}
const double norm_y = std::max(1.0, (double)height);
const double norm_x = std::max(1.0, (double)width);
// Mode 1 is a persistent centre-out Fermat spiral. Mode 2 skips the
// spiral and selects a random legal candidate biased toward the centre.
if (placement_mode == 1) {
constexpr double golden_angle = 2.39996322972865332;
constexpr double sample_spacing = 1.25;
const int start_step = spiral_cursor;
const int end_step = std::min(200000, start_step + 60000);
int last_y = std::numeric_limits<int>::min();
int last_x = std::numeric_limits<int>::min();
for (int step = start_step; step <= end_step; ++step) {
const double radius = sample_spacing * std::sqrt((double)step);
const double theta = golden_angle * (double)step;
const int y = (int)std::lround(
free_center_y + radius * std::sin(theta) - glyph_h * 0.5
);
const int x = (int)std::lround(
free_center_x + radius * std::cos(theta) - glyph_w * 0.5
);
if (y == last_y && x == last_x) continue;
last_y = y;
last_x = x;
if (y < 0 || x < 0 || y > max_row || x > max_col) continue;
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, y, x);
spiral_cursor = step;
return {true, y, x};
}
}
for (int probe = 0; probe < probes; ++probe) {
const int y = row_dist(rng);
const int x = col_dist(rng);
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
if (placement_mode == 0) {
stamp_glyph(stamp_glyph_data, glyph_h, glyph_w, y, x);
return {true, y, x};
}
const double cy = (double)y + glyph_h * 0.5;
const double cx = (double)x + glyph_w * 0.5;
const double dy = (cy - free_center_y) / norm_y;
const double dx = (cx - free_center_x) / norm_x;
const double score = dy * dy + dx * dx;
if (score < best_score) {
best_score = score;
best_y = y;
best_x = x;
}
}
if (best_y >= 0) {
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, best_y, best_x);
return {true, best_y, best_x};
}
// Dense late-stage fallback. Start at a seeded offset to avoid a
// top-left bias, but visit every possible origin so completeness is
// deterministic whenever a legal position exists.
const int n_rows = max_row + 1;
const int n_cols = max_col + 1;
const int row_start = row_dist(rng);
const int col_start = col_dist(rng);
for (int row_offset = 0; row_offset < n_rows; ++row_offset) {
const int y = (row_start + row_offset) % n_rows;
for (int col_offset = 0; col_offset < n_cols; ++col_offset) {
const int x = (col_start + col_offset) % n_cols;
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, y, x);
return {true, y, x};
}
}
return {false, -1, -1};
}
// =========================================================
return {false, -1, -1};
}
// Sample legal positions and prefer the one whose box center is closest
// to the free-mask centroid. This gives the visually important large
// words a stable focal region without paying for a full spiral scan.
DirectResult query_near_center(int box_h, int box_w, uint32_t seed, int probes) {
flush();
int max_row = height - box_h;
int max_col = width - box_w;
if (max_row < 0 || max_col < 0) return {false, -1, -1};
std::mt19937 rng(seed);
probes = std::max(16, std::min(probes, 1024));
int best_y = -1;
int best_x = -1;
double best_score = std::numeric_limits<double>::infinity();
std::uniform_int_distribution<int> row_dist(0, max_row);
std::uniform_int_distribution<int> col_dist(0, max_col);
std::uniform_real_distribution<double> jitter(0.0, 1e-4);
// Test the centroid-aligned position first.
int center_y = std::max(0, std::min(max_row, (int)std::lround(free_center_y - box_h * 0.5)));
int center_x = std::max(0, std::min(max_col, (int)std::lround(free_center_x - box_w * 0.5)));
if (get_area_sum_fast(center_y, center_x, box_h, box_w) == 0) {
return {true, center_y, center_x};
}
const double norm_y = std::max(1.0, (double)height);
const double norm_x = std::max(1.0, (double)width);
for (int p = 0; p < probes; ++p) {
int y = row_dist(rng);
int x = col_dist(rng);
if (get_area_sum_fast(y, x, box_h, box_w) != 0) continue;
double cy = (double)y + box_h * 0.5;
double cx = (double)x + box_w * 0.5;
double dy = (cy - free_center_y) / norm_y;
double dx = (cx - free_center_x) / norm_x;
double score = dy * dy + dx * dx + jitter(rng);
if (score < best_score) {
best_score = score;
best_y = y;
best_x = x;
}
}
if (best_y >= 0) return {true, best_y, best_x};
return query_direct(box_h, box_w, seed ^ 0x9E3779B9u);
}
inline bool glyph_fits_exact(
const unsigned char* glyph, int glyph_h, int glyph_w, int y, int x
) const {
for (int row = 0; row < glyph_h; ++row) {
const unsigned char* glyph_row = glyph + row * glyph_w;
const uint8_t* canvas_row = canvas.data() + (y + row) * width + x;
for (int col = 0; col < glyph_w; ++col) {
if (glyph_row[col] > 0 && canvas_row[col] != 0) return false;
}
}
return true;
}
inline void reserve_glyph_exact(
const unsigned char* glyph, int glyph_h, int glyph_w, int y, int x
) {
stamp_glyph(glyph, glyph_h, glyph_w, y, x);
}
// Find and reserve a position using the actual (optionally dilated) glyph
// bitmap. Unlike rectangle queries, transparent corners and gaps between
// strokes may overlap safely. Search and reservation stay in one C++ call,
// so no integral-image rebuild is needed between words.
DirectResult place_glyph_exact(
const unsigned char* collision_glyph,
const unsigned char* stamp_glyph_data,
int glyph_h,
int glyph_w,
uint32_t seed,
int probes,
int placement_mode
) {
ensure_canvas();
const int max_row = height - glyph_h;
const int max_col = width - glyph_w;
if (max_row < 0 || max_col < 0) return {false, -1, -1};
std::mt19937 rng(seed);
probes = std::max(32, std::min(probes, 2048));
std::uniform_int_distribution<int> row_dist(0, max_row);
std::uniform_int_distribution<int> col_dist(0, max_col);
int best_y = -1;
int best_x = -1;
double best_score = std::numeric_limits<double>::infinity();
// Only the spiral (mode 1) may anchor its first probe on the exact
// mask centroid; that is the "small words spiral out from the centre"
// behaviour we want. Mode 2 (large words) deliberately skips it: when
// every mode kept this shortcut, whichever word happened to be placed
// first landed on the same centroid pixel in every single generation,
// regardless of layout_seed, giving every export an unmoving bullseye.
if (placement_mode == 1) {
const int center_y = std::max(
0, std::min(max_row, (int)std::lround(free_center_y - glyph_h * 0.5))
);
const int center_x = std::max(
0, std::min(max_col, (int)std::lround(free_center_x - glyph_w * 0.5))
);
if (glyph_fits_exact(collision_glyph, glyph_h, glyph_w, center_y, center_x)) {
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, center_y, center_x);
return {true, center_y, center_x};
}
}
const double norm_y = std::max(1.0, (double)height);
const double norm_x = std::max(1.0, (double)width);
// Mode 1 is a persistent centre-out Fermat spiral. Mode 2 skips the
// spiral and places the word by random probe.
if (placement_mode == 1) {
constexpr double golden_angle = 2.39996322972865332;
constexpr double sample_spacing = 1.25;
constexpr double two_pi = 6.283185307179586;
// Each word starts its sweep at a random angle instead of exactly
// golden_angle past the previous one, so consecutive words are no
// longer locked into the fixed angular step that draws a textbook
// Vogel/sunflower figure. The draw comes from `rng`, already seeded
// per word from layout_seed, so a given seed still reproduces
// exactly, and different seeds now give genuinely different
// arrangements rather than the same figure with the names permuted.
//
// The offset deliberately spans the full circle. A *bounded* offset
// was tried and is much worse than useless: confining the sweep to a
// wedge makes a word skip positions at the packed frontier and
// settle for a worse one, which measured a 31% loss of final ink
// density (0.255 -> 0.176) on an 800-word cloud. Spanning the whole
// circle costs nothing, because the sweep still reaches every angle
// as the radius grows.
//
// Note this does not make the cloud look unstructured. Radius still
// tracks placement order closely (Pearson r ~= 0.98), because a
// centre-out fill that stays dense has to grow outward in order --
// the ordering and the density are the same property. Breaking that
// appearance without paying for it needs several spiral origins
// rather than jitter on one, which is a larger change than this.
std::uniform_real_distribution<double> phase_jitter(0.0, two_pi);
const double theta_offset = phase_jitter(rng);
const int start_step = spiral_cursor;
const int end_step = std::min(200000, start_step + 60000);
int last_y = std::numeric_limits<int>::min();
int last_x = std::numeric_limits<int>::min();
for (int step = start_step; step <= end_step; ++step) {
const double radius = sample_spacing * std::sqrt((double)step);
const double theta = golden_angle * (double)step + theta_offset;
const int y = (int)std::lround(
free_center_y + radius * std::sin(theta) - glyph_h * 0.5
);
const int x = (int)std::lround(
free_center_x + radius * std::cos(theta) - glyph_w * 0.5
);
if (y == last_y && x == last_x) continue;
last_y = y;
last_x = x;
if (y < 0 || x < 0 || y > max_row || x > max_col) continue;
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, y, x);
spiral_cursor = step;
return {true, y, x};
}
}
// Soft radial bound for mode 2. Pure first-accept scatter over the whole
// canvas let large words land far outside the crowd, leaving detached
// stragglers and a ragged silhouette. Draws beyond the bound are
// rejected, but the bound widens as the probe budget is consumed and is
// gone entirely for the last quarter of the probes, so this only biases
// *where* a word prefers to land -- it never removes a legal position
// and so cannot cost completeness.
const double mask_radius = 0.5 * std::sqrt(
(double)height * (double)height + (double)width * (double)width
);
for (int probe = 0; probe < probes; ++probe) {
const int y = row_dist(rng);
const int x = col_dist(rng);
if (placement_mode == 2) {
const double frac = (double)probe / (double)probes;
if (frac < 0.75) {
// 0.55 -> 1.0 of the mask radius over the first 75% of probes.
const double limit = mask_radius * (0.55 + 0.60 * frac);
const double cy = (double)y + glyph_h * 0.5 - free_center_y;
const double cx = (double)x + glyph_w * 0.5 - free_center_x;
if (cy * cy + cx * cx > limit * limit) continue;
}
}
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
if (placement_mode == 0) {
stamp_glyph(stamp_glyph_data, glyph_h, glyph_w, y, x);
return {true, y, x};
}
// Mode 2 (large words) takes the first legal random draw inside the
// radial bound above. It used to scan the whole probe budget and keep
// the candidate closest to the mask centroid, which packed every
// large word into one tight rosette at the centre -- the innermost
// radial shell held no spiral words at all, leaving a hard seam
// between a dense core and the spiral field. First-accept scatters
// them across the mass as intended, and is cheaper: it can return on
// the first hit instead of always running all `probes` fit tests.
if (placement_mode == 2) {
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, y, x);
return {true, y, x};
}
const double cy = (double)y + glyph_h * 0.5;
const double cx = (double)x + glyph_w * 0.5;
const double dy = (cy - free_center_y) / norm_y;
const double dx = (cx - free_center_x) / norm_x;
const double score = dy * dy + dx * dx;
if (score < best_score) {
best_score = score;
best_y = y;
best_x = x;
}
}
if (best_y >= 0) {
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, best_y, best_x);
return {true, best_y, best_x};
}
// Dense late-stage fallback. Start at a seeded offset to avoid a
// top-left bias, but visit every possible origin so completeness is
// deterministic whenever a legal position exists.
const int n_rows = max_row + 1;
const int n_cols = max_col + 1;
const int row_start = row_dist(rng);
const int col_start = col_dist(rng);
for (int row_offset = 0; row_offset < n_rows; ++row_offset) {
const int y = (row_start + row_offset) % n_rows;
for (int col_offset = 0; col_offset < n_cols; ++col_offset) {
const int x = (col_start + col_offset) % n_cols;
if (!glyph_fits_exact(collision_glyph, glyph_h, glyph_w, y, x)) continue;
reserve_glyph_exact(stamp_glyph_data, glyph_h, glyph_w, y, x);
return {true, y, x};
}
}
return {false, -1, -1};
}
// =========================================================
// v3: Batch query — process multiple (box_h, box_w) in one call
// Returns vector of {found, y, x} for each query
// =========================================================
@@ -786,13 +850,13 @@ public:
return {false, -1, -1, end_idx};
}
std::pair<bool, std::pair<int, int>> find_spot_parallel(int box_h, int box_w, int step) {
{
std::unique_lock<std::shared_mutex> lock(mutex_);
ensure_valid_coords_center_sorted();
}
{
std::shared_lock<std::shared_mutex> lock(mutex_);
std::pair<bool, std::pair<int, int>> find_spot_parallel(int box_h, int box_w, int step) {
{
std::unique_lock<std::shared_mutex> lock(mutex_);
ensure_valid_coords_center_sorted();
}
{
std::shared_lock<std::shared_mutex> lock(mutex_);
if (dirty_count > 0 && dirty_count >= rebuild_interval) {
lock.unlock();
std::unique_lock<std::shared_mutex> write_lock(mutex_);
@@ -894,67 +958,67 @@ static PyObject* Grid_query_reservoir(PyIntegralGrid* self, PyObject* args) {
}
// v3: Direct pixel-grid scan with parallel counting
static PyObject* Grid_query_direct(PyIntegralGrid* self, PyObject* args) {
static PyObject* Grid_query_direct(PyIntegralGrid* self, PyObject* args) {
int box_h, box_w;
unsigned int seed = 0;
if (!PyArg_ParseTuple(args, "ii|I", &box_h, &box_w, &seed)) return NULL;
auto r = self->grid->query_direct(box_h, box_w, seed);
auto r = self->grid->query_direct(box_h, box_w, seed);
if (r.found) return Py_BuildValue("ii", r.y, r.x);
Py_RETURN_NONE;
}
static PyObject* Grid_query_near_center(PyIntegralGrid* self, PyObject* args) {
int box_h, box_w;
unsigned int seed = 0;
int probes = 160;
if (!PyArg_ParseTuple(args, "ii|Ii", &box_h, &box_w, &seed, &probes)) return NULL;
auto r = self->grid->query_near_center(box_h, box_w, seed, probes);
if (r.found) return Py_BuildValue("ii", r.y, r.x);
Py_RETURN_NONE;
}
static PyObject* Grid_place_glyph_exact(PyIntegralGrid* self, PyObject* args) {
PyObject* collision_obj;
PyObject* stamp_obj;
int glyph_h, glyph_w;
unsigned int seed = 0;
int probes = 256;
int placement_mode = 0;
if (!PyArg_ParseTuple(
args, "OOiiIii", &collision_obj, &stamp_obj, &glyph_h, &glyph_w, &seed, &probes, &placement_mode
)) return NULL;
Py_buffer collision_view;
Py_buffer stamp_view;
if (PyObject_GetBuffer(collision_obj, &collision_view, PyBUF_SIMPLE) < 0) return NULL;
if (PyObject_GetBuffer(stamp_obj, &stamp_view, PyBUF_SIMPLE) < 0) {
PyBuffer_Release(&collision_view);
return NULL;
}
const Py_ssize_t required = (Py_ssize_t)glyph_h * (Py_ssize_t)glyph_w;
if (glyph_h <= 0 || glyph_w <= 0 || collision_view.len < required || stamp_view.len < required) {
PyBuffer_Release(&collision_view);
PyBuffer_Release(&stamp_view);
PyErr_SetString(PyExc_ValueError, "glyph buffer is smaller than glyph_h * glyph_w");
return NULL;
}
auto result = self->grid->place_glyph_exact(
(const unsigned char*)collision_view.buf,
(const unsigned char*)stamp_view.buf,
glyph_h,
glyph_w,
seed,
probes,
placement_mode
);
PyBuffer_Release(&collision_view);
PyBuffer_Release(&stamp_view);
if (result.found) return Py_BuildValue("ii", result.y, result.x);
Py_RETURN_NONE;
}
Py_RETURN_NONE;
}
static PyObject* Grid_query_near_center(PyIntegralGrid* self, PyObject* args) {
int box_h, box_w;
unsigned int seed = 0;
int probes = 160;
if (!PyArg_ParseTuple(args, "ii|Ii", &box_h, &box_w, &seed, &probes)) return NULL;
auto r = self->grid->query_near_center(box_h, box_w, seed, probes);
if (r.found) return Py_BuildValue("ii", r.y, r.x);
Py_RETURN_NONE;
}
static PyObject* Grid_place_glyph_exact(PyIntegralGrid* self, PyObject* args) {
PyObject* collision_obj;
PyObject* stamp_obj;
int glyph_h, glyph_w;
unsigned int seed = 0;
int probes = 256;
int placement_mode = 0;
if (!PyArg_ParseTuple(
args, "OOiiIii", &collision_obj, &stamp_obj, &glyph_h, &glyph_w, &seed, &probes, &placement_mode
)) return NULL;
Py_buffer collision_view;
Py_buffer stamp_view;
if (PyObject_GetBuffer(collision_obj, &collision_view, PyBUF_SIMPLE) < 0) return NULL;
if (PyObject_GetBuffer(stamp_obj, &stamp_view, PyBUF_SIMPLE) < 0) {
PyBuffer_Release(&collision_view);
return NULL;
}
const Py_ssize_t required = (Py_ssize_t)glyph_h * (Py_ssize_t)glyph_w;
if (glyph_h <= 0 || glyph_w <= 0 || collision_view.len < required || stamp_view.len < required) {
PyBuffer_Release(&collision_view);
PyBuffer_Release(&stamp_view);
PyErr_SetString(PyExc_ValueError, "glyph buffer is smaller than glyph_h * glyph_w");
return NULL;
}
auto result = self->grid->place_glyph_exact(
(const unsigned char*)collision_view.buf,
(const unsigned char*)stamp_view.buf,
glyph_h,
glyph_w,
seed,
probes,
placement_mode
);
PyBuffer_Release(&collision_view);
PyBuffer_Release(&stamp_view);
if (result.found) return Py_BuildValue("ii", result.y, result.x);
Py_RETURN_NONE;
}
// v3: Batch query — process multiple placements in one C++ call
// Input: list of (box_h, box_w, seed) tuples
@@ -1031,16 +1095,16 @@ static PyObject* Grid_rebuild_from_bitmap_partial(PyIntegralGrid* self, PyObject
Py_RETURN_NONE;
}
// Stamp glyph bitmap onto C++ canvas and rebuild the affected integral region.
static PyObject* Grid_stamp_and_rebuild(PyIntegralGrid* self, PyObject* args) {
// Stamp glyph bitmap onto C++ canvas and rebuild the affected integral region.
static PyObject* Grid_stamp_and_rebuild(PyIntegralGrid* self, PyObject* args) {
PyObject* glyph_obj;
int gh, gw, pos_r, pos_c;
if (!PyArg_ParseTuple(args, "Oiiii", &glyph_obj, &gh, &gw, &pos_r, &pos_c)) return NULL;
Py_buffer view;
if (PyObject_GetBuffer(glyph_obj, &view, PyBUF_SIMPLE) < 0) return NULL;
self->grid->stamp_glyph((const unsigned char*)view.buf, gh, gw, pos_r, pos_c);
self->grid->rebuild_from_canvas(pos_r, pos_c);
self->grid->stamp_glyph((const unsigned char*)view.buf, gh, gw, pos_r, pos_c);
self->grid->rebuild_from_canvas(pos_r, pos_c);
PyBuffer_Release(&view);
Py_RETURN_NONE;
@@ -1049,10 +1113,10 @@ static PyObject* Grid_stamp_and_rebuild(PyIntegralGrid* self, PyObject* args) {
static PyMethodDef Grid_methods[] = {
{"reorder_stratified", (PyCFunction)Grid_reorder_stratified, METH_VARARGS, "Reorder valid coords with stratified interleaving."},
{"query_sorted", (PyCFunction)Grid_query_sorted, METH_VARARGS, "Find position using sorted coordinate list (center-out, parallel)."},
{"query_reservoir", (PyCFunction)Grid_query_reservoir, METH_VARARGS, "Find position using parallel direct-scan reservoir sampling."},
{"query_direct", (PyCFunction)Grid_query_direct, METH_VARARGS, "Direct pixel-grid scan with parallel reservoir sampling."},
{"query_near_center", (PyCFunction)Grid_query_near_center, METH_VARARGS, "Sample legal positions and prefer the free-mask centroid."},
{"place_glyph_exact", (PyCFunction)Grid_place_glyph_exact, METH_VARARGS, "Place and reserve an exact glyph bitmap without changing its size."},
{"query_reservoir", (PyCFunction)Grid_query_reservoir, METH_VARARGS, "Find position using parallel direct-scan reservoir sampling."},
{"query_direct", (PyCFunction)Grid_query_direct, METH_VARARGS, "Direct pixel-grid scan with parallel reservoir sampling."},
{"query_near_center", (PyCFunction)Grid_query_near_center, METH_VARARGS, "Sample legal positions and prefer the free-mask centroid."},
{"place_glyph_exact", (PyCFunction)Grid_place_glyph_exact, METH_VARARGS, "Place and reserve an exact glyph bitmap without changing its size."},
{"batch_query", (PyCFunction)Grid_batch_query, METH_VARARGS, "Batch placement: list of (bh,bw,seed) -> list of (y,x)|None."},
{"update", (PyCFunction)Grid_update, METH_VARARGS, "Update grid with placed rectangle."},
{"flush", (PyCFunction)Grid_flush, METH_NOARGS, "Force rebuild integral image."},