73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
/** اندازهٔ استاندارد کاور فرم (نسبت ۳:۱) */
|
|
export const COVER_WIDTH = 1200;
|
|
export const COVER_HEIGHT = 400;
|
|
const MAX_INPUT_BYTES = 15_000_000;
|
|
/** تصاویر عریضتر از این نسبت بنر حساب میشوند و برش میخورند؛ بقیه لوگو هستند و کامل جا میگیرند */
|
|
const BANNER_MIN_RATIO = 2;
|
|
|
|
function loadImage(file: File): Promise<HTMLImageElement> {
|
|
return new Promise((resolve, reject) => {
|
|
const url = URL.createObjectURL(file);
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
URL.revokeObjectURL(url);
|
|
resolve(img);
|
|
};
|
|
img.onerror = () => {
|
|
URL.revokeObjectURL(url);
|
|
reject(new Error('تصویر قابل خواندن نیست.'));
|
|
};
|
|
img.src = url;
|
|
});
|
|
}
|
|
|
|
function toBlob(canvas: HTMLCanvasElement, type: string, quality?: number): Promise<Blob> {
|
|
return new Promise((resolve, reject) => {
|
|
canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('پردازش تصویر ناموفق بود.'))), type, quality);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* تصویر را به کاور ۱۲۰۰×۴۰۰ تبدیل میکند:
|
|
* بنرهای عریض از وسط برش میخورند (JPEG)،
|
|
* لوگوها و تصاویر مربعی/عمودی بدون برش وسط کادر با پسزمینهٔ شفاف قرار میگیرند (PNG) تا رنگ فرم پشتشان دیده شود.
|
|
*/
|
|
export async function prepareFormCover(file: File): Promise<File> {
|
|
if (!file.type.startsWith('image/')) throw new Error('فقط فایل تصویری (PNG، JPG، WEBP، SVG) مجاز است.');
|
|
if (file.size > MAX_INPUT_BYTES) throw new Error('حجم تصویر بیش از ۱۵ مگابایت است.');
|
|
|
|
const img = await loadImage(file);
|
|
const w = img.naturalWidth || img.width;
|
|
const h = img.naturalHeight || img.height;
|
|
if (!w || !h) throw new Error('ابعاد تصویر نامعتبر است.');
|
|
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = COVER_WIDTH;
|
|
canvas.height = COVER_HEIGHT;
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) throw new Error('مرورگر از پردازش تصویر پشتیبانی نمیکند.');
|
|
ctx.imageSmoothingEnabled = true;
|
|
ctx.imageSmoothingQuality = 'high';
|
|
|
|
const ratio = w / h;
|
|
if (ratio >= BANNER_MIN_RATIO) {
|
|
const target = COVER_WIDTH / COVER_HEIGHT;
|
|
let sw = w;
|
|
let sh = h;
|
|
if (ratio > target) sw = h * target;
|
|
else sh = w / target;
|
|
ctx.drawImage(img, (w - sw) / 2, (h - sh) / 2, sw, sh, 0, 0, COVER_WIDTH, COVER_HEIGHT);
|
|
const blob = await toBlob(canvas, 'image/jpeg', 0.88);
|
|
return new File([blob], 'cover.jpg', { type: 'image/jpeg' });
|
|
}
|
|
|
|
const boxW = COVER_WIDTH * 0.6;
|
|
const boxH = COVER_HEIGHT * 0.7;
|
|
const scale = Math.min(boxW / w, boxH / h, 3);
|
|
const dw = w * scale;
|
|
const dh = h * scale;
|
|
ctx.drawImage(img, (COVER_WIDTH - dw) / 2, (COVER_HEIGHT - dh) / 2, dw, dh);
|
|
const blob = await toBlob(canvas, 'image/png');
|
|
return new File([blob], 'cover.png', { type: 'image/png' });
|
|
}
|