@pooder/kit 5.3.1 → 6.0.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/extensions/background.js +475 -131
- package/.test-dist/src/extensions/dieline.js +283 -180
- package/.test-dist/src/extensions/dielineShape.js +66 -0
- package/.test-dist/src/extensions/feature.js +388 -303
- package/.test-dist/src/extensions/film.js +133 -74
- package/.test-dist/src/extensions/geometry.js +120 -56
- package/.test-dist/src/extensions/image.js +296 -212
- package/.test-dist/src/extensions/index.js +1 -3
- package/.test-dist/src/extensions/maskOps.js +75 -20
- package/.test-dist/src/extensions/ruler.js +312 -215
- package/.test-dist/src/extensions/sceneLayoutModel.js +9 -3
- package/.test-dist/src/extensions/sceneVisibility.js +3 -10
- package/.test-dist/src/extensions/tracer.js +229 -58
- package/.test-dist/src/extensions/white-ink.js +139 -129
- package/.test-dist/src/services/CanvasService.js +888 -126
- package/.test-dist/src/services/index.js +1 -0
- package/.test-dist/src/services/visibility.js +54 -0
- package/.test-dist/tests/run.js +58 -4
- package/CHANGELOG.md +12 -0
- package/dist/index.d.mts +377 -82
- package/dist/index.d.ts +377 -82
- package/dist/index.js +3920 -2178
- package/dist/index.mjs +3992 -2247
- package/package.json +1 -1
- package/src/extensions/background.ts +631 -145
- package/src/extensions/dieline.ts +280 -187
- package/src/extensions/dielineShape.ts +109 -0
- package/src/extensions/feature.ts +485 -366
- package/src/extensions/film.ts +152 -76
- package/src/extensions/geometry.ts +203 -104
- package/src/extensions/image.ts +319 -238
- package/src/extensions/index.ts +0 -1
- package/src/extensions/ruler.ts +481 -268
- package/src/extensions/sceneLayoutModel.ts +18 -6
- package/src/extensions/white-ink.ts +157 -171
- package/src/services/CanvasService.ts +1126 -140
- package/src/services/index.ts +1 -0
- package/src/services/renderSpec.ts +69 -4
- package/src/services/visibility.ts +78 -0
- package/tests/run.ts +139 -4
- package/.test-dist/src/CanvasService.js +0 -249
- package/.test-dist/src/ViewportSystem.js +0 -75
- package/.test-dist/src/background.js +0 -203
- package/.test-dist/src/bridgeSelection.js +0 -20
- package/.test-dist/src/constraints.js +0 -237
- package/.test-dist/src/dieline.js +0 -818
- package/.test-dist/src/edgeScale.js +0 -12
- package/.test-dist/src/feature.js +0 -826
- package/.test-dist/src/featureComplete.js +0 -32
- package/.test-dist/src/film.js +0 -167
- package/.test-dist/src/geometry.js +0 -506
- package/.test-dist/src/image.js +0 -1250
- package/.test-dist/src/maskOps.js +0 -270
- package/.test-dist/src/mirror.js +0 -104
- package/.test-dist/src/renderSpec.js +0 -2
- package/.test-dist/src/ruler.js +0 -343
- package/.test-dist/src/sceneLayout.js +0 -99
- package/.test-dist/src/sceneLayoutModel.js +0 -196
- package/.test-dist/src/sceneView.js +0 -40
- package/.test-dist/src/sceneVisibility.js +0 -42
- package/.test-dist/src/size.js +0 -332
- package/.test-dist/src/tracer.js +0 -544
- package/.test-dist/src/white-ink.js +0 -829
- package/.test-dist/src/wrappedOffsets.js +0 -33
- package/src/extensions/sceneVisibility.ts +0 -71
|
@@ -3,6 +3,7 @@ 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");
|
|
6
7
|
const geometry_1 = require("./geometry");
|
|
7
8
|
const sceneLayoutModel_1 = require("./sceneLayoutModel");
|
|
8
9
|
const IMAGE_OBJECT_LAYER_ID = "image.user";
|
|
@@ -23,6 +24,8 @@ class ImageTool {
|
|
|
23
24
|
this.isImageSelectionActive = false;
|
|
24
25
|
this.focusedImageId = null;
|
|
25
26
|
this.renderSeq = 0;
|
|
27
|
+
this.imageSpecs = [];
|
|
28
|
+
this.overlaySpecs = [];
|
|
26
29
|
this.onToolActivated = (event) => {
|
|
27
30
|
const before = this.isToolActive;
|
|
28
31
|
this.syncToolActiveFromWorkbench(event.id);
|
|
@@ -99,16 +102,20 @@ class ImageTool {
|
|
|
99
102
|
const center = target.getCenterPoint
|
|
100
103
|
? target.getCenterPoint()
|
|
101
104
|
: new fabric_1.Point(target.left ?? 0, target.top ?? 0);
|
|
105
|
+
const centerScene = this.canvasService
|
|
106
|
+
? this.canvasService.toScenePoint({ x: center.x, y: center.y })
|
|
107
|
+
: { x: center.x, y: center.y };
|
|
102
108
|
const objectScale = Number.isFinite(target?.scaleX) ? target.scaleX : 1;
|
|
109
|
+
const objectScaleScene = this.toSceneObjectScale(objectScale || 1);
|
|
103
110
|
const workingItem = this.workingItems.find((item) => item.id === id);
|
|
104
111
|
const sourceKey = workingItem?.sourceUrl || workingItem?.url || "";
|
|
105
112
|
const sourceSize = this.getSourceSize(sourceKey, target);
|
|
106
113
|
const coverScale = this.getCoverScale(frame, sourceSize);
|
|
107
114
|
const updates = {
|
|
108
|
-
left: this.clampNormalized((
|
|
109
|
-
top: this.clampNormalized((
|
|
115
|
+
left: this.clampNormalized((centerScene.x - frame.left) / frame.width),
|
|
116
|
+
top: this.clampNormalized((centerScene.y - frame.top) / frame.height),
|
|
110
117
|
angle: Number.isFinite(target.angle) ? target.angle : 0,
|
|
111
|
-
scale: Math.max(0.05,
|
|
118
|
+
scale: Math.max(0.05, objectScaleScene / coverScale),
|
|
112
119
|
};
|
|
113
120
|
this.focusedImageId = id;
|
|
114
121
|
this.updateImageInWorking(id, updates);
|
|
@@ -121,6 +128,37 @@ class ImageTool {
|
|
|
121
128
|
console.warn("CanvasService not found for ImageTool");
|
|
122
129
|
return;
|
|
123
130
|
}
|
|
131
|
+
this.renderProducerDisposable?.dispose();
|
|
132
|
+
this.renderProducerDisposable = this.canvasService.registerRenderProducer(this.id, () => ({
|
|
133
|
+
passes: [
|
|
134
|
+
{
|
|
135
|
+
id: IMAGE_OBJECT_LAYER_ID,
|
|
136
|
+
stack: 500,
|
|
137
|
+
order: 0,
|
|
138
|
+
visibility: {
|
|
139
|
+
op: "not",
|
|
140
|
+
expr: {
|
|
141
|
+
op: "sessionActive",
|
|
142
|
+
toolId: "pooder.kit.white-ink",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
objects: this.imageSpecs,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: IMAGE_OVERLAY_LAYER_ID,
|
|
149
|
+
stack: 800,
|
|
150
|
+
order: 0,
|
|
151
|
+
visibility: {
|
|
152
|
+
op: "not",
|
|
153
|
+
expr: {
|
|
154
|
+
op: "sessionActive",
|
|
155
|
+
toolId: "pooder.kit.white-ink",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
objects: this.overlaySpecs,
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
}), { priority: 300 });
|
|
124
162
|
context.eventBus.on("tool:activated", this.onToolActivated);
|
|
125
163
|
context.eventBus.on("object:modified", this.onObjectModified);
|
|
126
164
|
context.eventBus.on("selection:created", this.onSelectionChanged);
|
|
@@ -166,9 +204,14 @@ class ImageTool {
|
|
|
166
204
|
this.dirtyTrackerDisposable = undefined;
|
|
167
205
|
this.cropShapeHatchPattern = undefined;
|
|
168
206
|
this.cropShapeHatchPatternColor = undefined;
|
|
207
|
+
this.cropShapeHatchPatternKey = undefined;
|
|
208
|
+
this.imageSpecs = [];
|
|
209
|
+
this.overlaySpecs = [];
|
|
169
210
|
this.clearRenderedImages();
|
|
211
|
+
this.renderProducerDisposable?.dispose();
|
|
212
|
+
this.renderProducerDisposable = undefined;
|
|
170
213
|
if (this.canvasService) {
|
|
171
|
-
void this.canvasService.
|
|
214
|
+
void this.canvasService.flushRenderFromProducers();
|
|
172
215
|
this.canvasService = undefined;
|
|
173
216
|
}
|
|
174
217
|
this.context = undefined;
|
|
@@ -581,42 +624,40 @@ class ImageTool {
|
|
|
581
624
|
if (!layout) {
|
|
582
625
|
return { left: 0, top: 0, width: 0, height: 0 };
|
|
583
626
|
}
|
|
584
|
-
return {
|
|
627
|
+
return this.canvasService.toSceneRect({
|
|
585
628
|
left: layout.cutRect.left,
|
|
586
629
|
top: layout.cutRect.top,
|
|
587
630
|
width: layout.cutRect.width,
|
|
588
631
|
height: layout.cutRect.height,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
getFrameRectScreen(frame) {
|
|
635
|
+
if (!this.canvasService) {
|
|
636
|
+
return { left: 0, top: 0, width: 0, height: 0 };
|
|
637
|
+
}
|
|
638
|
+
return this.canvasService.toScreenRect(frame || this.getFrameRect());
|
|
639
|
+
}
|
|
640
|
+
toLayoutSceneRect(rect) {
|
|
641
|
+
return {
|
|
642
|
+
left: rect.left,
|
|
643
|
+
top: rect.top,
|
|
644
|
+
width: rect.width,
|
|
645
|
+
height: rect.height,
|
|
646
|
+
space: "scene",
|
|
589
647
|
};
|
|
590
648
|
}
|
|
591
649
|
async resolveDefaultFitArea() {
|
|
592
|
-
if (!this.
|
|
593
|
-
return null;
|
|
594
|
-
const commandService = this.context.services.get("CommandService");
|
|
595
|
-
if (!commandService)
|
|
650
|
+
if (!this.canvasService)
|
|
596
651
|
return null;
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
const cutRect = layout?.cutRect;
|
|
600
|
-
const width = Number(cutRect?.width);
|
|
601
|
-
const height = Number(cutRect?.height);
|
|
602
|
-
const left = Number(cutRect?.left);
|
|
603
|
-
const top = Number(cutRect?.top);
|
|
604
|
-
if (!Number.isFinite(width) ||
|
|
605
|
-
!Number.isFinite(height) ||
|
|
606
|
-
!Number.isFinite(left) ||
|
|
607
|
-
!Number.isFinite(top)) {
|
|
608
|
-
return null;
|
|
609
|
-
}
|
|
610
|
-
return {
|
|
611
|
-
width: Math.max(1, width),
|
|
612
|
-
height: Math.max(1, height),
|
|
613
|
-
left: left + width / 2,
|
|
614
|
-
top: top + height / 2,
|
|
615
|
-
};
|
|
616
|
-
}
|
|
617
|
-
catch {
|
|
652
|
+
const frame = this.getFrameRect();
|
|
653
|
+
if (frame.width <= 0 || frame.height <= 0)
|
|
618
654
|
return null;
|
|
619
|
-
|
|
655
|
+
return {
|
|
656
|
+
width: Math.max(1, frame.width),
|
|
657
|
+
height: Math.max(1, frame.height),
|
|
658
|
+
left: frame.left + frame.width / 2,
|
|
659
|
+
top: frame.top + frame.height / 2,
|
|
660
|
+
};
|
|
620
661
|
}
|
|
621
662
|
async fitImageToDefaultArea(id) {
|
|
622
663
|
if (!this.canvasService)
|
|
@@ -626,13 +667,14 @@ class ImageTool {
|
|
|
626
667
|
await this.fitImageToArea(id, area);
|
|
627
668
|
return;
|
|
628
669
|
}
|
|
629
|
-
const
|
|
630
|
-
const
|
|
670
|
+
const viewport = this.canvasService.getSceneViewportRect();
|
|
671
|
+
const canvasW = Math.max(1, viewport.width || 0);
|
|
672
|
+
const canvasH = Math.max(1, viewport.height || 0);
|
|
631
673
|
await this.fitImageToArea(id, {
|
|
632
674
|
width: canvasW,
|
|
633
675
|
height: canvasH,
|
|
634
|
-
left: canvasW / 2,
|
|
635
|
-
top: canvasH / 2,
|
|
676
|
+
left: viewport.left + canvasW / 2,
|
|
677
|
+
top: viewport.top + canvasH / 2,
|
|
636
678
|
});
|
|
637
679
|
}
|
|
638
680
|
getImageObjects() {
|
|
@@ -645,7 +687,7 @@ class ImageTool {
|
|
|
645
687
|
getOverlayObjects() {
|
|
646
688
|
if (!this.canvasService)
|
|
647
689
|
return [];
|
|
648
|
-
return this.canvasService.
|
|
690
|
+
return this.canvasService.getPassObjects(IMAGE_OVERLAY_LAYER_ID);
|
|
649
691
|
}
|
|
650
692
|
getImageObject(id) {
|
|
651
693
|
return this.getImageObjects().find((obj) => obj?.data?.id === id);
|
|
@@ -653,9 +695,9 @@ class ImageTool {
|
|
|
653
695
|
clearRenderedImages() {
|
|
654
696
|
if (!this.canvasService)
|
|
655
697
|
return;
|
|
656
|
-
|
|
657
|
-
this.
|
|
658
|
-
this.canvasService.
|
|
698
|
+
this.imageSpecs = [];
|
|
699
|
+
this.overlaySpecs = [];
|
|
700
|
+
this.canvasService.requestRenderFromProducers();
|
|
659
701
|
}
|
|
660
702
|
purgeSourceSizeCacheForItem(item) {
|
|
661
703
|
if (!item)
|
|
@@ -683,6 +725,32 @@ class ImageTool {
|
|
|
683
725
|
}
|
|
684
726
|
return { width: 1, height: 1 };
|
|
685
727
|
}
|
|
728
|
+
async ensureSourceSize(src) {
|
|
729
|
+
if (!src)
|
|
730
|
+
return null;
|
|
731
|
+
const cached = this.sourceSizeBySrc.get(src);
|
|
732
|
+
if (cached)
|
|
733
|
+
return cached;
|
|
734
|
+
try {
|
|
735
|
+
const image = await fabric_1.Image.fromURL(src, {
|
|
736
|
+
crossOrigin: "anonymous",
|
|
737
|
+
});
|
|
738
|
+
const width = Number(image?.width || 0);
|
|
739
|
+
const height = Number(image?.height || 0);
|
|
740
|
+
if (width > 0 && height > 0) {
|
|
741
|
+
const size = { width, height };
|
|
742
|
+
this.sourceSizeBySrc.set(src, size);
|
|
743
|
+
return size;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
catch (error) {
|
|
747
|
+
this.debug("image:size:load-failed", {
|
|
748
|
+
src,
|
|
749
|
+
error: error instanceof Error ? error.message : String(error),
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
686
754
|
getCoverScale(frame, size) {
|
|
687
755
|
const sw = Math.max(1, size.width);
|
|
688
756
|
const sh = Math.max(1, size.height);
|
|
@@ -710,16 +778,21 @@ class ImageTool {
|
|
|
710
778
|
}
|
|
711
779
|
toSceneGeometryLike(raw) {
|
|
712
780
|
const shape = raw?.shape;
|
|
713
|
-
if (shape
|
|
714
|
-
shape !== "circle" &&
|
|
715
|
-
shape !== "ellipse" &&
|
|
716
|
-
shape !== "custom") {
|
|
781
|
+
if (!(0, dielineShape_1.isDielineShape)(shape)) {
|
|
717
782
|
return null;
|
|
718
783
|
}
|
|
719
|
-
const
|
|
720
|
-
const
|
|
784
|
+
const radiusRaw = Number(raw?.radius);
|
|
785
|
+
const offsetRaw = Number(raw?.offset);
|
|
786
|
+
const unit = typeof raw?.unit === "string" ? raw.unit : "px";
|
|
787
|
+
const radius = unit === "scene" || !this.canvasService
|
|
788
|
+
? radiusRaw
|
|
789
|
+
: this.canvasService.toSceneLength(radiusRaw);
|
|
790
|
+
const offset = unit === "scene" || !this.canvasService
|
|
791
|
+
? offsetRaw
|
|
792
|
+
: this.canvasService.toSceneLength(offsetRaw);
|
|
721
793
|
return {
|
|
722
794
|
shape,
|
|
795
|
+
shapeStyle: (0, dielineShape_1.normalizeShapeStyle)(raw?.shapeStyle),
|
|
723
796
|
radius: Number.isFinite(radius) ? radius : 0,
|
|
724
797
|
offset: Number.isFinite(offset) ? offset : 0,
|
|
725
798
|
};
|
|
@@ -773,8 +846,11 @@ class ImageTool {
|
|
|
773
846
|
getCropShapeHatchPattern(color = "rgba(255, 0, 0, 0.6)") {
|
|
774
847
|
if (typeof document === "undefined")
|
|
775
848
|
return undefined;
|
|
849
|
+
const sceneScale = this.canvasService?.getSceneScale() || 1;
|
|
850
|
+
const cacheKey = `${color}::${sceneScale.toFixed(6)}`;
|
|
776
851
|
if (this.cropShapeHatchPattern &&
|
|
777
|
-
this.cropShapeHatchPatternColor === color
|
|
852
|
+
this.cropShapeHatchPatternColor === color &&
|
|
853
|
+
this.cropShapeHatchPatternKey === cacheKey) {
|
|
778
854
|
return this.cropShapeHatchPattern;
|
|
779
855
|
}
|
|
780
856
|
const size = 16;
|
|
@@ -804,8 +880,18 @@ class ImageTool {
|
|
|
804
880
|
// @ts-ignore: Fabric Pattern accepts canvas source here.
|
|
805
881
|
repetition: "repeat",
|
|
806
882
|
});
|
|
883
|
+
// Scene specs are scaled to screen by CanvasService; keep hatch density in screen pixels.
|
|
884
|
+
pattern.patternTransform = [
|
|
885
|
+
1 / sceneScale,
|
|
886
|
+
0,
|
|
887
|
+
0,
|
|
888
|
+
1 / sceneScale,
|
|
889
|
+
0,
|
|
890
|
+
0,
|
|
891
|
+
];
|
|
807
892
|
this.cropShapeHatchPattern = pattern;
|
|
808
893
|
this.cropShapeHatchPatternColor = color;
|
|
894
|
+
this.cropShapeHatchPatternKey = cacheKey;
|
|
809
895
|
return pattern;
|
|
810
896
|
}
|
|
811
897
|
buildCropShapeOverlaySpecs(frame, sceneGeometry) {
|
|
@@ -818,6 +904,7 @@ class ImageTool {
|
|
|
818
904
|
return [];
|
|
819
905
|
}
|
|
820
906
|
const shape = sceneGeometry.shape;
|
|
907
|
+
const shapeStyle = sceneGeometry.shapeStyle;
|
|
821
908
|
const inset = 0;
|
|
822
909
|
const shapeWidth = Math.max(1, frame.width);
|
|
823
910
|
const shapeHeight = Math.max(1, frame.height);
|
|
@@ -827,6 +914,7 @@ class ImageTool {
|
|
|
827
914
|
frameWidth: frame.width,
|
|
828
915
|
frameHeight: frame.height,
|
|
829
916
|
offset: sceneGeometry.offset,
|
|
917
|
+
shapeStyle,
|
|
830
918
|
inset,
|
|
831
919
|
shapeWidth,
|
|
832
920
|
shapeHeight,
|
|
@@ -849,6 +937,7 @@ class ImageTool {
|
|
|
849
937
|
x: frame.width / 2,
|
|
850
938
|
y: frame.height / 2,
|
|
851
939
|
features: [],
|
|
940
|
+
shapeStyle,
|
|
852
941
|
canvasWidth: frame.width,
|
|
853
942
|
canvasHeight: frame.height,
|
|
854
943
|
};
|
|
@@ -866,6 +955,9 @@ class ImageTool {
|
|
|
866
955
|
}
|
|
867
956
|
const patternFill = this.getCropShapeHatchPattern();
|
|
868
957
|
const hatchFill = patternFill || "rgba(255, 0, 0, 0.22)";
|
|
958
|
+
const shapeBounds = (0, geometry_1.getPathBounds)(shapePathData);
|
|
959
|
+
const hatchBounds = (0, geometry_1.getPathBounds)(hatchPathData);
|
|
960
|
+
const frameRect = this.toLayoutSceneRect(frame);
|
|
869
961
|
const hatchPathLength = hatchPathData.length;
|
|
870
962
|
const shapePathLength = shapePathData.length;
|
|
871
963
|
const specs = [
|
|
@@ -873,10 +965,16 @@ class ImageTool {
|
|
|
873
965
|
id: "image.cropShapeHatch",
|
|
874
966
|
type: "path",
|
|
875
967
|
data: { id: "image.cropShapeHatch", zIndex: 5 },
|
|
968
|
+
layout: {
|
|
969
|
+
reference: "custom",
|
|
970
|
+
referenceRect: frameRect,
|
|
971
|
+
alignX: "start",
|
|
972
|
+
alignY: "start",
|
|
973
|
+
offsetX: hatchBounds.x,
|
|
974
|
+
offsetY: hatchBounds.y,
|
|
975
|
+
},
|
|
876
976
|
props: {
|
|
877
977
|
pathData: hatchPathData,
|
|
878
|
-
left: frame.left,
|
|
879
|
-
top: frame.top,
|
|
880
978
|
originX: "left",
|
|
881
979
|
originY: "top",
|
|
882
980
|
fill: hatchFill,
|
|
@@ -893,15 +991,21 @@ class ImageTool {
|
|
|
893
991
|
id: "image.cropShapePath",
|
|
894
992
|
type: "path",
|
|
895
993
|
data: { id: "image.cropShapePath", zIndex: 6 },
|
|
994
|
+
layout: {
|
|
995
|
+
reference: "custom",
|
|
996
|
+
referenceRect: frameRect,
|
|
997
|
+
alignX: "start",
|
|
998
|
+
alignY: "start",
|
|
999
|
+
offsetX: shapeBounds.x,
|
|
1000
|
+
offsetY: shapeBounds.y,
|
|
1001
|
+
},
|
|
896
1002
|
props: {
|
|
897
1003
|
pathData: shapePathData,
|
|
898
|
-
left: frame.left,
|
|
899
|
-
top: frame.top,
|
|
900
1004
|
originX: "left",
|
|
901
1005
|
originY: "top",
|
|
902
1006
|
fill: "rgba(0,0,0,0)",
|
|
903
1007
|
stroke: "rgba(255, 0, 0, 0.9)",
|
|
904
|
-
strokeWidth: 1,
|
|
1008
|
+
strokeWidth: this.canvasService?.toSceneLength(1) ?? 1,
|
|
905
1009
|
selectable: false,
|
|
906
1010
|
evented: false,
|
|
907
1011
|
excludeFromExport: true,
|
|
@@ -918,6 +1022,8 @@ class ImageTool {
|
|
|
918
1022
|
fillRule: "evenodd",
|
|
919
1023
|
shapePathLength,
|
|
920
1024
|
hatchPathLength,
|
|
1025
|
+
shapeBounds,
|
|
1026
|
+
hatchBounds,
|
|
921
1027
|
hatchFillType: hatchFill && typeof hatchFill === "object" ? "pattern" : "color",
|
|
922
1028
|
ids: specs.map((spec) => spec.id),
|
|
923
1029
|
});
|
|
@@ -980,6 +1086,11 @@ class ImageTool {
|
|
|
980
1086
|
opacity: render.opacity,
|
|
981
1087
|
};
|
|
982
1088
|
}
|
|
1089
|
+
toSceneObjectScale(value) {
|
|
1090
|
+
if (!this.canvasService)
|
|
1091
|
+
return value;
|
|
1092
|
+
return value / this.canvasService.getSceneScale();
|
|
1093
|
+
}
|
|
983
1094
|
getCurrentSrc(obj) {
|
|
984
1095
|
if (!obj)
|
|
985
1096
|
return undefined;
|
|
@@ -987,109 +1098,28 @@ class ImageTool {
|
|
|
987
1098
|
return obj.getSrc();
|
|
988
1099
|
return obj?._originalElement?.src;
|
|
989
1100
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
async upsertImageObject(item, frame, seq) {
|
|
1006
|
-
if (!this.canvasService)
|
|
1007
|
-
return;
|
|
1008
|
-
const canvas = this.canvasService.canvas;
|
|
1009
|
-
const render = this.resolveRenderImageState(item);
|
|
1010
|
-
if (!render.src)
|
|
1011
|
-
return;
|
|
1012
|
-
let obj = this.getImageObject(item.id);
|
|
1013
|
-
const currentSrc = this.getCurrentSrc(obj);
|
|
1014
|
-
if (obj && currentSrc && currentSrc !== render.src) {
|
|
1015
|
-
canvas.remove(obj);
|
|
1016
|
-
obj = undefined;
|
|
1017
|
-
}
|
|
1018
|
-
if (!obj) {
|
|
1019
|
-
const created = await fabric_1.Image.fromURL(render.src, {
|
|
1020
|
-
crossOrigin: "anonymous",
|
|
1021
|
-
});
|
|
1022
|
-
if (seq !== this.renderSeq)
|
|
1023
|
-
return;
|
|
1024
|
-
created.set({
|
|
1101
|
+
async buildImageSpecs(items, frame) {
|
|
1102
|
+
const specs = [];
|
|
1103
|
+
for (const item of items) {
|
|
1104
|
+
const render = this.resolveRenderImageState(item);
|
|
1105
|
+
if (!render.src)
|
|
1106
|
+
continue;
|
|
1107
|
+
const ensured = await this.ensureSourceSize(render.src);
|
|
1108
|
+
const sourceSize = ensured || this.getSourceSize(render.src);
|
|
1109
|
+
const props = this.computeCanvasProps(render, sourceSize, frame);
|
|
1110
|
+
specs.push({
|
|
1111
|
+
id: item.id,
|
|
1112
|
+
type: "image",
|
|
1113
|
+
src: render.src,
|
|
1025
1114
|
data: {
|
|
1026
1115
|
id: item.id,
|
|
1027
1116
|
layerId: IMAGE_OBJECT_LAYER_ID,
|
|
1028
1117
|
type: "image-item",
|
|
1029
1118
|
},
|
|
1119
|
+
props,
|
|
1030
1120
|
});
|
|
1031
|
-
canvas.add(created);
|
|
1032
|
-
obj = created;
|
|
1033
|
-
}
|
|
1034
|
-
this.rememberSourceSize(render.src, obj);
|
|
1035
|
-
const sourceSize = this.getSourceSize(render.src, obj);
|
|
1036
|
-
const props = this.computeCanvasProps(render, sourceSize, frame);
|
|
1037
|
-
obj.set({
|
|
1038
|
-
...props,
|
|
1039
|
-
data: {
|
|
1040
|
-
...(obj.data || {}),
|
|
1041
|
-
id: item.id,
|
|
1042
|
-
layerId: IMAGE_OBJECT_LAYER_ID,
|
|
1043
|
-
type: "image-item",
|
|
1044
|
-
},
|
|
1045
|
-
});
|
|
1046
|
-
this.applyImageControlVisibility(obj);
|
|
1047
|
-
obj.setCoords();
|
|
1048
|
-
const resolver = this.loadResolvers.get(item.id);
|
|
1049
|
-
if (resolver) {
|
|
1050
|
-
resolver();
|
|
1051
|
-
this.loadResolvers.delete(item.id);
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
syncImageZOrder(items) {
|
|
1055
|
-
if (!this.canvasService)
|
|
1056
|
-
return;
|
|
1057
|
-
const canvas = this.canvasService.canvas;
|
|
1058
|
-
const objects = canvas.getObjects();
|
|
1059
|
-
let insertIndex = 0;
|
|
1060
|
-
const backgroundLayer = this.canvasService.getLayer("background");
|
|
1061
|
-
if (backgroundLayer) {
|
|
1062
|
-
const bgIndex = objects.indexOf(backgroundLayer);
|
|
1063
|
-
if (bgIndex >= 0)
|
|
1064
|
-
insertIndex = bgIndex + 1;
|
|
1065
|
-
}
|
|
1066
|
-
items.forEach((item) => {
|
|
1067
|
-
const obj = this.getImageObject(item.id);
|
|
1068
|
-
if (!obj)
|
|
1069
|
-
return;
|
|
1070
|
-
canvas.moveObjectTo(obj, insertIndex);
|
|
1071
|
-
insertIndex += 1;
|
|
1072
|
-
});
|
|
1073
|
-
const overlayObjects = this.getOverlayObjects().sort((a, b) => {
|
|
1074
|
-
const az = Number(a?.data?.zIndex ?? 0);
|
|
1075
|
-
const bz = Number(b?.data?.zIndex ?? 0);
|
|
1076
|
-
return az - bz;
|
|
1077
|
-
});
|
|
1078
|
-
overlayObjects.forEach((obj) => {
|
|
1079
|
-
canvas.bringObjectToFront(obj);
|
|
1080
|
-
});
|
|
1081
|
-
if (this.isDebugEnabled()) {
|
|
1082
|
-
const stack = canvas
|
|
1083
|
-
.getObjects()
|
|
1084
|
-
.map((obj, index) => ({
|
|
1085
|
-
index,
|
|
1086
|
-
id: obj?.data?.id,
|
|
1087
|
-
layerId: obj?.data?.layerId,
|
|
1088
|
-
zIndex: obj?.data?.zIndex,
|
|
1089
|
-
}))
|
|
1090
|
-
.filter((item) => item.layerId === IMAGE_OVERLAY_LAYER_ID);
|
|
1091
|
-
this.debug("overlay:stack", stack);
|
|
1092
1121
|
}
|
|
1122
|
+
return specs;
|
|
1093
1123
|
}
|
|
1094
1124
|
buildOverlaySpecs(frame, sceneGeometry) {
|
|
1095
1125
|
const visible = this.isImageEditingVisible();
|
|
@@ -1106,31 +1136,53 @@ class ImageTool {
|
|
|
1106
1136
|
});
|
|
1107
1137
|
return [];
|
|
1108
1138
|
}
|
|
1109
|
-
const
|
|
1110
|
-
const
|
|
1139
|
+
const viewport = this.canvasService.getSceneViewportRect();
|
|
1140
|
+
const canvasW = viewport.width || 0;
|
|
1141
|
+
const canvasH = viewport.height || 0;
|
|
1142
|
+
const canvasLeft = viewport.left || 0;
|
|
1143
|
+
const canvasTop = viewport.top || 0;
|
|
1111
1144
|
const visual = this.getFrameVisualConfig();
|
|
1112
|
-
const
|
|
1113
|
-
const
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1145
|
+
const strokeWidthScene = this.canvasService.toSceneLength(visual.strokeWidth);
|
|
1146
|
+
const dashLengthScene = this.canvasService.toSceneLength(visual.dashLength);
|
|
1147
|
+
const frameLeft = Math.max(canvasLeft, Math.min(canvasLeft + canvasW, frame.left));
|
|
1148
|
+
const frameTop = Math.max(canvasTop, Math.min(canvasTop + canvasH, frame.top));
|
|
1149
|
+
const frameRight = Math.max(frameLeft, Math.min(canvasLeft + canvasW, frame.left + frame.width));
|
|
1150
|
+
const frameBottom = Math.max(frameTop, Math.min(canvasTop + canvasH, frame.top + frame.height));
|
|
1116
1151
|
const visibleFrameH = Math.max(0, frameBottom - frameTop);
|
|
1117
|
-
const topH = frameTop;
|
|
1118
|
-
const bottomH = Math.max(0, canvasH - frameBottom);
|
|
1119
|
-
const leftW = frameLeft;
|
|
1120
|
-
const rightW = Math.max(0, canvasW - frameRight);
|
|
1152
|
+
const topH = Math.max(0, frameTop - canvasTop);
|
|
1153
|
+
const bottomH = Math.max(0, canvasTop + canvasH - frameBottom);
|
|
1154
|
+
const leftW = Math.max(0, frameLeft - canvasLeft);
|
|
1155
|
+
const rightW = Math.max(0, canvasLeft + canvasW - frameRight);
|
|
1156
|
+
const viewportRect = this.toLayoutSceneRect({
|
|
1157
|
+
left: canvasLeft,
|
|
1158
|
+
top: canvasTop,
|
|
1159
|
+
width: canvasW,
|
|
1160
|
+
height: canvasH,
|
|
1161
|
+
});
|
|
1162
|
+
const visibleFrameBandRect = this.toLayoutSceneRect({
|
|
1163
|
+
left: canvasLeft,
|
|
1164
|
+
top: frameTop,
|
|
1165
|
+
width: canvasW,
|
|
1166
|
+
height: visibleFrameH,
|
|
1167
|
+
});
|
|
1168
|
+
const frameRect = this.toLayoutSceneRect(frame);
|
|
1121
1169
|
const shapeOverlay = this.buildCropShapeOverlaySpecs(frame, sceneGeometry);
|
|
1122
1170
|
const mask = [
|
|
1123
1171
|
{
|
|
1124
1172
|
id: "image.cropMask.top",
|
|
1125
1173
|
type: "rect",
|
|
1126
1174
|
data: { id: "image.cropMask.top", zIndex: 1 },
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1175
|
+
layout: {
|
|
1176
|
+
reference: "custom",
|
|
1177
|
+
referenceRect: viewportRect,
|
|
1178
|
+
alignX: "start",
|
|
1179
|
+
alignY: "start",
|
|
1180
|
+
width: "100%",
|
|
1131
1181
|
height: topH,
|
|
1132
|
-
|
|
1133
|
-
|
|
1182
|
+
},
|
|
1183
|
+
props: {
|
|
1184
|
+
originX: "left",
|
|
1185
|
+
originY: "top",
|
|
1134
1186
|
fill: visual.outerBackground,
|
|
1135
1187
|
selectable: false,
|
|
1136
1188
|
evented: false,
|
|
@@ -1140,13 +1192,17 @@ class ImageTool {
|
|
|
1140
1192
|
id: "image.cropMask.bottom",
|
|
1141
1193
|
type: "rect",
|
|
1142
1194
|
data: { id: "image.cropMask.bottom", zIndex: 2 },
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1195
|
+
layout: {
|
|
1196
|
+
reference: "custom",
|
|
1197
|
+
referenceRect: viewportRect,
|
|
1198
|
+
alignX: "start",
|
|
1199
|
+
alignY: "end",
|
|
1200
|
+
width: "100%",
|
|
1147
1201
|
height: bottomH,
|
|
1148
|
-
|
|
1149
|
-
|
|
1202
|
+
},
|
|
1203
|
+
props: {
|
|
1204
|
+
originX: "left",
|
|
1205
|
+
originY: "top",
|
|
1150
1206
|
fill: visual.outerBackground,
|
|
1151
1207
|
selectable: false,
|
|
1152
1208
|
evented: false,
|
|
@@ -1156,13 +1212,17 @@ class ImageTool {
|
|
|
1156
1212
|
id: "image.cropMask.left",
|
|
1157
1213
|
type: "rect",
|
|
1158
1214
|
data: { id: "image.cropMask.left", zIndex: 3 },
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1215
|
+
layout: {
|
|
1216
|
+
reference: "custom",
|
|
1217
|
+
referenceRect: visibleFrameBandRect,
|
|
1218
|
+
alignX: "start",
|
|
1219
|
+
alignY: "start",
|
|
1162
1220
|
width: leftW,
|
|
1163
|
-
height:
|
|
1164
|
-
|
|
1165
|
-
|
|
1221
|
+
height: "100%",
|
|
1222
|
+
},
|
|
1223
|
+
props: {
|
|
1224
|
+
originX: "left",
|
|
1225
|
+
originY: "top",
|
|
1166
1226
|
fill: visual.outerBackground,
|
|
1167
1227
|
selectable: false,
|
|
1168
1228
|
evented: false,
|
|
@@ -1172,13 +1232,17 @@ class ImageTool {
|
|
|
1172
1232
|
id: "image.cropMask.right",
|
|
1173
1233
|
type: "rect",
|
|
1174
1234
|
data: { id: "image.cropMask.right", zIndex: 4 },
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1235
|
+
layout: {
|
|
1236
|
+
reference: "custom",
|
|
1237
|
+
referenceRect: visibleFrameBandRect,
|
|
1238
|
+
alignX: "end",
|
|
1239
|
+
alignY: "start",
|
|
1178
1240
|
width: rightW,
|
|
1179
|
-
height:
|
|
1180
|
-
|
|
1181
|
-
|
|
1241
|
+
height: "100%",
|
|
1242
|
+
},
|
|
1243
|
+
props: {
|
|
1244
|
+
originX: "left",
|
|
1245
|
+
originY: "top",
|
|
1182
1246
|
fill: visual.outerBackground,
|
|
1183
1247
|
selectable: false,
|
|
1184
1248
|
evented: false,
|
|
@@ -1189,26 +1253,32 @@ class ImageTool {
|
|
|
1189
1253
|
id: "image.cropFrame",
|
|
1190
1254
|
type: "rect",
|
|
1191
1255
|
data: { id: "image.cropFrame", zIndex: 7 },
|
|
1256
|
+
layout: {
|
|
1257
|
+
reference: "custom",
|
|
1258
|
+
referenceRect: frameRect,
|
|
1259
|
+
alignX: "start",
|
|
1260
|
+
alignY: "start",
|
|
1261
|
+
width: "100%",
|
|
1262
|
+
height: "100%",
|
|
1263
|
+
},
|
|
1192
1264
|
props: {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
width: frame.width,
|
|
1196
|
-
height: frame.height,
|
|
1197
|
-
originX: "center",
|
|
1198
|
-
originY: "center",
|
|
1265
|
+
originX: "left",
|
|
1266
|
+
originY: "top",
|
|
1199
1267
|
fill: visual.innerBackground,
|
|
1200
1268
|
stroke: visual.strokeStyle === "hidden"
|
|
1201
1269
|
? "rgba(0,0,0,0)"
|
|
1202
1270
|
: visual.strokeColor,
|
|
1203
|
-
strokeWidth: visual.strokeStyle === "hidden" ? 0 :
|
|
1271
|
+
strokeWidth: visual.strokeStyle === "hidden" ? 0 : strokeWidthScene,
|
|
1204
1272
|
strokeDashArray: visual.strokeStyle === "dashed"
|
|
1205
|
-
? [
|
|
1273
|
+
? [dashLengthScene, dashLengthScene]
|
|
1206
1274
|
: undefined,
|
|
1207
1275
|
selectable: false,
|
|
1208
1276
|
evented: false,
|
|
1209
1277
|
},
|
|
1210
1278
|
};
|
|
1211
|
-
const specs =
|
|
1279
|
+
const specs = shapeOverlay.length > 0
|
|
1280
|
+
? [...mask, ...shapeOverlay]
|
|
1281
|
+
: [...mask, ...shapeOverlay, frameSpec];
|
|
1212
1282
|
this.debug("overlay:built", {
|
|
1213
1283
|
frame,
|
|
1214
1284
|
shape: sceneGeometry?.shape,
|
|
@@ -1236,31 +1306,37 @@ class ImageTool {
|
|
|
1236
1306
|
skipRender: true,
|
|
1237
1307
|
});
|
|
1238
1308
|
}
|
|
1239
|
-
this.
|
|
1240
|
-
const id = obj?.data?.id;
|
|
1241
|
-
if (typeof id === "string" && !desiredIds.has(id)) {
|
|
1242
|
-
this.canvasService?.canvas.remove(obj);
|
|
1243
|
-
}
|
|
1244
|
-
});
|
|
1245
|
-
for (const item of renderItems) {
|
|
1246
|
-
if (seq !== this.renderSeq)
|
|
1247
|
-
return;
|
|
1248
|
-
await this.upsertImageObject(item, frame, seq);
|
|
1249
|
-
}
|
|
1309
|
+
const imageSpecs = await this.buildImageSpecs(renderItems, frame);
|
|
1250
1310
|
if (seq !== this.renderSeq)
|
|
1251
1311
|
return;
|
|
1252
|
-
this.syncImageZOrder(renderItems);
|
|
1253
1312
|
const sceneGeometry = await this.resolveSceneGeometryForOverlay();
|
|
1254
1313
|
if (seq !== this.renderSeq)
|
|
1255
1314
|
return;
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
this.
|
|
1315
|
+
this.imageSpecs = imageSpecs;
|
|
1316
|
+
this.overlaySpecs = this.buildOverlaySpecs(frame, sceneGeometry);
|
|
1317
|
+
await this.canvasService.flushRenderFromProducers();
|
|
1318
|
+
if (seq !== this.renderSeq)
|
|
1319
|
+
return;
|
|
1320
|
+
renderItems.forEach((item) => {
|
|
1321
|
+
if (!this.getImageObject(item.id))
|
|
1322
|
+
return;
|
|
1323
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
1324
|
+
if (!resolver)
|
|
1325
|
+
return;
|
|
1326
|
+
resolver();
|
|
1327
|
+
this.loadResolvers.delete(item.id);
|
|
1328
|
+
});
|
|
1329
|
+
if (this.focusedImageId && this.isToolActive) {
|
|
1330
|
+
this.setImageFocus(this.focusedImageId, {
|
|
1331
|
+
syncCanvasSelection: true,
|
|
1332
|
+
skipRender: true,
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1259
1335
|
const overlayCanvasCount = this.getOverlayObjects().length;
|
|
1260
1336
|
this.debug("render:done", {
|
|
1261
1337
|
seq,
|
|
1262
1338
|
renderCount: renderItems.length,
|
|
1263
|
-
overlayCount: overlaySpecs.length,
|
|
1339
|
+
overlayCount: this.overlaySpecs.length,
|
|
1264
1340
|
overlayCanvasCount,
|
|
1265
1341
|
isToolActive: this.isToolActive,
|
|
1266
1342
|
isImageSelectionActive: this.isImageSelectionActive,
|
|
@@ -1350,7 +1426,7 @@ class ImageTool {
|
|
|
1350
1426
|
const source = this.getSourceSize(render.src, obj);
|
|
1351
1427
|
const frame = this.getFrameRect();
|
|
1352
1428
|
const coverScale = this.getCoverScale(frame, source);
|
|
1353
|
-
const currentScale = obj.scaleX || 1;
|
|
1429
|
+
const currentScale = this.toSceneObjectScale(obj.scaleX || 1);
|
|
1354
1430
|
const zoom = Math.max(0.05, currentScale / coverScale);
|
|
1355
1431
|
const updated = {
|
|
1356
1432
|
scale: Number.isFinite(zoom) ? zoom : 1,
|
|
@@ -1388,12 +1464,17 @@ class ImageTool {
|
|
|
1388
1464
|
const frame = this.getFrameRect();
|
|
1389
1465
|
const baseCover = this.getCoverScale(frame, source);
|
|
1390
1466
|
const desiredScale = Math.max(Math.max(1, area.width) / Math.max(1, source.width), Math.max(1, area.height) / Math.max(1, source.height));
|
|
1391
|
-
const
|
|
1392
|
-
const
|
|
1467
|
+
const viewport = this.canvasService.getSceneViewportRect();
|
|
1468
|
+
const canvasW = viewport.width || 1;
|
|
1469
|
+
const canvasH = viewport.height || 1;
|
|
1393
1470
|
const areaLeftInput = area.left ?? 0.5;
|
|
1394
1471
|
const areaTopInput = area.top ?? 0.5;
|
|
1395
|
-
const areaLeftPx = areaLeftInput <= 1.5
|
|
1396
|
-
|
|
1472
|
+
const areaLeftPx = areaLeftInput <= 1.5
|
|
1473
|
+
? viewport.left + areaLeftInput * canvasW
|
|
1474
|
+
: areaLeftInput;
|
|
1475
|
+
const areaTopPx = areaTopInput <= 1.5
|
|
1476
|
+
? viewport.top + areaTopInput * canvasH
|
|
1477
|
+
: areaTopInput;
|
|
1397
1478
|
const updates = {
|
|
1398
1479
|
scale: Math.max(0.05, desiredScale / baseCover),
|
|
1399
1480
|
left: this.clampNormalized((areaLeftPx - frame.left) / Math.max(1, frame.width)),
|
|
@@ -1426,6 +1507,8 @@ class ImageTool {
|
|
|
1426
1507
|
next.push(this.normalizeItem({
|
|
1427
1508
|
...item,
|
|
1428
1509
|
url,
|
|
1510
|
+
// Keep original source for next image-tool session editing,
|
|
1511
|
+
// and use committedUrl as non-image-tools render source.
|
|
1429
1512
|
sourceUrl,
|
|
1430
1513
|
committedUrl: url,
|
|
1431
1514
|
}));
|
|
@@ -1447,7 +1530,8 @@ class ImageTool {
|
|
|
1447
1530
|
if (!normalizedIds.length) {
|
|
1448
1531
|
throw new Error("image-ids-required");
|
|
1449
1532
|
}
|
|
1450
|
-
const
|
|
1533
|
+
const frameScene = this.getFrameRect();
|
|
1534
|
+
const frame = this.getFrameRectScreen(frameScene);
|
|
1451
1535
|
const multiplier = Math.max(1, options.multiplier ?? 2);
|
|
1452
1536
|
const format = options.format === "jpeg" ? "jpeg" : "png";
|
|
1453
1537
|
const width = Math.max(1, Math.round(frame.width * multiplier));
|