emblemscanner: native wasm works

This commit is contained in:
Fam Zheng 2025-09-13 19:52:21 +01:00
parent 431f81faad
commit 7c7c94fa7b
5 changed files with 197 additions and 172 deletions

View File

@ -27,7 +27,6 @@
// Import utility functions from library // Import utility functions from library
const { const {
get_system_info, get_system_info,
get_phone_model,
get_camera_rule, get_camera_rule,
make_query, make_query,
fetch_real_ip, fetch_real_ip,
@ -40,7 +39,6 @@ const {
load_qrtool, load_qrtool,
is_qrtool_ready, is_qrtool_ready,
process_frame, process_frame,
process_frame_with_debug,
make_hint_text make_hint_text
} = require('./qrprocessor.js'); } = require('./qrprocessor.js');
@ -72,30 +70,28 @@ Page({
tenant_id: '', // Tenant identifier tenant_id: '', // Tenant identifier
debug_msgs: [], debug_msgs: [],
debug_image_data_url: '', debug_image_data_url: '',
debug_last_result: null,
qrtool_ready: false, qrtool_ready: false,
frame_processing_started: false,
// Frame processing statistics // Frame processing statistics
frames_processed: 0, frames_processed: 0,
frames_skipped: 0, frames_skipped: 0,
total_processing_time: 0, total_processing_time: 0,
avg_processing_time: 0, avg_processing_time_ms: 0,
last_frame_time: 0, last_frame_time_ms: 0,
rule_zoom: -1, rule_zoom: -1,
camera_rule: null, camera_rule: null,
use_web_view: false, use_web_view: false,
emblem_camera_url: null, emblem_camera_url: null,
// State machine: loading -> scanning -> verifying -> result // State machine: initializing -> loading -> scanning -> verifying -> result
app_state: 'loading', // 'loading', 'scanning_camera', 'scanning_webview', 'verifying', 'result' app_state: 'initializing', // 'initializing', 'loading', 'scanning_camera', 'scanning_webview', 'verifying', 'result'
scan_mode: 'unknown', // 'camera', 'webview' 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
}, },
/**
* Lifecycle function--Called when page load
*/
onLoad(options) { onLoad(options) {
console.log('QR Scanner module loaded', options); console.log('QR Scanner module loaded', options);
// Store query parameters
const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true'; const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true';
this.setData({ this.setData({
@ -103,54 +99,38 @@ Page({
no_web_view: no_web_view no_web_view: no_web_view
}); });
// Initialize image data storage
this.image_data_urls = []; this.image_data_urls = [];
// Handle debug mode locally
options = options || {}; options = options || {};
const enable_debug = options.debug || options.scene == 'debug' || false; const enable_debug = options.debug || options.scene == 'debug' || false;
// Get system information
this.initializeSystem(enable_debug); this.initializeSystem(enable_debug);
// Initialize QRTool WASM
this.initializeQRTool(); this.initializeQRTool();
// Fetch IP address and tenant info, then load camera rules
this.fetchRealIP(); this.fetchRealIP();
this.fetchTenantID(); this.fetchTenantID();
this.loadCameraRules(); this.loadCameraRules();
}, },
/**
* Fetch real IP address
*/
fetchRealIP() { fetchRealIP() {
fetch_real_ip((err, ip) => { fetch_real_ip((err, ip) => {
this.setData({ real_ip: ip }); this.setData({ real_ip: ip });
}); });
}, },
/**
* Fetch tenant ID (can be customized based on app logic)
*/
fetchTenantID() { fetchTenantID() {
const tenant_id = get_tenant_id(); const tenant_id = get_tenant_id();
this.setData({ tenant_id }); this.setData({ tenant_id });
}, },
/**
* Initialize QRTool WASM module
*/
initializeQRTool() { initializeQRTool() {
load_qrtool(); load_qrtool();
// Check readiness periodically
const checkReady = () => { const checkReady = () => {
if (is_qrtool_ready()) { if (is_qrtool_ready()) {
this.setData({ qrtool_ready: true }); this.setData({ qrtool_ready: true });
this.addDebugMessage('QRTool WASM loaded and ready'); this.addDebugMessage('QRTool WASM loaded and ready');
this.startFrameProcessing(); this.transitionToState('loading');
this.startFrameProcessingMaybe();
} else { } else {
setTimeout(checkReady, 100); setTimeout(checkReady, 100);
} }
@ -158,9 +138,6 @@ Page({
checkReady(); checkReady();
}, },
/**
* Initialize system information and device detection
*/
initializeSystem(enable_debug) { initializeSystem(enable_debug) {
const systemInfo = get_system_info(); const systemInfo = get_system_info();
const phone_model = systemInfo.model; const phone_model = systemInfo.model;
@ -175,18 +152,13 @@ Page({
console.log(`Phone model: ${phone_model}, Using native camera mode`); console.log(`Phone model: ${phone_model}, Using native camera mode`);
}, },
/**
* Load camera rules from API
*/
loadCameraRules() { loadCameraRules() {
get_camera_rule(null, (rule) => { get_camera_rule(null, (rule) => {
console.log('Camera rule loaded:', rule); console.log('Camera rule loaded:', rule);
// Check for no_web_view override
const should_use_webview = rule.web_view && !this.data.no_web_view; const should_use_webview = rule.web_view && !this.data.no_web_view;
let emblem_camera_url = null; let emblem_camera_url = null;
// Set up web-view URL if needed
if (should_use_webview) { if (should_use_webview) {
emblem_camera_url = "https://themblem.com/camera-5.1/?" + make_query(rule.zoom, this.data.return_page, this.data.real_ip, this.data.tenant_id); emblem_camera_url = "https://themblem.com/camera-5.1/?" + make_query(rule.zoom, this.data.return_page, this.data.real_ip, this.data.tenant_id);
this.addDebugMessage(`Using web-view camera: ${emblem_camera_url}`); this.addDebugMessage(`Using web-view camera: ${emblem_camera_url}`);
@ -203,10 +175,8 @@ Page({
emblem_camera_url: emblem_camera_url emblem_camera_url: emblem_camera_url
}); });
// Add rule info to debug messages
this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`); this.addDebugMessage(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`);
// Transition to appropriate scanning state
if (should_use_webview) { if (should_use_webview) {
this.startWebviewScanning(); this.startWebviewScanning();
} else { } else {
@ -215,19 +185,13 @@ Page({
}); });
}, },
/**
* Camera ready callback
*/
onCameraReady(e) { onCameraReady(e) {
console.log('Camera ready', e); console.log('Camera ready', e);
this.camera_context = wx.createCameraContext(); this.camera_context = wx.createCameraContext();
this.addDebugMessage('Camera initialized for WASM processing'); this.addDebugMessage('Camera initialized for WASM processing');
// State transition is handled in loadCameraRules this.startFrameProcessingMaybe();
}, },
/**
* Camera error callback
*/
onCameraError(e) { onCameraError(e) {
console.error('Camera error', e); console.error('Camera error', e);
this.addDebugMessage(`Camera error: ${JSON.stringify(e.detail)}`); this.addDebugMessage(`Camera error: ${JSON.stringify(e.detail)}`);
@ -237,11 +201,6 @@ Page({
}); });
}, },
/**
* Toggle torch/flash
*/
toggle_torch() { toggle_torch() {
const newFlash = this.data.camera_flash === 'torch' ? 'off' : 'torch'; const newFlash = this.data.camera_flash === 'torch' ? 'off' : 'torch';
this.setData({ this.setData({
@ -250,9 +209,6 @@ Page({
console.log('Torch toggled to:', newFlash); console.log('Torch toggled to:', newFlash);
}, },
/**
* Show scan guide
*/
show_scanguide() { show_scanguide() {
this.setData({ this.setData({
show_modal: 'scanguide', show_modal: 'scanguide',
@ -260,34 +216,22 @@ Page({
}); });
}, },
/**
* Show service modal
*/
show_service() { show_service() {
this.setData({ this.setData({
show_modal: 'service' show_modal: 'service'
}); });
}, },
/**
* Close modal and restart camera (legacy method name for WXML compatibility)
*/
restart_camera() { restart_camera() {
this.restartScanning(); this.restartScanning();
}, },
/**
* Close any modal
*/
close_modal() { close_modal() {
this.setData({ this.setData({
show_modal: '' show_modal: ''
}); });
}, },
/**
* Debug tap handler
*/
debug_tap() { debug_tap() {
const count = (this.debug_tap_count || 0) + 1; const count = (this.debug_tap_count || 0) + 1;
this.debug_tap_count = count; this.debug_tap_count = count;
@ -299,7 +243,6 @@ Page({
this.debug_tap_count = 0; this.debug_tap_count = 0;
} }
// Clear count after 3 seconds
setTimeout(() => { setTimeout(() => {
this.debug_tap_count = 0; this.debug_tap_count = 0;
}, 3000); }, 3000);
@ -331,11 +274,19 @@ Page({
}, },
/** /**
* Start frame processing - sets up camera frame listener * Start frame processing if both QRTool and camera are ready
*/ */
startFrameProcessing() { startFrameProcessingMaybe() {
// Already started, nothing to do
if (this.data.frame_processing_started) {
return;
}
// Check if both are ready
if (!this.data.qrtool_ready || !this.camera_context) { if (!this.data.qrtool_ready || !this.camera_context) {
this.addDebugMessage('QRTool or camera not ready, skipping frame processing'); const qrStatus = this.data.qrtool_ready ? 'ready' : 'not ready';
const cameraStatus = this.camera_context ? 'ready' : 'not ready';
this.addDebugMessage(`Cannot start frame processing - QRTool: ${qrStatus}, Camera: ${cameraStatus}`);
return; return;
} }
@ -349,6 +300,9 @@ Page({
// Start the listener // Start the listener
this.listener.start(); this.listener.start();
// Mark as started
this.setData({ frame_processing_started: true });
}, },
/** /**
@ -371,28 +325,27 @@ Page({
this.lastFrameTime = now; this.lastFrameTime = now;
// Start timing frame processing // Start timing frame processing
const processStart = performance.now(); const processStart = Date.now();
// Process frame data directly // Process frame data directly
try { try {
const result = this.data.enable_debug ? const result = process_frame(frame.width, frame.height, frame.data, this.data.camera_sensitivity, this.data.enable_debug);
process_frame_with_debug(frame.width, frame.height, frame.data, this.data.camera_sensitivity) :
process_frame(frame.width, frame.height, frame.data, this.data.camera_sensitivity);
// Calculate processing time // Calculate processing time
const processEnd = performance.now(); const processEnd = Date.now();
const processingTime = processEnd - processStart; const processingTime = processEnd - processStart;
// Update statistics // Update statistics
const newFramesProcessed = this.data.frames_processed + 1; const newFramesProcessed = this.data.frames_processed + 1;
const newTotalTime = this.data.total_processing_time + processingTime; const newTotalTime = this.data.total_processing_time + processingTime;
const newAvgTime = newTotalTime / newFramesProcessed; const newAvgTime = Math.round(newTotalTime / newFramesProcessed);
this.setData({ this.setData({
frames_processed: newFramesProcessed, frames_processed: newFramesProcessed,
total_processing_time: newTotalTime, total_processing_time: newTotalTime,
avg_processing_time: newAvgTime, avg_processing_time_ms: newAvgTime,
last_frame_time: processingTime last_frame_time_ms: Math.round(processingTime),
debug_last_result: result
}); });
if (result) { if (result) {
@ -401,18 +354,19 @@ Page({
} catch (error) { } catch (error) {
this.addDebugMessage(`Frame processing error: ${error.message}`); this.addDebugMessage(`Frame processing error: ${error.message}`);
// Still count failed processing attempts // Still count failed processing attempts
const processEnd = performance.now(); const processEnd = Date.now();
const processingTime = processEnd - processStart; const processingTime = processEnd - processStart;
const newFramesProcessed = this.data.frames_processed + 1; const newFramesProcessed = this.data.frames_processed + 1;
const newTotalTime = this.data.total_processing_time + processingTime; const newTotalTime = this.data.total_processing_time + processingTime;
const newAvgTime = newTotalTime / newFramesProcessed; const newAvgTime = Math.round(newTotalTime / newFramesProcessed);
this.setData({ this.setData({
frames_processed: newFramesProcessed, frames_processed: newFramesProcessed,
total_processing_time: newTotalTime, total_processing_time: newTotalTime,
avg_processing_time: newAvgTime, avg_processing_time_ms: newAvgTime,
last_frame_time: processingTime last_frame_time_ms: Math.round(processingTime),
debug_last_result: null
}); });
} }
}, },
@ -507,24 +461,31 @@ Page({
* State: Any -> Loading (restart) * State: Any -> Loading (restart)
*/ */
restartScanning() { restartScanning() {
this.transitionToState('loading'); // Go to initializing if QRTool isn't ready, otherwise loading
const newState = this.data.qrtool_ready ? 'loading' : 'initializing';
this.transitionToState(newState);
const hintText = this.data.qrtool_ready ? '初始化相机...' : '初始化QR工具...';
this.setData({ this.setData({
show_modal: '', show_modal: '',
hint_text: '初始化相机...', hint_text: hintText,
busy: true, busy: true,
// Reset frame processing statistics // Reset frame processing statistics
frames_processed: 0, frames_processed: 0,
frames_skipped: 0, frames_skipped: 0,
total_processing_time: 0, total_processing_time: 0,
avg_processing_time: 0, avg_processing_time_ms: 0,
last_frame_time: 0 last_frame_time_ms: 0,
frame_processing_started: false
}); });
// Reset frame timing // Reset frame timing
this.lastFrameTime = 0; this.lastFrameTime = 0;
// Reload camera rules to restart the flow // Reload camera rules to restart the flow (only if QRTool is ready)
this.loadCameraRules(); if (this.data.qrtool_ready) {
this.loadCameraRules();
}
}, },
/** /**
@ -537,7 +498,7 @@ Page({
const debugMsg = `${timestamp}: ${message}`; const debugMsg = `${timestamp}: ${message}`;
this.setData({ this.setData({
debug_msgs: [...this.data.debug_msgs, debugMsg].slice(-10) // Keep last 10 messages debug_msgs: [debugMsg, ...this.data.debug_msgs].slice(0, 5) // Keep first 5 messages (newest on top)
}); });
}, },

View File

@ -1,4 +1,10 @@
<view class="wrapper"> <view class="wrapper">
<!-- STATE: INITIALIZING -->
<view wx:if="{{ app_state == 'initializing' }}" class="loading-spinner">
<view class="spinner"></view>
<text>初始化QR工具...</text>
</view>
<!-- STATE: LOADING --> <!-- STATE: LOADING -->
<view wx:if="{{ app_state == 'loading' }}" class="loading-spinner"> <view wx:if="{{ app_state == 'loading' }}" class="loading-spinner">
<view class="spinner"></view> <view class="spinner"></view>
@ -31,7 +37,7 @@
<camera class="camera" <camera class="camera"
flash="{{ camera_flash }}" flash="{{ camera_flash }}"
frame-size="medium" frame-size="medium"
bindready="onCameraReady" bindinitdone="onCameraReady"
binderror="onCameraError"> binderror="onCameraError">
</camera> </camera>
</block> </block>
@ -46,58 +52,95 @@
<!-- Debug overlay (available in all states) --> <!-- Debug overlay (available in all states) -->
<view class="debug" wx:if="{{ enable_debug }}"> <view class="debug" wx:if="{{ enable_debug }}">
<view><image src="{{ debug_image_data_url }}"></image></view> <view class="debug-top-row">
<view class="debug-messages"> <view class="debug-info-panel">
<text wx:for="{{ debug_msgs }}" class="debug-msg">{{ item }}</text> <view class="debug-items">
</view> <!-- Application State -->
<view class="debug-items">
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">state:</text> <text class="debug-label">state:</text>
<text class="debug-value">{{ app_state }}</text> <text class="debug-value">{{ app_state }}</text>
</view> </view>
<!-- System Info -->
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">model:</text> <text class="debug-label">model:</text>
<text class="debug-value">{{ phone_model }}</text> <text class="debug-value">{{ phone_model }}</text>
</view> </view>
<view class="debug-item">
<text class="debug-label">zoom:</text> <!-- Camera Configuration -->
<text class="debug-value">{{ zoom }}/{{ max_zoom }}</text>
</view>
<view class="debug-item debug-flag-box" wx:if="{{ no_web_view }}">
<text class="debug-flag">no_web_view</text>
</view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">rule:</text> <text class="debug-label">rule:</text>
<text class="debug-value">{{ camera_rule.model || 'default' }}</text> <text class="debug-value">{{ camera_rule.model || 'default' }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">web_view:</text> <text class="debug-label">zoom:</text>
<text class="debug-value">{{ use_web_view }}</text> <text class="debug-value-number">{{ zoom }}/{{ max_zoom }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">sensitivity:</text> <text class="debug-label">sensitivity:</text>
<text class="debug-value">{{ camera_sensitivity }}</text> <text class="debug-value-number">{{ camera_sensitivity }}</text>
</view> </view>
<view class="debug-item">
<text class="debug-label">web_view:</text>
<text class="debug-value">{{ use_web_view }}</text>
</view>
<view class="debug-item debug-flag-box" wx:if="{{ no_web_view }}">
<text class="debug-flag">no_web_view</text>
</view>
<!-- Processing Status -->
<view class="debug-item">
<text class="debug-label">qrtool:</text>
<text class="debug-value">{{ qrtool_ready ? 'ready' : 'loading' }}</text>
</view>
<!-- Frame Statistics -->
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">frames:</text> <text class="debug-label">frames:</text>
<text class="debug-value">{{ frames_processed }}/{{ frames_processed + frames_skipped }}</text> <text class="debug-value-number">{{ frames_processed }}</text>
<text class="debug-value">/</text>
<text class="debug-value-number">{{ frames_processed + frames_skipped }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">skipped:</text> <text class="debug-label">skipped:</text>
<text class="debug-value">{{ frames_skipped }}</text> <text class="debug-value-number">{{ frames_skipped }}</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">avg:</text> <text class="debug-label">avg:</text>
<text class="debug-value">{{ avg_processing_time.toFixed(2) }}ms</text> <text class="debug-value-number">{{ avg_processing_time_ms }}ms</text>
</view> </view>
<view class="debug-item"> <view class="debug-item">
<text class="debug-label">last:</text> <text class="debug-label">last:</text>
<text class="debug-value">{{ last_frame_time.toFixed(2) }}ms</text> <text class="debug-value-number">{{ last_frame_time_ms }}ms</text>
</view> </view>
<!-- QR Detection Results -->
<view class="debug-item" wx:if="{{ debug_last_result }}">
<text class="debug-label">qr:</text>
<text class="debug-value">{{ debug_last_result.qrcode || 'none' }}</text>
</view>
<view class="debug-item" wx:if="{{ debug_last_result }}">
<text class="debug-label">ok:</text>
<text class="debug-value">{{ debug_last_result.ok ? 'yes' : 'no' }}</text>
</view>
<view class="debug-item" wx:if="{{ debug_last_result && debug_last_result.err }}">
<text class="debug-label">err:</text>
<text class="debug-value">{{ debug_last_result.err }}</text>
</view>
<!-- Legacy Result -->
<view class="debug-item" wx:if="{{ result }}"> <view class="debug-item" wx:if="{{ result }}">
<text class="debug-label">result:</text> <text class="debug-label">result:</text>
<text class="debug-value">{{ result }}</text> <text class="debug-value">{{ result }}</text>
</view> </view>
</view>
</view>
<view class="debug-image-box">
<image src="{{ debug_image_data_url }}"></image>
</view>
</view>
<view class="debug-messages">
<text wx:for="{{ debug_msgs }}" class="debug-msg">{{ item }}</text>
</view> </view>
</view> </view>

View File

@ -198,23 +198,37 @@ view.debug {
background-color: rgba(100, 100, 100, 0.5); background-color: rgba(100, 100, 100, 0.5);
z-index: 1000; z-index: 1000;
font-size: 10px; font-size: 10px;
overflow-y: auto;
opacity: 0.75; opacity: 0.75;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
view.debug image { .debug-top-row {
right: 10px; display: flex;
top: 10px; gap: 4px;
margin-bottom: 4px;
}
.debug-image-box {
flex-shrink: 0;
}
.debug-image-box image {
width: 64px; width: 64px;
height: 64px; height: 64px;
border: 1px solid rgba(239, 72, 35, 0.8); border: 1px solid rgba(239, 72, 35, 0.8);
} }
.debug-info-panel {
flex: 1;
display: flex;
flex-direction: column;
}
/* Debug messages section */ /* Debug messages section */
.debug-messages { .debug-messages {
margin-bottom: 4px; margin-top: 4px;
flex-shrink: 0;
} }
.debug-msg { .debug-msg {
@ -222,6 +236,8 @@ view.debug image {
margin: 1px 0; margin: 1px 0;
line-height: 1.2; line-height: 1.2;
color: #ffffff; color: #ffffff;
font-family: monospace;
font-size: 9px;
} }
/* Debug items container */ /* Debug items container */
@ -257,6 +273,13 @@ view.debug image {
word-break: break-word; word-break: break-word;
} }
.debug-value-number {
color: #99ff99;
font-family: monospace;
min-width: 32px;
text-align: right;
}
/* Special styling for no_web_view flag box */ /* Special styling for no_web_view flag box */
.debug-item.debug-flag-box { .debug-item.debug-flag-box {
border: 1px solid rgba(255, 0, 0, 0.6); border: 1px solid rgba(255, 0, 0, 0.6);

View File

@ -26,91 +26,90 @@ function is_qrtool_ready() {
/** /**
* Process camera frame for QR code detection * Process camera frame for QR code detection
*/ */
function process_frame(width, height, image_data, camera_sensitivity) { function process_frame(width, height, image_data, camera_sensitivity, enable_debug = false) {
if (!qrtool_ready) { if (!qrtool_ready) {
console.log("qrtool not ready"); console.log("qrtool not ready");
return null; return null;
} }
console.log('process_frame called:', {
width,
height,
data_type: typeof image_data,
data_constructor: image_data ? image_data.constructor.name : 'undefined',
data_length: image_data ? image_data.length : 'undefined',
data_byteLength: image_data ? image_data.byteLength : 'undefined',
bytes_per_element: image_data ? image_data.BYTES_PER_ELEMENT : 'undefined',
camera_sensitivity,
enable_debug
});
try { try {
// Allocate buffer for image data // Copy frame data to avoid TOCTOU
var buf = qrtool._malloc(image_data.length * image_data.BYTES_PER_ELEMENT); var uca1 = new Uint8ClampedArray(image_data);
qrtool.HEAPU8.set(image_data, buf); var uca = new Uint8ClampedArray(uca1);
console.log('Frame data copied:', {
uca_length: uca.length,
uca_bytes_per_element: uca.BYTES_PER_ELEMENT
});
var buf = qrtool._malloc(uca.length * uca.BYTES_PER_ELEMENT);
qrtool.HEAPU8.set(uca, buf);
console.log('Buffer allocated:', {
buffer_size: uca.length * uca.BYTES_PER_ELEMENT
});
var dot_area_buf = 0;
var debug_data_url = null;
if (enable_debug) {
const dot_area_size = 32;
const da_len = dot_area_size * dot_area_size * uca.BYTES_PER_ELEMENT * 4;
dot_area_buf = qrtool._malloc(da_len);
}
// Process QR code detection with angle
var result_str = qrtool.ccall('qrtool_angle', 'string', var result_str = qrtool.ccall('qrtool_angle', 'string',
['number', 'number', 'number', 'number', 'number'], ['number', 'number', 'number', 'number', 'number'],
[buf, width, height, 0, camera_sensitivity || 1.0] [buf, width, height, dot_area_buf, camera_sensitivity || 1]
); );
// Clean up buffer console.log('qrtool_angle result:', result_str);
if (enable_debug && dot_area_buf) {
const dot_area_size = 32;
const da_len = dot_area_size * dot_area_size * uca.BYTES_PER_ELEMENT * 4;
var debug_view = qrtool.HEAPU8.subarray(dot_area_buf, dot_area_buf + da_len);
debug_data_url = data_url_from_frame(dot_area_size, dot_area_size, debug_view);
}
qrtool._free(buf); qrtool._free(buf);
if (dot_area_buf) {
qrtool._free(dot_area_buf);
}
// Parse result // Parse result
var result = JSON.parse(result_str); var result = JSON.parse(result_str);
return { var returnValue = {
qrcode: result.qrcode || '', qrcode: result.qrcode || '',
angle: result.angle || 0, angle: result.angle || 0,
ok: result.ok || false, ok: result.ok || false,
err: result.err || '', err: result.err || '',
valid_pattern: is_emblem_qr_pattern(result.qrcode || '') valid_pattern: is_emblem_qr_pattern(result.qrcode || '')
}; };
if (enable_debug && debug_data_url) {
returnValue.debug_data_url = debug_data_url;
}
return returnValue;
} catch (error) { } catch (error) {
console.error('QR processing error:', error); console.error('QR processing error:', error);
return null; return null;
} }
} }
/**
* Process frame with dot area extraction (for debug visualization)
*/
function process_frame_with_debug(width, height, image_data, camera_sensitivity) {
if (!qrtool_ready) {
console.log("qrtool not ready");
return null;
}
try {
// Allocate buffer for image data
var buf = qrtool._malloc(image_data.length * image_data.BYTES_PER_ELEMENT);
qrtool.HEAPU8.set(image_data, buf);
// Allocate buffer for debug dot area
const dot_area_size = 32;
const da_len = dot_area_size * dot_area_size * image_data.BYTES_PER_ELEMENT * 4;
var dot_area_buf = qrtool._malloc(da_len);
// Process QR code detection with debug output
var result_str = qrtool.ccall('qrtool_angle', 'string',
['number', 'number', 'number', 'number', 'number'],
[buf, width, height, dot_area_buf, camera_sensitivity || 1.0]
);
// Extract debug image
var debug_view = qrtool.HEAPU8.subarray(dot_area_buf, dot_area_buf + da_len);
var debug_data_url = data_url_from_frame(dot_area_size, dot_area_size, debug_view);
// Clean up buffers
qrtool._free(buf);
qrtool._free(dot_area_buf);
// Parse result
var result = JSON.parse(result_str);
return {
qrcode: result.qrcode || '',
angle: result.angle || 0,
ok: result.ok || false,
err: result.err || '',
valid_pattern: is_emblem_qr_pattern(result.qrcode || ''),
debug_data_url: debug_data_url
};
} catch (error) {
console.error('QR processing error:', error);
return null;
}
}
/** /**
* Create offscreen canvas for debug image generation * Create offscreen canvas for debug image generation
@ -179,7 +178,6 @@ module.exports = {
load_qrtool, load_qrtool,
is_qrtool_ready, is_qrtool_ready,
process_frame, process_frame,
process_frame_with_debug,
data_url_from_frame, data_url_from_frame,
is_emblem_qr_pattern, is_emblem_qr_pattern,
make_hint_text make_hint_text

View File

@ -34,7 +34,7 @@ var performance = {
}; };
Module["instantiateWasm"] = (info, receiveInstance) => { Module["instantiateWasm"] = (info, receiveInstance) => {
console.log("loading wasm...", info); console.log("loading wasm...", info);
WebAssembly.instantiate("assets/qrtool.wx.wasm.br", info).then(result => { WebAssembly.instantiate("pages/emblemscanner/assets/qrtool.wx.wasm.br", info).then(result => {
console.log("result:", result); console.log("result:", result);
var inst = result["instance"]; var inst = result["instance"];
receiveInstance(inst); receiveInstance(inst);