Creating HelloWorld - Continuous Mode
This sample demonstrates the use case to capture continuously and edit the result images before exporting.
- Check out HelloWorld - Continuous Mode online
In this sample, we would like to achieve the workflow as below.
We’ll build on this skeleton page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Mobile Web Capture - HelloWorld - Continuous Mode</title>
</head>
<body>
</body>
<script type="module">
// Write your code here
</script>
</html>
Adding the dependency
This sample is using a CDN to include the SDKs. Please refer to Adding the dependency - Use a CDN.
If you would like to host the resources files on your own server. Please refer to Adding the dependency - Host yourself.
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.10/dist/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.10/dist/license.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@2.2.10/dist/ddn.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.10/dist/cvr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.0.0/dist/ddv.js"></script>
Define necessary HTML elements
For this sample, we define below element.
- Container to hold the viewer
<div id="container"></div>
Link CSS to HTML
ddv.css
is the necessary css file which defines the viewer style of Dynamsoft Document Viewer.
index.css
defines the style of elements which is in this sample.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.0.0/dist/ddv.css">
<link rel="stylesheet" href="./index.css">
<link rel="stylesheet" href="./css/iconfont.css">
index.css
content:
html,body {
width: 100%;
height: 100%;
margin:0;
padding:0;
overscroll-behavior-y: none;
}
#container {
width: 100%;
height: 100%;
}
iconfont.css
content:
@font-face {
font-family: "iconfont"; /* Project id */
src: url('iconfont.ttf?t=1724659624433') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 22px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-perspective:before {
content: "\e6a2";
}
Related SDK initialization
//Preloads the Document Normalizer module.
Dynamsoft.Core.CoreModule.loadWasm(["DDN"]);
//Preloads the Document Viewer module.
Dynamsoft.DDV.Core.loadWasm();
// Initialize DDN
await Dynamsoft.License.LicenseManager.initLicense(
"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTEwMjQ5NjE5NyJ9",
true
);
// Initialize DDV
await Dynamsoft.DDV.Core.init();
Configure document boundaries function
-
Step one: The related configuration code is packaged in
utils.js
, so it should be imported.import { isMobile, initDocDetectModule } from "./utils.js";
-
Step two: Call the following function.
await initDocDetectModule(Dynamsoft.DDV, Dynamsoft.CVR);
utils.js
content:
export function isMobile(){
return "ontouchstart" in document.documentElement;
}
export async function initDocDetectModule(DDV, CVR) {
const router = await CVR.CaptureVisionRouter.createInstance();
class DDNNormalizeHandler extends DDV.DocumentDetect {
async detect(image, config) {
if (!router) {
return Promise.resolve({
success: false
});
};
let width = image.width;
let height = image.height;
let ratio = 1;
let data;
if (height > 720) {
ratio = height / 720;
height = 720;
width = Math.floor(width / ratio);
data = compress(image.data, image.width, image.height, width, height);
} else {
data = image.data.slice(0);
}
// Define DSImage according to the usage of DDN
const DSImage = {
bytes: new Uint8Array(data),
width,
height,
stride: width * 4, //RGBA
format: 10 // IPF_ABGR_8888
};
// Use DDN normalized module
const results = await router.capture(DSImage, 'detect-document-boundaries');
// Filter the results and generate corresponding return values
if (results.items.length <= 0) {
return Promise.resolve({
success: false
});
};
const quad = [];
results.items[0].location.points.forEach((p) => {
quad.push([p.x * ratio, p.y * ratio]);
});
const detectResult = this.processDetectResult({
location: quad,
width: image.width,
height: image.height,
config
});
return Promise.resolve(detectResult);
}
}
DDV.setProcessingHandler('documentBoundariesDetect', new DDNNormalizeHandler())
}
function compress(
imageData,
imageWidth,
imageHeight,
newWidth,
newHeight,
) {
let source = null;
try {
source = new Uint8ClampedArray(imageData);
} catch (error) {
source = new Uint8Array(imageData);
}
const scaleW = newWidth / imageWidth;
const scaleH = newHeight / imageHeight;
const targetSize = newWidth * newHeight * 4;
const targetMemory = new ArrayBuffer(targetSize);
let distData = null;
try {
distData = new Uint8ClampedArray(targetMemory, 0, targetSize);
} catch (error) {
distData = new Uint8Array(targetMemory, 0, targetSize);
}
const filter = (distCol, distRow) => {
const srcCol = Math.min(imageWidth - 1, distCol / scaleW);
const srcRow = Math.min(imageHeight - 1, distRow / scaleH);
const intCol = Math.floor(srcCol);
const intRow = Math.floor(srcRow);
let distI = (distRow * newWidth) + distCol;
let srcI = (intRow * imageWidth) + intCol;
distI *= 4;
srcI *= 4;
for (let j = 0; j <= 3; j += 1) {
distData[distI + j] = source[srcI + j];
}
};
for (let col = 0; col < newWidth; col += 1) {
for (let row = 0; row < newHeight; row += 1) {
filter(col, row);
}
}
return distData;
}
Configure image filter feature which is in edit viewer
Dynamsoft.DDV.setProcessingHandler("imageFilter", new Dynamsoft.DDV.ImageFilter());
Create a capture viewer
To capture images, we need to create a capture viewer.
- Customize the capture viewer
UiConfig
based on the default one to implement the workflow.- Bind click event to “ImagePreview” element to show the edit viewer
const newCaptureViewerUiConfig = { type: Dynamsoft.DDV.Elements.Layout, flexDirection: "column", children: [ { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-capture-viewer-header-mobile", children: [ { type: Dynamsoft.DDV.Elements.CameraResolution, className: "ddv-capture-viewer-resolution", }, Dynamsoft.DDV.Elements.Flashlight, ], }, Dynamsoft.DDV.Elements.MainView, { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-capture-viewer-footer-mobile", children: [ Dynamsoft.DDV.Elements.AutoDetect, Dynamsoft.DDV.Elements.AutoCapture, { type: Dynamsoft.DDV.Elements.Capture, className: "ddv-capture-viewer-captureButton", }, { // Bind click event to "ImagePreview" element // The event will be registered later. type: Dynamsoft.DDV.Elements.ImagePreview, events:{ click: "showEditViewer", } }, Dynamsoft.DDV.Elements.CameraConvert, ], }, ], };
- Bind click event to “ImagePreview” element to show the edit viewer
-
Create the viewer by using the new
UiConfig
.// Create a capture viewer const captureViewer = new Dynamsoft.DDV.CaptureViewer({ container: "container", uiConfig: newCaptureViewerUiConfig, // Configure the new UiConfig viewerConfig: { acceptedPolygonConfidence: 60, // Configure the accpeted confidence to 60 enableAutoCapture: true, // Enable auto capture enableAutoDetect: true, // Enable real-time detection }, }); // Play video stream in 1080P captureViewer.play({ resolution: [1920,1080], });
Create an edit viewer
To review and edit the captured images, we create an edit viewer.
- Customize the capture viewer
UiConfig
based on the default one to implement the workflow.- Add a “Back” button to header and bind click event to go back the capture viewer
const newEditViewerUiConfig = { type: Dynamsoft.DDV.Elements.Layout, flexDirection: "column", className: "ddv-edit-viewer-mobile", children: [ { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-edit-viewer-header-mobile", children: [ { // Add a "Back" button to header and bind click event to go back the capture viewer // The event will be registered later type: Dynamsoft.DDV.Elements.Button, className: "ddv-button-back", events:{ click: "backToCaptureViewer" } }, Dynamsoft.DDV.Elements.Pagination, Dynamsoft.DDV.Elements.Load, Dynamsoft.DDV.Elements.Download, ], }, Dynamsoft.DDV.Elements.MainView, { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-edit-viewer-footer-mobile", children: [ Dynamsoft.DDV.Elements.DisplayMode, Dynamsoft.DDV.Elements.RotateLeft, { type: Dynamsoft.DDV.Elements.Button, className: "iconfont icon-perspective", events:{ click: "showPerspectiveViewer" } }, Dynamsoft.DDV.Elements.Filter, Dynamsoft.DDV.Elements.Undo, Dynamsoft.DDV.Elements.Delete, Dynamsoft.DDV.Elements.AnnotationSet, ], }, ], };
- Add a “Back” button to header and bind click event to go back the capture viewer
-
Create the viewer by using the new
UiConfig
.// Create an edit viewer const editViewer = new Dynamsoft.DDV.EditViewer({ container: "container", groupUid: captureViewer.groupUid, // Data synchronisation with the capture viewer uiConfig: newEditViewerUiConfig, // Configure the new UiConfig viewerConfig: { scrollToLatest: true, // Navigate to the latest image automatically } });
-
Since this viewer only shows when clicking “ImagePreview” element in the capture viewer, it should be hidden at first.
editViewer.hide();
Create a perspective viewer
- Customize the viewer’s
UiConfig
based on the default one to implement the workflow.- Bind click event to “PerspectiveAll” button to show the edit viewer
const newPerspectiveUiConfig = { type: Dynamsoft.DDV.Elements.Layout, flexDirection: "column", children: [ { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-perspective-viewer-header-mobile", children: [ Dynamsoft.DDV.Elements.Blank, Dynamsoft.DDV.Elements.Pagination, { // Bind event for "PerspectiveAll" button to show the edit viewer // The event will be registered later. type: Dynamsoft.DDV.Elements.PerspectiveAll, events:{ click: "showEditViewer" } }, ], }, Dynamsoft.DDV.Elements.MainView, { type: Dynamsoft.DDV.Elements.Layout, className: "ddv-perspective-viewer-footer-mobile", children: [ Dynamsoft.DDV.Elements.FullQuad, Dynamsoft.DDV.Elements.RotateLeft, Dynamsoft.DDV.Elements.RotateRight, Dynamsoft.DDV.Elements.DeleteCurrent, Dynamsoft.DDV.Elements.DeleteAll, ], }, ], };
- Bind click event to “PerspectiveAll” button to show the edit viewer
-
Create the viewer by using the new
UiConfig
.// Create a perspective viewer const perspectiveViewer = new Dynamsoft.DDV.PerspectiveViewer({ container: "container", groupUid: captureViewer.groupUid, // Data synchronisation with the capture viewer uiConfig: newPerspectiveUiConfig, // Configure the new UiConfig viewerConfig:{ scrollToLatest: true, // Navigate to the latest image automatically } });
-
Since this viewer only shows when clicking “ImagePreview” element in the capture viewer, it should be hidden at first.
perspectiveViewer.hide();
Configure the workflow
Since the workflow in this sample is very simple, only the four events mentioned above need to be registered to switch the viewers.
-
Register an event in
captureViewer
to show the edit viewer.captureViewer.on("showEditViewer",() => { captureViewer.hide(); captureViewer.stop(); editViewer.show(); });
-
Register an event in
editViewer
//go back the capture viewer editViewer.on("backToCaptureViewer",() => { captureViewer.show(); editViewer.hide(); captureViewer.play(); }); //show the perspective viewer editViewer.on("showPerspectiveViewer",() => { editViewer.hide(); perspectiveViewer.show(); });
-
Register an event in
perspectiveViewer
to show the edit viewer.captureViewer.on("showEditViewer",() => { captureViewer.hide(); captureViewer.stop(); editViewer.show(); });
Review the complete code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Mobile Web Capture - HelloWorld - Continuous Mode</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.0.0/dist/ddv.css">
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div id="container"></div>
</body>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-core@3.2.10/dist/core.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-license@3.2.10/dist/license.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-normalizer@2.2.10/dist/ddn.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-capture-vision-router@2.2.10/dist/cvr.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dynamsoft-document-viewer@2.0.0/dist/ddv.js"></script>
<script type="module">
import { isMobile, initDocDetectModule } from "./utils.js";
(async () => {
//Preloads the Document Normalizer module.
Dynamsoft.Core.CoreModule.loadWasm(["DDN"]);
//Preloads the Document Viewer module.
Dynamsoft.DDV.Core.loadWasm();
// Initialize DDN
await Dynamsoft.License.LicenseManager.initLicense(
"DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTEwMjQ5NjE5NyJ9",
true
);
// Initialize DDV
await Dynamsoft.DDV.Core.init();
// Configure document boundaries function
await initDocDetectModule(Dynamsoft.DDV, Dynamsoft.CVR);
// Configure image filter feature which is in edit viewer
Dynamsoft.DDV.setProcessingHandler("imageFilter", new Dynamsoft.DDV.ImageFilter());
// Define new UiConfig for capture viewer
const newCaptureViewerUiConfig = {
type: Dynamsoft.DDV.Elements.Layout,
flexDirection: "column",
children: [
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-capture-viewer-header-mobile",
children: [
{
type: Dynamsoft.DDV.Elements.CameraResolution,
className: "ddv-capture-viewer-resolution",
},
Dynamsoft.DDV.Elements.Flashlight,
],
},
Dynamsoft.DDV.Elements.MainView,
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-capture-viewer-footer-mobile",
children: [
Dynamsoft.DDV.Elements.AutoDetect,
Dynamsoft.DDV.Elements.AutoCapture,
{
type: Dynamsoft.DDV.Elements.Capture,
className: "ddv-capture-viewer-captureButton",
},
{
// Bind click event to "ImagePreview" element
// The event will be registered later.
type: Dynamsoft.DDV.Elements.ImagePreview,
events:{
click: "showEditViewer",
}
},
Dynamsoft.DDV.Elements.CameraConvert,
],
},
],
};
// Create a capture viewer
const captureViewer = new Dynamsoft.DDV.CaptureViewer({
container: "container",
uiConfig: newCaptureViewerUiConfig, // Configure the new UiConfig
viewerConfig: {
acceptedPolygonConfidence: 60, // Configure the accpeted confidence to 60
enableAutoCapture: true, // Enable auto capture
enableAutoDetect: true, // Enable real-time detection
},
});
// Play video stream in 1080P
captureViewer.play({
resolution: [1920,1080],
});
// Define new UiConfig for edit viewer
const newEditViewerUiConfig = {
type: Dynamsoft.DDV.Elements.Layout,
flexDirection: "column",
className: "ddv-edit-viewer-mobile",
children: [
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-edit-viewer-header-mobile",
children: [
{
// Add a "Back" button to header and bind click event to go back the capture viewer
// The event will be registered later
type: Dynamsoft.DDV.Elements.Button,
className: "ddv-button-back",
events:{
click: "backToCaptureViewer"
}
},
Dynamsoft.DDV.Elements.Pagination,
Dynamsoft.DDV.Elements.Load,
Dynamsoft.DDV.Elements.Download,
],
},
Dynamsoft.DDV.Elements.MainView,
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-edit-viewer-footer-mobile",
children: [
Dynamsoft.DDV.Elements.DisplayMode,
Dynamsoft.DDV.Elements.RotateLeft,
{
type: Dynamsoft.DDV.Elements.Button,
className: "iconfont icon-perspective",
events:{
click: "showPerspectiveViewer"
}
},
Dynamsoft.DDV.Elements.Filter,
Dynamsoft.DDV.Elements.Undo,
Dynamsoft.DDV.Elements.Delete,
Dynamsoft.DDV.Elements.AnnotationSet,
],
},
],
};
// Create an edit viewer
const editViewer = new Dynamsoft.DDV.EditViewer({
container: "container",
groupUid: captureViewer.groupUid, // Data synchronisation with the capture viewer
uiConfig: newEditViewerUiConfig, // Configure the new UiConfig
viewerConfig: {
scrollToLatest: true, // Navigate to the latest image automatically
}
});
editViewer.hide();
// Define new UiConfig for perspective viewer
const newPerspectiveUiConfig = {
type: Dynamsoft.DDV.Elements.Layout,
flexDirection: "column",
children: [
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-perspective-viewer-header-mobile",
children: [
Dynamsoft.DDV.Elements.Blank,
Dynamsoft.DDV.Elements.Pagination,
{
// Bind event for "PerspectiveAll" button to show the edit viewer
// The event will be registered later.
type: Dynamsoft.DDV.Elements.PerspectiveAll,
events:{
click: "showEditViewer"
}
},
],
},
Dynamsoft.DDV.Elements.MainView,
{
type: Dynamsoft.DDV.Elements.Layout,
className: "ddv-perspective-viewer-footer-mobile",
children: [
Dynamsoft.DDV.Elements.FullQuad,
Dynamsoft.DDV.Elements.RotateLeft,
Dynamsoft.DDV.Elements.RotateRight,
Dynamsoft.DDV.Elements.DeleteCurrent,
Dynamsoft.DDV.Elements.DeleteAll,
],
},
],
};
// Create an perspective viewer
const perspectiveViewer = new Dynamsoft.DDV.PerspectiveViewer({
container: "container",
groupUid: captureViewer.groupUid, // Data sync with the capture viewer
uiConfig: newPerspectiveUiConfig,
});
perspectiveViewer.hide();
// Register an event in `captureViewer` to show the edit viewer.
captureViewer.on("showEditViewer",() => {
captureViewer.hide();
captureViewer.stop();
editViewer.show();
});
// Register an event in `editViewer` to go back the capture viewer
editViewer.on("backToCaptureViewer",() => {
captureViewer.show();
editViewer.hide();
captureViewer.play();
});
// Register an event in `editViewer` to show the perspective viewer
editViewer.on("showPerspectiveViewer",() => {
editViewer.hide();
perspectiveViewer.show();
});
// Register an event in `perspective` to show the edit viewer
perspectiveViewer.on("showEditViewer",() => {
perspectiveViewer.hide();
editViewer.show();
});
})();
</script>
</html>
Download the whole project
Please note that in order to be compatible with desktop devices as much as possible, some compatibility codes have been added to the whole project code.
UiConfig
part is organized into uiConfig.js
and referenced in the core code to minimize the length of the core code.
Add auxiliary text if necessary
Sometimes, you may want to add some auxiliary text to icons to show better user guidance.