@pooder/kit 6.2.0 → 6.2.2
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/extensions/dieline/DielineTool.js +22 -9
- package/.test-dist/src/extensions/dieline/renderBuilder.js +47 -14
- package/.test-dist/src/extensions/feature/FeatureTool.js +20 -3
- package/.test-dist/src/extensions/featureCoordinates.js +21 -0
- package/.test-dist/src/extensions/featurePlacement.js +46 -0
- package/.test-dist/src/extensions/image/ImageTool.js +46 -348
- package/.test-dist/src/extensions/image/sessionOverlay.js +148 -0
- package/.test-dist/src/extensions/ruler/RulerTool.js +25 -2
- package/.test-dist/tests/run.js +25 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +4 -5
- package/dist/index.d.ts +4 -5
- package/dist/index.js +1506 -1494
- package/dist/index.mjs +1506 -1494
- package/package.json +1 -1
- package/src/extensions/dieline/DielineTool.ts +29 -9
- package/src/extensions/dieline/renderBuilder.ts +65 -17
- package/src/extensions/feature/FeatureTool.ts +23 -3
- package/src/extensions/featureCoordinates.ts +35 -0
- package/src/extensions/featurePlacement.ts +118 -0
- package/src/extensions/image/ImageTool.ts +57 -412
- package/src/extensions/image/sessionOverlay.ts +206 -0
- package/src/extensions/ruler/RulerTool.ts +24 -2
- package/tests/run.ts +37 -0
|
@@ -3,8 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ImageTool = void 0;
|
|
4
4
|
const core_1 = require("@pooder/core");
|
|
5
5
|
const fabric_1 = require("fabric");
|
|
6
|
-
const dielineShape_1 = require("../dielineShape");
|
|
7
|
-
const geometry_1 = require("../geometry");
|
|
8
6
|
const sceneLayoutModel_1 = require("../../shared/scene/sceneLayoutModel");
|
|
9
7
|
const frame_1 = require("../../shared/scene/frame");
|
|
10
8
|
const sourceSizeCache_1 = require("../../shared/imaging/sourceSizeCache");
|
|
@@ -13,6 +11,7 @@ const sessionState_1 = require("../../shared/runtime/sessionState");
|
|
|
13
11
|
const layers_1 = require("../../shared/constants/layers");
|
|
14
12
|
const commands_1 = require("./commands");
|
|
15
13
|
const config_1 = require("./config");
|
|
14
|
+
const sessionOverlay_1 = require("./sessionOverlay");
|
|
16
15
|
const IMAGE_DEFAULT_CONTROL_CAPABILITIES = [
|
|
17
16
|
"rotate",
|
|
18
17
|
"scale",
|
|
@@ -427,10 +426,20 @@ class ImageTool {
|
|
|
427
426
|
this.canvasService?.requestRenderAll();
|
|
428
427
|
}
|
|
429
428
|
}
|
|
429
|
+
clearSnapGuideContext() {
|
|
430
|
+
const topContext = this.canvasService?.canvas.contextTop;
|
|
431
|
+
if (!this.canvasService || !topContext)
|
|
432
|
+
return;
|
|
433
|
+
this.canvasService.canvas.clearContext(topContext);
|
|
434
|
+
}
|
|
430
435
|
clearSnapPreview() {
|
|
436
|
+
const shouldClearCanvas = this.hasRenderedSnapGuides || !!this.activeSnapX || !!this.activeSnapY;
|
|
431
437
|
this.activeSnapX = null;
|
|
432
438
|
this.activeSnapY = null;
|
|
433
439
|
this.hasRenderedSnapGuides = false;
|
|
440
|
+
if (shouldClearCanvas) {
|
|
441
|
+
this.clearSnapGuideContext();
|
|
442
|
+
}
|
|
434
443
|
this.canvasService?.requestRenderAll();
|
|
435
444
|
}
|
|
436
445
|
endMoveSnapInteraction() {
|
|
@@ -816,9 +825,6 @@ class ImageTool {
|
|
|
816
825
|
}
|
|
817
826
|
return this.canvasService.toScreenRect(frame || this.getFrameRect());
|
|
818
827
|
}
|
|
819
|
-
toLayoutSceneRect(rect) {
|
|
820
|
-
return (0, frame_1.toLayoutSceneRect)(rect);
|
|
821
|
-
}
|
|
822
828
|
async resolveDefaultFitArea() {
|
|
823
829
|
if (!this.canvasService)
|
|
824
830
|
return null;
|
|
@@ -941,78 +947,33 @@ class ImageTool {
|
|
|
941
947
|
"#f5f5f5",
|
|
942
948
|
};
|
|
943
949
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
if (!(0, dielineShape_1.isDielineShape)(shape)) {
|
|
950
|
+
resolveSessionOverlayState() {
|
|
951
|
+
if (!this.canvasService || !this.context) {
|
|
947
952
|
return null;
|
|
948
953
|
}
|
|
949
|
-
const radiusRaw = Number(raw?.radius);
|
|
950
|
-
const offsetRaw = Number(raw?.offset);
|
|
951
|
-
const unit = typeof raw?.unit === "string" ? raw.unit : "px";
|
|
952
|
-
const radius = unit === "scene" || !this.canvasService
|
|
953
|
-
? radiusRaw
|
|
954
|
-
: this.canvasService.toSceneLength(radiusRaw);
|
|
955
|
-
const offset = unit === "scene" || !this.canvasService
|
|
956
|
-
? offsetRaw
|
|
957
|
-
: this.canvasService.toSceneLength(offsetRaw);
|
|
958
|
-
return {
|
|
959
|
-
shape,
|
|
960
|
-
shapeStyle: (0, dielineShape_1.normalizeShapeStyle)(raw?.shapeStyle),
|
|
961
|
-
radius: Number.isFinite(radius) ? radius : 0,
|
|
962
|
-
offset: Number.isFinite(offset) ? offset : 0,
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
async resolveSceneGeometryForOverlay() {
|
|
966
|
-
if (!this.context)
|
|
967
|
-
return null;
|
|
968
|
-
const commandService = this.context.services.get("CommandService");
|
|
969
|
-
if (commandService) {
|
|
970
|
-
try {
|
|
971
|
-
const raw = await Promise.resolve(commandService.executeCommand("getSceneGeometry"));
|
|
972
|
-
const geometry = this.toSceneGeometryLike(raw);
|
|
973
|
-
if (geometry) {
|
|
974
|
-
this.debug("overlay:sceneGeometry:command", geometry);
|
|
975
|
-
return geometry;
|
|
976
|
-
}
|
|
977
|
-
this.debug("overlay:sceneGeometry:command:invalid", { raw });
|
|
978
|
-
}
|
|
979
|
-
catch (error) {
|
|
980
|
-
this.debug("overlay:sceneGeometry:command:error", {
|
|
981
|
-
error: error instanceof Error ? error.message : String(error),
|
|
982
|
-
});
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
if (!this.canvasService)
|
|
986
|
-
return null;
|
|
987
954
|
const configService = this.context.services.get("ConfigurationService");
|
|
988
|
-
if (!configService)
|
|
955
|
+
if (!configService) {
|
|
989
956
|
return null;
|
|
990
|
-
|
|
991
|
-
const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService,
|
|
957
|
+
}
|
|
958
|
+
const layout = (0, sceneLayoutModel_1.computeSceneLayout)(this.canvasService, (0, sceneLayoutModel_1.readSizeState)(configService));
|
|
992
959
|
if (!layout) {
|
|
993
|
-
this.debug("overlay:
|
|
960
|
+
this.debug("overlay:layout:missing");
|
|
994
961
|
return null;
|
|
995
962
|
}
|
|
996
|
-
const geometry =
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
: 0;
|
|
1006
|
-
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
1007
|
-
const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
1008
|
-
const maxRadius = Math.max(0, Math.min(frame.width, frame.height) / 2);
|
|
1009
|
-
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
963
|
+
const geometry = (0, sceneLayoutModel_1.buildSceneGeometry)(configService, layout);
|
|
964
|
+
this.debug("overlay:state:resolved", {
|
|
965
|
+
cutRect: layout.cutRect,
|
|
966
|
+
shape: geometry.shape,
|
|
967
|
+
shapeStyle: geometry.shapeStyle,
|
|
968
|
+
radius: geometry.radius,
|
|
969
|
+
offset: geometry.offset,
|
|
970
|
+
});
|
|
971
|
+
return { layout, geometry };
|
|
1010
972
|
}
|
|
1011
973
|
getCropShapeHatchPattern(color = "rgba(255, 0, 0, 0.6)") {
|
|
1012
974
|
if (typeof document === "undefined")
|
|
1013
975
|
return undefined;
|
|
1014
|
-
const
|
|
1015
|
-
const cacheKey = `${color}::${sceneScale.toFixed(6)}`;
|
|
976
|
+
const cacheKey = color;
|
|
1016
977
|
if (this.cropShapeHatchPattern &&
|
|
1017
978
|
this.cropShapeHatchPatternColor === color &&
|
|
1018
979
|
this.cropShapeHatchPatternKey === cacheKey) {
|
|
@@ -1045,140 +1006,11 @@ class ImageTool {
|
|
|
1045
1006
|
// @ts-ignore: Fabric Pattern accepts canvas source here.
|
|
1046
1007
|
repetition: "repeat",
|
|
1047
1008
|
});
|
|
1048
|
-
// Scene specs are scaled to screen by CanvasService; keep hatch density in screen pixels.
|
|
1049
|
-
pattern.patternTransform = [
|
|
1050
|
-
1 / sceneScale,
|
|
1051
|
-
0,
|
|
1052
|
-
0,
|
|
1053
|
-
1 / sceneScale,
|
|
1054
|
-
0,
|
|
1055
|
-
0,
|
|
1056
|
-
];
|
|
1057
1009
|
this.cropShapeHatchPattern = pattern;
|
|
1058
1010
|
this.cropShapeHatchPatternColor = color;
|
|
1059
1011
|
this.cropShapeHatchPatternKey = cacheKey;
|
|
1060
1012
|
return pattern;
|
|
1061
1013
|
}
|
|
1062
|
-
buildCropShapeOverlaySpecs(frame, sceneGeometry) {
|
|
1063
|
-
if (!sceneGeometry) {
|
|
1064
|
-
this.debug("overlay:shape:skip", { reason: "scene-geometry-missing" });
|
|
1065
|
-
return [];
|
|
1066
|
-
}
|
|
1067
|
-
if (sceneGeometry.shape === "custom") {
|
|
1068
|
-
this.debug("overlay:shape:skip", { reason: "shape-custom" });
|
|
1069
|
-
return [];
|
|
1070
|
-
}
|
|
1071
|
-
const shape = sceneGeometry.shape;
|
|
1072
|
-
const shapeStyle = sceneGeometry.shapeStyle;
|
|
1073
|
-
const inset = 0;
|
|
1074
|
-
const shapeWidth = Math.max(1, frame.width);
|
|
1075
|
-
const shapeHeight = Math.max(1, frame.height);
|
|
1076
|
-
const radius = this.resolveCutShapeRadius(sceneGeometry, frame);
|
|
1077
|
-
this.debug("overlay:shape:geometry", {
|
|
1078
|
-
shape,
|
|
1079
|
-
frameWidth: frame.width,
|
|
1080
|
-
frameHeight: frame.height,
|
|
1081
|
-
offset: sceneGeometry.offset,
|
|
1082
|
-
shapeStyle,
|
|
1083
|
-
inset,
|
|
1084
|
-
shapeWidth,
|
|
1085
|
-
shapeHeight,
|
|
1086
|
-
baseRadius: sceneGeometry.radius,
|
|
1087
|
-
radius,
|
|
1088
|
-
});
|
|
1089
|
-
const isSameAsFrame = Math.abs(shapeWidth - frame.width) <= 0.0001 &&
|
|
1090
|
-
Math.abs(shapeHeight - frame.height) <= 0.0001;
|
|
1091
|
-
if (shape === "rect" && radius <= 0.0001 && isSameAsFrame) {
|
|
1092
|
-
this.debug("overlay:shape:skip", {
|
|
1093
|
-
reason: "shape-rect-no-radius",
|
|
1094
|
-
});
|
|
1095
|
-
return [];
|
|
1096
|
-
}
|
|
1097
|
-
const baseOptions = {
|
|
1098
|
-
shape,
|
|
1099
|
-
width: shapeWidth,
|
|
1100
|
-
height: shapeHeight,
|
|
1101
|
-
radius,
|
|
1102
|
-
x: frame.width / 2,
|
|
1103
|
-
y: frame.height / 2,
|
|
1104
|
-
features: [],
|
|
1105
|
-
shapeStyle,
|
|
1106
|
-
canvasWidth: frame.width,
|
|
1107
|
-
canvasHeight: frame.height,
|
|
1108
|
-
};
|
|
1109
|
-
try {
|
|
1110
|
-
const shapePathData = (0, geometry_1.generateDielinePath)(baseOptions);
|
|
1111
|
-
const outerRectPathData = `M 0 0 L ${frame.width} 0 L ${frame.width} ${frame.height} L 0 ${frame.height} Z`;
|
|
1112
|
-
const hatchPathData = `${outerRectPathData} ${shapePathData}`;
|
|
1113
|
-
if (!shapePathData || !hatchPathData) {
|
|
1114
|
-
this.debug("overlay:shape:skip", {
|
|
1115
|
-
reason: "path-generation-empty",
|
|
1116
|
-
shape,
|
|
1117
|
-
radius,
|
|
1118
|
-
});
|
|
1119
|
-
return [];
|
|
1120
|
-
}
|
|
1121
|
-
const patternFill = this.getCropShapeHatchPattern();
|
|
1122
|
-
const hatchFill = patternFill || "rgba(255, 0, 0, 0.22)";
|
|
1123
|
-
const shapeBounds = (0, geometry_1.getPathBounds)(shapePathData);
|
|
1124
|
-
const hatchBounds = (0, geometry_1.getPathBounds)(hatchPathData);
|
|
1125
|
-
const frameRect = this.toLayoutSceneRect(frame);
|
|
1126
|
-
const hatchPathLength = hatchPathData.length;
|
|
1127
|
-
const shapePathLength = shapePathData.length;
|
|
1128
|
-
const specs = [
|
|
1129
|
-
{
|
|
1130
|
-
id: "image.cropShapeHatch",
|
|
1131
|
-
type: "path",
|
|
1132
|
-
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
1133
|
-
layout: {
|
|
1134
|
-
reference: "custom",
|
|
1135
|
-
referenceRect: frameRect,
|
|
1136
|
-
alignX: "start",
|
|
1137
|
-
alignY: "start",
|
|
1138
|
-
offsetX: hatchBounds.x,
|
|
1139
|
-
offsetY: hatchBounds.y,
|
|
1140
|
-
},
|
|
1141
|
-
props: {
|
|
1142
|
-
pathData: hatchPathData,
|
|
1143
|
-
originX: "left",
|
|
1144
|
-
originY: "top",
|
|
1145
|
-
fill: hatchFill,
|
|
1146
|
-
opacity: patternFill ? 1 : 0.8,
|
|
1147
|
-
stroke: "rgba(255, 0, 0, 0.9)",
|
|
1148
|
-
strokeWidth: this.canvasService?.toSceneLength(1) ?? 1,
|
|
1149
|
-
fillRule: "evenodd",
|
|
1150
|
-
selectable: false,
|
|
1151
|
-
evented: false,
|
|
1152
|
-
excludeFromExport: true,
|
|
1153
|
-
objectCaching: false,
|
|
1154
|
-
},
|
|
1155
|
-
},
|
|
1156
|
-
];
|
|
1157
|
-
this.debug("overlay:shape:built", {
|
|
1158
|
-
shape,
|
|
1159
|
-
radius,
|
|
1160
|
-
inset,
|
|
1161
|
-
shapeWidth,
|
|
1162
|
-
shapeHeight,
|
|
1163
|
-
fillRule: "evenodd",
|
|
1164
|
-
shapePathLength,
|
|
1165
|
-
hatchPathLength,
|
|
1166
|
-
shapeBounds,
|
|
1167
|
-
hatchBounds,
|
|
1168
|
-
hatchFillType: hatchFill && typeof hatchFill === "object" ? "pattern" : "color",
|
|
1169
|
-
ids: specs.map((spec) => spec.id),
|
|
1170
|
-
});
|
|
1171
|
-
return specs;
|
|
1172
|
-
}
|
|
1173
|
-
catch (error) {
|
|
1174
|
-
this.debug("overlay:shape:error", {
|
|
1175
|
-
shape,
|
|
1176
|
-
radius,
|
|
1177
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1178
|
-
});
|
|
1179
|
-
return [];
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
1014
|
resolveRenderImageState(item) {
|
|
1183
1015
|
const active = this.isToolActive;
|
|
1184
1016
|
const sourceUrl = item.sourceUrl || item.url;
|
|
@@ -1262,167 +1094,35 @@ class ImageTool {
|
|
|
1262
1094
|
}
|
|
1263
1095
|
return specs;
|
|
1264
1096
|
}
|
|
1265
|
-
buildOverlaySpecs(
|
|
1097
|
+
buildOverlaySpecs(overlayState) {
|
|
1266
1098
|
const visible = this.isImageEditingVisible();
|
|
1267
|
-
if (!visible ||
|
|
1268
|
-
frame.width <= 0 ||
|
|
1269
|
-
frame.height <= 0 ||
|
|
1270
|
-
!this.canvasService) {
|
|
1099
|
+
if (!visible || !overlayState || !this.canvasService) {
|
|
1271
1100
|
this.debug("overlay:hidden", {
|
|
1272
1101
|
visible,
|
|
1273
|
-
|
|
1102
|
+
cutRect: overlayState?.layout.cutRect,
|
|
1274
1103
|
isToolActive: this.isToolActive,
|
|
1275
1104
|
isImageSelectionActive: this.isImageSelectionActive,
|
|
1276
1105
|
focusedImageId: this.focusedImageId,
|
|
1277
1106
|
});
|
|
1278
1107
|
return [];
|
|
1279
1108
|
}
|
|
1280
|
-
const viewport = this.canvasService.
|
|
1281
|
-
const canvasW = viewport.width || 0;
|
|
1282
|
-
const canvasH = viewport.height || 0;
|
|
1283
|
-
const canvasLeft = viewport.left || 0;
|
|
1284
|
-
const canvasTop = viewport.top || 0;
|
|
1109
|
+
const viewport = this.canvasService.getScreenViewportRect();
|
|
1285
1110
|
const visual = this.getFrameVisualConfig();
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
const visibleFrameH = Math.max(0, frameBottom - frameTop);
|
|
1293
|
-
const topH = Math.max(0, frameTop - canvasTop);
|
|
1294
|
-
const bottomH = Math.max(0, canvasTop + canvasH - frameBottom);
|
|
1295
|
-
const leftW = Math.max(0, frameLeft - canvasLeft);
|
|
1296
|
-
const rightW = Math.max(0, canvasLeft + canvasW - frameRight);
|
|
1297
|
-
const viewportRect = this.toLayoutSceneRect({
|
|
1298
|
-
left: canvasLeft,
|
|
1299
|
-
top: canvasTop,
|
|
1300
|
-
width: canvasW,
|
|
1301
|
-
height: canvasH,
|
|
1302
|
-
});
|
|
1303
|
-
const visibleFrameBandRect = this.toLayoutSceneRect({
|
|
1304
|
-
left: canvasLeft,
|
|
1305
|
-
top: frameTop,
|
|
1306
|
-
width: canvasW,
|
|
1307
|
-
height: visibleFrameH,
|
|
1308
|
-
});
|
|
1309
|
-
const frameRect = this.toLayoutSceneRect(frame);
|
|
1310
|
-
const shapeOverlay = this.buildCropShapeOverlaySpecs(frame, sceneGeometry);
|
|
1311
|
-
const mask = [
|
|
1312
|
-
{
|
|
1313
|
-
id: "image.cropMask.top",
|
|
1314
|
-
type: "rect",
|
|
1315
|
-
data: { id: "image.cropMask.top", zIndex: 1 },
|
|
1316
|
-
layout: {
|
|
1317
|
-
reference: "custom",
|
|
1318
|
-
referenceRect: viewportRect,
|
|
1319
|
-
alignX: "start",
|
|
1320
|
-
alignY: "start",
|
|
1321
|
-
width: "100%",
|
|
1322
|
-
height: topH,
|
|
1323
|
-
},
|
|
1324
|
-
props: {
|
|
1325
|
-
originX: "left",
|
|
1326
|
-
originY: "top",
|
|
1327
|
-
fill: visual.outerBackground,
|
|
1328
|
-
selectable: false,
|
|
1329
|
-
evented: false,
|
|
1330
|
-
},
|
|
1111
|
+
const specs = (0, sessionOverlay_1.buildImageSessionOverlaySpecs)({
|
|
1112
|
+
viewport: {
|
|
1113
|
+
left: viewport.left,
|
|
1114
|
+
top: viewport.top,
|
|
1115
|
+
width: viewport.width,
|
|
1116
|
+
height: viewport.height,
|
|
1331
1117
|
},
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
reference: "custom",
|
|
1338
|
-
referenceRect: viewportRect,
|
|
1339
|
-
alignX: "start",
|
|
1340
|
-
alignY: "end",
|
|
1341
|
-
width: "100%",
|
|
1342
|
-
height: bottomH,
|
|
1343
|
-
},
|
|
1344
|
-
props: {
|
|
1345
|
-
originX: "left",
|
|
1346
|
-
originY: "top",
|
|
1347
|
-
fill: visual.outerBackground,
|
|
1348
|
-
selectable: false,
|
|
1349
|
-
evented: false,
|
|
1350
|
-
},
|
|
1351
|
-
},
|
|
1352
|
-
{
|
|
1353
|
-
id: "image.cropMask.left",
|
|
1354
|
-
type: "rect",
|
|
1355
|
-
data: { id: "image.cropMask.left", zIndex: 3 },
|
|
1356
|
-
layout: {
|
|
1357
|
-
reference: "custom",
|
|
1358
|
-
referenceRect: visibleFrameBandRect,
|
|
1359
|
-
alignX: "start",
|
|
1360
|
-
alignY: "start",
|
|
1361
|
-
width: leftW,
|
|
1362
|
-
height: "100%",
|
|
1363
|
-
},
|
|
1364
|
-
props: {
|
|
1365
|
-
originX: "left",
|
|
1366
|
-
originY: "top",
|
|
1367
|
-
fill: visual.outerBackground,
|
|
1368
|
-
selectable: false,
|
|
1369
|
-
evented: false,
|
|
1370
|
-
},
|
|
1371
|
-
},
|
|
1372
|
-
{
|
|
1373
|
-
id: "image.cropMask.right",
|
|
1374
|
-
type: "rect",
|
|
1375
|
-
data: { id: "image.cropMask.right", zIndex: 4 },
|
|
1376
|
-
layout: {
|
|
1377
|
-
reference: "custom",
|
|
1378
|
-
referenceRect: visibleFrameBandRect,
|
|
1379
|
-
alignX: "end",
|
|
1380
|
-
alignY: "start",
|
|
1381
|
-
width: rightW,
|
|
1382
|
-
height: "100%",
|
|
1383
|
-
},
|
|
1384
|
-
props: {
|
|
1385
|
-
originX: "left",
|
|
1386
|
-
originY: "top",
|
|
1387
|
-
fill: visual.outerBackground,
|
|
1388
|
-
selectable: false,
|
|
1389
|
-
evented: false,
|
|
1390
|
-
},
|
|
1391
|
-
},
|
|
1392
|
-
];
|
|
1393
|
-
const frameSpec = {
|
|
1394
|
-
id: "image.cropFrame",
|
|
1395
|
-
type: "rect",
|
|
1396
|
-
data: { id: "image.cropFrame", zIndex: 7 },
|
|
1397
|
-
layout: {
|
|
1398
|
-
reference: "custom",
|
|
1399
|
-
referenceRect: frameRect,
|
|
1400
|
-
alignX: "start",
|
|
1401
|
-
alignY: "start",
|
|
1402
|
-
width: "100%",
|
|
1403
|
-
height: "100%",
|
|
1404
|
-
},
|
|
1405
|
-
props: {
|
|
1406
|
-
originX: "left",
|
|
1407
|
-
originY: "top",
|
|
1408
|
-
fill: visual.innerBackground,
|
|
1409
|
-
stroke: visual.strokeStyle === "hidden"
|
|
1410
|
-
? "rgba(0,0,0,0)"
|
|
1411
|
-
: visual.strokeColor,
|
|
1412
|
-
strokeWidth: visual.strokeStyle === "hidden" ? 0 : strokeWidthScene,
|
|
1413
|
-
strokeDashArray: visual.strokeStyle === "dashed"
|
|
1414
|
-
? [dashLengthScene, dashLengthScene]
|
|
1415
|
-
: undefined,
|
|
1416
|
-
selectable: false,
|
|
1417
|
-
evented: false,
|
|
1418
|
-
},
|
|
1419
|
-
};
|
|
1420
|
-
const specs = shapeOverlay.length > 0
|
|
1421
|
-
? [...mask, ...shapeOverlay]
|
|
1422
|
-
: [...mask, ...shapeOverlay, frameSpec];
|
|
1118
|
+
layout: overlayState.layout,
|
|
1119
|
+
geometry: overlayState.geometry,
|
|
1120
|
+
visual,
|
|
1121
|
+
hatchPattern: this.getCropShapeHatchPattern(),
|
|
1122
|
+
});
|
|
1423
1123
|
this.debug("overlay:built", {
|
|
1424
|
-
|
|
1425
|
-
shape:
|
|
1124
|
+
cutRect: overlayState.layout.cutRect,
|
|
1125
|
+
shape: overlayState.geometry.shape,
|
|
1426
1126
|
overlayIds: specs.map((spec) => ({
|
|
1427
1127
|
id: spec.id,
|
|
1428
1128
|
zIndex: spec.data?.zIndex,
|
|
@@ -1450,11 +1150,9 @@ class ImageTool {
|
|
|
1450
1150
|
const imageSpecs = await this.buildImageSpecs(renderItems, frame);
|
|
1451
1151
|
if (seq !== this.renderSeq)
|
|
1452
1152
|
return;
|
|
1453
|
-
const
|
|
1454
|
-
if (seq !== this.renderSeq)
|
|
1455
|
-
return;
|
|
1153
|
+
const overlayState = this.resolveSessionOverlayState();
|
|
1456
1154
|
this.imageSpecs = imageSpecs;
|
|
1457
|
-
this.overlaySpecs = this.buildOverlaySpecs(
|
|
1155
|
+
this.overlaySpecs = this.buildOverlaySpecs(overlayState);
|
|
1458
1156
|
await this.canvasService.flushRenderFromProducers();
|
|
1459
1157
|
if (seq !== this.renderSeq)
|
|
1460
1158
|
return;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildImageSessionOverlaySpecs = buildImageSessionOverlaySpecs;
|
|
4
|
+
const geometry_1 = require("../geometry");
|
|
5
|
+
const EPSILON = 0.0001;
|
|
6
|
+
const SHAPE_OUTLINE_COLOR = "rgba(255, 0, 0, 0.9)";
|
|
7
|
+
const DEFAULT_HATCH_FILL = "rgba(255, 0, 0, 0.22)";
|
|
8
|
+
function buildRectPath(width, height) {
|
|
9
|
+
return `M 0 0 L ${width} 0 L ${width} ${height} L 0 ${height} Z`;
|
|
10
|
+
}
|
|
11
|
+
function buildViewportMaskPath(viewport, cutRect) {
|
|
12
|
+
const cutLeft = cutRect.left - viewport.left;
|
|
13
|
+
const cutTop = cutRect.top - viewport.top;
|
|
14
|
+
return [
|
|
15
|
+
buildRectPath(viewport.width, viewport.height),
|
|
16
|
+
`M ${cutLeft} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop} L ${cutLeft + cutRect.width} ${cutTop + cutRect.height} L ${cutLeft} ${cutTop + cutRect.height} Z`,
|
|
17
|
+
].join(" ");
|
|
18
|
+
}
|
|
19
|
+
function resolveCutShapeRadiusPx(geometry, cutRect) {
|
|
20
|
+
const visualRadius = Number.isFinite(geometry.radius)
|
|
21
|
+
? Math.max(0, geometry.radius)
|
|
22
|
+
: 0;
|
|
23
|
+
const visualOffset = Number.isFinite(geometry.offset) ? geometry.offset : 0;
|
|
24
|
+
const rawCutRadius = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
25
|
+
const maxRadius = Math.max(0, Math.min(cutRect.width, cutRect.height) / 2);
|
|
26
|
+
return Math.max(0, Math.min(maxRadius, rawCutRadius));
|
|
27
|
+
}
|
|
28
|
+
function buildBuiltinShapeOverlayPaths(cutRect, geometry) {
|
|
29
|
+
if (!geometry || geometry.shape === "custom") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const radius = resolveCutShapeRadiusPx(geometry, cutRect);
|
|
33
|
+
if (geometry.shape === "rect" && radius <= EPSILON) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const shapePathData = (0, geometry_1.generateDielinePath)({
|
|
37
|
+
shape: geometry.shape,
|
|
38
|
+
shapeStyle: geometry.shapeStyle,
|
|
39
|
+
width: Math.max(1, cutRect.width),
|
|
40
|
+
height: Math.max(1, cutRect.height),
|
|
41
|
+
radius,
|
|
42
|
+
x: cutRect.width / 2,
|
|
43
|
+
y: cutRect.height / 2,
|
|
44
|
+
features: [],
|
|
45
|
+
canvasWidth: Math.max(1, cutRect.width),
|
|
46
|
+
canvasHeight: Math.max(1, cutRect.height),
|
|
47
|
+
});
|
|
48
|
+
if (!shapePathData) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
shapePathData,
|
|
53
|
+
hatchPathData: `${buildRectPath(cutRect.width, cutRect.height)} ${shapePathData}`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function buildImageSessionOverlaySpecs(args) {
|
|
57
|
+
const { viewport, layout, geometry, visual, hatchPattern } = args;
|
|
58
|
+
const cutRect = layout.cutRect;
|
|
59
|
+
const specs = [];
|
|
60
|
+
specs.push({
|
|
61
|
+
id: "image.cropMask.rect",
|
|
62
|
+
type: "path",
|
|
63
|
+
space: "screen",
|
|
64
|
+
data: { id: "image.cropMask.rect", zIndex: 1 },
|
|
65
|
+
props: {
|
|
66
|
+
pathData: buildViewportMaskPath(viewport, cutRect),
|
|
67
|
+
left: viewport.left,
|
|
68
|
+
top: viewport.top,
|
|
69
|
+
originX: "left",
|
|
70
|
+
originY: "top",
|
|
71
|
+
fill: visual.outerBackground,
|
|
72
|
+
stroke: null,
|
|
73
|
+
fillRule: "evenodd",
|
|
74
|
+
selectable: false,
|
|
75
|
+
evented: false,
|
|
76
|
+
excludeFromExport: true,
|
|
77
|
+
objectCaching: false,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
const shapeOverlay = buildBuiltinShapeOverlayPaths(cutRect, geometry);
|
|
81
|
+
if (shapeOverlay) {
|
|
82
|
+
specs.push({
|
|
83
|
+
id: "image.cropShapeHatch",
|
|
84
|
+
type: "path",
|
|
85
|
+
space: "screen",
|
|
86
|
+
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
87
|
+
props: {
|
|
88
|
+
pathData: shapeOverlay.hatchPathData,
|
|
89
|
+
left: cutRect.left,
|
|
90
|
+
top: cutRect.top,
|
|
91
|
+
originX: "left",
|
|
92
|
+
originY: "top",
|
|
93
|
+
fill: hatchPattern || DEFAULT_HATCH_FILL,
|
|
94
|
+
opacity: hatchPattern ? 1 : 0.8,
|
|
95
|
+
stroke: null,
|
|
96
|
+
fillRule: "evenodd",
|
|
97
|
+
selectable: false,
|
|
98
|
+
evented: false,
|
|
99
|
+
excludeFromExport: true,
|
|
100
|
+
objectCaching: false,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
specs.push({
|
|
104
|
+
id: "image.cropShapeOutline",
|
|
105
|
+
type: "path",
|
|
106
|
+
space: "screen",
|
|
107
|
+
data: { id: "image.cropShapeOutline", zIndex: 6 },
|
|
108
|
+
props: {
|
|
109
|
+
pathData: shapeOverlay.shapePathData,
|
|
110
|
+
left: cutRect.left,
|
|
111
|
+
top: cutRect.top,
|
|
112
|
+
originX: "left",
|
|
113
|
+
originY: "top",
|
|
114
|
+
fill: "transparent",
|
|
115
|
+
stroke: SHAPE_OUTLINE_COLOR,
|
|
116
|
+
strokeWidth: 1,
|
|
117
|
+
selectable: false,
|
|
118
|
+
evented: false,
|
|
119
|
+
excludeFromExport: true,
|
|
120
|
+
objectCaching: false,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
specs.push({
|
|
125
|
+
id: "image.cropFrame",
|
|
126
|
+
type: "rect",
|
|
127
|
+
space: "screen",
|
|
128
|
+
data: { id: "image.cropFrame", zIndex: 7 },
|
|
129
|
+
props: {
|
|
130
|
+
left: cutRect.left,
|
|
131
|
+
top: cutRect.top,
|
|
132
|
+
width: cutRect.width,
|
|
133
|
+
height: cutRect.height,
|
|
134
|
+
originX: "left",
|
|
135
|
+
originY: "top",
|
|
136
|
+
fill: visual.innerBackground,
|
|
137
|
+
stroke: visual.strokeStyle === "hidden" ? "rgba(0,0,0,0)" : visual.strokeColor,
|
|
138
|
+
strokeWidth: visual.strokeStyle === "hidden" ? 0 : visual.strokeWidth,
|
|
139
|
+
strokeDashArray: visual.strokeStyle === "dashed"
|
|
140
|
+
? [visual.dashLength, visual.dashLength]
|
|
141
|
+
: undefined,
|
|
142
|
+
selectable: false,
|
|
143
|
+
evented: false,
|
|
144
|
+
excludeFromExport: true,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
return specs;
|
|
148
|
+
}
|