[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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.
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 fmt
q, 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.
FL_IMAGE
StructureBefore 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
.
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.
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; |
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()
.
37.5.1 Built-in support | ||
37.5.2 Adding New Formats | ||
37.5.3 Queries |
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"
(16), 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.
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.
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' : ' '); } |
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.(17)
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.
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.
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 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
FLIMAGE_SHARPEN
indicates a 3 by 3 sharpening kernel
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(18).
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.
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 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.
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()
.
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 option
q can be one of the
following constants or the bitwise OR of them:
FLIMAGE_NOSUBPIXEL
FLIMAGE_SUBPIXEL
FLIMAGE_ASPECT
FLIMAGE_CENTER
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.
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.
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); } |
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.
37.7.9.1 Using Text Strings | ||
37.7.9.2 Using Markers | ||
37.7.9.3 Pixelizing the Annotation |
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.
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.
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.
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.
In the following some of the utilities that may come in handy when you're writing image manipulation routines are described.
37.8.1 Memory Allocation | ||
37.8.2 Color Quantization | ||
37.8.3 Remarks |
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.
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.
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.
The Graphics Interchange Format (c) is the Copyright property of CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated.
The routine where this field is used searches some more locations than the default and should work on most systems automagically.
Strictly speaking, a piece of glass that is totally transparent can't have colors.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This document was generated by Build Daemon on October 16, 2020 using texi2html 1.82.