PIC(3) ... NAME pic - subroutines for device-independent picture I/O

advertisement
PIC(3)
UNIX Programmer's Manual
PIC(3)
NAME
pic - subroutines for device-independent picture I/O
OVERVIEW
pic is a package of subroutines for device-independent frame
buffer I/O and format-independent picture file I/O. It is
designed to provide a standard subroutine interface for computer graphics and image processing applications that manipulate raster images of 8, 24, or 32-bits per pixel. Application programs can be more portable if they do all their
raster graphics through this layer of routines rather than
making device or format-dependent calls. Device or file
format selection can be made at either compile-time or runtime.
To use the package, an application program first calls
pic_open, which opens a picture "device" for reading or
writing and returns a pointer that is passed in all subsequent pic calls. Several pictures can be open simultaneously so that one could, for example, be reading from two
picture devices (or files) of different types, and writing
to third device simultaneously. Following the pic_open call
but before pixel I/O the program typically reads or writes
various parameters of the picture. The parameters supported
by the pic library at present are:
nchan: int nchan;
Number of channels in the picture: 1, 3, or 4. 1 channel means intensity, perhaps color mapped, 3 channels
means RGB, and 4 channels means RGBA, where
A=alpha=opacity.
box: int xorig, yorig, xsize, ysize;
origin and size of picture, where (xorig,yorig) is the
upper left corner of the box and (xsize,ysize) is its
size.
After these parameters are read or written by the application, it typically reads or writes pixels. Pixels can be
accessed individually or on a scanline basis. All devices
should support top-down scanline access, and some may support random access to pixels and scanlines as well. There
is one set of routines for reading and writing 1-channel
pictures and another set for reading and writing 3 or 4channel pictures. Three-channel pictures ignore (skip over)
the a channel. If a picture was opened for reading then
only the parameter "get" routines and pixel read routines
should be called; if a picture was opened for writing then
most device libraries will allow any of the parameter "set"
or "get" or pixel read or write routines to be called.
When the application is done with a picture, it should call
pic_close, which does device-dependent cleanup, perhaps
flushing output buffers and closing the device or file
stream.
Conventions: Picture coordinates have the origin at the
upper left, with x pointing right and y pointing down. The
package currently has no notion of pixel aspect ratio, gamma
correction, or color correction. It is an error to set any
parameters after pixel writing has commenced on a picture
opened for writing. All pixel coordinates are in the same
coordinate system (not window-relative). Channels are 8
bits per pixel, with 0=dark and 255=bright. The pixel datatypes are:
typedef unsigned char Pixel1;
/* 1-channel picture */
typedef struct {Pixel1 r, g, b, a;} Pixel1_rgba;
/* 3 or 4channel picture */
SUBROUTINES
The application should think of a picture as an abstract
data type on which the following operations can be performed:
#include <pic.h>
Should be included by any application using pic.
Pic *pic_open(name, mode)
char *name, *mode;
Open picture with filename name with mode="r" for reading or "w" for writing. Returns a pointer which must be
used in all subsequent operations on the picture.
Returns 0 if unsuccessful. This routine uses
pic_file_dev to recognize the file's device type.
void pic_close(p)
Pic *p;
Close picture, flushing buffers if necessary. This
should be the last operation done on a picture.
void pic_catalog()
Print a list of the device libraries linked in with the
application.
char *pic_file_dev(file)
char *file;
Determine a file's device name by examining its magic
number in its first few bytes, if it is a file, or by
recognizing the suffix of its name. Returns 0 if device
is unrecognized. Examples:
pic_file_dev("mandrill.dump") == "dump"
pic_file_dev("iris") == "iris"
char *pic_get_name(p)
char *pic_get_dev(p)
Pic *p;
These two routines returns the picture's filename or
device name, respectively.
int pic_get_nchan(p)
void pic_set_nchan(p, nchan)
Pic *p;
int nchan;
Returns (pic_get_nchan) or sets (pic_set_nchan) the
number of channels in the picture: 1, 3, or 4.
void pic_get_box(p, &ox, &oy, &sx, &sy)
void pic_set_box(p, ox, oy, sx, sy)
Pic *p;
int ox, oy, sx, sy;
Get or set the origin (ox,oy) and size (sx,sy) of the
device. When a device with no intrinsic resolution
(such as a picture file) is opened for writing, its origin is undefined (ox==PIC_UNDEFINED) initially, so its
box must be set before pixels can be written.
Window *pic_get_window(p, win)
void pic_set_window(p, win)
Pic *p;
Window *w;
This is an alternative scheme for getting or setting the
box of a picture, redundant with the box routines above.
The Window structure is:
typedef struct { /* WINDOW: A DISCRETE 2-D RECTANGLE */
int x0, y0;
/* xmin and ymin */
int x1, y1;
/* xmax and ymax (inclusive) */
} Window;
The relation between box and window is: (x0,y0)=(ox,oy),
(x1,y1)=(ox+sx-1,oy+sy-1). pic_get_window returns its
window argument.
Pixel1 pic_read_pixel(p, x, y)
void pic_write_pixel(p, x, y, pv)
Pic *p;
int x, y;
Pixel1 pv;
Read or write a pixel from a 1-channel picture.
the pixel value to write.
void pic_read_pixel_rgba(p, x, y, pv)
void pic_write_pixel_rgba(p, x, y, r, g, b, a)
Pic *p;
int x, y;
pv is
Pixel1 r, g, b, a;
Pixel1_rgba *pv;
Read or write a pixel at (x,y) from a 3 or 4-channel
picture.
void pic_read_row(p, y, x0, nx, buf)
void pic_write_row(p, y, x0, nx, buf)
Pic *p;
int y, x0, nx;
Pixel1 *buf;
Read or write a portion of the 1-channel scanline at y=y
starting at x=x0, extending nx pixels to the right, from
or to the array buf, which has room for nx 1-channel
pixels.
void pic_read_row_rgba(p, y, x0, nx, buf)
void pic_write_row_rgba(p, y, x0, nx, buf)
Pic *p;
int y, x0, nx;
Pixel1_rgba *buf;
Read or write a portion of the 3 or 4-channel scanline
at y=y starting at x=x0, extending nx pixels to the
right, from or to the array buf, which has room for nx
4-channel pixels.
void pic_read_block(p, x0, y0, nx, ny, buf)
void pic_write_block(p, x0, y0, nx, ny, buf)
void pic_read_block_rgba(p, x0, y0, nx, ny, buf_rgba)
void pic_write_block_rgba(p, x0, y0, nx, ny, buf_rgba)
Pic *p;
int x0, y0, nx, ny;
Pixel1 *buf;
Pixel1_rgba *buf_rgba;
Similar to the row routines, but these read or write a
rectangular block of pixels with upper left corner
(x0,y0) and size (nx,ny). The buffers are effectively
of dimension [ny][nx].
void pic_clear(p, pv)
Pic *p;
Pixel1 pv;
Clear all pixels of the 1-channel picture to pixel value
pv.
void pic_clear_rgba(p, r, g, b, a)
Pic *p;
Pixel1 r, g, b, a;
Clear all pixels of the 3 or 4-channel picture to pixel
value (r,g,b,a).
Pic *pic_load(name1, name2)
char *name1, *name2;
Copy picture from file name1 into file name2, and return
the descriptor of the latter picture, opened for writ-
ing.
void pic_save(p, name)
Pic *p;
char *name;
Copy the picture in p into a new picture in file name.
Picture p is not closed.
void pic_copy(p, q)
Pic *p, *q;
Copy picture p into picture q.
Neither one is closed.
LINKING
The code within pic consists of two layers: the top layer of
device-independent code, and the bottom layer of devicedependent "device libraries", with one library for each device class known. An application using pic has control at
compile time of which device libraries are linked in to its
executable file. Some programs will be run on just one device, so it is wasteful of disk space to link in more than
the one device library, while other programs need to read
and write a variety of device types, so they will want to
link in all device libraries. Linking is controlled through
the global array pic_list. If the application declares its
own pic_list then it has explicit control of the device
libraries linked; otherwise the linker will pick up the
default pic_list from pic_file.o in libpic.a and link in all
device libraries. To create your own pic_list, put lines
similar to the following in your application source code:
extern Pic pic_dump, pic_foo;
Pic *pic_list[PIC_LISTMAX] = {&pic_dump, &pic_foo, 0};
This will cause the "dump" and "foo" device libraries to be
linked in. Note: the "0" terminating pic_list is vital.
The subroutine pic_catalog() prints pic_list.
EXAMPLE
The following program illustrates the use of the pic package:
#include <simple.h>
#include <pic.h>
/* pic_lum: take the luminance of afile and write it into bfile
* afile is expected to be 3 or 4-channel, bfile is written as
1-channel */
pic_lum(afile, bfile)
char *afile, *bfile;
{
int x, y, dx, dy;
Pic *a, *b;
Window win;
Pixel1_rgba *rgb;
Pixel1 *lum;
a = pic_open(afile, "r");
if (!a) die("can't read %s\n", afile);
b = pic_open(bfile, "w");
if (!a) die("can't write %s\n", bfile);
if (pic_get_nchan(a)<3) die("%s is not 3 channel\n",
afile);
pic_set_nchan(b, 1);
pic_set_window(b, pic_get_window(a, &win));
dx = win.x1-win.x0+1;
dy = win.y1-win.y0+1;
printf("%s->%s, res=%dx%d, origin=(%d,%d)\n",
afile, bfile, dx, dy, win.x0, win.y0);
rgb = (Pixel1_rgba *)malloc(dx*sizeof(Pixel1_rgba));
lum = (Pixel1 *)malloc(dx*sizeof(Pixel1));
for (y=0; y<dy; y++) {
pic_read_row_rgba(a, win.y0+y, win.x0, dx, rgb);
for (x=0; x<dx; x++)
lum[x] = .30*rgb[x].r + .59*rgb[x].g +
.11*rgb[x].b;
pic_write_row(b, win.y0+y, win.x0, dx, lum);
}
free(rgb);
free(lum);
pic_close(a);
pic_close(b);
}
static die(control, arg)
char *control, *arg;
{
fprintf(stderr, control, arg);
exit(1);
}
CREATING A DEVICE LIBRARY
To add a new device library to pic for a hypothetical file
or device type called "foo", you would write the following
subroutines:
Foo
void
*foo_open(file, mode)
foo_close(d)
char
void
void
*foo_get_name(d)
foo_clear(d, pv)
foo_clear_rgba(d, r, g, b, a)
void
void
void
void
foo_set_nchan(d, nchan)
foo_set_box(d, ox, oy, dx, dy)
foo_write_pixel(d, x, y, pv)
foo_write_pixel_rgba(d, x, y, r, g, b, a)
void
void
foo_write_row(d, y, x0, nx, buf)
foo_write_row_rgba(d, y, x0, nx, buf)
int
foo_get_nchan(d)
void
foo_get_box(d, ox, oy, dx, dy)
Pixel1
foo_read_pixel(d, x, y)
void
foo_read_pixel_rgba(d, x, y, pv)
void
foo_read_row(d, y, x0, nx, buf)
void
foo_read_row_rgba(d, y, x0, nx, buf)
where the arguments are identical to those of the pic subroutines, except that the first argument to these routines
is a pointer to the private data for this device,
Foo *d;
This private data is a structure of your own design, containing whatever state information is needed to perform the
above operations. If an operation is difficult on your device then politely punt, e.g.:
Pixel1 foo_read_pixel(p, x, y) Foo *p; int x, y; {
fprintf(stderr, "foo_read_pixel: unimplemented\n");
}
After the above routines are written they need to be
registered by collecting their addresses into a global
Pic_procs structure and creating a prototype Pic structure
containing the device's name and Pic_procs pointer. For our
device class "foo", we'd declare:
static Pic_procs pic_foo_procs = {
(char *(*)())foo_open, foo_close,
foo_get_name,
foo_clear, foo_clear_rgba,
foo_set_nchan, foo_set_box,
foo_write_pixel, foo_write_pixel_rgba,
foo_write_row, foo_write_row_rgba,
foo_get_nchan, foo_get_box,
foo_read_pixel, foo_read_pixel_rgba,
foo_read_row, foo_read_row_rgba,
};
Pic pic_foo = {"foo", &pic_foo_procs};
By convention, the device library for device "foo" goes in a
source file called foo.c that does not #include pic.h, and
the above global structures go in a small file called
foo_pic.c that does #include pic.h.
There are three global files that must be modified slightly
to register your new device with the pic library: Add the
address of your prototype Pic structure to the list of all
known devices in pic_all.c. Add magic number or recognition
code to pic_file_dev in pic_file.c. Modify Makefile. Run
make install. For further examples of this process, see the
files dump.h, dump.c, dump_pic.c.
AUTHOR
Paul Heckbert, August 1989.
ph@cs.cmu.edu
Download