Simple SANE Document Scanning in C on Linux

Dynamic Web TWAIN for Linux is coming soon. The underlying technology is SANE (Scanner Access Now Easy). Although we do not need to write any code other than JavaScript when using Dynamic Web TWAIN to develop SANE document scanning applications on Linux, learning the underlying technology will be helpful for a better understanding of how SANE works.

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