muse-image-editor 1.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/dist/ImageEditor.d.ts +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +714 -0
- package/dist/types.d.ts +39 -0
- package/dist/useMaskCanvas.d.ts +55 -0
- package/package.json +33 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { ImageEditorProps } from './types';
|
|
2
|
+
export declare function ImageEditor({ isOpen, onClose, onSave, onStepComplete, imageUrl, onGenerate, referenceImages, defaultReferenceIds, presets, initialHistory, imageResolution, title, }: ImageEditorProps): import("react/jsx-runtime").JSX.Element | null;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ImageEditor } from './ImageEditor';
|
|
2
|
+
export type { ImageEditorProps, EditHistoryStep, EditorPreset, EditorReferenceImage, GenerateRequest, } from './types';
|
|
3
|
+
export type { BrushStroke, UseMaskCanvasReturn } from './useMaskCanvas';
|
|
4
|
+
export { useMaskCanvas, replayStrokes } from './useMaskCanvas';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import { jsxs as c, jsx as r, Fragment as ce } from "react/jsx-runtime";
|
|
2
|
+
import ge, { useState as U, useRef as B, useCallback as C, useEffect as V } from "react";
|
|
3
|
+
import { Paintbrush as xe, Eraser as pe, Trash2 as fe, Undo2 as be, Redo2 as ve, X as we, ZoomOut as ze, ZoomIn as ye, Loader2 as me, ChevronRight as ke, ChevronDown as Ce, Sparkles as Ne, Check as he } from "lucide-react";
|
|
4
|
+
function Ie(s, u) {
|
|
5
|
+
const y = Math.max(4, s * u), x = y / 2, N = y + 2, l = N / 2, g = `<svg xmlns='http://www.w3.org/2000/svg' width='${N}' height='${N}'><circle cx='${l}' cy='${l}' r='${x}' fill='none' stroke='white' stroke-width='1.5'/><circle cx='${l}' cy='${l}' r='${x}' fill='none' stroke='black' stroke-width='0.75'/></svg>`;
|
|
6
|
+
return `url("data:image/svg+xml,${encodeURIComponent(g)}") ${N / 2} ${N / 2}, crosshair`;
|
|
7
|
+
}
|
|
8
|
+
function de(s, u, y, x, N) {
|
|
9
|
+
s.clearRect(0, 0, y, x);
|
|
10
|
+
for (const l of u) {
|
|
11
|
+
if (s.save(), l.tool === "eraser" ? (s.globalCompositeOperation = "destination-out", s.globalAlpha = 1) : (s.globalCompositeOperation = "source-over", s.globalAlpha = N), s.strokeStyle = "white", s.lineWidth = l.size, s.lineCap = "round", s.lineJoin = "round", s.beginPath(), l.points.length === 1) {
|
|
12
|
+
const g = l.points[0];
|
|
13
|
+
s.arc(g.x, g.y, l.size / 2, 0, Math.PI * 2), s.fillStyle = "white", s.fill();
|
|
14
|
+
} else {
|
|
15
|
+
s.moveTo(l.points[0].x, l.points[0].y);
|
|
16
|
+
for (let g = 1; g < l.points.length; g++)
|
|
17
|
+
s.lineTo(l.points[g].x, l.points[g].y);
|
|
18
|
+
s.stroke();
|
|
19
|
+
}
|
|
20
|
+
s.restore();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function Me(s = {}) {
|
|
24
|
+
const { enableShortcuts: u = !0, isActive: y = !0 } = s, [x, N] = U("brush"), [l, g] = U(24), [b, I] = U(1), [d, k] = U({ x: 0, y: 0 }), w = B(null), a = B(null), S = B(null), T = B(!1), M = B(null), Z = B(!1), F = B({ x: 0, y: 0 }), E = B({ x: 0, y: 0 }), [z, j] = U([]), [$, A] = U([]), D = B(null), [R, f] = U({ width: 0, height: 0 }), K = C((n) => {
|
|
25
|
+
const t = S.current;
|
|
26
|
+
if (!t) return { x: 0, y: 0 };
|
|
27
|
+
const i = t.getBoundingClientRect(), o = t.width / i.width, h = t.height / i.height;
|
|
28
|
+
return {
|
|
29
|
+
x: (n.clientX - i.left) * o,
|
|
30
|
+
y: (n.clientY - i.top) * h
|
|
31
|
+
};
|
|
32
|
+
}, []), O = C((n) => {
|
|
33
|
+
const t = S.current;
|
|
34
|
+
if (!t) return;
|
|
35
|
+
const i = t.getContext("2d");
|
|
36
|
+
i && de(i, n, t.width, t.height, 0.5);
|
|
37
|
+
}, []), ee = C((n) => {
|
|
38
|
+
if (n.button === 1) {
|
|
39
|
+
n.preventDefault(), Z.current = !0, F.current = { x: n.clientX, y: n.clientY }, E.current = { ...d }, n.target.setPointerCapture(n.pointerId);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (n.button !== 0) return;
|
|
43
|
+
n.preventDefault(), n.target.setPointerCapture(n.pointerId), T.current = !0;
|
|
44
|
+
const t = K(n);
|
|
45
|
+
M.current = t, D.current = {
|
|
46
|
+
points: [t],
|
|
47
|
+
size: l,
|
|
48
|
+
tool: x
|
|
49
|
+
};
|
|
50
|
+
const i = S.current;
|
|
51
|
+
if (!i) return;
|
|
52
|
+
const o = i.getContext("2d");
|
|
53
|
+
o && (o.save(), x === "eraser" ? (o.globalCompositeOperation = "destination-out", o.globalAlpha = 1) : (o.globalCompositeOperation = "source-over", o.globalAlpha = 0.5), o.fillStyle = "white", o.beginPath(), o.arc(t.x, t.y, l / 2, 0, Math.PI * 2), o.fill(), o.restore());
|
|
54
|
+
}, [x, l, K, d]), te = C((n) => {
|
|
55
|
+
if (Z.current) {
|
|
56
|
+
const h = n.clientX - F.current.x, v = n.clientY - F.current.y;
|
|
57
|
+
k({ x: E.current.x + h, y: E.current.y + v });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!T.current || !D.current) return;
|
|
61
|
+
const t = K(n);
|
|
62
|
+
D.current.points.push(t);
|
|
63
|
+
const i = S.current;
|
|
64
|
+
if (!i) return;
|
|
65
|
+
const o = i.getContext("2d");
|
|
66
|
+
!o || !M.current || (o.save(), x === "eraser" ? (o.globalCompositeOperation = "destination-out", o.globalAlpha = 1) : (o.globalCompositeOperation = "source-over", o.globalAlpha = 0.5), o.strokeStyle = "white", o.lineWidth = l, o.lineCap = "round", o.lineJoin = "round", o.beginPath(), o.moveTo(M.current.x, M.current.y), o.lineTo(t.x, t.y), o.stroke(), o.restore(), M.current = t);
|
|
67
|
+
}, [x, l, K]), ne = C((n) => {
|
|
68
|
+
if (Z.current) {
|
|
69
|
+
Z.current = !1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (T.current && (T.current = !1, M.current = null, D.current)) {
|
|
73
|
+
const t = D.current;
|
|
74
|
+
D.current = null, j((i) => {
|
|
75
|
+
const o = [...i, t];
|
|
76
|
+
return O(o), o;
|
|
77
|
+
}), A([]);
|
|
78
|
+
}
|
|
79
|
+
}, [O]), X = C(() => {
|
|
80
|
+
j((n) => {
|
|
81
|
+
if (n.length === 0) return n;
|
|
82
|
+
const t = n[n.length - 1];
|
|
83
|
+
A((o) => [t, ...o]);
|
|
84
|
+
const i = n.slice(0, -1);
|
|
85
|
+
return O(i), i;
|
|
86
|
+
});
|
|
87
|
+
}, [O]), q = C(() => {
|
|
88
|
+
A((n) => {
|
|
89
|
+
if (n.length === 0) return n;
|
|
90
|
+
const [t, ...i] = n;
|
|
91
|
+
return j((o) => {
|
|
92
|
+
const h = [...o, t];
|
|
93
|
+
return O(h), h;
|
|
94
|
+
}), i;
|
|
95
|
+
});
|
|
96
|
+
}, [O]), re = C(() => {
|
|
97
|
+
const n = S.current;
|
|
98
|
+
if (!n) return;
|
|
99
|
+
const t = n.getContext("2d");
|
|
100
|
+
t && t.clearRect(0, 0, n.width, n.height), j([]), A([]);
|
|
101
|
+
}, []), ae = C(() => I((n) => Math.min(5, n + 0.25)), []), ie = C(() => I((n) => Math.max(0.1, n - 0.25)), []), se = C((n) => {
|
|
102
|
+
n.preventDefault();
|
|
103
|
+
const t = n.deltaY > 0 ? -0.1 : 0.1;
|
|
104
|
+
I((i) => Math.min(5, Math.max(0.1, i + t)));
|
|
105
|
+
}, []);
|
|
106
|
+
V(() => {
|
|
107
|
+
if (!u || !y) return;
|
|
108
|
+
const n = (t) => {
|
|
109
|
+
t.key === "[" ? (t.preventDefault(), g((i) => Math.max(4, i - 4))) : t.key === "]" ? (t.preventDefault(), g((i) => Math.min(100, i + 4))) : t.key === "z" && (t.ctrlKey || t.metaKey) && t.shiftKey ? (t.preventDefault(), q()) : t.key === "z" && (t.ctrlKey || t.metaKey) && !t.shiftKey && (t.preventDefault(), X());
|
|
110
|
+
};
|
|
111
|
+
return window.addEventListener("keydown", n), () => window.removeEventListener("keydown", n);
|
|
112
|
+
}, [u, y, X, q]);
|
|
113
|
+
const le = C((n, t) => new Promise((i, o) => {
|
|
114
|
+
const h = new Image();
|
|
115
|
+
h.crossOrigin = "anonymous", h.onload = () => {
|
|
116
|
+
const v = h.naturalWidth, P = h.naturalHeight;
|
|
117
|
+
f({ width: v, height: P });
|
|
118
|
+
const W = a.current;
|
|
119
|
+
if (W) {
|
|
120
|
+
W.width = v, W.height = P;
|
|
121
|
+
const L = W.getContext("2d");
|
|
122
|
+
L && (L.clearRect(0, 0, v, P), L.drawImage(h, 0, 0));
|
|
123
|
+
}
|
|
124
|
+
const Y = S.current;
|
|
125
|
+
if (Y) {
|
|
126
|
+
Y.width = v, Y.height = P;
|
|
127
|
+
const L = Y.getContext("2d");
|
|
128
|
+
L && L.clearRect(0, 0, v, P);
|
|
129
|
+
}
|
|
130
|
+
if (j([]), A([]), !t) {
|
|
131
|
+
k({ x: 0, y: 0 });
|
|
132
|
+
const L = w.current;
|
|
133
|
+
if (L && v > 0 && P > 0) {
|
|
134
|
+
const H = L.getBoundingClientRect(), Q = 40, J = H.width - Q, G = H.height - Q;
|
|
135
|
+
if (J > 0 && G > 0) {
|
|
136
|
+
const _ = Math.min(J / v, G / P, 1);
|
|
137
|
+
I(_);
|
|
138
|
+
} else
|
|
139
|
+
I(1);
|
|
140
|
+
} else
|
|
141
|
+
I(1);
|
|
142
|
+
}
|
|
143
|
+
i();
|
|
144
|
+
}, h.onerror = () => o(new Error("Failed to load image")), h.src = n;
|
|
145
|
+
}), []), oe = C(() => {
|
|
146
|
+
const n = a.current;
|
|
147
|
+
if (!n) return null;
|
|
148
|
+
const { width: t, height: i } = R;
|
|
149
|
+
if (t === 0 || i === 0) return null;
|
|
150
|
+
const o = document.createElement("canvas");
|
|
151
|
+
o.width = t, o.height = i;
|
|
152
|
+
const h = o.getContext("2d");
|
|
153
|
+
if (!h) return null;
|
|
154
|
+
if (h.drawImage(n, 0, 0), z.length > 0) {
|
|
155
|
+
const v = document.createElement("canvas");
|
|
156
|
+
v.width = t, v.height = i;
|
|
157
|
+
const P = v.getContext("2d");
|
|
158
|
+
P && (de(P, z, t, i, 1), h.drawImage(v, 0, 0));
|
|
159
|
+
}
|
|
160
|
+
return o.toDataURL("image/png");
|
|
161
|
+
}, [z, R]), e = C((n = 20) => {
|
|
162
|
+
const t = a.current;
|
|
163
|
+
if (!t || z.length === 0) return null;
|
|
164
|
+
const { width: i, height: o } = R;
|
|
165
|
+
if (i === 0 || o === 0) return null;
|
|
166
|
+
let h = 1 / 0, v = 1 / 0, P = -1 / 0, W = -1 / 0;
|
|
167
|
+
for (const J of z) {
|
|
168
|
+
if (J.tool === "eraser") continue;
|
|
169
|
+
const G = J.size / 2;
|
|
170
|
+
for (const _ of J.points)
|
|
171
|
+
h = Math.min(h, _.x - G), v = Math.min(v, _.y - G), P = Math.max(P, _.x + G), W = Math.max(W, _.y + G);
|
|
172
|
+
}
|
|
173
|
+
if (h === 1 / 0) return null;
|
|
174
|
+
h = Math.max(0, Math.floor(h - n)), v = Math.max(0, Math.floor(v - n)), P = Math.min(i, Math.ceil(P + n)), W = Math.min(o, Math.ceil(W + n));
|
|
175
|
+
const Y = P - h, L = W - v;
|
|
176
|
+
if (Y <= 0 || L <= 0) return null;
|
|
177
|
+
const H = document.createElement("canvas");
|
|
178
|
+
H.width = Y, H.height = L;
|
|
179
|
+
const Q = H.getContext("2d");
|
|
180
|
+
return Q ? (Q.drawImage(t, h, v, Y, L, 0, 0, Y, L), H.toDataURL("image/png")) : null;
|
|
181
|
+
}, [z, R]), m = C(() => {
|
|
182
|
+
const { width: n, height: t } = R;
|
|
183
|
+
if (n === 0 || t === 0 || z.length === 0) return null;
|
|
184
|
+
const i = document.createElement("canvas");
|
|
185
|
+
i.width = n, i.height = t;
|
|
186
|
+
const o = i.getContext("2d");
|
|
187
|
+
return o ? (de(o, z, n, t, 1), i) : null;
|
|
188
|
+
}, [z, R]), p = Ie(l, b);
|
|
189
|
+
return {
|
|
190
|
+
imageCanvasRef: a,
|
|
191
|
+
maskCanvasRef: S,
|
|
192
|
+
containerRef: w,
|
|
193
|
+
activeTool: x,
|
|
194
|
+
setActiveTool: N,
|
|
195
|
+
brushSize: l,
|
|
196
|
+
setBrushSize: g,
|
|
197
|
+
zoom: b,
|
|
198
|
+
pan: d,
|
|
199
|
+
handleZoomIn: ae,
|
|
200
|
+
handleZoomOut: ie,
|
|
201
|
+
handleWheel: se,
|
|
202
|
+
strokes: z,
|
|
203
|
+
redoStack: $,
|
|
204
|
+
handleUndo: X,
|
|
205
|
+
handleRedo: q,
|
|
206
|
+
handleClearMask: re,
|
|
207
|
+
handlePointerDown: ee,
|
|
208
|
+
handlePointerMove: te,
|
|
209
|
+
handlePointerUp: ne,
|
|
210
|
+
imageDimensions: R,
|
|
211
|
+
loadImage: le,
|
|
212
|
+
exportMaskedImage: oe,
|
|
213
|
+
exportCroppedRegion: e,
|
|
214
|
+
exportMask: m,
|
|
215
|
+
cursorStyle: p
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const De = (s, u, y) => new Promise((x, N) => {
|
|
219
|
+
const l = new Image(), g = new Image();
|
|
220
|
+
let b = 0;
|
|
221
|
+
const I = () => {
|
|
222
|
+
const k = y.width, w = y.height, S = y.getContext("2d").getImageData(0, 0, k, w), T = new Float32Array(k * w);
|
|
223
|
+
for (let D = 0; D < k * w; D++)
|
|
224
|
+
T[D] = S.data[D * 4] / 255;
|
|
225
|
+
const M = 8, Z = ue(T, k, w, M), F = ue(Z, k, w, M), E = document.createElement("canvas");
|
|
226
|
+
E.width = k, E.height = w;
|
|
227
|
+
const z = E.getContext("2d");
|
|
228
|
+
z.drawImage(l, 0, 0, k, w);
|
|
229
|
+
const j = z.getImageData(0, 0, k, w);
|
|
230
|
+
z.clearRect(0, 0, k, w), z.drawImage(g, 0, 0, k, w);
|
|
231
|
+
const $ = z.getImageData(0, 0, k, w), A = z.createImageData(k, w);
|
|
232
|
+
for (let D = 0; D < k * w; D++) {
|
|
233
|
+
const R = F[D], f = D * 4;
|
|
234
|
+
A.data[f] = Math.round(j.data[f] * (1 - R) + $.data[f] * R), A.data[f + 1] = Math.round(j.data[f + 1] * (1 - R) + $.data[f + 1] * R), A.data[f + 2] = Math.round(j.data[f + 2] * (1 - R) + $.data[f + 2] * R), A.data[f + 3] = 255;
|
|
235
|
+
}
|
|
236
|
+
z.putImageData(A, 0, 0), x(E.toDataURL("image/png"));
|
|
237
|
+
}, d = () => {
|
|
238
|
+
++b === 2 && I();
|
|
239
|
+
};
|
|
240
|
+
l.onload = d, l.onerror = N, l.src = s, g.onload = d, g.onerror = N, g.src = u;
|
|
241
|
+
}), ue = (s, u, y, x) => {
|
|
242
|
+
const N = new Float32Array(u * y), l = x * 2 + 1;
|
|
243
|
+
for (let b = 0; b < y; b++) {
|
|
244
|
+
let I = 0;
|
|
245
|
+
for (let d = -x; d <= x; d++)
|
|
246
|
+
I += s[b * u + Math.max(0, Math.min(u - 1, d))];
|
|
247
|
+
for (let d = 0; d < u; d++) {
|
|
248
|
+
N[b * u + d] = I / l;
|
|
249
|
+
const k = Math.min(u - 1, d + x + 1), w = Math.max(0, d - x);
|
|
250
|
+
I += s[b * u + k] - s[b * u + w];
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const g = new Float32Array(u * y);
|
|
254
|
+
for (let b = 0; b < u; b++) {
|
|
255
|
+
let I = 0;
|
|
256
|
+
for (let d = -x; d <= x; d++)
|
|
257
|
+
I += N[Math.max(0, Math.min(y - 1, d)) * u + b];
|
|
258
|
+
for (let d = 0; d < y; d++) {
|
|
259
|
+
g[d * u + b] = I / l;
|
|
260
|
+
const k = Math.min(y - 1, d + x + 1), w = Math.max(0, d - x);
|
|
261
|
+
I += N[k * u + b] - N[w * u + b];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return g;
|
|
265
|
+
};
|
|
266
|
+
function Ue({
|
|
267
|
+
isOpen: s,
|
|
268
|
+
onClose: u,
|
|
269
|
+
onSave: y,
|
|
270
|
+
onStepComplete: x,
|
|
271
|
+
imageUrl: N,
|
|
272
|
+
onGenerate: l,
|
|
273
|
+
referenceImages: g,
|
|
274
|
+
defaultReferenceIds: b,
|
|
275
|
+
presets: I,
|
|
276
|
+
initialHistory: d,
|
|
277
|
+
imageResolution: k,
|
|
278
|
+
title: w = "Image Editor"
|
|
279
|
+
}) {
|
|
280
|
+
const a = Me({ isActive: s }), [S, T] = U([]), [M, Z] = U(0), [F, E] = U(!0), [z, j] = U(!1), [$, A] = U([]), [D, R] = U(/* @__PURE__ */ new Set()), [f, K] = U(""), [O, ee] = U(null), [te, ne] = U(!1), X = B(!1);
|
|
281
|
+
V(() => {
|
|
282
|
+
if (!s) {
|
|
283
|
+
X.current = !1;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (d && d.length > 0 && !X.current) {
|
|
287
|
+
X.current = !0, T(d);
|
|
288
|
+
const e = d[d.length - 1];
|
|
289
|
+
Z(e.index), E(!0), a.loadImage(e.imageDataUrl).then(() => E(!1)).catch(() => E(!1));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
X.current || (X.current = !0, E(!0), a.loadImage(N).then(() => {
|
|
293
|
+
const e = a.imageCanvasRef.current;
|
|
294
|
+
if (e) {
|
|
295
|
+
const m = document.createElement("canvas");
|
|
296
|
+
m.width = e.width, m.height = e.height;
|
|
297
|
+
const p = m.getContext("2d");
|
|
298
|
+
p && p.drawImage(e, 0, 0), T([{
|
|
299
|
+
index: 0,
|
|
300
|
+
label: "Original",
|
|
301
|
+
imageDataUrl: m.toDataURL("image/png"),
|
|
302
|
+
prompt: ""
|
|
303
|
+
}]), Z(0);
|
|
304
|
+
}
|
|
305
|
+
E(!1);
|
|
306
|
+
}).catch(() => E(!1)));
|
|
307
|
+
}, [s, N, d]), V(() => {
|
|
308
|
+
s && b && b.length > 0 && A(b);
|
|
309
|
+
}, [s, b]);
|
|
310
|
+
const q = C((e) => {
|
|
311
|
+
R((m) => {
|
|
312
|
+
const p = new Set(m);
|
|
313
|
+
return p.has(e) ? p.delete(e) : p.add(e), p;
|
|
314
|
+
});
|
|
315
|
+
}, []);
|
|
316
|
+
V(() => {
|
|
317
|
+
if (!s) return;
|
|
318
|
+
const e = (m) => {
|
|
319
|
+
m.key === "Escape" && u();
|
|
320
|
+
};
|
|
321
|
+
return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
|
|
322
|
+
}, [s, u]), V(() => {
|
|
323
|
+
if (s)
|
|
324
|
+
return document.body.style.overflow = "hidden", () => {
|
|
325
|
+
document.body.style.overflow = "";
|
|
326
|
+
};
|
|
327
|
+
}, [s]);
|
|
328
|
+
const re = C((e) => {
|
|
329
|
+
A((m) => m.includes(e) ? m.filter((p) => p !== e) : [...m, e]);
|
|
330
|
+
}, []), ae = C(
|
|
331
|
+
(e) => {
|
|
332
|
+
Z(e.index), a.loadImage(e.imageDataUrl, !0), K(e.prompt);
|
|
333
|
+
},
|
|
334
|
+
[a.loadImage]
|
|
335
|
+
), ie = C(async () => {
|
|
336
|
+
if (!l || !f.trim()) return;
|
|
337
|
+
const e = S[M];
|
|
338
|
+
if (!e) return;
|
|
339
|
+
const m = a.strokes.length > 0 ? a.exportMaskedImage() : void 0;
|
|
340
|
+
ne(!0);
|
|
341
|
+
try {
|
|
342
|
+
const p = await l({
|
|
343
|
+
sourceImageDataUrl: e.imageDataUrl,
|
|
344
|
+
prompt: f.trim(),
|
|
345
|
+
maskedImageDataUrl: m || void 0,
|
|
346
|
+
selectedReferenceIds: $.length > 0 ? [...$] : void 0
|
|
347
|
+
});
|
|
348
|
+
let n = p;
|
|
349
|
+
if (z && m) {
|
|
350
|
+
const h = a.exportMask();
|
|
351
|
+
h && (n = await De(e.imageDataUrl, p, h));
|
|
352
|
+
}
|
|
353
|
+
const t = S.slice(0, M + 1), i = t.length, o = {
|
|
354
|
+
index: i,
|
|
355
|
+
label: f.trim().slice(0, 40),
|
|
356
|
+
imageDataUrl: n,
|
|
357
|
+
prompt: f.trim(),
|
|
358
|
+
maskDataUrl: m || void 0
|
|
359
|
+
};
|
|
360
|
+
T([...t, o]), Z(i), a.loadImage(n, !0), x && x(o);
|
|
361
|
+
} catch (p) {
|
|
362
|
+
console.error("[ImageEditor] Edit failed:", p), alert(`Edit failed: ${p.message || p}`);
|
|
363
|
+
} finally {
|
|
364
|
+
ne(!1);
|
|
365
|
+
}
|
|
366
|
+
}, [f, S, M, a, $, l, z, x]), se = C(() => {
|
|
367
|
+
const e = S[M];
|
|
368
|
+
!e || M === 0 || y(e.imageDataUrl);
|
|
369
|
+
}, [S, M, y]), le = C((e) => {
|
|
370
|
+
K(e.prompt), ee(e.id);
|
|
371
|
+
}, []);
|
|
372
|
+
if (!s) return null;
|
|
373
|
+
const oe = ge.useMemo(() => {
|
|
374
|
+
if (!g || g.length === 0) return [];
|
|
375
|
+
const e = /* @__PURE__ */ new Map();
|
|
376
|
+
for (const m of g) {
|
|
377
|
+
const p = m.category ?? "general";
|
|
378
|
+
e.has(p) || e.set(p, []), e.get(p).push(m);
|
|
379
|
+
}
|
|
380
|
+
return Array.from(e.entries()).sort(([m], [p]) => m.localeCompare(p));
|
|
381
|
+
}, [g]);
|
|
382
|
+
return /* @__PURE__ */ c("div", { className: "fixed inset-0 z-50 bg-black/90 flex flex-col", children: [
|
|
383
|
+
/* @__PURE__ */ c("div", { className: "flex items-center gap-3 px-4 py-2.5 bg-zinc-900 border-b border-zinc-800", children: [
|
|
384
|
+
/* @__PURE__ */ c(
|
|
385
|
+
"button",
|
|
386
|
+
{
|
|
387
|
+
onClick: () => a.setActiveTool("brush"),
|
|
388
|
+
className: `flex items-center gap-1.5 px-3 py-1.5 rounded text-sm font-medium transition-colors ${a.activeTool === "brush" ? "bg-purple-600 text-white" : "bg-zinc-800 text-zinc-400 hover:text-zinc-200"}`,
|
|
389
|
+
title: "Brush (paint mask)",
|
|
390
|
+
children: [
|
|
391
|
+
/* @__PURE__ */ r(xe, { size: 16 }),
|
|
392
|
+
"Brush"
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
/* @__PURE__ */ c(
|
|
397
|
+
"button",
|
|
398
|
+
{
|
|
399
|
+
onClick: () => a.setActiveTool("eraser"),
|
|
400
|
+
className: `flex items-center gap-1.5 px-3 py-1.5 rounded text-sm font-medium transition-colors ${a.activeTool === "eraser" ? "bg-purple-600 text-white" : "bg-zinc-800 text-zinc-400 hover:text-zinc-200"}`,
|
|
401
|
+
title: "Eraser (remove mask)",
|
|
402
|
+
children: [
|
|
403
|
+
/* @__PURE__ */ r(pe, { size: 16 }),
|
|
404
|
+
"Eraser"
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
),
|
|
408
|
+
/* @__PURE__ */ r("div", { className: "w-px h-6 bg-zinc-700" }),
|
|
409
|
+
/* @__PURE__ */ c("div", { className: "flex items-center gap-2", children: [
|
|
410
|
+
/* @__PURE__ */ r("span", { className: "text-xs text-zinc-500", children: "Size" }),
|
|
411
|
+
/* @__PURE__ */ r(
|
|
412
|
+
"input",
|
|
413
|
+
{
|
|
414
|
+
type: "range",
|
|
415
|
+
min: 4,
|
|
416
|
+
max: 500,
|
|
417
|
+
value: a.brushSize,
|
|
418
|
+
onChange: (e) => a.setBrushSize(Number(e.target.value)),
|
|
419
|
+
className: "w-28 accent-purple-500"
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
/* @__PURE__ */ r("span", { className: "text-xs text-zinc-400 w-8 text-right", children: a.brushSize })
|
|
423
|
+
] }),
|
|
424
|
+
/* @__PURE__ */ r("div", { className: "w-px h-6 bg-zinc-700" }),
|
|
425
|
+
/* @__PURE__ */ c(
|
|
426
|
+
"button",
|
|
427
|
+
{
|
|
428
|
+
onClick: a.handleClearMask,
|
|
429
|
+
className: "flex items-center gap-1.5 px-3 py-1.5 rounded text-sm text-zinc-400 bg-zinc-800 hover:text-zinc-200 hover:bg-zinc-700 transition-colors",
|
|
430
|
+
title: "Clear mask",
|
|
431
|
+
children: [
|
|
432
|
+
/* @__PURE__ */ r(fe, { size: 14 }),
|
|
433
|
+
"Clear"
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
),
|
|
437
|
+
/* @__PURE__ */ r("div", { className: "flex-1" }),
|
|
438
|
+
/* @__PURE__ */ r("span", { className: "text-sm text-zinc-400 font-medium", children: w }),
|
|
439
|
+
/* @__PURE__ */ r("div", { className: "flex-1" }),
|
|
440
|
+
/* @__PURE__ */ r(
|
|
441
|
+
"button",
|
|
442
|
+
{
|
|
443
|
+
onClick: a.handleUndo,
|
|
444
|
+
disabled: a.strokes.length === 0,
|
|
445
|
+
className: "p-1.5 rounded text-zinc-400 hover:text-zinc-200 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
|
|
446
|
+
title: "Undo (Ctrl+Z)",
|
|
447
|
+
children: /* @__PURE__ */ r(be, { size: 18 })
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
/* @__PURE__ */ r(
|
|
451
|
+
"button",
|
|
452
|
+
{
|
|
453
|
+
onClick: a.handleRedo,
|
|
454
|
+
disabled: a.redoStack.length === 0,
|
|
455
|
+
className: "p-1.5 rounded text-zinc-400 hover:text-zinc-200 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",
|
|
456
|
+
title: "Redo (Ctrl+Shift+Z)",
|
|
457
|
+
children: /* @__PURE__ */ r(ve, { size: 18 })
|
|
458
|
+
}
|
|
459
|
+
),
|
|
460
|
+
/* @__PURE__ */ r("div", { className: "w-px h-6 bg-zinc-700" }),
|
|
461
|
+
/* @__PURE__ */ r(
|
|
462
|
+
"button",
|
|
463
|
+
{
|
|
464
|
+
onClick: u,
|
|
465
|
+
className: "p-1.5 rounded text-zinc-400 hover:text-white hover:bg-zinc-700 transition-colors",
|
|
466
|
+
title: "Close editor",
|
|
467
|
+
children: /* @__PURE__ */ r(we, { size: 20 })
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
] }),
|
|
471
|
+
/* @__PURE__ */ c("div", { className: "flex flex-1 min-h-0", children: [
|
|
472
|
+
/* @__PURE__ */ c(
|
|
473
|
+
"div",
|
|
474
|
+
{
|
|
475
|
+
className: "flex-1 flex items-center justify-center bg-zinc-950 overflow-hidden relative",
|
|
476
|
+
onWheel: a.handleWheel,
|
|
477
|
+
children: [
|
|
478
|
+
/* @__PURE__ */ c(
|
|
479
|
+
"div",
|
|
480
|
+
{
|
|
481
|
+
ref: a.containerRef,
|
|
482
|
+
style: {
|
|
483
|
+
transform: `translate(${a.pan.x}px, ${a.pan.y}px) scale(${a.zoom})`,
|
|
484
|
+
width: a.imageDimensions.width > 0 ? a.imageDimensions.width : void 0,
|
|
485
|
+
height: a.imageDimensions.height > 0 ? a.imageDimensions.height : void 0,
|
|
486
|
+
transformOrigin: "center center",
|
|
487
|
+
position: "relative"
|
|
488
|
+
},
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ r(
|
|
491
|
+
"canvas",
|
|
492
|
+
{
|
|
493
|
+
ref: a.imageCanvasRef,
|
|
494
|
+
style: { position: "absolute", top: 0, left: 0 }
|
|
495
|
+
}
|
|
496
|
+
),
|
|
497
|
+
/* @__PURE__ */ r(
|
|
498
|
+
"canvas",
|
|
499
|
+
{
|
|
500
|
+
ref: a.maskCanvasRef,
|
|
501
|
+
style: {
|
|
502
|
+
position: "absolute",
|
|
503
|
+
top: 0,
|
|
504
|
+
left: 0,
|
|
505
|
+
cursor: a.cursorStyle
|
|
506
|
+
},
|
|
507
|
+
onPointerDown: a.handlePointerDown,
|
|
508
|
+
onPointerMove: a.handlePointerMove,
|
|
509
|
+
onPointerUp: a.handlePointerUp,
|
|
510
|
+
onPointerCancel: a.handlePointerUp,
|
|
511
|
+
onContextMenu: (e) => e.preventDefault()
|
|
512
|
+
}
|
|
513
|
+
)
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
/* @__PURE__ */ c("div", { className: "absolute bottom-4 right-4 flex items-center gap-1 bg-zinc-900/80 rounded-lg px-2 py-1 border border-zinc-800", children: [
|
|
518
|
+
/* @__PURE__ */ r("button", { onClick: a.handleZoomOut, className: "p-1 text-zinc-400 hover:text-white transition-colors", title: "Zoom out", children: /* @__PURE__ */ r(ze, { size: 16 }) }),
|
|
519
|
+
/* @__PURE__ */ c("span", { className: "text-xs text-zinc-400 w-12 text-center", children: [
|
|
520
|
+
Math.round(a.zoom * 100),
|
|
521
|
+
"%"
|
|
522
|
+
] }),
|
|
523
|
+
/* @__PURE__ */ r("button", { onClick: a.handleZoomIn, className: "p-1 text-zinc-400 hover:text-white transition-colors", title: "Zoom in", children: /* @__PURE__ */ r(ye, { size: 16 }) })
|
|
524
|
+
] }),
|
|
525
|
+
a.imageDimensions.width > 0 && /* @__PURE__ */ c("div", { className: "absolute bottom-4 left-4 text-xs text-zinc-600", children: [
|
|
526
|
+
a.imageDimensions.width,
|
|
527
|
+
" x ",
|
|
528
|
+
a.imageDimensions.height
|
|
529
|
+
] }),
|
|
530
|
+
F && /* @__PURE__ */ r("div", { className: "absolute inset-0 flex items-center justify-center bg-zinc-950", children: /* @__PURE__ */ r(me, { size: 24, className: "text-zinc-500 animate-spin" }) })
|
|
531
|
+
]
|
|
532
|
+
}
|
|
533
|
+
),
|
|
534
|
+
/* @__PURE__ */ c("div", { className: "w-80 bg-zinc-900 border-l border-zinc-800 flex flex-col overflow-y-auto", children: [
|
|
535
|
+
g && g.length > 0 && oe.length > 0 && /* @__PURE__ */ c("div", { className: "border-b border-zinc-800", children: [
|
|
536
|
+
/* @__PURE__ */ r("div", { className: "px-3 pt-3 pb-1", children: /* @__PURE__ */ c("h3", { className: "text-xs font-semibold text-zinc-400 uppercase tracking-wider", children: [
|
|
537
|
+
"Reference Images",
|
|
538
|
+
$.length > 0 && /* @__PURE__ */ c("span", { className: "ml-2 text-[10px] text-zinc-500 normal-case font-normal", children: [
|
|
539
|
+
$.length,
|
|
540
|
+
" selected"
|
|
541
|
+
] })
|
|
542
|
+
] }) }),
|
|
543
|
+
oe.map(([e, m]) => {
|
|
544
|
+
const p = D.has(e), n = m.filter((t) => $.includes(t.id)).length;
|
|
545
|
+
return /* @__PURE__ */ c("div", { className: "border-t border-zinc-800/50", children: [
|
|
546
|
+
/* @__PURE__ */ c(
|
|
547
|
+
"button",
|
|
548
|
+
{
|
|
549
|
+
onClick: () => q(e),
|
|
550
|
+
className: "flex items-center gap-1.5 w-full px-3 py-1.5 text-left hover:bg-zinc-800/30 transition-colors",
|
|
551
|
+
children: [
|
|
552
|
+
p ? /* @__PURE__ */ r(ke, { size: 12, className: "text-zinc-600" }) : /* @__PURE__ */ r(Ce, { size: 12, className: "text-zinc-600" }),
|
|
553
|
+
/* @__PURE__ */ r("span", { className: "text-[11px] font-medium text-zinc-400 capitalize", children: e }),
|
|
554
|
+
/* @__PURE__ */ r("span", { className: "text-[10px] text-zinc-600", children: m.length }),
|
|
555
|
+
n > 0 && /* @__PURE__ */ c("span", { className: "ml-auto text-[9px] text-blue-400", children: [
|
|
556
|
+
n,
|
|
557
|
+
" sel"
|
|
558
|
+
] })
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
!p && /* @__PURE__ */ r("div", { className: "grid grid-cols-4 gap-1.5 px-3 pb-2", children: m.map((t) => {
|
|
563
|
+
const i = t.thumbnailUrl ?? t.url, o = t.name || t.category || "ref", h = $.indexOf(t.id), v = h !== -1;
|
|
564
|
+
return /* @__PURE__ */ c(
|
|
565
|
+
"button",
|
|
566
|
+
{
|
|
567
|
+
onClick: () => re(t.id),
|
|
568
|
+
className: `relative rounded overflow-hidden aspect-square border-2 transition-colors ${v ? "border-blue-500" : "border-transparent hover:border-zinc-600"}`,
|
|
569
|
+
title: t.name || o,
|
|
570
|
+
children: [
|
|
571
|
+
i && /* @__PURE__ */ r("img", { src: i, alt: o, className: "w-full h-full object-cover" }),
|
|
572
|
+
v && /* @__PURE__ */ r("span", { className: "absolute top-0.5 right-0.5 w-4 h-4 flex items-center justify-center text-[9px] font-bold bg-blue-600 text-white rounded-full leading-none", children: h + 1 }),
|
|
573
|
+
/* @__PURE__ */ r("div", { className: "absolute bottom-0 left-0 right-0 bg-black/60 text-[8px] text-zinc-300 px-1 py-0.5 text-center truncate", children: o })
|
|
574
|
+
]
|
|
575
|
+
},
|
|
576
|
+
t.id
|
|
577
|
+
);
|
|
578
|
+
}) })
|
|
579
|
+
] }, e);
|
|
580
|
+
})
|
|
581
|
+
] }),
|
|
582
|
+
/* @__PURE__ */ c("div", { className: "p-3 border-b border-zinc-800", children: [
|
|
583
|
+
/* @__PURE__ */ r("h3", { className: "text-xs font-semibold text-zinc-400 uppercase tracking-wider mb-2", children: "Prompt" }),
|
|
584
|
+
I && I.length > 0 && /* @__PURE__ */ r("div", { className: "flex flex-wrap gap-1.5 mb-2", children: I.map((e) => /* @__PURE__ */ r(
|
|
585
|
+
"button",
|
|
586
|
+
{
|
|
587
|
+
onClick: () => le(e),
|
|
588
|
+
className: `px-2 py-1 rounded text-xs font-medium transition-colors ${O === e.id ? "bg-indigo-600 text-white" : "bg-zinc-800 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700"}`,
|
|
589
|
+
children: e.label
|
|
590
|
+
},
|
|
591
|
+
e.id
|
|
592
|
+
)) }),
|
|
593
|
+
/* @__PURE__ */ c("div", { className: "relative", children: [
|
|
594
|
+
/\[(source|mask|image\d+)\]/i.test(f) && /* @__PURE__ */ c(
|
|
595
|
+
"div",
|
|
596
|
+
{
|
|
597
|
+
className: "absolute inset-0 px-2.5 py-2 text-sm whitespace-pre-wrap break-words overflow-hidden pointer-events-none rounded",
|
|
598
|
+
style: { lineHeight: "1.5" },
|
|
599
|
+
children: [
|
|
600
|
+
f.split(/(\[(?:source|mask|image\d+)\])/gi).map(
|
|
601
|
+
(e, m) => /^\[(?:source|mask|image\d+)\]$/i.test(e) ? /* @__PURE__ */ r("span", { className: "bg-blue-500/30 text-blue-300 rounded px-0.5 font-semibold", children: e }, m) : /* @__PURE__ */ r("span", { children: e }, m)
|
|
602
|
+
),
|
|
603
|
+
`
|
|
604
|
+
`
|
|
605
|
+
]
|
|
606
|
+
}
|
|
607
|
+
),
|
|
608
|
+
/* @__PURE__ */ r(
|
|
609
|
+
"textarea",
|
|
610
|
+
{
|
|
611
|
+
value: f,
|
|
612
|
+
onChange: (e) => {
|
|
613
|
+
K(e.target.value), ee(null);
|
|
614
|
+
},
|
|
615
|
+
placeholder: "Describe what to edit or change...",
|
|
616
|
+
rows: 3,
|
|
617
|
+
className: `w-full bg-zinc-800 border border-zinc-700 rounded px-2.5 py-2 text-sm placeholder-zinc-600 resize-none focus:outline-none focus:border-indigo-500 ${/\[(source|mask|image\d+)\]/i.test(f) ? "text-transparent caret-zinc-200" : "text-zinc-200"}`,
|
|
618
|
+
style: { lineHeight: "1.5", background: /\[(source|mask|image\d+)\]/i.test(f) ? "rgba(39,39,42,0.95)" : void 0 }
|
|
619
|
+
}
|
|
620
|
+
)
|
|
621
|
+
] }),
|
|
622
|
+
/* @__PURE__ */ c("p", { className: "text-[10px] text-zinc-500 mt-1", children: [
|
|
623
|
+
"Use [source] for the original image",
|
|
624
|
+
$.length > 0 && /* @__PURE__ */ c(ce, { children: [
|
|
625
|
+
", ",
|
|
626
|
+
$.map((e, m) => `[image${m + 1}]`).join(", "),
|
|
627
|
+
" for references"
|
|
628
|
+
] })
|
|
629
|
+
] })
|
|
630
|
+
] }),
|
|
631
|
+
l && /* @__PURE__ */ c("div", { className: "p-3 border-b border-zinc-800", children: [
|
|
632
|
+
/* @__PURE__ */ c("label", { className: "flex items-center gap-2 mb-2 cursor-pointer", children: [
|
|
633
|
+
/* @__PURE__ */ r(
|
|
634
|
+
"input",
|
|
635
|
+
{
|
|
636
|
+
type: "checkbox",
|
|
637
|
+
checked: z,
|
|
638
|
+
onChange: (e) => j(e.target.checked),
|
|
639
|
+
className: "accent-indigo-500"
|
|
640
|
+
}
|
|
641
|
+
),
|
|
642
|
+
/* @__PURE__ */ r("span", { className: "text-xs text-zinc-400", children: "Mask composite" }),
|
|
643
|
+
/* @__PURE__ */ r("span", { className: "text-[10px] text-zinc-600", children: "— only apply AI changes inside the mask" })
|
|
644
|
+
] }),
|
|
645
|
+
/* @__PURE__ */ r(
|
|
646
|
+
"button",
|
|
647
|
+
{
|
|
648
|
+
onClick: ie,
|
|
649
|
+
disabled: !f.trim() || te,
|
|
650
|
+
className: "w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-semibold transition-colors bg-indigo-600 text-white hover:bg-indigo-500 disabled:opacity-40 disabled:cursor-not-allowed",
|
|
651
|
+
children: te ? /* @__PURE__ */ c(ce, { children: [
|
|
652
|
+
/* @__PURE__ */ r(me, { size: 16, className: "animate-spin" }),
|
|
653
|
+
"Generating..."
|
|
654
|
+
] }) : /* @__PURE__ */ c(ce, { children: [
|
|
655
|
+
/* @__PURE__ */ r(Ne, { size: 16 }),
|
|
656
|
+
"Generate Edit"
|
|
657
|
+
] })
|
|
658
|
+
}
|
|
659
|
+
),
|
|
660
|
+
/* @__PURE__ */ r("p", { className: "text-[10px] text-zinc-600 mt-1.5 text-center", children: f.trim() ? a.strokes.length === 0 ? "Ready — paint a mask for targeted edits" : "Ready to generate" : "Enter a prompt or pick a preset" })
|
|
661
|
+
] }),
|
|
662
|
+
/* @__PURE__ */ c("div", { className: "p-3 flex-1 min-h-0 overflow-y-auto", children: [
|
|
663
|
+
/* @__PURE__ */ r("h3", { className: "text-xs font-semibold text-zinc-400 uppercase tracking-wider mb-2", children: "Edit History" }),
|
|
664
|
+
/* @__PURE__ */ r("div", { className: "flex flex-col gap-1", children: S.map((e) => /* @__PURE__ */ c(
|
|
665
|
+
"button",
|
|
666
|
+
{
|
|
667
|
+
onClick: () => ae(e),
|
|
668
|
+
className: `flex items-center gap-2 px-2.5 py-2 rounded text-left transition-colors ${M === e.index ? "bg-indigo-600/20 border border-indigo-500 text-zinc-200" : "bg-zinc-800/50 border border-transparent text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800"}`,
|
|
669
|
+
children: [
|
|
670
|
+
/* @__PURE__ */ r("div", { className: "w-8 h-8 rounded overflow-hidden flex-shrink-0 bg-zinc-700", children: /* @__PURE__ */ r("img", { src: e.imageDataUrl, alt: e.label, className: "w-full h-full object-cover" }) }),
|
|
671
|
+
/* @__PURE__ */ c("div", { className: "min-w-0 flex-1", children: [
|
|
672
|
+
/* @__PURE__ */ r("p", { className: "text-xs font-medium truncate", children: e.index === 0 ? "Original" : e.label }),
|
|
673
|
+
e.index > 0 && /* @__PURE__ */ c("p", { className: "text-[10px] text-zinc-500 truncate", children: [
|
|
674
|
+
"Step ",
|
|
675
|
+
e.index
|
|
676
|
+
] })
|
|
677
|
+
] }),
|
|
678
|
+
M === e.index && /* @__PURE__ */ r(he, { size: 14, className: "text-indigo-400 flex-shrink-0" })
|
|
679
|
+
]
|
|
680
|
+
},
|
|
681
|
+
e.index
|
|
682
|
+
)) })
|
|
683
|
+
] }),
|
|
684
|
+
/* @__PURE__ */ c("div", { className: "p-3 border-t border-zinc-800 flex gap-2", children: [
|
|
685
|
+
/* @__PURE__ */ r(
|
|
686
|
+
"button",
|
|
687
|
+
{
|
|
688
|
+
onClick: u,
|
|
689
|
+
className: "flex-1 px-3 py-2 rounded text-sm font-medium bg-zinc-800 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-700 transition-colors",
|
|
690
|
+
children: "Cancel"
|
|
691
|
+
}
|
|
692
|
+
),
|
|
693
|
+
/* @__PURE__ */ c(
|
|
694
|
+
"button",
|
|
695
|
+
{
|
|
696
|
+
onClick: se,
|
|
697
|
+
disabled: M === 0,
|
|
698
|
+
className: "flex-1 flex items-center justify-center gap-1.5 px-3 py-2 rounded text-sm font-semibold bg-green-600 text-white hover:bg-green-500 disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
699
|
+
children: [
|
|
700
|
+
/* @__PURE__ */ r(he, { size: 14 }),
|
|
701
|
+
"Apply"
|
|
702
|
+
]
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
] })
|
|
706
|
+
] })
|
|
707
|
+
] })
|
|
708
|
+
] });
|
|
709
|
+
}
|
|
710
|
+
export {
|
|
711
|
+
Ue as ImageEditor,
|
|
712
|
+
de as replayStrokes,
|
|
713
|
+
Me as useMaskCanvas
|
|
714
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface GenerateRequest {
|
|
2
|
+
sourceImageDataUrl: string;
|
|
3
|
+
prompt: string;
|
|
4
|
+
maskedImageDataUrl?: string;
|
|
5
|
+
selectedReferenceIds?: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface EditorReferenceImage {
|
|
8
|
+
id: string;
|
|
9
|
+
url: string;
|
|
10
|
+
name: string;
|
|
11
|
+
category?: string;
|
|
12
|
+
thumbnailUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface EditorPreset {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
prompt: string;
|
|
18
|
+
}
|
|
19
|
+
export interface EditHistoryStep {
|
|
20
|
+
index: number;
|
|
21
|
+
label: string;
|
|
22
|
+
imageDataUrl: string;
|
|
23
|
+
prompt: string;
|
|
24
|
+
maskDataUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ImageEditorProps {
|
|
27
|
+
isOpen: boolean;
|
|
28
|
+
onClose: () => void;
|
|
29
|
+
onSave: (resultDataUrl: string) => void;
|
|
30
|
+
imageUrl: string;
|
|
31
|
+
onGenerate?: (request: GenerateRequest) => Promise<string>;
|
|
32
|
+
referenceImages?: EditorReferenceImage[];
|
|
33
|
+
defaultReferenceIds?: string[];
|
|
34
|
+
presets?: EditorPreset[];
|
|
35
|
+
initialHistory?: EditHistoryStep[];
|
|
36
|
+
onStepComplete?: (step: EditHistoryStep) => void;
|
|
37
|
+
title?: string;
|
|
38
|
+
imageResolution?: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface BrushStroke {
|
|
2
|
+
points: Array<{
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}>;
|
|
6
|
+
size: number;
|
|
7
|
+
tool: 'brush' | 'eraser';
|
|
8
|
+
}
|
|
9
|
+
interface UseMaskCanvasOptions {
|
|
10
|
+
/** Whether to register keyboard shortcuts (default: true). */
|
|
11
|
+
enableShortcuts?: boolean;
|
|
12
|
+
/** Whether the canvas is active/visible (controls shortcut registration). */
|
|
13
|
+
isActive?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface UseMaskCanvasReturn {
|
|
16
|
+
imageCanvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
17
|
+
maskCanvasRef: React.RefObject<HTMLCanvasElement | null>;
|
|
18
|
+
containerRef: React.RefObject<HTMLDivElement | null>;
|
|
19
|
+
activeTool: 'brush' | 'eraser';
|
|
20
|
+
setActiveTool: (tool: 'brush' | 'eraser') => void;
|
|
21
|
+
brushSize: number;
|
|
22
|
+
setBrushSize: (size: number | ((prev: number) => number)) => void;
|
|
23
|
+
zoom: number;
|
|
24
|
+
pan: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
};
|
|
28
|
+
handleZoomIn: () => void;
|
|
29
|
+
handleZoomOut: () => void;
|
|
30
|
+
handleWheel: (e: React.WheelEvent) => void;
|
|
31
|
+
strokes: BrushStroke[];
|
|
32
|
+
redoStack: BrushStroke[];
|
|
33
|
+
handleUndo: () => void;
|
|
34
|
+
handleRedo: () => void;
|
|
35
|
+
handleClearMask: () => void;
|
|
36
|
+
handlePointerDown: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
37
|
+
handlePointerMove: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
38
|
+
handlePointerUp: (e: React.PointerEvent<HTMLCanvasElement>) => void;
|
|
39
|
+
imageDimensions: {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
};
|
|
43
|
+
loadImage: (url: string, preserveView?: boolean) => Promise<void>;
|
|
44
|
+
exportMaskedImage: () => string | null;
|
|
45
|
+
exportCroppedRegion: (padding?: number) => string | null;
|
|
46
|
+
exportMask: () => HTMLCanvasElement | null;
|
|
47
|
+
cursorStyle: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Replay all strokes onto a canvas context at the given opacity.
|
|
51
|
+
* Clears the canvas first.
|
|
52
|
+
*/
|
|
53
|
+
export declare function replayStrokes(ctx: CanvasRenderingContext2D, strokeList: BrushStroke[], width: number, height: number, opacity: number): void;
|
|
54
|
+
export declare function useMaskCanvas(options?: UseMaskCanvasOptions): UseMaskCanvasReturn;
|
|
55
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "muse-image-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "vite build && tsc --emitDeclarationOnly --outDir dist",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"react": ">=18.0.0",
|
|
21
|
+
"react-dom": ">=18.0.0",
|
|
22
|
+
"lucide-react": ">=0.300.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/react": "^19.0.0",
|
|
26
|
+
"@types/react-dom": "^19.0.0",
|
|
27
|
+
"react": "^19.2.4",
|
|
28
|
+
"react-dom": "^19.2.4",
|
|
29
|
+
"lucide-react": "^0.564.0",
|
|
30
|
+
"typescript": "~5.8.2",
|
|
31
|
+
"vite": "^6.2.0"
|
|
32
|
+
}
|
|
33
|
+
}
|