tauri-android-tax/qr-test.html

245 lines
9.4 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>QR 诊断测试</title>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
<style>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #f5f5f5; }
h2 { margin: 0 0 8px; }
.card { background: #fff; border-radius: 8px; padding: 16px; margin-bottom: 12px; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
input[type=file] { margin: 4px 0 12px; }
img, canvas { max-width: 100%; border: 1px solid #ccc; border-radius: 6px; margin: 6px 0; }
button { padding: 8px 14px; border: none; border-radius: 6px; background: #1976d2; color: #fff; font-size: 14px; cursor: pointer; margin-right: 8px; }
.result { margin-top: 10px; padding: 10px; border-radius: 6px; font-size: 13px; white-space: pre-wrap; word-break: break-all; }
.ok { background: #e8f5e9; color: #2e7d32; }
.fail { background: #ffebee; color: #c62828; }
.info { background: #e3f2fd; color: #1565c0; }
.row { display: flex; flex-wrap: wrap; gap: 12px; }
.col { flex: 1; min-width: 280px; }
</style>
</head>
<body>
<h2>jsQR 二维码诊断工具</h2>
<div class="card">
<input type="file" id="file" accept="image/*" />
<button onclick="testAll()">全面诊断</button>
<button onclick="testFull()">原始尺寸识别</button>
<button onclick="testHalf()">50%缩放识别</button>
<button onclick="testQuarter()">25%缩放识别</button>
<button onclick="testCenter()">中心裁剪识别</button>
</div>
<div class="row">
<div class="col">
<h3>原图</h3>
<img id="preview" style="display:none" />
</div>
<div class="col">
<h3>当前处理结果</h3>
<canvas id="resultCanvas" style="display:none"></canvas>
</div>
</div>
<div id="diag" style="display:none">
<h3>诊断日志</h3>
<div id="log"></div>
</div>
<script>
const fileInput = document.getElementById('file');
const preview = document.getElementById('preview');
const resultCanvas = document.getElementById('resultCanvas');
const logDiv = document.getElementById('log');
const diagDiv = document.getElementById('diag');
let cachedImageData = null;
/** 获取图片的 ImageData 缓存。@returns {Promise<ImageData>} */
async function getImageData() {
if (cachedImageData) return cachedImageData;
const file = fileInput.files[0];
if (!file) throw new Error('请先选择图片');
const url = URL.createObjectURL(file);
preview.src = url;
preview.style.display = 'block';
const img = await loadImage(url);
const c = document.createElement('canvas');
c.width = img.width; c.height = img.height;
const ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0);
cachedImageData = ctx.getImageData(0, 0, img.width, img.height);
URL.revokeObjectURL(url);
return cachedImageData;
}
/** 加载图片。@param {string} url @returns {Promise<HTMLImageElement>} */
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('图片加载失败'));
img.src = url;
});
}
/**
* 尝试 JSQR 识别并返回结果。
* @param {ImageData} imageData 图片数据。
* @param {string} label 测试标签。
* @returns {{ok:boolean, data?:string, msg:string}}
*/
function tryDecode(imageData, label) {
const start = performance.now();
const code = jsQR(imageData.data, imageData.width, imageData.height, {
inversionAttempts: 'attemptBoth',
});
const ms = (performance.now() - start).toFixed(0);
if (code) {
return { ok: true, data: code.data, msg: `${label}: 成功 (${ms}ms) → ${code.data}` };
}
return { ok: false, msg: `${label}: 失败 (${ms}ms) — ${imageData.width}x${imageData.height}` };
}
/**
* 缩放 ImageData 到指定倍数。
* @param {ImageData} src 源数据。
* @param {number} scale 缩放倍率 (0.5=一半, 0.25=四分之一)。
* @returns {ImageData}
*/
function scaleImageData(src, scale) {
const w = Math.floor(src.width * scale);
const h = Math.floor(src.height * scale);
const c = document.createElement('canvas');
c.width = w; c.height = h;
const ctx = c.getContext('2d');
const temp = document.createElement('canvas');
temp.width = src.width; temp.height = src.height;
temp.getContext('2d').putImageData(src, 0, 0);
ctx.drawImage(temp, 0, 0, w, h);
return ctx.getImageData(0, 0, w, h);
}
/**
* 裁剪图片中心区域(可能包含二维码的核心部分)。
* @param {ImageData} src 源数据。
* @returns {ImageData}
*/
function cropCenter(src) {
const cw = Math.floor(src.width * 0.6);
const ch = Math.floor(src.height * 0.6);
const ox = Math.floor((src.width - cw) / 2);
const oy = Math.floor((src.height - ch) / 2);
const c = document.createElement('canvas');
c.width = cw; c.height = ch;
const ctx = c.getContext('2d');
const temp = document.createElement('canvas');
temp.width = src.width; temp.height = src.height;
temp.getContext('2d').putImageData(src, 0, 0);
ctx.drawImage(temp, ox, oy, cw, ch, 0, 0, cw, ch);
return ctx.getImageData(0, 0, cw, ch);
}
/** 显示处理后的图片到 canvas。@param {ImageData} data */
function showResult(data) {
resultCanvas.width = data.width;
resultCanvas.height = data.height;
resultCanvas.getContext('2d').putImageData(data, 0, 0);
resultCanvas.style.display = 'block';
}
/** 添加日志。@param {string} msg @param {string} cls */
function addLog(msg, cls) {
const div = document.createElement('div');
div.className = 'result ' + cls;
div.textContent = msg;
logDiv.appendChild(div);
}
/** 原始尺寸识别。@returns {Promise<void>} */
async function testFull() {
cachedImageData = null;
diagDiv.style.display = 'block';
logDiv.innerHTML = '';
try {
const data = await getImageData();
addLog(`图片原始尺寸: ${data.width}x${data.height}`, 'info');
showResult(data);
const r = tryDecode(data, '原始尺寸');
addLog(r.msg, r.ok ? 'ok' : 'fail');
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
}
/** 50% 缩放识别。@returns {Promise<void>} */
async function testHalf() {
try {
const data = await getImageData();
const half = scaleImageData(data, 0.5);
showResult(half);
addLog(`缩放至 50%: ${half.width}x${half.height}`, 'info');
const r = tryDecode(half, '50%缩放');
addLog(r.msg, r.ok ? 'ok' : 'fail');
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
}
/** 25% 缩放识别。@returns {Promise<void>} */
async function testQuarter() {
try {
const data = await getImageData();
const quarter = scaleImageData(data, 0.25);
showResult(quarter);
addLog(`缩放至 25%: ${quarter.width}x${quarter.height}`, 'info');
const r = tryDecode(quarter, '25%缩放');
addLog(r.msg, r.ok ? 'ok' : 'fail');
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
}
/** 中心裁剪识别。@returns {Promise<void>} */
async function testCenter() {
try {
const data = await getImageData();
const crop = cropCenter(data);
showResult(crop);
addLog(`中心裁剪: ${crop.width}x${crop.height}`, 'info');
const r = tryDecode(crop, '中心裁剪');
addLog(r.msg, r.ok ? 'ok' : 'fail');
} catch (e) { addLog('错误: ' + e.message, 'fail'); }
}
/** 全面诊断:依次尝试多种策略。@returns {Promise<void>} */
async function testAll() {
diagDiv.style.display = 'block';
logDiv.innerHTML = '';
try {
const data = await getImageData();
addLog(`图片原始尺寸: ${data.width}x${data.height} (${(data.width * data.height / 1000000).toFixed(1)}MP)`, 'info');
const tests = [
{ fn: () => tryDecode(data, '原始尺寸'), show: () => showResult(data) },
{ fn: () => tryDecode(scaleImageData(data, 0.5), '50%缩放'), show: () => showResult(scaleImageData(data, 0.5)) },
{ fn: () => tryDecode(scaleImageData(data, 0.25), '25%缩放'), show: () => showResult(scaleImageData(data, 0.25)) },
{ fn: () => tryDecode(scaleImageData(data, 0.125), '12.5%缩放'), show: () => showResult(scaleImageData(data, 0.125)) },
{ fn: () => tryDecode(cropCenter(data), '中心60%裁剪'), show: () => showResult(cropCenter(data)) },
{ fn: () => tryDecode(scaleImageData(cropCenter(data), 0.5), '中心裁剪+50%缩放'), show: () => showResult(scaleImageData(cropCenter(data), 0.5)) },
];
for (const t of tests) {
t.show();
const r = t.fn();
addLog(r.msg, r.ok ? 'ok' : 'fail');
if (r.ok) {
addLog('🎉 识别成功!内容: ' + r.data, 'ok');
break;
}
}
} catch (e) {
addLog('错误: ' + e.message, 'fail');
}
}
</script>
</body>
</html>