How to Build Windows Virtual Scanner and Feed Custom Images

When building a TWAIN-compatible application and there is no physical scanner available, you can use a virtual scanner application for development and testing. TWAIN working group has released a TWAIN DS sample on GitHub. In this article, I will detail how to set up the development environment, as well as how to substitute the default TWAIN logo with custom images.

Document Acquisition Process

Before getting started, let’s take a glimpse of the workflow of the document acquisition process.

TWAIN data source

We are going to do something in the source layer.

Pre-requisites

How to Build and Debug the TWAIN DS Sample

Build

  1. Get the source code:

     git clone https://github.com/twain/twain-samples.git
    
  2. Install Qt and add system variable QTDIR: msvc2017 (x86) or msvc2017_64 (x64).

    Qt system variable

  3. If you have installed multiple Qt kits, such as MinGW, ARM64, MSVC, you have to move MSVC ahead of others in PATH to avoid deploying incompatible DLL files with windeployqt.exe

  4. Start Visual Studio as administrator because the output directory is C:\Windows\twain_32\sample2\. Open the project in Visual Studio and update linking options according to your OS environment.
  5. Build the project to generate a TWAINDS_Sample32.ds file, which is a dynamic library.

Debug

  1. Run twacker (32-bit/64-bit) application.
  2. In Visual Studio, set a breakpoint in DS_Entry() and then go to Debug > Attach to Process to find the twacker process.

    attach TWAIN DS to twacker

  3. Click the Attach button and then select the virtual scanner to trigger debugging.

    Debug TWAIN virtual scanner

Feeding Windows Virtual Scanner with Custom Images

I am tired of using the default TWAIN logo as the scanned image, so I am going to modify the source code of the TWAIN DS sample to load custom images.

The DS_Entry() function is the entry point of the TWAIN data source in CTWAINDS_Sample1.cpp. We can set a breakpoint in this function to see the stack calls.

After debugging the code, I found an appropriate place for the custom image loading code is in the constructor of CScanner_FreeImage class.

When triggering the acquire image event, the TWAINDS_Sample32.ds file will be reloaded to the memory, which means all variables will be reset. Therefore, we use a JSON file to read and write the current image index in order to load different images by clicking the Acquire Image button.

Note: the C:\Windows\twain_32\sample2\ directory is not writable without administrator privileges. Thus, we create a read-only file source.json that contains the image set folder and put an info.json file in the image set folder to store the current image index and max image count for ADF. For example:

source.json

{
    "folder": "C:/Users/admin/Pictures/barcode"
}

info.json

{
    "index": 0,
    "maxcount": 10
}

The code is as follows:

#include "json.hpp"
using json = nlohmann::json;

#include <filesystem>
#include <fstream>
namespace fs = std::experimental::filesystem;

CScanner_FreeImage::CScanner_FreeImage()
{
    ...

    char sourceConfig[PATH_MAX];
    SSNPRINTF(sourceConfig, sizeof(sourceConfig), PATH_MAX, "%s%csource.json", szTWAIN_DS_DIR, PATH_SEPERATOR);
    vector<string> images;

    if (FILE_EXISTS(sourceConfig))
    {
        // Read the image folder from source.json
        ifstream stream(sourceConfig);
        json  source;
        stream >> source;
        stream.close();

        string imageFolder = source["folder"];

        if (FILE_EXISTS(imageFolder.c_str()))
        { 
            // Get the image index
            string infoPath = imageFolder + PATH_SEPERATOR + "info.json";
            ifstream infoStream(infoPath);
            json info;
            infoStream >> info;
            infoStream.close();

            int index = info["index"];

            for (const auto& entry : fs::directory_iterator(imageFolder))
            {
                std::string path{ entry.path().u8string() };
                string suffix = path.substr(path.length() - 4, 4);
                // Get JPEG or PNG files
                if (!suffix.compare(".jpg") || !suffix.compare(".png"))
                {
                    images.push_back(path);
                }
            }

            if (images.size() > 0)
            {
                if (index >= images.size()) index = 0;

                // Set a custom image
                SSNPRINTF(m_szSourceImagePath, sizeof(m_szSourceImagePath), PATH_MAX, images[index].c_str());

                // Save image index to info.json
                index += 1;
                info["index"] = index;
                std::ofstream stream(infoPath);
                stream << info << std::endl;
                stream.close();
            }
        }
    }

    // If there's no config file for custom image set, use the default image
    if (images.size() == 0)
    {
        SSNPRINTF(m_szSourceImagePath, sizeof(m_szSourceImagePath), PATH_MAX, "%s%cTWAIN_logo.png", szTWAIN_DS_DIR, PATH_SEPERATOR);
    }
    ...
}

You can download json.hpp from https://github.com/nlohmann/json.

Now we can re-build the project and test it with Dynamic Web TWAIN online demo.

Before

scan images from default virtual scanner

After

scan images from modified virtual scanner

Source Code

https://github.com/yushulx/windows-virtual-scanner

Search Blog Posts