Images
******

Although images are not typically a part of the GUI, they are often part of an application. For this reason and others, image support is part of Forms Library. It is somewhat not unexpected that the users of a graphical user interface want some graphics support.

The most important reason to have  image support in the library is the amount of questions/requests on the  mailing list of the Forms Library about images. It convinced us that having image support will make many Forms Library users life easier.

The second reason has something to do with image support in X, which at best is cumbersome to use as the API reflects the underlying hardware, which, at the level of Xlib, is quite appropriate, but not quite what an application programmer wants to deal with. Image support in Forms Library for the large part is hardware independent. This is possible because xforms makes distinction between the real image it keeps and the image being displayed. At the expense of some flexibility and memory requirement, the high-level image support API should prove to be useful for most situations.

The third reason is that image support as it is now in the library is well isolated and is only linked into an application when it is actually being used. This is not a trivial point in the consideration to include image support in the library proper.

The Basic Image Support API
===========================

Reading and displaying images are quite easy. It can be as simple as a couple of lines of code:

     FL_IMAGE *image;

     if ((image = flimage_load("imagefilename"))
         image->display(image, win);

In this example, an image is created from a file, then the image is displayed in a window, win. For most casual uses, this is really what is needed to load and display an image.

As you may have guessed, an image in Forms Library is represented by a structure of type FL_IMAGE. In addition to the pixels in the image, it also keeps a variety of information about the image such as its type, dimension, lookup tables etc. Further, if the image can not be displayed directly on the display hardware (for example, the image is 24 bits, while the display is only capable of 8 bits), a separate displayable image is created and displayed. Any manipulation of the image is always performed on the original high-resolution image, and a new displayable image will be created if necessary.

Writing an image is just as simple

     if (flimage_dump(image, "filename", "jpeg") < 0)
         fprintf(stderr,"image write failed");

In this code snippet, an image in memory is written to a file in JPEG format. As you might have noticed by now, all image routines start with flimage. The exact APIs for reading and writing an image are as follows

     FL_IMAGE *flimage_load(const char *filename);
     int flimage_dump(FL_IMAGE *im, const char *filename, const char *fmt);

The function flimage_load() takes a filename and attempts to read it. If successful, an image (or multiple images) is created and returned. If for any reason the image can't be created (no permission to read, unknown file format, out of memory etc), a null pointer is returned. As will be documented later, error reporting and progress report can be configured so these tasks are performed inside the library.

The function flimage_dump() takes an image, either returned by flimage_load() (possibly after some processing) or created on the fly by the application, attempts to create a file to store the image. The image format written is controlled by the third parameter fmtq, which should be either the formal name or the short name of one of the supported formats (such as jpeg, ppm, gif, bmp etc., see section 23.3) or some other formats the application knows how to write. If this parameter is NULL, the original format the image was in is used. If the image is successfully written, a non-negative number is returned, otherwise a negative number. Depending on how the image support is configured, error reporting may have already occurred before the function returns.

Given these two routines, a file converter (i.e., changing the image file format) is simple

     if ((image = flimage_load("inputfile"))
         flimage_dump(image, "outfile", "newformat");

See the demo program iconvert.c for a flexible and usable image converter.

To free an image, use the following routine

     void flimage_free(FL_IMAGE *image);

The function first frees all memory allocated for the image, then the image structure itself. After the function returns, the image should not be referenced.

The following routines are available to display an image in a window

     int flimage_display(FL_IMAGE *image, FL_WINDOW win);
     int flimage_sdisplay(FL_IMAGE *image, FL_WINDOW win);

where win is a window ID. If the image(s) is successfully displayed, a non-negative integer is returned, a negative integer otherwise. The difference between the two display routines is that flimage_sdisplay() only displays a single image while flimage_display(), built on top of flimage_sdisplay(), can display single or multiple images. For typical use, flimage_display() or image->display should be used. flimage_sdisplay() is useful only if you're coding your own multi-image display routine. For example, flimage_display() is built roughly like the following

     int flimage_display(FL_IMAGE *im, FL_WINDOW win) {
         int err;

         for (err = 0; err >=0 && im; im = im->next) {
             err = flimage_sdisplay(im, win);
             fl_update_display(0);
             fl_msleep(im->setup->delay);
         }

         return err;
     }

And you can build your own multi-frame image display routine to suit your application's needs.

Despite the display routine's simple look, this function performs tasks that involve the details of dealing with different hardware capabilities, a daunting task for beginners. For PseudoColor displays (i.e., using color maps or color lookup tables), a color quantization or dithering step may be performed by the function to reduce the number of colors in the image (of course, the colorreduced image is kept only for display, the original image is untouched so future processing is carried out on the original full resolution image, rather than the displayed, an approximate of the original image). In general, when the information in an image is reduced in order to display it, the original image is not altered in any way. For example, this function can display a 24bit image on a 1bit display without losing any information on the original 24bit image.

By default, the entire image is displayed at the top-left corner of the window. To display the image at other locations within the window (perhaps to center it), use the image->wx and image->wy fields of the FL_IMAGE structure. These two fields specify where in the window the origin of the image should be. By repeatedly changing image->wx and image->wy and displaying, image panning can be implemented.

It is also possible to display a subimage by specifying non-zero value for (image->sx,image->sy) and (image->sw, image->sh). You can view the image as a 2D space with the origin at the top left corner. The positive y axis of the image space is pointing downward. (image->sx,image->sy) specify the subimage offset into the image (they must be non-negative) and (image->sw,image->sh) specify the width and height of the subimage. Taken the window offset and the subimage together, the more accurate statement of the functionality of the the function flimage_display() is that it displays a subimage specified by (image->sx,image->sy) and (image->sw,image->sh) starting at (image->wx, image->wy).

You can also use clipping to display a subimage by utilizing the following functions and image->gc

     fl_set_gc_clipping(image->gc, x, y, w, h);
     fl_unset_gc_clipping(image->gc);

where the coordinates are window coordinates. Of course, by manipulating image->gc directly, more interesting clipping or masking can be achieved. Since the GC is visual dependent, a newly created image before displaying may not yet have a valid GC assoiated with it. If you must set some clipping before displaying, you can set the image->gc yourself beforehand. Note that you if you free the GC, make sure you reset it to None.

To display an image in a canvas, the following can be used

     flimage_display(image, FL_ObjWin(canvas));

Since this function only knows about window IDs, and writes to the window directly, it may not be sensitive to the status of the form the canvas is on, e.g., a frozen form. In your application, you should check the status of the form before calling this function.

Sometimes it may be useful to find out if a specific file is an image file before attempting to read it (for example, as a file filter). To this end, the following routine exists

     int flimage_is_supported(const char *file);

The function returns true if the specified file is a known image file. If the file is not a known image or not readable for any reason, the function return 0.

The FL_IMAGE Structure
======================

Before we go into more details on image support, some comments on the image structure are in order. The image structure contains the following basic fields that describe fully the image in question and how it should be displayed.

     typedef unsigned char FL_PCTYPE;         /* primary color type */
     #define FL_PCBITS     8                  /* primary color bits */
     #define FL_PCMAX      ((1<<FL_PCBITS)-1) /* primary color max val */
     typedef unsigned int  FL_PACKED;         /* packed RGB(A) type */

     typedef struct flimage_ {
         int               type;
         int               w,
                           h;
         void            * app_data;
         void            * u_vdata;
         unsigned char  ** red;
         unsigned char  ** green;
         unsigned char  ** blue;
         unsigned char  ** alpha;
         unsigned short ** ci;
         unsigned short ** gray;
         FL_PACKED      ** packed;
         short           * red_lut;
         short           * green_lut;
         short           * blue_lut;
         short           * alpha_lut;
         int               map_len;
         int               colors;
         int               gray_maxval;
         int               app_background;
         int               wx,
                           wy;
         int               sx,
                           sy;
         int               sw,
                           sh;
         char            * comments;
         int              comments_len;
         void            * io_spec;
         int               spec_size;
         int               (*display) (struct flimage_ *, FL_WINDOW win);
         struct flimage_ * next;
         int               double_buffer;
         unsigned long     pixmap;
         /* more stuff omitted */
     } FL_IMAGE;

The meaning of each field is as follows:

type
     This field specifies the current image type and storage (1bit, 24bit etc. See next section for details). The image type also indicates implicitly which of the pixel fields should be used.

w,h
     The width and height of the image.

app_data
     A field that's initialized at image creation. Its value can be set by the application prior to any existence of image. Once set, all images created thereafter will have the same value for this field. See Section later. The Forms Library does not modify or reference it once it's initialized.

u_vdata
     A field for use by the application. This field is always initialize to null. The Forms Library does not reference or modify it.

red, green, blue, alpha
     This first three fields are the color components of a 24 bit image, each of which is a 2-dimensional array. The 2D array is arranged so the image runs from left to right and top to bottom. For example, the 3rd pixel on the 10th row is composed of the following RGB elements: (red[9][2],green[9][2],blue[9][2]). Note however, these fields are meaningful only if the image type is FL_IMAGE_RGB. Although it's always allocated for a 24bit image, alpha is currently not used by the Forms Library

ci
     The field are the pixel values for a color index image (image type FL_IMAGE_CI). The field is also a 2-dimensional array arranged in the same way as the fields red, green and blue, i.e., the image runs from left to right, top to bottom. For example, ci[3][9] should be used to obtain the 10th pixel on the 4th row. To obtain the RGB elements of a pixel, the pixel value should be used as an index into a lookup table specified by the fields red_lut, green_lut and blue_lut. Although ci can hold an unsigned short, only the lower FL_LUTBITS (12) bits are supported, i.e., the color index should not be bigger than 4095.

gray
     This field, again a 2-dimensional array, holds the pixels of a gray image. The pixel values are interpreted as intensities in a linear fashion. Two types of gray images are supported, 8 bit (FL_IMAGE_GRAY) and 16 bit (FL_IMAGE_GRAY16). For 16 bit gray image, the actual depths of the image is indicated by member gray_maxval. For example, if gray_maxval is 4095, it is assumed that the actual pixel value ranges from 0 to 4095, i.e., the gray scale image is 12 bit. For 8 bit grayscale image, gray_maxval is not used. This means that the type FL_IMAGE_GRAY is always assumed to be 8 bit, the loading and creating routine should take care to properly scale data that are less than 8 bit.

gray_maxval
     This field is meaningful only if the image type is FL_IMAGE_GRAY16. It specifies the actual dynamic range of the gray intensities. Its value should be set by the image loading routines if the gray image depth is more than 8 bits.

ci_maxval
     This field by default is 256, indicating the maximum value of the color index.

packed
     This field (a 2-dimensional array) holds a 24 bit/32 bit image in a packed format. Each element of the 2D array is an unsigned integer (for now) that holds the RGB, one byte each, in the lower 24 bits of the integer. The topmost byte is not used. The macro FL_PACK(r, g, b) should be used to pack the triplet (r, g, b) into a pixel and FL_UNPACK(p, r, g, b) should be used to unpack a pixel. To obtain individual primary colors, the macros FL_GETR(p), FL_GETG(p) and FL_GETB(p) are available.

     Note that the use of the macros to pack and unpack are strongly recommended. It will isolate the application program from future changes of the primary color type (for example, 16-bit resolution for R,G and B).

red_lut, green_lut, blue_lut, alpha_lut
     These are the lookup tables for a color index image. Each of the table is a 1D array of length image->map len. Although alpha lut is always allocated for a color index image, it's currently not used by the Forms Library.

map_len
     The length of the colormap (lookup table).

app_background
     A packed RGB value indicating the preferred color to use for the background of an image (also known as transparent color). This field is initialized to an illegal value. Since there is no portable way to obtain the window background the application has to set this field if transparency is to be achieved. In future versions of image support, other means of doing transparency will be explored and implemented.

wx, wy
     The window offset to use to display the image.

sx, sy, sw, sh
     The subimage to display.

comments
     This is typically set by the loading routines to convey some information about the image. The application is free to choose how to display the comment, which may have embedded newlines in it.

io_spec
     This field is meant for the reading/writing routine to place format specific state information that otherwise needs to be static or global.

spec_size
     This field should be set to the number of bytes io_spec contains.

display
     A function you can use to display an image. The image loading routine sets this function.

next
     This is a link to the next image. This is how flimage_load() chains multiple image together.

double_buffer
     If true, the display function will double-buffer the image by using a pixmap. For typical image display it's not necessary to enable double-buffering as it is very expensive (memory and speed). Double-buffering may be useful in image editing.

pixmap
     The backbuffer pixmap if double-buffered.

Although it is generally not necessary for an application to access individual pixels, the need to do so may arise. In doing so, it is important to consult the image->type field before dereferencing any of the pixel field. That is, you should access image->ci only if you know that the image type is FL_IMAGE_CI or FL_IMAGE_MONO.

Supported image types
=====================

Forms Library supports all common and not-so-common image types. For example, the supported images range from the simple 1 bit bitmap to full 24 bit RGB images. 12 bit gray scale images (common in medical imaging) are also supported.

The supported image types are denoted using the following constants, all of them (except FL_IMAGE_FLEX) using a different bit, so they can be bitwise ORed together:

     FL_IMAGE_MONO,    /* 1 bit bitmaps */
     FL_IMAGE_GRAY,    /* gray-scale image (8 bit) */
     FL_IMAGE_GRAY16,  /* gray-scale image (9 to 16 bit) */
     FL_IMAGE_CI,      /* generic color index image */
     FL_IMAGE_RGB,     /* 24 bit RGB(A) image */
     FL_IMAGE_PACKED,  /* 24 bit RGB(A) image. Packed storage */
     FL_IMAGE_FLEX,    /* All of the above */

For the 24 bit variety another 8 bit (image->alpha and the top-most byte of the packed integer) is available for the application, perhaps storing the alpha values into it. The Forms Library does not modify or reference this extra byte.

Mono (b&w) images are stored as a colormap image with a lut of length 2.

The FL_IMAGE_FLEX type is mainly for the reading and loading routines to indicate the types they are capable of handling. For example, if you're coding an output routine, you use FL_IMAGE_FLEX to indicate that the output routine can take any type the image. Otherwise the driver will convert the image type before handing the image over to the actual output routine.

In displaying an image of type FL_IMAGE_GRAY16, window leveling, a technique to visualize specific ranges of the data, is employed. Basically, you specify a window level (level) and a window width (wwidth) and the display function will map all pixels that fall within level-width/2 and level+width/2 linearly to the whole dynamic range of the intensities the hardware is capable of displaying. For example, if the display device can only display 256 shades of gray, level-width/2 is mapped to 0 and level+width/2 is mapped to 255, and pixels values between level-width/2 and level+width/2 are linearly mapped to values between 0 and 255. Pixel values that fall below level-width/2 are mapped to zero and those that larger than level+width/2 are mapped to 255.

Use the following routine to set the window level

     int flimage_windowlevel(FL_IMAGE *im, int level, int wwidth);

The function returns 1 if window level parameters are modified, otherwise 0 is returned. Setting wwidth to zero disables window leveling. Note that if im points to a multiple image, window level parameters are changed for all images.

To obtain the image type name in string format, e.g., for reporting purposes, use the following routine

     const char *flimage_type_name(int type);

To convert between different types of images, the following routine is available

     int flimage_convert(FL_IMAGE *image, int newtype, int ncolors);

The parameter newtype should be one of the supported image types mentioned earlier in this section. Parameter ncolors is meaningful only if newtype is FL_IMAGE_CI. In this case, it specifies the number of colors to generate, most likely from a color quantization process. If the conversion is successful a non-negative integer is returned, otherwise a negative integaer. Depending on which quantization function is used, the number of quantized colors may not be more than 256.

To keep information loss to a minimum, flimage_convert() may elect to keep the original image in memory even if the conversion is successful. For example, converting a full color image (24 bit) into a 8 bit image and then converting back can lose much information of the image if the converting function does not keep the original image.

What this means is that the following sequence gets back the original image

     /* the current image is RGB. Now we reduce the full color
        image to 8 bit color index image. The conversion routine
        will keep the 24 bit color. */

     flimage_convert(image, FL_IMAGE_CI, 256);

     /* Now convert back to RGB for image processing. The con-
        version routine will notice that the input image was
        originally converted from a 24bit image. Instead of
        doing the conversion, it simply retrieves the saved
        image and returns. */

     flimage_convert(image, FL_IMAGE_RGB, 0);

This behavior might not always be what the application wants. To override it, you can set image->force_convert to 1 before calling the conversion routine. Upon function return the flag is reset to zero.

Creating Images
===============

With the basic fields in the image structure and image types explained, we're now in a position to tackle the problem of creating images on the fly. The data may have come from some simulations or some other means, the task now is to create an image from the data and try to display/visualize it.

The first task involved in creating an image is to create an image structure that is properly initialized. To this end, the following routine is available

     FL_IMAGE *flimage_alloc(void);

The function returns a pointer to a piece of dynamically allocated memory that's properly initialized.

The task next is to put the existing data into the structure. This involves several steps. The first step is to figure out what type of image to create. For scalar data, there are two logical choices, either a gray-scale intensity image or a color index image with the data being interpreted as indices into some lookup table. Both of these may be useful. Gray-scale imagse are straight forward to create and the meaning of the pixel values is well defined and understood. On the other hand with color-mapped image you can selectively enhance the data range you want to visualize by choosing appropriate color-maps. For vector data, RGB image probably makes most sense. In any case it's strictly application's decision. All that is needed to make it work with Forms Library is to set the image->type field to a valid value. Of course the image dimension (width and height) also needs to be set. Once this is done, we need to copy the data into the image structure.

Before we copy the data we create the destination storage using one of the following routines

     void *fl_get_matrix(int nrows, int ncols, unsigned int elem_size);
     int flimage_getmem(FL_IMAGE *image);

The fl_get_matrix() function creates a 2-dimensional array of entities of size elem_size. The array is of nrows by ncols in size. The 2D array can be passed as a pointer to pointer and indexed as a real 2D arrays. The flimage_getmem() routine allocates the proper amount of memory appropriate for the image type, including colormaps when needed.

After the destination storage is allocated, copying the data into it is simple

     image->type = FL_IMAGE_GRAY;
     image->w    = data_columns;
     image->h    = data_row;
     flimage_getmem(image);
     /* or you can use the instead
       im->gray = fl_get_matrix(im->h, im->w, sizeof **im->gray);
     */

     for (row = 0; row < image->h; row++)
         for (col = 0; col < image->w; col++)
             image->gray[row][col] = data_at_row_and_col;

Of course, if data is stored row-by-row, a memcpy(3) instead of a loop over columns may be more efficient. Also if your data are stored in a single array, fl_make_matrix() might be a lot faster as it does not copy the data.

If the created image is a color index image, in addition to copying the data to image->ci, you also need to set the lookup table length image->map_len, which should reflect the dynamic range of the data:

     image->type    = FL_IMAGE_CI;
     image->w       = A;
     image->h       = B;
     image->map_len = X;
     flimage_getmem(image);  /* this will allocate ci and lut */

     for (row = 0; row < image->h; row++)
         for (col = 0; col < image->w; col++)
             image->ci[row][col] = data;

     for (i = 0; i < image->map_len; i++) {
        image->red_lut[i]   = some_value_less_than_FL_PCMAX;
        image->green_lut[i] = some_value_less_than_FL_PCMAX;
        image->blue_lut[i]  = some_value_less_than_FL_PCMAX;
     }

If the type is FL_IMAGE_GRAY16, you also need to set image->gray_maxval to the maximum value in the data.

Now we're ready to display the image

     flimage_display(image, win);

As mentioned before, the display routine may create a buffered, display hardware specific and potentially lower-resolution image than the original image. If for any reason, you need to modify the image, either the pixels or the lookup tables, you need to inform the library to invalidate the buffered image:

     image->modified = 1;

Supported Image Formats
=======================

There are many file formats for image storage. The popularity, flexibility and cleanness of the different formats varies. Forms Library supports several popular ones, but these are not the only ones that are popular. Toward the end of this section, it will be outlined how to extend the image support in the Forms Library so more image file can be read by flimage_load().

Built-in support
----------------

Each image file format in Forms Library is identified by any one of three pieces of information, the formal name, the short name, and the file extension. For example, for the GIF format, the formal name is "CompuServe GIF" (The Graphics Interchange Format (c) is the Copyright property of CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated.), the short name is "GIF", and file extension is "gif". This information is used to specify the output format for flimage_dump().

The following table summarizes the supported file formats with comments

FormalName
ShortName
Extension
Comments
Portable Pixmap
ppm
ppm
Portable Graymap
pgm
pgm
Portable Bitmap
pbm
pbm
CompuServe GIF
gif
gif
Windows/OS2 BMP file
bmp
bmp
JPEG/JFIF format
jpeg
jpg
X Window Bitmap
xbm
xbm
X Window Dump
xwd
xwd
X PixMap
xpm
xpm
XPM3 only
NASA/NOST FITS
fits
fits
Standard FITS and IMAGE extension
Portable Network Graphics
png
png
needs netpbm
SGI RGB format
iris
rgb
need pbmplus/netpbm package
PostScript format
ps
ps
needs gs for reading
Tagged Image File Format
tiff
tif
no compression support

 To avoid executable bloating with unnecessary code, only ppm, pgm, pbm and compression filters (gzip and compress) are enabled by default. To enable other formats, call flimage_enable_xxx() once anywhere after fl_initialize(), where xxx is the short name for the format. For example, to enable BMP format, flimage_enable_bmp() should be called.

Further, if you enable GIF support, you're responsible for any copyright/patent and intellectual property dispute arising from it. Under no circumstance should the authors of the Forms Library be liable for the use or misuse of the GIF format.

Usually there are choices on how the image should be read and written. The following is a rundown of the built-in options that control some aspects of image support. Note that these options are persistent in nature and once set they remain in force until reset.

     typedef struct {
         int quality;
         int smoothing;
     } FLIMAGE_JPEG_OPTIONS;

     void flimage_jpeg_output_options(FLIMAGE_JPEG_OPTIONS *option);

The default quality factor for JPEG output is 75. In general, the higher the quality factor rhe better the image is, but the file size gets larger. The default smoothing factor is 0.

     void flimage_pnm_output_options(int raw_format);

For PNM (ppm, pgm, and pbm) output, two variants are supported, the binary (raw) and ASCII format. The raw format is the default. If the output image is of type FL_IMAGE_GRAY16, ASCII format is always output.

     void flimage_gif_output_options(int interlace);

If interlace is true, an interlaced output is generated. Transparency, comments, and text are controlled, respectively, by image->tran_rgb, image->comments and image->text.

PostScript options affect both reading and writing.

     FLIMAGE_PS_OPTION *flimage_ps_options(void);

where the control structure has the following members

int orientation
     The orientation of the generated image on paper. Valid options are FLPS_AUTO, FLPS_PORTRAIT and FLPS_LANDSCAPE. The default is FLPS_AUTO.

int auto_fit
     By default, the output image is scaled to fit the paper if necessary. Set it to false (0) to turn auto-scaling off.

float xdpi, ydpi
     These two are the screen resolution. Typical screens these days have resolutions about 80 dpi. The settings of these affect both reading and writing.

float paper_w
     The paper width, in inches. The default is 8.5 in.

float paper_h
     The paper height, in inches. The default is 11.0 in

char* tmpdir
     A directory name where temporary working files go. The default is /tmp.

float hm, vm
     Horizontal and vertical margins, in inches, to leave when writing images. The default is 0.4 in (about 1 cm).

float xscale
     Default is 1.0.

float yscale
     Default is 1.0.

int first_page_only
     If set, only the first page of the document will be loaded even if the document is multi-paged. The default setting is false.

To change an option, simply call flimage_ps_options() and change the field from the pointer returned by the function:

     void SetMyPageSize(float w, float h) {
         FLIMAGE_PS_OPTION *options = flimage_ps_options();

         options->paper_w = w;
         options->paper_h = h;
     }

All these option setting routines can be used either as a configuration routine or an image-by-image basis by always calling one of these routines before flimage_dump(). For example,

     flimage_jpeg_output_options(option_for_this_image);
     flimage_dump(im, "file","jpeg");

You can also utilize the image->pre_write function to set the options. This function, if set, is always called inside flimage_dump() before the actual output begins.

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.

Queries
-------

Since the number of formats supported by the library is dynamic in nature, some query routines are available to obtain support information.

To obtain the number of currently supported image formats, use the routine

     int flimage_get_number_of_formats(void);

The functions returns the number of formats supported, for reading or writing or both. To obtain detailed information for each format, the following can be used

     typedef struct {
         const char * formal_name;
         const char * short_name;
         const char * extension;
         int          type;
         int          read_write;
         int          annotation;
     } FLIMAGE_FORMAT_INFO;

     const FLIMAGE_FORMAT_INFO *flimage_get_format_info(int n);

where parameter n is an integer between 1 and the return value of flimage_get_number_of_formats() . Upon function return a static buffer is returned containing the basic information about the image. The read_write field can be one of the following combinations thereof

FLIMAGE_READABLE
     supports reading

FLIMAGE_WRITABLE
     supports writing

or the bitwise OR of both.

These two routines are most useful for reporting or presenting capabilities to the user

     FLIMAGE_FORMAT_INFO *info;
     int n = flimage_get_number_of_formats();

     fprintf(stderr,"FL supports the following format\n");
     for (; n; n--) {
         info = flimage_get_format_info(n);
         fprintf(stderr,"%s format\t(%c%c)\n",
                 info->short_name,
                 (info->read_write & FLIMAGE_READABLE) ? 'r' : ' ',
                 (info->read_write & FLIMAGE_WRITABLE) ? 'w' : ' ');
     }

Setup and Configuration
=======================

Although the image support is designed with integration into a GUI system in mind, it neither assumes what the GUI system is nor does it need a GUI system to work. As a matter of fact, for the most part it doesn't even need an X connection to work (obviously without a connection, you won't be able to display images). For this reason, some of the typical (and necessary) tasks, such as progress and error reporting, are by default implemented only to use text output (i.e., to stderr). Obviously, with a GUI in place this is not quite adequate. Hooks are available for application program to re-define what to do with these tasks.

The interface to the library configuration is as follows

     void flimage_setup(FLIMAGE_SETUP *setup);

where the parameter setup is a pointer to a structure defined as follows:

     typedef struct {
         void       * app_data;
         int          (*visual_cue) (FL_IMAGE *im, const char *msg);
         void         (*error_message) (FL_IMAGE *im, const char *msg);
         const char * rgbfile;
         int          do_not_clear;
         int          max_frames;
         int          delay;
         int          double_buffer;
         int          add_extension;
     } FLIMAGE_SETUP;

with

app_data
     The application can use this field to set a value so the field image->app_data in all image structures returned by the library will have this value. It's most useful to set this field to something that's persistent during the application run, such as the fdui structure of the main control panel.

     Note that image->app_data is different from image->u_vdata in that all image structures returned by the library have the same value of image->app_data, which is set by the library. In contrast, image->u_vdata is set by the application on an image-by-image basis.

visual_cue
     This is the function that will be called by all image reading, writing and processing routines. The function is meant to give the user some visual feedback about what is happening. For lengthy tasks, this function is called repeatedly and periodically to indicate what percentage of the task is completed and to give the application program a chance to check and process GUI activities (for example, via fl_check_forms()).

     The first parameter to the function is the image currently being worked on and the second parameter is a short message, indicating the name of the task, such as "Reading JPG" etc.

     Two fields in the image structure can be used to obtain progress information. The member fields image->total indicates the total amount of work to be done in some arbitrary units (usually number of rows in the image). image->completed indicates how much of the task has been completed. The percentage of how much is completed is then simply the ratio of image->completed and image->total, multiplied by 100.

     At the begin of a task image->completed is set to a value less or equal 1, and at the end of the task, image->completed is set to image->total.

     A special value of -1 for image->completed may be used to indicate a task of unknown length.

error_message
     This is a function that is called when an error (of all severities) has occurred inside the library. It is recommanded that the application provide a means to show the messages to the user by sypplying this function.

     The first parameter is a pointer to the image that's being worked on, and the second parameter is a brief message, such as "memory allocation failed" etc.

     A convenience function, flimage_error(), is provided to call the error message handler.

rgbfile
     This field should be set to the full path to the color name database (rgb.txt) if your system has it in non-standard locations. On most systems, this file is /usr/lib/X11/rgb.txt, which is the default if this field is not set. (The routine where this field is used searches some more locations than the default and should work on most systems automagically.)

do_not_clear
     By default, flimage_display() clears the window before displaying the image. Set this member to 1 to disable window clearing.

no_auto_extension
     By default, flimage_dump() changes the filename extension to reflect the format. Set this member to 1 to disable extension substitution.

double_buffer
     If set, all image display will by default double-buffered. Double-buffering an image is very expensive (in terms of both resource and speed) as the backbuffer is simulated using a pixmap. If there are no annotations, double-buffering an image does not really improve anything.

     It is far better to turn double-buffering on and off on a image-by-image basis using the image->double_bufffer field.

max_frames
     This field specifies the maximum number of frames to read by flimage_load(). The default maximum is 30 frames.

delay
     This field specifies the delay (in milliseconds) between successive frames. It is used by the flimage_display() routine.

Note that it is always a good idea to clear the setup structure before initializing and using it

     FLIMAGE_SETUP mysetup;
     memset(mysetup, 0, sizeof mysetup);

     mysetup.max_frames = 100;
     mysetup.delay      = 10;

     flimage_setup(&mysetup);

It is possible to modify the image loading process by utilizing the following routines flimage_load() is based on:

     FL_IMAGE *flimage_open(const char *name);

This function takes a file name and returns an image sturcture pointer if the file is a recognized image file. Otherwise NULL is returned.

The function

     FL_IMAGE *flimage_read(FL_IMAGE *im);

takes an image structure returned by flimage_open() and fills the image structure. Between flimage_open() and flimage_read() you can inspect or modify fields in the image structure.

     int flimage_close(FL_IMAGE *im);

This function closes all file streams used to create the image.

Simple Image Processing
=======================

Some simple image processing capabilities are present in the Forms Library image support. All the image processing routines take an image as a parameter and process it in place. If appropriate, only the subimage specified by (image->subx, image->suby) and (image->subw, image->subw) is affected (note these are different fields from those for subimage displaying). The subimage fields are best set via user interaction, perhaps by having a rubber band that the user can drag to set the size.

In the following, each routine will be briefly explained.

Convolution
-----------

Convolution or filtering can be done easily using the following routine

     int flimage_convolve(FL_IMAGE *im, int **kernel,
                          int krow, int kcol);

This function takes a convolution kernel of krow by kcol and convolves it with the image. The result replaces the input image. The kernel size should be odd. If successful, the function returns a positive integer, otherwise a negative number. The kernel should be allocated by fl_get_matrix(). To use a kernel that's a C 2-dimensional array (cast to a pointer to int), use the following function

     int flimage_convolvea(FL_IMAGE *im, int *kernel,
                           int krow, int kcol);

The difference between these two functions is in their usage syntax:

     int **kernel1 = fl_get_matrix(sizeof **kernel, n, m);
     int kernel2[n][m];
     kernel1[x][y] = z;
     kernel2[x][y] = z;
     flimage_convolve(im, kernel1, n, m);
     flimage_convolvea(im, (int*) kernel2, n, m); /* note the cast */

Two special built-in kernels are designated with the following symbolic constants

FLIMAGE_SMOOTH
     indicates a 3 by 3 smoothing kernel

FLIMAGE_SHARPEN
     indicates a 3 by 3 sharpening kernel

Tint
----

Tint as implemented in the Forms Library emulates the effect of looking at an image through a piece of colored glass. You can specify the color and transparency of the glass:

     int flimage_tint(FL_IMAGE *im, unsigned int packed, double opacity);

where the parameter packed is a packed RGB color, specifying the color of the glass. opacity specifies how much the color of the image is absorbed by the glass. A value of 0 means the glass is totally transparent, i.e., the glass has no effect3, while a value of 1.0 means total opaqueness, i.e., all you see is the color of the glass. Any value between these two extremes results in a color that is a combination of the pixel color and the glass color. For example, to tint a part of the image bluish, you can set packed to FL_PACK(0,0,200) and use an opacity of 0 (Strictly speaking, a piece of glass that is totally transparent can't have colors.).

Tint is most useful in cases where you want to put some annotations on the image, but do not want to use a uniform and opaque background that completely obscures the image behind. By using tint, you can have a background that provides some contrast to the text, yet not obscures the image beneath completely.

Tint operation uses the subimage settings.

Rotation
--------

Image rotation can be easily done with the following routine

     int flimage_rotate(FL_IMAGE *im, int angle, int subpixel);

where angle is the angle in one-tenth of a degree (i.e., a 45 degree rotation should be specified as 450) with a positive sign for counter-clock rotation. The parameter subpixel should be one of the following, specifying if subpixel sampling should be enabled. It can be set to either FLIMAGE_NOSUBPIXEL or FLIMAGE_SUBPIXEL.

If subpixel sampling is enabled, the resulting image pixels are interpolated from the original pixels. This usually has an "anti-aliasing" effect that leads to less severe jagged edges and similar artifacts commonly encountered in rotations. However, it also means that a color indexed image gets converted to a RGB image. If preserving the pixel value is important, you should not turn subpixel sampling on.

flimage_rotate() return a negative number if it for some reason (usually due to running out of memory) fails to perform the rotation.

Since the rotated image has to be on a rectangular grid, the regions that are not occupied by the image are filled with a fill color, where the default is black. If a different fill color is desired you can set the image->fill_ccolor field to a packed RGB color before calling the rotation function. Note that even for color indexed images the fill color should be specified in RGB. The rotation function will search the colormap for the appropriate index if no subpixel sampling is used.

Repeated rotations should be avoided if possible. If you have to call it more than once it's a good idea to crop after rotations in order to get rid of the regions that contain only fill color.

Image Flipping
--------------

Image flipping refers to the mirror operation in x- or y-direction at the center. For example, to flip the columns of an image, the left and right of the image are flipped (just like having a vertical mirror in the center of the image) thus the first pixel on any given row becomes the last, and the last pixel becomes the first etc.

The API for flipping is as follows

     int flimage_flip(FL_IMAGE *im, int what);

where what can be 'c' or 'r'. indicating if column and row flipping is desired.

Cropping
--------

There are two functions available to crop an image

     int flimage_autocrop(FL_IMAGE *im, unsigned int background);
     int flimage_crop(FL_IMAGE *im, int xl, int yt, int xr, int yb);

The first function, as its name suggests, automatically crops an image using the background as the color to crop. The function works by searching the image from all four sides and removing all contiguous regions of the uniform background from the sides. The image is modified in place. If cropping is successful, a non-negative integer is returned, otherwise -1. If background is specified as the constant FLIMAGE_AUTOCOLOR, the background is chosen as the first pixel of the image.

The second function uses the parameters supplied by the user to crop the image. xl and xr are the offsets into the image from the left and the right sides, respectively, i.e., if both xl and xr are 1, the cropping removes the first column and the last column from the image. Parameters yt and yb specify the offsets into the image from the top and bottom of the image respectively.

Note the offsets do not have to be positive. When they are negative, they indicate enlargement of the image. The additional regions are filled with the uniform color specified by image->fill_color, a packed RGB color. This can be quite useful to add a couple of pixels of border to an image. For example, the following adds a 1 pixel wide yellow border to an image

     image->fill_color = FL_PACK(255,255,0);
     flimage_crop(image, -1, -1, -1, -1);

Another function is available that can be used to obtain the auto-cropping offsets

     int flimage_get_autocrop(FL_IMAGE *im, unsigned background,
                              int *xl, int *yt, int *xl, int *yb);

This function works the same way as flimage_autocrop(), except that no actual cropping is performed. Upon function return the parameters xl, yt, xl and yb are set to the offsets found by the function. The application can then make adjustment to these offsets and call flimage_crop().

Scaling
-------

An image can be scaled to any desired size with or without subpixel sampling. Without subpixel sampling simple pixel replication is used, otherwise a box average algorithm is employed that yields an anti-aliased image with much less artifacts. A special option is available that scales the image to the desired size but keeps the aspect ratio of the image the same by filling the part of the image that would otherwise be empty.

The main entry point to the scaling function is

     int flimage_scale(FL_IMAGE *im, int newwidth, int newheight,
                       int option);

where the parameters newwidth and newheight specify the desired image size. Parameter optionq can be one of the following constants or the bitwise OR of them:

FLIMAGE_NOSUBPIXEL
     scale the image with no subpixel sampling

FLIMAGE_SUBPIXEL
     scale the image with subpixel sampling

FLIMAGE_ASPECT
     scale the image with no aspect ratio change

FLIMAGE_CENTER
     center the scaled image if aspect

FLIMAGE_NOCENTER
     do not center the scaled image

For example, FLIMAGE_ASPECT|FLIMAGE_SUBPIXEL requests fitting the image to the new size with subpixel sampling. FLIMAGE_ASPECT specifies a scaling that results in an image of the requested size (even if the scales are different for width and height) without changing the aspect ratio of the original image by filling in the stretched regions with the fill color image->fill_color, a packed RGB color:

     im->fill_color = FL_PACK(255,0,0);
     flimage_scale(im, im->w+2, im->h, FLIMAGE_SUBPIXEL|FLIMAGE_ASPECT);

This code generates an image that is two pixels wider than the original image but with the same aspect ratio. The two additional pixel columns on each side of the image are filled with the fill color (red), yielding a red border. The fitting can be useful in turning a series of images of unequal sizes into images of equal sizes with no perceptible change in image quality.

Depending on what the application requires, simple scaling (zooming) with no subpixel sampling is much faster than box averaging or blending, but subpixel sampling tends to yield smoother images with less scaling artifacts.

Warping
-------

Image warping (or texture mapping in 2D) refers to the transformation of pixel coordinates. Rotation, scaling and shearing etc. are examples of (linear and non-perspective) image warping. In typical applications some of the commonly used pixel coordinate transformations are implemented using more efficient algorithms instead of a general warping. For example, image rotation is often implemented using three shears rather than a general warp (Forms Library implements rotation via image warping).

Non-perspective linear image warping in general is characterized by a 2x2 warp matrix W and a translation vector T with two elements as follows

            P' = W * P + T

where P is a vector describing a position via it's x and y coordinates and P' is the position after warping.

The elements w[i][j] of the warp matrix are constants (if the warp matrix isn't constant or is of higher order, we usually call such a transformation morphing rather than warping). Since our destination for the warped image is an array of pixels rather than a properly defined coordinate system (such as a window) the translation has no meaning. For the following discussion, we assume the translation vector is zero. (In doing the actual warping, the warped image is indeed shifted so it starts at the (0,0) element of the array representing it).

Although, theoretically, any 2D matrix can be used as a warp matrix, there are practical constraints in image warping due to the discreteness of pixel coordinates. First of all, we have to snap all pixel coordinates onto a 2D rectangular integer grid. This in general will leave holes in the warped image because two pixels may get mapped to a single destination location, leaving a hole in the destination image. Secondly, truncation or rounding the resulting floating point values introduces errors. Because of these reasons, image warping is performed in reverse. That is, instead of looping over all pixel coordinates in the original image and transforming those into new coordinates, we start from the new coordinates and use inverse warping to obtain the coordinates of the pixel in the original image. This requires that the inverse of the warp matrix must exist (which is the case if w[0][0] * w[1][1] != w[0][1] * w[1][0], i.e., the warp matrix has a non-vanishing determinante). With inverse warping the transformation becomes a re-sampling of the original image, and subpixel sampling (anti-aliasing) can be easily implemented.

The following function is available in the library to perform warping

     int flimage_warp(FL_IMAGE *im, float matrix[][2],
                      int neww, int newh, int subpixel);

where matrix is the warp matrix. neww and newh specify the warped image size. To have the warp function figure out the minimum enclosing rectangle of the warped image you can pass zeros for the new width and height. Nevertheless, you can specify whatever size you want and the warp function will fill the empty grid location with the fill color. This is how the aspect ratio preserving scaling is implemented.

In general, the warped image will not be rectangular in shape. To make the image rectangular the function fills the empty regions. The fill color is specified by setting the image->fill_color field with a packed RGB color.

The last argument, subpixel specifies if subpixel sampling should be used. Although subpixel sampling adds processing time, it generally improves image quality significantly. The valid values for this parameter is any logical OR of FLIMAGE_NOSUBPIXEL, FLIMAGE_SUBPIXEL and FLIMAGE_NOCENTER.

FLIMAGE_NOCENTER is only useful if you specify an image dimension that is larger than the warped image, and in that case the warped image is flushed top-left within the image grid, otherwise it is centered.

To illustrate how image warping can be used, we show how an image rotation by an angle deg can be implemented:

     float m[2][2];
     m[0][0] = m[1][1] = cos(deg * M_PI / 180.0);
     m[0][1] = sin(deg * M_PI / 180.0);
     m[1][0] = -m[0][1];

     flimage_warp(im, mat, 0, 0, FLIMAGE_SUBPIXEL);

Please note that the transformation is done in-place, i.e., after the function returns the image structure pointer, im, points to the rotated image.

If you specify a warp matrix with the off-diagonal elements being zero (scaling matrix), the image will only be scaled (in x-direction by m[0][0] and in y-direction by m[1][1]) without being also rotated.

By experimenting with various warp matrices you can obtain some interesting images. Just keep in mind that large values of the warp matrix elements tend to make the final image larger.

General Pixel Transformation
----------------------------

Many image processing tasks can be implemented as seperate RGB transformations. These transformations can be done very efficiently through the use of lookup tables. For this reason the following routine exists:

     int flimage_transform_pixels(FL_IMAGE *im, int *red,
                                  int *green, int *blue);

where red, green and blue are the lookup tables of a length of at least FL_PCMAX + 1 (typically 256). The function returns a postive number on success and the image will be replaced. Note that this routine notices the settings of the subimage, i.e., you can transform a portion of the image.

To illustrate the use of this routine let's look at how a simple contrast adjustment may be implemented:

     #include <forms.h>
     #include <math.h>

     int AdjustContrast(FL_IMAGE *im) {
         int r[FL_PCMAX+1],
             g[FL_PCMAX+1],
             b[FL_PCMAX+1];
         int i,
             scale = 10;

         /* in this example rgb are adjusted the same way */
         for ( i = 0; i <= FL_PCMAX; i++)
             r[i] = g[i] = b[i] = i * log10(1 + i * scale / FL_PCMAX )
                                  / log10( 1 + scale );

         return flimage_transform_pixels(im, r, g, b);
     }

Image Annotation
----------------

You can annotate an image with text or simple markers (arrows etc.). The location of the annotation can either be in pixel coordinate system or some application defined coordinate system.

Using Text Strings
..................

To place text into the image, use the following routine

     int flimage_add_text(FL_IMAGE *im, const char *str, int len,
                          int fstyle, int fsize, unsigned tcolor,
                          unsigned bcolor, int nobk, double tx,
                          double ty, int rotation);

where fstyle and fsize are the same as the label font style and size defined earlier in Section 3.11.3. tcolor and bcolor specify the colors to use for the text str and the background if the nobk argument is false. If nobk is true the text is drawn without a background. tx and ty specify the location of the text relative to the image origin. The location specified is the lower-right corner of the text. Note that the location specified can be in some physical space other than pixel space. For example, if the pixel-pixel distance represents 10 miles on a map, you'd like to be able to specify the text location in miles rather than pixels. The location is converted into pixel space using the following code

         tx_pixel = im->xdist_scale * tx + im->xdist_offset;
         ty_pixel = im->ydist_scale * ty + im->ydist_offset;

By default, the offsets im->xdist_offset and im->yxdist_offset are initialized to 0 and the scales im->xdist_scale and im->ydist_scale to 1.

The function returns the current number of strings for the image. The interpretation of text used also used elsewhere applies, i.e., if str starts with character @ a symbol is drawn.

There is another function, maybe more convenient depending on the application, that you can use

     int flimage_add_text_struct(FL_IMAGE *im,
                                 const FLIMAGE_TEXT *text);

With this function instead of passing all the parameters individual;y you pass a FLIMAGE_TEXT structure to the function. The structure has the following fields:

str
     The string to append to the image.

len
     Length of the string in bytes.

x, y
     A location relative to the image origin, given in pixels (no conversion from other coordinate systems is done)

align
     Specifies the alignment of the string relative to the give location.

style, size
     The font style and size to use.

color
     The text color

bcolor
     The background color

nobk
     If true indicates that no background is to be drawn.

angle
     Angle (in thenth of a degree) the text is to be rotated from the default horizontal orientation. Currently only PostScript output handles this correctly.

To delete the all texts you added to an image, use

     void flimage_delete_all_text(FL_IMAGE *im);

You also can suppress the display of annotation text without deleting it. To do this, simply set im->dont_display_text to true.

Using Markers
.............

In addition to text strings you can also add simple markers (arrows, circles etc) to your image.

To add a marker to an image use the following routines

     int flimage_add_marker(FL_IMAGE *im, const char *name,
                            double x, double y, double w, double h,
                            int linestyle, int fill, int rotation,
                            FL_COLOR, FL_COLOR bcol);
     int flimage_add_marker_struct(FL_IMAGE *im, const FLIMAGE_MARKER *m);

where name is the marker name (see below for a list of built-in markers). The marker name must consist of regular ASCII characters. linestyle indicates the line style (FL_SOLID, FL DOT etc., see Chapter 27 for a complete list. fill indicates if the marker should be filled or not. x and y are the coordinates of the center of the marker in physical coordinates (i.e., the same transformation as described above for annotated texts is applied), w and h are the size of the bounding box of the marker, again in physical coordinates. Every marker has a natural orientation from which you can rotate it. The angle of rotation is given by rotation in tenth of a degree. col is the color of the marker, in packed RGB format. bcol is currently un-used.

The second function takes a structure that specifies the marker. The members of the structure are as follows:

name
     The name of the marker.

x, y
     Position of center of the marker in pixel coordinates, relative to the origin of the image.

w, h
     The size of the bounding box in pixel coordinates.

color
     The color of the marker in packed RGB format.

fill
     If true the marker is filled.

thickness
     The line line thickness used for drawing.

style
     The line style to be used for drawing.

angle
     Angle of rotation in tenth of a degree from the marker's nature orientation.

If successful both functions return the number of markers that are currently associated with the image, otherwise a negative number.

Some built-in markers in different orientations are shown in Fig. 22.1.

To delete all markers added to an image use the function

     void flimage_delete_all_markers(FL_IMAGE *im);

Of course the library would not be complete without the ability for applications to define new markers. The following function is provided so you can define your own markers:

     int flimage_define_marker(const char *name,
                               void (*draw) (FLIMAGE_MARKER *marker),
                               const char *psdraw);

When the marker is to be drawn the function draw() is called with the marker structure. In addition to the fields listed above the following fields are filled by the library to facilitate the operation of drawing the marker

display
     The display to be drawn on.

gc
     The GC to be used in drawing

win
     The window to draw to.

psdraw
     A string that draws a marker in a square with the corner coordinates (-1, -1), (-1, 1), (1, 1) and (1, -1) in PostScript. For example the rectangle marker has the following psdraw string:

               -1 -1 moveto
               -1  1 lineto
                1  1 lineto
                1 -1 lineto
                closepath

Defining new markers is the preferred method of placing arbitary drawings onto an image as it works well with double-buffering and pixelization of the markers.

Pixelizing the Annotation
.........................

Annotations placed on the image are kept seperate from the image pixels themselves. The reasons for doing so are twofold. First, keeping the annotation seperate makes it possible to later edit the annotations. The second reason is that typically the screen has a lower resolutions than other output devices. By keeping the annotations seperate from the pixels makes it possible to obtain better image qualities when the annotations are rendered on higher-resolution devices (for example a PostScript printer).

If for some reason making the annotations a part of the image pixels is desired, use the following routine

     int flimage_render_annotation(FL_IMAGE *image, FL_WINDOW win);

The function returns -1 if an error occurs. The parameter win is used to create the appropriate pixmap. After the function returns the annotations are rendered into the image pixels (thus an annotation or a part of it that was outside of the image is lost). Note that during rendering the image type may change depending on the capabilities of win. Annotations that were kept separately are deleted. Note that the image must have been displayed at least once prior to calling this function for it to work correctly.

You can always enlarge the image first via the cropping function with some solid borders. Then you can put annotation outside of the original image but within the enlarged image.

Not all image formats support the storage of text and markers. This means if you attempt to save an image that has associated text and markers into an image format that does not support it, you may lose the annotation. All pnm formats supports the storage of annotations. To find out if a particular format supports annotation storage, look at the annotation field of the FLIMAGE_FORMAT_INFO structure. A zero value indicates it does not support it.

Write Your Own Routines
-----------------------

The only communication required between an image processing routine and the rest of the image routines is to let the display routine know that the image has been modified by setting image->modified to 1. This information is used by the display routine to invalidate any buffered displayable images that were created from the original image. After displaying, image->modified is reset by the display routine.

Utilities
=========

In the following some of the utilities that may come in handy when you're writing image manipulation routines are described.

Memory Allocation
-----------------

To create a matrix to be used in several of the functions listed above use either fl_get_matrix() described above or

     void *fl_make_matrix(int nrow, int ncol, unsigned int esize,
                           void *inMem);

where nrow and ncol are the number of rows and columns of the matrix respectively. esize is the size (in bytes) of each matrix element.

Both functions return a two-dimensional array of entities of size esize. The first function initializes all elements to zero. The second function does not allocate nor initialize memory for the matrix itself. Instead it uses the memory with address inMem that is supplied by the caller, which should be a one-dimensional array of length nrow * ncol * esize.

You can use the returned pointer as a regular two-dimensional array (matrix[r][c]) or as a single array of length nrow *ncol, starting from at matrix[0]:

     short **matrix = fl_get_matrix(nrow, ncol, sizeof **matrix);

     /* access the matrix as a 2-d array */
     matrix[3][4] = 5;

     /* or access it as 1D array */
     *(matrix[0] + 3 * ncol + 4) = 5;

     /* most useful in image processing to use it as 1D array */

     memcpy(saved, matrix, nrow * ncol * sizeof **matrix);

To free a matrix allocated using one the above functions, use

     void fl_free_matrix(void *matrix);

The function frees all memory allocated. After the function returns the matrix cab not be de-referenced anymore. In the case where the matrix was created by fl_make_matrix() the function will only free the memory that's allocated to hold the matrix indices but not the memory supplied by the caller. It is the caller's responsibility to free that part of the memory.

There are also some useful functions that manipulate images directly. The following is a brief summary of them.

     FL_IMAGE *flimage_dup(FL_IMAGE *im);

This function duplicates an image im and returns the duplicated image. At the moment, only the first image is duplicated even if the input image has multiple frames. Furthermore, markers and annotations are not duplicated.

     Pixmap flimage_to_pixmap(FL_IMAGE *im, FL_WINDOW win);
     int flimage_from_pixmap(FL_IMAGE *im, Pixmap pixmap);

The first function converts an image into a Pixmap (a server side resource) that can be used in the pixmap object (see pixmap-class???).

The second function does the reverse. im must be a properly allocated image.

Color Quantization
------------------

In order to display a RGB image on a color-mapped device of limited depth, the number of colors in the original image will have to be reduced. Color quantization is one way of doing this.

Two color quantization algorithms are available in the Forms Library. One uses Heckbert's median cut algorithm followed by Floyd-Steinberg dithering after which the pixels are mapped to the colors selected. The code implementing this is from the Independent JPEG Group's two pass quantizer (jquant2.c in the IJG's distribution), which under copyright (c) 1991-1996 by Thomas G. Lane and the IJG.

Another method is based on the Octree quantization algorithm with no dithering and is implemented by Steve Lamont (spl@ucsd.edu) and is under vopyright (c) 1998 by Steve Lamont and the National Center for Microscopy and Imaging Research. This quantization library is available from ftp://ncmir.ucsd.edu/pub/quantize/libquantize.html. The quantizer based on this library is not compiled into the image support. The source code for using this quantizer is in image subdirectory.

By default, the median cut algorithm is used. You can switch to the octree based algorithm using the following call

     void flimage_select_octree_quantizer(void);

To switch back to the median cut quantizer use

     void flimage_select_mediancut_quantizer(void);

The median-cut quantizer tends to give better images because of the dithering step. However, in this particular implementation, the number of quantized colors is limited to 256. There is no such limit with the octree quantizer implementation.

Remarks
-------

See itest.c and ibrowser.c for example use of the image support in Forms Library. iconvert.c is a program that converts between different file formats and does not require an X connection.

Due to access limitations, not all combinations of display depth and bits per pixel (bpp) are tested. Depths of 1 bit (1 bpp), 4 bits (8 bpp), 8 bits (8 bpp), 16 bits (16 bpp), 24 bits (32 bpp), 30 bits (32 bpp) were tested. Although it works in 12 bit PseudoColor mode, due to limitations of the default quantizer the display function does not take full advantage of the larger lookup table. Special provisions were made so a gray12 image will be displayed in 4096 shades of gray if the hardware supports 12-bit grayscale.

If JPEG support (image_jpeg.c) is not compiled into the Forms Library, you can obtain the jpeg library source from ftp://ftp.uu.net/graphics/jpeg.