@pixerate/editor-react 0.8.0 → 0.9.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/{chunk-NKQKD43U.js → chunk-IMZDKODL.js} +212 -3
- package/dist/chunk-IMZDKODL.js.map +1 -0
- package/dist/image-editor/index.cjs +211 -2
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +14 -1
- package/dist/image-editor/index.d.ts +14 -1
- package/dist/image-editor/index.js +1 -1
- package/dist/index.cjs +211 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-NKQKD43U.js.map +0 -1
|
@@ -88,6 +88,22 @@ function useImageEditor(options = {}) {
|
|
|
88
88
|
const toBlob = (0, import_react.useCallback)((opts) => controller.toBlob(opts), [controller]);
|
|
89
89
|
const toJSON = (0, import_react.useCallback)(() => controller.toJSON(), [controller]);
|
|
90
90
|
const loadJSON = (0, import_react.useCallback)((data) => controller.loadJSON(data), [controller]);
|
|
91
|
+
const renderMask = (0, import_react.useCallback)(
|
|
92
|
+
(opts) => controller.renderMask(opts),
|
|
93
|
+
[controller]
|
|
94
|
+
);
|
|
95
|
+
const toMaskDataURL = (0, import_react.useCallback)(
|
|
96
|
+
(opts) => controller.toMaskDataURL(opts),
|
|
97
|
+
[controller]
|
|
98
|
+
);
|
|
99
|
+
const applyInpaintedImage = (0, import_react.useCallback)(
|
|
100
|
+
(newDataUrl) => controller.applyInpaintedImage(newDataUrl),
|
|
101
|
+
[controller]
|
|
102
|
+
);
|
|
103
|
+
const applyBackgroundRemovedImage = (0, import_react.useCallback)(
|
|
104
|
+
(newDataUrl) => controller.applyBackgroundRemovedImage(newDataUrl),
|
|
105
|
+
[controller]
|
|
106
|
+
);
|
|
91
107
|
return (0, import_react.useMemo)(
|
|
92
108
|
() => ({
|
|
93
109
|
controller,
|
|
@@ -124,7 +140,11 @@ function useImageEditor(options = {}) {
|
|
|
124
140
|
toDataURL,
|
|
125
141
|
toBlob,
|
|
126
142
|
toJSON,
|
|
127
|
-
loadJSON
|
|
143
|
+
loadJSON,
|
|
144
|
+
renderMask,
|
|
145
|
+
toMaskDataURL,
|
|
146
|
+
applyInpaintedImage,
|
|
147
|
+
applyBackgroundRemovedImage
|
|
128
148
|
}),
|
|
129
149
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
130
150
|
[controller, state, historyTicks]
|
|
@@ -141,7 +161,10 @@ var ImageEditor = ({
|
|
|
141
161
|
className = "",
|
|
142
162
|
style,
|
|
143
163
|
onSave,
|
|
144
|
-
exportOptions
|
|
164
|
+
exportOptions,
|
|
165
|
+
onInpaint,
|
|
166
|
+
onRemoveBackground,
|
|
167
|
+
aiHooks
|
|
145
168
|
}) => {
|
|
146
169
|
const internalEditor = useImageEditor({ image: src });
|
|
147
170
|
const editor = externalEditor || internalEditor;
|
|
@@ -150,6 +173,73 @@ var ImageEditor = ({
|
|
|
150
173
|
const containerRef = (0, import_react2.useRef)(null);
|
|
151
174
|
const [activeTab, setActiveTab] = (0, import_react2.useState)("crop");
|
|
152
175
|
const [activeCropRatio, setActiveCropRatio] = (0, import_react2.useState)(null);
|
|
176
|
+
const [inpaintPrompt, setInpaintPrompt] = (0, import_react2.useState)("");
|
|
177
|
+
const [inpaintMaskMode, setInpaintMaskMode] = (0, import_react2.useState)("annotations");
|
|
178
|
+
const [isRemovingBg, setIsRemovingBg] = (0, import_react2.useState)(false);
|
|
179
|
+
const [isInpainting, setIsInpainting] = (0, import_react2.useState)(false);
|
|
180
|
+
const [aiError, setAiError] = (0, import_react2.useState)(null);
|
|
181
|
+
const [aiSuccess, setAiSuccess] = (0, import_react2.useState)(null);
|
|
182
|
+
const effectiveOnInpaint = onInpaint || aiHooks?.onInpaint;
|
|
183
|
+
const effectiveOnRemoveBg = onRemoveBackground || aiHooks?.onRemoveBackground;
|
|
184
|
+
const handleRemoveBackground = async () => {
|
|
185
|
+
if (!effectiveOnRemoveBg) {
|
|
186
|
+
setAiError("No background removal hook is configured.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
setAiError(null);
|
|
190
|
+
setAiSuccess(null);
|
|
191
|
+
setIsRemovingBg(true);
|
|
192
|
+
try {
|
|
193
|
+
const imageDataUrl = await editor.toDataURL(exportOptions);
|
|
194
|
+
const result = await effectiveOnRemoveBg({ imageDataUrl });
|
|
195
|
+
if (typeof result === "string") {
|
|
196
|
+
await editor.applyBackgroundRemovedImage(result);
|
|
197
|
+
setAiSuccess("Background removed successfully!");
|
|
198
|
+
} else {
|
|
199
|
+
setAiSuccess("Background removal complete.");
|
|
200
|
+
}
|
|
201
|
+
} catch (err) {
|
|
202
|
+
setAiError(err?.message || "Failed to remove background.");
|
|
203
|
+
} finally {
|
|
204
|
+
setIsRemovingBg(false);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
const handleInpaint = async () => {
|
|
208
|
+
if (!effectiveOnInpaint) {
|
|
209
|
+
setAiError("No inpainting hook is configured.");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!inpaintPrompt.trim()) {
|
|
213
|
+
setAiError("Please enter an inpainting prompt.");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
setAiError(null);
|
|
217
|
+
setAiSuccess(null);
|
|
218
|
+
setIsInpainting(true);
|
|
219
|
+
try {
|
|
220
|
+
const imageDataUrl = await editor.toDataURL(exportOptions);
|
|
221
|
+
const maskDataUrl = await editor.toMaskDataURL({
|
|
222
|
+
useAnnotations: inpaintMaskMode === "annotations",
|
|
223
|
+
useCrop: inpaintMaskMode === "crop"
|
|
224
|
+
});
|
|
225
|
+
const result = await effectiveOnInpaint({
|
|
226
|
+
prompt: inpaintPrompt.trim(),
|
|
227
|
+
imageDataUrl,
|
|
228
|
+
maskDataUrl,
|
|
229
|
+
cropBox: inpaintMaskMode === "crop" ? editor.state.crop : null
|
|
230
|
+
});
|
|
231
|
+
if (typeof result === "string") {
|
|
232
|
+
await editor.applyInpaintedImage(result);
|
|
233
|
+
setAiSuccess("Inpainting applied successfully!");
|
|
234
|
+
} else {
|
|
235
|
+
setAiSuccess("Inpainting complete.");
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
setAiError(err?.message || "Failed to apply inpainting.");
|
|
239
|
+
} finally {
|
|
240
|
+
setIsInpainting(false);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
153
243
|
const [activeColor, setActiveColor] = (0, import_react2.useState)("#ef4444");
|
|
154
244
|
const [strokeWidth, setStrokeWidth] = (0, import_react2.useState)(3);
|
|
155
245
|
const [fontSize, setFontSize] = (0, import_react2.useState)(24);
|
|
@@ -605,6 +695,21 @@ var ImageEditor = ({
|
|
|
605
695
|
className: `px-3 py-1.5 rounded-md font-medium transition-colors ${activeTab === "depth" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
|
|
606
696
|
children: "Depth Mask"
|
|
607
697
|
}
|
|
698
|
+
),
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
700
|
+
"button",
|
|
701
|
+
{
|
|
702
|
+
type: "button",
|
|
703
|
+
onClick: () => {
|
|
704
|
+
setActiveTab("ai");
|
|
705
|
+
editor.setTool("select");
|
|
706
|
+
},
|
|
707
|
+
className: `px-3 py-1.5 rounded-md font-medium transition-colors flex items-center gap-1.5 ${activeTab === "ai" ? "bg-indigo-600 text-white shadow" : "text-slate-400 hover:text-slate-200"}`,
|
|
708
|
+
children: [
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
|
|
710
|
+
"AI Tools"
|
|
711
|
+
]
|
|
712
|
+
}
|
|
608
713
|
)
|
|
609
714
|
] }),
|
|
610
715
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
@@ -1204,6 +1309,110 @@ var ImageEditor = ({
|
|
|
1204
1309
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Invert Mask" })
|
|
1205
1310
|
] })
|
|
1206
1311
|
] })
|
|
1312
|
+
] }),
|
|
1313
|
+
activeTab === "ai" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-4 text-xs", children: [
|
|
1314
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
|
|
1315
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1316
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
|
|
1317
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-3.5 h-3.5 text-indigo-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M6 3v12M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" }) }),
|
|
1318
|
+
"Background Removal"
|
|
1319
|
+
] }),
|
|
1320
|
+
isRemovingBg && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-900/60 text-indigo-300 animate-pulse", children: "Removing..." })
|
|
1321
|
+
] }),
|
|
1322
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Isolate foreground subject with AI alpha transparency segmentation." }),
|
|
1323
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1324
|
+
"button",
|
|
1325
|
+
{
|
|
1326
|
+
type: "button",
|
|
1327
|
+
onClick: handleRemoveBackground,
|
|
1328
|
+
disabled: isRemovingBg || isInpainting,
|
|
1329
|
+
className: "mt-1 px-3 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
|
|
1330
|
+
children: isRemovingBg ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1331
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
|
|
1332
|
+
"Removing Background..."
|
|
1333
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
|
|
1335
|
+
"Remove Background"
|
|
1336
|
+
] })
|
|
1337
|
+
}
|
|
1338
|
+
)
|
|
1339
|
+
] }),
|
|
1340
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-3 rounded-lg bg-slate-950/60 border border-slate-800 flex flex-col gap-2.5", children: [
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "font-semibold text-slate-200 uppercase tracking-wider text-[11px] flex items-center gap-1.5", children: [
|
|
1343
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "w-3.5 h-3.5 text-purple-400", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m21.64 3.64-1.28-1.28a1.2 1.2 0 0 0-1.7 0l-9.72 9.72-1.94 4.86 4.86-1.94 9.72-9.72a1.2 1.2 0 0 0 .06-1.64z" }),
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "m14 7 3 3" })
|
|
1346
|
+
] }),
|
|
1347
|
+
"Generative Inpainting"
|
|
1348
|
+
] }),
|
|
1349
|
+
isInpainting && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded text-[10px] font-medium bg-purple-900/60 text-purple-300 animate-pulse", children: "Inpainting..." })
|
|
1350
|
+
] }),
|
|
1351
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-slate-400 text-[11px] leading-relaxed", children: "Replace, modify, or fill selected regions using prompt-guided generative inpainting." }),
|
|
1352
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
1353
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: "ai-inpaint-prompt-react", className: "text-slate-300 text-[11px] font-medium", children: "Prompt" }),
|
|
1354
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1355
|
+
"textarea",
|
|
1356
|
+
{
|
|
1357
|
+
id: "ai-inpaint-prompt-react",
|
|
1358
|
+
value: inpaintPrompt,
|
|
1359
|
+
onChange: (e) => setInpaintPrompt(e.target.value),
|
|
1360
|
+
placeholder: "e.g. replace sunglasses with steampunk goggles, remove person in background...",
|
|
1361
|
+
rows: 3,
|
|
1362
|
+
className: "w-full bg-slate-900 border border-slate-700 rounded-lg p-2 text-xs text-slate-100 placeholder-slate-500 focus:outline-none focus:border-indigo-500 resize-none"
|
|
1363
|
+
}
|
|
1364
|
+
)
|
|
1365
|
+
] }),
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
1367
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-slate-300 text-[11px] font-medium", children: "Target Mask Region" }),
|
|
1368
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid grid-cols-2 gap-1.5", children: [
|
|
1369
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1370
|
+
"button",
|
|
1371
|
+
{
|
|
1372
|
+
type: "button",
|
|
1373
|
+
onClick: () => setInpaintMaskMode("annotations"),
|
|
1374
|
+
className: `px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors ${inpaintMaskMode === "annotations" ? "bg-purple-600/30 border-purple-500 text-purple-200" : "bg-slate-900 border-slate-700 text-slate-400 hover:text-slate-200"}`,
|
|
1375
|
+
children: [
|
|
1376
|
+
"Drawn Shapes (",
|
|
1377
|
+
state.annotations.length,
|
|
1378
|
+
")"
|
|
1379
|
+
]
|
|
1380
|
+
}
|
|
1381
|
+
),
|
|
1382
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
1383
|
+
"button",
|
|
1384
|
+
{
|
|
1385
|
+
type: "button",
|
|
1386
|
+
onClick: () => setInpaintMaskMode("crop"),
|
|
1387
|
+
className: `px-2 py-1.5 rounded border text-[11px] font-medium text-left transition-colors ${inpaintMaskMode === "crop" ? "bg-purple-600/30 border-purple-500 text-purple-200" : "bg-slate-900 border-slate-700 text-slate-400 hover:text-slate-200"}`,
|
|
1388
|
+
children: [
|
|
1389
|
+
"Crop Box (",
|
|
1390
|
+
state.crop ? "Selected" : "Full",
|
|
1391
|
+
")"
|
|
1392
|
+
]
|
|
1393
|
+
}
|
|
1394
|
+
)
|
|
1395
|
+
] })
|
|
1396
|
+
] }),
|
|
1397
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1398
|
+
"button",
|
|
1399
|
+
{
|
|
1400
|
+
type: "button",
|
|
1401
|
+
onClick: handleInpaint,
|
|
1402
|
+
disabled: isInpainting || isRemovingBg || !inpaintPrompt.trim(),
|
|
1403
|
+
className: "mt-1 px-3 py-2 rounded-lg bg-purple-600 hover:bg-purple-500 disabled:opacity-50 text-white font-medium shadow transition-colors flex items-center justify-center gap-2",
|
|
1404
|
+
children: isInpainting ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1405
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
|
|
1406
|
+
"Generating Inpaint..."
|
|
1407
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1408
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className: "w-3.5 h-3.5", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" }) }),
|
|
1409
|
+
"Apply Inpainting"
|
|
1410
|
+
] })
|
|
1411
|
+
}
|
|
1412
|
+
)
|
|
1413
|
+
] }),
|
|
1414
|
+
aiError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-2.5 rounded-lg bg-rose-950/60 border border-rose-800 text-rose-300 text-[11px]", children: aiError }),
|
|
1415
|
+
aiSuccess && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-2.5 rounded-lg bg-emerald-950/60 border border-emerald-800 text-emerald-300 text-[11px]", children: aiSuccess })
|
|
1207
1416
|
] })
|
|
1208
1417
|
] })
|
|
1209
1418
|
] })
|