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:
How to Build and Debug the TWAIN DS Sample
Build
-
Get the source code:
git clone https://github.com/twain/twain-samples.git
-
Install Qt and add system variable
QTDIR
: msvc2017 (x86) or msvc2017_64 (x64). 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 withwindeployqt.exe
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.Build the project to generate a
TWAINDS_Sample32.ds
file, which is a dynamic library.
Debug
- Run twacker (32-bit/64-bit) application.
-
In Visual Studio, set a breakpoint in
DS_Entry()
and then go toDebug > Attach to Process
to find the twacker process. -
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