import { resolveChatContentType } from '@/lib/chat-media'; const DEFAULT_MAX_EDGE = 2560; const DEFAULT_QUALITY = 0.82; function loadImageFromFile(file: File) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file); const image = new Image(); image.onload = () => { URL.revokeObjectURL(url); resolve(image); }; image.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Не удалось прочитать изображение')); }; image.src = url; }); } function canvasToBlob(canvas: HTMLCanvasElement, type: string, quality: number) { return new Promise((resolve, reject) => { canvas.toBlob( (blob) => { if (!blob) { reject(new Error('Не удалось сжать изображение')); return; } resolve(blob); }, type, quality ); }); } export function isCompressibleImageFile(file: File) { const type = (file.type || '').toLowerCase(); if (type.startsWith('image/') && !type.includes('gif') && !type.includes('svg')) { return true; } return /\.(jpe?g|png|webp|heic|heif|bmp)$/i.test(file.name); } export async function compressChatImage( file: File, options?: { maxEdge?: number; quality?: number } ) { const maxEdge = options?.maxEdge ?? DEFAULT_MAX_EDGE; const quality = options?.quality ?? DEFAULT_QUALITY; if (!isCompressibleImageFile(file)) { return { file, width: undefined, height: undefined, compressed: false }; } const image = await loadImageFromFile(file); const width = image.naturalWidth || image.width; const height = image.naturalHeight || image.height; if (!width || !height) { return { file, width: undefined, height: undefined, compressed: false }; } const scale = Math.min(1, maxEdge / Math.max(width, height)); const targetWidth = Math.max(1, Math.round(width * scale)); const targetHeight = Math.max(1, Math.round(height * scale)); if (scale >= 1 && file.size <= 350_000) { return { file, width, height, compressed: false }; } const canvas = document.createElement('canvas'); canvas.width = targetWidth; canvas.height = targetHeight; const context = canvas.getContext('2d'); if (!context) { return { file, width, height, compressed: false }; } context.drawImage(image, 0, 0, targetWidth, targetHeight); const outputType = file.type === 'image/png' ? 'image/jpeg' : file.type || 'image/jpeg'; const blob = await canvasToBlob(canvas, outputType, quality); const extension = outputType.includes('webp') ? 'webp' : 'jpg'; const baseName = file.name.replace(/\.[^.]+$/, '') || 'image'; const compressedFile = new File([blob], `${baseName}.${extension}`, { type: outputType, lastModified: Date.now() }); return { file: compressedFile, width: targetWidth, height: targetHeight, compressed: true }; } export async function readImageDimensions(file: File) { if (!isCompressibleImageFile(file)) { return { width: undefined, height: undefined }; } try { const image = await loadImageFromFile(file); return { width: image.naturalWidth || image.width, height: image.naturalHeight || image.height }; } catch { return { width: undefined, height: undefined }; } } export function createFilePreviewUrl(file: File) { const contentType = resolveChatContentType(file); if (contentType.startsWith('image/') || contentType.startsWith('video/')) { return URL.createObjectURL(file); } return null; }