Skip to content

Commit 82e7c1d

Browse files
committed
feat: add image preview
1 parent a423abd commit 82e7c1d

File tree

8 files changed

+149
-48
lines changed

8 files changed

+149
-48
lines changed

assets/upload-image.svg

Lines changed: 14 additions & 0 deletions
Loading

css/index.css

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,24 @@ img {
238238
display: none;
239239
}
240240

241+
.scanner-container .header .upload-image-btn {
242+
width: 30px;
243+
height: 30px;
244+
cursor: pointer;
245+
display: flex;
246+
align-items: center;
247+
cursor: pointer;
248+
}
249+
250+
.scanner-container .header .upload-image-icon {
251+
width: 24px;
252+
height: 24px;
253+
}
254+
255+
.scanner-container .header .upload-image-icon:hover {
256+
opacity: 0.8;
257+
}
258+
241259
.result-container {
242260
position: absolute;
243261
width: 100%;
@@ -269,6 +287,17 @@ img {
269287
color: #aaaaaa;
270288
}
271289

290+
.result-container .scanned-image {
291+
max-height: 200px;
292+
}
293+
294+
.result-container .scanned-image img,
295+
.result-container .scanned-image canvas {
296+
object-fit: contain;
297+
width: 100%;
298+
height: 100%;
299+
}
300+
272301
.result-container .parsed-result-area {
273302
width: 100%;
274303
height: 84%;
@@ -292,7 +321,7 @@ img {
292321
font-family: monospace;
293322
}
294323

295-
.result-container .restart-video {
324+
.result-container .scan-again {
296325
width: 100%;
297326
height: 10%;
298327
min-height: 60px;
@@ -304,7 +333,7 @@ img {
304333
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
305334
}
306335

307-
.result-container .restart-video .btn-restart-video {
336+
.result-container .scan-again .btn-scan-again {
308337
width: 160px;
309338
height: 60%;
310339
border: 0;

index.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ <h3 style="font-weight: normal">Quick Start Options</h3>
7979
<img class="up" src="./assets/arrow-up 1.svg" alt="up" />
8080
</div>
8181
<div class="camera-list"></div>
82+
<input type="file" class="upload-image-input" style="display: none" />
83+
<div class="upload-image-btn" onclick="document.querySelector('.upload-image-input').click()">
84+
<img class="upload-image-icon" src="./assets/upload-image.svg" alt="upload-image" />
85+
</div>
8286
<div class="music-container">
8387
<img class="music" src="./assets/music-selected.svg" alt="music" />
8488
<img class="no-music" src="./assets/music-unselected.svg" alt="no-music" />
@@ -195,8 +199,8 @@ <h3 style="font-weight: normal">Quick Start Options</h3>
195199
right: 0;
196200
bottom: 0;
197201
margin: auto;
198-
width: 40%;
199-
height: 40%;
202+
width: 20%;
203+
height: 20%;
200204
fill: #aaa;
201205
animation: 1s linear infinite dce-rotate;
202206
"
@@ -389,9 +393,10 @@ <h3 style="font-weight: normal">Quick Start Options</h3>
389393
</svg>
390394
</div>
391395
</div>
396+
<div class="scanned-image"></div>
392397
<div class="parsed-result-area"></div>
393-
<div class="restart-video">
394-
<button class="btn-restart-video">Restart Video</button>
398+
<div class="scan-again">
399+
<button class="btn-scan-again">Scan Again</button>
395400
</div>
396401
</div>
397402
<div class="scan-mode-container">

js/const.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const homePage = document.querySelector(".home-page");
2828

2929
const cameraViewContainer = document.querySelector(".camera-view-container");
3030

31+
const uploadImageInput = document.querySelector(".upload-image-input");
32+
3133
const cameraListContainer = document.querySelector(".camera-list");
3234
const cameraSelector = document.querySelector(".camera-selector");
3335

@@ -39,14 +41,15 @@ const scannerContainer = document.querySelector(".scanner-container");
3941
const mrzGuideFrame = document.querySelector(".mrz-frame");
4042

4143
const resultContainer = document.querySelector(".result-container");
44+
const scannedImage = document.querySelector(".scanned-image");
4245
const parsedResultArea = document.querySelector(".parsed-result-area");
4346

4447
const startScaningBtn = document.querySelectorAll(".start-btn");
4548

4649
const scanModeContainer = document.querySelector(".scan-mode-container");
4750
const scanBothBtn = document.querySelector("#scan-both-btn");
4851

49-
const restartVideoBtn = document.querySelector(".btn-restart-video");
52+
const scanAgainBtn = document.querySelector(".btn-scan-again");
5053

5154
const playSoundBtn = document.querySelector(".music");
5255
const closeSoundBtn = document.querySelector(".no-music");

js/index.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { init, pDataLoad } from "./init.js";
1+
import { handleCapturedResult, init, pDataLoad } from "./init.js";
22
import { judgeCurResolution, shouldShowScanModeContainer, showNotification } from "./util.js";
33
import { checkOrientation, getVisibleRegionOfVideo } from "./util.js";
44

@@ -139,7 +139,29 @@ const restartVideo = async () => {
139139
resultContainer.style.display = "none";
140140
document.querySelector(`#scan-${currentMode}-btn`).click();
141141
};
142-
restartVideoBtn.addEventListener("click", restartVideo);
142+
scanAgainBtn.addEventListener("click", restartVideo);
143+
144+
uploadImageInput.addEventListener("change", async (event) => {
145+
try {
146+
const file = event.target.files[0];
147+
148+
if (file) {
149+
// Open the camera after the model and .wasm files have loaded
150+
pInit = pInit || (await init);
151+
await pDataLoad.promise;
152+
153+
event.target.value = "";
154+
155+
// Decode selected image with 'both' template.
156+
const result = await cvRouter.capture(file, SCAN_TEMPLATES.both);
157+
handleCapturedResult(result, file);
158+
}
159+
} catch (ex) {
160+
let errMsg = ex.message || ex;
161+
alert(errMsg);
162+
console.error(errMsg);
163+
}
164+
});
143165

144166
cameraSelector.addEventListener("click", (e) => {
145167
e.stopPropagation();

js/init.js

Lines changed: 62 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -98,42 +98,70 @@ let init = (async () => {
9898

9999
/* Defines the result receiver for the solution.*/
100100
const resultReceiver = new Dynamsoft.CVR.CapturedResultReceiver();
101-
resultReceiver.onCapturedResultReceived = (result) => {
102-
const recognizedResults = result.textLineResultItems;
103-
const parsedResults = result.parsedResultItems;
104-
105-
if (recognizedResults?.length) {
106-
// Play sound feedback if enabled
107-
isSoundOn ? Dynamsoft.DCE.Feedback.beep() : null;
108-
109-
parsedResultArea.innerText = "";
110-
111-
// Add MRZ Text to Result
112-
const mrzElement = resultToHTMLElement("MRZ String", formatMRZ(recognizedResults[0]?.text));
113-
mrzElement.classList.add("code");
114-
parsedResultArea.appendChild(mrzElement);
115-
116-
// If a parsed result is obtained, use it to render the result page
117-
if (parsedResults) {
118-
const parseResultInfo = extractDocumentFields(parsedResults[0]);
119-
Object.entries(parseResultInfo).map(([field, value]) => {
120-
const resultElement = resultToHTMLElement(field, value);
121-
parsedResultArea.appendChild(resultElement);
122-
});
123-
} else {
124-
alert(`Failed to parse the content.`);
125-
parsedResultArea.style.justifyContent = "flex-start";
126-
}
127-
resultContainer.style.display = "flex";
128-
cameraListContainer.style.display = "none";
129-
informationListContainer.style.display = "none";
130-
scanModeContainer.style.display = "none";
101+
resultReceiver.onCapturedResultReceived = handleCapturedResult;
131102

132-
cvRouter.stopCapturing();
133-
cameraView.clearAllInnerDrawingItems();
134-
}
135-
};
136103
await cvRouter.addResultReceiver(resultReceiver);
137104
})();
138105

106+
export const handleCapturedResult = (result, uploadedImage = null) => {
107+
const recognizedResults = result.textLineResultItems;
108+
const parsedResults = result.parsedResultItems;
109+
const originalImage = result.items?.[0]?.imageData;
110+
111+
if (recognizedResults?.length) {
112+
// Play sound feedback if enabled
113+
isSoundOn ? Dynamsoft.DCE.Feedback.beep() : null;
114+
115+
parsedResultArea.innerText = "";
116+
117+
// Add MRZ Text to Result
118+
const mrzElement = resultToHTMLElement("MRZ String", formatMRZ(recognizedResults[0]?.text));
119+
mrzElement.classList.add("code");
120+
parsedResultArea.appendChild(mrzElement);
121+
122+
// If a parsed result is obtained, use it to render the result page
123+
if (parsedResults) {
124+
const parseResultInfo = extractDocumentFields(parsedResults[0]);
125+
Object.entries(parseResultInfo).map(([field, value]) => {
126+
const resultElement = resultToHTMLElement(field, value);
127+
parsedResultArea.appendChild(resultElement);
128+
});
129+
130+
if (uploadedImage && uploadedImage.type.startsWith("image/")) {
131+
handleUploadedImage(uploadedImage);
132+
} else if (originalImage) {
133+
scannedImage.innerHTML = "";
134+
scannedImage.append(originalImage.toCanvas());
135+
}
136+
} else {
137+
alert(`Failed to parse the content.`);
138+
parsedResultArea.style.justifyContent = "flex-start";
139+
}
140+
resultContainer.style.display = "flex";
141+
cameraListContainer.style.display = "none";
142+
informationListContainer.style.display = "none";
143+
scanModeContainer.style.display = "none";
144+
145+
cameraEnhancer.close();
146+
cvRouter.stopCapturing();
147+
cameraView.clearAllInnerDrawingItems();
148+
}
149+
};
150+
151+
function handleUploadedImage(file) {
152+
const img = document.createElement("img");
153+
const imageUrl = URL.createObjectURL(file);
154+
155+
img.src = imageUrl;
156+
img.className = "uploaded-image";
157+
158+
// Append the image to the div
159+
scannedImage.innerHTMl = "";
160+
scannedImage.append(img);
161+
162+
img.onload = () => {
163+
URL.revokeObjectURL(imageUrl);
164+
};
165+
}
166+
139167
export { pDataLoad, init };

js/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ export function extractDocumentFields(result) {
3333
const fullExpiryYear = `${expiryYearBase}${expiryYear}`;
3434

3535
parseResultInfo["Document Type"] = documentType;
36-
parseResultInfo["Issuing State"] = result.getFieldValue("issuingState");
3736
parseResultInfo["Surname"] = result.getFieldValue("primaryIdentifier");
3837
parseResultInfo["Given Name"] = result.getFieldValue("secondaryIdentifier");
38+
parseResultInfo["Nationality"] = result.getFieldValue("nationality");
3939
parseResultInfo["Document Number"] =
4040
type === "P" ? result.getFieldValue("passportNumber") : result.getFieldValue("documentNumber");
41-
parseResultInfo["Nationality"] = result.getFieldValue("nationality");
41+
parseResultInfo["Issuing State"] = result.getFieldValue("issuingState");
4242
parseResultInfo["Sex"] = result.getFieldValue("sex");
4343
parseResultInfo["Date of Birth (YYYY-MM-DD)"] =
4444
fullBirthYear + "-" + result.getFieldValue("birthMonth") + "-" + result.getFieldValue("birthDay");

template.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
"CaptureVisionTemplates": [
33
{
44
"Name": "ReadPassportAndId",
5-
"OutputOriginalImage": 0,
5+
"OutputOriginalImage": 1,
66
"ImageROIProcessingNameArray": ["roi-passport-and-id"],
77
"SemanticProcessingNameArray": ["sp-passport-and-id"],
88
"Timeout": 2000
99
},
1010
{
1111
"Name": "ReadPassport",
12-
"OutputOriginalImage": 0,
12+
"OutputOriginalImage": 1,
1313
"ImageROIProcessingNameArray": ["roi-passport"],
1414
"SemanticProcessingNameArray": ["sp-passport"],
1515
"Timeout": 2000
1616
},
1717
{
1818
"Name": "ReadId",
19-
"OutputOriginalImage": 0,
19+
"OutputOriginalImage": 1,
2020
"ImageROIProcessingNameArray": ["roi-id"],
2121
"SemanticProcessingNameArray": ["sp-id"],
2222
"Timeout": 2000

0 commit comments

Comments
 (0)