diff --git a/scanner/pages/emblemscanner/emblemscanner.js b/scanner/pages/emblemscanner/emblemscanner.js
index aa44ca0..b736e23 100644
--- a/scanner/pages/emblemscanner/emblemscanner.js
+++ b/scanner/pages/emblemscanner/emblemscanner.js
@@ -40,16 +40,15 @@ const {
} = require('./upload.js');
// Import precheck utilities for image processing
-const {
- data_url_from_frame
-} = require('../../precheck.js');
+// Note: We now use our own data_url_from_frame from qrprocessor.js
// Import QR processing module
const {
load_qrtool,
is_qrtool_ready,
process_frame,
- make_hint_text
+ make_hint_text,
+ data_url_from_frame
} = require('./qrprocessor.js');
Page({
@@ -74,17 +73,19 @@ Page({
qrarc_class: 'sm',
qrmarkers_class: 'hidden',
frame_upload_interval_ms: 2000,
- return_page: '', // Page to navigate to after successful scan
+ return_page: '/pages/test_result_page/test_result_page', // Page to navigate to after successful scan
server_url: 'https://themblem.com', // Default server URL
real_ip: '', // User's real IP address
tenant_id: '', // Tenant identifier
debug_msgs: [],
debug_image_data_url: '',
debug_last_result: null,
+ debug_current_frame_url: '', // Current frame being processed
qrtool_ready: false,
// Frame processing statistics
frames_processed: 0,
frames_skipped: 0,
+ ok_frames: 0,
total_processing_time: 0,
avg_processing_time_ms: 0,
last_frame_time_ms: 0,
@@ -93,6 +94,7 @@ Page({
use_web_view: false,
use_worker: false,
emblem_camera_url: null,
+ first_qr_found: false, // Track if first QR has been detected to trigger zoom-in
// 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'
@@ -105,7 +107,7 @@ Page({
const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true';
this.setData({
- return_page: options.return_page || '',
+ return_page: options.return_page || '/pages/test_result_page/test_result_page',
no_web_view: no_web_view
});
@@ -202,17 +204,18 @@ Page({
if (result) {
// For worker, we need to trigger image collection when we find a good QR
- if (result.ok && result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
+ if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`Worker QR detected: ${result.qrcode}`);
// Trigger zoom-in if function is set up
- if (this.on_qr_found && !this.data.qr_found) {
- this.on_qr_found();
- this.setData({ qr_found: true });
+ if (this.on_first_qr_found && !this.data.first_qr_found) {
+ this.on_first_qr_found();
}
- // Request worker to submit image data
- this.worker.postMessage({ type: "ready_to_submit" });
+ if (result.ok) {
+ // Request worker to submit image data
+ this.worker.postMessage({ type: "ready_to_submit" });
+ }
}
this.handleQRResult(result, this.lastWorkerFrame);
@@ -222,14 +225,20 @@ Page({
const result = msg.res;
const imageData = msg.image_data;
if (imageData) {
- const dataUrl = data_url_from_frame(imageData.width, imageData.height, new Uint8ClampedArray(imageData.data));
- this.image_data_urls.push(dataUrl);
+ // Worker now sends data URL directly, no need to convert
+ this.image_data_urls.push(imageData.data);
+
+ // 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();
this.submitImageForVerification(this.image_data_urls, result.qrcode);
this.image_data_urls = []; // Reset for next scan
+ this.setData({ ok_frames: 0 }); // Reset counter
} else {
this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 worker images`);
}
@@ -382,8 +391,9 @@ Page({
this.camera_context.setZoom({ zoom: initial_zoom });
// Set up zoom-in behavior when QR is found
- this.on_qr_found = () => {
- this.addDebugMessage(`QR found, zoom to ${zoom}x`);
+ this.on_first_qr_found = () => {
+ this.setData({ first_qr_found: true });
+ this.addDebugMessage(`First QR found, zoom to ${zoom}x`);
this.camera_context.setZoom({ zoom: zoom });
this.setData({
zoom: zoom,
@@ -465,10 +475,22 @@ Page({
if (this.data.use_worker && this.worker) {
// 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);
+ }
+
this.worker.postMessage({
type: 'frame',
width: frame.width,
height: frame.height,
+ data: uca, // Pass actual frame data
camera_sensitivity: this.data.camera_sensitivity
});
} else {
@@ -484,7 +506,17 @@ Page({
const processStart = Date.now();
try {
- const result = process_frame(frame.width, frame.height, frame.data, this.data.camera_sensitivity, this.data.enable_debug);
+ // 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);
+ }
+
+ const result = process_frame(frame.width, frame.height, uca, this.data.camera_sensitivity, this.data.enable_debug);
// Calculate processing time
const processEnd = Date.now();
@@ -504,7 +536,7 @@ Page({
});
if (result) {
- this.handleQRResult(result, frame);
+ this.handleQRResult(result, frame, uca);
}
} catch (error) {
this.addDebugMessage(`Frame processing error: ${error.message}`);
@@ -529,7 +561,7 @@ Page({
/**
* Handle QR processing result
*/
- handleQRResult(result, frame) {
+ handleQRResult(result, frame, copiedFrameData = null) {
// Update debug info if available
if (result.debug_data_url) {
this.setData({
@@ -543,18 +575,17 @@ 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 (!this.data.qr_found && result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
+ if (result.qrcode && result.valid_pattern && is_emblem_qr_pattern(result.qrcode)) {
this.addDebugMessage(`QR detected and ready: ${result.qrcode}`);
// Trigger zoom-in if function is set up
- if (this.on_qr_found) {
- this.on_qr_found();
+ if (!this.data.first_qr_found && this.on_first_qr_found) {
+ this.on_first_qr_found();
}
- this.onQRCodeDetected(result.qrcode, frame);
- this.setData({
- qr_found: true
- });
+ if (result.ok) {
+ this.onQRCodeDetected(result.qrcode, frame, copiedFrameData); // Pass the copied frame data
+ }
} else {
// Update hint for user guidance
this.setData({ hint_text: hint });
@@ -566,23 +597,38 @@ Page({
},
/**
- * Handle successful QR code detection - collect images from "ok" frames and verify
+ * Handle successful QR code detection - collect images from "ok" frames and verify (non-worker case)
*/
- onQRCodeDetected(qrCode, frameData) {
- // Convert frame data to data URL for upload (only called for "ok" frames)
- if (frameData) {
- const dataUrl = data_url_from_frame(frameData.width, frameData.height, frameData.data);
+ onQRCodeDetected(qrCode, frameData, copiedFrameData = null) {
+ // Only collect images for non-worker case (worker handles its own image collection)
+ if (!this.data.use_worker && frameData && copiedFrameData) {
+ // Convert frame data to data URL for upload (only called for "ok" frames)
+ // Use the copied frame data instead of original frameData.data to avoid corruption
+ const dataUrl = data_url_from_frame(frameData.width, frameData.height, copiedFrameData);
this.image_data_urls.push(dataUrl);
- this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images`);
- }
-
- // Need 3 "ok" frames before verification (like camera.js)
- if (this.image_data_urls.length >= 3) {
- this.addDebugMessage('3 good images collected, starting verification');
- this.startVerifying();
- this.submitImageForVerification(this.image_data_urls, qrCode);
- this.image_data_urls = []; // Reset for next scan
+
+ // Update ok frames counter
+ this.setData({
+ ok_frames: this.image_data_urls.length
+ });
+
+ this.addDebugMessage(`Collected ${this.image_data_urls.length}/3 good images (direct) - using copied data`);
+
+ // Add debug info about the submitted image
+ if (this.data.enable_debug) {
+ this.addDebugMessage(`Submitted image preview: ${dataUrl.substring(0, 50)}...`);
+ }
+
+ // Need 3 "ok" frames before verification (like camera.js)
+ if (this.image_data_urls.length >= 3) {
+ this.addDebugMessage('3 good images collected, starting verification');
+ this.startVerifying();
+ this.submitImageForVerification(this.image_data_urls, qrCode);
+ this.image_data_urls = []; // Reset for next scan
+ this.setData({ ok_frames: 0 }); // Reset counter
+ }
}
+ // For worker case, image collection is handled in setupWorker() message handler
// If less than 3 images, continue scanning to collect more
},
@@ -671,25 +717,20 @@ Page({
goToResult(qrCode, serialCode) {
this.transitionToState('result');
- if (this.data.return_page) {
- // Pass both qr_code and serial_code parameters like camera-5.1 does
- // serialCode comes from server verification response
- const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
-
- wx.redirectTo({
- url: url,
- success: () => {
- this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
- },
- fail: (err) => {
- this.addDebugMessage(`Navigation failed: ${err.errMsg}`);
- this.restartScanning();
- }
- });
- } else {
- this.addDebugMessage('No return page specified');
- this.restartScanning();
- }
+ // Pass both qr_code and serial_code parameters like camera-5.1 does
+ // serialCode comes from server verification response
+ const url = `${this.data.return_page}?qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
+
+ wx.redirectTo({
+ url: url,
+ success: () => {
+ this.addDebugMessage(`Navigated to: ${this.data.return_page}`);
+ },
+ fail: (err) => {
+ this.addDebugMessage(`Navigation failed: ${err.errMsg}`);
+ this.restartScanning();
+ }
+ });
},
/**
@@ -708,9 +749,11 @@ Page({
// Reset frame processing statistics
frames_processed: 0,
frames_skipped: 0,
+ ok_frames: 0,
total_processing_time: 0,
avg_processing_time_ms: 0,
- last_frame_time_ms: 0
+ last_frame_time_ms: 0,
+ first_qr_found: false
});
// Reset frame timing
@@ -736,6 +779,18 @@ Page({
});
},
+ /**
+ * Update debug frame URLs for visualization
+ */
+ updateDebugFrameUrls(frameDataUrl) {
+ if (!this.data.enable_debug) return;
+
+ // Just show the current frame
+ this.setData({
+ debug_current_frame_url: frameDataUrl
+ });
+ },
+
/**
* Log function for debugging
*/
diff --git a/scanner/pages/emblemscanner/emblemscanner.wxml b/scanner/pages/emblemscanner/emblemscanner.wxml
index 06e0c2d..94dc05c 100644
--- a/scanner/pages/emblemscanner/emblemscanner.wxml
+++ b/scanner/pages/emblemscanner/emblemscanner.wxml
@@ -71,6 +71,14 @@
+
+
+ Processed Frame
+
+
+
+ Raw Frame
+
@@ -84,34 +92,18 @@
model:
{{ phone_model }}
-
-
-
- rule:
- {{ camera_rule.model || 'default' }}
-
zoom:
{{ zoom }}/{{ max_zoom }}
sensitivity:
- {{ camera_sensitivity }}
-
-
- web_view:
- {{ use_web_view }}
+ {{ camera_sensitivity }}
no_web_view
-
-
- qrtool:
- {{ qrtool_ready ? 'ready' : 'loading' }}
-
-
frames:
@@ -120,17 +112,14 @@
{{ frames_processed + frames_skipped }}
- skipped:
- {{ frames_skipped }}
+ ok:
+ {{ ok_frames }}
+ /3
avg:
{{ avg_processing_time_ms }}ms
-
- last:
- {{ last_frame_time_ms }}ms
-
@@ -153,9 +142,6 @@
-
-
-
{{ item }}
diff --git a/scanner/pages/emblemscanner/emblemscanner.wxss b/scanner/pages/emblemscanner/emblemscanner.wxss
index 55fdc14..0109170 100644
--- a/scanner/pages/emblemscanner/emblemscanner.wxss
+++ b/scanner/pages/emblemscanner/emblemscanner.wxss
@@ -219,6 +219,25 @@ view.debug {
border: 1px solid rgba(239, 72, 35, 0.8);
}
+.debug-frame-box {
+ flex-shrink: 0;
+}
+
+.debug-frame-box image {
+ width: 64px;
+ height: 64px;
+ border: 1px solid rgba(35, 150, 239, 0.8);
+}
+
+.debug-frame-box .debug-label,
+.debug-image-box .debug-label {
+ font-size: 8px;
+ color: #ffffff;
+ text-align: center;
+ margin-top: 2px;
+ font-weight: bold;
+}
+
.debug-info-panel {
flex: 1;
display: flex;
@@ -291,6 +310,7 @@ view.debug {
font-weight: bold;
}
+
/* Tooltip */
view.tooltip {
position: fixed;
diff --git a/scanner/pages/emblemscanner/qrprocessor.js b/scanner/pages/emblemscanner/qrprocessor.js
index c6d1bb2..836042a 100644
--- a/scanner/pages/emblemscanner/qrprocessor.js
+++ b/scanner/pages/emblemscanner/qrprocessor.js
@@ -89,23 +89,31 @@ function process_frame(width, height, image_data, camera_sensitivity, enable_deb
/**
* Create offscreen canvas for debug image generation
*/
-const offscreenCanvas = wx.createOffscreenCanvas({
- type: '2d',
- width: 100,
- height: 100,
-});
+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 debug visualization
+ * Convert raw frame data to data URL for image visualization
*/
function data_url_from_frame(width, height, image_data) {
- offscreenCanvas.width = width;
- offscreenCanvas.height = height;
- var ctx = offscreenCanvas.getContext('2d');
+ 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 offscreenCanvas.toDataURL("image/jpeg", 1.0);
+ return canvas.toDataURL("image/jpeg", 1.0);
}
/**
diff --git a/scanner/pages/emblemscanner/upload.js b/scanner/pages/emblemscanner/upload.js
index 9dc0b8a..58fe480 100644
--- a/scanner/pages/emblemscanner/upload.js
+++ b/scanner/pages/emblemscanner/upload.js
@@ -1,7 +1,3 @@
-function upload_image_data_urls(image_data_urls, success, fail, log) {
- do_upload(image_data_urls, success, fail, log);
-}
-
function check_auto_torch(qrcode, cb) {
var gd = getApp().globalData;
var url = gd.server_url + '/api/v1/check-auto-torch/?qrcode=' + encodeURIComponent(qrcode);
@@ -33,7 +29,7 @@ function check_auto_torch(qrcode, cb) {
});
}
-function do_upload(image_data_urls, success, fail, log) {
+function upload_image_data_urls(image_data_urls, success, fail, log) {
var ui = wx.getStorageSync('userinfo');
var gd = getApp().globalData;
var fd = {
diff --git a/scanner/pages/emblemscanner/worker/index.js b/scanner/pages/emblemscanner/worker/index.js
index ca8816e..bd06929 100644
--- a/scanner/pages/emblemscanner/worker/index.js
+++ b/scanner/pages/emblemscanner/worker/index.js
@@ -2,6 +2,34 @@ console.log("hello from emblemscanner worker");
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;
qrtool.onRuntimeInitialized = () => {
@@ -23,7 +51,7 @@ function handle_frame(data, width, height, camera_sensitivity) {
ok: false,
err: "qrtool not ready",
},
- processing_time: Date.now() - begin,
+ processing_time: 0,
});
return;
}
@@ -43,10 +71,11 @@ function handle_frame(data, width, height, camera_sensitivity) {
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, width, height },
+ image_data: { data: dataUrl, width, height },
};
}
}
@@ -56,9 +85,9 @@ worker.onMessage((msg) => {
switch (msg.type) {
case "frame":
try {
- const data = worker.getCameraFrameData();
- if (data) {
- handle_frame(data, msg.width, msg.height, msg.camera_sensitivity);
+ // Use frame data from message instead of getCameraFrameData()
+ if (msg.data) {
+ handle_frame(msg.data, msg.width, msg.height, msg.camera_sensitivity);
}
} catch (e) {
console.log(e);
@@ -68,6 +97,6 @@ worker.onMessage((msg) => {
worker.postMessage(submit_message);
break;
default:
- console.log("Unknown message type:", msg.data.type);
+ console.log("Unknown message type:", msg.type);
}
});
\ No newline at end of file
diff --git a/scanner/pages/index/index.js b/scanner/pages/index/index.js
index aee203e..0e40e42 100644
--- a/scanner/pages/index/index.js
+++ b/scanner/pages/index/index.js
@@ -129,7 +129,7 @@ Page({
goto_camera: function () {
this.getUserProfile(() => {
wx.navigateTo({
- url: '/pages/emblemscanner/emblemscanner?return_page=/pages/test_result_page/test_result_page'
+ url: '/pages/emblemscanner/emblemscanner?return_page=/pages/productinfo/productinfo'
});
});
},
diff --git a/scanner/project.private.config.json b/scanner/project.private.config.json
index c1062fb..4a0a90d 100644
--- a/scanner/project.private.config.json
+++ b/scanner/project.private.config.json
@@ -5,7 +5,7 @@
{
"name": "emblemscanner force camera",
"pathName": "pages/emblemscanner/emblemscanner",
- "query": "debug=1&no_web_view=1",
+ "query": "debug=1&no_web_view=1&return_page=/pages/test_result_page/test_result_page",
"scene": null,
"launchMode": "default"
},