@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.mjs
CHANGED
|
@@ -2958,15 +2958,19 @@ function analyzeAlpha(imageData, alphaOpaqueCutoff) {
|
|
|
2958
2958
|
};
|
|
2959
2959
|
}
|
|
2960
2960
|
function circularMorphology(mask, width, height, radius, op) {
|
|
2961
|
-
const
|
|
2961
|
+
const r = Math.max(0, Math.floor(radius));
|
|
2962
|
+
if (r <= 0) {
|
|
2963
|
+
return mask.slice();
|
|
2964
|
+
}
|
|
2965
|
+
const dilateDisk = (m, radiusPx) => {
|
|
2962
2966
|
const horizontalDist = new Int32Array(width * height);
|
|
2963
2967
|
for (let y = 0; y < height; y++) {
|
|
2964
|
-
let lastSolid = -
|
|
2968
|
+
let lastSolid = -radiusPx * 2;
|
|
2965
2969
|
for (let x = 0; x < width; x++) {
|
|
2966
2970
|
if (m[y * width + x]) lastSolid = x;
|
|
2967
2971
|
horizontalDist[y * width + x] = x - lastSolid;
|
|
2968
2972
|
}
|
|
2969
|
-
lastSolid = width +
|
|
2973
|
+
lastSolid = width + radiusPx * 2;
|
|
2970
2974
|
for (let x = width - 1; x >= 0; x--) {
|
|
2971
2975
|
if (m[y * width + x]) lastSolid = x;
|
|
2972
2976
|
horizontalDist[y * width + x] = Math.min(
|
|
@@ -2976,12 +2980,12 @@ function circularMorphology(mask, width, height, radius, op) {
|
|
|
2976
2980
|
}
|
|
2977
2981
|
}
|
|
2978
2982
|
const result = new Uint8Array(width * height);
|
|
2979
|
-
const r2 =
|
|
2983
|
+
const r2 = radiusPx * radiusPx;
|
|
2980
2984
|
for (let x = 0; x < width; x++) {
|
|
2981
2985
|
for (let y = 0; y < height; y++) {
|
|
2982
2986
|
let found = false;
|
|
2983
|
-
const minY = Math.max(0, y -
|
|
2984
|
-
const maxY = Math.min(height - 1, y +
|
|
2987
|
+
const minY = Math.max(0, y - radiusPx);
|
|
2988
|
+
const maxY = Math.min(height - 1, y + radiusPx);
|
|
2985
2989
|
for (let dy = minY; dy <= maxY; dy++) {
|
|
2986
2990
|
const dY = dy - y;
|
|
2987
2991
|
const hDist = horizontalDist[dy * width + x];
|
|
@@ -2995,23 +2999,62 @@ function circularMorphology(mask, width, height, radius, op) {
|
|
|
2995
2999
|
}
|
|
2996
3000
|
return result;
|
|
2997
3001
|
};
|
|
2998
|
-
const
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3002
|
+
const erodeDiamond = (m, radiusPx) => {
|
|
3003
|
+
if (radiusPx <= 0) return m.slice();
|
|
3004
|
+
let current = m;
|
|
3005
|
+
for (let step = 0; step < radiusPx; step++) {
|
|
3006
|
+
const next = new Uint8Array(width * height);
|
|
3007
|
+
for (let y = 1; y < height - 1; y++) {
|
|
3008
|
+
const row = y * width;
|
|
3009
|
+
for (let x = 1; x < width - 1; x++) {
|
|
3010
|
+
const idx = row + x;
|
|
3011
|
+
if (current[idx] && current[idx - 1] && current[idx + 1] && current[idx - width] && current[idx + width]) {
|
|
3012
|
+
next[idx] = 1;
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
current = next;
|
|
3017
|
+
}
|
|
3018
|
+
return current;
|
|
3019
|
+
};
|
|
3020
|
+
const restoreBridgePixels = (source, eroded) => {
|
|
3021
|
+
const restored = eroded.slice();
|
|
3022
|
+
for (let y = 1; y < height - 1; y++) {
|
|
3023
|
+
const row = y * width;
|
|
3024
|
+
for (let x = 1; x < width - 1; x++) {
|
|
3025
|
+
const idx = row + x;
|
|
3026
|
+
if (!source[idx] || restored[idx]) continue;
|
|
3027
|
+
const up = source[idx - width] === 1;
|
|
3028
|
+
const down = source[idx + width] === 1;
|
|
3029
|
+
const left = source[idx - 1] === 1;
|
|
3030
|
+
const right = source[idx + 1] === 1;
|
|
3031
|
+
const upLeft = source[idx - width - 1] === 1;
|
|
3032
|
+
const upRight = source[idx - width + 1] === 1;
|
|
3033
|
+
const downLeft = source[idx + width - 1] === 1;
|
|
3034
|
+
const downRight = source[idx + width + 1] === 1;
|
|
3035
|
+
const keepsBridge = left && right || up && down || upLeft && downRight || upRight && downLeft;
|
|
3036
|
+
if (keepsBridge) {
|
|
3037
|
+
restored[idx] = 1;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
return restored;
|
|
3042
|
+
};
|
|
3043
|
+
const erodePreservingBridges = (m, radiusPx) => {
|
|
3044
|
+
const eroded = erodeDiamond(m, radiusPx);
|
|
3045
|
+
return restoreBridgePixels(m, eroded);
|
|
3005
3046
|
};
|
|
3006
3047
|
switch (op) {
|
|
3007
3048
|
case "dilate":
|
|
3008
|
-
return
|
|
3049
|
+
return dilateDisk(mask, r);
|
|
3009
3050
|
case "erode":
|
|
3010
|
-
return
|
|
3011
|
-
case "closing":
|
|
3012
|
-
|
|
3051
|
+
return erodePreservingBridges(mask, r);
|
|
3052
|
+
case "closing": {
|
|
3053
|
+
const erodeRadius = Math.max(1, Math.floor(r * 0.65));
|
|
3054
|
+
return erodePreservingBridges(dilateDisk(mask, r), erodeRadius);
|
|
3055
|
+
}
|
|
3013
3056
|
case "opening":
|
|
3014
|
-
return
|
|
3057
|
+
return dilateDisk(erodePreservingBridges(mask, r), r);
|
|
3015
3058
|
default:
|
|
3016
3059
|
return mask;
|
|
3017
3060
|
}
|
|
@@ -3074,78 +3117,6 @@ function fillHoles(mask, width, height) {
|
|
|
3074
3117
|
}
|
|
3075
3118
|
return filledMask;
|
|
3076
3119
|
}
|
|
3077
|
-
function countForeground(mask) {
|
|
3078
|
-
let c = 0;
|
|
3079
|
-
for (let i = 0; i < mask.length; i++) c += mask[i] ? 1 : 0;
|
|
3080
|
-
return c;
|
|
3081
|
-
}
|
|
3082
|
-
function isMaskConnected8(mask, width, height) {
|
|
3083
|
-
const total = countForeground(mask);
|
|
3084
|
-
if (total === 0) return true;
|
|
3085
|
-
let start = -1;
|
|
3086
|
-
for (let i = 0; i < mask.length; i++) {
|
|
3087
|
-
if (mask[i]) {
|
|
3088
|
-
start = i;
|
|
3089
|
-
break;
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
if (start === -1) return true;
|
|
3093
|
-
const visited = new Uint8Array(mask.length);
|
|
3094
|
-
const queue = [start];
|
|
3095
|
-
visited[start] = 1;
|
|
3096
|
-
let seen = 1;
|
|
3097
|
-
let head = 0;
|
|
3098
|
-
while (head < queue.length) {
|
|
3099
|
-
const idx = queue[head++];
|
|
3100
|
-
const x = idx % width;
|
|
3101
|
-
const y = (idx - x) / width;
|
|
3102
|
-
for (let dy = -1; dy <= 1; dy++) {
|
|
3103
|
-
const ny = y + dy;
|
|
3104
|
-
if (ny < 0 || ny >= height) continue;
|
|
3105
|
-
for (let dx = -1; dx <= 1; dx++) {
|
|
3106
|
-
if (dx === 0 && dy === 0) continue;
|
|
3107
|
-
const nx = x + dx;
|
|
3108
|
-
if (nx < 0 || nx >= width) continue;
|
|
3109
|
-
const nidx = ny * width + nx;
|
|
3110
|
-
if (mask[nidx] && !visited[nidx]) {
|
|
3111
|
-
visited[nidx] = 1;
|
|
3112
|
-
queue.push(nidx);
|
|
3113
|
-
seen++;
|
|
3114
|
-
}
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
}
|
|
3118
|
-
return seen === total;
|
|
3119
|
-
}
|
|
3120
|
-
function findMinimalConnectRadius(mask, width, height, maxRadius) {
|
|
3121
|
-
if (maxRadius <= 0) return 0;
|
|
3122
|
-
if (isMaskConnected8(mask, width, height)) return 0;
|
|
3123
|
-
let low = 0;
|
|
3124
|
-
let high = 1;
|
|
3125
|
-
while (high <= maxRadius) {
|
|
3126
|
-
const closed = circularMorphology(mask, width, height, high, "closing");
|
|
3127
|
-
if (isMaskConnected8(closed, width, height)) break;
|
|
3128
|
-
high *= 2;
|
|
3129
|
-
}
|
|
3130
|
-
if (high > maxRadius) high = maxRadius;
|
|
3131
|
-
if (!isMaskConnected8(
|
|
3132
|
-
circularMorphology(mask, width, height, high, "closing"),
|
|
3133
|
-
width,
|
|
3134
|
-
height
|
|
3135
|
-
)) {
|
|
3136
|
-
return high;
|
|
3137
|
-
}
|
|
3138
|
-
while (low + 1 < high) {
|
|
3139
|
-
const mid = Math.floor((low + high) / 2);
|
|
3140
|
-
const closed = circularMorphology(mask, width, height, mid, "closing");
|
|
3141
|
-
if (isMaskConnected8(closed, width, height)) {
|
|
3142
|
-
high = mid;
|
|
3143
|
-
} else {
|
|
3144
|
-
low = mid;
|
|
3145
|
-
}
|
|
3146
|
-
}
|
|
3147
|
-
return high;
|
|
3148
|
-
}
|
|
3149
3120
|
function polygonSignedArea(points) {
|
|
3150
3121
|
if (points.length < 3) return 0;
|
|
3151
3122
|
let sum = 0;
|
|
@@ -3169,10 +3140,19 @@ var ImageTracer = class {
|
|
|
3169
3140
|
return pathData;
|
|
3170
3141
|
}
|
|
3171
3142
|
static async traceWithBounds(imageUrl, options = {}) {
|
|
3172
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i
|
|
3143
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
3173
3144
|
const img = await this.loadImage(imageUrl);
|
|
3174
3145
|
const width = img.width;
|
|
3175
3146
|
const height = img.height;
|
|
3147
|
+
if (width <= 0 || height <= 0) {
|
|
3148
|
+
const w = (_a = options.scaleToWidth) != null ? _a : 0;
|
|
3149
|
+
const h = (_b = options.scaleToHeight) != null ? _b : 0;
|
|
3150
|
+
return {
|
|
3151
|
+
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
3152
|
+
baseBounds: { x: 0, y: 0, width: w, height: h },
|
|
3153
|
+
bounds: { x: 0, y: 0, width: w, height: h }
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3176
3156
|
const debug = options.debug === true;
|
|
3177
3157
|
const debugLog = (message, payload) => {
|
|
3178
3158
|
if (!debug) return;
|
|
@@ -3189,96 +3169,178 @@ var ImageTracer = class {
|
|
|
3189
3169
|
if (!ctx) throw new Error("Could not get 2D context");
|
|
3190
3170
|
ctx.drawImage(img, 0, 0);
|
|
3191
3171
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
3192
|
-
const threshold = (
|
|
3193
|
-
const
|
|
3194
|
-
const
|
|
3195
|
-
const
|
|
3196
|
-
const
|
|
3197
|
-
|
|
3198
|
-
|
|
3172
|
+
const threshold = (_c = options.threshold) != null ? _c : 10;
|
|
3173
|
+
const expand = Math.max(0, Math.floor((_d = options.expand) != null ? _d : 0));
|
|
3174
|
+
const simplifyTolerance = (_e = options.simplifyTolerance) != null ? _e : 2.5;
|
|
3175
|
+
const useSmoothing = options.smoothing !== false;
|
|
3176
|
+
const componentMode = "all";
|
|
3177
|
+
const minComponentArea = 0;
|
|
3178
|
+
const maxDim = Math.max(width, height);
|
|
3179
|
+
const maskMode = "auto";
|
|
3180
|
+
const whiteThreshold = 240;
|
|
3181
|
+
const alphaOpaqueCutoff = 250;
|
|
3182
|
+
const preprocessDilateRadius = Math.max(
|
|
3183
|
+
2,
|
|
3184
|
+
Math.floor(Math.max(maxDim * 0.012, expand * 0.35))
|
|
3185
|
+
);
|
|
3186
|
+
const preprocessErodeRadius = Math.max(
|
|
3187
|
+
1,
|
|
3188
|
+
Math.floor(preprocessDilateRadius * 0.65)
|
|
3189
|
+
);
|
|
3190
|
+
const smoothDilateRadius = Math.max(
|
|
3191
|
+
1,
|
|
3192
|
+
Math.floor(preprocessDilateRadius * 0.25)
|
|
3193
|
+
);
|
|
3194
|
+
const smoothErodeRadius = Math.max(1, Math.floor(smoothDilateRadius * 0.8));
|
|
3195
|
+
const connectStartDilateRadius = Math.max(
|
|
3196
|
+
1,
|
|
3197
|
+
Math.floor(Math.max(maxDim * 6e-3, expand * 0.2))
|
|
3199
3198
|
);
|
|
3200
|
-
const
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
const
|
|
3205
|
-
const alphaAnalysis = analyzeAlpha(imageData, alphaOpaqueCutoff);
|
|
3199
|
+
const connectMaxDilateRadius = Math.max(
|
|
3200
|
+
connectStartDilateRadius,
|
|
3201
|
+
Math.floor(Math.max(maxDim * 0.2, expand * 2.5))
|
|
3202
|
+
);
|
|
3203
|
+
const connectErodeRatio = 0.65;
|
|
3206
3204
|
debugLog("traceWithBounds:start", {
|
|
3207
3205
|
width,
|
|
3208
3206
|
height,
|
|
3209
3207
|
threshold,
|
|
3210
|
-
radius,
|
|
3211
3208
|
expand,
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3209
|
+
simplifyTolerance,
|
|
3210
|
+
smoothing: useSmoothing,
|
|
3211
|
+
strategy: {
|
|
3212
|
+
maskMode,
|
|
3213
|
+
whiteThreshold,
|
|
3214
|
+
alphaOpaqueCutoff,
|
|
3215
|
+
fillHoles: true,
|
|
3216
|
+
preprocessDilateRadius,
|
|
3217
|
+
preprocessErodeRadius,
|
|
3218
|
+
smoothDilateRadius,
|
|
3219
|
+
smoothErodeRadius,
|
|
3220
|
+
connectEnabled: true,
|
|
3221
|
+
connectStartDilateRadius,
|
|
3222
|
+
connectMaxDilateRadius,
|
|
3223
|
+
connectErodeRatio
|
|
3224
|
+
}
|
|
3228
3225
|
});
|
|
3229
|
-
const padding =
|
|
3226
|
+
const padding = Math.max(
|
|
3227
|
+
preprocessDilateRadius,
|
|
3228
|
+
smoothDilateRadius,
|
|
3229
|
+
connectMaxDilateRadius,
|
|
3230
|
+
expand
|
|
3231
|
+
) + 2;
|
|
3230
3232
|
const paddedWidth = width + padding * 2;
|
|
3231
3233
|
const paddedHeight = height + padding * 2;
|
|
3234
|
+
const summarizeMaskContours = (m) => {
|
|
3235
|
+
const summary = this.summarizeAllContours(
|
|
3236
|
+
m,
|
|
3237
|
+
paddedWidth,
|
|
3238
|
+
paddedHeight,
|
|
3239
|
+
minComponentArea
|
|
3240
|
+
);
|
|
3241
|
+
return {
|
|
3242
|
+
rawContourCount: summary.rawCount,
|
|
3243
|
+
selectedContourCount: summary.selectedCount
|
|
3244
|
+
};
|
|
3245
|
+
};
|
|
3232
3246
|
let mask = createMask(imageData, {
|
|
3233
3247
|
threshold,
|
|
3234
3248
|
padding,
|
|
3235
3249
|
paddedWidth,
|
|
3236
3250
|
paddedHeight,
|
|
3237
|
-
maskMode
|
|
3238
|
-
whiteThreshold
|
|
3251
|
+
maskMode,
|
|
3252
|
+
whiteThreshold,
|
|
3239
3253
|
alphaOpaqueCutoff
|
|
3240
3254
|
});
|
|
3241
|
-
if (
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3255
|
+
if (debug) {
|
|
3256
|
+
debugLog(
|
|
3257
|
+
"traceWithBounds:mask:after-create",
|
|
3258
|
+
summarizeMaskContours(mask)
|
|
3259
|
+
);
|
|
3246
3260
|
}
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3261
|
+
mask = circularMorphology(
|
|
3262
|
+
mask,
|
|
3263
|
+
paddedWidth,
|
|
3264
|
+
paddedHeight,
|
|
3265
|
+
preprocessDilateRadius,
|
|
3266
|
+
"dilate"
|
|
3267
|
+
);
|
|
3268
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3269
|
+
mask = circularMorphology(
|
|
3270
|
+
mask,
|
|
3271
|
+
paddedWidth,
|
|
3272
|
+
paddedHeight,
|
|
3273
|
+
preprocessErodeRadius,
|
|
3274
|
+
"erode"
|
|
3275
|
+
);
|
|
3276
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3277
|
+
if (debug) {
|
|
3278
|
+
debugLog("traceWithBounds:mask:after-preprocess", {
|
|
3279
|
+
dilateRadius: preprocessDilateRadius,
|
|
3280
|
+
erodeRadius: preprocessErodeRadius,
|
|
3281
|
+
...summarizeMaskContours(mask)
|
|
3282
|
+
});
|
|
3250
3283
|
}
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3284
|
+
mask = circularMorphology(
|
|
3285
|
+
mask,
|
|
3286
|
+
paddedWidth,
|
|
3287
|
+
paddedHeight,
|
|
3288
|
+
smoothDilateRadius,
|
|
3289
|
+
"dilate"
|
|
3254
3290
|
);
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3291
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3292
|
+
mask = circularMorphology(
|
|
3293
|
+
mask,
|
|
3294
|
+
paddedWidth,
|
|
3295
|
+
paddedHeight,
|
|
3296
|
+
smoothErodeRadius,
|
|
3297
|
+
"erode"
|
|
3298
|
+
);
|
|
3299
|
+
mask = fillHoles(mask, paddedWidth, paddedHeight);
|
|
3300
|
+
if (debug) {
|
|
3301
|
+
debugLog("traceWithBounds:mask:after-smooth", {
|
|
3302
|
+
dilateRadius: smoothDilateRadius,
|
|
3303
|
+
erodeRadius: smoothErodeRadius,
|
|
3304
|
+
...summarizeMaskContours(mask)
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
const beforeConnectSummary = summarizeMaskContours(mask);
|
|
3308
|
+
if (beforeConnectSummary.selectedContourCount <= 1) {
|
|
3309
|
+
debugLog("traceWithBounds:mask:connect-skipped", {
|
|
3310
|
+
reason: "already-single-component",
|
|
3311
|
+
before: beforeConnectSummary
|
|
3312
|
+
});
|
|
3313
|
+
} else {
|
|
3314
|
+
const connectResult = this.findForceConnectResult(
|
|
3266
3315
|
mask,
|
|
3267
3316
|
paddedWidth,
|
|
3268
3317
|
paddedHeight,
|
|
3269
|
-
|
|
3318
|
+
minComponentArea,
|
|
3319
|
+
connectStartDilateRadius,
|
|
3320
|
+
connectMaxDilateRadius,
|
|
3321
|
+
connectErodeRatio
|
|
3270
3322
|
);
|
|
3271
|
-
if (
|
|
3272
|
-
mask
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3323
|
+
if (debug) {
|
|
3324
|
+
debugLog("traceWithBounds:mask:after-connect", {
|
|
3325
|
+
before: beforeConnectSummary,
|
|
3326
|
+
appliedDilateRadius: connectResult.appliedDilateRadius,
|
|
3327
|
+
appliedErodeRadius: connectResult.appliedErodeRadius,
|
|
3328
|
+
reachedSingleComponent: connectResult.reachedSingleComponent,
|
|
3329
|
+
after: {
|
|
3330
|
+
rawContourCount: connectResult.rawContourCount,
|
|
3331
|
+
selectedContourCount: connectResult.selectedContourCount
|
|
3332
|
+
}
|
|
3333
|
+
});
|
|
3334
|
+
}
|
|
3335
|
+
mask = connectResult.mask;
|
|
3336
|
+
}
|
|
3337
|
+
if (debug) {
|
|
3338
|
+
const afterConnectSummary = summarizeMaskContours(mask);
|
|
3339
|
+
if (afterConnectSummary.selectedContourCount > 1) {
|
|
3340
|
+
debugLog("traceWithBounds:mask:connect-warning", {
|
|
3341
|
+
reason: "still-multi-component-after-connect-search",
|
|
3342
|
+
summary: afterConnectSummary
|
|
3343
|
+
});
|
|
3282
3344
|
}
|
|
3283
3345
|
}
|
|
3284
3346
|
const baseMask = mask;
|
|
@@ -3293,8 +3355,8 @@ var ImageTracer = class {
|
|
|
3293
3355
|
minComponentArea
|
|
3294
3356
|
);
|
|
3295
3357
|
if (!baseContours.length) {
|
|
3296
|
-
const w = (
|
|
3297
|
-
const h = (
|
|
3358
|
+
const w = (_f = options.scaleToWidth) != null ? _f : width;
|
|
3359
|
+
const h = (_g = options.scaleToHeight) != null ? _g : height;
|
|
3298
3360
|
debugLog("fallback:no-base-contour", { width: w, height: h });
|
|
3299
3361
|
return {
|
|
3300
3362
|
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
@@ -3313,8 +3375,8 @@ var ImageTracer = class {
|
|
|
3313
3375
|
)
|
|
3314
3376
|
).filter((contour) => contour.length > 2);
|
|
3315
3377
|
if (!baseUnpaddedContours.length) {
|
|
3316
|
-
const w = (
|
|
3317
|
-
const h = (
|
|
3378
|
+
const w = (_h = options.scaleToWidth) != null ? _h : width;
|
|
3379
|
+
const h = (_i = options.scaleToHeight) != null ? _i : height;
|
|
3318
3380
|
debugLog("fallback:empty-base-contours", { width: w, height: h });
|
|
3319
3381
|
return {
|
|
3320
3382
|
pathData: `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`,
|
|
@@ -3359,14 +3421,10 @@ var ImageTracer = class {
|
|
|
3359
3421
|
};
|
|
3360
3422
|
}
|
|
3361
3423
|
const expandedUnpaddedContours = expandedContours.map(
|
|
3362
|
-
(contour) =>
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
})),
|
|
3367
|
-
width,
|
|
3368
|
-
height
|
|
3369
|
-
)
|
|
3424
|
+
(contour) => contour.map((p) => ({
|
|
3425
|
+
x: p.x - padding,
|
|
3426
|
+
y: p.y - padding
|
|
3427
|
+
}))
|
|
3370
3428
|
).filter((contour) => contour.length > 2);
|
|
3371
3429
|
if (!expandedUnpaddedContours.length) {
|
|
3372
3430
|
debugLog("fallback:empty-expanded-contours", {
|
|
@@ -3399,39 +3457,31 @@ var ImageTracer = class {
|
|
|
3399
3457
|
options.scaleToHeight,
|
|
3400
3458
|
baseBounds
|
|
3401
3459
|
);
|
|
3402
|
-
baseBounds = this.boundsFromPoints(
|
|
3460
|
+
baseBounds = this.boundsFromPoints(
|
|
3461
|
+
this.flattenContours(baseScaledContours)
|
|
3462
|
+
);
|
|
3403
3463
|
}
|
|
3404
|
-
const useSmoothing = options.smoothing !== false;
|
|
3405
3464
|
debugLog("traceWithBounds:contours", {
|
|
3406
3465
|
baseContourCount: baseContoursRaw.length,
|
|
3407
3466
|
baseSelectedCount: baseContours.length,
|
|
3408
3467
|
expandedContourCount: expandedContoursRaw.length,
|
|
3409
3468
|
expandedSelectedCount: expandedContours.length,
|
|
3410
|
-
connectRadiusMax,
|
|
3411
|
-
appliedConnectRadius: rConnect,
|
|
3412
3469
|
baseBounds,
|
|
3413
3470
|
expandedBounds: globalBounds,
|
|
3414
3471
|
expandedDeltaX: globalBounds.width - baseBounds.width,
|
|
3415
3472
|
expandedDeltaY: globalBounds.height - baseBounds.height,
|
|
3473
|
+
expandedMayOverflowImageBounds: expand > 0,
|
|
3416
3474
|
useSmoothing,
|
|
3417
3475
|
componentMode
|
|
3418
3476
|
});
|
|
3419
3477
|
if (useSmoothing) {
|
|
3420
3478
|
return {
|
|
3421
|
-
pathData: this.contoursToSVGPaper(
|
|
3422
|
-
finalContours,
|
|
3423
|
-
(_n = options.simplifyTolerance) != null ? _n : 2.5
|
|
3424
|
-
),
|
|
3479
|
+
pathData: this.contoursToSVGPaper(finalContours, simplifyTolerance),
|
|
3425
3480
|
baseBounds,
|
|
3426
3481
|
bounds: globalBounds
|
|
3427
3482
|
};
|
|
3428
3483
|
} else {
|
|
3429
|
-
const simplifiedContours = finalContours.map(
|
|
3430
|
-
(points) => {
|
|
3431
|
-
var _a2;
|
|
3432
|
-
return this.douglasPeucker(points, (_a2 = options.simplifyTolerance) != null ? _a2 : 2);
|
|
3433
|
-
}
|
|
3434
|
-
).filter((points) => points.length > 2);
|
|
3484
|
+
const simplifiedContours = finalContours.map((points) => this.douglasPeucker(points, simplifyTolerance)).filter((points) => points.length > 2);
|
|
3435
3485
|
const pathData = this.contoursToSVG(simplifiedContours) || this.contoursToSVG(finalContours);
|
|
3436
3486
|
return {
|
|
3437
3487
|
pathData,
|
|
@@ -3494,39 +3544,101 @@ var ImageTracer = class {
|
|
|
3494
3544
|
}
|
|
3495
3545
|
return selected;
|
|
3496
3546
|
}
|
|
3497
|
-
static
|
|
3498
|
-
const
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3547
|
+
static summarizeAllContours(mask, width, height, minComponentArea) {
|
|
3548
|
+
const raw = this.traceAllContours(mask, width, height);
|
|
3549
|
+
const selected = this.selectContours(raw, "all", minComponentArea);
|
|
3550
|
+
return {
|
|
3551
|
+
rawCount: raw.length,
|
|
3552
|
+
selectedCount: selected.length
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
static findForceConnectResult(sourceMask, width, height, minComponentArea, startDilateRadius, maxDilateRadius, erodeRatio) {
|
|
3556
|
+
const initial = this.summarizeAllContours(
|
|
3557
|
+
sourceMask,
|
|
3558
|
+
width,
|
|
3559
|
+
height,
|
|
3560
|
+
minComponentArea
|
|
3561
|
+
);
|
|
3562
|
+
if (initial.selectedCount <= 1) {
|
|
3563
|
+
return {
|
|
3564
|
+
mask: sourceMask,
|
|
3565
|
+
appliedDilateRadius: 0,
|
|
3566
|
+
appliedErodeRadius: 0,
|
|
3567
|
+
reachedSingleComponent: true,
|
|
3568
|
+
rawContourCount: initial.rawCount,
|
|
3569
|
+
selectedContourCount: initial.selectedCount
|
|
3570
|
+
};
|
|
3571
|
+
}
|
|
3572
|
+
const normalizedStart = Math.max(1, Math.floor(startDilateRadius));
|
|
3573
|
+
const normalizedMax = Math.max(
|
|
3574
|
+
normalizedStart,
|
|
3575
|
+
Math.floor(maxDilateRadius)
|
|
3576
|
+
);
|
|
3577
|
+
const normalizedErodeRatio = Math.max(0, erodeRatio);
|
|
3578
|
+
const evaluate = (dilateRadius) => {
|
|
3579
|
+
const erodeRadius = Math.max(
|
|
3580
|
+
1,
|
|
3581
|
+
Math.floor(dilateRadius * normalizedErodeRatio)
|
|
3582
|
+
);
|
|
3583
|
+
let mask = sourceMask;
|
|
3584
|
+
mask = circularMorphology(mask, width, height, dilateRadius, "dilate");
|
|
3585
|
+
mask = fillHoles(mask, width, height);
|
|
3586
|
+
mask = circularMorphology(mask, width, height, erodeRadius, "erode");
|
|
3587
|
+
mask = fillHoles(mask, width, height);
|
|
3588
|
+
const summary = this.summarizeAllContours(
|
|
3589
|
+
mask,
|
|
3590
|
+
width,
|
|
3591
|
+
height,
|
|
3592
|
+
minComponentArea
|
|
3593
|
+
);
|
|
3594
|
+
return {
|
|
3595
|
+
dilateRadius,
|
|
3596
|
+
erodeRadius,
|
|
3597
|
+
mask,
|
|
3598
|
+
rawCount: summary.rawCount,
|
|
3599
|
+
selectedCount: summary.selectedCount
|
|
3600
|
+
};
|
|
3601
|
+
};
|
|
3602
|
+
let low = normalizedStart - 1;
|
|
3603
|
+
let high = normalizedStart;
|
|
3604
|
+
let highResult = evaluate(high);
|
|
3605
|
+
while (high < normalizedMax && highResult.selectedCount > 1) {
|
|
3606
|
+
low = high;
|
|
3607
|
+
high = Math.min(
|
|
3608
|
+
normalizedMax,
|
|
3609
|
+
Math.max(high + 1, Math.floor(high * 1.6))
|
|
3610
|
+
);
|
|
3611
|
+
highResult = evaluate(high);
|
|
3514
3612
|
}
|
|
3515
|
-
if (
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3613
|
+
if (highResult.selectedCount > 1) {
|
|
3614
|
+
return {
|
|
3615
|
+
mask: highResult.mask,
|
|
3616
|
+
appliedDilateRadius: highResult.dilateRadius,
|
|
3617
|
+
appliedErodeRadius: highResult.erodeRadius,
|
|
3618
|
+
reachedSingleComponent: false,
|
|
3619
|
+
rawContourCount: highResult.rawCount,
|
|
3620
|
+
selectedContourCount: highResult.selectedCount
|
|
3621
|
+
};
|
|
3519
3622
|
}
|
|
3623
|
+
let best = highResult;
|
|
3520
3624
|
while (low + 1 < high) {
|
|
3521
3625
|
const mid = Math.floor((low + high) / 2);
|
|
3522
|
-
const
|
|
3523
|
-
if (
|
|
3626
|
+
const midResult = evaluate(mid);
|
|
3627
|
+
if (midResult.selectedCount <= 1) {
|
|
3628
|
+
best = midResult;
|
|
3524
3629
|
high = mid;
|
|
3525
3630
|
} else {
|
|
3526
3631
|
low = mid;
|
|
3527
3632
|
}
|
|
3528
3633
|
}
|
|
3529
|
-
return
|
|
3634
|
+
return {
|
|
3635
|
+
mask: best.mask,
|
|
3636
|
+
appliedDilateRadius: best.dilateRadius,
|
|
3637
|
+
appliedErodeRadius: best.erodeRadius,
|
|
3638
|
+
reachedSingleComponent: true,
|
|
3639
|
+
rawContourCount: best.rawCount,
|
|
3640
|
+
selectedContourCount: best.selectedCount
|
|
3641
|
+
};
|
|
3530
3642
|
}
|
|
3531
3643
|
static selectContours(contours, mode, minComponentArea) {
|
|
3532
3644
|
if (!contours.length) return [];
|
|
@@ -3568,154 +3680,6 @@ var ImageTracer = class {
|
|
|
3568
3680
|
height: maxY - minY
|
|
3569
3681
|
};
|
|
3570
3682
|
}
|
|
3571
|
-
static createMask(imageData, threshold, padding, paddedWidth, paddedHeight) {
|
|
3572
|
-
const { width, height, data } = imageData;
|
|
3573
|
-
const mask = new Uint8Array(paddedWidth * paddedHeight);
|
|
3574
|
-
let hasTransparency = false;
|
|
3575
|
-
for (let i = 3; i < data.length; i += 4) {
|
|
3576
|
-
if (data[i] < 255) {
|
|
3577
|
-
hasTransparency = true;
|
|
3578
|
-
break;
|
|
3579
|
-
}
|
|
3580
|
-
}
|
|
3581
|
-
for (let y = 0; y < height; y++) {
|
|
3582
|
-
for (let x = 0; x < width; x++) {
|
|
3583
|
-
const srcIdx = (y * width + x) * 4;
|
|
3584
|
-
const r = data[srcIdx];
|
|
3585
|
-
const g = data[srcIdx + 1];
|
|
3586
|
-
const b = data[srcIdx + 2];
|
|
3587
|
-
const a = data[srcIdx + 3];
|
|
3588
|
-
const destIdx = (y + padding) * paddedWidth + (x + padding);
|
|
3589
|
-
if (hasTransparency) {
|
|
3590
|
-
if (a > threshold) {
|
|
3591
|
-
mask[destIdx] = 1;
|
|
3592
|
-
}
|
|
3593
|
-
} else {
|
|
3594
|
-
if (!(r > 240 && g > 240 && b > 240)) {
|
|
3595
|
-
mask[destIdx] = 1;
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3600
|
-
return mask;
|
|
3601
|
-
}
|
|
3602
|
-
/**
|
|
3603
|
-
* Fast circular morphology using a distance-transform inspired separable approach.
|
|
3604
|
-
* O(N * R) complexity, where R is the radius.
|
|
3605
|
-
*/
|
|
3606
|
-
static circularMorphology(mask, width, height, radius, op) {
|
|
3607
|
-
const dilate = (m, r) => {
|
|
3608
|
-
const horizontalDist = new Int32Array(width * height);
|
|
3609
|
-
for (let y = 0; y < height; y++) {
|
|
3610
|
-
let lastSolid = -r * 2;
|
|
3611
|
-
for (let x = 0; x < width; x++) {
|
|
3612
|
-
if (m[y * width + x]) lastSolid = x;
|
|
3613
|
-
horizontalDist[y * width + x] = x - lastSolid;
|
|
3614
|
-
}
|
|
3615
|
-
lastSolid = width + r * 2;
|
|
3616
|
-
for (let x = width - 1; x >= 0; x--) {
|
|
3617
|
-
if (m[y * width + x]) lastSolid = x;
|
|
3618
|
-
horizontalDist[y * width + x] = Math.min(
|
|
3619
|
-
horizontalDist[y * width + x],
|
|
3620
|
-
lastSolid - x
|
|
3621
|
-
);
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
const result = new Uint8Array(width * height);
|
|
3625
|
-
const r2 = r * r;
|
|
3626
|
-
for (let x = 0; x < width; x++) {
|
|
3627
|
-
for (let y = 0; y < height; y++) {
|
|
3628
|
-
let found = false;
|
|
3629
|
-
const minY = Math.max(0, y - r);
|
|
3630
|
-
const maxY = Math.min(height - 1, y + r);
|
|
3631
|
-
for (let dy = minY; dy <= maxY; dy++) {
|
|
3632
|
-
const dY = dy - y;
|
|
3633
|
-
const hDist = horizontalDist[dy * width + x];
|
|
3634
|
-
if (hDist * hDist + dY * dY <= r2) {
|
|
3635
|
-
found = true;
|
|
3636
|
-
break;
|
|
3637
|
-
}
|
|
3638
|
-
}
|
|
3639
|
-
if (found) result[y * width + x] = 1;
|
|
3640
|
-
}
|
|
3641
|
-
}
|
|
3642
|
-
return result;
|
|
3643
|
-
};
|
|
3644
|
-
const erode = (m, r) => {
|
|
3645
|
-
const inverted = new Uint8Array(m.length);
|
|
3646
|
-
for (let i = 0; i < m.length; i++) inverted[i] = m[i] ? 0 : 1;
|
|
3647
|
-
const dilatedInverted = dilate(inverted, r);
|
|
3648
|
-
const result = new Uint8Array(m.length);
|
|
3649
|
-
for (let i = 0; i < m.length; i++) result[i] = dilatedInverted[i] ? 0 : 1;
|
|
3650
|
-
return result;
|
|
3651
|
-
};
|
|
3652
|
-
switch (op) {
|
|
3653
|
-
case "dilate":
|
|
3654
|
-
return dilate(mask, radius);
|
|
3655
|
-
case "erode":
|
|
3656
|
-
return erode(mask, radius);
|
|
3657
|
-
case "closing":
|
|
3658
|
-
return erode(dilate(mask, radius), radius);
|
|
3659
|
-
case "opening":
|
|
3660
|
-
return dilate(erode(mask, radius), radius);
|
|
3661
|
-
default:
|
|
3662
|
-
return mask;
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
/**
|
|
3666
|
-
* Fills internal holes in the binary mask using flood fill from edges.
|
|
3667
|
-
*/
|
|
3668
|
-
static fillHoles(mask, width, height) {
|
|
3669
|
-
const background = new Uint8Array(width * height);
|
|
3670
|
-
const queue = [];
|
|
3671
|
-
for (let x = 0; x < width; x++) {
|
|
3672
|
-
if (mask[x] === 0) {
|
|
3673
|
-
background[x] = 1;
|
|
3674
|
-
queue.push([x, 0]);
|
|
3675
|
-
}
|
|
3676
|
-
const lastRow = (height - 1) * width + x;
|
|
3677
|
-
if (mask[lastRow] === 0) {
|
|
3678
|
-
background[lastRow] = 1;
|
|
3679
|
-
queue.push([x, height - 1]);
|
|
3680
|
-
}
|
|
3681
|
-
}
|
|
3682
|
-
for (let y = 1; y < height - 1; y++) {
|
|
3683
|
-
if (mask[y * width] === 0) {
|
|
3684
|
-
background[y * width] = 1;
|
|
3685
|
-
queue.push([0, y]);
|
|
3686
|
-
}
|
|
3687
|
-
if (mask[y * width + width - 1] === 0) {
|
|
3688
|
-
background[y * width + width - 1] = 1;
|
|
3689
|
-
queue.push([width - 1, y]);
|
|
3690
|
-
}
|
|
3691
|
-
}
|
|
3692
|
-
const dirs = [
|
|
3693
|
-
[0, 1],
|
|
3694
|
-
[0, -1],
|
|
3695
|
-
[1, 0],
|
|
3696
|
-
[-1, 0]
|
|
3697
|
-
];
|
|
3698
|
-
let head = 0;
|
|
3699
|
-
while (head < queue.length) {
|
|
3700
|
-
const [cx, cy] = queue[head++];
|
|
3701
|
-
for (const [dx, dy] of dirs) {
|
|
3702
|
-
const nx = cx + dx;
|
|
3703
|
-
const ny = cy + dy;
|
|
3704
|
-
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
3705
|
-
const nidx = ny * width + nx;
|
|
3706
|
-
if (mask[nidx] === 0 && background[nidx] === 0) {
|
|
3707
|
-
background[nidx] = 1;
|
|
3708
|
-
queue.push([nx, ny]);
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
const filledMask = new Uint8Array(width * height);
|
|
3714
|
-
for (let i = 0; i < width * height; i++) {
|
|
3715
|
-
filledMask[i] = background[i] === 0 ? 1 : 0;
|
|
3716
|
-
}
|
|
3717
|
-
return filledMask;
|
|
3718
|
-
}
|
|
3719
3683
|
/**
|
|
3720
3684
|
* Traces all contours in the mask with optimized start-point detection
|
|
3721
3685
|
*/
|
|
@@ -4252,10 +4216,17 @@ var DielineTool = class {
|
|
|
4252
4216
|
command: "detectEdge",
|
|
4253
4217
|
title: "Detect Edge from Image",
|
|
4254
4218
|
handler: async (imageUrl, options) => {
|
|
4255
|
-
var _a;
|
|
4219
|
+
var _a, _b, _c;
|
|
4256
4220
|
try {
|
|
4257
4221
|
const detectOptions = options || {};
|
|
4258
4222
|
const debug = detectOptions.debug === true;
|
|
4223
|
+
const tracerOptions = {
|
|
4224
|
+
expand: (_a = detectOptions.expand) != null ? _a : 0,
|
|
4225
|
+
smoothing: (_b = detectOptions.smoothing) != null ? _b : true,
|
|
4226
|
+
simplifyTolerance: (_c = detectOptions.simplifyTolerance) != null ? _c : 2,
|
|
4227
|
+
threshold: detectOptions.threshold,
|
|
4228
|
+
debug
|
|
4229
|
+
};
|
|
4259
4230
|
const loadImage = (url) => {
|
|
4260
4231
|
return new Promise((resolve, reject) => {
|
|
4261
4232
|
const img2 = new Image();
|
|
@@ -4267,7 +4238,7 @@ var DielineTool = class {
|
|
|
4267
4238
|
};
|
|
4268
4239
|
const [img, traced] = await Promise.all([
|
|
4269
4240
|
loadImage(imageUrl),
|
|
4270
|
-
ImageTracer.traceWithBounds(imageUrl,
|
|
4241
|
+
ImageTracer.traceWithBounds(imageUrl, tracerOptions)
|
|
4271
4242
|
]);
|
|
4272
4243
|
const { pathData, baseBounds, bounds } = traced;
|
|
4273
4244
|
if (debug) {
|
|
@@ -4278,21 +4249,8 @@ var DielineTool = class {
|
|
|
4278
4249
|
expandedBounds: bounds,
|
|
4279
4250
|
currentDielineWidth: s.width,
|
|
4280
4251
|
currentDielineHeight: s.height,
|
|
4281
|
-
options:
|
|
4282
|
-
|
|
4283
|
-
morphologyRadius: detectOptions.morphologyRadius,
|
|
4284
|
-
connectRadiusMax: detectOptions.connectRadiusMax,
|
|
4285
|
-
smoothing: detectOptions.smoothing,
|
|
4286
|
-
simplifyTolerance: detectOptions.simplifyTolerance,
|
|
4287
|
-
threshold: detectOptions.threshold,
|
|
4288
|
-
maskMode: detectOptions.maskMode,
|
|
4289
|
-
whiteThreshold: detectOptions.whiteThreshold,
|
|
4290
|
-
alphaOpaqueCutoff: detectOptions.alphaOpaqueCutoff,
|
|
4291
|
-
noChannels: detectOptions.noChannels,
|
|
4292
|
-
componentMode: detectOptions.componentMode,
|
|
4293
|
-
minComponentArea: detectOptions.minComponentArea,
|
|
4294
|
-
forceConnected: detectOptions.forceConnected
|
|
4295
|
-
}
|
|
4252
|
+
options: tracerOptions,
|
|
4253
|
+
strategy: "single-connected-silhouette"
|
|
4296
4254
|
});
|
|
4297
4255
|
}
|
|
4298
4256
|
return {
|
|
@@ -7009,6 +6967,82 @@ var WhiteInkTool = class {
|
|
|
7009
6967
|
height
|
|
7010
6968
|
};
|
|
7011
6969
|
}
|
|
6970
|
+
getImagePlacementState(id) {
|
|
6971
|
+
const rawItems = this.getConfig("image.items", []);
|
|
6972
|
+
if (!Array.isArray(rawItems) || rawItems.length === 0) return null;
|
|
6973
|
+
const matched = (id ? rawItems.find(
|
|
6974
|
+
(item) => item && typeof item === "object" && typeof item.id === "string" && item.id === id
|
|
6975
|
+
) : void 0) || rawItems[0];
|
|
6976
|
+
if (!matched || typeof matched !== "object") return null;
|
|
6977
|
+
const sourceUrl = typeof matched.sourceUrl === "string" && matched.sourceUrl.length > 0 ? matched.sourceUrl : typeof matched.url === "string" ? matched.url : "";
|
|
6978
|
+
const committedUrl = typeof matched.committedUrl === "string" ? matched.committedUrl : "";
|
|
6979
|
+
return {
|
|
6980
|
+
id: typeof matched.id === "string" && matched.id.length > 0 ? matched.id : id || "image",
|
|
6981
|
+
sourceUrl,
|
|
6982
|
+
committedUrl,
|
|
6983
|
+
left: Number.isFinite(matched.left) ? Number(matched.left) : 0.5,
|
|
6984
|
+
top: Number.isFinite(matched.top) ? Number(matched.top) : 0.5,
|
|
6985
|
+
scale: Number.isFinite(matched.scale) ? Math.max(0.05, matched.scale) : 1,
|
|
6986
|
+
angle: Number.isFinite(matched.angle) ? matched.angle : 0
|
|
6987
|
+
};
|
|
6988
|
+
}
|
|
6989
|
+
shouldRestoreSnapshotToSource(snapshot, placement) {
|
|
6990
|
+
if (!placement.sourceUrl || !placement.committedUrl) return false;
|
|
6991
|
+
if (placement.sourceUrl === placement.committedUrl) return false;
|
|
6992
|
+
return snapshot.src === placement.committedUrl;
|
|
6993
|
+
}
|
|
6994
|
+
getCoverScale(frame, source) {
|
|
6995
|
+
const frameW = Math.max(1, frame.width);
|
|
6996
|
+
const frameH = Math.max(1, frame.height);
|
|
6997
|
+
const sourceW = Math.max(1, source.width);
|
|
6998
|
+
const sourceH = Math.max(1, source.height);
|
|
6999
|
+
return Math.max(frameW / sourceW, frameH / sourceH);
|
|
7000
|
+
}
|
|
7001
|
+
async ensureSourceSize(sourceUrl) {
|
|
7002
|
+
if (!sourceUrl) return null;
|
|
7003
|
+
const cached = this.getSourceSize(sourceUrl);
|
|
7004
|
+
if (cached) return cached;
|
|
7005
|
+
try {
|
|
7006
|
+
const image = await this.loadImageElement(sourceUrl);
|
|
7007
|
+
const size = this.getElementSize(image);
|
|
7008
|
+
if (!size) return null;
|
|
7009
|
+
this.rememberSourceSize(sourceUrl, size);
|
|
7010
|
+
return {
|
|
7011
|
+
width: size.width,
|
|
7012
|
+
height: size.height
|
|
7013
|
+
};
|
|
7014
|
+
} catch (e) {
|
|
7015
|
+
return null;
|
|
7016
|
+
}
|
|
7017
|
+
}
|
|
7018
|
+
async resolveAlignedImageSnapshot(snapshot) {
|
|
7019
|
+
const placement = this.getImagePlacementState(snapshot.id);
|
|
7020
|
+
if (!placement) return snapshot;
|
|
7021
|
+
if (!this.shouldRestoreSnapshotToSource(snapshot, placement)) {
|
|
7022
|
+
return snapshot;
|
|
7023
|
+
}
|
|
7024
|
+
const frame = this.getFrameRect();
|
|
7025
|
+
if (frame.width <= 0 || frame.height <= 0) {
|
|
7026
|
+
return snapshot;
|
|
7027
|
+
}
|
|
7028
|
+
const sourceSize = await this.ensureSourceSize(placement.sourceUrl);
|
|
7029
|
+
if (!sourceSize) return snapshot;
|
|
7030
|
+
const coverScale = this.getCoverScale(frame, sourceSize);
|
|
7031
|
+
return {
|
|
7032
|
+
...snapshot,
|
|
7033
|
+
src: placement.sourceUrl,
|
|
7034
|
+
element: void 0,
|
|
7035
|
+
left: frame.left + placement.left * frame.width,
|
|
7036
|
+
top: frame.top + placement.top * frame.height,
|
|
7037
|
+
scaleX: coverScale * placement.scale,
|
|
7038
|
+
scaleY: coverScale * placement.scale,
|
|
7039
|
+
angle: placement.angle,
|
|
7040
|
+
originX: "center",
|
|
7041
|
+
originY: "center",
|
|
7042
|
+
width: sourceSize.width,
|
|
7043
|
+
height: sourceSize.height
|
|
7044
|
+
};
|
|
7045
|
+
}
|
|
7012
7046
|
getImageElementFromObject(obj) {
|
|
7013
7047
|
if (!obj) return null;
|
|
7014
7048
|
if (typeof obj.getElement === "function") {
|
|
@@ -7381,9 +7415,11 @@ var WhiteInkTool = class {
|
|
|
7381
7415
|
let whiteSpecs = [];
|
|
7382
7416
|
let coverSpecs = [];
|
|
7383
7417
|
if (previewActive) {
|
|
7384
|
-
const
|
|
7418
|
+
const baseSnapshot = this.getImageSnapshot(this.getPrimaryImageObject());
|
|
7385
7419
|
const item = this.getEffectiveWhiteInkItem(this.resolveRenderItems());
|
|
7386
|
-
if (
|
|
7420
|
+
if (baseSnapshot && item) {
|
|
7421
|
+
const snapshot = await this.resolveAlignedImageSnapshot(baseSnapshot);
|
|
7422
|
+
if (seq !== this.renderSeq) return;
|
|
7387
7423
|
const sources = await this.resolveRenderSources(snapshot, item);
|
|
7388
7424
|
if (seq !== this.renderSeq) return;
|
|
7389
7425
|
if (sources == null ? void 0 : sources.whiteSrc) {
|