77 lines
2.8 KiB
JavaScript
77 lines
2.8 KiB
JavaScript
// 二维码解码核心逻辑:与 UI、相机、Tauri 解耦,便于单元测试。
|
||
// 相机扫码走原生插件,图片扫码走纯 JS 的 jsQR,桌面端也可用。
|
||
import jsQR from "jsqr";
|
||
|
||
/**
|
||
* 校验扫码内容是否为有效网址(仅接受 http/https)。
|
||
* @param {unknown} content 扫码得到的原始内容。
|
||
* @returns {string} 合法网址(去除首尾空白);非法时返回空字符串。
|
||
*/
|
||
export function extractUrl(content) {
|
||
if (typeof content !== "string") return "";
|
||
const text = content.trim();
|
||
return /^https?:\/\//i.test(text) ? text : "";
|
||
}
|
||
|
||
/**
|
||
* 从像素数据中解码二维码。
|
||
* @param {{data: Uint8ClampedArray, width: number, height: number}} imageData
|
||
* 画布像素数据(RGBA)。
|
||
* @returns {string|null} 解码到的文本;未识别到返回 null。
|
||
*/
|
||
export function decodeQrFromImageData(imageData) {
|
||
if (!imageData || !imageData.data || !imageData.width || !imageData.height) {
|
||
return null;
|
||
}
|
||
// attemptBoth 允许识别反色(浅底深码或深底浅码)二维码,提升清晰码的识别率。
|
||
const result = jsQR(imageData.data, imageData.width, imageData.height, {
|
||
inversionAttempts: "attemptBoth",
|
||
});
|
||
return result ? result.data : null;
|
||
}
|
||
|
||
/**
|
||
* 将图片元素绘制到离屏画布并取出像素数据(仅浏览器环境可用)。
|
||
* @param {CanvasImageSource & {width:number,height:number}} image 已加载的图片源。
|
||
* @param {number} [maxSize=1600] 长边最大像素,过大图片等比缩小以兼顾速度与精度。
|
||
* @returns {ImageData} 画布像素数据。
|
||
*/
|
||
export function imageToImageData(image, maxSize = 1600) {
|
||
const sw = image.naturalWidth || image.width;
|
||
const sh = image.naturalHeight || image.height;
|
||
const scale = Math.min(1, maxSize / Math.max(sw, sh));
|
||
const w = Math.max(1, Math.round(sw * scale));
|
||
const h = Math.max(1, Math.round(sh * scale));
|
||
const canvas = document.createElement("canvas");
|
||
canvas.width = w;
|
||
canvas.height = h;
|
||
const ctx = canvas.getContext("2d");
|
||
ctx.drawImage(image, 0, 0, w, h);
|
||
return ctx.getImageData(0, 0, w, h);
|
||
}
|
||
|
||
/**
|
||
* 从文件(用户选择的图片)解码二维码(仅浏览器环境可用)。
|
||
* @param {Blob} file 图片文件。
|
||
* @returns {Promise<string|null>} 解码文本;未识别到返回 null。
|
||
*/
|
||
export function decodeQrFromFile(file) {
|
||
return new Promise((resolve, reject) => {
|
||
const url = URL.createObjectURL(file);
|
||
const img = new Image();
|
||
img.onload = () => {
|
||
URL.revokeObjectURL(url);
|
||
try {
|
||
resolve(decodeQrFromImageData(imageToImageData(img)));
|
||
} catch (e) {
|
||
reject(e);
|
||
}
|
||
};
|
||
img.onerror = () => {
|
||
URL.revokeObjectURL(url);
|
||
reject(new Error("图片加载失败"));
|
||
};
|
||
img.src = url;
|
||
});
|
||
}
|