"use client"; import React, { useEffect, useMemo, useRef, useState } from 'react'; type ImageEditorPaneProps = { imageBase64: string; onUpdate: (base64: string) => void; onClose: () => void; prompt?: string; }; function loadImage(base64: string): Promise { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = reject; img.src = `data:image/png;base64,${base64}`; }); } function canvasToBase64(canvas: HTMLCanvasElement): string { return canvas.toDataURL("image/png").split(",")[1] || ""; } export default function ImageEditorPane({ imageBase64, onUpdate, onClose, prompt }: ImageEditorPaneProps) { const [working, setWorking] = useState(false); const [brightness, setBrightness] = useState(0); // -100..100 const [contrast, setContrast] = useState(0); // -100..100 const previewRef = useRef(null); const applyAndUpdate = async (fn: (img: HTMLImageElement, ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement) => void | Promise) => { setWorking(true); try { const img = await loadImage(imageBase64); const canvas = document.createElement("canvas"); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext("2d", { willReadFrequently: true }); if (!ctx) return; ctx.drawImage(img, 0, 0); await fn(img, ctx, canvas); const b64 = canvasToBase64(canvas); onUpdate(b64); } finally { setWorking(false); } }; const cropCenter = (aspect: number) => applyAndUpdate((img, ctx, canvas) => { const w = img.naturalWidth; const h = img.naturalHeight; let cw = w; let ch = Math.round(w / aspect); if (ch > h) { ch = h; cw = Math.round(h * aspect); } const sx = Math.round((w - cw) / 2); const sy = Math.round((h - ch) / 2); const out = document.createElement("canvas"); out.width = cw; out.height = ch; const octx = out.getContext("2d")!; octx.imageSmoothingEnabled = true; octx.drawImage(img, sx, sy, cw, ch, 0, 0, cw, ch); canvas.width = cw; canvas.height = ch; ctx.clearRect(0,0,canvas.width, canvas.height); ctx.drawImage(out, 0, 0); }); const applyAdjust = () => applyAndUpdate((img, ctx, canvas) => { const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; const b = brightness / 100 * 255; // shift const c = Math.pow((contrast + 100) / 100, 2); // gamma-like for (let i = 0; i < data.length; i += 4) { for (let k = 0; k < 3; k++) { let v = data[i + k]; v = v + b; // brightness v = ((v - 128) * c) + 128; // contrast data[i + k] = Math.max(0, Math.min(255, v)); } } ctx.putImageData(imgData, 0, 0); }); const upscale2x = () => applyAndUpdate((img, ctx, canvas) => { const outW = img.naturalWidth * 2; const outH = img.naturalHeight * 2; const out = document.createElement("canvas"); out.width = outW; out.height = outH; const octx = out.getContext("2d")!; octx.imageSmoothingEnabled = true; octx.imageSmoothingQuality = 'high'; octx.drawImage(img, 0, 0, outW, outH); canvas.width = outW; canvas.height = outH; ctx.clearRect(0,0,canvas.width, canvas.height); ctx.drawImage(out, 0, 0); }); return (
{prompt || 'Image Editor'}
B setBrightness(parseInt(e.target.value))} /> C setContrast(parseInt(e.target.value))} />
Editor
); }