Appendix E. Scanner Driver Architecture

This appendix discusses scanner driver architecture. It provides a detailed analysis and discussion of the template scanner driver.

The following major topics are discussed:

Overview

Scanner drivers are programs that are executed by applications that link with libscan.a and call SCOpen(3). They accept commands from the application via an input pipe and return results via an output pipe.

All scanner drivers must implement the basic set of commands so that any application using the libscan interface can have access to the functionality offered by the scanner. Many library routines are provided for scanner driver developers to implement functionality in software that may not be implemented in hardware for some scanners. The support routines for writing scanner drivers can be found in libscan.a.

Driver Structure

A scanner driver consists of a number of functions that implement the set of commands required to drive the scanner. In the main routine, a table of these functions, with the position of each function in the table corresponding to its SCN #define in scanipc.h, is passed to SCDriverSetCallbacks(), then SCDriverMainLoop() is called.

SCDriverMainLoop() waits for input from the application and calls the function in the table corresponding to each command received. Each function has the following prototype:

void scanfunc(int cmd, SCARG *arg, SCRES *res);

Arguments:

cmd 

Contains the SCN #define of the command to be executed.

arg 

Is the argument to this scanning function. SCARG is defined in scandrv.h as follows:

typedef struct tag_scarg {
    void *data;
    int len;
} SCARG;

arg->data points to the arguments transferred from the application; the meaning of arg->data depends upon the cmd (see below).

arg->len encodes the byte length of arg->data.

res 

Is the result of this scanning function. SCRES is defined in scandrv.h as follows:

typedef struct tag_scres {
    void *data;
    int len;
    void *freeparam;
    void (*free)(void *param, void *data);
    int errno;
    char *errmsg;
} SCRES;

res->data should be set to point to the data to be returned to the application as a result of cmd.

res->len is the byte length of res->data.

res->free is a pointer to a function that is called if it is nonzero after res->data has been transferred to the application. The function is called with res->freeparam as its first argument and res->data as its second argument.

res->errno should be set to one of the SCE #defines in /usr/include/scanner.h or one of the errno values from /usr/include/sys/errno.h if an error occurs during the execution of cmd. If res->errno is nonzero, the libscan function being executed on the application side returns an error status, and SCerrno is set to the value of res->errno.

res->errmsg is the error message pointer. If res->errno is set to SCEDRVMSG, then res->errmsg should point to a driver-specific error message.

Before a scanning function is called, the entire res structure is zeroed. Scanning functions are allowed to assume that any member of the res structure not explicitly set remains set to 0.

Scanner Functions

Required Scanner Functions

All scanner drivers must implement the functions listed in Table E-1.

Table E-1. Scanner Driver Functions

Function

Description

SCN_INITOK()

Checks for successful scanner driver initialization

SCN_PAGESIZE()

Returns the size of the scan area that is supported by the scanner

SCN_MINMAXRES()

Returns the smallest and largest horizontal and vertical resolution

SCN_NRES()

Returns the number of resolution pairs supported in hardware

SCN_RES()

Returns floating-point numbers representing the supported hardware resolutions

SCN_NTYPES()

Returns the number of data types supported by the driver

SCN_TYPES()

Returns an array of SCDATATYPE objects, one for each of the types supported by the driver

SCN_FEEDERGETFLAGS()

Gets the document feeder flags

SCN_FEEDERSETFLAGS()

Sets the document feeder flags

SCN_FEEDERREADY()

Determines if the feeder is ready to be advanced

SCN_FEEDERADVANCE()

Advances the feeder to the next document

SCN_SETUP()

Sets the scanning parameters

SCN_GETSIZE()

Returns scan line width (in bytes and pixels) and the number of scan lines

SCN_SCAN()

Tells the scanner driver to start scanning

SCN_ABORT()

Stops the scan and releases temporarily allocated resources

SCN_DIE()

Cleans up and calls exit()


SCN_INITOK() Function

arg->data: NULL
res->data: NULL

This function exists as a mechanism for the application to determine whether the scanner driver managed to initialize itself and the scanner properly. If any problem occurred during initialization, res->errno should be set to one of the SCE #defines in scanner.h; otherwise, no action is necessary.

SCN_PAGESIZE() Function

arg->data: int * (Metric)
res->data: SCWINDOW *
typedef struct tag_scwindow {
   float x, y, width, height;
} SCWINDOW;

This function returns the size of the scannable area supported by the scanner. Fill res->data in with the x, y, width, and height of the scannable area in inches or centimeters, depending on whether arg->data is SC_INCHES or SC_CENTIM.

SCN_MINMAXRES() Function

arg->data: NULL
res->data: SCMINMAXRES *
typedef struct tag_scminmaxres {
    float minx, miny, maxx, maxy;
} SCMINMAXRES;

This function sets the smallest and largest horizontal and vertical resolutions. res->data->minx should be set to the smallest horizontal resolution supported in hardware by the scanner, res->data->miny to the smallest vertical resolution, res->data->maxx to the largest horizontal resolution, and res->data->maxy to the largest vertical resolution.

SCN_NRES() Function

arg->data: NULL
res->data: int *

This function sets the number of resolution pairs supported in hardware. *res->data should be set to the number of (xres, yres) resolution pairs supported in hardware by the scanner.

SCN_RES() Function

arg->data: int *
res->data: float *

This function sets floating-point numbers representing supported hardware resolutions. arg->data points to the metric of the resolution; either SC_INCHES for pixels/inch or SC_CENTIM for pixels/centimeter. res->data should be set to point to a floating-point array that represent supported hardware resolutions. There should be an even number of resolutions, with all of the horizontal resolutions first, then all of the vertical resolutions.


Note: All scanner drivers must support arbitrary resolutions; software routines are provided to perform zoom operations. The above information is provided so that scanner application developers can retrieve pure data from the scanner and perform their own zooming (with filters; libscan zooming does no filtering) to achieve the desired resolution.


SCN_NTYPES() Function

arg->data: NULL
res->data: int *

This function sets the number of data types supported by the driver. *res->data should be set to the number of data types supported by this scanner driver.

SCN_TYPES() Function

arg->data: NULL
res->data: SCDATATYPE *
typedef struct tag_scdatatype {
    unsigned int packing : 4;
    unsigned int channels : 4;
    unsigned int type : 8;
    unsigned int bpp : 8;
} SCDATATYPE;

The res->data of the SCN_TYPES() function points to an array of SCDATATYPE objects, one for each of the types supported by the scanner driver.

All scanner drivers must support monochrome data; that is, the type { SC_PACKPIX, 1, SC_MONO, 1 }. All scanner drivers that support any kind of greyscale or color output must support the type { SC_PACKPIX, 1, SC_GREY, 8 }; that is, 8-bit gray-scale. All scanner drivers that support any kind of color output must support either { SC_PACKPIX, 3, SC_RGB, 8 } (24-bit CHUNKY color data) or { SC_PACKPLANE, 3, SC_RGB, 8 } (24-bit planar color data).

Library routines in libscan.a exist to facilitate compliance with these conventions.

SCN_FEEDERGETFLAGS() Function

arg->data: NULL
res->data: SCFEEDERFLAGS *
typedef unsigned int SCFEEDERFLAGS;

This function returns the feeder flags for this scanner to the application. See “Header Files” in Chapter 6.

SCN_FEEDERSETFLAGS() Function

arg->data: SCFEEDERFLAGS *
res->data: NULL

This function sets the feeder flags.

SCN_FEEDERREADY() Function

arg->data: NULL
res->data: NULL

This function determines whether or not the feeder is ready for an advance command.

SCN_FEEDERADVANCE() Function

arg->data: NULL
res->data: NULL

This function causes the feeder to advance to the next document.

SCN_SETUP() Function

arg->data: SCSETUP *
res->data: NULL

typedef struct tag_scsetup {
    int preview;
    SCDATATYPE type;
    int rmetric;
    float xres, yres;
    int wmetric;
    float x, y, width, height;
} SCSETUP;

The SCN_SETUP() function sets the scanning parameters. The upper-left x and y coordinates, the width, and the height are specified in either pixels, inches, or centimeters, depending on whether arg->data->wmetric is SC_PIXELS, SC_INCHES, or SC_CENTIM.

Set the scanning horizontal and vertical resolutions, in pixels per inch or pixels per centimeter, depending on the value of arg->data->rmetric. Set the data type for scanning. If this is a preview, arg->data->preview will have a nonzero value.


Note: If a resolution or combination of resolutions not supported in hardware is specified, the driver MUST zoom the image in order to supply the requested resolution. Library routines to aid zooming are available in libscan.a.


SCN_GETSIZE() Function

arg->data: NULL
res->data: SCSIZE *
typedef struct tag_scsize {
    long xbytes, xpixels, ysize;
} SCSIZE;

This function returns, to the scanning application, the width of a scan line in bytes and pixels, and the number of scan lines in the scan. This is called after SCN_SETUP() so the application knows exactly how much data to expect.

SCN_SCAN() Function

arg->data: NULL

res->data: NULL

This function tells the scanner driver to initiate scanning.

SCN_ABORT() Function

arg->data: NULL
res->data: NULL

This function stops the scan and releases any resources temporarily allocated. The application has decided to stop retrieving data before scanning has been completed. The driver should physically stop the scan and release any resources that were temporarily allocated for the scan.

SCN_DIE() Function

arg->data: NULL
res->data: NULL

This function cleans up and calls exit(2). The application has demanded that the driver terminate. This function should not return; it should perform any necessary cleanup and then call exit.

Type Conversion Macros

The macros listed in Table E-2 are provided to convert between data types.

Table E-2. Type Conversion Macros

Macro

Description

GRIDTOFLOAT

Convert from grid format to floating-point format

FLOATTOGRID

Convert from floating-point format to grid format


GRIDTOFLOAT and FLOATTOGRID Macros

GRIDTOFLOAT(int pos, int n)
FLOATTOGRID(float pos, int n)

These macros determine which destination pixel or line the source pixel or line at pos corresponds to. For example, if we are scanning at 120 dpi, but the application has requested 100 dpi, and our scan height is 1 inch, we need to skip 20 scan lines to provide the desired resolution. The following loop obtains scan lines from the scanner and passes them on to the application:

float fy;
int imgy, scany;

while (imgy < 100) {
    fy = GRIDTOFLOAT(imgy, 100);
    scany = FLOATTOGRID(fy, 120);
    ...
    /* Get the scany'th scan line from the scanner */
    /* Do conversion and horizontal zooming */
    /* Call SCDriverPutRow */
    imgy++;
}

Zooming and Type Conversion Functions

The functions listed in Table E-3 are provided to support zooming and converting between data types.

All conversion routines simultaneously zoom, so that only one conversion per line should ever be necessary.

Table E-3. Zooming and Type Conversion Functions

Function

Description

SCCreateZoomMap()

Creates a zoom map

SCDestroyZoomMap()

Frees memory allocated to store a zoom map

SCZoomRow1()

Zooms a row of 1-bit pixels

SCZoomRow8()

Zooms a row of 8-bit pixels

SCZoomRow24()

Zooms a row of 24-bit pixels

SCZoomRow32()

Zooms a row of 32-bit pixels

SCBandRGB8ToPixelRGB8()

Converts a row of pixels, in three rows (R, G, and B) of 8-bit components per pixel, to a row of 24-bit pixels

SCGrey8ToMono()

Converts a row of pixels from 8-bit greyscale to monochrome


SCCreateZoomMap() Function

int *SCCreateZoomMap(int anx, int bnx);

This function creates a zoom map. When zooming in the horizontal direction, it is wasteful to use GRIDTOFLOAT and FLOATTOGRID for every pixel of every line, since the same calculations would be repeated many times. A zoom map is an array of bnx integers, each of which is the pixel between 0 and anx - 1 that should be used when zooming a row of anx pixels to a row of bnx pixels. The zooming and conversion functions all take zoom maps for efficient zooming; for conversion functions where no zooming is to occur, the zmap parameter can be NULL.

SCDestroyZoomMap() Function

void SCDestroyZoomMap(int *zmap);

This function frees memory allocated to store a zoom map.

SCZoomRow1() Function

void SCZoomRow1(char *abuf, int anx, char *bbuf, int bnx, int *zmap);

This function zooms a row of anx pixels to a row of bnx pixels, 1 bit per pixel.

SCZoomRow8() Function

void SCZoomRow8(char *abuf, int anx, char *bbuf, int bnx, int *zmap);

This function zooms a row of anx pixels to a row of bnx pixels, 8 bits per pixel.

SCZoomRow24() Function

void SCZoomRow24(void *abuf, int anx, void *bbuf, int bnx, int *zmap);

This function zooms a row of anx pixels to a row of bnx pixels, 24 bits per pixel.

SCZoomRow32() Function

void SCZoomRow32(void *abuf, int anx, void *bbuf, int bnx, int *zmap);

This function zooms a row of anx pixels to a row of bnx pixels, 32 bits per pixel.

SCBandRGB8ToPixelRGB8() Function

void SCBandRGB8ToPixelRGB8(void *frombuf, int fromx, 
        void *tobuf, int tox, int *zmap);

This function converts a row of fromx pixels, laid out in three rows (R, G, and B) of 8-bit components per pixel, to a row of tox pixels, 24 bits per pixel.

SCGrey8ToMono() Function

void SCGrey8ToMono(unsigned char thresh, void *frombuf, 
        int fromx, void *tobuf, int tox, int *zmap);

This function converts a row of pixels from 8-bit greyscale to monochrome, thresholding each pixel with thresh.

Queues and Multi-Threaded Scanner Drivers

To achieve optimal performance in a scanner driver, it is helpful to parallelize the operations being performed. A pipeline carries data from the scanner to the ultimate destination, often a file. One can imagine that at the beginning of the pipeline, most of the time is spent waiting for I/O to complete. An intermediate image processing stage is CPU-bound as it zooms and converts rows of data. The final stage, writing to a file, is again I/O-bound.

Rather than adding these times together, we notice that all three stages of the pipeline can occur at the same time; that is, while the scanning stage is waiting for I/O, the file-writing stage can also be waiting for I/O, and the image-processing stage can be using the CPU. As you can imagine, performance gets even better on multiprocessor systems.

To support this, a multi-threaded queue interface is included in libscan.a. Each queue is semaphored so that the read thread can be different from the write thread, and so that the dequeue operation on an empty queue blocks until another thread has enqueued something.

In the driver template, separate threads implement the scanning stage and the image-processing stage. The main thread of the driver simply starts the two processes and waits for more commands from the application.

The driver template uses two queues: one to hold free buffers and one to hold freshly scanned lines. The amount of concurrency is metered by the initial size of the free queue; the scanning thread blocks when there are no more free buffers if it gets too far ahead of the image-processing thread.

The scanning thread dequeues a buffer from the scan free queue, gets data from the scanner, and stores it in the buffer. Then it breaks the buffer up into scan lines, enqueueing each line on the scan queue (it is typically faster to scan chunks of lines rather than one line at a time).

The image-processing thread dequeues a buffer from the scan queue. It then zooms and converts it, and writes the result to a stream that the application is reading to obtain the data. It puts the original buffer back on the scan free queue (actually, since the scanning thread breaks its buffer up into scan line-sized chunks, the image-processing thread has to know how to put the chunks back together).

Figure E-1 illustrates the scanning process.

Figure E-1. Scanner Driver Architecture

Figure E-1 Scanner Driver Architecture

Queue Manipulating Functions

The following functions are provided for manipulating queues:

typedef struct tag_scqueue SCQUEUE;

Table E-4 lists the queue manipulating functions.

Table E-4. Queue Manipulating Functions

Function

Description

SCCreateQueue()

Creates a queue that is multi-threaded safe and blocks on an empty dequeue

SCDestroyQueue()

Frees the resources used by a queue

SCEnqueue()

Adds an element to the tail of the queue

SCDequeue()

Removes an element from the head of a queue and returns it

SCQueueSetExit()

Sets a flag associated with a queue that tells all queue users to exit


SCCreateQueue() Function

SCQUEUE * SCCreateQueue(int nelems);

This function creates a queue that is multi-threaded safe and blocks on an empty dequeue. nelems is the maximum number of elements that can be stored in the newly created queue. Enqueue operations on full queues block until another thread has completed a dequeue operation.

SCDestroyQueue() Function

int SCDestroyQueue(SCQUEUE *q);

This function frees the resources used by a queue.

SCEnqueue() Function

void SCEnqueue(SCQUEUE *q, void *data);

This function adds an element to the tail of the queue. It unblocks a thread waiting to dequeue or blocks it if the queue is full.

SCDequeue() Function

void * SCDequeue(SCQUEUE *q);

This function removes an element from the head of a queue and returns it. SCDequeue() unblocks a thread waiting to enqueue or blocks it if the queue is empty.

SCQueueSetExit() Function

void * SCQueueSetExit(SCQUEUE *q);

This function sets a flag associated with a queue that tells all users of the queue to exit. Any thread blocking in SCEnqueue() or SCDequeue() is terminated. SCQueueSetExit() is used by the main thread of a scanner driver to tell the child threads to exit when the user aborts a scan.