kedar-bhumkar commited on
Commit
474a754
·
verified ·
1 Parent(s): b7ef1a8

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +132 -11
  2. app.js +1540 -0
  3. index.html +69 -19
  4. styles.css +491 -0
README.md CHANGED
@@ -1,11 +1,132 @@
1
- ---
2
- title: Barcode Scanner
3
- emoji: 🌍
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: static
7
- pinned: false
8
- short_description: 'ZXing powered barcode scanner '
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Advanced Barcode Scanner
3
+ emoji: 📷
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: static
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # Advanced Barcode Scanner
12
+
13
+ A powerful web application that uses ZXing (Zebra Crossing) to detect and decode barcodes from uploaded images, camera feed, or captured still images.
14
+
15
+ ## Features
16
+
17
+ - **Multiple Input Methods**:
18
+ - Upload images containing barcodes
19
+ - Use live camera feed for real-time scanning
20
+ - Capture still images from camera for enhanced processing
21
+
22
+ - **Advanced Detection**:
23
+ - Image enhancement with adjustable parameters
24
+ - Multiple binarizers for improved detection
25
+ - Fallback mechanisms when primary detection fails
26
+
27
+ - **Focus Mode**:
28
+ - Full-screen camera view for better positioning
29
+ - Visual guides with crosshair for precise alignment
30
+ - Optimized processing parameters for difficult barcodes
31
+
32
+ - **Debug Mode**:
33
+ - Real-time processing information
34
+ - Detailed error reporting
35
+ - Performance insights
36
+
37
+ - **User Experience**:
38
+ - Visual feedback with barcode highlighting
39
+ - Success sound on detection
40
+ - Responsive design for all devices
41
+
42
+ ## Supported Barcode Formats
43
+
44
+ This application can recognize various barcode formats including:
45
+ - QR Code
46
+ - Data Matrix
47
+ - Aztec
48
+ - PDF 417
49
+ - EAN-13 and EAN-8
50
+ - UPC-A and UPC-E
51
+ - Code 128
52
+ - Code 39
53
+ - Code 93
54
+ - Interleaved 2 of 5 (ITF)
55
+ - Codabar
56
+
57
+ ## How It Works
58
+
59
+ This application uses ZXing (Zebra Crossing), a powerful barcode scanning library, to detect and decode barcodes. The process includes:
60
+
61
+ 1. **Image Acquisition**: From upload, camera feed, or captured still
62
+ 2. **Image Enhancement**: Adjusting brightness, contrast, and applying thresholds
63
+ 3. **Barcode Detection**: Using multiple algorithms to locate barcodes
64
+ 4. **Decoding**: Reading the actual data from the barcode
65
+ 5. **Result Display**: Showing the decoded information and highlighting the barcode location
66
+
67
+ ## Getting Started
68
+
69
+ ### Prerequisites
70
+
71
+ - A modern web browser (Chrome, Firefox, Safari, Edge)
72
+ - Internet connection (to load the ZXing library from CDN)
73
+ - Camera access (for live scanning and image capture features)
74
+ - HTTPS connection or localhost (required for camera access)
75
+
76
+ ### Running the Application
77
+
78
+ 1. Clone or download this repository
79
+ 2. Open the `index.html` file in your web browser
80
+ 3. Choose your preferred input method:
81
+ - Click "Upload Image with Barcode" to select an image
82
+ - Click "Start Camera" to use live scanning
83
+ - When camera is active, use "Capture Image" for still image processing
84
+ 4. Wait for the processing to complete
85
+ 5. View the recognized barcode data in the results section
86
+
87
+ Alternatively, you can use a local web server to serve the files:
88
+
89
+ ```bash
90
+ # Using Python 3
91
+ python -m http.server
92
+
93
+ # Using Node.js with http-server
94
+ npx http-server
95
+ ```
96
+
97
+ Then navigate to `http://localhost:8000` (or the port specified by your server).
98
+
99
+ ## Usage Tips
100
+
101
+ ### For Uploaded Images
102
+ - Use clear, well-lit images
103
+ - Ensure the barcode is in focus
104
+ - Position the barcode to be as straight as possible
105
+
106
+ ### For Live Camera Scanning
107
+ - Hold the camera steady
108
+ - Position the barcode within the guide box
109
+ - Ensure adequate lighting
110
+ - Toggle image enhancement if detection is difficult
111
+ - Use Focus Mode for challenging barcodes
112
+
113
+ ### For Captured Images
114
+ - Use when live scanning isn't detecting the barcode
115
+ - Hold the camera steady before capturing
116
+ - Position the barcode clearly in frame
117
+ - Try with and without image enhancement
118
+
119
+ ### Troubleshooting
120
+ - If a barcode isn't detected, try toggling the image enhancement option
121
+ - Enable Debug Mode to see detailed processing information
122
+ - For difficult barcodes, try Focus Mode or Capture Image feature
123
+ - Ensure adequate lighting and proper positioning
124
+
125
+ ## License
126
+
127
+ This project is open source and available under the MIT License.
128
+
129
+ ## Acknowledgements
130
+
131
+ - [ZXing](https://github.com/zxing-js/library) - JavaScript port of the ZXing barcode scanning library
132
+ - [Hugging Face](https://huggingface.co) - For hosting and sharing this application
app.js ADDED
@@ -0,0 +1,1540 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ // DOM elements
3
+ const imageUpload = document.getElementById('imageUpload');
4
+ const preview = document.getElementById('preview');
5
+ const scanCanvas = document.getElementById('scanCanvas');
6
+ const status = document.getElementById('status');
7
+ const result = document.getElementById('result');
8
+
9
+ // Camera elements - will be created dynamically
10
+ let videoElement;
11
+ let cameraContainer;
12
+ let cameraControls;
13
+ let startCameraButton;
14
+ let stopCameraButton;
15
+ let switchCameraButton;
16
+ let enhancementToggle;
17
+ let debugModeToggle;
18
+ let cameraStream = null;
19
+ let activeCameraId = null;
20
+ let availableCameras = [];
21
+ let isScanning = false;
22
+ let scanInterval = null;
23
+ let useImageEnhancement = true; // Default to true
24
+ let debugMode = false; // Default to false
25
+ let focusModeActive = false;
26
+
27
+ // Check if ZXing library is loaded
28
+ if (typeof ZXing === 'undefined') {
29
+ status.textContent = 'Error: ZXing library not loaded';
30
+ result.textContent = 'Please check your internet connection and reload the page.';
31
+ console.error('ZXing library not loaded');
32
+ return;
33
+ }
34
+
35
+ // Initialize ZXing code reader and hints
36
+ const codeReader = new ZXing.BrowserMultiFormatReader();
37
+ const hints = new Map();
38
+ const formats = [
39
+ ZXing.BarcodeFormat.QR_CODE,
40
+ ZXing.BarcodeFormat.DATA_MATRIX,
41
+ ZXing.BarcodeFormat.AZTEC,
42
+ ZXing.BarcodeFormat.PDF_417,
43
+ ZXing.BarcodeFormat.EAN_13,
44
+ ZXing.BarcodeFormat.EAN_8,
45
+ ZXing.BarcodeFormat.UPC_A,
46
+ ZXing.BarcodeFormat.UPC_E,
47
+ ZXing.BarcodeFormat.CODE_128,
48
+ ZXing.BarcodeFormat.CODE_39,
49
+ ZXing.BarcodeFormat.CODE_93,
50
+ ZXing.BarcodeFormat.ITF,
51
+ ZXing.BarcodeFormat.CODABAR
52
+ ];
53
+ hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);
54
+ hints.set(ZXing.DecodeHintType.TRY_HARDER, true);
55
+
56
+ // Initialize the app
57
+ function init() {
58
+ status.textContent = 'Ready to scan barcodes. Upload an image or use camera.';
59
+ createCameraElements();
60
+
61
+ // Check if we're in a secure context (HTTPS or localhost)
62
+ if (!isSecureContext()) {
63
+ status.textContent = 'Camera access requires HTTPS. Current connection is not secure.';
64
+ const warningDiv = document.createElement('div');
65
+ warningDiv.className = 'security-warning';
66
+ warningDiv.innerHTML = '<strong>Security Warning:</strong> Camera access requires a secure connection (HTTPS). ' +
67
+ 'You are currently on an insecure connection, which may prevent camera access. ' +
68
+ 'Please access this page via HTTPS or use localhost for testing.';
69
+ document.querySelector('.container').insertBefore(warningDiv, document.querySelector('.upload-section'));
70
+ }
71
+
72
+ checkCameraSupport();
73
+ }
74
+
75
+ // Check if we're in a secure context (HTTPS or localhost)
76
+ function isSecureContext() {
77
+ // Check if the context is secure using the SecureContext API
78
+ if (window.isSecureContext === true) {
79
+ return true;
80
+ }
81
+
82
+ // Fallback check for older browsers
83
+ return location.protocol === 'https:' ||
84
+ location.hostname === 'localhost' ||
85
+ location.hostname === '127.0.0.1';
86
+ }
87
+
88
+ // Create camera UI elements
89
+ function createCameraElements() {
90
+ // Create camera container
91
+ cameraContainer = document.createElement('div');
92
+ cameraContainer.id = 'camera-container';
93
+ cameraContainer.className = 'camera-container';
94
+ cameraContainer.style.display = 'none';
95
+
96
+ // Create video element
97
+ videoElement = document.createElement('video');
98
+ videoElement.id = 'camera-feed';
99
+ videoElement.autoplay = true;
100
+ videoElement.playsInline = true;
101
+
102
+ // Create camera controls
103
+ cameraControls = document.createElement('div');
104
+ cameraControls.className = 'camera-controls';
105
+
106
+ // Create camera buttons
107
+ startCameraButton = document.createElement('button');
108
+ startCameraButton.id = 'start-camera';
109
+ startCameraButton.textContent = 'Start Camera';
110
+ startCameraButton.addEventListener('click', startCamera);
111
+
112
+ stopCameraButton = document.createElement('button');
113
+ stopCameraButton.id = 'stop-camera';
114
+ stopCameraButton.textContent = 'Stop Camera';
115
+ stopCameraButton.style.display = 'none';
116
+ stopCameraButton.addEventListener('click', stopCamera);
117
+
118
+ switchCameraButton = document.createElement('button');
119
+ switchCameraButton.id = 'switch-camera';
120
+ switchCameraButton.textContent = 'Switch Camera';
121
+ switchCameraButton.style.display = 'none';
122
+ switchCameraButton.addEventListener('click', switchCamera);
123
+
124
+ // Create capture image button
125
+ const captureImageButton = document.createElement('button');
126
+ captureImageButton.id = 'capture-image';
127
+ captureImageButton.textContent = 'Capture Image';
128
+ captureImageButton.style.display = 'none';
129
+ captureImageButton.addEventListener('click', captureImage);
130
+
131
+ // Create focus mode button
132
+ const focusModeButton = document.createElement('button');
133
+ focusModeButton.id = 'focus-mode';
134
+ focusModeButton.textContent = 'Focus Mode';
135
+ focusModeButton.style.display = 'none';
136
+ focusModeButton.addEventListener('click', toggleFocusMode);
137
+
138
+ // Create retry button for permission issues
139
+ const retryPermissionButton = document.createElement('button');
140
+ retryPermissionButton.id = 'retry-permission';
141
+ retryPermissionButton.textContent = 'Retry Camera Permission';
142
+ retryPermissionButton.style.display = 'none';
143
+ retryPermissionButton.addEventListener('click', () => {
144
+ retryPermissionButton.style.display = 'none';
145
+ status.textContent = 'Requesting camera permission...';
146
+ checkCameraSupport();
147
+ });
148
+
149
+ // Create enhancement toggle
150
+ const enhancementContainer = document.createElement('div');
151
+ enhancementContainer.className = 'enhancement-toggle-container';
152
+
153
+ enhancementToggle = document.createElement('input');
154
+ enhancementToggle.type = 'checkbox';
155
+ enhancementToggle.id = 'enhancement-toggle';
156
+ enhancementToggle.checked = useImageEnhancement;
157
+ enhancementToggle.addEventListener('change', (e) => {
158
+ useImageEnhancement = e.target.checked;
159
+ status.textContent = useImageEnhancement ?
160
+ 'Image enhancement enabled. This may help detect barcodes in difficult lighting.' :
161
+ 'Image enhancement disabled. Use this if barcodes are not being detected correctly.';
162
+ });
163
+
164
+ const enhancementLabel = document.createElement('label');
165
+ enhancementLabel.htmlFor = 'enhancement-toggle';
166
+ enhancementLabel.textContent = 'Enable image enhancement';
167
+
168
+ enhancementContainer.appendChild(enhancementToggle);
169
+ enhancementContainer.appendChild(enhancementLabel);
170
+
171
+ // Create debug mode toggle
172
+ const debugContainer = document.createElement('div');
173
+ debugContainer.className = 'enhancement-toggle-container debug-toggle-container';
174
+
175
+ debugModeToggle = document.createElement('input');
176
+ debugModeToggle.type = 'checkbox';
177
+ debugModeToggle.id = 'debug-toggle';
178
+ debugModeToggle.checked = debugMode;
179
+ debugModeToggle.addEventListener('change', (e) => {
180
+ debugMode = e.target.checked;
181
+ status.textContent = debugMode ?
182
+ 'Debug mode enabled. Processing details will be shown.' :
183
+ 'Debug mode disabled.';
184
+
185
+ // Create or remove debug info element
186
+ let debugInfo = document.getElementById('debug-info');
187
+ if (debugMode) {
188
+ if (!debugInfo) {
189
+ debugInfo = document.createElement('div');
190
+ debugInfo.id = 'debug-info';
191
+ debugInfo.className = 'debug-info';
192
+ result.parentNode.appendChild(debugInfo);
193
+ }
194
+ } else {
195
+ if (debugInfo) {
196
+ debugInfo.remove();
197
+ }
198
+ }
199
+ });
200
+
201
+ const debugLabel = document.createElement('label');
202
+ debugLabel.htmlFor = 'debug-toggle';
203
+ debugLabel.textContent = 'Debug mode';
204
+
205
+ debugContainer.appendChild(debugModeToggle);
206
+ debugContainer.appendChild(debugLabel);
207
+
208
+ // Append elements
209
+ cameraControls.appendChild(startCameraButton);
210
+ cameraControls.appendChild(stopCameraButton);
211
+ cameraControls.appendChild(switchCameraButton);
212
+ cameraControls.appendChild(captureImageButton);
213
+ cameraControls.appendChild(focusModeButton);
214
+ cameraControls.appendChild(retryPermissionButton);
215
+ cameraControls.appendChild(enhancementContainer);
216
+ cameraControls.appendChild(debugContainer);
217
+ cameraContainer.appendChild(videoElement);
218
+
219
+ // Insert camera elements into the DOM
220
+ const uploadSection = document.querySelector('.upload-section');
221
+ uploadSection.parentNode.insertBefore(cameraContainer, uploadSection.nextSibling);
222
+ uploadSection.parentNode.insertBefore(cameraControls, uploadSection.nextSibling);
223
+ }
224
+
225
+ // Check if camera is supported
226
+ function checkCameraSupport() {
227
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
228
+ console.warn('Camera access not supported in this browser');
229
+ startCameraButton.disabled = true;
230
+ startCameraButton.title = 'Camera not supported in this browser';
231
+ status.textContent = 'Camera not supported in this browser. Try using a modern browser like Chrome, Firefox, or Edge.';
232
+ return false;
233
+ }
234
+
235
+ // First try to access the camera to trigger permission prompt
236
+ navigator.mediaDevices.getUserMedia({ video: true })
237
+ .then(stream => {
238
+ // Stop the stream immediately after getting permission
239
+ stream.getTracks().forEach(track => track.stop());
240
+
241
+ // Now enumerate devices after permission is granted
242
+ return navigator.mediaDevices.enumerateDevices();
243
+ })
244
+ .then(devices => {
245
+ availableCameras = devices.filter(device => device.kind === 'videoinput');
246
+ console.log('Available cameras:', availableCameras);
247
+
248
+ if (availableCameras.length === 0) {
249
+ startCameraButton.disabled = true;
250
+ startCameraButton.title = 'No cameras detected';
251
+ status.textContent = 'No cameras detected on your device';
252
+ return;
253
+ }
254
+
255
+ // Enable camera button
256
+ startCameraButton.disabled = false;
257
+ startCameraButton.title = 'Start camera for barcode scanning';
258
+ status.textContent = 'Camera permission granted. Click "Start Camera" to begin scanning.';
259
+
260
+ // Show switch camera button if multiple cameras available
261
+ if (availableCameras.length > 1) {
262
+ switchCameraButton.style.display = 'inline-block';
263
+ }
264
+ })
265
+ .catch(error => {
266
+ console.error('Error accessing camera:', error);
267
+ if (error.name === 'NotAllowedError' || error.name === 'PermissionDeniedError') {
268
+ status.textContent = 'Camera permission denied. Please click "Retry Camera Permission" and allow camera access when prompted.';
269
+ startCameraButton.disabled = true;
270
+ startCameraButton.title = 'Camera permission denied';
271
+
272
+ // Show retry permission button
273
+ const retryButton = document.getElementById('retry-permission');
274
+ if (retryButton) {
275
+ retryButton.style.display = 'inline-block';
276
+ }
277
+ } else {
278
+ // Try to enumerate devices anyway, in case permissions were granted before
279
+ navigator.mediaDevices.enumerateDevices()
280
+ .then(devices => {
281
+ availableCameras = devices.filter(device => device.kind === 'videoinput');
282
+ console.log('Available cameras (without permission):', availableCameras);
283
+
284
+ if (availableCameras.length === 0) {
285
+ startCameraButton.disabled = true;
286
+ startCameraButton.title = 'No cameras detected';
287
+ status.textContent = 'No cameras detected on your device';
288
+ } else {
289
+ startCameraButton.disabled = false;
290
+ startCameraButton.title = 'Start camera for barcode scanning';
291
+
292
+ if (availableCameras.length > 1) {
293
+ switchCameraButton.style.display = 'inline-block';
294
+ }
295
+ }
296
+ })
297
+ .catch(enumError => {
298
+ console.error('Error enumerating devices:', enumError);
299
+ startCameraButton.disabled = true;
300
+ startCameraButton.title = 'Error accessing camera information';
301
+ status.textContent = 'Error accessing camera. Please check if your camera is connected and working properly.';
302
+ });
303
+ }
304
+ });
305
+
306
+ return true;
307
+ }
308
+
309
+ // Start camera
310
+ function startCamera() {
311
+ // Hide image preview and show camera
312
+ preview.style.display = 'none';
313
+ cameraContainer.style.display = 'block';
314
+
315
+ // Update buttons
316
+ startCameraButton.style.display = 'none';
317
+ stopCameraButton.style.display = 'inline-block';
318
+
319
+ // Show focus mode and capture image buttons
320
+ const focusModeButton = document.getElementById('focus-mode');
321
+ const captureImageButton = document.getElementById('capture-image');
322
+ if (focusModeButton) {
323
+ focusModeButton.style.display = 'inline-block';
324
+ }
325
+ if (captureImageButton) {
326
+ captureImageButton.style.display = 'inline-block';
327
+ }
328
+
329
+ // Clear previous results
330
+ status.textContent = 'Starting camera...';
331
+ result.textContent = '';
332
+
333
+ // Try to get cameras again if none were detected initially
334
+ if (availableCameras.length === 0) {
335
+ navigator.mediaDevices.enumerateDevices()
336
+ .then(devices => {
337
+ availableCameras = devices.filter(device => device.kind === 'videoinput');
338
+ console.log('Re-checking available cameras:', availableCameras);
339
+ continueStartCamera();
340
+ })
341
+ .catch(error => {
342
+ console.error('Error re-enumerating devices:', error);
343
+ continueStartCamera();
344
+ });
345
+ } else {
346
+ continueStartCamera();
347
+ }
348
+ }
349
+
350
+ // Continue camera startup after checking for cameras
351
+ function continueStartCamera() {
352
+ // Select camera (use first camera by default or previously selected)
353
+ const cameraId = activeCameraId || (availableCameras.length > 0 ? availableCameras[0].deviceId : null);
354
+
355
+ if (!cameraId) {
356
+ // Try a more generic approach if no specific camera ID is available
357
+ const constraints = {
358
+ video: {
359
+ facingMode: 'environment' // Prefer back camera
360
+ }
361
+ };
362
+
363
+ startCameraWithConstraints(constraints);
364
+ } else {
365
+ // Use specific camera ID
366
+ const constraints = {
367
+ video: {
368
+ deviceId: { exact: cameraId },
369
+ width: { ideal: 1280 },
370
+ height: { ideal: 720 }
371
+ }
372
+ };
373
+
374
+ startCameraWithConstraints(constraints);
375
+ }
376
+ }
377
+
378
+ // Start camera with specific constraints
379
+ function startCameraWithConstraints(constraints) {
380
+ navigator.mediaDevices.getUserMedia(constraints)
381
+ .then(stream => {
382
+ cameraStream = stream;
383
+ videoElement.srcObject = stream;
384
+
385
+ // Get the actual device ID from the stream
386
+ const videoTrack = stream.getVideoTracks()[0];
387
+ if (videoTrack) {
388
+ const settings = videoTrack.getSettings();
389
+ activeCameraId = settings.deviceId;
390
+ console.log('Active camera ID:', activeCameraId);
391
+ console.log('Camera settings:', settings);
392
+ }
393
+
394
+ // Wait for video to be ready
395
+ videoElement.onloadedmetadata = () => {
396
+ status.textContent = 'Camera active. Point at a barcode to scan.';
397
+ startBarcodeScanning();
398
+ };
399
+ })
400
+ .catch(error => {
401
+ console.error('Error starting camera with constraints:', error, constraints);
402
+
403
+ // If failed with specific constraints, try generic constraints
404
+ if (constraints.video.deviceId) {
405
+ console.log('Trying generic camera constraints...');
406
+ startCameraWithConstraints({
407
+ video: {
408
+ facingMode: 'environment'
409
+ }
410
+ });
411
+ } else {
412
+ status.textContent = 'Error starting camera: ' + (error.message || 'Unknown error');
413
+ stopCamera();
414
+ }
415
+ });
416
+ }
417
+
418
+ // Stop camera
419
+ function stopCamera() {
420
+ // Stop scanning
421
+ stopBarcodeScanning();
422
+
423
+ // Stop camera stream
424
+ if (cameraStream) {
425
+ cameraStream.getTracks().forEach(track => track.stop());
426
+ cameraStream = null;
427
+ }
428
+
429
+ // Update UI
430
+ videoElement.srcObject = null;
431
+ cameraContainer.style.display = 'none';
432
+ startCameraButton.style.display = 'inline-block';
433
+ stopCameraButton.style.display = 'none';
434
+
435
+ // Hide focus mode and capture image buttons
436
+ const focusModeButton = document.getElementById('focus-mode');
437
+ const captureImageButton = document.getElementById('capture-image');
438
+ if (focusModeButton) {
439
+ focusModeButton.style.display = 'none';
440
+ }
441
+ if (captureImageButton) {
442
+ captureImageButton.style.display = 'none';
443
+ }
444
+
445
+ // Remove focus mode if active
446
+ document.body.classList.remove('focus-mode-active');
447
+
448
+ status.textContent = 'Camera stopped. Upload an image or restart camera.';
449
+ }
450
+
451
+ // Switch between available cameras
452
+ function switchCamera() {
453
+ if (availableCameras.length <= 1) return;
454
+
455
+ // Find next camera in the list
456
+ const currentIndex = availableCameras.findIndex(camera => camera.deviceId === activeCameraId);
457
+ const nextIndex = (currentIndex + 1) % availableCameras.length;
458
+ activeCameraId = availableCameras[nextIndex].deviceId;
459
+
460
+ // Restart camera with new device
461
+ if (cameraStream) {
462
+ stopCamera();
463
+ startCamera();
464
+ }
465
+ }
466
+
467
+ // Start continuous barcode scanning
468
+ function startBarcodeScanning() {
469
+ if (isScanning) return;
470
+ isScanning = true;
471
+
472
+ // Add scanning indicator
473
+ const scanIndicator = document.createElement('div');
474
+ scanIndicator.id = 'scan-indicator';
475
+ scanIndicator.className = 'scan-indicator';
476
+ cameraContainer.appendChild(scanIndicator);
477
+
478
+ // Process frames at regular intervals
479
+ scanInterval = setInterval(() => {
480
+ if (!videoElement || !cameraStream) return;
481
+
482
+ // Capture current frame
483
+ const width = videoElement.videoWidth;
484
+ const height = videoElement.videoHeight;
485
+
486
+ if (width === 0 || height === 0) return; // Skip if video dimensions aren't available yet
487
+
488
+ // Set canvas dimensions to match video
489
+ scanCanvas.width = width;
490
+ scanCanvas.height = height;
491
+
492
+ // Draw video frame on canvas
493
+ const ctx = scanCanvas.getContext('2d');
494
+ ctx.drawImage(videoElement, 0, 0, width, height);
495
+
496
+ // Add scanning guide overlay
497
+ drawScanningGuide(ctx, width, height);
498
+
499
+ // Process the frame
500
+ processVideoFrame(ctx, width, height);
501
+ }, 100); // Scan more frequently (every 100ms instead of 200ms)
502
+ }
503
+
504
+ // Stop barcode scanning
505
+ function stopBarcodeScanning() {
506
+ isScanning = false;
507
+ if (scanInterval) {
508
+ clearInterval(scanInterval);
509
+ scanInterval = null;
510
+ }
511
+
512
+ // Remove scanning indicator if it exists
513
+ const scanIndicator = document.getElementById('scan-indicator');
514
+ if (scanIndicator) {
515
+ scanIndicator.remove();
516
+ }
517
+ }
518
+
519
+ // Draw scanning guide overlay
520
+ function drawScanningGuide(ctx, width, height) {
521
+ // Draw a semi-transparent guide rectangle in the center
522
+ const guideSize = Math.min(width, height) * (focusModeActive ? 0.5 : 0.7); // Smaller guide in focus mode
523
+ const x = (width - guideSize) / 2;
524
+ const y = (height - guideSize) / 2;
525
+
526
+ // Draw outer darkened area
527
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
528
+ ctx.fillRect(0, 0, width, height);
529
+
530
+ // Draw transparent center
531
+ ctx.clearRect(x, y, guideSize, guideSize);
532
+
533
+ // Draw guide border
534
+ ctx.strokeStyle = focusModeActive ? 'rgba(231, 76, 60, 0.8)' : 'rgba(52, 152, 219, 0.8)';
535
+ ctx.lineWidth = focusModeActive ? 6 : 4;
536
+ ctx.strokeRect(x, y, guideSize, guideSize);
537
+
538
+ // Draw corner markers
539
+ const markerLength = guideSize * 0.1;
540
+ ctx.lineWidth = focusModeActive ? 8 : 6;
541
+
542
+ // Top-left corner
543
+ ctx.beginPath();
544
+ ctx.moveTo(x, y + markerLength);
545
+ ctx.lineTo(x, y);
546
+ ctx.lineTo(x + markerLength, y);
547
+ ctx.stroke();
548
+
549
+ // Top-right corner
550
+ ctx.beginPath();
551
+ ctx.moveTo(x + guideSize - markerLength, y);
552
+ ctx.lineTo(x + guideSize, y);
553
+ ctx.lineTo(x + guideSize, y + markerLength);
554
+ ctx.stroke();
555
+
556
+ // Bottom-right corner
557
+ ctx.beginPath();
558
+ ctx.moveTo(x + guideSize, y + guideSize - markerLength);
559
+ ctx.lineTo(x + guideSize, y + guideSize);
560
+ ctx.lineTo(x + guideSize - markerLength, y + guideSize);
561
+ ctx.stroke();
562
+
563
+ // Bottom-left corner
564
+ ctx.beginPath();
565
+ ctx.moveTo(x + markerLength, y + guideSize);
566
+ ctx.lineTo(x, y + guideSize);
567
+ ctx.lineTo(x, y + guideSize - markerLength);
568
+ ctx.stroke();
569
+
570
+ // Add text instruction
571
+ ctx.font = '16px Arial';
572
+ ctx.fillStyle = 'white';
573
+ ctx.textAlign = 'center';
574
+
575
+ if (focusModeActive) {
576
+ ctx.fillText('FOCUS MODE: Position barcode in the red box', width / 2, y + guideSize + 30);
577
+
578
+ // Add crosshair in focus mode
579
+ const centerX = x + guideSize / 2;
580
+ const centerY = y + guideSize / 2;
581
+ const crosshairSize = guideSize * 0.1;
582
+
583
+ ctx.strokeStyle = 'rgba(231, 76, 60, 0.8)';
584
+ ctx.lineWidth = 2;
585
+
586
+ // Horizontal line
587
+ ctx.beginPath();
588
+ ctx.moveTo(centerX - crosshairSize, centerY);
589
+ ctx.lineTo(centerX + crosshairSize, centerY);
590
+ ctx.stroke();
591
+
592
+ // Vertical line
593
+ ctx.beginPath();
594
+ ctx.moveTo(centerX, centerY - crosshairSize);
595
+ ctx.lineTo(centerX, centerY + crosshairSize);
596
+ ctx.stroke();
597
+ } else {
598
+ ctx.fillText('Position barcode within the box', width / 2, y + guideSize + 30);
599
+ }
600
+ }
601
+
602
+ // Process video frame for barcode detection
603
+ function processVideoFrame(ctx, width, height) {
604
+ try {
605
+ // Pulse the scan indicator to show active scanning
606
+ const scanIndicator = document.getElementById('scan-indicator');
607
+ if (scanIndicator) {
608
+ scanIndicator.classList.add('pulse');
609
+ setTimeout(() => {
610
+ scanIndicator.classList.remove('pulse');
611
+ }, 50);
612
+ }
613
+
614
+ // Update debug info
615
+ if (debugMode) {
616
+ updateDebugInfo('Processing frame', { width, height });
617
+ }
618
+
619
+ // Store original image data for fallback
620
+ const originalImageData = ctx.getImageData(0, 0, width, height);
621
+
622
+ // Apply image enhancements to improve detection if enabled
623
+ if (useImageEnhancement) {
624
+ enhanceImage(ctx, width, height);
625
+ if (debugMode) {
626
+ updateDebugInfo('Image enhancement applied');
627
+ }
628
+ }
629
+
630
+ // Create a data URL from the canvas for the BrowserMultiFormatReader
631
+ const dataUrl = scanCanvas.toDataURL('image/png');
632
+
633
+ // Try direct MultiFormatReader approach first (more reliable for video)
634
+ try {
635
+ // Create a ZXing HTMLCanvasElementLuminanceSource
636
+ const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas);
637
+
638
+ // Create a MultiFormatReader with hints
639
+ const reader = new ZXing.MultiFormatReader();
640
+
641
+ // Set up hints with all supported formats and try harder flag
642
+ const newHints = new Map();
643
+ newHints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);
644
+ newHints.set(ZXing.DecodeHintType.TRY_HARDER, true);
645
+ // Add these additional hints for better video frame detection
646
+ newHints.set(ZXing.DecodeHintType.PURE_BARCODE, false);
647
+ newHints.set(ZXing.DecodeHintType.CHARACTER_SET, "UTF-8");
648
+ newHints.set(ZXing.DecodeHintType.ASSUME_GS1, false);
649
+ reader.setHints(newHints);
650
+
651
+ // Try different binarizers for better detection
652
+ const binarizers = [
653
+ new ZXing.HybridBinarizer(luminanceSource),
654
+ new ZXing.GlobalHistogramBinarizer(luminanceSource)
655
+ ];
656
+
657
+ let decodedResult = null;
658
+ let usedBinarizer = '';
659
+ let errorMessages = [];
660
+
661
+ // Try each binarizer
662
+ for (const binarizer of binarizers) {
663
+ if (decodedResult) break; // Stop if we already found a result
664
+
665
+ const binaryBitmap = new ZXing.BinaryBitmap(binarizer);
666
+ usedBinarizer = binarizer instanceof ZXing.HybridBinarizer ? 'HybridBinarizer' : 'GlobalHistogramBinarizer';
667
+
668
+ if (debugMode) {
669
+ updateDebugInfo(`Trying ${usedBinarizer}`);
670
+ }
671
+
672
+ try {
673
+ // Try to decode the image using the binary bitmap
674
+ const result = reader.decode(binaryBitmap);
675
+
676
+ decodedResult = {
677
+ text: result.getText(),
678
+ format: result.getBarcodeFormat(),
679
+ resultPoints: result.getResultPoints()
680
+ };
681
+
682
+ if (debugMode) {
683
+ updateDebugInfo(`Success with ${usedBinarizer}`, decodedResult);
684
+ }
685
+
686
+ } catch (error) {
687
+ // Store error for debugging
688
+ errorMessages.push(`${usedBinarizer}: ${error.message || 'Unknown error'}`);
689
+
690
+ if (debugMode) {
691
+ updateDebugInfo(`Failed with ${usedBinarizer}`, { error: error.message || 'Unknown error' });
692
+ }
693
+ }
694
+ }
695
+
696
+ // If we got a result from the direct approach
697
+ if (decodedResult) {
698
+ // Barcode detected successfully
699
+ status.textContent = 'Barcode recognized successfully!';
700
+
701
+ // Format the result
702
+ const resultText = `Code: ${decodedResult.text}\nFormat: ${decodedResult.format}`;
703
+ document.getElementById('result').textContent = resultText;
704
+
705
+ // Draw the barcode location on the canvas
706
+ if (decodedResult.resultPoints && decodedResult.resultPoints.length > 0) {
707
+ drawBarcodeLocation(ctx, decodedResult.resultPoints);
708
+ }
709
+
710
+ // Play success sound
711
+ playSuccessBeep();
712
+
713
+ // Pause scanning briefly after successful detection
714
+ stopBarcodeScanning();
715
+ setTimeout(startBarcodeScanning, 2000);
716
+ return;
717
+ }
718
+
719
+ // If direct approach failed, try with original image if we enhanced it
720
+ if (useImageEnhancement) {
721
+ // Restore original image
722
+ ctx.putImageData(originalImageData, 0, 0);
723
+
724
+ if (debugMode) {
725
+ updateDebugInfo('Trying with original (non-enhanced) image');
726
+ }
727
+
728
+ // Try again with original image
729
+ for (const binarizer of binarizers) {
730
+ if (decodedResult) break; // Stop if we already found a result
731
+
732
+ const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas);
733
+ const binaryBitmap = new ZXing.BinaryBitmap(binarizer);
734
+ usedBinarizer = binarizer instanceof ZXing.HybridBinarizer ? 'HybridBinarizer (original)' : 'GlobalHistogramBinarizer (original)';
735
+
736
+ try {
737
+ // Try to decode the image using the binary bitmap
738
+ const result = reader.decode(binaryBitmap);
739
+
740
+ decodedResult = {
741
+ text: result.getText(),
742
+ format: result.getBarcodeFormat(),
743
+ resultPoints: result.getResultPoints()
744
+ };
745
+
746
+ if (debugMode) {
747
+ updateDebugInfo(`Success with ${usedBinarizer}`, decodedResult);
748
+ }
749
+
750
+ } catch (error) {
751
+ // Store error for debugging
752
+ errorMessages.push(`${usedBinarizer}: ${error.message || 'Unknown error'}`);
753
+
754
+ if (debugMode) {
755
+ updateDebugInfo(`Failed with ${usedBinarizer}`, { error: error.message || 'Unknown error' });
756
+ }
757
+ }
758
+ }
759
+
760
+ // If we got a result from the original image
761
+ if (decodedResult) {
762
+ // Barcode detected successfully
763
+ status.textContent = 'Barcode recognized successfully!';
764
+
765
+ // Format the result
766
+ const resultText = `Code: ${decodedResult.text}\nFormat: ${decodedResult.format}`;
767
+ document.getElementById('result').textContent = resultText;
768
+
769
+ // Draw the barcode location on the canvas
770
+ if (decodedResult.resultPoints && decodedResult.resultPoints.length > 0) {
771
+ drawBarcodeLocation(ctx, decodedResult.resultPoints);
772
+ }
773
+
774
+ // Play success sound
775
+ playSuccessBeep();
776
+
777
+ // Pause scanning briefly after successful detection
778
+ stopBarcodeScanning();
779
+ setTimeout(startBarcodeScanning, 2000);
780
+ return;
781
+ }
782
+ }
783
+
784
+ // If all direct methods failed, fall back to BrowserMultiFormatReader
785
+ if (debugMode) {
786
+ updateDebugInfo('Trying BrowserMultiFormatReader fallback');
787
+ }
788
+
789
+ // Use the BrowserMultiFormatReader to decode the image
790
+ codeReader.decodeFromImageUrl(dataUrl)
791
+ .then(result => {
792
+ if (result) {
793
+ // Barcode detected successfully
794
+ status.textContent = 'Barcode recognized successfully!';
795
+
796
+ // Format the result
797
+ const resultText = `Code: ${result.text}\nFormat: ${result.format}`;
798
+ document.getElementById('result').textContent = resultText;
799
+
800
+ if (debugMode) {
801
+ updateDebugInfo('Success with BrowserMultiFormatReader', {
802
+ text: result.text,
803
+ format: result.format
804
+ });
805
+ }
806
+
807
+ // Draw the barcode location on the canvas if available
808
+ if (result.resultPoints && result.resultPoints.length > 0) {
809
+ drawBarcodeLocation(ctx, result.resultPoints);
810
+ }
811
+
812
+ // Play success sound
813
+ playSuccessBeep();
814
+
815
+ // Pause scanning briefly after successful detection
816
+ stopBarcodeScanning();
817
+ setTimeout(startBarcodeScanning, 2000);
818
+ }
819
+ })
820
+ .catch((error) => {
821
+ // No barcode detected with any method
822
+ if (debugMode) {
823
+ updateDebugInfo('Failed with all methods', {
824
+ error: error.message || 'Unknown error',
825
+ allErrors: errorMessages.join(', ')
826
+ });
827
+ }
828
+ });
829
+
830
+ } catch (directError) {
831
+ // If direct MultiFormatReader approach fails completely, fall back to BrowserMultiFormatReader
832
+ if (debugMode) {
833
+ updateDebugInfo('Direct MultiFormatReader failed, trying BrowserMultiFormatReader', {
834
+ error: directError.message || 'Unknown error'
835
+ });
836
+ }
837
+
838
+ // Use the BrowserMultiFormatReader to decode the image
839
+ codeReader.decodeFromImageUrl(dataUrl)
840
+ .then(result => {
841
+ if (result) {
842
+ // Barcode detected successfully
843
+ status.textContent = 'Barcode recognized successfully!';
844
+
845
+ // Format the result
846
+ const resultText = `Code: ${result.text}\nFormat: ${result.format}`;
847
+ document.getElementById('result').textContent = resultText;
848
+
849
+ if (debugMode) {
850
+ updateDebugInfo('Success with BrowserMultiFormatReader', {
851
+ text: result.text,
852
+ format: result.format
853
+ });
854
+ }
855
+
856
+ // Draw the barcode location on the canvas if available
857
+ if (result.resultPoints && result.resultPoints.length > 0) {
858
+ drawBarcodeLocation(ctx, result.resultPoints);
859
+ }
860
+
861
+ // Play success sound
862
+ playSuccessBeep();
863
+
864
+ // Pause scanning briefly after successful detection
865
+ stopBarcodeScanning();
866
+ setTimeout(startBarcodeScanning, 2000);
867
+ }
868
+ })
869
+ .catch((error) => {
870
+ // No barcode detected with any method
871
+ if (debugMode) {
872
+ updateDebugInfo('Failed with all methods', {
873
+ error: error.message || 'Unknown error'
874
+ });
875
+ }
876
+ });
877
+ }
878
+
879
+ } catch (error) {
880
+ console.error('Error processing video frame:', error);
881
+ if (debugMode) {
882
+ updateDebugInfo('Error processing frame', { error: error.message || 'Unknown error' });
883
+ }
884
+ }
885
+ }
886
+
887
+ // Update debug information
888
+ function updateDebugInfo(action, data = {}) {
889
+ if (!debugMode) return;
890
+
891
+ const debugInfo = document.getElementById('debug-info');
892
+ if (!debugInfo) return;
893
+
894
+ const timestamp = new Date().toLocaleTimeString();
895
+ const entry = document.createElement('div');
896
+ entry.className = 'debug-entry';
897
+
898
+ let content = `<strong>${timestamp}</strong>: ${action}`;
899
+
900
+ if (Object.keys(data).length > 0) {
901
+ content += '<ul>';
902
+ for (const [key, value] of Object.entries(data)) {
903
+ content += `<li><strong>${key}:</strong> ${value}</li>`;
904
+ }
905
+ content += '</ul>';
906
+ }
907
+
908
+ entry.innerHTML = content;
909
+
910
+ // Add to the top
911
+ if (debugInfo.firstChild) {
912
+ debugInfo.insertBefore(entry, debugInfo.firstChild);
913
+ } else {
914
+ debugInfo.appendChild(entry);
915
+ }
916
+
917
+ // Limit entries to 10
918
+ while (debugInfo.children.length > 10) {
919
+ debugInfo.removeChild(debugInfo.lastChild);
920
+ }
921
+ }
922
+
923
+ // Enhance image to improve barcode detection
924
+ function enhanceImage(ctx, width, height) {
925
+ try {
926
+ // Get image data
927
+ const imageData = ctx.getImageData(0, 0, width, height);
928
+ const data = imageData.data;
929
+
930
+ // Try different enhancement approaches
931
+ // First, store the original image data
932
+ const originalData = new Uint8ClampedArray(data);
933
+
934
+ // Image enhancement parameters - adjust based on focus mode
935
+ const brightness = focusModeActive ? 20 : 15; // -255 to 255
936
+ const contrast = focusModeActive ? 1.4 : 1.3; // 0 to 2+
937
+ const threshold = focusModeActive ? 135 : 128; // 0 to 255 (middle value for better results)
938
+
939
+ // Apply brightness, contrast, and threshold
940
+ for (let i = 0; i < data.length; i += 4) {
941
+ // Apply brightness
942
+ data[i] += brightness; // R
943
+ data[i + 1] += brightness; // G
944
+ data[i + 2] += brightness; // B
945
+
946
+ // Apply contrast
947
+ data[i] = Math.min(255, Math.max(0, ((data[i] - 128) * contrast) + 128)); // R
948
+ data[i + 1] = Math.min(255, Math.max(0, ((data[i + 1] - 128) * contrast) + 128)); // G
949
+ data[i + 2] = Math.min(255, Math.max(0, ((data[i + 2] - 128) * contrast) + 128)); // B
950
+
951
+ // Calculate grayscale value
952
+ const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
953
+
954
+ // Apply threshold for better barcode detection
955
+ // This creates higher contrast between dark and light areas
956
+ if (gray < threshold) {
957
+ data[i] = 0; // R
958
+ data[i + 1] = 0; // G
959
+ data[i + 2] = 0; // B
960
+ } else {
961
+ data[i] = 255; // R
962
+ data[i + 1] = 255; // G
963
+ data[i + 2] = 255; // B
964
+ }
965
+ }
966
+
967
+ // Put the modified image data back on the canvas
968
+ ctx.putImageData(imageData, 0, 0);
969
+
970
+ // Apply sharpening filter
971
+ applySharpening(ctx, width, height);
972
+
973
+ // Store this enhanced version for potential fallback
974
+ const enhancedImageData = ctx.getImageData(0, 0, width, height);
975
+
976
+ // If debug mode is on, we'll keep the enhanced image visible
977
+ if (!debugMode) {
978
+ // For non-debug mode, we'll try a less aggressive approach as a fallback
979
+ // This will be used in the next scan cycle if the current one fails
980
+ window.lastEnhancedImageData = enhancedImageData;
981
+ window.originalImageData = originalData;
982
+ window.lastImageDimensions = { width, height };
983
+ }
984
+
985
+ } catch (error) {
986
+ console.error('Error enhancing image:', error);
987
+ }
988
+ }
989
+
990
+ // Apply sharpening filter to the image
991
+ function applySharpening(ctx, width, height) {
992
+ try {
993
+ // Get image data
994
+ const imageData = ctx.getImageData(0, 0, width, height);
995
+ const data = imageData.data;
996
+ const dataBackup = new Uint8ClampedArray(data);
997
+
998
+ // Sharpening kernel - less aggressive
999
+ const kernel = [
1000
+ 0, -0.5, 0,
1001
+ -0.5, 3, -0.5,
1002
+ 0, -0.5, 0
1003
+ ];
1004
+
1005
+ // Apply convolution
1006
+ for (let y = 1; y < height - 1; y++) {
1007
+ for (let x = 1; x < width - 1; x++) {
1008
+ const offset = (y * width + x) * 4;
1009
+
1010
+ // For each color channel
1011
+ for (let c = 0; c < 3; c++) {
1012
+ let val = 0;
1013
+
1014
+ // Apply kernel
1015
+ for (let ky = -1; ky <= 1; ky++) {
1016
+ for (let kx = -1; kx <= 1; kx++) {
1017
+ const idx = ((y + ky) * width + (x + kx)) * 4 + c;
1018
+ val += dataBackup[idx] * kernel[(ky + 1) * 3 + (kx + 1)];
1019
+ }
1020
+ }
1021
+
1022
+ // Set the new value
1023
+ data[offset + c] = Math.min(255, Math.max(0, val));
1024
+ }
1025
+ }
1026
+ }
1027
+
1028
+ // Put the modified image data back on the canvas
1029
+ ctx.putImageData(imageData, 0, 0);
1030
+
1031
+ } catch (error) {
1032
+ console.error('Error applying sharpening:', error);
1033
+ }
1034
+ }
1035
+
1036
+ // Play a success beep sound
1037
+ function playSuccessBeep() {
1038
+ try {
1039
+ const audioContext = new (window.AudioContext || window.webkitAudioContext)();
1040
+ const oscillator = audioContext.createOscillator();
1041
+ const gainNode = audioContext.createGain();
1042
+
1043
+ oscillator.type = 'sine';
1044
+ oscillator.frequency.setValueAtTime(1800, audioContext.currentTime);
1045
+ oscillator.frequency.setValueAtTime(1200, audioContext.currentTime + 0.1);
1046
+
1047
+ gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
1048
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
1049
+
1050
+ oscillator.connect(gainNode);
1051
+ gainNode.connect(audioContext.destination);
1052
+
1053
+ oscillator.start();
1054
+ oscillator.stop(audioContext.currentTime + 0.3);
1055
+ } catch (error) {
1056
+ console.error('Error playing success beep:', error);
1057
+ }
1058
+ }
1059
+
1060
+ // Handle image upload
1061
+ imageUpload.addEventListener('change', async (e) => {
1062
+ const file = e.target.files[0];
1063
+ if (!file) return;
1064
+
1065
+ // Stop camera if running
1066
+ stopCamera();
1067
+
1068
+ // Check if file is an image
1069
+ if (!file.type.match('image.*')) {
1070
+ status.textContent = 'Error: Not an image file';
1071
+ result.textContent = 'Please upload an image file (JPEG, PNG, etc.)';
1072
+ return;
1073
+ }
1074
+
1075
+ // Display image preview
1076
+ const reader = new FileReader();
1077
+ reader.onload = (event) => {
1078
+ preview.src = event.target.result;
1079
+ preview.style.display = 'block';
1080
+
1081
+ // Process the image with ZXing after it's loaded
1082
+ preview.onload = () => {
1083
+ processImage(preview);
1084
+ };
1085
+ };
1086
+ reader.onerror = () => {
1087
+ status.textContent = 'Error: Failed to read file';
1088
+ result.textContent = 'There was an error reading the file. Please try again.';
1089
+ };
1090
+ reader.readAsDataURL(file);
1091
+ });
1092
+
1093
+ // Process image with ZXing
1094
+ async function processImage(imageElement) {
1095
+ status.textContent = 'Processing image...';
1096
+ result.textContent = '';
1097
+
1098
+ try {
1099
+ // Get image dimensions
1100
+ const width = imageElement.naturalWidth;
1101
+ const height = imageElement.naturalHeight;
1102
+
1103
+ // Set canvas dimensions to match image
1104
+ scanCanvas.width = width;
1105
+ scanCanvas.height = height;
1106
+
1107
+ // Draw image on canvas
1108
+ const ctx = scanCanvas.getContext('2d');
1109
+ ctx.drawImage(imageElement, 0, 0, width, height);
1110
+
1111
+ // Create a ZXing HTMLCanvasElementLuminanceSource
1112
+ const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas);
1113
+ const binaryBitmap = new ZXing.BinaryBitmap(new ZXing.HybridBinarizer(luminanceSource));
1114
+
1115
+ try {
1116
+ // Create a multi-format reader
1117
+ const reader = new ZXing.MultiFormatReader();
1118
+ reader.setHints(hints);
1119
+
1120
+ // Try to decode the image using the binary bitmap
1121
+ const decodedResult = reader.decode(binaryBitmap);
1122
+
1123
+ // Barcode detected successfully
1124
+ status.textContent = 'Barcode recognized successfully!';
1125
+
1126
+ // Format the result
1127
+ const resultText = `Code: ${decodedResult.getText()}\nFormat: ${decodedResult.getBarcodeFormat()}`;
1128
+ document.getElementById('result').textContent = resultText;
1129
+
1130
+ // Draw the barcode location on the canvas
1131
+ drawBarcodeLocation(ctx, decodedResult.getResultPoints());
1132
+
1133
+ } catch (error) {
1134
+ // Try alternative method if the first one fails
1135
+ try {
1136
+ // Use the BrowserMultiFormatReader as a fallback
1137
+ const result = await codeReader.decodeFromImageUrl(imageElement.src);
1138
+
1139
+ // Barcode detected successfully
1140
+ status.textContent = 'Barcode recognized successfully!';
1141
+
1142
+ // Format the result
1143
+ const resultText = `Code: ${result.text}\nFormat: ${result.format}`;
1144
+ document.getElementById('result').textContent = resultText;
1145
+
1146
+ // Draw the barcode location on the canvas if available
1147
+ if (result.resultPoints && result.resultPoints.length > 0) {
1148
+ drawBarcodeLocation(ctx, result.resultPoints);
1149
+ }
1150
+
1151
+ } catch (secondError) {
1152
+ // Try one more approach with different binarizer
1153
+ try {
1154
+ const globalHistogramBinarizer = new ZXing.GlobalHistogramBinarizer(luminanceSource);
1155
+ const secondBinaryBitmap = new ZXing.BinaryBitmap(globalHistogramBinarizer);
1156
+
1157
+ const reader = new ZXing.MultiFormatReader();
1158
+ reader.setHints(hints);
1159
+ const thirdResult = reader.decode(secondBinaryBitmap);
1160
+
1161
+ status.textContent = 'Barcode recognized successfully!';
1162
+ const resultText = `Code: ${thirdResult.getText()}\nFormat: ${thirdResult.getBarcodeFormat()}`;
1163
+ document.getElementById('result').textContent = resultText;
1164
+
1165
+ drawBarcodeLocation(ctx, thirdResult.getResultPoints());
1166
+ } catch (thirdError) {
1167
+ // No barcode detected after all attempts
1168
+ status.textContent = 'No barcode detected';
1169
+ result.textContent = 'Could not detect a valid barcode in the image. Please try another image with a clearer barcode.';
1170
+ console.error('ZXing errors:', error, secondError, thirdError);
1171
+ }
1172
+ }
1173
+ }
1174
+ } catch (error) {
1175
+ // Handle any exceptions during processing
1176
+ status.textContent = 'Error processing image';
1177
+ result.textContent = error.message || 'An unexpected error occurred';
1178
+ console.error('Error in processImage:', error);
1179
+ }
1180
+ }
1181
+
1182
+ // Draw barcode location on canvas
1183
+ function drawBarcodeLocation(ctx, points) {
1184
+ if (!points || points.length === 0) return;
1185
+
1186
+ ctx.strokeStyle = 'rgba(255, 0, 0, 0.8)'; // Red color for detected barcodes
1187
+ ctx.lineWidth = 5;
1188
+
1189
+ ctx.beginPath();
1190
+
1191
+ // For QR codes and other 2D barcodes (usually 4 points)
1192
+ if (points.length >= 4) {
1193
+ ctx.moveTo(points[0].x, points[0].y);
1194
+ ctx.lineTo(points[1].x, points[1].y);
1195
+ ctx.lineTo(points[2].x, points[2].y);
1196
+ ctx.lineTo(points[3].x, points[3].y);
1197
+ ctx.lineTo(points[0].x, points[0].y);
1198
+ }
1199
+ // For 1D barcodes (usually 2 points)
1200
+ else if (points.length >= 2) {
1201
+ // Calculate the corners of a rectangle for the barcode
1202
+ const p1 = points[0];
1203
+ const p2 = points[points.length - 1];
1204
+
1205
+ // Calculate the width of the barcode (perpendicular to the line)
1206
+ const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
1207
+ const perpAngle = angle + Math.PI / 2;
1208
+ const width = 20; // Width of the barcode rectangle
1209
+
1210
+ // Calculate the four corners of the rectangle
1211
+ const corners = [
1212
+ { x: p1.x - width * Math.cos(perpAngle), y: p1.y - width * Math.sin(perpAngle) },
1213
+ { x: p1.x + width * Math.cos(perpAngle), y: p1.y + width * Math.sin(perpAngle) },
1214
+ { x: p2.x + width * Math.cos(perpAngle), y: p2.y + width * Math.sin(perpAngle) },
1215
+ { x: p2.x - width * Math.cos(perpAngle), y: p2.y - width * Math.sin(perpAngle) }
1216
+ ];
1217
+
1218
+ // Draw the rectangle
1219
+ ctx.moveTo(corners[0].x, corners[0].y);
1220
+ ctx.lineTo(corners[1].x, corners[1].y);
1221
+ ctx.lineTo(corners[2].x, corners[2].y);
1222
+ ctx.lineTo(corners[3].x, corners[3].y);
1223
+ ctx.lineTo(corners[0].x, corners[0].y);
1224
+ }
1225
+
1226
+ ctx.stroke();
1227
+ }
1228
+
1229
+ // Handle page visibility changes
1230
+ document.addEventListener('visibilitychange', () => {
1231
+ if (document.hidden && cameraStream) {
1232
+ // Pause scanning when page is not visible
1233
+ stopBarcodeScanning();
1234
+ } else if (!document.hidden && cameraStream && !isScanning) {
1235
+ // Resume scanning when page becomes visible again
1236
+ startBarcodeScanning();
1237
+ }
1238
+ });
1239
+
1240
+ // Toggle focus mode for better barcode scanning
1241
+ function toggleFocusMode() {
1242
+ focusModeActive = !focusModeActive;
1243
+
1244
+ const focusModeButton = document.getElementById('focus-mode');
1245
+ if (focusModeButton) {
1246
+ focusModeButton.textContent = focusModeActive ? 'Exit Focus Mode' : 'Focus Mode';
1247
+ focusModeButton.classList.toggle('active', focusModeActive);
1248
+ }
1249
+
1250
+ document.body.classList.toggle('focus-mode-active', focusModeActive);
1251
+
1252
+ if (focusModeActive) {
1253
+ status.textContent = 'Focus Mode active. Hold barcode steady in the center of the screen.';
1254
+
1255
+ // Pause and restart scanning to apply new settings
1256
+ if (isScanning) {
1257
+ stopBarcodeScanning();
1258
+ startBarcodeScanning();
1259
+ }
1260
+ } else {
1261
+ status.textContent = 'Focus Mode disabled. Camera active.';
1262
+
1263
+ // Pause and restart scanning to apply new settings
1264
+ if (isScanning) {
1265
+ stopBarcodeScanning();
1266
+ startBarcodeScanning();
1267
+ }
1268
+ }
1269
+ }
1270
+
1271
+ // Capture a still image from the camera feed
1272
+ function captureImage() {
1273
+ if (!videoElement || !cameraStream) {
1274
+ status.textContent = 'Camera not active. Cannot capture image.';
1275
+ return;
1276
+ }
1277
+
1278
+ try {
1279
+ // Temporarily pause scanning
1280
+ const wasScanning = isScanning;
1281
+ if (isScanning) {
1282
+ stopBarcodeScanning();
1283
+ }
1284
+
1285
+ // Get video dimensions
1286
+ const width = videoElement.videoWidth;
1287
+ const height = videoElement.videoHeight;
1288
+
1289
+ if (width === 0 || height === 0) {
1290
+ status.textContent = 'Cannot capture image. Video feed not ready.';
1291
+ if (wasScanning) {
1292
+ startBarcodeScanning();
1293
+ }
1294
+ return;
1295
+ }
1296
+
1297
+ // Create a temporary canvas for the capture
1298
+ const captureCanvas = document.createElement('canvas');
1299
+ captureCanvas.width = width;
1300
+ captureCanvas.height = height;
1301
+
1302
+ // Draw the current video frame to the canvas
1303
+ const ctx = captureCanvas.getContext('2d');
1304
+ ctx.drawImage(videoElement, 0, 0, width, height);
1305
+
1306
+ // Create an image element from the canvas
1307
+ const capturedImage = new Image();
1308
+ capturedImage.onload = () => {
1309
+ // Display the captured image
1310
+ preview.src = capturedImage.src;
1311
+ preview.style.display = 'block';
1312
+
1313
+ // Hide camera feed temporarily
1314
+ cameraContainer.style.display = 'none';
1315
+
1316
+ // Process the captured image
1317
+ status.textContent = 'Processing captured image...';
1318
+ processImage(capturedImage, true);
1319
+ };
1320
+
1321
+ // Convert canvas to data URL and set as image source
1322
+ capturedImage.src = captureCanvas.toDataURL('image/png');
1323
+
1324
+ // Show a notification
1325
+ status.textContent = 'Image captured from camera. Processing...';
1326
+
1327
+ } catch (error) {
1328
+ console.error('Error capturing image:', error);
1329
+ status.textContent = 'Error capturing image: ' + (error.message || 'Unknown error');
1330
+
1331
+ // Resume scanning if it was active
1332
+ if (wasScanning) {
1333
+ startBarcodeScanning();
1334
+ }
1335
+ }
1336
+ }
1337
+
1338
+ // Process image with ZXing
1339
+ async function processImage(imageElement, fromCamera = false) {
1340
+ status.textContent = 'Processing image...';
1341
+ result.textContent = '';
1342
+
1343
+ try {
1344
+ // Get image dimensions
1345
+ const width = imageElement.naturalWidth;
1346
+ const height = imageElement.naturalHeight;
1347
+
1348
+ // Set canvas dimensions to match image
1349
+ scanCanvas.width = width;
1350
+ scanCanvas.height = height;
1351
+
1352
+ // Draw image on canvas
1353
+ const ctx = scanCanvas.getContext('2d');
1354
+ ctx.drawImage(imageElement, 0, 0, width, height);
1355
+
1356
+ // Apply image enhancement if enabled
1357
+ if (useImageEnhancement) {
1358
+ enhanceImage(ctx, width, height);
1359
+ if (debugMode) {
1360
+ updateDebugInfo('Image enhancement applied to captured image');
1361
+ }
1362
+ }
1363
+
1364
+ // Create a ZXing HTMLCanvasElementLuminanceSource
1365
+ const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(scanCanvas);
1366
+
1367
+ // Try different binarizers for better detection
1368
+ const binarizers = [
1369
+ new ZXing.HybridBinarizer(luminanceSource),
1370
+ new ZXing.GlobalHistogramBinarizer(luminanceSource)
1371
+ ];
1372
+
1373
+ let decodedResult = null;
1374
+ let usedBinarizer = '';
1375
+ let errorMessages = [];
1376
+
1377
+ // Try each binarizer
1378
+ for (const binarizer of binarizers) {
1379
+ if (decodedResult) break; // Stop if we already found a result
1380
+
1381
+ const binaryBitmap = new ZXing.BinaryBitmap(binarizer);
1382
+ usedBinarizer = binarizer instanceof ZXing.HybridBinarizer ? 'HybridBinarizer' : 'GlobalHistogramBinarizer';
1383
+
1384
+ if (debugMode) {
1385
+ updateDebugInfo(`Trying ${usedBinarizer} on captured image`);
1386
+ }
1387
+
1388
+ try {
1389
+ // Create a multi-format reader with hints
1390
+ const reader = new ZXing.MultiFormatReader();
1391
+
1392
+ // Set up hints with all supported formats and try harder flag
1393
+ const newHints = new Map();
1394
+ newHints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);
1395
+ newHints.set(ZXing.DecodeHintType.TRY_HARDER, true);
1396
+ newHints.set(ZXing.DecodeHintType.PURE_BARCODE, false);
1397
+ newHints.set(ZXing.DecodeHintType.CHARACTER_SET, "UTF-8");
1398
+ reader.setHints(newHints);
1399
+
1400
+ // Try to decode the image using the binary bitmap
1401
+ const result = reader.decode(binaryBitmap);
1402
+
1403
+ decodedResult = {
1404
+ text: result.getText(),
1405
+ format: result.getBarcodeFormat(),
1406
+ resultPoints: result.getResultPoints()
1407
+ };
1408
+
1409
+ if (debugMode) {
1410
+ updateDebugInfo(`Success with ${usedBinarizer} on captured image`, decodedResult);
1411
+ }
1412
+
1413
+ } catch (error) {
1414
+ // Store error for debugging
1415
+ errorMessages.push(`${usedBinarizer}: ${error.message || 'Unknown error'}`);
1416
+
1417
+ if (debugMode) {
1418
+ updateDebugInfo(`Failed with ${usedBinarizer} on captured image`, { error: error.message || 'Unknown error' });
1419
+ }
1420
+ }
1421
+ }
1422
+
1423
+ // If we got a result from the direct approach
1424
+ if (decodedResult) {
1425
+ // Barcode detected successfully
1426
+ status.textContent = 'Barcode recognized successfully!';
1427
+
1428
+ // Format the result
1429
+ const resultText = `Code: ${decodedResult.text}\nFormat: ${decodedResult.format}`;
1430
+ document.getElementById('result').textContent = resultText;
1431
+
1432
+ // Draw the barcode location on the canvas
1433
+ if (decodedResult.resultPoints && decodedResult.resultPoints.length > 0) {
1434
+ drawBarcodeLocation(ctx, decodedResult.resultPoints);
1435
+ }
1436
+
1437
+ // Play success sound
1438
+ playSuccessBeep();
1439
+
1440
+ // If this was from a camera capture, show a button to return to camera
1441
+ if (fromCamera) {
1442
+ showReturnToCameraButton();
1443
+ }
1444
+
1445
+ return;
1446
+ }
1447
+
1448
+ // If direct approach failed, try BrowserMultiFormatReader as fallback
1449
+ try {
1450
+ // Use the BrowserMultiFormatReader as a fallback
1451
+ const dataUrl = scanCanvas.toDataURL('image/png');
1452
+ const result = await codeReader.decodeFromImageUrl(dataUrl);
1453
+
1454
+ // Barcode detected successfully
1455
+ status.textContent = 'Barcode recognized successfully!';
1456
+
1457
+ // Format the result
1458
+ const resultText = `Code: ${result.text}\nFormat: ${result.format}`;
1459
+ document.getElementById('result').textContent = resultText;
1460
+
1461
+ // Draw the barcode location on the canvas if available
1462
+ if (result.resultPoints && result.resultPoints.length > 0) {
1463
+ drawBarcodeLocation(ctx, result.resultPoints);
1464
+ }
1465
+
1466
+ // Play success sound
1467
+ playSuccessBeep();
1468
+
1469
+ // If this was from a camera capture, show a button to return to camera
1470
+ if (fromCamera) {
1471
+ showReturnToCameraButton();
1472
+ }
1473
+
1474
+ } catch (error) {
1475
+ // No barcode detected after all attempts
1476
+ status.textContent = 'No barcode detected';
1477
+ result.textContent = 'Could not detect a valid barcode in the image. Please try again with a clearer image.';
1478
+
1479
+ if (debugMode) {
1480
+ updateDebugInfo('Failed with all methods on captured image', {
1481
+ error: error.message || 'Unknown error',
1482
+ allErrors: errorMessages.join(', ')
1483
+ });
1484
+ }
1485
+
1486
+ // If this was from a camera capture, show a button to return to camera
1487
+ if (fromCamera) {
1488
+ showReturnToCameraButton();
1489
+ }
1490
+ }
1491
+
1492
+ } catch (error) {
1493
+ // Handle any exceptions during processing
1494
+ status.textContent = 'Error processing image';
1495
+ result.textContent = error.message || 'An unexpected error occurred';
1496
+ console.error('Error in processImage:', error);
1497
+
1498
+ // If this was from a camera capture, show a button to return to camera
1499
+ if (fromCamera) {
1500
+ showReturnToCameraButton();
1501
+ }
1502
+ }
1503
+ }
1504
+
1505
+ // Show a button to return to camera view after processing a captured image
1506
+ function showReturnToCameraButton() {
1507
+ // Check if button already exists
1508
+ let returnButton = document.getElementById('return-to-camera');
1509
+ if (returnButton) {
1510
+ returnButton.style.display = 'inline-block';
1511
+ return;
1512
+ }
1513
+
1514
+ // Create return to camera button
1515
+ returnButton = document.createElement('button');
1516
+ returnButton.id = 'return-to-camera';
1517
+ returnButton.className = 'return-to-camera-btn';
1518
+ returnButton.textContent = 'Return to Camera';
1519
+ returnButton.addEventListener('click', () => {
1520
+ // Hide the preview and show the camera
1521
+ preview.style.display = 'none';
1522
+ cameraContainer.style.display = 'block';
1523
+
1524
+ // Hide the return button
1525
+ returnButton.style.display = 'none';
1526
+
1527
+ // Restart scanning
1528
+ startBarcodeScanning();
1529
+
1530
+ status.textContent = 'Returned to camera. Point at a barcode to scan.';
1531
+ });
1532
+
1533
+ // Add to the DOM
1534
+ const resultSection = document.getElementById('result').parentNode;
1535
+ resultSection.appendChild(returnButton);
1536
+ }
1537
+
1538
+ // Initialize the app
1539
+ init();
1540
+ });
index.html CHANGED
@@ -1,19 +1,69 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Barcode Scanner</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <h1>Barcode Scanner</h1>
12
+ <div class="upload-section">
13
+ <label for="imageUpload" class="upload-label">
14
+ <span>Upload Image with Barcode</span>
15
+ <input type="file" id="imageUpload" accept="image/*">
16
+ </label>
17
+ </div>
18
+
19
+ <div class="preview-section">
20
+ <div class="image-preview">
21
+ <h2>Image Preview</h2>
22
+ <div id="scanner-container">
23
+ <img id="preview" src="#" alt="Preview" style="display: none;">
24
+ <canvas id="scanCanvas"></canvas>
25
+ </div>
26
+ </div>
27
+
28
+ <div class="result-section">
29
+ <h2>Recognition Result</h2>
30
+ <div id="status">Upload an image or use camera to start recognition</div>
31
+ <div id="result"></div>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="info-section">
36
+ <h3>How It Works</h3>
37
+ <p>This application uses ZXing (Zebra Crossing), a powerful barcode scanning library, to detect and decode barcodes from images or your device camera.</p>
38
+ <p>For best results:</p>
39
+ <ul>
40
+ <li>Use clear, well-lit images or good lighting when using camera</li>
41
+ <li>Ensure the barcode is in focus</li>
42
+ <li>Position the barcode to be as straight as possible</li>
43
+ <li>Hold the camera steady when scanning with device camera</li>
44
+ <li>Supported formats: QR Code, Data Matrix, UPC, EAN, Code 128, Code 39, ITF, and more</li>
45
+ </ul>
46
+ </div>
47
+
48
+ <div class="troubleshooting-section">
49
+ <h3>Troubleshooting</h3>
50
+ <p>If you're having trouble with barcode recognition:</p>
51
+ <ol>
52
+ <li><strong>Image Quality:</strong> Make sure your image is clear, well-lit, and the barcode is in focus.</li>
53
+ <li><strong>Camera Position:</strong> When using the camera, hold it steady and ensure good lighting.</li>
54
+ <li><strong>Barcode Type:</strong> This scanner supports both 1D barcodes (EAN, UPC, Code128, etc.) and 2D barcodes (QR Code, Data Matrix).</li>
55
+ <li><strong>Orientation:</strong> ZXing can detect barcodes in various orientations, but straight alignment may improve results.</li>
56
+ <li><strong>Contrast:</strong> Ensure there's good contrast between the barcode and background.</li>
57
+ <li><strong>Size:</strong> The barcode should be a reasonable size in the image - not too small or too large.</li>
58
+ <li><strong>Different Image:</strong> If all else fails, try a different image of the barcode.</li>
59
+ <li><strong>Camera Permissions:</strong> Make sure you've granted camera permissions if using the camera feature.</li>
60
+ </ol>
61
+ <p>Red boxes indicate detected barcode areas.</p>
62
+ </div>
63
+ </div>
64
+
65
+ <!-- Load ZXing library -->
66
+ <script src="https://unpkg.com/@zxing/library@latest"></script>
67
+ <script src="app.js"></script>
68
+ </body>
69
+ </html>
styles.css ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
6
+ }
7
+
8
+ body {
9
+ background-color: #f5f5f5;
10
+ color: #333;
11
+ line-height: 1.6;
12
+ }
13
+
14
+ .container {
15
+ max-width: 900px;
16
+ margin: 2rem auto;
17
+ padding: 2rem;
18
+ background-color: #fff;
19
+ border-radius: 10px;
20
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
21
+ }
22
+
23
+ h1 {
24
+ text-align: center;
25
+ margin-bottom: 2rem;
26
+ color: #2c3e50;
27
+ }
28
+
29
+ h2 {
30
+ color: #3498db;
31
+ margin-bottom: 1rem;
32
+ }
33
+
34
+ h3 {
35
+ color: #2c3e50;
36
+ margin-bottom: 0.8rem;
37
+ }
38
+
39
+ .upload-section {
40
+ margin-bottom: 2rem;
41
+ text-align: center;
42
+ }
43
+
44
+ .upload-label {
45
+ display: inline-block;
46
+ padding: 12px 24px;
47
+ background-color: #3498db;
48
+ color: white;
49
+ border-radius: 5px;
50
+ cursor: pointer;
51
+ transition: background-color 0.3s;
52
+ }
53
+
54
+ .upload-label:hover {
55
+ background-color: #2980b9;
56
+ }
57
+
58
+ input[type="file"] {
59
+ display: none;
60
+ }
61
+
62
+ .preview-section {
63
+ display: flex;
64
+ flex-wrap: wrap;
65
+ gap: 2rem;
66
+ margin-bottom: 2rem;
67
+ }
68
+
69
+ .image-preview, .result-section {
70
+ flex: 1;
71
+ min-width: 300px;
72
+ }
73
+
74
+ #scanner-container {
75
+ position: relative;
76
+ max-width: 100%;
77
+ margin-top: 1rem;
78
+ border-radius: 5px;
79
+ overflow: hidden;
80
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
81
+ }
82
+
83
+ #preview {
84
+ max-width: 100%;
85
+ display: block;
86
+ }
87
+
88
+ #scanCanvas {
89
+ position: absolute;
90
+ top: 0;
91
+ left: 0;
92
+ width: 100%;
93
+ height: 100%;
94
+ z-index: 10;
95
+ pointer-events: none;
96
+ }
97
+
98
+ #status {
99
+ margin-bottom: 1rem;
100
+ padding: 10px;
101
+ background-color: #f8f9fa;
102
+ border-radius: 5px;
103
+ font-style: italic;
104
+ }
105
+
106
+ #result {
107
+ padding: 15px;
108
+ background-color: #e9f7fe;
109
+ border-radius: 5px;
110
+ border-left: 4px solid #3498db;
111
+ word-break: break-all;
112
+ min-height: 50px;
113
+ white-space: pre-line;
114
+ }
115
+
116
+ .info-section, .troubleshooting-section {
117
+ margin-top: 2rem;
118
+ padding: 1.5rem;
119
+ background-color: #f8f9fa;
120
+ border-radius: 5px;
121
+ }
122
+
123
+ .info-section {
124
+ border-left: 4px solid #2c3e50;
125
+ }
126
+
127
+ .troubleshooting-section {
128
+ border-left: 4px solid #e74c3c;
129
+ margin-top: 1.5rem;
130
+ }
131
+
132
+ .info-section ul, .troubleshooting-section ol {
133
+ margin-left: 1.5rem;
134
+ margin-top: 0.5rem;
135
+ }
136
+
137
+ .info-section p, .troubleshooting-section p {
138
+ margin-bottom: 0.5rem;
139
+ }
140
+
141
+ .troubleshooting-section li {
142
+ margin-bottom: 0.5rem;
143
+ }
144
+
145
+ .troubleshooting-section strong {
146
+ color: #e74c3c;
147
+ }
148
+
149
+ /* Security warning */
150
+ .security-warning {
151
+ background-color: #fff3cd;
152
+ color: #856404;
153
+ padding: 15px;
154
+ margin-bottom: 20px;
155
+ border-radius: 5px;
156
+ border-left: 4px solid #ffc107;
157
+ font-size: 0.95rem;
158
+ line-height: 1.5;
159
+ }
160
+
161
+ .security-warning strong {
162
+ color: #e67e22;
163
+ }
164
+
165
+ /* Camera styles */
166
+ .camera-controls {
167
+ display: flex;
168
+ justify-content: center;
169
+ gap: 10px;
170
+ margin: 1rem 0;
171
+ }
172
+
173
+ .camera-controls button {
174
+ padding: 10px 20px;
175
+ background-color: #3498db;
176
+ color: white;
177
+ border: none;
178
+ border-radius: 5px;
179
+ cursor: pointer;
180
+ transition: background-color 0.3s;
181
+ font-size: 1rem;
182
+ }
183
+
184
+ .camera-controls button:hover {
185
+ background-color: #2980b9;
186
+ }
187
+
188
+ .camera-controls button:disabled {
189
+ background-color: #95a5a6;
190
+ cursor: not-allowed;
191
+ }
192
+
193
+ #stop-camera {
194
+ background-color: #e74c3c;
195
+ }
196
+
197
+ #stop-camera:hover {
198
+ background-color: #c0392b;
199
+ }
200
+
201
+ #switch-camera {
202
+ background-color: #2ecc71;
203
+ }
204
+
205
+ #switch-camera:hover {
206
+ background-color: #27ae60;
207
+ }
208
+
209
+ #focus-mode {
210
+ background-color: #9b59b6;
211
+ }
212
+
213
+ #focus-mode:hover {
214
+ background-color: #8e44ad;
215
+ }
216
+
217
+ #focus-mode.active {
218
+ background-color: #e74c3c;
219
+ }
220
+
221
+ #focus-mode.active:hover {
222
+ background-color: #c0392b;
223
+ }
224
+
225
+ #capture-image {
226
+ background-color: #f39c12;
227
+ }
228
+
229
+ #capture-image:hover {
230
+ background-color: #e67e22;
231
+ }
232
+
233
+ #retry-permission {
234
+ background-color: #f39c12;
235
+ margin-top: 10px;
236
+ }
237
+
238
+ #retry-permission:hover {
239
+ background-color: #d35400;
240
+ }
241
+
242
+ .return-to-camera-btn {
243
+ display: block;
244
+ margin: 15px auto;
245
+ padding: 10px 20px;
246
+ background-color: #3498db;
247
+ color: white;
248
+ border: none;
249
+ border-radius: 5px;
250
+ cursor: pointer;
251
+ transition: background-color 0.3s;
252
+ font-size: 1rem;
253
+ }
254
+
255
+ .return-to-camera-btn:hover {
256
+ background-color: #2980b9;
257
+ }
258
+
259
+ .camera-container {
260
+ position: relative;
261
+ width: 100%;
262
+ max-width: 640px;
263
+ margin: 0 auto 1rem;
264
+ border-radius: 5px;
265
+ overflow: hidden;
266
+ box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
267
+ }
268
+
269
+ #camera-feed {
270
+ width: 100%;
271
+ display: block;
272
+ }
273
+
274
+ /* Scanning indicator and animation */
275
+ .scan-indicator {
276
+ position: absolute;
277
+ top: 10px;
278
+ right: 10px;
279
+ width: 12px;
280
+ height: 12px;
281
+ border-radius: 50%;
282
+ background-color: #e74c3c;
283
+ box-shadow: 0 0 5px rgba(231, 76, 60, 0.7);
284
+ z-index: 20;
285
+ }
286
+
287
+ .scan-indicator.pulse {
288
+ animation: pulse 0.2s ease-out;
289
+ }
290
+
291
+ @keyframes pulse {
292
+ 0% {
293
+ transform: scale(1);
294
+ opacity: 1;
295
+ }
296
+ 50% {
297
+ transform: scale(1.5);
298
+ opacity: 0.7;
299
+ }
300
+ 100% {
301
+ transform: scale(1);
302
+ opacity: 1;
303
+ }
304
+ }
305
+
306
+ /* Scanning animation */
307
+ .camera-container::after {
308
+ content: '';
309
+ position: absolute;
310
+ top: 0;
311
+ left: 0;
312
+ right: 0;
313
+ height: 3px;
314
+ background: linear-gradient(to right, transparent, #3498db, transparent);
315
+ animation: scanner-animation 2s linear infinite;
316
+ z-index: 15;
317
+ opacity: 0.7;
318
+ }
319
+
320
+ @keyframes scanner-animation {
321
+ 0% {
322
+ top: 0;
323
+ }
324
+ 50% {
325
+ top: 100%;
326
+ }
327
+ 50.1% {
328
+ top: 0;
329
+ }
330
+ 100% {
331
+ top: 100%;
332
+ }
333
+ }
334
+
335
+ /* Enhancement toggle */
336
+ .enhancement-toggle-container {
337
+ display: flex;
338
+ align-items: center;
339
+ margin-top: 10px;
340
+ padding: 8px 12px;
341
+ background-color: #f8f9fa;
342
+ border-radius: 5px;
343
+ border-left: 3px solid #3498db;
344
+ }
345
+
346
+ #enhancement-toggle {
347
+ margin-right: 10px;
348
+ width: 18px;
349
+ height: 18px;
350
+ cursor: pointer;
351
+ }
352
+
353
+ .enhancement-toggle-container label {
354
+ font-size: 0.9rem;
355
+ cursor: pointer;
356
+ }
357
+
358
+ /* Debug mode */
359
+ .debug-toggle-container {
360
+ background-color: #f8f9fa;
361
+ border-left: 3px solid #e74c3c;
362
+ }
363
+
364
+ .debug-info {
365
+ margin-top: 15px;
366
+ padding: 10px;
367
+ background-color: #2c3e50;
368
+ color: #ecf0f1;
369
+ border-radius: 5px;
370
+ font-family: monospace;
371
+ font-size: 0.85rem;
372
+ max-height: 300px;
373
+ overflow-y: auto;
374
+ }
375
+
376
+ .debug-entry {
377
+ padding: 5px 0;
378
+ border-bottom: 1px solid #34495e;
379
+ }
380
+
381
+ .debug-entry:first-child {
382
+ border-top: 1px solid #34495e;
383
+ }
384
+
385
+ .debug-entry strong {
386
+ color: #3498db;
387
+ }
388
+
389
+ .debug-entry ul {
390
+ margin: 5px 0 5px 20px;
391
+ padding: 0;
392
+ list-style-type: none;
393
+ }
394
+
395
+ .debug-entry li {
396
+ margin: 2px 0;
397
+ }
398
+
399
+ /* Focus mode styles */
400
+ .focus-mode-active .camera-container {
401
+ position: fixed;
402
+ top: 0;
403
+ left: 0;
404
+ width: 100%;
405
+ height: 100%;
406
+ max-width: none;
407
+ margin: 0;
408
+ z-index: 1000;
409
+ border-radius: 0;
410
+ }
411
+
412
+ .focus-mode-active #camera-feed {
413
+ width: 100%;
414
+ height: 100%;
415
+ object-fit: cover;
416
+ }
417
+
418
+ .focus-mode-active .camera-controls {
419
+ position: fixed;
420
+ bottom: 20px;
421
+ left: 0;
422
+ right: 0;
423
+ z-index: 1001;
424
+ background-color: rgba(0, 0, 0, 0.5);
425
+ padding: 10px;
426
+ border-radius: 0;
427
+ }
428
+
429
+ .focus-mode-active .enhancement-toggle-container,
430
+ .focus-mode-active .debug-toggle-container {
431
+ display: none;
432
+ }
433
+
434
+ .focus-mode-active #status {
435
+ position: fixed;
436
+ top: 20px;
437
+ left: 0;
438
+ right: 0;
439
+ z-index: 1001;
440
+ background-color: rgba(0, 0, 0, 0.5);
441
+ color: white;
442
+ border-radius: 0;
443
+ text-align: center;
444
+ margin: 0 auto;
445
+ max-width: 80%;
446
+ }
447
+
448
+ .focus-mode-active .scan-indicator {
449
+ width: 16px;
450
+ height: 16px;
451
+ }
452
+
453
+ .focus-mode-active .scan-indicator.pulse {
454
+ animation: focus-pulse 0.2s ease-out;
455
+ }
456
+
457
+ @keyframes focus-pulse {
458
+ 0% {
459
+ transform: scale(1);
460
+ opacity: 1;
461
+ }
462
+ 50% {
463
+ transform: scale(2);
464
+ opacity: 0.7;
465
+ }
466
+ 100% {
467
+ transform: scale(1);
468
+ opacity: 1;
469
+ }
470
+ }
471
+
472
+ @media (max-width: 768px) {
473
+ .container {
474
+ padding: 1rem;
475
+ margin: 1rem;
476
+ }
477
+
478
+ .preview-section {
479
+ flex-direction: column;
480
+ }
481
+
482
+ .camera-controls {
483
+ flex-direction: column;
484
+ align-items: center;
485
+ }
486
+
487
+ .camera-controls button {
488
+ width: 100%;
489
+ max-width: 300px;
490
+ }
491
+ }