A virtual scanner is a software-based solution that mimics the functionality of a physical document scanner. It allows developers and testers to simulate scanning operations without requiring actual hardware, making it an essential tool for testing and development. In this tutorial, we'll explore how to create a virtual scanner device for Linux using the SANE (Scanner Access Now Easy) framework. By the end of this guide, you'll have a fully functional virtual scanner capable of loading custom images, which can be used to test applications like the Dynamic Web TWAIN online demo.
Linux Virtual Scanner Demo Video
Prerequisites
Before getting started, install the necessary packages by running the following command:
sudo apt install build-essential autoconf libtool libsane-dev sane sane-utils imagemagick libmagickcore-dev
The Prebuilt Virtual Scanner Provided by SANE
The SANE framework includes a prebuilt backend designed for testing purposes. To use it, follow these steps:
-
Uncomment the line
#test
in the/etc/sane.d/dll.conf
file and save the changes. -
Run the following command to list all available scanners:
$ scanimage -L device `test:0' is a Noname frontend-tester virtual device device `test:1' is a Noname frontend-tester virtual device
-
Use the following command to simulate a scan and generate a black image:
$ scanimage -d test > test.pnm
To explore additional capabilities of the virtual scanner, you can view all available options by running:
scanimage -d test --help
For example, you can change the test picture to a color image with the following command:
$ scanimage -d test --test-picture Color --mode Color > color.pnm
The test picture supports the following patterns:
- Solid black: Fills the entire scan area with black.
- Solid white: Fills the entire scan area with white.
- Color pattern: Generates various color test patterns based on the selected mode.
- Grid: Draws a black-and-white grid with squares measuring 10 mm in width and height.
Feeding Custom Images
To feed a custom image into the virtual scanner, you have two options:
- Modify the test.c file and rebuild the SANE backend.
- Create a simple custom backend from scratch.
In this tutorial, we'll focus on the second approach: building a custom backend.
Preparing a Custom Image
PNM is frequently used as the default output format by many SANE frontends (such as scanimage
). If you have a JPEG image, you can convert it to PNM using ImageMagick
:
convert test.jpg test.pnm
Steps to Create a Simple Virtual Scanner for Linux
The simple Linux virtual scanner supports two key features: simulating a document scanner and feeding custom images. No additional settings are supported in this implementation.
Step 1: Create a New Backend in C
Create a new C file named custom_scanner.c
. This file will contain functions that are invoked by SANE frontends. The function names must adhere to the SANE naming convention: sane_<backend_name>_<function_name>
. In this case, the backend name is custom_scanner
.
SANE_Status sane_custom_scanner_init(SANE_Int *version_code, SANE_Auth_Callback authorize)
SANE_Status sane_custom_scanner_get_devices(const SANE_Device ***devices, SANE_Bool local_only)
SANE_Status sane_custom_scanner_open(SANE_String_Const name, SANE_Handle *handle)
void sane_custom_scanner_close(SANE_Handle handle)
SANE_Status sane_custom_scanner_control_option(SANE_Handle handle, SANE_Int option, SANE_Action action, void *value, SANE_Int *info)
const SANE_Option_Descriptor *sane_custom_scanner_get_option_descriptor(SANE_Handle handle, SANE_Int option)
SANE_Status sane_custom_scanner_get_parameters(SANE_Handle handle, SANE_Parameters *params)
SANE_Status sane_custom_scanner_start(SANE_Handle handle)
SANE_Status sane_custom_scanner_read(SANE_Handle handle, SANE_Byte *data, SANE_Int max_length, SANE_Int *length)
void sane_custom_scanner_cancel(SANE_Handle handle)
void sane_custom_scanner_exit()
Creating a Mock Device
We can define a static mock device to represent the virtual scanner. This device information will be returned when the frontend requests available devices.
static SANE_Device mock_device = {
.name = "my-scanner",
.vendor = "Custom",
.model = "Virtual Scanner",
.type = "Test"};
SANE_Status sane_custom_scanner_get_devices(const SANE_Device ***devices, SANE_Bool local_only)
{
static const SANE_Device *devs[] = {&mock_device, NULL};
*devices = devs;
return SANE_STATUS_GOOD;
}
SANE_Status sane_custom_scanner_open(SANE_String_Const name, SANE_Handle *handle)
{
*handle = (SANE_Handle)&mock_device;
return SANE_STATUS_GOOD;
}
Initializing SANE Options
The virtual scanner supports only one option: the number of options. The frontend can query the number of options and retrieve the corresponding option descriptor.
#define OPTION_NUM 0
static const SANE_Option_Descriptor options[] = {
{.name = SANE_TITLE_NUM_OPTIONS,
.title = "Number of options",
.type = SANE_TYPE_INT,
.size = sizeof(SANE_Int),
.cap = SANE_CAP_SOFT_DETECT,
.constraint_type = SANE_CONSTRAINT_NONE}};
SANE_Status sane_custom_scanner_control_option(SANE_Handle handle, SANE_Int option, SANE_Action action, void *value, SANE_Int *info)
{
if (option == 0 && action == SANE_ACTION_GET_VALUE)
{
*(SANE_Int *)value = OPTION_NUM;
return SANE_STATUS_GOOD;
}
return SANE_STATUS_UNSUPPORTED;
}
const SANE_Option_Descriptor *sane_custom_scanner_get_option_descriptor(SANE_Handle handle, SANE_Int option)
{
if (option == 0)
return &options[0];
return NULL;
}
Generating Custom Images
The virtual scanner reads a custom image file and returns the image data to the frontend:
#define CUSTOM_IMAGE_PATH "/home/xiao/Desktop/dwt/test.pnm"
SANE_Status sane_custom_scanner_get_parameters(SANE_Handle handle, SANE_Parameters *params)
{
if (params)
{
FILE *fp = fopen(CUSTOM_IMAGE_PATH, "rb");
if (fp)
{
char magic[3];
int width, height, maxval;
fscanf(fp, "%2s", magic);
char ch;
while ((ch = fgetc(fp)) == '#')
{
while (fgetc(fp) != '\n')
;
}
ungetc(ch, fp);
fscanf(fp, "%d %d %d", &width, &height, &maxval);
fclose(fp);
params->pixels_per_line = width;
params->lines = height;
if (strcmp(magic, "P6") == 0)
{
params->format = SANE_FRAME_RGB;
params->depth = 8;
params->bytes_per_line = width * 3;
}
else
{
params->format = SANE_FRAME_GRAY;
params->depth = 8;
params->bytes_per_line = width;
}
}
}
else
{
params->format = SANE_FRAME_RED;
params->depth = 1;
return SANE_STATUS_EOF;
}
return SANE_STATUS_GOOD;
}
SANE_Status sane_custom_scanner_start(SANE_Handle handle)
{
static int start_count = 0;
if (start_count == 1)
return SANE_STATUS_EOF;
start_count++;
return SANE_STATUS_GOOD;
}
SANE_Status sane_custom_scanner_read(SANE_Handle handle, SANE_Byte *data, SANE_Int max_length, SANE_Int *length)
{
static FILE *fp = NULL;
static long pixel_data_offset = 0;
static long total_pixels_bytes = 0;
static int width = 0, height = 0;
static int bytes_per_line = 0;
static int header_sent = 0;
static long total_read = 0;
if (!fp)
{
fp = fopen(CUSTOM_IMAGE_PATH, "rb");
if (!fp)
return SANE_STATUS_IO_ERROR;
char magic[3];
int width, height, max_val;
fscanf(fp, "%2s", magic);
fscanf(fp, "%d %d", &width, &height);
fscanf(fp, "%d", &max_val);
fgetc(fp);
pixel_data_offset = ftell(fp);
fseek(fp, 0, SEEK_END);
total_read = ftell(fp);
rewind(fp);
size_t actual_read = fread(data, 1, pixel_data_offset, fp);
total_read -= actual_read;
*length = actual_read;
return SANE_STATUS_GOOD;
}
if (total_read <= 0)
{
*length = 0;
fclose(fp);
fp = NULL;
return SANE_STATUS_EOF;
}
size_t rgb_block_size = (max_length / 3) * 3;
size_t read_size = fmin(rgb_block_size, total_read);
size_t actual_read = fread(data, 1, read_size, fp);
total_read -= actual_read;
*length = actual_read;
// Swap R, G, B channels
for (int i = 0; i < *length; i += 3)
{
SANE_Byte temp = data[i + 1];
data[i + 1] = data[i + 2];
data[i + 2] = data[i];
data[i] = temp;
}
return SANE_STATUS_GOOD;
}
Explanation
- The image file path is defined as
CUSTOM_IMAGE_PATH
. - The
sane_custom_scanner_get_parameters
function retrieves image parameters such as width, height, and format. - The
sane_custom_scanner_start
function starts the scanning process and should be triggered only once. - The
sane_custom_scanner_read
function reads the image data and returns it to the frontend.
Note: The R, G, and B channels need to be swapped to display the image correctly. By default, the image may appear with incorrect colors.
Step 2: Build and Install the Backend
All SANE backends are located in /usr/lib/x86_64-linux-gnu/sane/
. Use gcc
to build the shared library and copy it to the appropriate directory:
gcc -shared -fPIC -o libsane-custom_scanner.so.1 custom_scanner.c -lsane -I/usr/include/sane
sudo cp libsane-mock_sane.so.1 /usr/lib/x86_64-linux-gnu/sane/
Next, update the SANE configuration file /etc/sane.d/dll.conf
to include the custom scanner backend:
echo "custom_scanner" | sudo tee -a /etc/sane.d/dll.conf
Step 3: Test the Virtual Scanner
Test the virtual scanner using the scanimage
command:
scanimage -d custom_scanner > output.pnm
To troubleshoot issues, enable debug output by running:
SANE_DEBUG_DLL=255 scanimage -d custom_scanner > output.pnm
Finally, test the virtual scanner with the Dynamic Web TWAIN online demo, which supports document scanning and image processing.
Top comments (0)