@pooder/kit 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.test-dist/src/CanvasService.js +249 -249
- package/.test-dist/src/ViewportSystem.js +75 -75
- package/.test-dist/src/background.js +203 -203
- package/.test-dist/src/bridgeSelection.js +20 -20
- package/.test-dist/src/constraints.js +237 -237
- package/.test-dist/src/dieline.js +818 -818
- package/.test-dist/src/edgeScale.js +12 -12
- package/.test-dist/src/extensions/background.js +203 -0
- package/.test-dist/src/extensions/bridgeSelection.js +20 -0
- package/.test-dist/src/extensions/constraints.js +237 -0
- package/.test-dist/src/extensions/dieline.js +828 -0
- package/.test-dist/src/extensions/edgeScale.js +12 -0
- package/.test-dist/src/extensions/feature.js +825 -0
- package/.test-dist/src/extensions/featureComplete.js +32 -0
- package/.test-dist/src/extensions/film.js +167 -0
- package/.test-dist/src/extensions/geometry.js +545 -0
- package/.test-dist/src/extensions/image.js +1529 -0
- package/.test-dist/src/extensions/index.js +30 -0
- package/.test-dist/src/extensions/maskOps.js +279 -0
- package/.test-dist/src/extensions/mirror.js +104 -0
- package/.test-dist/src/extensions/ruler.js +345 -0
- package/.test-dist/src/extensions/sceneLayout.js +96 -0
- package/.test-dist/src/extensions/sceneLayoutModel.js +196 -0
- package/.test-dist/src/extensions/sceneVisibility.js +62 -0
- package/.test-dist/src/extensions/size.js +331 -0
- package/.test-dist/src/extensions/tracer.js +538 -0
- package/.test-dist/src/extensions/white-ink.js +1190 -0
- package/.test-dist/src/extensions/wrappedOffsets.js +33 -0
- package/.test-dist/src/feature.js +826 -826
- package/.test-dist/src/featureComplete.js +32 -32
- package/.test-dist/src/film.js +167 -167
- package/.test-dist/src/geometry.js +506 -506
- package/.test-dist/src/image.js +1250 -1250
- package/.test-dist/src/index.js +2 -19
- package/.test-dist/src/maskOps.js +270 -270
- package/.test-dist/src/mirror.js +104 -104
- package/.test-dist/src/renderSpec.js +2 -2
- package/.test-dist/src/ruler.js +343 -343
- package/.test-dist/src/sceneLayout.js +99 -99
- package/.test-dist/src/sceneLayoutModel.js +196 -196
- package/.test-dist/src/sceneView.js +40 -40
- package/.test-dist/src/sceneVisibility.js +42 -42
- package/.test-dist/src/services/CanvasService.js +249 -0
- package/.test-dist/src/services/ViewportSystem.js +76 -0
- package/.test-dist/src/services/index.js +24 -0
- package/.test-dist/src/services/renderSpec.js +2 -0
- package/.test-dist/src/size.js +332 -332
- package/.test-dist/src/tracer.js +544 -544
- package/.test-dist/src/white-ink.js +829 -829
- package/.test-dist/src/wrappedOffsets.js +33 -33
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +411 -375
- package/dist/index.mjs +411 -375
- package/package.json +1 -1
- package/src/coordinate.ts +106 -106
- package/src/extensions/background.ts +230 -230
- package/src/extensions/bridgeSelection.ts +17 -17
- package/src/extensions/constraints.ts +322 -322
- package/src/extensions/dieline.ts +20 -17
- package/src/extensions/edgeScale.ts +19 -19
- package/src/extensions/feature.ts +1021 -1021
- package/src/extensions/featureComplete.ts +46 -46
- package/src/extensions/film.ts +194 -194
- package/src/extensions/geometry.ts +719 -719
- package/src/extensions/image.ts +1924 -1924
- package/src/extensions/index.ts +11 -11
- package/src/extensions/maskOps.ts +365 -299
- package/src/extensions/mirror.ts +128 -128
- package/src/extensions/ruler.ts +451 -451
- package/src/extensions/sceneLayout.ts +140 -140
- package/src/extensions/sceneLayoutModel.ts +342 -342
- package/src/extensions/sceneVisibility.ts +71 -71
- package/src/extensions/size.ts +389 -389
- package/src/extensions/tracer.ts +302 -370
- package/src/extensions/white-ink.ts +1489 -1366
- package/src/extensions/wrappedOffsets.ts +33 -33
- package/src/index.ts +2 -2
- package/src/services/CanvasService.ts +300 -300
- package/src/services/ViewportSystem.ts +95 -95
- package/src/services/index.ts +3 -3
- package/src/services/renderSpec.ts +18 -18
- package/src/units.ts +27 -27
- package/tests/run.ts +118 -118
- package/tsconfig.test.json +15 -15
package/dist/index.js
CHANGED
|
@@ -2993,15 +2993,19 @@ function analyzeAlpha(imageData, alphaOpaqueCutoff) {
|
|
|
2993
2993
|
};
|
|
2994
2994
|
}
|
|
2995
2995
|
function circularMorphology(mask, width, height, radius, op) {
|
|
2996
|
-
const
|
|
2996
|
+
const r = Math.max(0, Math.floor(radius));
|
|
2997
|
+
if (r <= 0) {
|
|
2998
|
+
return mask.slice();
|
|
2999
|
+
}
|
|
3000
|
+
const dilateDisk = (m, radiusPx) => {
|
|
2997
3001
|
const horizontalDist = new Int32Array(width * height);
|
|
2998
3002
|
for (let y = 0; y < height; y++) {
|
|
2999
|
-
let lastSolid = -
|
|
3003
|
+
let lastSolid = -radiusPx * 2;
|
|
3000
3004
|
for (let x = 0; x < width; x++) {
|
|
3001
3005
|
if (m[y * width + x]) lastSolid = x;
|
|
3002
3006
|
horizontalDist[y * width + x] = x - lastSolid;
|
|
3003
3007
|
}
|
|
3004
|
-
lastSolid = width +
|
|
3008
|
+
lastSolid = width + radiusPx * 2;
|
|
3005
3009
|
for (let x = width - 1; x >= 0; x--) {
|
|
3006
3010
|
if (m[y * width + x]) lastSolid = x;
|
|
3007
3011
|
horizontalDist[y * width + x] = Math.min(
|
|
@@ -3011,12 +3015,12 @@ function circularMorphology(mask, width, height, radius, op) {
|
|
|
3011
3015
|
}
|
|
3012
3016
|
}
|
|
3013
3017
|
const result = new Uint8Array(width * height);
|
|
3014
|
-
const r2 =
|
|
3018
|
+
const r2 = radiusPx * radiusPx;
|
|
3015
3019
|
for (let x = 0; x < width; x++) {
|
|
3016
3020
|
for (let y = 0; y < height; y++) {
|
|
3017
3021
|
let found = false;
|
|
3018
|
-
const minY = Math.max(0, y -
|
|
3019
|
-
const maxY = Math.min(height - 1, y +
|
|
3022
|
+
const minY = Math.max(0, y - radiusPx);
|
|
3023
|
+
const maxY = Math.min(height - 1, y + radiusPx);
|
|
3020
3024
|
for (let dy = minY; dy <= maxY; dy++) {
|
|
3021
3025
|
const dY = dy - y;
|
|
3022
3026
|
const hDist = horizontalDist[dy * width + x];
|
|
@@ -3030,23 +3034,62 @@ function circularMorphology(mask, width, height, radius, op) {
|
|
|
3030
3034
|
}
|
|
3031
3035
|
return result;
|
|
3032
3036
|
};
|
|
3033
|
-
const
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3037
|
+
const erodeDiamond = (m, radiusPx) => {
|
|
3038
|
+
if (radiusPx <= 0) return m.slice();
|
|
3039
|
+
let current = m;
|
|
3040
|
+
for (let step = 0; step < radiusPx; step++) {
|
|
3041
|
+
const next = new Uint8Array(width * height);
|
|
3042
|
+
for (let y = 1; y < height - 1; y++) {
|
|
3043
|
+
const row = y * width;
|
|
3044
|
+
for (let x = 1; x < width - 1; x++) {
|
|
3045
|
+
const idx = row + x;
|
|
3046
|
+
if (current[idx] && current[idx - 1] && current[idx + 1] && current[idx - width] && current[idx + width]) {
|
|
3047
|
+
next[idx] = 1;
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
current = next;
|
|
3052
|
+
}
|
|
3053
|
+
return current;
|
|
3054
|
+
};
|
|
3055
|
+
const restoreBridgePixels = (source, eroded) => {
|
|
3056
|
+
const restored = eroded.slice();
|
|
3057
|
+
for (let y = 1; y < height - 1; y++) {
|
|
3058
|
+
const row = y * width;
|
|
3059
|
+
for (let x = 1; x < width - 1; x++) {
|
|
3060
|
+
const idx = row + x;
|
|
3061
|
+
if (!source[idx] || restored[idx]) continue;
|
|
3062
|
+
const up = source[idx - width] === 1;
|
|
3063
|
+
const down = source[idx + width] === 1;
|
|
3064
|
+
const left = source[idx - 1] === 1;
|
|
3065
|
+
const right = source[idx + 1] === 1;
|
|
3066
|
+
const upLeft = source[idx - width - 1] === 1;
|
|
3067
|
+
const upRight = source[idx - width + 1] === 1;
|
|
3068
|
+
const downLeft = source[idx + width - 1] === 1;
|
|
3069
|
+
const downRight = source[idx + width + 1] === 1;
|
|
3070
|
+
const keepsBridge = left && right || up && down || upLeft && downRight || upRight && downLeft;
|
|
3071
|
+
if (keepsBridge) {
|
|
3072
|
+
restored[idx] = 1;
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return restored;
|
|
3077
|
+
};
|
|
3078
|
+
const erodePreservingBridges = (m, radiusPx) => {
|
|
3079
|
+
const eroded = erodeDiamond(m, radiusPx);
|
|
3080
|
+
return restoreBridgePixels(m, eroded);
|
|
3040
3081
|
};
|
|
3041
3082
|
switch (op) {
|
|
3042
3083
|
case "dilate":
|
|
3043
|
-
return
|
|
3084
|
+
return dilateDisk(mask, r);
|
|
3044
3085
|
case "erode":
|
|
3045
|
-
return
|
|
3046
|
-
case "closing":
|
|
3047
|
-
|
|
3086
|
+
return erodePreservingBridges(mask, r);
|
|
3087
|
+
case "closing": {
|
|
3088
|
+
const erodeRadius = Math.max(1, Math.floor(r * 0.65));
|
|
3089
|
+
return erodePreservingBridges(dilateDisk(mask, r), erodeRadius);
|
|
3090
|
+
}
|
|
3048
3091
|
case "opening":
|
|
3049
|
-
return
|
|
3092
|
+
return dilateDisk(erodePreservingBridges(mask, r), r);
|
|
3050
3093
|
default:
|
|
3051
3094
|
return mask;
|
|
3052
3095
|
}
|
|
@@ -3109,78 +3152,6 @@ function fillHoles(mask, width, height) {
|
|
|
3109
3152
|
}
|
|
3110
3153
|
return filledMask;
|
|
3111
3154
|
}
|
|
3112
|
-
function countForeground(mask) {
|
|
3113
|
-
let c = 0;
|
|
3114
|
-
for (let i = 0; i < mask.length; i++) c += mask[i] ? 1 : 0;
|
|
3115
|
-
return c;
|
|
3116
|
-
}
|
|
3117
|
-
function isMaskConnected8(mask, width, height) {
|
|
3118
|
-
const total = countForeground(mask);
|
|
3119
|
-
if (total === 0) return true;
|
|
3120
|
-
let start = -1;
|
|
3121
|
-
for (let i = 0; i < mask.length; i++) {
|
|
3122
|
-
if (mask[i]) {
|
|
3123
|
-
start = i;
|
|
3124
|
-
break;
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
if (start === -1) return true;
|
|
3128
|
-
const visited = new Uint8Array(mask.length);
|
|
3129
|
-
const queue = [start];
|
|
3130
|
-
visited[start] = 1;
|
|
3131
|
-
let seen = 1;
|
|
3132
|
-
let head = 0;
|
|
3133
|
-
while (head < queue.length) {
|
|
3134
|
-
const idx = queue[head++];
|
|
3135
|
-
const x = idx % width;
|
|
3136
|
-
const y = (idx - x) / width;
|
|
3137
|
-
for (let dy = -1; dy <= 1; dy++) {
|
|
3138
|
-
const ny = y + dy;
|
|
3139
|
-
if (ny < 0 || ny >= height) continue;
|
|
3140
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
3141
|
-
if (dx === 0 && dy === 0) continue;
|
|
3142
|
-
const nx = x + dx;
|
|
3143
|
-
if (nx < 0 || nx >= width) continue;
|
|
3144
|
-
const nidx = ny * width + nx;
|
|
3145
|
-
if (mask[nidx] && !visited[nidx]) {
|
|
3146
|
-
visited[nidx] = 1;
|
|
3147
|
-
queue.push(nidx);
|
|
3148
|
-
seen++;
|
|
3149
|
-
}
|
|
3150
|
-
}
|
|
3151
|
-
}
|
|
3152
|
-
}
|
|
3153
|
-
return seen === total;
|
|
3154
|
-
}
|
|
3155
|
-
function findMinimalConnectRadius(mask, width, height, maxRadius) {
|
|
3156
|
-
if (maxRadius <= 0) return 0;
|
|
3157
|
-
if (isMaskConnected8(mask, width, height)) return 0;
|
|
3158
|
-
let low = 0;
|
|
3159
|
-
let high = 1;
|
|
3160
|
-
while (high <= maxRadius) {
|
|
3161
|
-
const closed = circularMorphology(mask, width, height, high, "closing");
|
|
3162
|
-
if (isMaskConnected8(closed, width, height)) break;
|
|
3163
|
-
high *= 2;
|
|
3164
|
-
}
|
|
3165
|
-
if (high > maxRadius) high = maxRadius;
|
|
3166
|
-
if (!isMaskConnected8(
|
|
3167
|
-
circularMorphology(mask, width, height, high, "closing"),
|
|
3168
|
-
width,
|
|
3169
|
-
height
|
|
3170
|
-
)) {
|
|
3171
|
-
return high;
|
|
3172
|
-
}
|
|
3173
|
-
while (low + 1 < high) {
|
|
3174
|
-
const mid = Math.floor((low + high) / 2);
|
|
3175
|
-
const closed = circularMorphology(mask, width, height, mid, "closing");
|
|
3176
|
-
if (isMaskConnected8(closed, width, height)) {
|
|
3177
|
-
high = mid;
|
|
3178
|
-
} else {
|
|
3179
|
-
low = mid;
|
|
3180
|
-
}
|
|
3181
|
-
}
|
|
3182
|
-
return high;
|
|
3183
|
-
}
|
|
3184
3155
|
function polygonSignedArea(points) {
|
|
3185
3156
|
if (points.length < 3) return 0;
|
|
3186
3157
|
let sum = 0;
|
|
@@ -3204,10 +3175,19 @@ var ImageTracer = class {
|
|
|
3204
3175
|
return pathData;
|
|
3205
3176
|
}
|
|
3206
3177
|
static async traceWithBounds(imageUrl, options = {}) {
|
|
3207
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i
|
|
3178
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
3208
3179
|
const img = await this.loadImage(imageUrl);
|
|
3209
3180
|
const width = img.width;
|
|
3210
3181
|
const height = img.height;
|
|
3182
|
+
if (width <= 0 || height <= 0) {
|
|
3183
|
+
const w = (_a = options.scaleToWidth) != null ? _a : 0;
|
|
3184
|
+
const h = (_b = options.scaleToHeight) != null ? _b : 0;
|
|
3185
|
+
return {
|
|
3186
|
+
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
3187
|
+
baseBounds: { x: 0, y: 0, width: w, height: h },
|
|
3188
|
+
bounds: { x: 0, y: 0, width: w, height: h }
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3211
3191
|
const debug = options.debug === true;
|
|
3212
3192
|
const debugLog = (message, payload) => {
|
|
3213
3193
|
if (!debug) return;
|
|
@@ -3224,96 +3204,178 @@ var ImageTracer = class {
|
|
|
3224
3204
|
if (!ctx) throw new Error("Could not get 2D context");
|
|
3225
3205
|
ctx.drawImage(img, 0, 0);
|
|
3226
3206
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
3227
|
-
const threshold = (
|
|
3228
|
-
const
|
|
3229
|
-
const
|
|
3230
|
-
const
|
|
3231
|
-
const
|
|
3232
|
-
|
|
3233
|
-
|
|
3207
|
+
const threshold = (_c = options.threshold) != null ? _c : 10;
|
|
3208
|
+
const expand = Math.max(0, Math.floor((_d = options.expand) != null ? _d : 0));
|
|
3209
|
+
const simplifyTolerance = (_e = options.simplifyTolerance) != null ? _e : 2.5;
|
|
3210
|
+
const useSmoothing = options.smoothing !== false;
|
|
3211
|
+
const componentMode = "all";
|
|
3212
|
+
const minComponentArea = 0;
|
|
3213
|
+
const maxDim = Math.max(width, height);
|
|
3214
|
+
const maskMode = "auto";
|
|
3215
|
+
const whiteThreshold = 240;
|
|
3216
|
+
const alphaOpaqueCutoff = 250;
|
|
3217
|
+
const preprocessDilateRadius = Math.max(
|
|
3218
|
+
2,
|
|
3219
|
+
Math.floor(Math.max(maxDim * 0.012, expand * 0.35))
|
|
3220
|
+
);
|
|
3221
|
+
const preprocessErodeRadius = Math.max(
|
|
3222
|
+
1,
|
|
3223
|
+
Math.floor(preprocessDilateRadius * 0.65)
|
|
3224
|
+
);
|
|
3225
|
+
const smoothDilateRadius = Math.max(
|
|
3226
|
+
1,
|
|
3227
|
+
Math.floor(preprocessDilateRadius * 0.25)
|
|
3228
|
+
);
|
|
3229
|
+
const smoothErodeRadius = Math.max(1, Math.floor(smoothDilateRadius * 0.8));
|
|
3230
|
+
const connectStartDilateRadius = Math.max(
|
|
3231
|
+
1,
|
|
3232
|
+
Math.floor(Math.max(maxDim * 6e-3, expand * 0.2))
|
|
3234
3233
|
);
|
|
3235
|
-
const
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
const
|
|
3240
|
-
const alphaAnalysis = analyzeAlpha(imageData, alphaOpaqueCutoff);
|
|
3234
|
+
const connectMaxDilateRadius = Math.max(
|
|
3235
|
+
connectStartDilateRadius,
|
|
3236
|
+
Math.floor(Math.max(maxDim * 0.2, expand * 2.5))
|
|
3237
|
+
);
|
|
3238
|
+
const connectErodeRatio = 0.65;
|
|
3241
3239
|
debugLog("traceWithBounds:start", {
|
|
3242
3240
|
width,
|
|
3243
3241
|
height,
|
|
3244
3242
|
threshold,
|
|
3245
|
-
radius,
|
|
3246
3243
|
expand,
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3244
|
+
simplifyTolerance,
|
|
3245
|
+
smoothing: useSmoothing,
|
|
3246
|
+
strategy: {
|
|
3247
|
+
maskMode,
|
|
3248
|
+
whiteThreshold,
|
|
3249
|
+
alphaOpaqueCutoff,
|
|
3250
|
+
fillHoles: true,
|
|
3251
|
+
preprocessDilateRadius,
|
|
3252
|
+
preprocessErodeRadius,
|
|
3253
|
+
smoothDilateRadius,
|
|
3254
|
+
smoothErodeRadius,
|
|
3255
|
+
connectEnabled: true,
|
|
3256
|
+
connectStartDilateRadius,
|
|
3257
|
+
connectMaxDilateRadius,
|
|
3258
|
+
connectErodeRatio
|
|
3259
|
+
}
|
|
3263
3260
|
});
|
|
3264
|
-
const padding =
|
|
3261
|
+
const padding = Math.max(
|
|
3262
|
+
preprocessDilateRadius,
|
|
3263
|
+
smoothDilateRadius,
|
|
3264
|
+
connectMaxDilateRadius,
|
|
3265
|
+
expand
|
|
3266
|
+
) + 2;
|
|
3265
3267
|
const paddedWidth = width + padding * 2;
|
|
3266
3268
|
const paddedHeight = height + padding * 2;
|
|
3269
|
+
const summarizeMaskContours = (m) => {
|
|
3270
|
+
const summary = this.summarizeAllContours(
|
|
3271
|
+
m,
|
|
3272
|
+
paddedWidth,
|
|
3273
|
+
paddedHeight,
|
|
3274
|
+
minComponentArea
|
|
3275
|
+
);
|
|
3276
|
+
return {
|
|
3277
|
+
rawContourCount: summary.rawCount,
|
|
3278
|
+
selectedContourCount: summary.selectedCount
|
|
3279
|
+
};
|
|
3280
|
+
};
|
|
3267
3281
|
let mask = createMask(imageData, {
|
|
3268
3282
|
threshold,
|
|
3269
3283
|
padding,
|
|
3270
3284
|
paddedWidth,
|
|
3271
3285
|
paddedHeight,
|
|
3272
|
-
maskMode
|
|
3273
|
-
whiteThreshold
|
|
3286
|
+
maskMode,
|
|
3287
|
+
whiteThreshold,
|
|
3274
3288
|
alphaOpaqueCutoff
|
|
3275
3289
|
});
|
|
3276
|
-
if (
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3290
|
+
if (debug) {
|
|
3291
|
+
debugLog(
|
|
3292
|
+
"traceWithBounds:mask:after-create",
|
|
3293
|
+
summarizeMaskContours(mask)
|
|
3294
|
+
);
|
|
3281
3295
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3296
|
+
mask = circularMorphology(
|
|
3297
|
+
mask,
|
|
3298
|
+
paddedWidth,
|
|
3299
|
+
paddedHeight,
|
|
3300
|
+
preprocessDilateRadius,
|
|
3301
|
+
"dilate"
|
|
3302
|
+
);
|
|
3303
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3304
|
+
mask = circularMorphology(
|
|
3305
|
+
mask,
|
|
3306
|
+
paddedWidth,
|
|
3307
|
+
paddedHeight,
|
|
3308
|
+
preprocessErodeRadius,
|
|
3309
|
+
"erode"
|
|
3310
|
+
);
|
|
3311
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3312
|
+
if (debug) {
|
|
3313
|
+
debugLog("traceWithBounds:mask:after-preprocess", {
|
|
3314
|
+
dilateRadius: preprocessDilateRadius,
|
|
3315
|
+
erodeRadius: preprocessErodeRadius,
|
|
3316
|
+
...summarizeMaskContours(mask)
|
|
3317
|
+
});
|
|
3285
3318
|
}
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3319
|
+
mask = circularMorphology(
|
|
3320
|
+
mask,
|
|
3321
|
+
paddedWidth,
|
|
3322
|
+
paddedHeight,
|
|
3323
|
+
smoothDilateRadius,
|
|
3324
|
+
"dilate"
|
|
3289
3325
|
);
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3326
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3327
|
+
mask = circularMorphology(
|
|
3328
|
+
mask,
|
|
3329
|
+
paddedWidth,
|
|
3330
|
+
paddedHeight,
|
|
3331
|
+
smoothErodeRadius,
|
|
3332
|
+
"erode"
|
|
3333
|
+
);
|
|
3334
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3335
|
+
if (debug) {
|
|
3336
|
+
debugLog("traceWithBounds:mask:after-smooth", {
|
|
3337
|
+
dilateRadius: smoothDilateRadius,
|
|
3338
|
+
erodeRadius: smoothErodeRadius,
|
|
3339
|
+
...summarizeMaskContours(mask)
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
const beforeConnectSummary = summarizeMaskContours(mask);
|
|
3343
|
+
if (beforeConnectSummary.selectedContourCount <= 1) {
|
|
3344
|
+
debugLog("traceWithBounds:mask:connect-skipped", {
|
|
3345
|
+
reason: "already-single-component",
|
|
3346
|
+
before: beforeConnectSummary
|
|
3347
|
+
});
|
|
3348
|
+
} else {
|
|
3349
|
+
const connectResult = this.findForceConnectResult(
|
|
3301
3350
|
mask,
|
|
3302
3351
|
paddedWidth,
|
|
3303
3352
|
paddedHeight,
|
|
3304
|
-
|
|
3353
|
+
minComponentArea,
|
|
3354
|
+
connectStartDilateRadius,
|
|
3355
|
+
connectMaxDilateRadius,
|
|
3356
|
+
connectErodeRatio
|
|
3305
3357
|
);
|
|
3306
|
-
if (
|
|
3307
|
-
mask
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
}
|
|
3358
|
+
if (debug) {
|
|
3359
|
+
debugLog("traceWithBounds:mask:after-connect", {
|
|
3360
|
+
before: beforeConnectSummary,
|
|
3361
|
+
appliedDilateRadius: connectResult.appliedDilateRadius,
|
|
3362
|
+
appliedErodeRadius: connectResult.appliedErodeRadius,
|
|
3363
|
+
reachedSingleComponent: connectResult.reachedSingleComponent,
|
|
3364
|
+
after: {
|
|
3365
|
+
rawContourCount: connectResult.rawContourCount,
|
|
3366
|
+
selectedContourCount: connectResult.selectedContourCount
|
|
3367
|
+
}
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
mask = connectResult.mask;
|
|
3371
|
+
}
|
|
3372
|
+
if (debug) {
|
|
3373
|
+
const afterConnectSummary = summarizeMaskContours(mask);
|
|
3374
|
+
if (afterConnectSummary.selectedContourCount > 1) {
|
|
3375
|
+
debugLog("traceWithBounds:mask:connect-warning", {
|
|
3376
|
+
reason: "still-multi-component-after-connect-search",
|
|
3377
|
+
summary: afterConnectSummary
|
|
3378
|
+
});
|
|
3317
3379
|
}
|
|
3318
3380
|
}
|
|
3319
3381
|
const baseMask = mask;
|
|
@@ -3328,8 +3390,8 @@ var ImageTracer = class {
|
|
|
3328
3390
|
minComponentArea
|
|
3329
3391
|
);
|
|
3330
3392
|
if (!baseContours.length) {
|
|
3331
|
-
const w = (
|
|
3332
|
-
const h = (
|
|
3393
|
+
const w = (_f = options.scaleToWidth) != null ? _f : width;
|
|
3394
|
+
const h = (_g = options.scaleToHeight) != null ? _g : height;
|
|
3333
3395
|
debugLog("fallback:no-base-contour", { width: w, height: h });
|
|
3334
3396
|
return {
|
|
3335
3397
|
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
@@ -3348,8 +3410,8 @@ var ImageTracer = class {
|
|
|
3348
3410
|
)
|
|
3349
3411
|
).filter((contour) => contour.length > 2);
|
|
3350
3412
|
if (!baseUnpaddedContours.length) {
|
|
3351
|
-
const w = (
|
|
3352
|
-
const h = (
|
|
3413
|
+
const w = (_h = options.scaleToWidth) != null ? _h : width;
|
|
3414
|
+
const h = (_i = options.scaleToHeight) != null ? _i : height;
|
|
3353
3415
|
debugLog("fallback:empty-base-contours", { width: w, height: h });
|
|
3354
3416
|
return {
|
|
3355
3417
|
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
@@ -3394,14 +3456,10 @@ var ImageTracer = class {
|
|
|
3394
3456
|
};
|
|
3395
3457
|
}
|
|
3396
3458
|
const expandedUnpaddedContours = expandedContours.map(
|
|
3397
|
-
(contour) =>
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
})),
|
|
3402
|
-
width,
|
|
3403
|
-
height
|
|
3404
|
-
)
|
|
3459
|
+
(contour) => contour.map((p) => ({
|
|
3460
|
+
x: p.x - padding,
|
|
3461
|
+
y: p.y - padding
|
|
3462
|
+
}))
|
|
3405
3463
|
).filter((contour) => contour.length > 2);
|
|
3406
3464
|
if (!expandedUnpaddedContours.length) {
|
|
3407
3465
|
debugLog("fallback:empty-expanded-contours", {
|
|
@@ -3434,39 +3492,31 @@ var ImageTracer = class {
|
|
|
3434
3492
|
options.scaleToHeight,
|
|
3435
3493
|
baseBounds
|
|
3436
3494
|
);
|
|
3437
|
-
baseBounds = this.boundsFromPoints(
|
|
3495
|
+
baseBounds = this.boundsFromPoints(
|
|
3496
|
+
this.flattenContours(baseScaledContours)
|
|
3497
|
+
);
|
|
3438
3498
|
}
|
|
3439
|
-
const useSmoothing = options.smoothing !== false;
|
|
3440
3499
|
debugLog("traceWithBounds:contours", {
|
|
3441
3500
|
baseContourCount: baseContoursRaw.length,
|
|
3442
3501
|
baseSelectedCount: baseContours.length,
|
|
3443
3502
|
expandedContourCount: expandedContoursRaw.length,
|
|
3444
3503
|
expandedSelectedCount: expandedContours.length,
|
|
3445
|
-
connectRadiusMax,
|
|
3446
|
-
appliedConnectRadius: rConnect,
|
|
3447
3504
|
baseBounds,
|
|
3448
3505
|
expandedBounds: globalBounds,
|
|
3449
3506
|
expandedDeltaX: globalBounds.width - baseBounds.width,
|
|
3450
3507
|
expandedDeltaY: globalBounds.height - baseBounds.height,
|
|
3508
|
+
expandedMayOverflowImageBounds: expand > 0,
|
|
3451
3509
|
useSmoothing,
|
|
3452
3510
|
componentMode
|
|
3453
3511
|
});
|
|
3454
3512
|
if (useSmoothing) {
|
|
3455
3513
|
return {
|
|
3456
|
-
pathData: this.contoursToSVGPaper(
|
|
3457
|
-
finalContours,
|
|
3458
|
-
(_n = options.simplifyTolerance) != null ? _n : 2.5
|
|
3459
|
-
),
|
|
3514
|
+
pathData: this.contoursToSVGPaper(finalContours, simplifyTolerance),
|
|
3460
3515
|
baseBounds,
|
|
3461
3516
|
bounds: globalBounds
|
|
3462
3517
|
};
|
|
3463
3518
|
} else {
|
|
3464
|
-
const simplifiedContours = finalContours.map(
|
|
3465
|
-
(points) => {
|
|
3466
|
-
var _a2;
|
|
3467
|
-
return this.douglasPeucker(points, (_a2 = options.simplifyTolerance) != null ? _a2 : 2);
|
|
3468
|
-
}
|
|
3469
|
-
).filter((points) => points.length > 2);
|
|
3519
|
+
const simplifiedContours = finalContours.map((points) => this.douglasPeucker(points, simplifyTolerance)).filter((points) => points.length > 2);
|
|
3470
3520
|
const pathData = this.contoursToSVG(simplifiedContours) || this.contoursToSVG(finalContours);
|
|
3471
3521
|
return {
|
|
3472
3522
|
pathData,
|
|
@@ -3529,39 +3579,101 @@ var ImageTracer = class {
|
|
|
3529
3579
|
}
|
|
3530
3580
|
return selected;
|
|
3531
3581
|
}
|
|
3532
|
-
static
|
|
3533
|
-
const
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3582
|
+
static summarizeAllContours(mask, width, height, minComponentArea) {
|
|
3583
|
+
const raw = this.traceAllContours(mask, width, height);
|
|
3584
|
+
const selected = this.selectContours(raw, "all", minComponentArea);
|
|
3585
|
+
return {
|
|
3586
|
+
rawCount: raw.length,
|
|
3587
|
+
selectedCount: selected.length
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
static findForceConnectResult(sourceMask, width, height, minComponentArea, startDilateRadius, maxDilateRadius, erodeRatio) {
|
|
3591
|
+
const initial = this.summarizeAllContours(
|
|
3592
|
+
sourceMask,
|
|
3593
|
+
width,
|
|
3594
|
+
height,
|
|
3595
|
+
minComponentArea
|
|
3596
|
+
);
|
|
3597
|
+
if (initial.selectedCount <= 1) {
|
|
3598
|
+
return {
|
|
3599
|
+
mask: sourceMask,
|
|
3600
|
+
appliedDilateRadius: 0,
|
|
3601
|
+
appliedErodeRadius: 0,
|
|
3602
|
+
reachedSingleComponent: true,
|
|
3603
|
+
rawContourCount: initial.rawCount,
|
|
3604
|
+
selectedContourCount: initial.selectedCount
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
const normalizedStart = Math.max(1, Math.floor(startDilateRadius));
|
|
3608
|
+
const normalizedMax = Math.max(
|
|
3609
|
+
normalizedStart,
|
|
3610
|
+
Math.floor(maxDilateRadius)
|
|
3611
|
+
);
|
|
3612
|
+
const normalizedErodeRatio = Math.max(0, erodeRatio);
|
|
3613
|
+
const evaluate = (dilateRadius) => {
|
|
3614
|
+
const erodeRadius = Math.max(
|
|
3615
|
+
1,
|
|
3616
|
+
Math.floor(dilateRadius * normalizedErodeRatio)
|
|
3617
|
+
);
|
|
3618
|
+
let mask = sourceMask;
|
|
3619
|
+
mask = circularMorphology(mask, width, height, dilateRadius, "dilate");
|
|
3620
|
+
mask = fillHoles(mask, width, height);
|
|
3621
|
+
mask = circularMorphology(mask, width, height, erodeRadius, "erode");
|
|
3622
|
+
mask = fillHoles(mask, width, height);
|
|
3623
|
+
const summary = this.summarizeAllContours(
|
|
3624
|
+
mask,
|
|
3625
|
+
width,
|
|
3626
|
+
height,
|
|
3627
|
+
minComponentArea
|
|
3628
|
+
);
|
|
3629
|
+
return {
|
|
3630
|
+
dilateRadius,
|
|
3631
|
+
erodeRadius,
|
|
3632
|
+
mask,
|
|
3633
|
+
rawCount: summary.rawCount,
|
|
3634
|
+
selectedCount: summary.selectedCount
|
|
3635
|
+
};
|
|
3636
|
+
};
|
|
3637
|
+
let low = normalizedStart - 1;
|
|
3638
|
+
let high = normalizedStart;
|
|
3639
|
+
let highResult = evaluate(high);
|
|
3640
|
+
while (high < normalizedMax && highResult.selectedCount > 1) {
|
|
3641
|
+
low = high;
|
|
3642
|
+
high = Math.min(
|
|
3643
|
+
normalizedMax,
|
|
3644
|
+
Math.max(high + 1, Math.floor(high * 1.6))
|
|
3645
|
+
);
|
|
3646
|
+
highResult = evaluate(high);
|
|
3549
3647
|
}
|
|
3550
|
-
if (
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3648
|
+
if (highResult.selectedCount > 1) {
|
|
3649
|
+
return {
|
|
3650
|
+
mask: highResult.mask,
|
|
3651
|
+
appliedDilateRadius: highResult.dilateRadius,
|
|
3652
|
+
appliedErodeRadius: highResult.erodeRadius,
|
|
3653
|
+
reachedSingleComponent: false,
|
|
3654
|
+
rawContourCount: highResult.rawCount,
|
|
3655
|
+
selectedContourCount: highResult.selectedCount
|
|
3656
|
+
};
|
|
3554
3657
|
}
|
|
3658
|
+
let best = highResult;
|
|
3555
3659
|
while (low + 1 < high) {
|
|
3556
3660
|
const mid = Math.floor((low + high) / 2);
|
|
3557
|
-
const
|
|
3558
|
-
if (
|
|
3661
|
+
const midResult = evaluate(mid);
|
|
3662
|
+
if (midResult.selectedCount <= 1) {
|
|
3663
|
+
best = midResult;
|
|
3559
3664
|
high = mid;
|
|
3560
3665
|
} else {
|
|
3561
3666
|
low = mid;
|
|
3562
3667
|
}
|
|
3563
3668
|
}
|
|
3564
|
-
return
|
|
3669
|
+
return {
|
|
3670
|
+
mask: best.mask,
|
|
3671
|
+
appliedDilateRadius: best.dilateRadius,
|
|
3672
|
+
appliedErodeRadius: best.erodeRadius,
|
|
3673
|
+
reachedSingleComponent: true,
|
|
3674
|
+
rawContourCount: best.rawCount,
|
|
3675
|
+
selectedContourCount: best.selectedCount
|
|
3676
|
+
};
|
|
3565
3677
|
}
|
|
3566
3678
|
static selectContours(contours, mode, minComponentArea) {
|
|
3567
3679
|
if (!contours.length) return [];
|
|
@@ -3603,154 +3715,6 @@ var ImageTracer = class {
|
|
|
3603
3715
|
height: maxY - minY
|
|
3604
3716
|
};
|
|
3605
3717
|
}
|
|
3606
|
-
static createMask(imageData, threshold, padding, paddedWidth, paddedHeight) {
|
|
3607
|
-
const { width, height, data } = imageData;
|
|
3608
|
-
const mask = new Uint8Array(paddedWidth * paddedHeight);
|
|
3609
|
-
let hasTransparency = false;
|
|
3610
|
-
for (let i = 3; i < data.length; i += 4) {
|
|
3611
|
-
if (data[i] < 255) {
|
|
3612
|
-
hasTransparency = true;
|
|
3613
|
-
break;
|
|
3614
|
-
}
|
|
3615
|
-
}
|
|
3616
|
-
for (let y = 0; y < height; y++) {
|
|
3617
|
-
for (let x = 0; x < width; x++) {
|
|
3618
|
-
const srcIdx = (y * width + x) * 4;
|
|
3619
|
-
const r = data[srcIdx];
|
|
3620
|
-
const g = data[srcIdx + 1];
|
|
3621
|
-
const b = data[srcIdx + 2];
|
|
3622
|
-
const a = data[srcIdx + 3];
|
|
3623
|
-
const destIdx = (y + padding) * paddedWidth + (x + padding);
|
|
3624
|
-
if (hasTransparency) {
|
|
3625
|
-
if (a > threshold) {
|
|
3626
|
-
mask[destIdx] = 1;
|
|
3627
|
-
}
|
|
3628
|
-
} else {
|
|
3629
|
-
if (!(r > 240 && g > 240 && b > 240)) {
|
|
3630
|
-
mask[destIdx] = 1;
|
|
3631
|
-
}
|
|
3632
|
-
}
|
|
3633
|
-
}
|
|
3634
|
-
}
|
|
3635
|
-
return mask;
|
|
3636
|
-
}
|
|
3637
|
-
/**
|
|
3638
|
-
* Fast circular morphology using a distance-transform inspired separable approach.
|
|
3639
|
-
* O(N * R) complexity, where R is the radius.
|
|
3640
|
-
*/
|
|
3641
|
-
static circularMorphology(mask, width, height, radius, op) {
|
|
3642
|
-
const dilate = (m, r) => {
|
|
3643
|
-
const horizontalDist = new Int32Array(width * height);
|
|
3644
|
-
for (let y = 0; y < height; y++) {
|
|
3645
|
-
let lastSolid = -r * 2;
|
|
3646
|
-
for (let x = 0; x < width; x++) {
|
|
3647
|
-
if (m[y * width + x]) lastSolid = x;
|
|
3648
|
-
horizontalDist[y * width + x] = x - lastSolid;
|
|
3649
|
-
}
|
|
3650
|
-
lastSolid = width + r * 2;
|
|
3651
|
-
for (let x = width - 1; x >= 0; x--) {
|
|
3652
|
-
if (m[y * width + x]) lastSolid = x;
|
|
3653
|
-
horizontalDist[y * width + x] = Math.min(
|
|
3654
|
-
horizontalDist[y * width + x],
|
|
3655
|
-
lastSolid - x
|
|
3656
|
-
);
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
const result = new Uint8Array(width * height);
|
|
3660
|
-
const r2 = r * r;
|
|
3661
|
-
for (let x = 0; x < width; x++) {
|
|
3662
|
-
for (let y = 0; y < height; y++) {
|
|
3663
|
-
let found = false;
|
|
3664
|
-
const minY = Math.max(0, y - r);
|
|
3665
|
-
const maxY = Math.min(height - 1, y + r);
|
|
3666
|
-
for (let dy = minY; dy <= maxY; dy++) {
|
|
3667
|
-
const dY = dy - y;
|
|
3668
|
-
const hDist = horizontalDist[dy * width + x];
|
|
3669
|
-
if (hDist * hDist + dY * dY <= r2) {
|
|
3670
|
-
found = true;
|
|
3671
|
-
break;
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
if (found) result[y * width + x] = 1;
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
return result;
|
|
3678
|
-
};
|
|
3679
|
-
const erode = (m, r) => {
|
|
3680
|
-
const inverted = new Uint8Array(m.length);
|
|
3681
|
-
for (let i = 0; i < m.length; i++) inverted[i] = m[i] ? 0 : 1;
|
|
3682
|
-
const dilatedInverted = dilate(inverted, r);
|
|
3683
|
-
const result = new Uint8Array(m.length);
|
|
3684
|
-
for (let i = 0; i < m.length; i++) result[i] = dilatedInverted[i] ? 0 : 1;
|
|
3685
|
-
return result;
|
|
3686
|
-
};
|
|
3687
|
-
switch (op) {
|
|
3688
|
-
case "dilate":
|
|
3689
|
-
return dilate(mask, radius);
|
|
3690
|
-
case "erode":
|
|
3691
|
-
return erode(mask, radius);
|
|
3692
|
-
case "closing":
|
|
3693
|
-
return erode(dilate(mask, radius), radius);
|
|
3694
|
-
case "opening":
|
|
3695
|
-
return dilate(erode(mask, radius), radius);
|
|
3696
|
-
default:
|
|
3697
|
-
return mask;
|
|
3698
|
-
}
|
|
3699
|
-
}
|
|
3700
|
-
/**
|
|
3701
|
-
* Fills internal holes in the binary mask using flood fill from edges.
|
|
3702
|
-
*/
|
|
3703
|
-
static fillHoles(mask, width, height) {
|
|
3704
|
-
const background = new Uint8Array(width * height);
|
|
3705
|
-
const queue = [];
|
|
3706
|
-
for (let x = 0; x < width; x++) {
|
|
3707
|
-
if (mask[x] === 0) {
|
|
3708
|
-
background[x] = 1;
|
|
3709
|
-
queue.push([x, 0]);
|
|
3710
|
-
}
|
|
3711
|
-
const lastRow = (height - 1) * width + x;
|
|
3712
|
-
if (mask[lastRow] === 0) {
|
|
3713
|
-
background[lastRow] = 1;
|
|
3714
|
-
queue.push([x, height - 1]);
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
for (let y = 1; y < height - 1; y++) {
|
|
3718
|
-
if (mask[y * width] === 0) {
|
|
3719
|
-
background[y * width] = 1;
|
|
3720
|
-
queue.push([0, y]);
|
|
3721
|
-
}
|
|
3722
|
-
if (mask[y * width + width - 1] === 0) {
|
|
3723
|
-
background[y * width + width - 1] = 1;
|
|
3724
|
-
queue.push([width - 1, y]);
|
|
3725
|
-
}
|
|
3726
|
-
}
|
|
3727
|
-
const dirs = [
|
|
3728
|
-
[0, 1],
|
|
3729
|
-
[0, -1],
|
|
3730
|
-
[1, 0],
|
|
3731
|
-
[-1, 0]
|
|
3732
|
-
];
|
|
3733
|
-
let head = 0;
|
|
3734
|
-
while (head < queue.length) {
|
|
3735
|
-
const [cx, cy] = queue[head++];
|
|
3736
|
-
for (const [dx, dy] of dirs) {
|
|
3737
|
-
const nx = cx + dx;
|
|
3738
|
-
const ny = cy + dy;
|
|
3739
|
-
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
3740
|
-
const nidx = ny * width + nx;
|
|
3741
|
-
if (mask[nidx] === 0 && background[nidx] === 0) {
|
|
3742
|
-
background[nidx] = 1;
|
|
3743
|
-
queue.push([nx, ny]);
|
|
3744
|
-
}
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
const filledMask = new Uint8Array(width * height);
|
|
3749
|
-
for (let i = 0; i < width * height; i++) {
|
|
3750
|
-
filledMask[i] = background[i] === 0 ? 1 : 0;
|
|
3751
|
-
}
|
|
3752
|
-
return filledMask;
|
|
3753
|
-
}
|
|
3754
3718
|
/**
|
|
3755
3719
|
* Traces all contours in the mask with optimized start-point detection
|
|
3756
3720
|
*/
|
|
@@ -4287,10 +4251,17 @@ var DielineTool = class {
|
|
|
4287
4251
|
command: "detectEdge",
|
|
4288
4252
|
title: "Detect Edge from Image",
|
|
4289
4253
|
handler: async (imageUrl, options) => {
|
|
4290
|
-
var _a;
|
|
4254
|
+
var _a, _b, _c;
|
|
4291
4255
|
try {
|
|
4292
4256
|
const detectOptions = options || {};
|
|
4293
4257
|
const debug = detectOptions.debug === true;
|
|
4258
|
+
const tracerOptions = {
|
|
4259
|
+
expand: (_a = detectOptions.expand) != null ? _a : 0,
|
|
4260
|
+
smoothing: (_b = detectOptions.smoothing) != null ? _b : true,
|
|
4261
|
+
simplifyTolerance: (_c = detectOptions.simplifyTolerance) != null ? _c : 2,
|
|
4262
|
+
threshold: detectOptions.threshold,
|
|
4263
|
+
debug
|
|
4264
|
+
};
|
|
4294
4265
|
const loadImage = (url) => {
|
|
4295
4266
|
return new Promise((resolve, reject) => {
|
|
4296
4267
|
const img2 = new Image();
|
|
@@ -4302,7 +4273,7 @@ var DielineTool = class {
|
|
|
4302
4273
|
};
|
|
4303
4274
|
const [img, traced] = await Promise.all([
|
|
4304
4275
|
loadImage(imageUrl),
|
|
4305
|
-
ImageTracer.traceWithBounds(imageUrl,
|
|
4276
|
+
ImageTracer.traceWithBounds(imageUrl, tracerOptions)
|
|
4306
4277
|
]);
|
|
4307
4278
|
const { pathData, baseBounds, bounds } = traced;
|
|
4308
4279
|
if (debug) {
|
|
@@ -4313,21 +4284,8 @@ var DielineTool = class {
|
|
|
4313
4284
|
expandedBounds: bounds,
|
|
4314
4285
|
currentDielineWidth: s.width,
|
|
4315
4286
|
currentDielineHeight: s.height,
|
|
4316
|
-
options:
|
|
4317
|
-
|
|
4318
|
-
morphologyRadius: detectOptions.morphologyRadius,
|
|
4319
|
-
connectRadiusMax: detectOptions.connectRadiusMax,
|
|
4320
|
-
smoothing: detectOptions.smoothing,
|
|
4321
|
-
simplifyTolerance: detectOptions.simplifyTolerance,
|
|
4322
|
-
threshold: detectOptions.threshold,
|
|
4323
|
-
maskMode: detectOptions.maskMode,
|
|
4324
|
-
whiteThreshold: detectOptions.whiteThreshold,
|
|
4325
|
-
alphaOpaqueCutoff: detectOptions.alphaOpaqueCutoff,
|
|
4326
|
-
noChannels: detectOptions.noChannels,
|
|
4327
|
-
componentMode: detectOptions.componentMode,
|
|
4328
|
-
minComponentArea: detectOptions.minComponentArea,
|
|
4329
|
-
forceConnected: detectOptions.forceConnected
|
|
4330
|
-
}
|
|
4287
|
+
options: tracerOptions,
|
|
4288
|
+
strategy: "single-connected-silhouette"
|
|
4331
4289
|
});
|
|
4332
4290
|
}
|
|
4333
4291
|
return {
|
|
@@ -7034,6 +6992,82 @@ var WhiteInkTool = class {
|
|
|
7034
6992
|
height
|
|
7035
6993
|
};
|
|
7036
6994
|
}
|
|
6995
|
+
getImagePlacementState(id) {
|
|
6996
|
+
const rawItems = this.getConfig("image.items", []);
|
|
6997
|
+
if (!Array.isArray(rawItems) || rawItems.length === 0) return null;
|
|
6998
|
+
const matched = (id ? rawItems.find(
|
|
6999
|
+
(item) => item && typeof item === "object" && typeof item.id === "string" && item.id === id
|
|
7000
|
+
) : void 0) || rawItems[0];
|
|
7001
|
+
if (!matched || typeof matched !== "object") return null;
|
|
7002
|
+
const sourceUrl = typeof matched.sourceUrl === "string" && matched.sourceUrl.length > 0 ? matched.sourceUrl : typeof matched.url === "string" ? matched.url : "";
|
|
7003
|
+
const committedUrl = typeof matched.committedUrl === "string" ? matched.committedUrl : "";
|
|
7004
|
+
return {
|
|
7005
|
+
id: typeof matched.id === "string" && matched.id.length > 0 ? matched.id : id || "image",
|
|
7006
|
+
sourceUrl,
|
|
7007
|
+
committedUrl,
|
|
7008
|
+
left: Number.isFinite(matched.left) ? Number(matched.left) : 0.5,
|
|
7009
|
+
top: Number.isFinite(matched.top) ? Number(matched.top) : 0.5,
|
|
7010
|
+
scale: Number.isFinite(matched.scale) ? Math.max(0.05, matched.scale) : 1,
|
|
7011
|
+
angle: Number.isFinite(matched.angle) ? matched.angle : 0
|
|
7012
|
+
};
|
|
7013
|
+
}
|
|
7014
|
+
shouldRestoreSnapshotToSource(snapshot, placement) {
|
|
7015
|
+
if (!placement.sourceUrl || !placement.committedUrl) return false;
|
|
7016
|
+
if (placement.sourceUrl === placement.committedUrl) return false;
|
|
7017
|
+
return snapshot.src === placement.committedUrl;
|
|
7018
|
+
}
|
|
7019
|
+
getCoverScale(frame, source) {
|
|
7020
|
+
const frameW = Math.max(1, frame.width);
|
|
7021
|
+
const frameH = Math.max(1, frame.height);
|
|
7022
|
+
const sourceW = Math.max(1, source.width);
|
|
7023
|
+
const sourceH = Math.max(1, source.height);
|
|
7024
|
+
return Math.max(frameW / sourceW, frameH / sourceH);
|
|
7025
|
+
}
|
|
7026
|
+
async ensureSourceSize(sourceUrl) {
|
|
7027
|
+
if (!sourceUrl) return null;
|
|
7028
|
+
const cached = this.getSourceSize(sourceUrl);
|
|
7029
|
+
if (cached) return cached;
|
|
7030
|
+
try {
|
|
7031
|
+
const image = await this.loadImageElement(sourceUrl);
|
|
7032
|
+
const size = this.getElementSize(image);
|
|
7033
|
+
if (!size) return null;
|
|
7034
|
+
this.rememberSourceSize(sourceUrl, size);
|
|
7035
|
+
return {
|
|
7036
|
+
width: size.width,
|
|
7037
|
+
height: size.height
|
|
7038
|
+
};
|
|
7039
|
+
} catch (e) {
|
|
7040
|
+
return null;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
async resolveAlignedImageSnapshot(snapshot) {
|
|
7044
|
+
const placement = this.getImagePlacementState(snapshot.id);
|
|
7045
|
+
if (!placement) return snapshot;
|
|
7046
|
+
if (!this.shouldRestoreSnapshotToSource(snapshot, placement)) {
|
|
7047
|
+
return snapshot;
|
|
7048
|
+
}
|
|
7049
|
+
const frame = this.getFrameRect();
|
|
7050
|
+
if (frame.width <= 0 || frame.height <= 0) {
|
|
7051
|
+
return snapshot;
|
|
7052
|
+
}
|
|
7053
|
+
const sourceSize = await this.ensureSourceSize(placement.sourceUrl);
|
|
7054
|
+
if (!sourceSize) return snapshot;
|
|
7055
|
+
const coverScale = this.getCoverScale(frame, sourceSize);
|
|
7056
|
+
return {
|
|
7057
|
+
...snapshot,
|
|
7058
|
+
src: placement.sourceUrl,
|
|
7059
|
+
element: void 0,
|
|
7060
|
+
left: frame.left + placement.left * frame.width,
|
|
7061
|
+
top: frame.top + placement.top * frame.height,
|
|
7062
|
+
scaleX: coverScale * placement.scale,
|
|
7063
|
+
scaleY: coverScale * placement.scale,
|
|
7064
|
+
angle: placement.angle,
|
|
7065
|
+
originX: "center",
|
|
7066
|
+
originY: "center",
|
|
7067
|
+
width: sourceSize.width,
|
|
7068
|
+
height: sourceSize.height
|
|
7069
|
+
};
|
|
7070
|
+
}
|
|
7037
7071
|
getImageElementFromObject(obj) {
|
|
7038
7072
|
if (!obj) return null;
|
|
7039
7073
|
if (typeof obj.getElement === "function") {
|
|
@@ -7406,9 +7440,11 @@ var WhiteInkTool = class {
|
|
|
7406
7440
|
let whiteSpecs = [];
|
|
7407
7441
|
let coverSpecs = [];
|
|
7408
7442
|
if (previewActive) {
|
|
7409
|
-
const
|
|
7443
|
+
const baseSnapshot = this.getImageSnapshot(this.getPrimaryImageObject());
|
|
7410
7444
|
const item = this.getEffectiveWhiteInkItem(this.resolveRenderItems());
|
|
7411
|
-
if (
|
|
7445
|
+
if (baseSnapshot && item) {
|
|
7446
|
+
const snapshot = await this.resolveAlignedImageSnapshot(baseSnapshot);
|
|
7447
|
+
if (seq !== this.renderSeq) return;
|
|
7412
7448
|
const sources = await this.resolveRenderSources(snapshot, item);
|
|
7413
7449
|
if (seq !== this.renderSeq) return;
|
|
7414
7450
|
if (sources == null ? void 0 : sources.whiteSrc) {
|