Simple SANE Document Scanning in C on Linux

Dynamic Web TWAIN for Linux was introduced on 11/03/2016. It enables interaction with document scanners from within a browser on Linux. The underlying technology is SANE (Scanner Access Now Easy).

We do not need to write any code other than JavaScript when using Dynamic Web TWAIN. Learn how to to develop SANE document scanning applications on Linux.

Dynamic Web TWAIN is a commercial product. If that’s not an option for you, you may try building a simple SANE scanning application in C from scratch. This article discusses how to implement scanning WITHOUT Dynamic Web TWAIN.

linux sane document scanning

SANE Download

If SANE is not installed on your system, download it with the following command:

sudo apt-get update
sudo apt-get install sane

In addition, you need to download the source code package which includes the header files for programming.

SANE Document Scanning on Ubuntu and Raspberry Pi

Prerequisites

SANE Workflow

The basic SANE workflow is as follows:

Linux SANE document scanning flowchart

We can ignore sane_get_option_descriptor() and sane_control_option() to make the code as simple as possible.

Implementation

  1. SANE Initialization:

     void init()
     {
         SANE_Int version_code = 0;
     	sane_init (&version_code, auth_callback);
         printf("SANE version code: %d\n", version_code);
     }
    
  2. Get all connected SANE-compatible devices:

     SANE_Status get_devices(const SANE_Device ***device_list)
     {
         printf("Get all devices...\n");
     	SANE_Status sane_status = 0;
     	if (sane_status = sane_get_devices (device_list, SANE_FALSE))
     	{
     		printf("sane_get_devices status: %s\n", sane_strstatus(sane_status));
     	}	
         return sane_status;
     }
    
  3. Open a target device:

     SANE_Status open_device(SANE_Device *device, SANE_Handle *sane_handle)
     {
         SANE_Status sane_status = 0;
         printf("Name: %s, vendor: %s, model: %s, type: %s\n", device->name, device->model, device->vendor, device->type);
         if (sane_status = sane_open(device->name, sane_handle))
         {
             printf("sane_open status: %s\n", sane_strstatus(sane_status));
         }
        
         return sane_status;
     }
    
  4. Scan documents:

     SANE_Status start_scan(SANE_Handle sane_handle, SANE_String_Const fileName)
     {
         SANE_Status sane_status = 0;
         device = sane_handle;
         return do_scan(fileName);
     }
    

    Write pnm header:

     static void write_pnm_header (SANE_Frame format, int width, int height, int depth, FILE *ofp)
     {
         switch (format)
         {
             case SANE_FRAME_RED:
             case SANE_FRAME_GREEN:
             case SANE_FRAME_BLUE:
             case SANE_FRAME_RGB:
                 fprintf (ofp, "P6\n# SANE data follows\n%d %d\n%d\n", width, height, (depth <= 8) ? 255 : 65535);
                 break;
             default:
                 if (depth == 1)
                     fprintf (ofp, "P4\n# SANE data follows\n%d %d\n", width, height);
                 else
                     fprintf (ofp, "P5\n# SANE data follows\n%d %d\n%d\n", width, height,(depth <= 8) ? 255 : 65535);
                 break;
         }
     }
    

    Write buffer to pnm file:

     while (1)
             {
                 double progr;
                 status = sane_read (device, buffer, buffer_size, &len);
                 total_bytes += (SANE_Word) len;
                 progr = ((total_bytes * 100.) / (double) hundred_percent);
                 if (progr > 100.)
                     progr = 100.;
        
                 if (status != SANE_STATUS_GOOD)
                 {
                     if (status != SANE_STATUS_EOF)
                     {
                         return status;
                     }
                     break;
                 }
        
                 if ((parm.depth != 16)) 
                         fwrite (buffer, 1, len, ofp);
                     else
                     {
     #if !defined(WORDS_BIGENDIAN)
                         int i, start = 0;
                         /* check if we have saved one byte from the last sane_read */
                         if (hang_over > -1)
                         {
                             if (len > 0)
                             {
                                 fwrite (buffer, 1, 1, ofp);
                                 buffer[0] = (SANE_Byte) hang_over;
                                 hang_over = -1;
                                 start = 1;
                             }
                         }
                         /* now do the byte-swapping */
                         for (i = start; i < (len - 1); i += 2)
                         {
                             unsigned char LSB;
                             LSB = buffer[i];
                             buffer[i] = buffer[i + 1];
                             buffer[i + 1] = LSB;
                         }
                         /* check if we have an odd number of bytes */
                         if (((len - start) % 2) != 0)
                         {
                             hang_over = buffer[len - 1];
                             len--;
                         }
     #endif
                         fwrite (buffer, 1, len, ofp);
                     }
    
  5. Close the device:

     void close_device(SANE_Handle sane_handle)
     {
         sane_close(sane_handle);
     }
    
  6. Release all SANE resources:

     void exit()
     {
         sane_exit();
     }
    

Building

Generate a symbolic link for SANE shared library on Ubuntu:

sudo ln –s /usr/lib/x86_64-linux-gnu/libsane.so.1 /usr/lib/libsane.so

Generate a symbolic link for SANE shared library on Raspberry Pi:

sudo ln –s /usr/lib/arm-linux-gnueabihf/libsane.so.1 /usr/lib/libsane.so

Specify the paths of header files in makefile:

SANE_INCLUDE=<Your SANE Package Path>/include

Build the project:

make

Run the application:

sudo ./hellosane

View the pnm image files with GIMP.

Source Code

https://github.com/yushulx/linux-document-scanning