// 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(); }