Convert a Mobile Camera into a Document Scanner | Mobile Capture

Dynamic Web TWAIN has added some exciting features related to the mobile platform since its version 16.

  • Document capturing via mobile cameras, 16.0
  • PDF Rasterizer mobile edition, 16.0
  • LoadImageEx supports mobile platform, 16.1
  • Acquiring images from a remote scanner, 16.1
  • The scanDocument API which makes it easy to call a built-in document scanner to scan documents using mobile cameras in a mobile-friendly interface with features like automatic capture, document borders detection and image filter, 17.2

All of these make it possible to build a mobile document scanning and capture app in HTML5 with ease. Such a mobile document scanner can not only utilize high-resolution cameras equipped on modern mobile devices but also interact with connected scanners. Your phone can be the hub of the entire document scanning process.

Deprecation Note: the document scanning via camera feature in Dynamic Web TWAIN has been separated as Mobile Web Capture. Please use this instead.

Requirements

Dynamic Web TWAIN SDK v17.2.5

Mobile Document Scanning Implementation

Let’s implement the scanner in steps.

Preview

Our goal is to build a web document scanner that mobile devices can use. The layout should be optimized. Here is a preview of the final result:

Home Preview Save Preview

On the top is a viewer of scanned documents. Below the viewer, there is an actions tab. Since there is not much screen space for mobile devices, using a tab is a good choice if there are many actions available.

After pressing the Scan Documents button, a built-in scanner will appear. Users can take a photo, edit the result and then save it.

Scanner Cropper

A Minimum Implementation: Snapshot from MediaDevices Cameras

Here, we first create a minimum version which captures images with the camera and save them to image or PDF files. Scanned documents can later to exported to image or PDF files, as well. The entire HTML file is as below.

<!DOCTYPE html>
<html>

<head>
    <title>Dynamic Web TWAIN Mobile Sample</title>
    <script type="text/javascript" src="Resources/dynamsoft.webtwain.initiate.js"></script>
    <script type="text/javascript" src="Resources/dynamsoft.webtwain.config.js"></script>
    <script type="text/javascript" src="Resources/addon/dynamsoft.webtwain.addon.camera.js"></script>
    <script type="text/javascript" src="Resources/addon/dynamsoft.webtwain.addon.pdf.js"></script>
    <script type="text/javascript" src="common.js"></script>
    <script type="text/javascript" src="tabs.js"></script>
    <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
    <link rel="stylesheet" href="css/normalize.css">
    <link rel="stylesheet" href="css/skeleton.css">
    <link rel="stylesheet" href="css/tabs.css">
    <link rel="stylesheet" href="css/index.css">
</head>

<body>
    <div style="height:100%;width:100%;position:absolute;overflow:hidden;">
        <div id="dwtcontrolContainer" style="width:100%;height:60%"></div>
        <div id="action" style="height:40%;">
            <div class="tab" style="display:flex;overflow-x:auto;height:50px;">
                <button class="tablinks" onclick="switchTab(event, 'camera')">Camera</button>
                <button class="tablinks" onclick="switchTab(event, 'save')">Save</button>
            </div>
            <div style="height:calc(100% - 50px);overflow-y: auto;">
                <div id="camera" class="tabcontent">
                    <input id="scanDocumentButton" type="button" value="Scan Documents" onclick="ScanDocuments();" />
                </div>
                <div id="save"  class="tabcontent">
                    Default Filename: <input type="text" id="filename" value="DynamicWebTWAIN"/>
                    <input onclick="SaveWithFileDialog();" type="button" value="Save">
                    <div>
                        <label>
                            <input type="radio" value="jpg" name="ImageType" id="imgTypejpeg" />JPEG</label>
                        <label>
                            <input type="radio" value="tif" name="ImageType" id="imgTypetiff" />TIFF</label>
                        <label>
                            <input type="radio" value="pdf" name="ImageType" id="imgTypepdf" checked="checked" />PDF</label>
                    </div>
                </div>                
           </div>
        </div>
    </div>
    <script type="text/javascript">
        Dynamsoft.DWT.UseLocalService = false;
        Dynamsoft.DWT.RegisterEvent('OnWebTwainReady', Dynamsoft_OnReady); // Register OnWebTwainReady event. This event fires as soon as Dynamic Web TWAIN is initialized and ready to be used
        var DWObject;
        switchTab(null, 'camera')
        
        function Dynamsoft_OnReady() {
            DWObject = Dynamsoft.DWT.GetWebTwain('dwtcontrolContainer'); // Get the Dynamic Web TWAIN object that is embeded in the div with id 'dwtcontrolContainer'
            if (DWObject) {
                DWObject.Viewer.width="100%";
                DWObject.Viewer.height="100%";
                DWObject.SetViewMode(2, 2);
            }
        }
        

        function ScanDocuments() {
            if (DWObject) {
                let cameraContainer = document.createElement("div");
                cameraContainer.className = "fullscreen";
                document.body.appendChild(cameraContainer);
                function funcConfirmExit () {
                    cameraContainer.remove();
                    return true;
                }
                let scanOptions = {};
                scanOptions.element = cameraContainer
                scanOptions.scannerViewer = {funcConfirmExit: funcConfirmExit}
                DWObject.Addon.Camera.scanDocument(scanOptions).then(
                    function(){
                        console.log("OK");
                    }, 
                    function(error){
                        console.log(error.message);
                    }
                );
            }
        }


        //Callback functions for async APIs
        function OnSuccess() {
            console.log('successful');
        }

        function OnFailure(errorCode, errorString) {
            alert(errorString);
        }

        function SaveWithFileDialog() {
            if (DWObject) {
                if (DWObject.HowManyImagesInBuffer > 0) {
                    DWObject.IfShowFileDialog = true;
                    var filename=document.getElementById("filename").value;
                    if (document.getElementById("imgTypejpeg").checked == true) {
                        //If the current image is B&W
                        //1 is B&W, 8 is Gray, 24 is RGB
                        if (DWObject.GetImageBitDepth(DWObject.CurrentImageIndexInBuffer) == 1)
                            //If so, convert the image to Gray
                            DWObject.ConvertToGrayScale(DWObject.CurrentImageIndexInBuffer);
                        //Save image in JPEG
                        DWObject.SaveAsJPEG(filename+".jpg", DWObject.CurrentImageIndexInBuffer);
                    }
                    else if (document.getElementById("imgTypetiff").checked == true)
                        DWObject.SaveAllAsMultiPageTIFF(filename+".tiff", OnSuccess, OnFailure);
                    else if (document.getElementById("imgTypepdf").checked == true)
                        DWObject.SaveAllAsPDF(filename+".pdf", OnSuccess, OnFailure);
                }
            }
        }
    </script>
</body>
</html>

Here are some points worth mentioning:

  • We use the Skeleton CSS framework and add the viewport meta to make the page responsive and suitable for mobile devices.

      <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
      <link rel="stylesheet" href="css/normalize.css">
      <link rel="stylesheet" href="css/skeleton.css">
      <link rel="stylesheet" href="css/tabs.css">
    
  • The tabs are created according to How To Create Tabs. The tab’s style is modified so that the tab is horizontally scrollable.
  • The camera addon’s scanDocument API is used to access mobile cameras which is through MediaDevices. MediaDevices requires HTTPS. If you don’t have an HTTPS server, you can create a local server using Python 3 following this post.
  • You need to copy the Resources folder from Dynamsoft\Dynamic Web TWAIN SDK <version>\Resources to the project’s root directory.

Build the Muscle: Add More Inputs

Now we can add new functions based on the minimum implementation. Basically, there are three input sources: scanner, camera, local files.

Enable Loading Local Files

Loading local files is very simple with the LoadImageEx method. It can load images as well as PDFs.

JavaScript:

//Callback functions for async APIs
function OnSuccess() {
    console.log('successful');
}

function OnFailure(errorCode, errorString) {
    alert(errorString);
}

function LoadLocal(){
    DWObject.IfShowFileDialog = true;
    // PDF Rasterizer Addon is used here to ensure PDF support
    DWObject.Addon.PDF.SetResolution(200);
    DWObject.Addon.PDF.SetConvertMode(Dynamsoft.EnumDWT_ConvertMode.CM_RENDERALL);
    DWObject.LoadImageEx("", Dynamsoft.EnumDWT_ImageType.IT_ALL, OnSuccess, OnFailure);
}

HTML:

<button class="tablinks" onclick="switchTab(event, 'local')">Local</button>
......
<div id="local" class="tabcontent">            
    <input onclick="LoadLocal();" type="button" value="Load Images">                
</div>

Interact with Scanners

Since mobile devices normally cannot connect to scanners physically, we can only use the RemoteScan capability. How to Build a Universal Document Scanning App in HTML5 explains how to do this in detail.

Build the Muscle: Image Editing

We may also need to remove, reorder and edit images. Dynamic Web TWAIN provides rich APIs to do this.

It also has a powerful image editor which can adapt to mobile devices. Functions like rotation, deskewing, flipping, mirroring, cropping and cutting are supported.

Image Editor

JavaScript:

function ShowImageEditor(){
    var editorSettings = {
        /*element: document.getElementById("imageEditor"),
        width: 600,
        height: 400,*/
        border: '1px solid rgb(204, 204, 204);',
        topMenuBorder: '',
        innerBorder: '',
        background: "rgb(255, 255, 255)",
        promptToSaveChange: true
    };
    var imageEditor = DWObject.Viewer.createImageEditor(editorSettings);
    imageEditor.show();
}

HTML:

<button class="tablinks" onclick="switchTab(event, 'edit')">Edit</button>
......
<div id="edit" class="tabcontent">
    <input onclick="ShowImageEditor()" type="button" value="Show Editor" />
</div>

Source Code

Get the source code and have a try! The final version can run on both desktop and mobile.

https://github.com/Dynamsoft/mobile-document-scanning