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