Adding New Formats (XForms (Forms Library) - Version 1.2 (Rev. 4) A Graphical User Interface Toolkit for X)

Next: Queries, Previous: Built-in support, Up: Supported Image Formats [Contents][Index]

#### 37.5.2 Adding New Formats

It is possible for application to add new formats to the library so `flimage_load()` and `flimage_dump()` know how to handle them. Basically, the application program tells the library how to identify the image format, and the image dimension, and how to read and write pixels.

The API for doing so is the following

    typedef int (*FLIMAGE_Identify) (FILE *);
    typedef int (*FLIMAGE_Description) (FL_IMAGE *);
    typedef int (*FLIMAGE_Read_Pixels) (FL_IMAGE *);
    typedef int (*FLIMAGE_Write_Image) (FL_IMAGE *);

    int flimage_add_format(const char *formal_name,
                           const char *short_name,
                           const char *extension,
                           int type,
                           FLIMAGE_Identify identify,
                           FLIMAGE_Description description,
                           FLIMAGE_Read_Pixels read_pixels,
                           FLIMAGE_Write_Image write_image);

where we have

`formal_name`

  The formal name of the image format

`short_name`

  An abbreviated name for the image format

`extension`

  File extension, if this field is `NULL`, `short_name` will be substituted

`type`

  The image type. This field generally is one of the supported image types (e.g., `FL_IMAGE_RGB`), but it does not have to. For image file formats that are capable of holding more than one type of images, this field can be set to indicate this by ORing the supported types together (e.g., `FL_IMAGE_RGB|FL_IMAGE_GRAY`). However, when description returns, the image type should be set to the actual type in the file.

`identify`

  This function should return 1 if the file pointed to by the file pointer passed in is the expected image format (by checking signature etc.). It should return a negative number if the file is not recognized. The decision if the file pointer should be rewound or not is between this function and the description function.

`description`

  This function in general should set the image dimension and type fields (and colormap length for color index images) if successful, so the driver can allocate the necessary memory for read pixel. Of course, if `read_pixels` elects to allocate memory itself, the `description` function does not have to set any fields. However, if reading should continue, the function should return 1 otherwise a negative number.

  The function should read from input file stream `image->fpin`.

  It is likely that some information obtained in this function needs to be passed to the actual pixel reading routine. The easiest way is, of course, to make these information static within the file, but if a GUI system is in place, all the reading routines should try to be reentrant. The method to avoid static variables is to use the `image->io_spec` field to keep these information. If this field points to some dynamically allocated memory, you do not need to free it after `read_pixels` function finishes. However, if you free it or this field points to static memory, you should set to this field to `NULL` when finished.

  The following is a short example showing how this field may be utilized.

      typedef struct {
          int bits_per_pixel;
          int other_stuff;
      } SPEC;

      static int description(FL_IMAGE *im) {
          SPEC *sp = fl_calloc(1, sizeof *sp);

          im->io_spec        = sp;
          im->spec_size      = sizeof *sp;
          sp->bits_per_pixel = read_from_file(im->fpin);

          return 0;
      }

      static int read_pixels(FL_IMAGE *im) {
          SPEC *sp = im->io_spec;

           int bits_per_pixel = sp->bits_per_pixel;

           read_file_based_on_bits_per_pixel(im->fpin);

           /* You don't have to free im->io_spec, but if you do
              remember to set it to NULL before returning */

           return 0;
      }

`read_pixels`

  This function reads the pixels from the file and fills one of the pixel matrix in the image structure depending on the type. If reading is successful, a non-negative number should be returned otherwise a negative number should be returned.

  Upon entry, `image->completed` is set to zero.

  The function should not close the file.

`write_image`

  This function takes an image structure and should write the image out in a format it knows. Prior to calling this routine, the driver will have already converted the image type to the type it wants. The function should return 1 on success and a negative number otherwise. If only reading of the image format is supported this parameter can be set to `NULL`.

  The function should write to file stream `image->fpout`.

By calling `flimage_add_format()` the newly specified image format is added to a "recognized image format" pool in the library. When `flimage_load()` is called the library, after verifying that the file is readable, loops over each of the formats and calls the `identify` routine until a format is identified or the pool exhausted. If the file is recognized as one of the supported formats the `description` routine is called to obtain the image dimension and type. Upon its return the library allocates all memory needed, then calls `read_pixels`. If the image format pool is exhausted before the file is recognized `flimage_load()` fails.

On output, when `flimage_dump()` is called, the requested format name is used to look up the output routine from the image format pool. Once an output routine for the requested format is found, the library looks the image type the output is capable of writing. If the current image type is not among the types supported by the format the library converts image to the type needed prior to calling the output routine `write_image()`. So what `flimage_dump()` does is

    int flimage_dump(FL_IMAGE *im, const char *filename,
                     const char *formatName) {
        format = search_image_format_pool(formatName);
        if (!format)
            return -1;

        im->fpout = fopen(filename);
        if (im->pre_write)
            im->pre_write(im);

        convert image type if necessary(im);
        format->write_pixels(im);
        ...
    }

If the name of the image format supplied by `flimage_add_format()` is identical to one that is already supported, the new routines replace those that are in the pool. This way, the application can override the built-in supports.

For a non-trivial example of adding a new format, see file `flimage_jpeg.c`. Another way of adding image formats is through external filters that convert an unsupported format into one that is. All you need to do is inform the library what external filter to use. `pbmplus` or `netpbm` are excellent packages for this purpose.

The library has two functions that deal with external filters

    int flimage_description_via_filter(FL_IMAGE * im, char *const *cmds,
                                       const char *what, int verbose);
    int flimage_write_via_filter(FL_IMAGE *im, char *const *cmds,
                                 char *const formats[], int verbose);

where `cmds` are a list of shell commands (filters) that convert the format in question into one of the supported formats. Parameter `what` is for reporting purposes and parameter `verbose` controls if some information and error messages should be printed. This is mainly for debugging purposes.

Let us go through one example to show how this filter facility can be used. In this example, we support SGI’s rgb format via the `netpbm` package.

As with regular image format, we first define a function that identifies the image format:

    static int IRIS_identify(FILE *fp) {
        char buf[2];

        fread(buf, 1, 2, fp);
        return    (buf[0] == '\001' && buf[1] == '\332')
               || (buf[0] == '\332' && buf[1] == '\001');
    }

Then we need to define the filter(s) that can convert a RGB file into one that’s supported. Here we use `sgitopnm`, but you can use diferent filters if available. Function `flimage_description_via_filter()` will try all the filters specified until one of them succeeds. If none does an error code is returned:

    static int IRIS_description(FL_IMAGE *im) {
        static char *cmds[] = {"sgitopnm %s > %s",
                               NULL  /* sentinel, indicating end of
                                        list of filters */ };
        return flimage_description_via_filter(im, cmds,
                                               "Reading RGB...", 0);
    }

All commands should be suitable format strings for function `sprintf()` and contain `%s` twice. The first one will be replaced by the input file name, the second by a filename which will be supplied by the library to hold the converted image. The list must be terminate with a `NULL` element.

In the above example, `sgitopnm %s > %s` specifies the external command, `sgitopnm`, and how it operates. Basically, the library will do a `sprintf(cmdbuf, cmd[i], irisfile, tmpfile)` and then execute `cmdbuf`.

There is really no need for a load function as the filter will have already invoked the correct load function when it returns. For the record of capability queries, a dummy load function is needed:

    static int IRIS_load(FL_IMAGE * im) {
         fprintf(stderr, "We should never get here...\n");
         return -1;
    }

Writing an image is similar:

    static int IRIS_dump(FL_IMAGE *im) {
        static char *cmds[] = {"pnmtosgi %s > %s",
                               NULL};
        static char *cmds_rle[] = {"pnmtosgi -rle %s > %s",
                                   NULL};
        static char *formats[] = {"ppm", "pgm", "pbm", NULL};

        return flimage_write_via_filter(im, rle ? cmds_rle : cmds,
                                        formats, 0);
    }

Again, the external commands should accept two arguments. The first argument will be supplied by the library, a temporary file that holds the converted image in a format the filter understands, and the second argument will be the requested output filename.

For output, an additional argument is required. The additional argument `formats` specifies the image format accepted by the external filter. In this case, this is the pnm format. It is important that if the filter accepts more than one format, you should specify the formats in decreasing generality, i.e., ppm, pgm, pbm.

With these functions in place, finally we’re ready to add iris support into the library

    void add_iris(void) {
        flimage_add_format("SGI Iris", "iris", "rgb",
                           FL_IMAGE_RGB|FL_IMAGE_GRAY|FL_IMAGE_MONO,
                           IRIS_identify,
                           IRIS_description,
                           IRIS_load,
                           IRIS_dump);
    }

After a call of `add_iris()` you can now use `flimage_load()` and `flimage_dump()` to read and write SGI iris format just like any other format.

Next: Queries, Previous: Built-in support, Up: Supported Image Formats [Contents][Index]