@pooder/kit 5.1.0 → 5.2.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 +6 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +3452 -3187
- package/dist/index.mjs +3461 -3191
- package/package.json +1 -1
- package/src/extensions/image.ts +340 -10
package/package.json
CHANGED
package/src/extensions/image.ts
CHANGED
|
@@ -8,9 +8,19 @@ import {
|
|
|
8
8
|
ToolSessionService,
|
|
9
9
|
WorkbenchService,
|
|
10
10
|
} from "@pooder/core";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
Canvas as FabricCanvas,
|
|
13
|
+
Image as FabricImage,
|
|
14
|
+
Pattern,
|
|
15
|
+
Point,
|
|
16
|
+
} from "fabric";
|
|
12
17
|
import { CanvasService, RenderObjectSpec } from "../services";
|
|
13
|
-
import {
|
|
18
|
+
import { generateDielinePath } from "./geometry";
|
|
19
|
+
import {
|
|
20
|
+
buildSceneGeometry,
|
|
21
|
+
computeSceneLayout,
|
|
22
|
+
readSizeState,
|
|
23
|
+
} from "./sceneLayoutModel";
|
|
14
24
|
|
|
15
25
|
export interface ImageItem {
|
|
16
26
|
id: string;
|
|
@@ -54,6 +64,15 @@ interface FrameVisualConfig {
|
|
|
54
64
|
outerBackground: string;
|
|
55
65
|
}
|
|
56
66
|
|
|
67
|
+
type DielineShape = "rect" | "circle" | "ellipse" | "custom";
|
|
68
|
+
type ShapeOverlayShape = Exclude<DielineShape, "custom">;
|
|
69
|
+
|
|
70
|
+
interface SceneGeometryLike {
|
|
71
|
+
shape: DielineShape;
|
|
72
|
+
radius: number;
|
|
73
|
+
offset: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
57
76
|
interface UpsertImageOptions {
|
|
58
77
|
id?: string;
|
|
59
78
|
mode?: "replace" | "add";
|
|
@@ -113,6 +132,8 @@ export class ImageTool implements Extension {
|
|
|
113
132
|
private focusedImageId: string | null = null;
|
|
114
133
|
private renderSeq = 0;
|
|
115
134
|
private dirtyTrackerDisposable?: { dispose(): void };
|
|
135
|
+
private cropShapeHatchPattern?: Pattern;
|
|
136
|
+
private cropShapeHatchPatternColor?: string;
|
|
116
137
|
|
|
117
138
|
activate(context: ExtensionContext) {
|
|
118
139
|
this.context = context;
|
|
@@ -128,6 +149,7 @@ export class ImageTool implements Extension {
|
|
|
128
149
|
context.eventBus.on("selection:updated", this.onSelectionChanged);
|
|
129
150
|
context.eventBus.on("selection:cleared", this.onSelectionCleared);
|
|
130
151
|
context.eventBus.on("scene:layout:change", this.onSceneLayoutChanged);
|
|
152
|
+
context.eventBus.on("scene:geometry:change", this.onSceneGeometryChanged);
|
|
131
153
|
|
|
132
154
|
const configService = context.services.get<ConfigurationService>(
|
|
133
155
|
"ConfigurationService",
|
|
@@ -175,8 +197,11 @@ export class ImageTool implements Extension {
|
|
|
175
197
|
context.eventBus.off("selection:updated", this.onSelectionChanged);
|
|
176
198
|
context.eventBus.off("selection:cleared", this.onSelectionCleared);
|
|
177
199
|
context.eventBus.off("scene:layout:change", this.onSceneLayoutChanged);
|
|
200
|
+
context.eventBus.off("scene:geometry:change", this.onSceneGeometryChanged);
|
|
178
201
|
this.dirtyTrackerDisposable?.dispose();
|
|
179
202
|
this.dirtyTrackerDisposable = undefined;
|
|
203
|
+
this.cropShapeHatchPattern = undefined;
|
|
204
|
+
this.cropShapeHatchPatternColor = undefined;
|
|
180
205
|
|
|
181
206
|
this.clearRenderedImages();
|
|
182
207
|
if (this.canvasService) {
|
|
@@ -258,6 +283,10 @@ export class ImageTool implements Extension {
|
|
|
258
283
|
this.updateImages();
|
|
259
284
|
};
|
|
260
285
|
|
|
286
|
+
private onSceneGeometryChanged = () => {
|
|
287
|
+
this.updateImages();
|
|
288
|
+
};
|
|
289
|
+
|
|
261
290
|
private syncToolActiveFromWorkbench(fallbackId?: string | null) {
|
|
262
291
|
const wb = this.context?.services.get<WorkbenchService>("WorkbenchService");
|
|
263
292
|
const activeId = wb?.activeToolId;
|
|
@@ -415,9 +444,7 @@ export class ImageTool implements Extension {
|
|
|
415
444
|
{
|
|
416
445
|
command: "exportUserCroppedImage",
|
|
417
446
|
title: "Export User Cropped Image",
|
|
418
|
-
handler: async (
|
|
419
|
-
options: ExportUserCroppedImageOptions = {},
|
|
420
|
-
) => {
|
|
447
|
+
handler: async (options: ExportUserCroppedImageOptions = {}) => {
|
|
421
448
|
return await this.exportUserCroppedImage(options);
|
|
422
449
|
},
|
|
423
450
|
},
|
|
@@ -894,10 +921,268 @@ export class ImageTool implements Extension {
|
|
|
894
921
|
"image.frame.innerBackground",
|
|
895
922
|
"rgba(0,0,0,0)",
|
|
896
923
|
) || "rgba(0,0,0,0)",
|
|
897
|
-
outerBackground:
|
|
924
|
+
outerBackground:
|
|
925
|
+
this.getConfig<string>("image.frame.outerBackground", "#f5f5f5") ||
|
|
926
|
+
"#f5f5f5",
|
|
898
927
|
};
|
|
899
928
|
}
|
|
900
929
|
|
|
930
|
+
private toSceneGeometryLike(raw: any): SceneGeometryLike | null {
|
|
931
|
+
const shape = raw?.shape;
|
|
932
|
+
if (
|
|
933
|
+
shape !== "rect" &&
|
|
934
|
+
shape !== "circle" &&
|
|
935
|
+
shape !== "ellipse" &&
|
|
936
|
+
shape !== "custom"
|
|
937
|
+
) {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const radius = Number(raw?.radius);
|
|
942
|
+
const offset = Number(raw?.offset);
|
|
943
|
+
return {
|
|
944
|
+
shape,
|
|
945
|
+
radius: Number.isFinite(radius) ? radius : 0,
|
|
946
|
+
offset: Number.isFinite(offset) ? offset : 0,
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
private async resolveSceneGeometryForOverlay(): Promise<SceneGeometryLike | null> {
|
|
951
|
+
if (!this.context) return null;
|
|
952
|
+
const commandService = this.context.services.get<any>("CommandService");
|
|
953
|
+
if (commandService) {
|
|
954
|
+
try {
|
|
955
|
+
const raw = await Promise.resolve(
|
|
956
|
+
commandService.executeCommand("getSceneGeometry"),
|
|
957
|
+
);
|
|
958
|
+
const geometry = this.toSceneGeometryLike(raw);
|
|
959
|
+
if (geometry) {
|
|
960
|
+
this.debug("overlay:sceneGeometry:command", geometry);
|
|
961
|
+
return geometry;
|
|
962
|
+
}
|
|
963
|
+
this.debug("overlay:sceneGeometry:command:invalid", { raw });
|
|
964
|
+
} catch (error) {
|
|
965
|
+
this.debug("overlay:sceneGeometry:command:error", {
|
|
966
|
+
error: error instanceof Error ? error.message : String(error),
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
if (!this.canvasService) return null;
|
|
972
|
+
const configService = this.context.services.get<ConfigurationService>(
|
|
973
|
+
"ConfigurationService",
|
|
974
|
+
);
|
|
975
|
+
if (!configService) return null;
|
|
976
|
+
|
|
977
|
+
const sizeState = readSizeState(configService);
|
|
978
|
+
const layout = computeSceneLayout(this.canvasService, sizeState);
|
|
979
|
+
if (!layout) {
|
|
980
|
+
this.debug("overlay:sceneGeometry:fallback:missing-layout");
|
|
981
|
+
return null;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const geometry = this.toSceneGeometryLike(
|
|
985
|
+
buildSceneGeometry(configService, layout),
|
|
986
|
+
);
|
|
987
|
+
if (geometry) {
|
|
988
|
+
this.debug("overlay:sceneGeometry:fallback", geometry);
|
|
989
|
+
}
|
|
990
|
+
return geometry;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
private resolveCutShapeRadius(
|
|
994
|
+
geometry: SceneGeometryLike,
|
|
995
|
+
frame: FrameRect,
|
|
996
|
+
): number {
|
|
997
|
+
const visualRadius = Number.isFinite(geometry.radius)
|
|
998
|
+
? Math.max(0, geometry.radius)
|
|
999
|
+
: 0;
|
|
1000
|
+
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
1001
|
+
const rawCutRadius =
|
|
1002
|
+
visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
1003
|
+
const maxRadius = Math.max(0, Math.min(frame.width, frame.height) / 2);
|
|
1004
|
+
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
private getCropShapeHatchPattern(
|
|
1008
|
+
color = "rgba(255, 0, 0, 0.6)",
|
|
1009
|
+
): Pattern | undefined {
|
|
1010
|
+
if (typeof document === "undefined") return undefined;
|
|
1011
|
+
if (
|
|
1012
|
+
this.cropShapeHatchPattern &&
|
|
1013
|
+
this.cropShapeHatchPatternColor === color
|
|
1014
|
+
) {
|
|
1015
|
+
return this.cropShapeHatchPattern;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
const size = 16;
|
|
1019
|
+
const patternCanvas = document.createElement("canvas");
|
|
1020
|
+
patternCanvas.width = size;
|
|
1021
|
+
patternCanvas.height = size;
|
|
1022
|
+
const ctx = patternCanvas.getContext("2d");
|
|
1023
|
+
if (!ctx) return undefined;
|
|
1024
|
+
|
|
1025
|
+
ctx.clearRect(0, 0, size, size);
|
|
1026
|
+
ctx.fillStyle = "rgba(255, 0, 0, 0.08)";
|
|
1027
|
+
ctx.fillRect(0, 0, size, size);
|
|
1028
|
+
ctx.strokeStyle = color;
|
|
1029
|
+
ctx.lineWidth = 1.5;
|
|
1030
|
+
ctx.beginPath();
|
|
1031
|
+
ctx.moveTo(-size, size);
|
|
1032
|
+
ctx.lineTo(size, -size);
|
|
1033
|
+
ctx.moveTo(-size / 2, size + size / 2);
|
|
1034
|
+
ctx.lineTo(size + size / 2, -size / 2);
|
|
1035
|
+
ctx.moveTo(0, size);
|
|
1036
|
+
ctx.lineTo(size, 0);
|
|
1037
|
+
ctx.moveTo(size / 2, size + size / 2);
|
|
1038
|
+
ctx.lineTo(size + size + size / 2, -size / 2);
|
|
1039
|
+
ctx.stroke();
|
|
1040
|
+
|
|
1041
|
+
const pattern = new Pattern({
|
|
1042
|
+
source: patternCanvas,
|
|
1043
|
+
// @ts-ignore: Fabric Pattern accepts canvas source here.
|
|
1044
|
+
repetition: "repeat",
|
|
1045
|
+
});
|
|
1046
|
+
this.cropShapeHatchPattern = pattern;
|
|
1047
|
+
this.cropShapeHatchPatternColor = color;
|
|
1048
|
+
return pattern;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
private buildCropShapeOverlaySpecs(
|
|
1052
|
+
frame: FrameRect,
|
|
1053
|
+
sceneGeometry: SceneGeometryLike | null,
|
|
1054
|
+
): RenderObjectSpec[] {
|
|
1055
|
+
if (!sceneGeometry) {
|
|
1056
|
+
this.debug("overlay:shape:skip", { reason: "scene-geometry-missing" });
|
|
1057
|
+
return [];
|
|
1058
|
+
}
|
|
1059
|
+
if (sceneGeometry.shape === "custom") {
|
|
1060
|
+
this.debug("overlay:shape:skip", { reason: "shape-custom" });
|
|
1061
|
+
return [];
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const shape = sceneGeometry.shape as ShapeOverlayShape;
|
|
1065
|
+
const inset = 0;
|
|
1066
|
+
const shapeWidth = Math.max(1, frame.width);
|
|
1067
|
+
const shapeHeight = Math.max(1, frame.height);
|
|
1068
|
+
const radius = this.resolveCutShapeRadius(sceneGeometry, frame);
|
|
1069
|
+
|
|
1070
|
+
this.debug("overlay:shape:geometry", {
|
|
1071
|
+
shape,
|
|
1072
|
+
frameWidth: frame.width,
|
|
1073
|
+
frameHeight: frame.height,
|
|
1074
|
+
offset: sceneGeometry.offset,
|
|
1075
|
+
inset,
|
|
1076
|
+
shapeWidth,
|
|
1077
|
+
shapeHeight,
|
|
1078
|
+
baseRadius: sceneGeometry.radius,
|
|
1079
|
+
radius,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
const isSameAsFrame =
|
|
1083
|
+
Math.abs(shapeWidth - frame.width) <= 0.0001 &&
|
|
1084
|
+
Math.abs(shapeHeight - frame.height) <= 0.0001;
|
|
1085
|
+
if (shape === "rect" && radius <= 0.0001 && isSameAsFrame) {
|
|
1086
|
+
this.debug("overlay:shape:skip", {
|
|
1087
|
+
reason: "shape-rect-no-radius",
|
|
1088
|
+
});
|
|
1089
|
+
return [];
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
const baseOptions = {
|
|
1093
|
+
shape,
|
|
1094
|
+
width: shapeWidth,
|
|
1095
|
+
height: shapeHeight,
|
|
1096
|
+
radius,
|
|
1097
|
+
x: frame.width / 2,
|
|
1098
|
+
y: frame.height / 2,
|
|
1099
|
+
features: [],
|
|
1100
|
+
canvasWidth: frame.width,
|
|
1101
|
+
canvasHeight: frame.height,
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
try {
|
|
1105
|
+
const shapePathData = generateDielinePath(baseOptions);
|
|
1106
|
+
const outerRectPathData = `M 0 0 L ${frame.width} 0 L ${frame.width} ${frame.height} L 0 ${frame.height} Z`;
|
|
1107
|
+
const hatchPathData = `${outerRectPathData} ${shapePathData}`;
|
|
1108
|
+
if (!shapePathData || !hatchPathData) {
|
|
1109
|
+
this.debug("overlay:shape:skip", {
|
|
1110
|
+
reason: "path-generation-empty",
|
|
1111
|
+
shape,
|
|
1112
|
+
radius,
|
|
1113
|
+
});
|
|
1114
|
+
return [];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const patternFill = this.getCropShapeHatchPattern();
|
|
1118
|
+
const hatchFill = patternFill || "rgba(255, 0, 0, 0.22)";
|
|
1119
|
+
const hatchPathLength = hatchPathData.length;
|
|
1120
|
+
const shapePathLength = shapePathData.length;
|
|
1121
|
+
const specs: RenderObjectSpec[] = [
|
|
1122
|
+
{
|
|
1123
|
+
id: "image.cropShapeHatch",
|
|
1124
|
+
type: "path",
|
|
1125
|
+
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
1126
|
+
props: {
|
|
1127
|
+
pathData: hatchPathData,
|
|
1128
|
+
left: frame.left,
|
|
1129
|
+
top: frame.top,
|
|
1130
|
+
originX: "left",
|
|
1131
|
+
originY: "top",
|
|
1132
|
+
fill: hatchFill,
|
|
1133
|
+
opacity: patternFill ? 1 : 0.8,
|
|
1134
|
+
stroke: null,
|
|
1135
|
+
fillRule: "evenodd",
|
|
1136
|
+
selectable: false,
|
|
1137
|
+
evented: false,
|
|
1138
|
+
excludeFromExport: true,
|
|
1139
|
+
objectCaching: false,
|
|
1140
|
+
},
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
id: "image.cropShapePath",
|
|
1144
|
+
type: "path",
|
|
1145
|
+
data: { id: "image.cropShapePath", zIndex: 6 },
|
|
1146
|
+
props: {
|
|
1147
|
+
pathData: shapePathData,
|
|
1148
|
+
left: frame.left,
|
|
1149
|
+
top: frame.top,
|
|
1150
|
+
originX: "left",
|
|
1151
|
+
originY: "top",
|
|
1152
|
+
fill: "rgba(0,0,0,0)",
|
|
1153
|
+
stroke: "rgba(255, 0, 0, 0.9)",
|
|
1154
|
+
strokeWidth: 1,
|
|
1155
|
+
selectable: false,
|
|
1156
|
+
evented: false,
|
|
1157
|
+
excludeFromExport: true,
|
|
1158
|
+
objectCaching: false,
|
|
1159
|
+
},
|
|
1160
|
+
},
|
|
1161
|
+
];
|
|
1162
|
+
this.debug("overlay:shape:built", {
|
|
1163
|
+
shape,
|
|
1164
|
+
radius,
|
|
1165
|
+
inset,
|
|
1166
|
+
shapeWidth,
|
|
1167
|
+
shapeHeight,
|
|
1168
|
+
fillRule: "evenodd",
|
|
1169
|
+
shapePathLength,
|
|
1170
|
+
hatchPathLength,
|
|
1171
|
+
hatchFillType:
|
|
1172
|
+
hatchFill && typeof hatchFill === "object" ? "pattern" : "color",
|
|
1173
|
+
ids: specs.map((spec) => spec.id),
|
|
1174
|
+
});
|
|
1175
|
+
return specs;
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
this.debug("overlay:shape:error", {
|
|
1178
|
+
shape,
|
|
1179
|
+
radius,
|
|
1180
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1181
|
+
});
|
|
1182
|
+
return [];
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
901
1186
|
private resolveRenderImageState(item: ImageItem): RenderImageState {
|
|
902
1187
|
const active = this.isToolActive;
|
|
903
1188
|
const sourceUrl = item.sourceUrl || item.url;
|
|
@@ -962,6 +1247,21 @@ export class ImageTool implements Extension {
|
|
|
962
1247
|
return obj?._originalElement?.src;
|
|
963
1248
|
}
|
|
964
1249
|
|
|
1250
|
+
private applyImageControlVisibility(obj: any) {
|
|
1251
|
+
if (typeof obj?.setControlsVisibility !== "function") return;
|
|
1252
|
+
obj.setControlsVisibility({
|
|
1253
|
+
mt: false,
|
|
1254
|
+
mb: false,
|
|
1255
|
+
ml: false,
|
|
1256
|
+
mr: false,
|
|
1257
|
+
tl: true,
|
|
1258
|
+
tr: true,
|
|
1259
|
+
bl: true,
|
|
1260
|
+
br: true,
|
|
1261
|
+
mtr: true,
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
965
1265
|
private async upsertImageObject(
|
|
966
1266
|
item: ImageItem,
|
|
967
1267
|
frame: FrameRect,
|
|
@@ -1010,6 +1310,7 @@ export class ImageTool implements Extension {
|
|
|
1010
1310
|
type: "image-item",
|
|
1011
1311
|
},
|
|
1012
1312
|
});
|
|
1313
|
+
this.applyImageControlVisibility(obj);
|
|
1013
1314
|
obj.setCoords();
|
|
1014
1315
|
|
|
1015
1316
|
const resolver = this.loadResolvers.get(item.id);
|
|
@@ -1047,9 +1348,25 @@ export class ImageTool implements Extension {
|
|
|
1047
1348
|
overlayObjects.forEach((obj) => {
|
|
1048
1349
|
canvas.bringObjectToFront(obj);
|
|
1049
1350
|
});
|
|
1351
|
+
|
|
1352
|
+
if (this.isDebugEnabled()) {
|
|
1353
|
+
const stack = canvas
|
|
1354
|
+
.getObjects()
|
|
1355
|
+
.map((obj: any, index: number) => ({
|
|
1356
|
+
index,
|
|
1357
|
+
id: obj?.data?.id,
|
|
1358
|
+
layerId: obj?.data?.layerId,
|
|
1359
|
+
zIndex: obj?.data?.zIndex,
|
|
1360
|
+
}))
|
|
1361
|
+
.filter((item) => item.layerId === IMAGE_OVERLAY_LAYER_ID);
|
|
1362
|
+
this.debug("overlay:stack", stack);
|
|
1363
|
+
}
|
|
1050
1364
|
}
|
|
1051
1365
|
|
|
1052
|
-
private buildOverlaySpecs(
|
|
1366
|
+
private buildOverlaySpecs(
|
|
1367
|
+
frame: FrameRect,
|
|
1368
|
+
sceneGeometry: SceneGeometryLike | null,
|
|
1369
|
+
): RenderObjectSpec[] {
|
|
1053
1370
|
const visible = this.isImageEditingVisible();
|
|
1054
1371
|
if (
|
|
1055
1372
|
!visible ||
|
|
@@ -1087,6 +1404,7 @@ export class ImageTool implements Extension {
|
|
|
1087
1404
|
const bottomH = Math.max(0, canvasH - frameBottom);
|
|
1088
1405
|
const leftW = frameLeft;
|
|
1089
1406
|
const rightW = Math.max(0, canvasW - frameRight);
|
|
1407
|
+
const shapeOverlay = this.buildCropShapeOverlaySpecs(frame, sceneGeometry);
|
|
1090
1408
|
|
|
1091
1409
|
const mask: RenderObjectSpec[] = [
|
|
1092
1410
|
{
|
|
@@ -1158,7 +1476,7 @@ export class ImageTool implements Extension {
|
|
|
1158
1476
|
const frameSpec: RenderObjectSpec = {
|
|
1159
1477
|
id: "image.cropFrame",
|
|
1160
1478
|
type: "rect",
|
|
1161
|
-
data: { id: "image.cropFrame", zIndex:
|
|
1479
|
+
data: { id: "image.cropFrame", zIndex: 7 },
|
|
1162
1480
|
props: {
|
|
1163
1481
|
left: frame.left + frame.width / 2,
|
|
1164
1482
|
top: frame.top + frame.height / 2,
|
|
@@ -1181,7 +1499,16 @@ export class ImageTool implements Extension {
|
|
|
1181
1499
|
},
|
|
1182
1500
|
};
|
|
1183
1501
|
|
|
1184
|
-
|
|
1502
|
+
const specs = [...mask, ...shapeOverlay, frameSpec];
|
|
1503
|
+
this.debug("overlay:built", {
|
|
1504
|
+
frame,
|
|
1505
|
+
shape: sceneGeometry?.shape,
|
|
1506
|
+
overlayIds: specs.map((spec) => ({
|
|
1507
|
+
id: spec.id,
|
|
1508
|
+
zIndex: spec.data?.zIndex,
|
|
1509
|
+
})),
|
|
1510
|
+
});
|
|
1511
|
+
return specs;
|
|
1185
1512
|
}
|
|
1186
1513
|
|
|
1187
1514
|
private updateImages() {
|
|
@@ -1217,7 +1544,10 @@ export class ImageTool implements Extension {
|
|
|
1217
1544
|
if (seq !== this.renderSeq) return;
|
|
1218
1545
|
|
|
1219
1546
|
this.syncImageZOrder(renderItems);
|
|
1220
|
-
const
|
|
1547
|
+
const sceneGeometry = await this.resolveSceneGeometryForOverlay();
|
|
1548
|
+
if (seq !== this.renderSeq) return;
|
|
1549
|
+
|
|
1550
|
+
const overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
|
|
1221
1551
|
await this.canvasService.applyObjectSpecsToRootLayer(
|
|
1222
1552
|
IMAGE_OVERLAY_LAYER_ID,
|
|
1223
1553
|
overlaySpecs,
|