This appendix discusses scanner driver architecture. It provides a detailed analysis and discussion of the template scanner driver.
The following major topics are discussed:
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.
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:
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:
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.
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() |
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.
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.
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.
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.
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. |
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.
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.
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.
arg->data: SCFEEDERFLAGS * res->data: NULL |
This function sets the feeder flags.
arg->data: NULL res->data: NULL |
This function determines whether or not the feeder is ready for an advance command.
arg->data: NULL res->data: NULL |
This function causes the feeder to advance to the next document.
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. |
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.
arg->data: NULL |
res->data: NULL |
This function tells the scanner driver to initiate scanning.
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.
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(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++; } |
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 |
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.
void SCDestroyZoomMap(int *zmap); |
This function frees memory allocated to store a zoom map.
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.
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.
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.
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.
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.
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.
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 |
---|---|
Creates a queue that is multi-threaded safe and blocks on an empty dequeue | |
Frees the resources used by a queue | |
Adds an element to the tail of the queue | |
Removes an element from the head of a queue and returns it | |
Sets a flag associated with a queue that tells all queue users to exit |
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.
int SCDestroyQueue(SCQUEUE *q); |
This function frees the resources used by a queue.
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.
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.
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.