Files
auditly/utils/imageUtils.ts

157 lines
4.4 KiB
TypeScript

/**
* Image processing utilities for resizing and encoding images
*/
export interface ProcessedImage {
dataUrl: string;
blob: Blob;
width: number;
height: number;
originalSize: number;
compressedSize: number;
}
/**
* Resize an image to a specific size and convert to base64
* @param file - The image file to process
* @param maxWidth - Maximum width (default: 128)
* @param maxHeight - Maximum height (default: 128)
* @param quality - JPEG quality (0-1, default: 0.8)
* @returns Promise with processed image data
*/
export const processImage = async (
file: File,
maxWidth: number = 128,
maxHeight: number = 128,
quality: number = 0.8
): Promise<ProcessedImage> => {
return new Promise((resolve, reject) => {
// Validate file type
if (!file.type.startsWith('image/')) {
reject(new Error('File must be an image'));
return;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
// Calculate dimensions to maintain aspect ratio
let { width, height } = calculateDimensions(
img.width,
img.height,
maxWidth,
maxHeight
);
canvas.width = width;
canvas.height = height;
// Draw and resize image
ctx!.imageSmoothingEnabled = true;
ctx!.imageSmoothingQuality = 'high';
ctx!.drawImage(img, 0, 0, width, height);
// Convert to blob and data URL
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('Failed to process image'));
return;
}
const dataUrl = canvas.toDataURL('image/jpeg', quality);
resolve({
dataUrl,
blob,
width,
height,
originalSize: file.size,
compressedSize: blob.size,
});
},
'image/jpeg',
quality
);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
// Load the image
img.src = URL.createObjectURL(file);
});
};
/**
* Calculate dimensions to fit within max bounds while maintaining aspect ratio
*/
const calculateDimensions = (
originalWidth: number,
originalHeight: number,
maxWidth: number,
maxHeight: number
): { width: number; height: number } => {
const aspectRatio = originalWidth / originalHeight;
let width = maxWidth;
let height = maxHeight;
if (originalWidth > originalHeight) {
// Landscape
height = width / aspectRatio;
if (height > maxHeight) {
height = maxHeight;
width = height * aspectRatio;
}
} else {
// Portrait or square
width = height * aspectRatio;
if (width > maxWidth) {
width = maxWidth;
height = width / aspectRatio;
}
}
return {
width: Math.round(width),
height: Math.round(height),
};
};
/**
* Validate image file
*/
export const validateImageFile = (file: File): { valid: boolean; error?: string } => {
// Check file type
if (!file.type.startsWith('image/')) {
return { valid: false, error: 'File must be an image' };
}
// Check file size (max 10MB)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
return { valid: false, error: 'Image must be smaller than 10MB' };
}
// Check supported formats
const supportedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
if (!supportedTypes.includes(file.type)) {
return { valid: false, error: 'Supported formats: JPEG, PNG, GIF, WebP' };
}
return { valid: true };
};
/**
* Generate a unique filename
*/
export const generateUniqueFileName = (originalName: string, prefix: string = 'img'): string => {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8);
const extension = originalName.split('.').pop() || 'jpg';
return `${prefix}_${timestamp}_${random}.${extension}`;
};