Files
IdP/apps/frontend/lib/chat-image-compress.ts
lendry 7fc3ca7952 fix
2026-07-10 12:37:54 +03:00

118 lines
3.5 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.
import { resolveChatContentType } from '@/lib/chat-media';
const DEFAULT_MAX_EDGE = 2560;
const DEFAULT_QUALITY = 0.82;
function loadImageFromFile(file: File) {
return new Promise<HTMLImageElement>((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<Blob>((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;
}