@pooder/kit 5.0.4 → 5.1.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/CHANGELOG.md +11 -0
- package/dist/index.d.mts +239 -267
- package/dist/index.d.ts +239 -267
- package/dist/index.js +6603 -5936
- package/dist/index.mjs +6670 -5991
- package/package.json +2 -2
- package/src/{background.ts → extensions/background.ts} +1 -1
- package/src/{dieline.ts → extensions/dieline.ts} +39 -17
- package/src/{feature.ts → extensions/feature.ts} +80 -67
- package/src/{film.ts → extensions/film.ts} +1 -1
- package/src/{geometry.ts → extensions/geometry.ts} +151 -105
- package/src/{image.ts → extensions/image.ts} +97 -84
- package/src/extensions/index.ts +11 -0
- package/src/{maskOps.ts → extensions/maskOps.ts} +28 -10
- package/src/{mirror.ts → extensions/mirror.ts} +1 -1
- package/src/{ruler.ts → extensions/ruler.ts} +5 -3
- package/src/extensions/sceneLayout.ts +140 -0
- package/src/{sceneLayoutModel.ts → extensions/sceneLayoutModel.ts} +17 -10
- package/src/extensions/sceneVisibility.ts +71 -0
- package/src/{size.ts → extensions/size.ts} +23 -13
- package/src/{tracer.ts → extensions/tracer.ts} +374 -45
- package/src/{white-ink.ts → extensions/white-ink.ts} +620 -236
- package/src/index.ts +2 -14
- package/src/{ViewportSystem.ts → services/ViewportSystem.ts} +5 -2
- package/src/services/index.ts +3 -0
- package/src/sceneLayout.ts +0 -121
- package/src/sceneVisibility.ts +0 -49
- /package/src/{bridgeSelection.ts → extensions/bridgeSelection.ts} +0 -0
- /package/src/{constraints.ts → extensions/constraints.ts} +0 -0
- /package/src/{edgeScale.ts → extensions/edgeScale.ts} +0 -0
- /package/src/{featureComplete.ts → extensions/featureComplete.ts} +0 -0
- /package/src/{wrappedOffsets.ts → extensions/wrappedOffsets.ts} +0 -0
- /package/src/{CanvasService.ts → services/CanvasService.ts} +0 -0
- /package/src/{renderSpec.ts → services/renderSpec.ts} +0 -0
|
@@ -212,8 +212,14 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
212
212
|
radius: [Math.max(0, width / 2), Math.max(0, height / 2)],
|
|
213
213
|
});
|
|
214
214
|
} else if (shape === "custom" && pathData) {
|
|
215
|
-
const
|
|
216
|
-
path.
|
|
215
|
+
const hasMultipleSubPaths = ((pathData.match(/[Mm]/g) || []).length ?? 0) > 1;
|
|
216
|
+
const path: paper.PathItem = hasMultipleSubPaths
|
|
217
|
+
? new paper.CompoundPath(pathData)
|
|
218
|
+
: (() => {
|
|
219
|
+
const single = new paper.Path();
|
|
220
|
+
single.pathData = pathData;
|
|
221
|
+
return single;
|
|
222
|
+
})();
|
|
217
223
|
// Align center
|
|
218
224
|
path.position = center;
|
|
219
225
|
if (
|
|
@@ -233,6 +239,37 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
233
239
|
}
|
|
234
240
|
}
|
|
235
241
|
|
|
242
|
+
function resolveBridgeBasePath(
|
|
243
|
+
shape: paper.PathItem,
|
|
244
|
+
anchor: paper.Point,
|
|
245
|
+
): paper.Path | null {
|
|
246
|
+
if (shape instanceof paper.Path) {
|
|
247
|
+
return shape;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (shape instanceof paper.CompoundPath) {
|
|
251
|
+
const children = (shape.children || []).filter(
|
|
252
|
+
(child): child is paper.Path => child instanceof paper.Path,
|
|
253
|
+
);
|
|
254
|
+
if (!children.length) return null;
|
|
255
|
+
let best = children[0];
|
|
256
|
+
let bestDistance = Infinity;
|
|
257
|
+
for (const child of children) {
|
|
258
|
+
const location = child.getNearestLocation(anchor);
|
|
259
|
+
const point = location?.point;
|
|
260
|
+
if (!point) continue;
|
|
261
|
+
const distance = point.getDistance(anchor);
|
|
262
|
+
if (distance < bestDistance) {
|
|
263
|
+
bestDistance = distance;
|
|
264
|
+
best = child;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return best;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
236
273
|
/**
|
|
237
274
|
* Creates a Paper.js Item for a single feature.
|
|
238
275
|
*/
|
|
@@ -307,113 +344,122 @@ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
|
307
344
|
const inset = Math.min(1, Math.max(0, itemBounds.width * 0.01));
|
|
308
345
|
const xLeft = itemBounds.left + inset;
|
|
309
346
|
const xRight = itemBounds.right - inset;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
347
|
+
const bridgeBasePath = resolveBridgeBasePath(mainShape, center);
|
|
348
|
+
const canBridge = !!bridgeBasePath && xRight - xLeft > eps;
|
|
349
|
+
|
|
350
|
+
if (canBridge && bridgeBasePath) {
|
|
351
|
+
const leftHit = getExitHit({
|
|
352
|
+
mainShape: bridgeBasePath,
|
|
353
|
+
x: xLeft,
|
|
354
|
+
bridgeBottom,
|
|
355
|
+
toY,
|
|
356
|
+
eps,
|
|
357
|
+
delta,
|
|
358
|
+
overlap,
|
|
359
|
+
op: f.operation,
|
|
360
|
+
});
|
|
361
|
+
const rightHit = getExitHit({
|
|
362
|
+
mainShape: bridgeBasePath,
|
|
363
|
+
x: xRight,
|
|
364
|
+
bridgeBottom,
|
|
365
|
+
toY,
|
|
366
|
+
eps,
|
|
367
|
+
delta,
|
|
368
|
+
overlap,
|
|
369
|
+
op: f.operation,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (leftHit && rightHit) {
|
|
373
|
+
const pathLength = bridgeBasePath.length;
|
|
374
|
+
const leftOffset = leftHit.location.offset;
|
|
375
|
+
const rightOffset = rightHit.location.offset;
|
|
376
|
+
|
|
377
|
+
const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
|
|
378
|
+
const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
|
|
379
|
+
const countFor = (d: number) =>
|
|
380
|
+
Math.max(8, Math.min(80, Math.ceil(d / 6)));
|
|
381
|
+
|
|
382
|
+
const offsetsA = sampleWrappedOffsets(
|
|
383
|
+
pathLength,
|
|
384
|
+
leftOffset,
|
|
385
|
+
rightOffset,
|
|
386
|
+
countFor(distanceA),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
const offsetsB = sampleWrappedOffsets(
|
|
390
|
+
pathLength,
|
|
391
|
+
rightOffset,
|
|
392
|
+
leftOffset,
|
|
393
|
+
countFor(distanceB),
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
const pointsA = offsetsA
|
|
397
|
+
.map((o) => bridgeBasePath.getPointAt(o))
|
|
398
|
+
.filter((p): p is paper.Point => Boolean(p));
|
|
399
|
+
const pointsB = offsetsB
|
|
400
|
+
.map((o) => bridgeBasePath.getPointAt(o))
|
|
401
|
+
.filter((p): p is paper.Point => Boolean(p));
|
|
402
|
+
|
|
403
|
+
if (pointsA.length >= 2 && pointsB.length >= 2) {
|
|
404
|
+
let topBase = selectOuterChain({
|
|
405
|
+
mainShape: bridgeBasePath,
|
|
406
|
+
pointsA,
|
|
407
|
+
pointsB,
|
|
408
|
+
delta,
|
|
409
|
+
overlap,
|
|
410
|
+
op: f.operation,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const dist2 = (a: paper.Point, b: paper.Point) => {
|
|
414
|
+
const dx = a.x - b.x;
|
|
415
|
+
const dy = a.y - b.y;
|
|
416
|
+
return dx * dx + dy * dy;
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
if (
|
|
420
|
+
dist2(topBase[0], leftHit.point) >
|
|
421
|
+
dist2(topBase[0], rightHit.point)
|
|
422
|
+
) {
|
|
423
|
+
topBase = topBase.slice().reverse();
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
topBase = topBase.slice();
|
|
427
|
+
topBase[0] = leftHit.point;
|
|
428
|
+
topBase[topBase.length - 1] = rightHit.point;
|
|
429
|
+
|
|
430
|
+
const capShiftY =
|
|
431
|
+
f.operation === "subtract"
|
|
432
|
+
? -Math.max(overlap * 2, delta)
|
|
433
|
+
: overlap;
|
|
434
|
+
const topPoints = topBase.map((p) =>
|
|
435
|
+
p.add(new paper.Point(0, capShiftY)),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const bridgeBottomY = bridgeBottom + overlap * 2;
|
|
439
|
+
const bridgePoly = new paper.Path({ insert: false });
|
|
440
|
+
for (const p of topPoints) bridgePoly.add(p);
|
|
441
|
+
bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
|
|
442
|
+
bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
|
|
443
|
+
bridgePoly.closed = true;
|
|
444
|
+
|
|
445
|
+
const unitedItem = item.unite(bridgePoly);
|
|
446
|
+
item.remove();
|
|
447
|
+
bridgePoly.remove();
|
|
448
|
+
|
|
449
|
+
if (f.operation === "add") {
|
|
450
|
+
adds.push(unitedItem);
|
|
451
|
+
} else {
|
|
452
|
+
subtracts.push(unitedItem);
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
372
457
|
}
|
|
373
458
|
|
|
374
|
-
let topBase = selectOuterChain({
|
|
375
|
-
mainShape,
|
|
376
|
-
pointsA,
|
|
377
|
-
pointsB,
|
|
378
|
-
delta,
|
|
379
|
-
overlap,
|
|
380
|
-
op: f.operation,
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
const dist2 = (a: paper.Point, b: paper.Point) => {
|
|
384
|
-
const dx = a.x - b.x;
|
|
385
|
-
const dy = a.y - b.y;
|
|
386
|
-
return dx * dx + dy * dy;
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
if (dist2(topBase[0], leftHit.point) > dist2(topBase[0], rightHit.point)) {
|
|
390
|
-
topBase = topBase.slice().reverse();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
topBase = topBase.slice();
|
|
394
|
-
topBase[0] = leftHit.point;
|
|
395
|
-
topBase[topBase.length - 1] = rightHit.point;
|
|
396
|
-
|
|
397
|
-
const capShiftY = f.operation === "subtract" ? -Math.max(overlap * 2, delta) : overlap;
|
|
398
|
-
const topPoints = topBase.map(
|
|
399
|
-
(p) => p.add(new paper.Point(0, capShiftY)),
|
|
400
|
-
);
|
|
401
|
-
|
|
402
|
-
const bridgeBottomY = bridgeBottom + overlap * 2;
|
|
403
|
-
const bridgePoly = new paper.Path({ insert: false });
|
|
404
|
-
for (const p of topPoints) bridgePoly.add(p);
|
|
405
|
-
bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
|
|
406
|
-
bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
|
|
407
|
-
bridgePoly.closed = true;
|
|
408
|
-
|
|
409
|
-
const unitedItem = item.unite(bridgePoly);
|
|
410
|
-
item.remove();
|
|
411
|
-
bridgePoly.remove();
|
|
412
|
-
|
|
413
459
|
if (f.operation === "add") {
|
|
414
|
-
adds.push(
|
|
460
|
+
adds.push(item);
|
|
415
461
|
} else {
|
|
416
|
-
subtracts.push(
|
|
462
|
+
subtracts.push(item);
|
|
417
463
|
}
|
|
418
464
|
} else {
|
|
419
465
|
if (f.operation === "add") {
|
|
@@ -9,8 +9,7 @@ import {
|
|
|
9
9
|
WorkbenchService,
|
|
10
10
|
} from "@pooder/core";
|
|
11
11
|
import { Canvas as FabricCanvas, Image as FabricImage, Point } from "fabric";
|
|
12
|
-
import CanvasService from "
|
|
13
|
-
import type { RenderObjectSpec } from "./renderSpec";
|
|
12
|
+
import { CanvasService, RenderObjectSpec } from "../services";
|
|
14
13
|
import { computeSceneLayout, readSizeState } from "./sceneLayoutModel";
|
|
15
14
|
|
|
16
15
|
export interface ImageItem {
|
|
@@ -73,43 +72,26 @@ interface UpdateImageOptions {
|
|
|
73
72
|
target?: "auto" | "config" | "working";
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
interface
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
width: number;
|
|
80
|
-
height: number;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
interface DetectEdgeResult {
|
|
84
|
-
pathData: string;
|
|
85
|
-
rawBounds?: DetectBounds;
|
|
86
|
-
baseBounds?: DetectBounds;
|
|
87
|
-
imageWidth?: number;
|
|
88
|
-
imageHeight?: number;
|
|
75
|
+
interface ExportCroppedImageOptions {
|
|
76
|
+
multiplier?: number;
|
|
77
|
+
format?: "png" | "jpeg";
|
|
89
78
|
}
|
|
90
79
|
|
|
91
|
-
interface
|
|
92
|
-
|
|
93
|
-
centerX: number;
|
|
94
|
-
centerY: number;
|
|
95
|
-
objectScale: number;
|
|
96
|
-
sourceWidth: number;
|
|
97
|
-
sourceHeight: number;
|
|
80
|
+
interface ExportUserCroppedImageOptions extends ExportCroppedImageOptions {
|
|
81
|
+
imageIds?: string[];
|
|
98
82
|
}
|
|
99
83
|
|
|
100
|
-
interface
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
multiplier
|
|
105
|
-
|
|
84
|
+
interface ExportUserCroppedImageResult {
|
|
85
|
+
url: string;
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
multiplier: number;
|
|
89
|
+
format: "png" | "jpeg";
|
|
90
|
+
imageIds: string[];
|
|
106
91
|
}
|
|
107
92
|
|
|
108
93
|
const IMAGE_OBJECT_LAYER_ID = "image.user";
|
|
109
94
|
const IMAGE_OVERLAY_LAYER_ID = "image-overlay";
|
|
110
|
-
const IMAGE_DETECT_EXPAND_DEFAULT = 30;
|
|
111
|
-
const IMAGE_DETECT_SIMPLIFY_TOLERANCE_DEFAULT = 2;
|
|
112
|
-
const IMAGE_DETECT_MULTIPLIER_DEFAULT = 2;
|
|
113
95
|
|
|
114
96
|
export class ImageTool implements Extension {
|
|
115
97
|
id = "pooder.kit.image";
|
|
@@ -431,12 +413,12 @@ export class ImageTool implements Extension {
|
|
|
431
413
|
},
|
|
432
414
|
},
|
|
433
415
|
{
|
|
434
|
-
command: "
|
|
435
|
-
title: "Export
|
|
416
|
+
command: "exportUserCroppedImage",
|
|
417
|
+
title: "Export User Cropped Image",
|
|
436
418
|
handler: async (
|
|
437
|
-
options:
|
|
419
|
+
options: ExportUserCroppedImageOptions = {},
|
|
438
420
|
) => {
|
|
439
|
-
return await this.
|
|
421
|
+
return await this.exportUserCroppedImage(options);
|
|
440
422
|
},
|
|
441
423
|
},
|
|
442
424
|
{
|
|
@@ -1470,10 +1452,11 @@ export class ImageTool implements Extension {
|
|
|
1470
1452
|
|
|
1471
1453
|
const next: ImageItem[] = [];
|
|
1472
1454
|
for (const item of this.workingItems) {
|
|
1473
|
-
const
|
|
1455
|
+
const exported = await this.exportCroppedImageByIds([item.id], {
|
|
1474
1456
|
multiplier: 2,
|
|
1475
1457
|
format: "png",
|
|
1476
1458
|
});
|
|
1459
|
+
const url = exported.url;
|
|
1477
1460
|
|
|
1478
1461
|
const sourceUrl = item.sourceUrl || item.url;
|
|
1479
1462
|
const previousCommitted = item.committedUrl;
|
|
@@ -1499,15 +1482,22 @@ export class ImageTool implements Extension {
|
|
|
1499
1482
|
|
|
1500
1483
|
private async exportCroppedImageByIds(
|
|
1501
1484
|
imageIds: string[],
|
|
1502
|
-
options:
|
|
1503
|
-
): Promise<
|
|
1485
|
+
options: ExportCroppedImageOptions,
|
|
1486
|
+
): Promise<ExportUserCroppedImageResult> {
|
|
1504
1487
|
if (!this.canvasService) {
|
|
1505
1488
|
throw new Error("CanvasService not initialized");
|
|
1506
1489
|
}
|
|
1507
1490
|
|
|
1491
|
+
const normalizedIds = [...new Set(imageIds)].filter(
|
|
1492
|
+
(id): id is string => typeof id === "string" && id.length > 0,
|
|
1493
|
+
);
|
|
1494
|
+
if (!normalizedIds.length) {
|
|
1495
|
+
throw new Error("image-ids-required");
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1508
1498
|
const frame = this.getFrameRect();
|
|
1509
1499
|
const multiplier = Math.max(1, options.multiplier ?? 2);
|
|
1510
|
-
const format = options.format
|
|
1500
|
+
const format: "png" | "jpeg" = options.format === "jpeg" ? "jpeg" : "png";
|
|
1511
1501
|
|
|
1512
1502
|
const width = Math.max(1, Math.round(frame.width * multiplier));
|
|
1513
1503
|
const height = Math.max(1, Math.round(frame.height * multiplier));
|
|
@@ -1521,61 +1511,84 @@ export class ImageTool implements Extension {
|
|
|
1521
1511
|
} as any);
|
|
1522
1512
|
tempCanvas.setDimensions({ width, height });
|
|
1523
1513
|
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
.
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1514
|
+
try {
|
|
1515
|
+
const idSet = new Set(normalizedIds);
|
|
1516
|
+
const sourceObjects = this.canvasService.canvas
|
|
1517
|
+
.getObjects()
|
|
1518
|
+
.filter((obj: any) => {
|
|
1519
|
+
return (
|
|
1520
|
+
obj?.data?.layerId === IMAGE_OBJECT_LAYER_ID &&
|
|
1521
|
+
typeof obj?.data?.id === "string" &&
|
|
1522
|
+
idSet.has(obj.data.id)
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
if (!sourceObjects.length) {
|
|
1527
|
+
throw new Error("image-objects-not-found");
|
|
1528
|
+
}
|
|
1534
1529
|
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1530
|
+
for (const source of sourceObjects as any[]) {
|
|
1531
|
+
const clone = await source.clone();
|
|
1532
|
+
const center = source.getCenterPoint
|
|
1533
|
+
? source.getCenterPoint()
|
|
1534
|
+
: new Point(source.left ?? 0, source.top ?? 0);
|
|
1540
1535
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1536
|
+
clone.set({
|
|
1537
|
+
originX: "center",
|
|
1538
|
+
originY: "center",
|
|
1539
|
+
left: (center.x - frame.left) * multiplier,
|
|
1540
|
+
top: (center.y - frame.top) * multiplier,
|
|
1541
|
+
scaleX: (source.scaleX || 1) * multiplier,
|
|
1542
|
+
scaleY: (source.scaleY || 1) * multiplier,
|
|
1543
|
+
angle: source.angle || 0,
|
|
1544
|
+
selectable: false,
|
|
1545
|
+
evented: false,
|
|
1546
|
+
});
|
|
1547
|
+
clone.setCoords();
|
|
1548
|
+
tempCanvas.add(clone);
|
|
1549
|
+
}
|
|
1555
1550
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1551
|
+
tempCanvas.renderAll();
|
|
1552
|
+
const blob = await tempCanvas.toBlob({ format, multiplier: 1 });
|
|
1553
|
+
if (!blob) {
|
|
1554
|
+
throw new Error("image-export-failed");
|
|
1555
|
+
}
|
|
1559
1556
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1557
|
+
return {
|
|
1558
|
+
url: URL.createObjectURL(blob),
|
|
1559
|
+
width,
|
|
1560
|
+
height,
|
|
1561
|
+
multiplier,
|
|
1562
|
+
format,
|
|
1563
|
+
imageIds: (sourceObjects as any[])
|
|
1564
|
+
.map((obj: any) => obj?.data?.id)
|
|
1565
|
+
.filter((id: any): id is string => typeof id === "string"),
|
|
1566
|
+
};
|
|
1567
|
+
} finally {
|
|
1568
|
+
tempCanvas.dispose();
|
|
1569
|
+
}
|
|
1562
1570
|
}
|
|
1563
1571
|
|
|
1564
|
-
private async
|
|
1565
|
-
options:
|
|
1566
|
-
): Promise<
|
|
1572
|
+
private async exportUserCroppedImage(
|
|
1573
|
+
options: ExportUserCroppedImageOptions = {},
|
|
1574
|
+
): Promise<ExportUserCroppedImageResult> {
|
|
1567
1575
|
if (!this.canvasService) {
|
|
1568
1576
|
throw new Error("CanvasService not initialized");
|
|
1569
1577
|
}
|
|
1570
1578
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
.filter((id: any) => typeof id === "string");
|
|
1579
|
+
await this.updateImagesAsync();
|
|
1580
|
+
this.syncToolActiveFromWorkbench();
|
|
1574
1581
|
|
|
1575
|
-
const
|
|
1576
|
-
imageIds
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1582
|
+
const imageIds =
|
|
1583
|
+
options.imageIds && options.imageIds.length > 0
|
|
1584
|
+
? options.imageIds
|
|
1585
|
+
: (this.isToolActive ? this.workingItems : this.items).map(
|
|
1586
|
+
(item) => item.id,
|
|
1587
|
+
);
|
|
1588
|
+
if (!imageIds.length) {
|
|
1589
|
+
throw new Error("no-images-to-export");
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
return await this.exportCroppedImageByIds(imageIds, options);
|
|
1580
1593
|
}
|
|
1581
1594
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./background";
|
|
2
|
+
export * from "./image";
|
|
3
|
+
export * from "./size";
|
|
4
|
+
export * from "./dieline";
|
|
5
|
+
export * from "./feature";
|
|
6
|
+
export * from "./film";
|
|
7
|
+
export * from "./mirror";
|
|
8
|
+
export * from "./ruler";
|
|
9
|
+
export * from "./white-ink";
|
|
10
|
+
export { SceneLayoutService } from "./sceneLayout";
|
|
11
|
+
export { SceneVisibilityService } from "./sceneVisibility";
|
|
@@ -10,6 +10,13 @@ export interface CreateMaskOptions {
|
|
|
10
10
|
alphaOpaqueCutoff?: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface AlphaAnalysis {
|
|
14
|
+
total: number;
|
|
15
|
+
minAlpha: number;
|
|
16
|
+
belowOpaqueRatio: number;
|
|
17
|
+
veryTransparentRatio: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export function createMask(
|
|
14
21
|
imageData: ImageData,
|
|
15
22
|
options: CreateMaskOptions,
|
|
@@ -55,7 +62,21 @@ export function createMask(
|
|
|
55
62
|
return mask;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
function inferMaskMode(
|
|
65
|
+
export function inferMaskMode(
|
|
66
|
+
imageData: ImageData,
|
|
67
|
+
alphaOpaqueCutoff: number,
|
|
68
|
+
): MaskMode {
|
|
69
|
+
const analysis = analyzeAlpha(imageData, alphaOpaqueCutoff);
|
|
70
|
+
if (analysis.minAlpha === 255) return "whitebg";
|
|
71
|
+
if (analysis.veryTransparentRatio >= 0.0005) return "alpha";
|
|
72
|
+
if (analysis.belowOpaqueRatio >= 0.01) return "alpha";
|
|
73
|
+
return "whitebg";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function analyzeAlpha(
|
|
77
|
+
imageData: ImageData,
|
|
78
|
+
alphaOpaqueCutoff: number,
|
|
79
|
+
): AlphaAnalysis {
|
|
59
80
|
const { data } = imageData;
|
|
60
81
|
const total = data.length / 4;
|
|
61
82
|
|
|
@@ -70,15 +91,12 @@ function inferMaskMode(imageData: ImageData, alphaOpaqueCutoff: number): MaskMod
|
|
|
70
91
|
if (a < 32) veryTransparent++;
|
|
71
92
|
}
|
|
72
93
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (belowOpaqueRatio >= 0.01) return "alpha";
|
|
80
|
-
|
|
81
|
-
return "whitebg";
|
|
94
|
+
return {
|
|
95
|
+
total,
|
|
96
|
+
minAlpha,
|
|
97
|
+
belowOpaqueRatio: belowOpaque / total,
|
|
98
|
+
veryTransparentRatio: veryTransparent / total,
|
|
99
|
+
};
|
|
82
100
|
}
|
|
83
101
|
|
|
84
102
|
export function circularMorphology(
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
ConfigurationService,
|
|
8
8
|
} from "@pooder/core";
|
|
9
9
|
import { Rect, Line, Text, Group, Polygon } from "fabric";
|
|
10
|
-
import CanvasService from "
|
|
11
|
-
import { formatMm } from "
|
|
10
|
+
import { CanvasService } from "../services";
|
|
11
|
+
import { formatMm } from "../units";
|
|
12
12
|
import { computeSceneLayout, readSizeState } from "./sceneLayoutModel";
|
|
13
13
|
|
|
14
14
|
export class RulerTool implements Extension {
|
|
@@ -304,7 +304,9 @@ export class RulerTool implements Extension {
|
|
|
304
304
|
const rulerBottom = rulerRect.top + rulerRect.height;
|
|
305
305
|
|
|
306
306
|
// Display Dimensions (Physical)
|
|
307
|
-
const displayWidthMm = useCutAsRuler
|
|
307
|
+
const displayWidthMm = useCutAsRuler
|
|
308
|
+
? layout.cutWidthMm
|
|
309
|
+
: layout.trimWidthMm;
|
|
308
310
|
const displayHeightMm = useCutAsRuler
|
|
309
311
|
? layout.cutHeightMm
|
|
310
312
|
: layout.trimHeightMm;
|