@overtone-art/canvas-editor-core 0.2.7 → 0.2.8
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/dist/chunk-ORZZ6MGQ.mjs +228 -0
- package/dist/chunk-ORZZ6MGQ.mjs.map +1 -0
- package/dist/index.d.mts +157 -4
- package/dist/index.d.ts +157 -4
- package/dist/index.global.js +59 -59
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +660 -55
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +500 -21
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +51 -2
- package/dist/node.d.ts +51 -2
- package/dist/node.js +546 -13
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +446 -4
- package/dist/node.mjs.map +1 -1
- package/dist/{types-bOY7oYVq.d.mts → types-D60CfxL9.d.mts} +80 -2
- package/dist/{types-bOY7oYVq.d.ts → types-D60CfxL9.d.ts} +80 -2
- package/package.json +37 -2
- package/dist/chunk-NINRTPOJ.mjs +0 -104
- package/dist/chunk-NINRTPOJ.mjs.map +0 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// src/displacement.ts
|
|
2
|
+
var CHANNEL_INDEX = {
|
|
3
|
+
red: 0,
|
|
4
|
+
green: 1,
|
|
5
|
+
blue: 2,
|
|
6
|
+
alpha: 3
|
|
7
|
+
};
|
|
8
|
+
function finiteScale(value, fallback, label) {
|
|
9
|
+
const resolved = value ?? fallback;
|
|
10
|
+
if (!Number.isFinite(resolved)) throw new Error(`${label} must be finite`);
|
|
11
|
+
return resolved;
|
|
12
|
+
}
|
|
13
|
+
function sample(source, width, height, x, y, channel) {
|
|
14
|
+
const clampedX = Math.max(0, Math.min(width - 1, x));
|
|
15
|
+
const clampedY = Math.max(0, Math.min(height - 1, y));
|
|
16
|
+
const x0 = Math.floor(clampedX);
|
|
17
|
+
const y0 = Math.floor(clampedY);
|
|
18
|
+
const x1 = Math.min(width - 1, x0 + 1);
|
|
19
|
+
const y1 = Math.min(height - 1, y0 + 1);
|
|
20
|
+
const tx = clampedX - x0;
|
|
21
|
+
const ty = clampedY - y0;
|
|
22
|
+
const top = source[(y0 * width + x0) * 4 + channel] * (1 - tx) + source[(y0 * width + x1) * 4 + channel] * tx;
|
|
23
|
+
const bottom = source[(y1 * width + x0) * 4 + channel] * (1 - tx) + source[(y1 * width + x1) * 4 + channel] * tx;
|
|
24
|
+
return top * (1 - ty) + bottom * ty;
|
|
25
|
+
}
|
|
26
|
+
function displaceRgba(source, map, width, height, options) {
|
|
27
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
28
|
+
throw new Error("Displacement dimensions must be positive integers");
|
|
29
|
+
}
|
|
30
|
+
const expectedLength = width * height * 4;
|
|
31
|
+
if (source.length !== expectedLength || map.length !== expectedLength) {
|
|
32
|
+
throw new Error("Displacement source and map must match the requested dimensions");
|
|
33
|
+
}
|
|
34
|
+
const scaleX = finiteScale(options.scaleX, 10, "Displacement scaleX");
|
|
35
|
+
const scaleY = finiteScale(options.scaleY, 10, "Displacement scaleY");
|
|
36
|
+
const channelX = CHANNEL_INDEX[options.channelX ?? "red"];
|
|
37
|
+
const channelY = CHANNEL_INDEX[options.channelY ?? "green"];
|
|
38
|
+
const output = new Uint8ClampedArray(expectedLength);
|
|
39
|
+
for (let y = 0; y < height; y += 1) {
|
|
40
|
+
for (let x = 0; x < width; x += 1) {
|
|
41
|
+
const offset = (y * width + x) * 4;
|
|
42
|
+
const sourceX = x + (map[offset + channelX] - 128) / 127 * scaleX;
|
|
43
|
+
const sourceY = y + (map[offset + channelY] - 128) / 127 * scaleY;
|
|
44
|
+
for (let channel = 0; channel < 4; channel += 1) {
|
|
45
|
+
output[offset + channel] = Math.round(
|
|
46
|
+
sample(source, width, height, sourceX, sourceY, channel)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/export.ts
|
|
55
|
+
import { StaticCanvas } from "fabric";
|
|
56
|
+
function computePrintAreaClip(area, scaleX, scaleY, targetWidth, targetHeight) {
|
|
57
|
+
const left = Math.max(0, Math.min(targetWidth, area.left * scaleX));
|
|
58
|
+
const top = Math.max(0, Math.min(targetHeight, area.top * scaleY));
|
|
59
|
+
const right = Math.max(left, Math.min(targetWidth, (area.left + area.width) * scaleX));
|
|
60
|
+
const bottom = Math.max(top, Math.min(targetHeight, (area.top + area.height) * scaleY));
|
|
61
|
+
return { left, top, width: right - left, height: bottom - top };
|
|
62
|
+
}
|
|
63
|
+
function computeCoverPlacement(sourceWidth, sourceHeight, targetWidth, targetHeight) {
|
|
64
|
+
if (sourceWidth <= 0 || sourceHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) {
|
|
65
|
+
throw new Error("Cover dimensions must be positive");
|
|
66
|
+
}
|
|
67
|
+
const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);
|
|
68
|
+
const width = sourceWidth * scale;
|
|
69
|
+
const height = sourceHeight * scale;
|
|
70
|
+
return {
|
|
71
|
+
left: (targetWidth - width) / 2,
|
|
72
|
+
top: (targetHeight - height) / 2,
|
|
73
|
+
width,
|
|
74
|
+
height
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function canvasElementToBlob(output, format, quality) {
|
|
78
|
+
const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
output.toBlob(
|
|
81
|
+
(blob) => blob ? resolve(blob) : reject(new Error(`Failed to export ${format}`)),
|
|
82
|
+
mime,
|
|
83
|
+
quality
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function exportPNG(canvas, options = {}) {
|
|
88
|
+
const { multiplier = 1, format = "png", quality = 1 } = options;
|
|
89
|
+
const output = canvas.toCanvasElement(multiplier);
|
|
90
|
+
return canvasElementToBlob(output, format, quality);
|
|
91
|
+
}
|
|
92
|
+
async function exportIsolatedPNG(source, objects, options = {}) {
|
|
93
|
+
const element = source.lowerCanvasEl.ownerDocument.createElement("canvas");
|
|
94
|
+
const canvas = new StaticCanvas(element, {
|
|
95
|
+
width: options.width ?? source.getWidth(),
|
|
96
|
+
height: options.height ?? source.getHeight(),
|
|
97
|
+
backgroundColor: options.backgroundColor || void 0
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
const clones = options.cloneObjects === false ? objects : await Promise.all(objects.map((object) => object.clone()));
|
|
101
|
+
if (clones.length) canvas.add(...clones);
|
|
102
|
+
if (options.backgroundImage) canvas.backgroundImage = await options.backgroundImage.clone();
|
|
103
|
+
canvas.requestRenderAll();
|
|
104
|
+
return await exportPNG(canvas, options);
|
|
105
|
+
} finally {
|
|
106
|
+
canvas.dispose();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function exportMockup(canvas, mockup, options = {}) {
|
|
110
|
+
const { multiplier = 1, format = "png", quality = 1 } = options;
|
|
111
|
+
const design = canvas.toCanvasElement(multiplier);
|
|
112
|
+
const output = design.ownerDocument.createElement("canvas");
|
|
113
|
+
output.width = design.width;
|
|
114
|
+
output.height = design.height;
|
|
115
|
+
const context = output.getContext("2d");
|
|
116
|
+
if (!context) throw new Error("2D canvas context is unavailable");
|
|
117
|
+
const loadImage = (url) => new Promise((resolve, reject) => {
|
|
118
|
+
const element = new Image();
|
|
119
|
+
element.crossOrigin = "anonymous";
|
|
120
|
+
element.onload = () => resolve(element);
|
|
121
|
+
element.onerror = () => reject(new Error(`Failed to load mockup image: ${url}`));
|
|
122
|
+
element.src = url;
|
|
123
|
+
});
|
|
124
|
+
const drawCover = (image, targetContext = context) => {
|
|
125
|
+
const placement = computeCoverPlacement(
|
|
126
|
+
image.naturalWidth || image.width,
|
|
127
|
+
image.naturalHeight || image.height,
|
|
128
|
+
output.width,
|
|
129
|
+
output.height
|
|
130
|
+
);
|
|
131
|
+
targetContext.drawImage(
|
|
132
|
+
image,
|
|
133
|
+
placement.left,
|
|
134
|
+
placement.top,
|
|
135
|
+
placement.width,
|
|
136
|
+
placement.height
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
const scratch = [design];
|
|
140
|
+
try {
|
|
141
|
+
drawCover(await loadImage(mockup.image));
|
|
142
|
+
let compositedDesign = design;
|
|
143
|
+
if (mockup.displacement) {
|
|
144
|
+
const sourceContext = design.getContext("2d");
|
|
145
|
+
if (!sourceContext) throw new Error("2D design context is unavailable");
|
|
146
|
+
const mapCanvas = design.ownerDocument.createElement("canvas");
|
|
147
|
+
scratch.push(mapCanvas);
|
|
148
|
+
mapCanvas.width = design.width;
|
|
149
|
+
mapCanvas.height = design.height;
|
|
150
|
+
const mapContext = mapCanvas.getContext("2d");
|
|
151
|
+
if (!mapContext) throw new Error("2D displacement-map context is unavailable");
|
|
152
|
+
drawCover(await loadImage(mockup.displacement.image), mapContext);
|
|
153
|
+
const warped = design.ownerDocument.createElement("canvas");
|
|
154
|
+
scratch.push(warped);
|
|
155
|
+
warped.width = design.width;
|
|
156
|
+
warped.height = design.height;
|
|
157
|
+
const warpedContext = warped.getContext("2d");
|
|
158
|
+
if (!warpedContext) throw new Error("2D displaced-design context is unavailable");
|
|
159
|
+
let sourcePixels;
|
|
160
|
+
let mapPixels;
|
|
161
|
+
try {
|
|
162
|
+
sourcePixels = sourceContext.getImageData(0, 0, design.width, design.height).data;
|
|
163
|
+
mapPixels = mapContext.getImageData(0, 0, design.width, design.height).data;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error("Failed to apply mockup displacement map; verify image CORS access", {
|
|
166
|
+
cause: error
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const pixels = displaceRgba(sourcePixels, mapPixels, design.width, design.height, {
|
|
170
|
+
...mockup.displacement,
|
|
171
|
+
scaleX: (mockup.displacement.scaleX ?? 10) * multiplier,
|
|
172
|
+
scaleY: (mockup.displacement.scaleY ?? 10) * multiplier
|
|
173
|
+
});
|
|
174
|
+
const imageData = warpedContext.createImageData(design.width, design.height);
|
|
175
|
+
imageData.data.set(pixels);
|
|
176
|
+
warpedContext.putImageData(imageData, 0, 0);
|
|
177
|
+
compositedDesign = warped;
|
|
178
|
+
}
|
|
179
|
+
context.save();
|
|
180
|
+
if (mockup.printArea && mockup.clipToPrintArea !== false) {
|
|
181
|
+
const clip = computePrintAreaClip(
|
|
182
|
+
mockup.printArea,
|
|
183
|
+
output.width / canvas.getWidth(),
|
|
184
|
+
output.height / canvas.getHeight(),
|
|
185
|
+
output.width,
|
|
186
|
+
output.height
|
|
187
|
+
);
|
|
188
|
+
context.beginPath();
|
|
189
|
+
context.rect(clip.left, clip.top, clip.width, clip.height);
|
|
190
|
+
context.clip();
|
|
191
|
+
}
|
|
192
|
+
context.globalAlpha = Math.max(0, Math.min(1, mockup.designOpacity ?? 1));
|
|
193
|
+
context.globalCompositeOperation = !mockup.designBlendMode || mockup.designBlendMode === "normal" ? "source-over" : mockup.designBlendMode;
|
|
194
|
+
context.drawImage(compositedDesign, 0, 0);
|
|
195
|
+
context.restore();
|
|
196
|
+
if (mockup.overlay) {
|
|
197
|
+
context.save();
|
|
198
|
+
context.globalAlpha = Math.max(0, Math.min(1, mockup.overlay.opacity ?? 1));
|
|
199
|
+
context.globalCompositeOperation = mockup.overlay.blendMode === "normal" ? "source-over" : mockup.overlay.blendMode ?? "multiply";
|
|
200
|
+
drawCover(await loadImage(mockup.overlay.image));
|
|
201
|
+
context.restore();
|
|
202
|
+
}
|
|
203
|
+
return await canvasElementToBlob(output, format, quality);
|
|
204
|
+
} finally {
|
|
205
|
+
for (const element of scratch) {
|
|
206
|
+
element.width = 0;
|
|
207
|
+
element.height = 0;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function exportSVG(canvas) {
|
|
212
|
+
return canvas.toSVG();
|
|
213
|
+
}
|
|
214
|
+
function exportDataURL(canvas, format = "png", multiplier = 1) {
|
|
215
|
+
return canvas.toDataURL({ format, multiplier });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export {
|
|
219
|
+
displaceRgba,
|
|
220
|
+
computePrintAreaClip,
|
|
221
|
+
computeCoverPlacement,
|
|
222
|
+
exportPNG,
|
|
223
|
+
exportIsolatedPNG,
|
|
224
|
+
exportMockup,
|
|
225
|
+
exportSVG,
|
|
226
|
+
exportDataURL
|
|
227
|
+
};
|
|
228
|
+
//# sourceMappingURL=chunk-ORZZ6MGQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/displacement.ts","../src/export.ts"],"sourcesContent":["import type { MockupDisplacement, MockupDisplacementChannel } from './types';\n\nconst CHANNEL_INDEX: Record<MockupDisplacementChannel, number> = {\n red: 0,\n green: 1,\n blue: 2,\n alpha: 3,\n};\n\nfunction finiteScale(value: number | undefined, fallback: number, label: string): number {\n const resolved = value ?? fallback;\n if (!Number.isFinite(resolved)) throw new Error(`${label} must be finite`);\n return resolved;\n}\n\nfunction sample(\n source: Uint8ClampedArray,\n width: number,\n height: number,\n x: number,\n y: number,\n channel: number,\n): number {\n const clampedX = Math.max(0, Math.min(width - 1, x));\n const clampedY = Math.max(0, Math.min(height - 1, y));\n const x0 = Math.floor(clampedX);\n const y0 = Math.floor(clampedY);\n const x1 = Math.min(width - 1, x0 + 1);\n const y1 = Math.min(height - 1, y0 + 1);\n const tx = clampedX - x0;\n const ty = clampedY - y0;\n const top =\n source[(y0 * width + x0) * 4 + channel] * (1 - tx) +\n source[(y0 * width + x1) * 4 + channel] * tx;\n const bottom =\n source[(y1 * width + x0) * 4 + channel] * (1 - tx) +\n source[(y1 * width + x1) * 4 + channel] * tx;\n return top * (1 - ty) + bottom * ty;\n}\n\n/**\n * Warp RGBA pixels with an equally sized channel map. A channel value of 128\n * is neutral; 0 and 255 move by the configured negative/positive maximum.\n */\nexport function displaceRgba(\n source: Uint8ClampedArray,\n map: Uint8ClampedArray,\n width: number,\n height: number,\n options: Omit<MockupDisplacement, 'image'>,\n): Uint8ClampedArray {\n if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {\n throw new Error('Displacement dimensions must be positive integers');\n }\n const expectedLength = width * height * 4;\n if (source.length !== expectedLength || map.length !== expectedLength) {\n throw new Error('Displacement source and map must match the requested dimensions');\n }\n\n const scaleX = finiteScale(options.scaleX, 10, 'Displacement scaleX');\n const scaleY = finiteScale(options.scaleY, 10, 'Displacement scaleY');\n const channelX = CHANNEL_INDEX[options.channelX ?? 'red'];\n const channelY = CHANNEL_INDEX[options.channelY ?? 'green'];\n const output = new Uint8ClampedArray(expectedLength);\n\n for (let y = 0; y < height; y += 1) {\n for (let x = 0; x < width; x += 1) {\n const offset = (y * width + x) * 4;\n const sourceX = x + ((map[offset + channelX] - 128) / 127) * scaleX;\n const sourceY = y + ((map[offset + channelY] - 128) / 127) * scaleY;\n for (let channel = 0; channel < 4; channel += 1) {\n output[offset + channel] = Math.round(\n sample(source, width, height, sourceX, sourceY, channel),\n );\n }\n }\n }\n return output;\n}\n","import { StaticCanvas } from 'fabric';\nimport type { Canvas, FabricObject, ImageFormat } from 'fabric';\nimport type { MockupConfig, MockupPrintArea } from './types';\nimport { displaceRgba } from './displacement';\n\nexport interface PngExportOptions {\n multiplier?: number;\n format?: ImageFormat;\n quality?: number;\n}\n\nexport interface CoverPlacement {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport function computePrintAreaClip(\n area: MockupPrintArea,\n scaleX: number,\n scaleY: number,\n targetWidth: number,\n targetHeight: number,\n): MockupPrintArea {\n const left = Math.max(0, Math.min(targetWidth, area.left * scaleX));\n const top = Math.max(0, Math.min(targetHeight, area.top * scaleY));\n const right = Math.max(left, Math.min(targetWidth, (area.left + area.width) * scaleX));\n const bottom = Math.max(top, Math.min(targetHeight, (area.top + area.height) * scaleY));\n return { left, top, width: right - left, height: bottom - top };\n}\n\n/** Object-fit: cover geometry, exported for deterministic preview/composite tests. */\nexport function computeCoverPlacement(\n sourceWidth: number,\n sourceHeight: number,\n targetWidth: number,\n targetHeight: number,\n): CoverPlacement {\n if (sourceWidth <= 0 || sourceHeight <= 0 || targetWidth <= 0 || targetHeight <= 0) {\n throw new Error('Cover dimensions must be positive');\n }\n const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight);\n const width = sourceWidth * scale;\n const height = sourceHeight * scale;\n return {\n left: (targetWidth - width) / 2,\n top: (targetHeight - height) / 2,\n width,\n height,\n };\n}\n\nfunction canvasElementToBlob(\n output: HTMLCanvasElement,\n format: ImageFormat,\n quality: number,\n): Promise<Blob> {\n const mime = format === 'jpeg' ? 'image/jpeg' : `image/${format}`;\n return new Promise<Blob>((resolve, reject) => {\n output.toBlob(\n (blob) => (blob ? resolve(blob) : reject(new Error(`Failed to export ${format}`))),\n mime,\n quality,\n );\n });\n}\n\nexport async function exportPNG(canvas: Canvas, options: PngExportOptions = {}): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const output = canvas.toCanvasElement(multiplier);\n return canvasElementToBlob(output, format, quality);\n}\n\n/** Render cloned objects without mutating the live editor canvas. */\nexport async function exportIsolatedPNG(\n source: Canvas,\n objects: FabricObject[],\n options: PngExportOptions & {\n width?: number;\n height?: number;\n backgroundColor?: string;\n backgroundImage?: FabricObject | null;\n cloneObjects?: boolean;\n } = {},\n): Promise<Blob> {\n const element = source.lowerCanvasEl.ownerDocument.createElement('canvas');\n const canvas = new StaticCanvas(element, {\n width: options.width ?? source.getWidth(),\n height: options.height ?? source.getHeight(),\n backgroundColor: options.backgroundColor || undefined,\n });\n try {\n const clones =\n options.cloneObjects === false\n ? objects\n : await Promise.all(objects.map((object) => object.clone()));\n if (clones.length) canvas.add(...clones);\n if (options.backgroundImage) canvas.backgroundImage = await options.backgroundImage.clone();\n canvas.requestRenderAll();\n // Awaited, not returned: `finally` would otherwise dispose the canvas while\n // the export is still reading from it.\n return await exportPNG(canvas as unknown as Canvas, options);\n } finally {\n canvas.dispose();\n }\n}\n\n/** Rasterize the browser mockup preview together with the transparent design. */\nexport async function exportMockup(\n canvas: Canvas,\n mockup: MockupConfig,\n options: PngExportOptions = {},\n): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const design = canvas.toCanvasElement(multiplier);\n const output = design.ownerDocument.createElement('canvas');\n output.width = design.width;\n output.height = design.height;\n const context = output.getContext('2d');\n if (!context) throw new Error('2D canvas context is unavailable');\n\n const loadImage = (url: string) =>\n new Promise<HTMLImageElement>((resolve, reject) => {\n const element = new Image();\n element.crossOrigin = 'anonymous';\n element.onload = () => resolve(element);\n element.onerror = () => reject(new Error(`Failed to load mockup image: ${url}`));\n element.src = url;\n });\n const drawCover = (\n image: HTMLImageElement,\n targetContext: CanvasRenderingContext2D = context,\n ) => {\n const placement = computeCoverPlacement(\n image.naturalWidth || image.width,\n image.naturalHeight || image.height,\n output.width,\n output.height,\n );\n targetContext.drawImage(\n image,\n placement.left,\n placement.top,\n placement.width,\n placement.height,\n );\n };\n\n // Full-size scratch buffers, released explicitly in the finally below: a\n // detached canvas element can hold its backing store well past its last\n // reference, and a 4K mockup allocates three of them per export.\n const scratch: HTMLCanvasElement[] = [design];\n try {\n drawCover(await loadImage(mockup.image));\n let compositedDesign: CanvasImageSource = design;\n if (mockup.displacement) {\n const sourceContext = design.getContext('2d');\n if (!sourceContext) throw new Error('2D design context is unavailable');\n const mapCanvas = design.ownerDocument.createElement('canvas');\n scratch.push(mapCanvas);\n mapCanvas.width = design.width;\n mapCanvas.height = design.height;\n const mapContext = mapCanvas.getContext('2d');\n if (!mapContext) throw new Error('2D displacement-map context is unavailable');\n drawCover(await loadImage(mockup.displacement.image), mapContext);\n\n const warped = design.ownerDocument.createElement('canvas');\n scratch.push(warped);\n warped.width = design.width;\n warped.height = design.height;\n const warpedContext = warped.getContext('2d');\n if (!warpedContext) throw new Error('2D displaced-design context is unavailable');\n let sourcePixels: Uint8ClampedArray;\n let mapPixels: Uint8ClampedArray;\n try {\n sourcePixels = sourceContext.getImageData(0, 0, design.width, design.height).data;\n mapPixels = mapContext.getImageData(0, 0, design.width, design.height).data;\n } catch (error) {\n throw new Error('Failed to apply mockup displacement map; verify image CORS access', {\n cause: error,\n });\n }\n const pixels = displaceRgba(sourcePixels, mapPixels, design.width, design.height, {\n ...mockup.displacement,\n scaleX: (mockup.displacement.scaleX ?? 10) * multiplier,\n scaleY: (mockup.displacement.scaleY ?? 10) * multiplier,\n });\n const imageData = warpedContext.createImageData(design.width, design.height);\n imageData.data.set(pixels);\n warpedContext.putImageData(imageData, 0, 0);\n compositedDesign = warped;\n }\n context.save();\n if (mockup.printArea && mockup.clipToPrintArea !== false) {\n const clip = computePrintAreaClip(\n mockup.printArea,\n output.width / canvas.getWidth(),\n output.height / canvas.getHeight(),\n output.width,\n output.height,\n );\n context.beginPath();\n context.rect(clip.left, clip.top, clip.width, clip.height);\n context.clip();\n }\n context.globalAlpha = Math.max(0, Math.min(1, mockup.designOpacity ?? 1));\n context.globalCompositeOperation =\n !mockup.designBlendMode || mockup.designBlendMode === 'normal'\n ? 'source-over'\n : mockup.designBlendMode;\n context.drawImage(compositedDesign, 0, 0);\n context.restore();\n\n if (mockup.overlay) {\n context.save();\n context.globalAlpha = Math.max(0, Math.min(1, mockup.overlay.opacity ?? 1));\n context.globalCompositeOperation =\n mockup.overlay.blendMode === 'normal'\n ? 'source-over'\n : (mockup.overlay.blendMode ?? 'multiply');\n drawCover(await loadImage(mockup.overlay.image));\n context.restore();\n }\n return await canvasElementToBlob(output, format, quality);\n } finally {\n for (const element of scratch) {\n element.width = 0;\n element.height = 0;\n }\n }\n}\n\nexport function exportSVG(canvas: Canvas): string {\n return canvas.toSVG();\n}\n\nexport function exportDataURL(canvas: Canvas, format: ImageFormat = 'png', multiplier = 1): string {\n return canvas.toDataURL({ format, multiplier });\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Canvas, FabricObject, ImageFormat } from 'fabric';
|
|
2
|
-
import { b as EditorEvents, l as LayerType, k as LayerMeta, L as LayerData,
|
|
3
|
-
export { D as DEFAULT_PATTERN_CONFIG, d as ExportFormat, f as ImageAttribution, n as LicensePayload,
|
|
2
|
+
import { b as EditorEvents, l as LayerType, k as LayerMeta, L as LayerData, U as SerializedLayer, G as PatternSourceResolver, P as PatternConfig, A as PatternLocks, _ as Unit, z as MockupPrintArea, v as MockupConfig, e as FontDefinition, m as LicenseConfig, o as LicenseStatus, Q as ProjectState, M as MaskBrushOptions, p as MaskPoint, r as MaskRefinementProvider, q as MaskRefinementPrompt, t as MaskRefinementResult, E as EditorConfig, V as ShapePlugin, X as TemplateDefinition, I as ImageAdjustments, c as EditorState, S as SemanticExportOptions, W as SvgExportOptions, K as PrintifyPositioning, N as NormalizedLayerPosition, J as PositioningAdapter, h as ImageProviderResult, i as ImageSearchOptions, j as ImageSearchResult, F as FileAdapter, g as ImageProvider, T as SerializedBackgroundImageOptions, B as BackgroundImageOptions, R as ResizeOptions, a as DpiIssue, C as CanvasSizePreset, w as MockupDisplacement } from './types-D60CfxL9.mjs';
|
|
3
|
+
export { D as DEFAULT_PATTERN_CONFIG, d as ExportFormat, f as ImageAttribution, n as LicensePayload, s as MaskRefinementRequest, u as MockupBlendMode, x as MockupDisplacementChannel, y as MockupOverlay, H as PatternState, O as ProjectPage, Y as TemplateParameter, Z as TileMode } from './types-D60CfxL9.mjs';
|
|
4
4
|
|
|
5
5
|
type EventHandler<T> = (data: T) => void;
|
|
6
6
|
declare class EventEmitter<TEvents extends {} = Record<string, unknown>> {
|
|
@@ -53,8 +53,12 @@ declare class LayerManager {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
declare class HistoryManager {
|
|
56
|
+
private static readonly ASSET_KEY;
|
|
56
57
|
private undoStack;
|
|
57
58
|
private redoStack;
|
|
59
|
+
private assets;
|
|
60
|
+
private assetIds;
|
|
61
|
+
private nextAssetId;
|
|
58
62
|
private maxSize;
|
|
59
63
|
private maxBytes;
|
|
60
64
|
private paused;
|
|
@@ -94,6 +98,21 @@ declare class HistoryManager {
|
|
|
94
98
|
private emitChanged;
|
|
95
99
|
private snapshotBytes;
|
|
96
100
|
private trimToBudget;
|
|
101
|
+
/**
|
|
102
|
+
* History is internal and can content-address large raster strings without
|
|
103
|
+
* changing the public EditorState wire format. Unchanged images are retained
|
|
104
|
+
* once even when dozens of snapshots reference them.
|
|
105
|
+
*/
|
|
106
|
+
private compactState;
|
|
107
|
+
private expandState;
|
|
108
|
+
/**
|
|
109
|
+
* Runs on every commit, so it scans for the serialized reference marker
|
|
110
|
+
* instead of re-parsing every snapshot — parsing multi-megabyte raster states
|
|
111
|
+
* per save would cost more than the retention it reclaims. `JSON.stringify`
|
|
112
|
+
* emits the marker verbatim; the same text inside user data is escaped and
|
|
113
|
+
* therefore cannot match.
|
|
114
|
+
*/
|
|
115
|
+
private pruneAssets;
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
/**
|
|
@@ -308,6 +327,60 @@ declare class ProjectManager {
|
|
|
308
327
|
private emitChanged;
|
|
309
328
|
}
|
|
310
329
|
|
|
330
|
+
type MaskRefinementErrorCode = 'not-found' | 'cancelled' | 'provider' | 'invalid-result';
|
|
331
|
+
declare class MaskRefinementError extends Error {
|
|
332
|
+
readonly code: MaskRefinementErrorCode;
|
|
333
|
+
readonly cause?: unknown | undefined;
|
|
334
|
+
constructor(code: MaskRefinementErrorCode, message: string, cause?: unknown | undefined);
|
|
335
|
+
}
|
|
336
|
+
interface MaskPerformanceSample {
|
|
337
|
+
width: number;
|
|
338
|
+
height: number;
|
|
339
|
+
backingBytes: number;
|
|
340
|
+
strokeBackupBytes: number;
|
|
341
|
+
estimatedPeakBytes: number;
|
|
342
|
+
historyBytes: number;
|
|
343
|
+
interactionLatencyMs: number;
|
|
344
|
+
usedJsHeapBytes?: number;
|
|
345
|
+
elapsedMs: number;
|
|
346
|
+
}
|
|
347
|
+
/** Raster-mask editing without exposing Fabric brush classes in the public contract. */
|
|
348
|
+
declare class MaskController {
|
|
349
|
+
private readonly editor;
|
|
350
|
+
private backing;
|
|
351
|
+
private context;
|
|
352
|
+
private layerId;
|
|
353
|
+
private brush;
|
|
354
|
+
private previousPoint;
|
|
355
|
+
private strokeBackup;
|
|
356
|
+
private strokeStartedAt;
|
|
357
|
+
private lastInteractionLatencyMs;
|
|
358
|
+
private refinement;
|
|
359
|
+
private disposed;
|
|
360
|
+
constructor(editor: CanvasEditor);
|
|
361
|
+
create(width?: number, height?: number): Promise<Layer>;
|
|
362
|
+
attach(layerId: string): void;
|
|
363
|
+
beginStroke(options: MaskBrushOptions): void;
|
|
364
|
+
addPoint(point: MaskPoint): void;
|
|
365
|
+
endStroke(): Promise<void>;
|
|
366
|
+
cancelStroke(): void;
|
|
367
|
+
isStrokeActive(): boolean;
|
|
368
|
+
activeLayerId(): string | null;
|
|
369
|
+
detach(layerId?: string): void;
|
|
370
|
+
refine(layerId: string, provider: MaskRefinementProvider, prompts: MaskRefinementPrompt[], options?: {
|
|
371
|
+
signal?: AbortSignal;
|
|
372
|
+
onProgress?: (progress: number) => void;
|
|
373
|
+
}): Promise<MaskRefinementResult>;
|
|
374
|
+
cancelRefinement(): void;
|
|
375
|
+
measure(): MaskPerformanceSample | null;
|
|
376
|
+
dispose(): void;
|
|
377
|
+
private drawDot;
|
|
378
|
+
private makeCanvas;
|
|
379
|
+
private attachBacking;
|
|
380
|
+
private requireMask;
|
|
381
|
+
private assertActive;
|
|
382
|
+
}
|
|
383
|
+
|
|
311
384
|
declare class CanvasEditor {
|
|
312
385
|
readonly canvas: Canvas;
|
|
313
386
|
readonly layers: LayerManager;
|
|
@@ -320,12 +393,14 @@ declare class CanvasEditor {
|
|
|
320
393
|
readonly fonts: FontRegistry;
|
|
321
394
|
readonly licensing: LicenseManager;
|
|
322
395
|
readonly pages: ProjectManager;
|
|
396
|
+
readonly masks: MaskController;
|
|
323
397
|
private fileAdapter?;
|
|
324
398
|
private imageProvider?;
|
|
325
399
|
private zoomLevel;
|
|
326
400
|
private mockup;
|
|
327
401
|
private designBackground;
|
|
328
402
|
private designBackgroundImage;
|
|
403
|
+
private backgroundImageOptions;
|
|
329
404
|
constructor(canvasElement: HTMLCanvasElement, config: EditorConfig);
|
|
330
405
|
addImage(url: string, options?: Partial<Record<string, unknown>>): Promise<Layer>;
|
|
331
406
|
/** Replace an image source without changing its layer identity or visual transform. */
|
|
@@ -350,6 +425,10 @@ declare class CanvasEditor {
|
|
|
350
425
|
toPNG(options?: PngExportOptions): Promise<Blob>;
|
|
351
426
|
toJPEG(options?: Omit<PngExportOptions, 'format'>): Promise<Blob>;
|
|
352
427
|
toWebP(options?: Omit<PngExportOptions, 'format'>): Promise<Blob>;
|
|
428
|
+
/** Export one layer in document coordinates or at its native image resolution. */
|
|
429
|
+
exportLayer(id: string, options?: SemanticExportOptions): Promise<Blob>;
|
|
430
|
+
/** Export only the configured document background, excluding design layers. */
|
|
431
|
+
exportBackground(options?: SemanticExportOptions): Promise<Blob>;
|
|
353
432
|
private toRaster;
|
|
354
433
|
toSVG(): string;
|
|
355
434
|
toSVGAsync(options?: SvgExportOptions): Promise<string>;
|
|
@@ -377,9 +456,10 @@ declare class CanvasEditor {
|
|
|
377
456
|
setBackground(color: string): void;
|
|
378
457
|
getDesignBackground(): string;
|
|
379
458
|
getDesignBackgroundImage(): FabricObject | null;
|
|
459
|
+
getBackgroundImageOptions(): SerializedBackgroundImageOptions | null;
|
|
380
460
|
setBackgroundImage(url: string | null, options?: BackgroundImageOptions): Promise<void>;
|
|
381
461
|
/** Used by state restoration and advanced integrations with an existing Fabric object. */
|
|
382
|
-
setBackgroundImageObject(image: FabricObject | null, save?: boolean): void;
|
|
462
|
+
setBackgroundImageObject(image: FabricObject | null, save?: boolean, options?: SerializedBackgroundImageOptions | null): void;
|
|
383
463
|
setTransparentBackground(): void;
|
|
384
464
|
resize(width: number, height: number, options?: ResizeOptions): void;
|
|
385
465
|
/** Effective source resolution for an image at its current physical size. */
|
|
@@ -437,4 +517,77 @@ declare function deserializeEditor(editor: CanvasEditor, state: EditorState): Pr
|
|
|
437
517
|
/** Common print and digital canvas sizes. */
|
|
438
518
|
declare const CANVAS_SIZE_PRESETS: readonly CanvasSizePreset[];
|
|
439
519
|
|
|
440
|
-
|
|
520
|
+
type AnnotationPrimitive = {
|
|
521
|
+
id: string;
|
|
522
|
+
type: 'point';
|
|
523
|
+
x: number;
|
|
524
|
+
y: number;
|
|
525
|
+
label?: string;
|
|
526
|
+
} | {
|
|
527
|
+
id: string;
|
|
528
|
+
type: 'rectangle';
|
|
529
|
+
left: number;
|
|
530
|
+
top: number;
|
|
531
|
+
width: number;
|
|
532
|
+
height: number;
|
|
533
|
+
label?: string;
|
|
534
|
+
} | {
|
|
535
|
+
id: string;
|
|
536
|
+
type: 'label';
|
|
537
|
+
x: number;
|
|
538
|
+
y: number;
|
|
539
|
+
text: string;
|
|
540
|
+
} | {
|
|
541
|
+
id: string;
|
|
542
|
+
type: 'path';
|
|
543
|
+
points: Array<{
|
|
544
|
+
x: number;
|
|
545
|
+
y: number;
|
|
546
|
+
}>;
|
|
547
|
+
label?: string;
|
|
548
|
+
};
|
|
549
|
+
interface ViewportTransform {
|
|
550
|
+
zoom: number;
|
|
551
|
+
panX: number;
|
|
552
|
+
panY: number;
|
|
553
|
+
devicePixelRatio?: number;
|
|
554
|
+
}
|
|
555
|
+
/** Host-owned annotations that never enter editor layers, history, serialization, or exports. */
|
|
556
|
+
declare class AnnotationOverlay {
|
|
557
|
+
private items;
|
|
558
|
+
private transform;
|
|
559
|
+
set(annotation: AnnotationPrimitive): void;
|
|
560
|
+
remove(id: string): boolean;
|
|
561
|
+
clear(): void;
|
|
562
|
+
getAll(): AnnotationPrimitive[];
|
|
563
|
+
setTransform(transform: ViewportTransform): void;
|
|
564
|
+
documentToViewport(point: {
|
|
565
|
+
x: number;
|
|
566
|
+
y: number;
|
|
567
|
+
}): {
|
|
568
|
+
x: number;
|
|
569
|
+
y: number;
|
|
570
|
+
};
|
|
571
|
+
viewportToDocument(point: {
|
|
572
|
+
x: number;
|
|
573
|
+
y: number;
|
|
574
|
+
}): {
|
|
575
|
+
x: number;
|
|
576
|
+
y: number;
|
|
577
|
+
};
|
|
578
|
+
documentToDevice(point: {
|
|
579
|
+
x: number;
|
|
580
|
+
y: number;
|
|
581
|
+
}): {
|
|
582
|
+
x: number;
|
|
583
|
+
y: number;
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Warp RGBA pixels with an equally sized channel map. A channel value of 128
|
|
589
|
+
* is neutral; 0 and 255 move by the configured negative/positive maximum.
|
|
590
|
+
*/
|
|
591
|
+
declare function displaceRgba(source: Uint8ClampedArray, map: Uint8ClampedArray, width: number, height: number, options: Omit<MockupDisplacement, 'image'>): Uint8ClampedArray;
|
|
592
|
+
|
|
593
|
+
export { AnnotationOverlay, type AnnotationPrimitive, BackgroundImageOptions, CANVAS_SIZE_PRESETS, CanvasEditor, CanvasSizePreset, type CoverPlacement, CropController, DpiIssue, EditorConfig, EditorEvents, EditorState, EventEmitter, FileAdapter, FontDefinition, FontRegistry, HistoryManager, ImageAdjustments, ImageProvider, ImageProviderResult, ImageSearchOptions, ImageSearchResult, Layer, LayerData, LayerManager, LayerMeta, LayerType, LicenseConfig, LicenseManager, LicenseStatus, MaskBrushOptions, MaskController, type MaskPerformanceSample, MaskPoint, MaskRefinementError, type MaskRefinementErrorCode, MaskRefinementPrompt, MaskRefinementProvider, MaskRefinementResult, MockupConfig, MockupDisplacement, MockupPrintArea, NormalizedLayerPosition, PatternConfig, PatternLocks, PatternManager, PatternSourceResolver, type PngExportOptions, PositioningAdapter, PrintifyPositioning, ProjectManager, ProjectState, ResizeOptions, SemanticExportOptions, SerializedBackgroundImageOptions, SerializedLayer, ShapePlugin, SnapManager, SvgExportOptions, TemplateDefinition, type TilePlacement, Unit, UnitConverter, type ViewportTransform, applyPatternLocks, buildPatternDataURL, captureLocks, clamp, clearPatternImageCache, computeCoverPlacement, computePrintAreaClip, computeTilePositions, deserializeEditor, displaceRgba, drawTiles, escapeXml, exportDataURL, exportMockup, exportPNG, exportSVG, generateId, isCssColor, loadPatternImage, restoreLocks, round2, sanitizeSvg, serializeEditor };
|