From 9f9ea255a83f86623cf4e11ce182bd2eaf13f210 Mon Sep 17 00:00:00 2001 From: Grzegorz Kucmierz Date: Wed, 4 Mar 2026 03:18:06 +0000 Subject: [PATCH] feat(qr): implement background style and gradient support for QR generation --- src/components/tools/QrCode.vue | 34 +++++++++++++++++++++------- src/workers/qrcode.worker.js | 39 +++++++++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/components/tools/QrCode.vue b/src/components/tools/QrCode.vue index 9c15e98..8547cfa 100644 --- a/src/components/tools/QrCode.vue +++ b/src/components/tools/QrCode.vue @@ -13,9 +13,10 @@ const router = useRouter() const text = useLocalStorage('text', '', 'qr-code') const ecc = useLocalStorage('ecc', 'M', 'qr-code') const size = useLocalStorage('size', 300, 'qr-code') -const format = useLocalStorage('format', 'png', 'qr-code') const isBgTransparent = useLocalStorage('isBgTransparent', true, 'qr-code') -const bgColor = useLocalStorage('bgColor', '#ffffff', 'qr-code') +const bgType = useLocalStorage('bgType', 'solid', 'qr-code') +const bgColor1 = useLocalStorage('bgColor1', '#ffffff', 'qr-code') +const bgColor2 = useLocalStorage('bgColor2', '#e2e8f0', 'qr-code') const fgType = useLocalStorage('fgType', 'solid', 'qr-code') const fgColor1 = useLocalStorage('fgColor1', '#000000', 'qr-code') const fgColor2 = useLocalStorage('fgColor2', '#10b981', 'qr-code') @@ -60,14 +61,16 @@ const generateQR = () => { text: text.value, ecc: ecc.value, isBgTransparent: isBgTransparent.value, - bgColor: bgColor.value, + bgType: bgType.value, + bgColor1: bgColor1.value, + bgColor2: bgColor2.value, fgType: fgType.value, fgColor1: fgColor1.value, fgColor2: fgColor2.value }) } -watch([text, ecc, isBgTransparent, bgColor, fgType, fgColor1, fgColor2], () => { +watch([text, ecc, isBgTransparent, bgType, bgColor1, bgColor2, fgType, fgColor1, fgColor2], () => { generateQR() }) @@ -128,6 +131,11 @@ const downloadFile = async () => { if (format.value === 'jpeg' && isBgTransparent.value) { ctx.fillStyle = '#ffffff' ctx.fillRect(0, 0, size.value, size.value) + } else if (format.value === 'jpeg' && bgType.value !== 'solid') { + // Let the Canvas render the SVG's background gradient naturally instead of filling + // Though drawing bounding rect white might still be needed behind transparent parts + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, size.value, size.value) } ctx.drawImage(img, 0, 0, size.value, size.value) @@ -204,10 +212,20 @@ const triggerDownload = (blob, filename) => {
- + + +
+ +
+
- -
@@ -216,7 +234,7 @@ const triggerDownload = (blob, filename) => {
-
+
diff --git a/src/workers/qrcode.worker.js b/src/workers/qrcode.worker.js index e5e64c7..956bc94 100644 --- a/src/workers/qrcode.worker.js +++ b/src/workers/qrcode.worker.js @@ -1,7 +1,7 @@ import QRCode from 'qrcode' self.onmessage = async (e) => { - const { id, text, ecc, isBgTransparent, bgColor, fgType, fgColor1, fgColor2 } = e.data + const { id, text, ecc, isBgTransparent, bgType, bgColor1, bgColor2, fgType, fgColor1, fgColor2 } = e.data if (!text) { self.postMessage({ id, svgContent: '' }) @@ -15,19 +15,44 @@ self.onmessage = async (e) => { margin: 1, color: { dark: fgType === 'solid' ? fgColor1 : '#000000', - light: isBgTransparent ? '#00000000' : bgColor + light: isBgTransparent ? '#00000000' : (bgType === 'solid' ? bgColor1 : '#00000000') } }) + let defsHtml = '' + if (fgType !== 'solid') { const isLinear = fgType === 'linear' - const defs = isLinear - ? `` - : `` + defsHtml += isLinear + ? `` + : `` + } - svgContent = svgContent.replace('shape-rendering="crispEdges">', `shape-rendering="crispEdges">${defs}`) + if (!isBgTransparent && bgType !== 'solid') { + const isLinear = bgType === 'linear' + defsHtml += isLinear + ? `` + : `` + } + + if (defsHtml) { + svgContent = svgContent.replace('shape-rendering="crispEdges">', `shape-rendering="crispEdges">${defsHtml}`) + } + + if (fgType !== 'solid') { // qrcode outputs so it's safe to replace - svgContent = svgContent.replace(/stroke="#000000"/g, 'stroke="url(#qr-grad)"') + svgContent = svgContent.replace(/stroke="#000000"/g, 'stroke="url(#qr-fg-grad)"') + } + + if (!isBgTransparent && bgType !== 'solid') { + // Find viewBox to inject background rect + const viewBoxMatch = svgContent.match(/viewBox="0 0 (\d+) (\d+)"/) + if (viewBoxMatch) { + const w = viewBoxMatch[1] + const h = viewBoxMatch[2] + // Inject a rect immediately inside the svg + svgContent = svgContent.replace('', ``) + } } self.postMessage({ id, svgContent })