@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.
Files changed (34) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.d.mts +239 -267
  3. package/dist/index.d.ts +239 -267
  4. package/dist/index.js +6603 -5936
  5. package/dist/index.mjs +6670 -5991
  6. package/package.json +2 -2
  7. package/src/{background.ts → extensions/background.ts} +1 -1
  8. package/src/{dieline.ts → extensions/dieline.ts} +39 -17
  9. package/src/{feature.ts → extensions/feature.ts} +80 -67
  10. package/src/{film.ts → extensions/film.ts} +1 -1
  11. package/src/{geometry.ts → extensions/geometry.ts} +151 -105
  12. package/src/{image.ts → extensions/image.ts} +97 -84
  13. package/src/extensions/index.ts +11 -0
  14. package/src/{maskOps.ts → extensions/maskOps.ts} +28 -10
  15. package/src/{mirror.ts → extensions/mirror.ts} +1 -1
  16. package/src/{ruler.ts → extensions/ruler.ts} +5 -3
  17. package/src/extensions/sceneLayout.ts +140 -0
  18. package/src/{sceneLayoutModel.ts → extensions/sceneLayoutModel.ts} +17 -10
  19. package/src/extensions/sceneVisibility.ts +71 -0
  20. package/src/{size.ts → extensions/size.ts} +23 -13
  21. package/src/{tracer.ts → extensions/tracer.ts} +374 -45
  22. package/src/{white-ink.ts → extensions/white-ink.ts} +620 -236
  23. package/src/index.ts +2 -14
  24. package/src/{ViewportSystem.ts → services/ViewportSystem.ts} +5 -2
  25. package/src/services/index.ts +3 -0
  26. package/src/sceneLayout.ts +0 -121
  27. package/src/sceneVisibility.ts +0 -49
  28. /package/src/{bridgeSelection.ts → extensions/bridgeSelection.ts} +0 -0
  29. /package/src/{constraints.ts → extensions/constraints.ts} +0 -0
  30. /package/src/{edgeScale.ts → extensions/edgeScale.ts} +0 -0
  31. /package/src/{featureComplete.ts → extensions/featureComplete.ts} +0 -0
  32. /package/src/{wrappedOffsets.ts → extensions/wrappedOffsets.ts} +0 -0
  33. /package/src/{CanvasService.ts → services/CanvasService.ts} +0 -0
  34. /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 path = new paper.Path();
216
- path.pathData = pathData;
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
- if (!(mainShape instanceof paper.Path)) {
312
- throw new Error("Geometry: Bridge requires base shape to be a Path");
313
- }
314
-
315
- const leftHit = getExitHit({
316
- mainShape,
317
- x: xLeft,
318
- bridgeBottom,
319
- toY,
320
- eps,
321
- delta,
322
- overlap,
323
- op: f.operation,
324
- });
325
- const rightHit = getExitHit({
326
- mainShape,
327
- x: xRight,
328
- bridgeBottom,
329
- toY,
330
- eps,
331
- delta,
332
- overlap,
333
- op: f.operation,
334
- });
335
-
336
- if (!leftHit || !rightHit || xRight - xLeft <= eps) {
337
- throw new Error("Geometry: Bridge ray intersection not found");
338
- }
339
-
340
- const path = mainShape as paper.Path;
341
- const pathLength = path.length;
342
- const leftOffset = leftHit.location.offset;
343
- const rightOffset = rightHit.location.offset;
344
-
345
- const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
346
- const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
347
- const countFor = (d: number) => Math.max(8, Math.min(80, Math.ceil(d / 6)));
348
-
349
- const offsetsA = sampleWrappedOffsets(
350
- pathLength,
351
- leftOffset,
352
- rightOffset,
353
- countFor(distanceA),
354
- );
355
-
356
- const offsetsB = sampleWrappedOffsets(
357
- pathLength,
358
- rightOffset,
359
- leftOffset,
360
- countFor(distanceB),
361
- );
362
-
363
- const pointsA = offsetsA
364
- .map((o) => path.getPointAt(o))
365
- .filter((p): p is paper.Point => Boolean(p));
366
- const pointsB = offsetsB
367
- .map((o) => path.getPointAt(o))
368
- .filter((p): p is paper.Point => Boolean(p));
369
-
370
- if (pointsA.length < 2 || pointsB.length < 2) {
371
- throw new Error("Geometry: Bridge contour sampling failed");
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(unitedItem);
460
+ adds.push(item);
415
461
  } else {
416
- subtracts.push(unitedItem);
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 "./CanvasService";
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 DetectBounds {
77
- x: number;
78
- y: number;
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 ImageRenderSnapshot {
92
- id: string;
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 DetectFromFrameOptions {
101
- expand?: number;
102
- smoothing?: boolean;
103
- simplifyTolerance?: number;
104
- multiplier?: number;
105
- debug?: boolean;
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: "exportImageFrameUrl",
435
- title: "Export Image Frame Url",
416
+ command: "exportUserCroppedImage",
417
+ title: "Export User Cropped Image",
436
418
  handler: async (
437
- options: { multiplier?: number; format?: "png" | "jpeg" } = {},
419
+ options: ExportUserCroppedImageOptions = {},
438
420
  ) => {
439
- return await this.exportImageFrameUrl(options);
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 url = await this.exportCroppedImageByIds([item.id], {
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: { multiplier?: number; format?: "png" | "jpeg" },
1503
- ): Promise<string> {
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 ?? "png";
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
- const idSet = new Set(imageIds);
1525
- const sourceObjects = this.canvasService.canvas
1526
- .getObjects()
1527
- .filter((obj: any) => {
1528
- return (
1529
- obj?.data?.layerId === IMAGE_OBJECT_LAYER_ID &&
1530
- typeof obj?.data?.id === "string" &&
1531
- idSet.has(obj.data.id)
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
- for (const source of sourceObjects as any[]) {
1536
- const clone = await source.clone();
1537
- const center = source.getCenterPoint
1538
- ? source.getCenterPoint()
1539
- : new Point(source.left ?? 0, source.top ?? 0);
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
- clone.set({
1542
- originX: "center",
1543
- originY: "center",
1544
- left: (center.x - frame.left) * multiplier,
1545
- top: (center.y - frame.top) * multiplier,
1546
- scaleX: (source.scaleX || 1) * multiplier,
1547
- scaleY: (source.scaleY || 1) * multiplier,
1548
- angle: source.angle || 0,
1549
- selectable: false,
1550
- evented: false,
1551
- });
1552
- clone.setCoords();
1553
- tempCanvas.add(clone);
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
- tempCanvas.renderAll();
1557
- const dataUrl = tempCanvas.toDataURL({ format, multiplier: 1 });
1558
- tempCanvas.dispose();
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
- const blob = await (await fetch(dataUrl)).blob();
1561
- return URL.createObjectURL(blob);
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 exportImageFrameUrl(
1565
- options: { multiplier?: number; format?: "png" | "jpeg" } = {},
1566
- ): Promise<{ url: string }> {
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
- const imageIds = this.getImageObjects()
1572
- .map((obj: any) => obj?.data?.id)
1573
- .filter((id: any) => typeof id === "string");
1579
+ await this.updateImagesAsync();
1580
+ this.syncToolActiveFromWorkbench();
1574
1581
 
1575
- const url = await this.exportCroppedImageByIds(
1576
- imageIds as string[],
1577
- options,
1578
- );
1579
- return { url };
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(imageData: ImageData, alphaOpaqueCutoff: number): MaskMode {
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
- if (minAlpha === 255) return "whitebg";
74
-
75
- const belowOpaqueRatio = belowOpaque / total;
76
- const veryTransparentRatio = veryTransparent / total;
77
-
78
- if (veryTransparentRatio >= 0.0005) return "alpha";
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(
@@ -5,7 +5,7 @@ import {
5
5
  CommandContribution,
6
6
  ConfigurationContribution,
7
7
  } from "@pooder/core";
8
- import CanvasService from "./CanvasService";
8
+ import { CanvasService } from "../services";
9
9
 
10
10
  export class MirrorTool implements Extension {
11
11
  id = "pooder.kit.mirror";
@@ -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 "./CanvasService";
11
- import { formatMm } from "./units";
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 ? layout.cutWidthMm : layout.trimWidthMm;
307
+ const displayWidthMm = useCutAsRuler
308
+ ? layout.cutWidthMm
309
+ : layout.trimWidthMm;
308
310
  const displayHeightMm = useCutAsRuler
309
311
  ? layout.cutHeightMm
310
312
  : layout.trimHeightMm;