Decision Framework
Answer these questions to narrow down which approach fits your project. Your recommendation updates as you answer.
Approach Comparison
| Criterion | Physical Scanner TWAIN / WIA / SANE |
Camera-Based Mobile / Overhead |
Network / Remote REST API |
|---|---|---|---|
| Best for | High-volume, ADF-fed stacks | Bound books, fragile docs, mobile | Shared-office, multi-user access |
| Throughput | 20–200 ppm with duplex | ~5–15 pages/min (manual) | Depends on scanner |
| Image quality | Consistent 200–600 DPI | Variable (lighting/angle) | Same as physical scanner |
| Document types | Loose sheets only (ADF) | Any — bound, fragile, odd sizes | Loose sheets (scanner-dependent) |
| Client setup | TWAIN driver on each PC | Browser only | Browser only (REST endpoint) |
| Offline support | Yes (local driver) | Yes (browser-side) | No (needs network) |
| Multi-user | One user per scanner | One device, one user | Many users share one scanner |
| SDK | Dynamic Web TWAIN | Mobile Web Capture | DWT Service REST API |
Try Each Approach
All three demos share the same Dynamsoft license. A 30-day public trial key is pre-filled; replace it with your own if you have one.
Scanned pages will appear here
Make sure the Dynamsoft Service is installed and running.Camera preview & captures will appear here
Click "Start Camera" to begin, then "Capture Document" to grab the current frame.API Endpoints Being Called (v19+)
GET {host}/api/device/scanners — List available scanners
POST {host}/api/device/scanners/jobs — Create a scan job (auto)
Header: DWT-PRODUCT-KEY: {your-license}
GET {host}/api/device/scanners/jobs/{id}/next-page?type=image/png — Get page image
DELETE {host}/api/device/scanners/jobs/{id} — Delete job
Fetched Pages
Scanned page images will appear here
Click "Discover Scanners" → select a scanner → "Scan & Fetch Images".Raw API Log
{
"message": "Click 'Discover Scanners' to begin.
The DWT Service must be running on the host machine."
}
Code Examples
1. TWAIN Scanner via Dynamic Web TWAIN JavaScript
// Initialize and scan with ADF + duplex (DWT v19+)
Dynamsoft.DWT.ProductKey = "YOUR-LICENSE-KEY";
Dynamsoft.DWT.ResourcesPath = "https://cdn.jsdelivr.net/npm/dwt@19.4.1/dist";
Dynamsoft.DWT.AutoLoad = false;
Dynamsoft.DWT.CreateDWTObjectEx(
{ WebTwainId: "dwtScanner" },
function (obj) {
// Headless mode — bind viewer to your own container
obj.Viewer.bind("dwt-container");
obj.Viewer.show();
obj.GetDevicesAsync().then(function (devices) {
return obj.SelectDeviceAsync(devices[0]);
}).then(function () {
return obj.AcquireImageAsync({
IfShowUI: false,
PixelType: 2, Resolution: 300,
IfFeederEnabled: true,
IfDuplexEnabled: true,
IfCloseSourceAfterAcquire: true,
});
}).then(function () {
// Get a URL for the latest scanned image
var lastIndex = obj.HowManyImagesInBuffer - 1;
var url = obj.GetImageURL(lastIndex);
document.getElementById("preview").src = url;
});
},
function (error) { console.error(error.code, error.message); }
);
2. Camera Capture with DCV Bundle JavaScript
// Initialize license + camera (getUserMedia) + document detection
await Dynamsoft.License.LicenseManager.initLicense("YOUR-KEY", true);
await Dynamsoft.Core.CoreModule.loadWasm(["DDN"]);
const cvr = await Dynamsoft.CVR.CaptureVisionRouter.createInstance();
// Open camera via native getUserMedia
const stream = await navigator.mediaDevices.getUserMedia({
video: { width: { ideal: 1920 }, height: { ideal: 1080 } }
});
videoElement.srcObject = stream;
// Detection loop using requestAnimationFrame
async function scanLoop() {
const result = await cvr.capture(videoElement, "DetectDocumentBoundaries_Default");
const quad = result.items?.[0]?.location?.points; // detected boundary
if (quad && isStable(quad, previousQuad)) {
// Normalize: correct perspective and crop
const settings = await cvr.getSimplifiedSettings("NormalizeDocument_Default");
settings.roi.points = quad;
settings.roiMeasuredInPercentage = 0;
await cvr.updateSettings("NormalizeDocument_Default", settings);
// Capture current frame and normalize
const frameCanvas = captureFrame(videoElement);
const normResult = await cvr.capture(frameCanvas, "NormalizeDocument_Default");
const canvas = normResult.items[0].toCanvas(); // final document
}
requestAnimationFrame(scanLoop);
}
3. Remote Scanner via REST API JavaScript
// List scanners on a remote machine running DWT Service (v19+)
const host = "http://scanner-host:18622/api";
const scanners = await (await fetch(host + "/device/scanners")).json();
// Populate scanner dropdown for user selection
scanners.forEach(s => addOption(s.name));
// When user clicks "Scan": create job + fetch images in one flow
const scanner = scanners[selectedIdx];
const job = await (await fetch(host + "/device/scanners/jobs", {
method: "POST",
headers: {
"Content-Type": "application/json",
"DWT-PRODUCT-KEY": "YOUR-LICENSE-KEY"
},
body: JSON.stringify({
autoRun: true,
device: scanner.device,
name: scanner.name,
config: { Resolution: 300, PixelType: 2, IfFeederEnabled: true }
})
})).json();
// Fetch each scanned page image as PNG bytes
while (true) {
const imgRes = await fetch(
host + "/device/scanners/jobs/" + job.jobuid + "/next-page?type=image/png",
{ headers: { "DWT-PRODUCT-KEY": "YOUR-LICENSE-KEY" } }
);
if (imgRes.status === 204) break; // no more pages
const blob = await imgRes.blob();
// display blob via URL.createObjectURL(blob)
}
// Clean up
await fetch(host + "/device/scanners/jobs/" + job.jobuid, { method: "DELETE" });
Real-World Combinations
Hospital Records
Physical scanner (ADF) for loose patient records + Camera capture for bound logbooks and oversized charts.
Physical + CameraLaw Firm e-Discovery
Remote scanning via REST API — all lawyers scan to a central repository from their browsers, with scanner locking.
REST APISchool Exam Archive
Physical scanner for answer sheets → OCR → barcode indexing → searchable PDF archive.
Physical + OCRRemote Notary
Camera-based capture on mobile for on-the-go document digitization, instant upload to DMS.
Camera