From 36c231d05cd3271960d5d1a183b70336aa1973ff5ffcf6cc887a17fc9e9278f0 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Tue, 30 Jun 2026 19:13:51 -0400 Subject: [PATCH] =?UTF-8?q?Bot=20commit:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D1=84=D0=B0=D0=B9=D0=BB=20static/image-pr?= =?UTF-8?q?ocessor.worker.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- static/image-processor.worker.js | 125 +++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 static/image-processor.worker.js diff --git a/static/image-processor.worker.js b/static/image-processor.worker.js new file mode 100644 index 0000000..dd06400 --- /dev/null +++ b/static/image-processor.worker.js @@ -0,0 +1,125 @@ +// 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(); +} \ No newline at end of file