fix: apply the safety margin with a distance transform

An elliptical erode kernel costs radius² per pixel. While typing a length,
an intermediate value like "3" mm made the margin hundreds of pixels and
froze the page for minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
immortaly007andClaude Opus 5.5 committed 2026-10-15 15:21:42 +02:00
1 parent 55a25ba978
commit 8ea525a1e8
2 files changed
+33 -7

No files matched your search

+22 -7
View File
@@ -103,14 +103,9 @@ export function traceMask(cv: CV, mask: Mat, marginPx: number): Shape | null {
const padded = new cv.Mat();
const contours = new cv.MatVector();
const hierarchy = new cv.Mat();
let kernel: Mat | null = null;
try {
cv.copyMakeBorder(mask, padded, pad, pad, pad, pad, cv.BORDER_CONSTANT, new cv.Scalar(0));
if (radius > 0) {
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE, new cv.Size(2 * radius + 1, 2 * radius + 1));
if (marginPx < 0) cv.erode(padded, padded, kernel);
else cv.dilate(padded, padded, kernel);
}
if (radius > 0) offsetMask(cv, padded, marginPx < 0 ? -radius : radius);
cv.findContours(padded, contours, hierarchy, cv.RETR_CCOMP, cv.CHAIN_APPROX_SIMPLE);
const count = contours.size();
@@ -146,7 +141,27 @@ export function traceMask(cv: CV, mask: Mat, marginPx: number): Shape | null {
padded.delete();
contours.delete();
hierarchy.delete();
kernel?.delete();
}
}
/**
* Erodes (negative) or dilates (positive) a mask by a radius, in place. Uses a distance transform:
* an elliptical kernel costs radius² per pixel and froze the UI for large margins.
*/
function offsetMask(cv: CV, mask: Mat, radius: number) {
const source = new cv.Mat();
const dist = new cv.Mat();
try {
// Dilating the piece is eroding the background, so measure from the other side.
if (radius > 0) cv.bitwise_not(mask, source);
else mask.copyTo(source);
cv.distanceTransform(source, dist, cv.DIST_L2, cv.DIST_MASK_PRECISE);
const r = Math.abs(radius);
cv.threshold(dist, dist, r, 255, radius > 0 ? cv.THRESH_BINARY_INV : cv.THRESH_BINARY);
dist.convertTo(mask, cv.CV_8U);
} finally {
source.delete();
dist.delete();
}
}