How to Build Windows Virtual Scanner and Feed Custom Images

Xiao Ling
4 min readNov 15, 2021

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.

We are going to do something in the source layer.

Pre-requisites

  • Visual Studio 2017 or above
  • Qt 5.12.11 msvc2017
  • Test tools:

Dynamic Web TWAIN online demo

twacker

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).

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.

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

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 image index. For example:

source.json

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

info.json

{
"index": 0
}

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

After

Source Code

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

Originally published at https://www.dynamsoft.com on November 15, 2021.

--

--

Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover