How to write your own picture drivers


Structure of a picture driver

A picture driver is an external module which is loaded by PROforma. The init routine should be a data structure of type PICTdriver.

The module identifier has to be "PROforma external driver".

/* picture driver definition */

typedef struct _PICTdriver {
    struct _PICTdriver *next;   /* next driver in list of drivers */
    int identifier;             /* identifies type of driver */

    char name[PF_MAXDRIVERNAME];/* name of driver  - the name of a driver */
                                /* should not start with "-" or " " !! */

    /* can we recognize the picture */
    Error (*Recognize)(Gstate, char *base);
    Error (*AspectRatio)(Gstate, char *base, int *xratio, int *yratio);

    /* display the picture */
    Error (*Display)(Gstate, char *base, pt xsiz, pt ysiz,
                     Error (*ColourSelect)(Gstate, int));

    /* info on colours used in the picture */
    Error (*ColourInfo)(Gstate, char *base, int *count, int *colourspace);
    Error (*ColourGet)(Gstate, char *base, int which, void *colour);

    /* for future extensions */
    Error Handle(int command, ...);
} PICTdriver;

The next pointer allows you to define several picture drivers in one external module. However, an external module can only contain one type of drivers (in this case picture drivers).

Each driver contains an identifier which indicates the type of driver. For a picture driver, the value has to be PF_PICTUREDRIVER.

Each picture driver should have a (preferably unique) name. It is adviseable to make these names as descriptive as possible, e.g. "QL mode 4 screen, 512x256". Note that driver names are case sensitive !

The member functions

A picture driver is quite simple, as it does not contain many member functions. The main difficulty when writing picture drivers lies in interpretting the actual picture types.
Recognize
Try to recognize the given picture as one that can be displayed by this picture driver. ERR_INAM should be returned if the picture can not be recognized. This routine is included to allow applications to automatically detect the picture driver which has to be used. However, not all picture files embed sufficient information for automatic detection, therefore it is quite allowed to reject all pictures as not recognized.

A picture which is passed to the picture driver is just a block of memory. It is intended that this block starts by the FileInfo structure of the file, followed by a copy of the picture file itself, as if it was loaded with the following code

#include "mem_h"
#include "io_h"
#include "PROforma_h"
#define catch(x) do { if (err=(x)) return err; } while (0)
...
{
    Error err;
    char *base;
    Channel file;
    Size size;

    catch( IOOpenPath(filename,OPEN_OLD,path,&file) );
    catch( IOLength(file,&size) );
    catch( MEMAllocate(size+sizeof(FileInfo),&base) );
    catch( IOFileInfo(file, (FileInfo *)base) );
    catch( IOLoadFile(file,size,base+sizeof(FileInfo)) );
    IOClose(file);
    ...
    PFPictureDisplay(gstate, id, base, xsize, ysize);
}

This is the only picture driver access function which can not assume that the picture is of a suitable type. Pictures may only be recognized when you are quite sure it is not supposed to be displayed by another picture driver.

AspectRatio
Try to determine the aspect ratio of the given picture. Again, some picture formats do not include the aspect ratio, and it is therefore allowed to fail (ERR_ITNF) on all pictures passed, however, in all cases a guessed aspect ratio should be filled in (4x3 for a full screen is normally a good guess).

The aspect ratio is returned as two integers. This means that when the picture is displayed with a with of xratio, then the height should be yratio to preserve the aspect ratio of the original.

Display
Actually display a picture. The picture will be displayed at the current position, and at the given size. Pictures can be rotated etc., but all that is handled by the support routine PFPictureElement. For an idea of the recommended way to implement Display, see the example below.

Please note that the ColourSelect parameter is invalid when the picture has fixed colours. In that case the PFColourXXX commands should be called directly to set the drawing colour.

Before calling the Display routine for a picture, PROforma will first adjust the PageOrigin to the top left position of the picture. Also, the current graphics state is saved before and restore after the Display routine. This makes sure that the graphics state is not affected by drawing pictures.

ColourInfo
Get some information about the number of colours and the colourspace which is used for the default colours.

This command is used to query the default colours which can be used to display a picture. A picture driver can however choose only to support fixed colours (especially for real-colour images). In that case, zero (0) can be returned as the number of colours.

The possible value for the colourspace are PF_COLOURSPACE_RGB, PF_COLOURSPACE_GRAYSHADE or PF_COLOURSPACE_CMYK.

ColourGet
This function is used by PROforma to build the table with the default colours. The which parameter is always in the range [0..count-1], where count is returned by ColourInfo. The colour parameter points to an area where the colour has to be filled in (using the correct colourspace).
Handle
This is a function which is provided for possible future extensions of the PROforma drivers. It should always return ERR_NIMP.

Support routines

The PROforma core library contains a support routine which is specifically intended for drawing pictures.
PFPictureElement
Draw a picture element, which is a filled rectangle of given size and at the given position. The size and position are relative with the current point and in user space. The current drawing colour is used.
This routine will return ERR_ORNG if the rectangle is completely invisible. This can be used to speed up the drawing of pictures.

Example

code

To start with, the file with all the definition of the data structures which are used by PROforma has to be loaded. Most of this doesn't concern the author of picture drivers, by you do need the definition of the picture driver structure. Accidently, this also includes PROforma_h.

As the definition of PROforma Core routines is not included in the header files, import the PFPictureElement function.

#include "PFmodule.h"
Error PFPictureElement(Gstate gstate, pt xsiz, pt ysiz, pt xorg, pt yorg);
Next up, define the actual structure of the picture data, as this is used by all the member functions. For the sake of the example, I have defined a very simple picture format, including the data needed to recognize the picture, and the aspect ratio (which is optional). The picture itself has one byte for each pixel, giving 256 distinct colours.
typedef struct {
    char identifier[24];    /* "example picture format" */
    short xsiz, ysiz;       /* size in pixels */
    short xratio, yratio;   /* pixel aspect ratio (0 if not known) */
    unsigned char data[2];  /* start of picture data */
} Picture;

Start with the real work. For starters, try to recognize a picture as being of the correct type. You should always try to build in as many checks as possible, as illustrated here by assuring that the picture has a real size, and that the aspect ratio is possible (zero indicactes that the ratio is not known).

static Error Recognize(Gstate gstate, char *base)
{
    FileInfo *fi=(FileInfo *)base;
    Picture *pict=(Picture *)(base+sizeof(FileInfo));

    if (fi->type==FILETYPE_NORMAL &&
        STRSameCD(pict->identifier,"example picture format") &&
        pict->xsiz>0 && pict->ysiz>0)
        return ERR_OK;
    else
        return ERR_INAM;
}
Get the aspect ratio of the picture. We can assume that the picture passed is of correct type. If the aspect ratio is defined in the picture, than return that. If not, give an error, and assume the picture was a full screen.
static Error AspectRatio(Gstate gstate, char *base, int *xratio, int *yratio)
{
    Picture *pict=(Picture *)(base+sizeof(FileInfo));
    if (pict->xratio && pict->yratio)
    {
        *xratio=pict->xratio*pict->xsiz;
        *yratio=pict->yratio*pict->ysiz;
        return ERR_OK;
    }
    else
    {
        *xratio=4; *yratio=3;
        return ERR_ITNF;
    }
}

Get information about the number of colours used, and the colourspace. In this simple example, all pictures have 256 colours, and as pictures usually originate from a screen, the colours will be given as red, green and blue components.

static Error ColourInfo(Gstate gstate, char *base, int *count, int *space)
{
    *count=256;
    *space=PF_COLOURSPACE_RGB;
    return ERR_OK;
}

Get the default colours used for the picture. The default colours only have to be defined here (except when the colours are fixed). Although the default colours are often embedded in the picture format, they have to be calculated in this example.

The colour is calculated by looking at the bits. Each colour component has to bits allocated to it, and the two remaining bits can increase the intensity of the colour.

static Error ColourGet(Gstate gstate, char *base, int which, void *colour)
{
    /* the colours are 8 bit : iirrggbb (i for intensity) */
    ColourRGB *rgb=(ColourRGB *)colour;
    int intensity=((which>>6)&3)+1;

    rgb->red  =intensity * ((which>>4)&3) * (pt_hundred/12);
    rgb->green=intensity * ((which>>2)&3) * (pt_hundred/12);
    rgb->blue =intensity * ((which   )&3) * (pt_hundred/12);
    return ERR_OK;
}

To make sure that a picture is always drawn as fast as possible, the background of the picture is drawn first. If this is completely invisible, you can stop immediately.

Each line is also cleared to the background colour before drawing the individual pixels (or spans). This can also indicate that a line can be discarded, especially when drawing on screen (where speed is most important).

When drawing the spans, unnecessary drawing is not done, by making sure that the colour is different from the background colour. This could give a larger speed gain if the background colour would be the most used colour in the picture. However, actually determining that colour each time the picture is displayed, would probably slow the displaying of the picture down.

static Error Display(Gstate gstate, char *base, pt xsiz, pt ysiz,
                            Error (*ColourSelect)(Gstate, int))
{
    Picture *pict=(Picture *)(base+sizeof(FileInfo));
    int xpix,ypix=pict->ysiz;
    int colour, backcolour=0;
    short bit=0x80;
    int length, lastcolour;
    unsigned char *linestart=pict->data;
    unsigned char *linepos;
    int posx;
    pt posy=0;

    /* make sure that at least some part of the picture is visible */
    ColourSelect(gstate,backcolour);
    if (PFPictureElement(gstate,xsiz,ysiz,0,posy)) return ERR_OK;

    xsiz/=pict->xsiz;
    ysiz/=pict->ysiz;

    while(ypix)
    {
        posx=0;
        linepos=linestart;

        /* see if this line is clipped - and set background colour */
        ColourSelect(gstate,backcolour);
        if (!PFPictureElement(gstate,xsiz*pict->xsiz,ysiz,0,posy))
        {
            length=1;
            /* get colour */
            lastcolour=*linepos;

            for(xpix=pict->xsiz-1; xpix; xpix--)
            {
                /* get colour */
                colour=*linepos++;

                if (colour!=lastcolour)
                {
                    if (lastcolour!=backcolour)
                    {
                        ColourSelect(gstate,lastcolour);
                        PFPictureElement(gstate,xsiz*length,ysiz,xsiz*posx,posy);
                    }
                    /* skip pixels on page */
                    posx+=length;
                    lastcolour=colour;
                    length=1;
                } else
                    length++;
            }
            /* there may be a sequence left at the end of the line */
            if (colour!=backcolour)
            {
                ColourSelect(gstate,colour);
                PFPictureElement(gstate,xsiz*length,ysiz,xsiz*posx,posy);
            }
        }
        posy+=ysiz;
        ypix--;
        linestart+=pict->xsiz;
    }
    return ERR_OK;
}

We also need a dummy routine, for future compatibility with possible extensions of the picture drivers.

Error Handle(int command, ...)
{
    return ERR_NIMP;
}

To finish the driver, only the actual driver definition has to be written. The structure is called init to make sure PROforma (the external module system to be precise) knows where to find the picture driver definition.

PICTdriver init = {
    NULL, PF_PICTUREDRIVER,
    "example 256 colour picture",
    Recognize,
    AspectRatio,
    Display,
    ColourInfo,
    ColourGet,
    Handle
};

makefile

Here are the lines from the makefile which allow you to build the example given above as a genuine PROforma picture driver. Note that all occurences of "pict_example" can be replaced by any other filename.
pict_example_pfd : pict_example_o core-dll_o
        ${LD} -ms -opict_example_pfd \
        pict_example_o core-dll_o \
        -lsms -sxmod
        mkxmod pict_example_pfd \"PROforma external driver\"

PROGS, Professional & Graphical Software
last edited November 11, 1996