125 lines
4.1 KiB
JavaScript
125 lines
4.1 KiB
JavaScript
// image-processor.worker.js
|
|
self.onmessage = async function (e) {
|
|
const { imageUrl, text, options, id } = e.data;
|
|
|
|
try {
|
|
// Загружаем изображение как Blob
|
|
const response = await fetch(imageUrl, {
|
|
mode: 'cors',
|
|
credentials: 'same-origin'
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
|
|
const imageBlob = await response.blob();
|
|
const imageBitmap = await createImageBitmap(imageBlob);
|
|
|
|
// Создаём OffscreenCanvas
|
|
const offscreen = new OffscreenCanvas(
|
|
options.width ?? imageBitmap.width,
|
|
options.height ?? imageBitmap.height
|
|
);
|
|
|
|
const ctx = offscreen.getContext('2d', { alpha: true });
|
|
|
|
// Рисуем изображение
|
|
ctx.drawImage(imageBitmap, 0, 0, offscreen.width, offscreen.height);
|
|
|
|
if (!text?.trim()) {
|
|
const blob = await offscreen.convertToBlob({
|
|
type: options.type ?? 'image/png',
|
|
quality: options.quality ?? 0.92
|
|
});
|
|
self.postMessage({ id, blob }, [blob]);
|
|
return;
|
|
}
|
|
|
|
// === Отрисовка текста ===
|
|
const basePadding = 6;
|
|
const fontSize = options.fontSize ?? Math.round(offscreen.width * 0.05);
|
|
ctx.font = `${fontSize}px ${options.fontFamily ?? 'sans-serif'}`;
|
|
ctx.textBaseline = 'bottom';
|
|
ctx.textAlign = 'center';
|
|
|
|
const textColor = options.textColor ?? '#5F1798';
|
|
const bgColor = options.bgColor ?? '#fff';
|
|
const strokeColor = options.strokeColor ?? 'rgba(0,0,0,0.35)';
|
|
const shadowBlur = options.shadowBlur ?? 6;
|
|
const maxWidth = offscreen.width - basePadding * 2 - (options.extraPadding ?? 0);
|
|
|
|
// Разбивка текста на строки
|
|
const words = text.split(' ');
|
|
let line = '';
|
|
const lines = [];
|
|
for (const word of words) {
|
|
const testLine = line ? line + ' ' + word : word;
|
|
if (ctx.measureText(testLine).width > maxWidth && line) {
|
|
lines.push(line);
|
|
line = word;
|
|
} else {
|
|
line = testLine;
|
|
}
|
|
}
|
|
if (line) lines.push(line);
|
|
|
|
const lineHeight = fontSize + (options.lineGap ?? 6);
|
|
const boxHeight = lines.length * lineHeight + basePadding * 2;
|
|
const maxLineWidth = Math.max(...lines.map(l => ctx.measureText(l).width));
|
|
const boxWidth = maxLineWidth + basePadding * 2;
|
|
|
|
const bottomPadding = options.bottomPadding ?? Math.round(fontSize * 0.6);
|
|
const boxX = (offscreen.width - boxWidth) / 2;
|
|
const boxY = offscreen.height - bottomPadding - boxHeight;
|
|
const radius = options.radius ?? 8;
|
|
|
|
// Фон под текстом
|
|
ctx.save();
|
|
ctx.fillStyle = bgColor;
|
|
ctx.shadowColor = strokeColor;
|
|
ctx.shadowBlur = shadowBlur;
|
|
roundRect(ctx, boxX, boxY, boxWidth, boxHeight, radius, true, false);
|
|
ctx.restore();
|
|
|
|
// Текст
|
|
ctx.fillStyle = textColor;
|
|
ctx.shadowColor = 'transparent';
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const lineY = boxY + basePadding + (i + 1) * lineHeight - (lineHeight - fontSize) / 2;
|
|
const lineX = offscreen.width / 2;
|
|
|
|
if (options.outline) {
|
|
ctx.lineWidth = options.outlineWidth ?? 2;
|
|
ctx.strokeStyle = options.outlineColor ?? 'rgba(255,255,255,0.8)';
|
|
ctx.strokeText(lines[i], lineX, lineY);
|
|
}
|
|
ctx.fillText(lines[i], lineX, lineY);
|
|
}
|
|
|
|
const blob = await offscreen.convertToBlob({
|
|
type: options.type ?? 'image/png',
|
|
quality: options.quality ?? 0.92
|
|
});
|
|
|
|
self.postMessage({ id, blob }, [blob]); // Transferable object
|
|
|
|
} catch (err) {
|
|
self.postMessage({ id, error: err.message });
|
|
}
|
|
};
|
|
|
|
function roundRect(ctx, x, y, w, h, r, fill, stroke) {
|
|
if (typeof r === 'number') r = { tl: r, tr: r, br: r, bl: r };
|
|
ctx.beginPath();
|
|
ctx.moveTo(x + r.tl, y);
|
|
ctx.lineTo(x + w - r.tr, y);
|
|
ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr);
|
|
ctx.lineTo(x + w, y + h - r.br);
|
|
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
|
|
ctx.lineTo(x + r.bl, y + h);
|
|
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
|
|
ctx.lineTo(x, y + r.tl);
|
|
ctx.quadraticCurveTo(x, y, x + r.tl, y);
|
|
ctx.closePath();
|
|
if (fill) ctx.fill();
|
|
if (stroke) ctx.stroke();
|
|
} |