Files
BrainWind/app/src/lib/canvas-capture.ts
T

121 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Utilitários de captura de canvas 3D — M9.3
*
* Funções determinísticas (sem dependência de React) para:
* - Extrair data URL de um canvas 2D/WebGL
* - Redimensionar a imagem para uma largura máxima (preservando aspect ratio)
* - Validar formato/qualidade
*
* Funciona com qualquer HTMLCanvasElement (incluindo R3F, Konva, D3).
* Para canvas WebGL, o browser exige que `preserveDrawingBuffer: true`
* seja passado ao `getContext('webgl2')` OU que a captura seja feita
* imediatamente após o frame renderizado. Como R3F usa o loop de
* animação do `useFrame`, a captura dentro do mesmo frame funciona.
*
* Dica: para WebGL, chamar `gl.flush()` ou renderizar um frame extra
* antes de `toDataURL` evita canvas em branco.
*/
export interface CaptureOptions {
/** Formato de saída. Padrão: 'png' */
format?: 'png' | 'jpeg' | 'webp';
/** Qualidade JPEG/WebP (01). Ignorado para PNG. Padrão: 0.92 */
quality?: number;
/** Largura máxima do PNG final (px). 0 = sem redimensionamento */
maxWidth?: number;
/** Altura máxima do PNG final (px). 0 = sem limite */
maxHeight?: number;
}
/**
* Converte um HTMLCanvasElement em data URL.
*
* Para PNG, o segundo argumento é ignorado. Para JPEG/WebP, `quality`
* controla a compressão (1 = sem perda, 0 = máxima compressão).
*/
export function canvasToDataURL(
canvas: HTMLCanvasElement,
format: 'png' | 'jpeg' | 'webp' = 'png',
quality = 0.92,
): string {
if (!canvas) throw new Error('canvas é null');
const mime = `image/${format}`;
return canvas.toDataURL(mime, quality);
}
/**
* Captura e opcionalmente redimensiona a imagem do canvas.
*
* Usa um canvas 2D temporário para escalar, preservando a proporção.
* Retorna a data URL final pronta para嵌入 em `<Image src=...>` ou PDF.
*/
export async function captureCanvasImage(
canvas: HTMLCanvasElement,
options: CaptureOptions = {},
): Promise<string> {
const { format = 'png', quality = 0.92, maxWidth = 0, maxHeight = 0 } = options;
const srcW = canvas.width;
const srcH = canvas.height;
let outW = srcW;
let outH = srcH;
if (maxWidth > 0 && maxHeight > 0) {
const ratio = Math.min(maxWidth / srcW, maxHeight / srcH);
outW = Math.round(srcW * ratio);
outH = Math.round(srcH * ratio);
} else if (maxWidth > 0) {
outW = Math.min(maxWidth, srcW);
outH = Math.round((outW / srcW) * srcH);
} else if (maxHeight > 0) {
outH = Math.min(maxHeight, srcH);
outW = Math.round((outH / srcH) * srcW);
}
if (outW === srcW && outH === srcH) {
return canvasToDataURL(canvas, format, quality);
}
const off = document.createElement('canvas');
off.width = outW;
off.height = outH;
const ctx = off.getContext('2d');
if (!ctx) throw new Error('Não foi possível criar contexto 2D');
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(canvas, 0, 0, outW, outH);
return off.toDataURL(`image/${format}`, quality);
}
/**
* Faz o download da imagem capturada.
*/
export function downloadImage(dataUrl: string, filename: string): void {
const link = document.createElement('a');
link.setAttribute('href', dataUrl);
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* Estima o tamanho da data URL em KB (útil para preview).
*/
export function estimateDataUrlSizeKB(dataUrl: string): number {
const commaIdx = dataUrl.indexOf(',');
if (commaIdx < 0) return 0;
const base64 = dataUrl.slice(commaIdx + 1);
return Math.round((base64.length * 3) / 4 / 1024);
}
/**
* Converte data URL em Blob (útil para upload ou PDF embed).
*/
export async function dataURLtoBlob(dataUrl: string): Promise<Blob> {
const res = await fetch(dataUrl);
return res.blob();
}