121 lines
3.7 KiB
TypeScript
121 lines
3.7 KiB
TypeScript
/**
|
||
* 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 (0–1). 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();
|
||
} |