Chapter 1. Writing an ImageVision Library Program

To write an image processing program, you use the C++ classes in the ImageVision Library (IL). This chapter shows several, typical image processing applications.

This chapter contains the following major sections:

A Sample Program in C++

The sample C++ program presented in this section reads image data from a file, processes it, displays it, and saves the processed data in a new file. Each task the program performs is described in more detail in subsequent chapters. This chapter gives you a brief introduction to the capabilities of the IL and provides you with a code example that can serve as a template for programs you write.

Image processing applications typically perform at least some of the following tasks:

Read image data 


Read formatted image data from a file on disk, for example, and decompress it if necessary.

Process the data 


Manipulate the data, for example, to enhance the original image or to produce a statistical analysis of the data.

Display the image on the screen 


Allow a user to interactively view selected portions of simultaneously-displayed images.

Save the processed data in a file 


Format and possibly compress the data.

The C++ program presented in Example 1-1 demonstrates how the IL accomplishes these tasks. (A version of this program in the C language appears later in this chapter.) In Example 1-1, the user invokes the program from the command line and specifies a file of image data to be processed. The program then performs the following tasks:

  1. Opens the input file of image data.

  2. Constructs a sharpening operator that uses the file of image data as input.

  3. Constructs a rotate operator that uses the output of the sharpening operator as input.

  4. Displays the sharpened and rotated image data on the screen.

  5. Continues to display the processed image until the user quits by pressing the <Ctrl> <q> keys or by using the window menu.

  6. Copies the sharpened and rotated image to a file on disk.

The code for this program is available online so that you can easily compile and run it. Look in:

/usr/share/src/il/guide/sampleProg.c++

Other sample code is also available online; see “Online Source Code”.

C++ Version of the Sample Program

The code in Example 1-1 shows the C++ version of the sample program.

Example 1-1. Sample Program (in C++) Using X Window Management


#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <il/ilFileImg.h>
#include <il/ilSharpenImg.h>
#include <il/ilRotZoomImg.h>
#include <il/ilViewer.h>

void
main(int argc, char **argv)
{
    // Step 1: Open the file of image data.

    if (argc < 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        exit(0);
    }

    ilFileImg inImg(argv[1]);

    // Step 2: Create IL objects for sharpening and rotating

   ilSharpenImg sharperImg(&inImg, 0.5);
    ilRotZoomImg rotatedImg(&sharperImg, 90.0);

    // Step 3: Set up and open a window for display.
    iflSize size;
    rotatedImg.getDimensions(size);
    Display* dpy = XOpenDisplay(NULL);
    ilViewer viewer(dpy, size.x, size.y);

     // Step 4: Display the processed data.

    viewer.addView(&rotatedImg, ilLast, ilCenter);

    // Step 5: Display until the user quits.

    viewer.eventLoop();

    XCloseDisplay(dpy);

    // Step 6: Write the processed data to a file.

    iflFileConfig fc(&size);
    ilFileImg tmpFile("outFile.tif", &inImg, &fc);
    tmpFile.copy(&rotatedImg);
    tmpFile.flush();

}

More about the Sample Program

The sample program uses the IL in a recommended way, but many good programming habits were not followed in the interest of keeping the program short. More specifically, this program does not do any of the following things:

  • check return arguments and write error messages as appropriate

  • strip arguments off the command line in an elegant way and check them for appropriate values (or provide a graphical user interface)

  • provide feedback to the user, for example, to indicate that a file of processed image data has been created

The remainder of this section walks through Example 1-1, explaining how it uses the IL. This discussion is intended to give you a taste of the kinds of things the IL can do and what you, as a programmer, need to do to accomplish them. Each of the following topics is discussed extensively elsewhere in this book.

Header Files

The first few lines of code include the necessary header files from the IL. These header files also include other IL header files, as well as header files from the Graphics Library and the standard C library. If you use this program as a template and modify it to suit your needs, be sure you include the header files necessary for your program. Since the IL provides many more capabilities than you need for any particular program, you do not need to include all of its header files. To minimize compile time and the size of your executable, you should include only those header files actually required by your program.

In this example,

  • the header X11/Xlib.h is included to configure an X window for OpenGL rendering

  • the header X11/keysym.h is included to handle user input

  • the header il/ilFileImg.h is included to implement the ilFileImg class

  • the header il/ilSharpenImg.h is included to implement the ilSharpenImg class

  • the header il/ilRotZoomImg.h is included to implement the ilRotZoomImg class.

  • the header il/ilViewer.h is included to manage views in an X window.

In general, when writing an IL program in C++, you will need to include an IL header file for each IL class you use. More information about programming and compiling IL programs is included in “Compiling and Linking an IL Program”.

Step 1: Open the File of Image Data

In step 1 of Example 1-1, an image data file specified by the user is opened by invoking the ilFileImg constructor. This function takes one argument: the pathname of the file. In this example, the filename is taken as an argument from the command line and the file is opened for reading. Figure 1-1 shows an example image file.

Figure 1-1. An Image before Processing

Figure 1-1 An Image before Processing

Before any image data can be read, the ilFileImg constructor determines the format of the image file by returning a pointer to one of the supported ilFileImg types. IL recognizes the image file formats at runtime by searching for dynamic shared objects (DSOs) that contain the code for specific file formats. Table 1-1 shows all of the IFL -supported image file formats and their customary suffixes.

Table 1-1. IFL-supported Image Formats

File Format

Customary Suffix

SGI

.rgb, .sgi, .rgba, .bw, .screen

TIFF

.tif, .tiff

JFIF

.jpg, .jpeg, .jfif

FIT

.fit

PCD

.pcd

PCDO

pcdo

GIF

.gif

PPM

.ppm, .pgm, .pbm, .pnm

PNG

.png

Raw

.raw

IFL is a lower-level library upon which IL is built.

You can also create your own image file formats. For more information about defining image file formats, see Appendix E, “Implementing Your Own Image File Format.”.

Step 2: Create IL Objects for Sharpening and Rotating

Now that the source of the image data is ready, the IL objects used for processing the data are created in step 2. For this sample program, data is first sharpened and then rotated by using the ilSharpenImg and ilRotZoomImg classes. These two classes demonstrate two of the many image manipulation functions included in the IL.

As shown in Example 1-1, the parameter 0.5 is passed in with a pointer to the input image data file to create the sharpening object. This parameter, which is a single-precision floating point number, can range in value from 0 to 1; it defines how much the data is sharpened. The specific algorithm that ilSharpenImg uses to sharpen image data is described in detail in its class reference page (read “Reading the Reference Pages” for an explanation of the difference between normal reference pages and class reference pages). If this were an interactive program, you could allow the user to change the sharpness factor dynamically, perhaps with a slider widget.

You can use the ilRotZoomImg class to rotate and/or zoom (magnify or minify) an image. In Example 1-1, the sharpened image data is rotated 90 degrees, in a counterclockwise direction, as specified by the parameter passed to ilRotZoomImg. The ilRotZoomImg class is discussed in detail in its reference page.

The program uses the size of the rotated image to set the size of the X window opened to display the image.

You can invoke any number of operators on a set of data. See Chapter 4, “Operating on an Image,” for more information about how the IL allows you to operate on image data. You can also easily add your own algorithms; “Implementing an Image Processing Operator”, tells you how to extend the IL to include a new image processing operator.

As an IL program executes, image data is processed only on demand, for example, when it's needed for displaying or writing to a file. This execution model eliminates unnecessary processing and minimizes transfers of data in and out of memory. In Example 1-1, data is not actually processed until step 4. The execution model is discussed in detail in “The IL Execution Model”.

Step 3: Open a Window for Display

Example 1-1 calls the X Window library function, XOpenDisplay(), to return a pointer to the display device which, in turn, is passed to ilDisplay to open a window.

Step 4: Display the Processed Data

In step 3, an ilViewer object is created to display the processed image data. In a more interactive image processing program, you would use an ilViewer object to manage the dynamic display of multiple images. Also, you could rewrite the program to display the sharpened image before it's rotated. Example 1-1, however, simply displays the final image by calling the addView() member function on the sharpened, rotated image. Displaying processed images is covered in detail in Chapter 5, “Displaying an Image.” The result of running the sample program with the image from Figure 1-1 is shown in Figure 1-2.

Figure 1-2. The Image after Processing

Figure 1-2 The Image after Processing

In the IL's execution model, data is processed in conveniently sized chunks, called pages. As you execute Example 1-1, you can watch as successive pages of image data are displayed —one rectangular part of the image after another—after the pages have been processed.

Step 5: Display Until the User Quits

In step 5, the program uses the eventLoop() function in ilViewer to handle X events until the user types <Ctrl> <q>. You could also write your own event loop using X library calls and pass events you do not want to handle to the event() function in ilViewer.

Step 6: Write the Processed Data to a File

Many image processing applications need to write processed image data to a file. In step 6, the iflFileConfig function, fc(), sets the size of the image in pixels. All other image attributes are copied from inImg which is passed to the output file object's constructor.

The ilFileImg constructor creates a file for writing data using the TIFF file format. This version of the constructor needs to know the name of the output file, a pointer to the original image file, and the size of the image in pixels. See “Creating an Image File” for more information about ilFileImg.

The ilFileImg constructor only creates a file. The ilFileImg.copy() function actually writes the processed (sharpened and rotated) image data directly into the file. The ilFileImg.flush() function then writes to the disk file all pages still residing in memory.

The C Interface

Since the IL was written in C++, it implements the C interface as a wrapper to C++ member functions. This wrapper has names that are similar to those of the C++ member functions. Thus, the concepts explained in this guide apply to C as well as C++ programmers even though most of the code examples are shown in C++.

Creating and Deleting C++-style Objects

A C++ class object must be defined as something the C language recognizes to make it usable in a C program. For example, the header file il/ilCdefs.h defines all the IL classes as being of data type struct. To “create” such a struct in your program, call the appropriate function, which is usually of the form ClassNameCreate(). The call to create an ilDisplay struct, for example, is ilDisplayCreate().

In C, use these statements:

ilDisplay* disp;
disp = ilDisplayCreateWindow(dpy, size.x, size.y,     ilVisDoubleBuffer,0,0, ilDefault, ExposureMask | 
    KeyPressMask | StructureNotifyMask);

You can see in this example some other differences between the C and C++ calls. In C++, you can have variables created automatically for you, or you can allocate them dynamically yourself. The C variable disp must be declared as a pointer to type ilDisplay.

Since disp appears as just a struct to C, you need to call a destructor directly when you need to delete it. The destructor naming scheme is similar to the creator scheme. In order to delete the display you created with the calls above, use ilViewerDelete().

In C, use this statement:

ilViewerDelete(viewer);

In C++, use this statement:

delete ilViewer; // not needed unless created with new

Calling Functions

Once you have accomplished the C equivalent of creating an object, you can manipulate it with the C version of the functions associated with that object. The C function name generally includes the C++ class name, and the functions themselves take a pointer to the “object” as an additional argument.

In C, use this statement:

status = ilDisplayAddView(disp, rotatedImg, 0, ilCenter);

In C++, use this statement:

status = disp.addView(rotatedImg);

As you can see, the C++ function addView(), which is a member function of the ilDisplay class, becomes ilDisplayAddView(). Most functions follow this form: the name of the base class is used as the prefix for the functions. C++ functions from the ilImage base class (or from ilImage's parent class, ilLink) add “il,” not “ilImage.” ilCacheImg's flush() function does this as well; it becomes ilFlush(), not ilCacheImgFlush().


Note: The C version of the man pages list the C names for each method.

The C++ versions of the IL functions fill in default values for some arguments. If you omit those arguments, C++ simply calls the function with the defaults. C, however, does not fill in defaults for you. You must supply values for each argument. The C++ sample program takes advantage of this feature when creating a new ilSharpenImg object.

In C, use this statement:

sharperImg = ilSharpenImgCreate(theImg, 0.5, 1.5, ilPadSrc);

In C++, use this statement:

ilSharpenImg sharperImg(theImg);

0.5, 1.5, and ilPadSrc are the default values for the sharpness factor, radius, and edge mode arguments, respectively. In C, you must pass them explicitly.

Including Header Files

To use the IL in your C programs, you need to include only il/ilCdefs.h. This file includes information about all the IL classes and functions.

A Sample Program in C

Example 1-2 shows the equivalent of Example 1-1 written in C. Example 1-2 opens a file image, sharpens and rotates it, sets up the window configuration, opens an X window, and displays the processed image.

Example 1-2. Sample Program (in C) Using X Window Management


#include <il/ilCdefs.h>
#include <X11/keysym.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/fcntl.h>

void
main(int argc, char **argv)
{
    ilFileImg *inImg, *tmpFile;
    iflFileConfig *fc;
    ilSharpenImg *sharperImg;
    ilRotZoomImg *rotatedImg;
    ilViewer *viewer;
    iflSize size;
    Display *dpy;
    XEvent event;
    int ever;

    if (argc < 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        exit(0);
    }

    // Step 1: Open the file of the image data.

    inImg = ilFileImgOpen(argv[1], O_RDONLY, NULL);

    // Step 2: Create IL objects for sharpening and rotating.

    sharperImg = ilSharpenImgCreate(inImg, 0.5, 1.5, ilPadSrc);
    rotatedImg = ilRotZoomImgCreate(sharperImg,90.0, 1, 1, ilBiLinear);

    // Step 3: Set up and open a window for display.

    dpy = XOpenDisplay(NULL);
    ilGetSize(rotatedImg, &size);
    disp = ilViewerCreateWindow(dpy, size.x, size.y, ilVisDoubleBuffer, 0,
                                 0, ilDefault, ExposureMask | KeyPressMask |
                                 StructureNotifyMask);

    // Step 4: Display the processed data.

   ilDisplayAddViewTop(viewer, rotatedImg, ilCenter);
    ilDisplayRedraw(viewer, ilDefault);

    // Step 5: Display until the user quits.

   ilViewer.eventLoop(viewer);

XCloseDisplay(dpy);
    // step 6: Write the processed data to a file

   fc = iflFileConfigCreate(&size, 0, 0, 0, 0, 0, NULL);
    tmpFile = ilFileImgCreate("outFile.tif", inImg, fc, NULL);
    ilCopy(tmpFile, rotatedImg);
    ilImageDelete(tmpFile);
}

Example 1-2 shows several examples of function name changes, for example, the C++ call rotatedImg.getSize() becomes ilGetSize() in C.