This commit is contained in:
lendry
2026-06-26 17:49:22 +03:00
parent c23f35e732
commit 4e853f8041
17 changed files with 1547 additions and 167 deletions

View File

@@ -0,0 +1,114 @@
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) {
if (file.type.startsWith('image/')) {
return URL.createObjectURL(file);
}
return null;
}