emblemscanner: worker is okay
This commit is contained in:
parent
780acc13ce
commit
7d0e30656e
@ -102,8 +102,8 @@ qrtool.wx.wasm.br: qrtool.wx.js
|
||||
brotli -kf qrtool.wx.wasm
|
||||
|
||||
install-scanner: qrtool.wx.wasm.br
|
||||
@cp -v qrtool.wx.js qrtool.wx.wasm.br ../scanner/assets
|
||||
@cp -v qrtool.wx.js ../scanner/worker
|
||||
@cp -v qrtool.wx.js qrtool.wx.wasm.br ../scanner/pages/emblemscanner/
|
||||
@cp -v qrtool.wx.js ../scanner/pages/emblemscanner/worker/
|
||||
|
||||
install-web: qrtool.web.wasm
|
||||
@cp -v qrtool.web.js qrtool.web.wasm ../web/public/camera-5.0/js/
|
||||
|
||||
@ -7,7 +7,7 @@ var performance = {
|
||||
};
|
||||
Module['instantiateWasm'] = (info, receiveInstance) => {
|
||||
console.log("loading wasm...", info);
|
||||
WebAssembly.instantiate("assets/qrtool.wx.wasm.br", info).then((result) => {
|
||||
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then((result) => {
|
||||
console.log("result:", result);
|
||||
var inst = result['instance'];
|
||||
receiveInstance(inst);
|
||||
|
||||
@ -21,5 +21,5 @@
|
||||
"lazyCodeLoading": "requiredComponents",
|
||||
"sitemapLocation": "sitemap.json",
|
||||
"useExtendedLib": {},
|
||||
"workers": "worker"
|
||||
"workers": "pages/emblemscanner/worker"
|
||||
}
|
||||
Binary file not shown.
@ -68,8 +68,6 @@ Page({
|
||||
should_check_auto_torch: true,
|
||||
done_checking_auto_torch: false,
|
||||
camera_sensitivity: 1,
|
||||
frame_uploaded: 0,
|
||||
frame_upload_time_cost: 0,
|
||||
qrarc_class: 'sm',
|
||||
qrmarkers_class: 'hidden',
|
||||
frame_upload_interval_ms: 2000,
|
||||
@ -98,7 +96,8 @@ Page({
|
||||
// State machine: loading_rules -> loading_qrtool -> init_camera -> scanning -> verifying -> result
|
||||
app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'scanning', 'webview_scanning', 'verifying', 'result'
|
||||
scan_mode: 'unknown', // 'camera', 'webview'
|
||||
no_web_view: false // Override web-view rule, force native camera
|
||||
no_web_view: false, // Override web-view rule, force native camera
|
||||
worker_processing: false // Track if worker is currently processing a frame
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@ -180,15 +179,25 @@ Page({
|
||||
*/
|
||||
setupWorker() {
|
||||
// Create worker with local worker file
|
||||
this.worker = wx.createWorker('/pages/emblemscanner/worker/index.js');
|
||||
this.worker = wx.createWorker('/pages/emblemscanner/worker/index.js', {
|
||||
useExperimentalWorker: true,
|
||||
});
|
||||
|
||||
this.worker.onMessage((msg) => {
|
||||
console.log('Worker message:', msg.type);
|
||||
|
||||
|
||||
if (msg.type === "result") {
|
||||
// Clear processing flag when we get a result
|
||||
this.setData({ worker_processing: false });
|
||||
|
||||
const result = msg.res;
|
||||
const processingTime = msg.processing_time;
|
||||
|
||||
// Add WASM response to debug messages
|
||||
if (this.data.enable_debug) {
|
||||
this.addDebugMessage(`WASM response: ${JSON.stringify(result)}`);
|
||||
}
|
||||
|
||||
// Update statistics
|
||||
const newFramesProcessed = this.data.frames_processed + 1;
|
||||
const newTotalTime = this.data.total_processing_time + processingTime;
|
||||
@ -198,13 +207,12 @@ Page({
|
||||
frames_processed: newFramesProcessed,
|
||||
total_processing_time: newTotalTime,
|
||||
avg_processing_time_ms: newAvgTime,
|
||||
last_frame_time_ms: Math.round(processingTime),
|
||||
debug_last_result: result
|
||||
last_frame_time_ms: Math.round(processingTime)
|
||||
});
|
||||
|
||||
if (result) {
|
||||
// For worker, we need to trigger image collection when we find a good QR
|
||||
if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
|
||||
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
|
||||
this.addDebugMessage(`Worker QR detected: ${result.qrcode}`);
|
||||
|
||||
// Trigger zoom-in if function is set up
|
||||
@ -225,14 +233,18 @@ Page({
|
||||
const result = msg.res;
|
||||
const imageData = msg.image_data;
|
||||
if (imageData) {
|
||||
// Worker now sends data URL directly, no need to convert
|
||||
this.image_data_urls.push(imageData.data);
|
||||
|
||||
console.log(`worker submitted image data: ${imageData.data}`);
|
||||
const uca = new Uint8ClampedArray(imageData.data);
|
||||
console.log(`first 4 bytes of image data in message: ${uca[0]}, ${uca[1]}, ${uca[2]}, ${uca[3]}`);
|
||||
const dataUrl = data_url_from_frame(imageData.width, imageData.height, uca);
|
||||
this.updateDebugFrameUrls(dataUrl);
|
||||
this.image_data_urls.push(dataUrl);
|
||||
|
||||
// Update ok frames counter
|
||||
this.setData({
|
||||
ok_frames: this.image_data_urls.length
|
||||
});
|
||||
|
||||
|
||||
if (this.image_data_urls.length >= 3) {
|
||||
this.addDebugMessage('3 good images collected via worker, starting verification');
|
||||
this.startVerifying();
|
||||
@ -446,9 +458,23 @@ Page({
|
||||
this.listener = this.camera_context.onCameraFrame((frame) => {
|
||||
this.onCameraFrame(frame);
|
||||
});
|
||||
|
||||
// Start the listener
|
||||
this.listener.start();
|
||||
|
||||
// Start the listener with worker if using worker mode
|
||||
if (this.data.use_worker) {
|
||||
if (!this.worker) {
|
||||
this.addDebugMessage('Worker not found, cannot start listener');
|
||||
this.setData({
|
||||
show_modal: 'verifyfailed',
|
||||
hint_text: '工作线程初始化失败'
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.listener.start({
|
||||
worker: this.worker
|
||||
});
|
||||
} else {
|
||||
this.listener.start();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -473,24 +499,34 @@ Page({
|
||||
|
||||
// Use worker for iPhone, direct processing for other devices
|
||||
if (this.data.use_worker && this.worker) {
|
||||
// Skip if worker is already processing a frame
|
||||
if (this.data.worker_processing) {
|
||||
this.setData({
|
||||
frames_skipped: this.data.frames_skipped + 1
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Worker processing (iPhone)
|
||||
this.lastWorkerFrame = frame; // Store for handleQRResult
|
||||
|
||||
|
||||
// Copy frame data to avoid TOCTOU race condition (like camera.js)
|
||||
var uca1 = new Uint8ClampedArray(frame.data);
|
||||
var uca = new Uint8ClampedArray(uca1);
|
||||
|
||||
|
||||
// Generate debug frame data URL if debug is enabled
|
||||
if (this.data.enable_debug) {
|
||||
const frameDataUrl = data_url_from_frame(frame.width, frame.height, uca);
|
||||
this.updateDebugFrameUrls(frameDataUrl);
|
||||
}
|
||||
|
||||
|
||||
// Set processing flag before sending message
|
||||
this.setData({ worker_processing: true });
|
||||
|
||||
this.worker.postMessage({
|
||||
type: 'frame',
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
data: uca, // Pass actual frame data
|
||||
camera_sensitivity: this.data.camera_sensitivity
|
||||
});
|
||||
} else {
|
||||
@ -584,7 +620,7 @@ Page({
|
||||
// Check if we have a valid QR code that's ready for upload (like camera.js)
|
||||
// don't require ok as we only care about the view has a valid qrcode in it
|
||||
// zooming in so that it's more likely to be clear enough for upload
|
||||
if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
|
||||
if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
|
||||
this.addDebugMessage(`QR detected: ${result.qrcode} ok: ${result.ok}: err ${result.err}`);
|
||||
|
||||
// Trigger zoom-in if function is set up
|
||||
|
||||
@ -70,8 +70,7 @@ function process_frame(width, height, image_data, camera_sensitivity, enable_deb
|
||||
qrcode: result.qrcode || '',
|
||||
angle: result.angle || 0,
|
||||
ok: result.ok || false,
|
||||
err: result.err || '',
|
||||
valid_pattern: is_emblem_qr_pattern(result.qrcode || '')
|
||||
err: result.err || ''
|
||||
};
|
||||
|
||||
if (enable_debug && debug_data_url) {
|
||||
@ -137,7 +136,7 @@ function make_hint_text(result) {
|
||||
}
|
||||
|
||||
if (result.qrcode && result.qrcode.length > 0) {
|
||||
if (!result.valid_pattern) {
|
||||
if (!is_emblem_qr_pattern(result.qrcode)) {
|
||||
return "无效编码";
|
||||
}
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ var performance = {
|
||||
};
|
||||
Module["instantiateWasm"] = (info, receiveInstance) => {
|
||||
console.log("loading wasm...", info);
|
||||
WebAssembly.instantiate("pages/emblemscanner/assets/qrtool.wx.wasm.br", info).then(result => {
|
||||
WebAssembly.instantiate("pages/emblemscanner/qrtool.wx.wasm.br", info).then(result => {
|
||||
console.log("result:", result);
|
||||
var inst = result["instance"];
|
||||
receiveInstance(inst);
|
||||
@ -4252,7 +4252,7 @@ var wasmImports = {
|
||||
/** @export */t: invoke_dii,
|
||||
/** @export */P: invoke_diii,
|
||||
/** @export */va: invoke_diiii,
|
||||
/** @export */n: invoke_fi,
|
||||
/** @export */m: invoke_fi,
|
||||
/** @export */G: invoke_fii,
|
||||
/** @export */Ca: invoke_fiii,
|
||||
/** @export */Ob: invoke_fiiii,
|
||||
@ -4271,7 +4271,7 @@ var wasmImports = {
|
||||
/** @export */I: invoke_iiiidii,
|
||||
/** @export */da: invoke_iiiidiii,
|
||||
/** @export */Mb: invoke_iiiiff,
|
||||
/** @export */m: invoke_iiiii,
|
||||
/** @export */n: invoke_iiiii,
|
||||
/** @export */ua: invoke_iiiiid,
|
||||
/** @export */v: invoke_iiiiii,
|
||||
/** @export */Xa: invoke_iiiiiiffii,
|
||||
@ -4523,6 +4523,16 @@ function invoke_viiiiiii(index, a1, a2, a3, a4, a5, a6, a7) {
|
||||
_setThrew(1, 0);
|
||||
}
|
||||
}
|
||||
function invoke_viiiiii(index, a1, a2, a3, a4, a5, a6) {
|
||||
var sp = stackSave();
|
||||
try {
|
||||
getWasmTableEntry(index)(a1, a2, a3, a4, a5, a6);
|
||||
} catch (e) {
|
||||
stackRestore(sp);
|
||||
if (e !== e + 0) throw e;
|
||||
_setThrew(1, 0);
|
||||
}
|
||||
}
|
||||
function invoke_iifii(index, a1, a2, a3, a4) {
|
||||
var sp = stackSave();
|
||||
try {
|
||||
@ -4613,16 +4623,6 @@ function invoke_iid(index, a1, a2) {
|
||||
_setThrew(1, 0);
|
||||
}
|
||||
}
|
||||
function invoke_viiiiii(index, a1, a2, a3, a4, a5, a6) {
|
||||
var sp = stackSave();
|
||||
try {
|
||||
getWasmTableEntry(index)(a1, a2, a3, a4, a5, a6);
|
||||
} catch (e) {
|
||||
stackRestore(sp);
|
||||
if (e !== e + 0) throw e;
|
||||
_setThrew(1, 0);
|
||||
}
|
||||
}
|
||||
function invoke_viiiiiiiii(index, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
|
||||
var sp = stackSave();
|
||||
try {
|
||||
|
||||
BIN
scanner/pages/emblemscanner/qrtool.wx.wasm.br
Executable file
BIN
scanner/pages/emblemscanner/qrtool.wx.wasm.br
Executable file
Binary file not shown.
@ -1,34 +1,8 @@
|
||||
console.log("hello from emblemscanner worker");
|
||||
|
||||
let qrtool = require('../qrtool.wx.js');
|
||||
let qrtool = require('./qrtool.wx.js');
|
||||
|
||||
// Create offscreen canvas for image generation (self-contained)
|
||||
let offscreenCanvas = null;
|
||||
|
||||
function getOffscreenCanvas() {
|
||||
if (!offscreenCanvas) {
|
||||
offscreenCanvas = wx.createOffscreenCanvas({
|
||||
type: '2d',
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
return offscreenCanvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw frame data to data URL for image visualization
|
||||
*/
|
||||
function data_url_from_frame(width, height, image_data) {
|
||||
const canvas = getOffscreenCanvas();
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
var ctx = canvas.getContext('2d');
|
||||
var imgd = ctx.createImageData(width, height);
|
||||
imgd.data.set(image_data);
|
||||
ctx.putImageData(imgd, 0, 0);
|
||||
return canvas.toDataURL("image/jpeg", 1.0);
|
||||
}
|
||||
|
||||
var qrtool_ready = false;
|
||||
|
||||
@ -43,6 +17,34 @@ qrtool.onRuntimeInitialized = () => {
|
||||
var submit_message = null;
|
||||
|
||||
function handle_frame(data, width, height, camera_sensitivity) {
|
||||
const begin = Date.now();
|
||||
console.log(`handling frame ${width}x${height}, data is ${data.byteLength} bytes`);
|
||||
var uca = new Uint8ClampedArray(data);
|
||||
console.log(`first 4 bytes of uca: ${uca[0]}, ${uca[1]}, ${uca[2]}, ${uca[3]}`);
|
||||
var buf = qrtool._malloc(uca.length * uca.BYTES_PER_ELEMENT);
|
||||
qrtool.HEAPU8.set(uca, buf);
|
||||
var r = qrtool.ccall('qrtool_angle', 'string', ['number', 'number', 'number', 'number', 'number'], [buf, width, height, 0, camera_sensitivity]);
|
||||
qrtool._free(buf);
|
||||
console.log(`image ${width}x${height} processed in ${Date.now() - begin}ms, result: ${r}`);
|
||||
var res = JSON.parse(r);
|
||||
worker.postMessage({
|
||||
type: "result",
|
||||
res,
|
||||
processing_time: Date.now() - begin,
|
||||
});
|
||||
if (res.ok) {
|
||||
// Send raw image data back to main thread for data URL conversion
|
||||
// since we have no access to offscreen canvas in WASM worker
|
||||
submit_message = {
|
||||
type: "submit",
|
||||
res,
|
||||
image_data: { data, width, height },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
worker.onMessage((msg) => {
|
||||
console.log("emblemscanner worker got message", msg.type);
|
||||
if (!qrtool_ready) {
|
||||
console.log("qrtool not ready");
|
||||
worker.postMessage({
|
||||
@ -55,41 +57,29 @@ function handle_frame(data, width, height, camera_sensitivity) {
|
||||
});
|
||||
return;
|
||||
}
|
||||
const begin = Date.now();
|
||||
var uca = new Uint8ClampedArray(data);
|
||||
var buf = qrtool._malloc(uca.length * uca.BYTES_PER_ELEMENT);
|
||||
qrtool.HEAPU8.set(uca, buf);
|
||||
var r = qrtool.ccall('qrtool_angle', 'string', ['number', 'number', 'number', 'number', 'number'], [buf, width, height, 0, camera_sensitivity]);
|
||||
qrtool._free(buf);
|
||||
console.log("emblemscanner worker r:", r);
|
||||
var res = JSON.parse(r);
|
||||
worker.postMessage({
|
||||
type: "result",
|
||||
res,
|
||||
processing_time: Date.now() - begin,
|
||||
});
|
||||
if (res.ok) {
|
||||
// Since image_data takes seconds to serialize, send it in a separate
|
||||
// message to the UI can update with the good news
|
||||
const dataUrl = data_url_from_frame(width, height, uca);
|
||||
submit_message = {
|
||||
type: "submit",
|
||||
res,
|
||||
image_data: { data: dataUrl, width, height },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
worker.onMessage((msg) => {
|
||||
console.log("emblemscanner worker got message", msg.type);
|
||||
switch (msg.type) {
|
||||
case "frame":
|
||||
try {
|
||||
// Use frame data from message instead of getCameraFrameData()
|
||||
if (msg.data) {
|
||||
handle_frame(msg.data, msg.width, msg.height, msg.camera_sensitivity);
|
||||
const data = worker.getCameraFrameData();
|
||||
if (data) {
|
||||
handle_frame(data, msg.width, msg.height, msg.camera_sensitivity);
|
||||
} else {
|
||||
worker.postMessage({
|
||||
type: "result",
|
||||
res: {
|
||||
ok: false,
|
||||
err: "no frame data",
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
worker.postMessage({
|
||||
type: "result",
|
||||
res: {
|
||||
ok: false,
|
||||
err: `failed to handle frame: ${e.message}`,
|
||||
},
|
||||
});
|
||||
console.log(e);
|
||||
}
|
||||
break;
|
||||
|
||||
5580
scanner/pages/emblemscanner/worker/qrtool.wx.js
Normal file
5580
scanner/pages/emblemscanner/worker/qrtool.wx.js
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user