157 lines
4.4 KiB
TypeScript
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}`;
|
|
}; |