From 2ecddb17aa5bf0c64f86c14d5aea5e1b5bcfeed4 Mon Sep 17 00:00:00 2001 From: Frost-ZX Date: Sun, 23 Feb 2025 22:56:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=B7=A5=E5=85=B7=E7=AE=B1/=E4=BA=8C?= =?UTF-8?q?=E7=BB=B4=E7=A0=81=E8=A7=A3=E6=9E=90=E5=92=8C=E7=94=9F=E6=88=90?= =?UTF-8?q?):=20=E4=BC=98=E5=8C=96=E8=A7=A3=E6=9E=90=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=9C=A8=E4=BA=8C=E7=BB=B4=E7=A0=81=E6=89=80=E5=9C=A8?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E6=B7=BB=E5=8A=A0=E7=9F=A9=E5=BD=A2=E6=A0=87?= =?UTF-8?q?=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/assets/js/qr-code.js | 184 ++++++++++++------ .../Conversion/QrcodeReaderAndGenerator.vue | 2 +- 2 files changed, 124 insertions(+), 62 deletions(-) diff --git a/src/assets/js/qr-code.js b/src/assets/js/qr-code.js index 21d2182..ca152e4 100644 --- a/src/assets/js/qr-code.js +++ b/src/assets/js/qr-code.js @@ -51,31 +51,49 @@ prepareZXingModule({ }); /** - * @description 转换 Blob 为 DataURL - * @param {Blob} blob - * @param {Callback} callback + * @description 在图片上绘制矩形,返回 DataURL + * @param {Blob} blob 图片二进制 + * @param {Rect[]} rects 矩形位置信息列表 */ -function blobToDataURL(blob, callback) { +function drawRectsOnImage(blob, rects) { - /** @typedef {(data: { error: boolean, result: string }) => void} Callback */ + /** @typedef {{ x: number; y: number; w: number; h: number }} Rect */ - let reader = new FileReader(); + let canvas = document.createElement('canvas'); + let ctx = canvas.getContext('2d'); + + return renderImageToCanvas(blob, canvas).then((success) => { + + if (!success) { + return ''; + } + + rects.forEach((rect, index) => { + + let { x, y, w, h } = rect; + + let text = String(index + 1); + + // 绘制矩形 + ctx.fillStyle = 'rgba(0, 255, 0, 0.25)'; + ctx.lineWidth = 4; + ctx.strokeStyle = 'rgba(0, 255, 0, 1)'; + ctx.fillRect(x, y, w, h); + ctx.strokeRect(x, y, w, h); + + // 绘制文本 + ctx.font = `bold ${Math.round(w / 2)}px sans-serif`; + ctx.fillStyle = 'rgba(255, 255, 255, 1)'; + ctx.lineWidth = Number((w / 100).toFixed(4)); + ctx.strokeStyle = 'rgba(0, 0, 0, 1)'; + ctx.fillText(text, x, y + h); + ctx.strokeText(text, x, y + h); - reader.onerror = function () { - callback({ - error: true, - result: reader.result, }); - }; - reader.onload = function () { - callback({ - error: false, - result: reader.result, - }); - }; + return canvas.toDataURL('image/png'); - reader.readAsDataURL(blob); + }); } @@ -143,6 +161,54 @@ function modifySvgContent(options) { } +/** + * 将图片 Blob 渲染到 Canvas + * @param {Blob} blob + * @param {HTMLCanvasElement} canvas + * @returns {Promise} + */ +function renderImageToCanvas(blob, canvas) { + + let ctx = canvas.getContext('2d'); + + if (window.createImageBitmap) { + return createImageBitmap(blob).then((bitmap) => { + canvas.width = bitmap.width; + canvas.height = bitmap.height; + ctx.drawImage(bitmap, 0, 0); + return true; + }).catch((error) => { + console.error('渲染图片失败:'); + console.error(error); + return false; + }); + } else { + return new Promise((resolve) => { + + let image = new Image(); + let url = URL.createObjectURL(blob); + + image.onload = () => { + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + URL.revokeObjectURL(url); + ctx.drawImage(image, 0, 0); + resolve(true); + }; + + image.onerror = () => { + console.error(PREFIX, '渲染图片失败:加载图片失败'); + URL.revokeObjectURL(url); + resolve(false); + }; + + image.src = url; + + }); + } + +} + /** * @description 将 SVG 字符串渲染到 Canvas * @param {object} options @@ -177,11 +243,12 @@ function renderSvgToCanvas(options) { return new Promise((resolve) => { + let ctx = canvas.getContext('2d'); + let image = new Image(); let svgBlob = new Blob([svgInfo.svgString], { type: 'image/svg+xml;charset=utf-8', }); let svgUrl = URL.createObjectURL(svgBlob); - let image = new Image(); image.onerror = () => { console.error(PREFIX, '加载 SVG 失败'); @@ -190,7 +257,6 @@ function renderSvgToCanvas(options) { }; image.onload = () => { - let ctx = canvas.getContext('2d'); ctx.drawImage( image, svgInfo.offsetX, svgInfo.offsetY, svgInfo.sizeW, svgInfo.sizeH, @@ -207,74 +273,70 @@ function renderSvgToCanvas(options) { /** * @description 解析二维码图片 - * @param {Blob} image 图片二进制 + * @param {Blob} blob 图片二进制 */ -export function readQrCodeImage(image) { +export function readQrCodeImage(blob) { /** * @desc 返回结果 * @type {{ error: string; image: string; textList: string[]; }} */ - let returns = { + let result = { error: '', image: '', textList: [], }; - /** 读取图片,转换为 DataURL */ - let fileReader = new FileReader(); + return readBarcodes(blob, readerOptions).then((codeList) => { - return new Promise((resolve) => { + let rectList = []; + let textList = result.textList; - // 处理读取异常 - fileReader.onerror = function () { - console.error(PREFIX, '解析二维码失败:读取图片失败'); - returns.error = '读取图片失败'; - resolve(''); - }; - - // 处理读取完成 - fileReader.onload = function () { - resolve(fileReader.result); - }; - - // 开始读取 - fileReader.readAsDataURL(image); - - }).then((dataURL) => { - if (dataURL) { - returns.image = dataURL; - return readBarcodes(image, readerOptions); - } else { - return null; - } - }).then((resultList) => { - - let textList = returns.textList; - - if (resultList.length === 0) { + if (codeList.length === 0) { console.warn(PREFIX, '解析二维码失败:未识别到内容'); - return returns; } else { - console.debug(PREFIX, '解析二维码成功:', resultList); + console.debug(PREFIX, '解析二维码成功:', codeList); } - for (let i = 0; i < resultList.length; i++) { + for (let i = 0; i < codeList.length; i++) { - let item = resultList[i]; + let item = codeList[i]; + let position = item.position; + let posX0 = position.topLeft.x; + let posX1 = position.bottomRight.x; + let posY0 = position.topLeft.y; + let posY1 = position.bottomRight.y; + // 记录二维码坐标 + rectList.push({ + x: posX0, + y: posY0, + w: posX1 - posX0, + h: posY1 - posY0, + }); + + // 记录二维码文本 textList.push(item.text); } - return returns; + // 框选二维码区域 + return drawRectsOnImage(blob, rectList); + + }).then((dataURL) => { + + if (dataURL) { + result.image = dataURL; + } + + return result; }).catch((error) => { console.error(PREFIX, '解析二维码失败:'); console.error(error); - returns.error = String(error); - return returns; - });; + result.error = String(error); + return result; + }); } diff --git a/src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue b/src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue index e5d9860..de53a1d 100644 --- a/src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue +++ b/src/views/ToolboxView/Conversion/QrcodeReaderAndGenerator.vue @@ -50,7 +50,7 @@ object-fit="contain" width="100%" height="100%" - :preview-disabled="true" + :preview-disabled="false" :src="readerData.dataURL" />