Los tres uploaders del panel (logo, "quiénes somos" y galería) ahora abren un recorte interactivo (zoom + encuadre) y reescalan en el navegador antes de subir: logo y "quiénes somos" a máx. 500 px, galería a máx. 1200 px, reencodando a WebP (calidad 0.82). El SVG del logo se sube tal cual para no rasterizar el vector. Esto reduce el peso de los data URIs base64 que se guardan en Postgres y se inlinean en el funnel. Nueva dependencia react-easy-crop: librería de recorte ligera, sin estado global, compatible con React 19; el reescalado y reencodado se hacen con canvas nativo (lib/image/crop.ts), sin dependencias extra.
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
export type CropArea = { x: number; y: number; width: number; height: number };
|
|
|
|
// Recorta la región indicada (en píxeles del original) y reescala el lado más
|
|
// largo a `maxDimension`, devolviendo un WebP ya optimizado listo para subir.
|
|
export async function getCroppedWebp(
|
|
src: string,
|
|
area: CropArea,
|
|
maxDimension: number,
|
|
quality = 0.82,
|
|
): Promise<Blob> {
|
|
const img = await loadImage(src);
|
|
const scale = Math.min(1, maxDimension / Math.max(area.width, area.height));
|
|
const outW = Math.max(1, Math.round(area.width * scale));
|
|
const outH = Math.max(1, Math.round(area.height * scale));
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = outW;
|
|
canvas.height = outH;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) throw new Error('No se pudo crear el lienzo.');
|
|
ctx.imageSmoothingQuality = 'high';
|
|
ctx.drawImage(img, area.x, area.y, area.width, area.height, 0, 0, outW, outH);
|
|
|
|
const blob = await new Promise<Blob | null>((resolve) =>
|
|
canvas.toBlob(resolve, 'image/webp', quality),
|
|
);
|
|
if (!blob) throw new Error('No se pudo procesar la imagen.');
|
|
return blob;
|
|
}
|
|
|
|
function loadImage(src: string): Promise<HTMLImageElement> {
|
|
return new Promise((resolve, reject) => {
|
|
const img = new Image();
|
|
img.onload = () => resolve(img);
|
|
img.onerror = () => reject(new Error('No se pudo cargar la imagen.'));
|
|
img.src = src;
|
|
});
|
|
}
|