2.2 Core C API

VIPS is built on top of several other libraries, two of which, glib and gobject, are exposed at various points in the C API.

You can read up on glib at the GTK+ website:

http://www.gtk.org

There’s also an excellent book by Matthias Warkus, The Official GNOME 2 Developer’s Guide, which covers the same material in a tutorial manner.

2.2.1 Startup

Before calling any VIPS function, you need to start VIPS up:

int im_init_world( const char ⋆argv0 );

The argv0 argument is the value of argv[0] your program was passed by the host operating system. VIPS uses this with im_guess_prefix() and im_guess_libdir() to try to find various VIPS data files.

If you don’t call this function, VIPS will call it for you the first time you use a VIPS function. But it won’t be able to get the argv0 value for you, so it may not be able to find it’s data files.

VIPS also offers the optional:

GOptionGroup ⋆im_get_option_group( void );

You can use this with GOption to parse your program’s command-line arguments. It adds several useful VIPS flags, including --vips-concurrency.

Figure 2.2 shows both these functions in use. Again, the GOption stuff is optional and just lets VIPS add some flags to your program. You do need the im_init_world() though.


#include <stdio.h>  
#include <vips/vips.h>  
 
static gboolean print_stuff;  
 
static GOptionEntry options[] = {  
  { "print", 'p', 0, G_OPTION_ARG_NONE, &print_stuff,  
    "print \"hello world!\"", NULL },  
  { NULL }  
};  
 
int  
main( int argc, char ⋆⋆argv )  
{  
  GOptionContext ⋆context;  
  GError ⋆error = NULL;  
 
  if( im_init_world( argv[0] ) )  
    error_exit( "unable to start VIPS" );  
 
  context = g_option_context_new( "- my program" );  
  g_option_context_add_main_entries( context,  
    options, "main" );  
  g_option_context_add_group( context, im_get_option_group() );  
  if( !g_option_context_parse( context, &argc, &argv, &error ) ) {  
    if( error ) {  
      fprintf( stderr, "%s\n", error->message );  
      g_error_free( error );  
    }  
    error_exit( "try \"%s --help\"", g_get_prgname() );  
  }  
  g_option_context_free( context );  
 
  if( print_stuff )  
    printf( "hello, world!\n" );  
 
  return( 0 );  
}


Figure 2.2: Hello World for VIPS

2.2.2 Image descriptors

The base level of the VIPS I/O system provides IMAGE descriptors. An image represented by a descriptor may be an image file on disc, an area of memory that has been allocated for the image, an output file, a delayed computation, and so on. Programs need (usually) only know that they have a descriptor, they do not see many of the details. Figure 2.3 shows the definition of the IMAGE descriptor.


typedef struct {  
    /⋆ Fields from image header.  
     ⋆/  
    int Xsize;             /⋆ Pels per line ⋆/  
    int Ysize;             /⋆ Lines ⋆/  
    int Bands;             /⋆ Number of bands ⋆/  
    int Bbits;             /⋆ Bits per band ⋆/  
    int BandFmt;           /⋆ Band format ⋆/  
    int Coding;            /⋆ Coding type ⋆/  
    int Type;              /⋆ Type of file ⋆/  
    float XRes;            /⋆ Horizontal res in pels/mm ⋆/  
    float YRes;            /⋆ Vertical res in pels/mm  ⋆/  
    int Length;            /⋆ Obsolete (unused) ⋆/  
    short Compression;     /⋆ Obsolete (unused) ⋆/  
    short Level;           /⋆ Obsolete (unused) ⋆/  
    int Xoffset;           /⋆ Position of origin ⋆/  
    int Yoffset;  
 
    /⋆ Derived fields that may be read by the user.  
     ⋆/  
    char ⋆filename;        /⋆ File name ⋆/  
    im_time_t ⋆time;       /⋆ Timing for eval callback ⋆/  
    int kill;              /⋆ Set to non-zero to block eval ⋆/  
 
    ... and lots of other private fields used by VIPS for  
    ... housekeeping.  
} IMAGE;


Figure 2.3: The IMAGE descriptor

The first set of fields simply come from the image file header: see §1.2.1 for a full description of all the fields. The next set are maintained for you by the VIPS I/O system. filename is the name of the file that this image came from. If you have attached an eval callback to this image, time points to a set of timing statistics which can be used by user-interfaces built on VIPS to provide feedback about the progress of evaluation — see §2.2.8. Finally, if you set kill to non-zero, VIPS will block any pipelines which use this descriptor as an intermediate. See §2.2.12.

The remaining fields are private and are used by VIPS for housekeeping.

2.2.3 Header fields

You can access header fields either directly (as im->Xsize, for example) or programmatically with im_header_int() and friends. For example:

int i;  
 
im_header_int( im, "Xsize", &i );

There’s also im_header_map() to loop over header fields, and im_header_get_type to test the type of fields. These functions work for image meta fields as well, see §2.2.6.

2.2.4 Opening and closing

Descriptors are created with im_open(). You can also read images with the format system: see §2.5. The two APIs are complimentary, though im_open() is more useful.

At the command-line, try:

$ vips list classes

/noindent to see a list of all the supported file formats.

im_open() takes a file name and a string representing the mode with which the descriptor is to be opened:

IMAGE ⋆im_open( const char ⋆filename,  
  const char ⋆mode )

The possible values for mode are:

"r"
The file is opened read-only. If you open a non-VIPS image, or a VIPS image written on a machine with a different byte ordering, im_open() will automatically convert it to native VIPS format. If the underlying file does not support random access (JPEG, for example), the entire file will be converted in memory.

VIPS can read images in many file formats. You can control the details of the conversion with extra characters embedded in the filename. For example:

fred = im_open( "fred.tif:2",  
  "r" );

will read page 2 of a multi-page TIFF. See the man pages for details.

"w"
An IMAGE descriptor is created which, when written to, will write pixels to disc in the specified file. Any existing file of that name is deleted.

VIPS looks at the filename suffix to determine the save format. If there is no suffix, or the filename ends in ".v", the image is written in VIPS native format.

If you want to control the details of the conversion to the disc format (such as setting the Q factor for a JPEG, for example), you embed extra control characters in the filename. For example:

fred = im_open( "fred.jpg:95",  
  "w" );

writes to fred will write a JPEG with Q 95. Again, see the man pages for the conversion functions for details.

"t"
As the "w" mode, but pels written to the descriptor will be saved in a temporary memory buffer.
"p"
This creates a special partial image. Partial images are used to join VIPS operations together, see §2.2.12.
"rw"
As the "r" mode, but the image is mapped into the caller’s address space read-write. This mode is only provided for the use of paintbox-style applications which need to directly modify an image. Most programs should use the "w" mode for image output.

If an error occurs opening the image, im_open() calls im_error() with a string describing the cause of the error and returns NULL. im_error() has type

void im_error( const char ⋆domain,  
  const char ⋆format, ... )

The first argument is a string giving the name of the thing that raised the error (just "im_open", for example). The format and subsequent arguments work exactly as printf(). It formats the message and appends the string formed to the error log. You can get a pointer to the error text with im_error_buffer().

const char ⋆im_error_buffer()

Applications may display this string to give users feedback about the cause of the error. The VIPS exit function, error_exit(), prints im_error_buffer() to stderr and terminates the program with an error code of 1.

void error_exit( const char ⋆format,  
  ... )

There are other functions for handling errors: see the man page for im_error().

Descriptors are closed with im_close(). It has type:

int im_close( IMAGE ⋆im )

im_close() returns 0 on success and non-zero on error.

2.2.5 Examples

As an example, Figure 2.2.5 will print the width and height of an image stored on disc.


#include <stdio.h>  
#include <vips/vips.h>  
 
int  
main( int argc, char ⋆⋆argv )  
{  
  IMAGE ⋆im;  
 
  /⋆ Check arguments.  
   ⋆/  
  if( im_init_world( argv[0] ) )  
    error_exit( "unable to start VIPS" );  
  if( argc != 2 )  
    error_exit( "usage: %s filename", argv[0] );  
 
  /⋆ Open file.  
   ⋆/  
  if( !(im = im_open( argv[1], "r" )) )  
    error_exit( "unable to open %s for input", argv[1] );  
 
  /⋆ Process.  
   ⋆/  
  printf( "width = %d, height = %d\n", im->Xsize, im->Ysize );  
 
  /⋆ Close.  
   ⋆/  
  if( im_close( im ) )  
      error_exit( "unable to close %s", argv[1] );  
 
  return( 0 );  
}


Figure 2.4: Print width and height of an image

To compile this example, use:

cc ‘pkg-config vips-7.14 \  
  --cflags --libs‘ myfunc.c

As a slightly more complicated example, Figure 2.2.5 will calculate the photographic negative of an image.


#include <stdio.h>  
#include <vips/vips.h>  
 
int  
main( int argc, char ⋆⋆argv )  
{  
  IMAGE ⋆in, ⋆out;  
 
  /⋆ Check arguments.  
   ⋆/  
  if( im_init_world( argv[0] ) )  
    error_exit( "unable to start VIPS" );  
  if( argc != 3 )  
    error_exit( "usage: %s infile outfile", argv[0] );  
 
  /⋆ Open images for read and write, invert, update the history with our  
   ⋆ args, and close.  
   ⋆/  
  if( !(in = im_open( argv[1], "r" )) ||  
    !(out = im_open( argv[2], "w" )) ||  
    im_invert( in, out ) ||  
    im_updatehist( out, argc, argv ) ||  
    im_close( in ) ||  
    im_close( out ) )  
    error_exit( argv[0] );  
 
  return( 0 );  
}


Figure 2.5: Find photographic negative

2.2.6 Metadata

VIPS lets you attach arbitrary metadata to an IMAGE. For example, ICC profiles, EXIF tags, image history, whatever you like. VIPS will efficiently propagate metadata as images are processed (usually just by copying pointers) and will automatically save and load metadata from VIPS files (see §1.2.1).

A piece of metadata is a value and an identifying name. A set of convenience functions let you set and get int, double, string and blob. For example:

int im_meta_set_int( IMAGE ⋆,  
  const char ⋆field, int );  
int im_meta_get_int( IMAGE ⋆,  
  const char ⋆field, int ⋆ );

So you can do:

if( im_meta_set_int( im, "poop", 42 ) )  
  return( -1 );

to create an int called "poop", then at some later point (possibly much, much later), in an image distantly derived from im, you can use:

int i;  
 
if( im_meta_get_int( im, "poop", &i ) )  
  return( -1 );

And get the value 42 back.

You can use im_meta_set() and im_meta_get() to attach arbitrary GValue to images. See the man page for im_meta_set() for full details.

You can test for a field being present with im_meta_get_type() (you’ll get G_TYPE_INT back for "poop", for example, or 0 if it is not defined for this image).

2.2.7 History

VIPS tracks the history of an image, that is, the sequence of operations which have led to the creation of an image. You can view a VIPS image’s history with the header command, or with nip2’s View Header menu. Whenever an application performs an action, it should append a line of shell script to the history which would perform the same action.

The call to im_updatehist() in Figure 2.2.5 adds a line to the image history noting the invocation of this program, its arguments, and the time and date at which it was run. You may also find im_histlin() helpful. It has type:

void im_histlin( IMAGE ⋆im,  
  const char ⋆fmt, ... )

It formats its arguments as printf() and appends the string formed to the image history.

You read an image’s history with im_history_get(). It returns the entire history of an image, one action per line. No need to free the result.

const char ⋆  
  im_history_get( IMAGE ⋆im );

2.2.8 Eval callbacks

VIPS lets you attach callbacks to image descriptors. These are functions you provide which VIPS will call when certain events occur. See §3.3.6 for more detail.

Eval callbacks are called repeatedly during evaluation and can be used by user-interface programs to give feedback about the progress of evaluation.

2.2.9 Detailed rules for descriptors

These rules are intended to answer awkward questions.

  1. You can output to a descriptor only once.
  2. You can use a descriptor as an input many times.
  3. You can only output to a descriptor that was opened with modes "w", "t" and "p".
  4. You can only use a descriptor as input if it was opened with modes "r" or "rw".
  5. If you have output to a descriptor, you may subsequently use it as an input. "w" descriptors are automatically changed to "r" descriptors.

    If the function you are passing the descriptor to uses WIO (see §2.2.12), then "p" descriptors become "t". If the function you are passing the descriptor to uses PIO, then "p" descriptors are unchanged.

2.2.10 Automatic resource deallocation

VIPS lets you allocate resources local to an image descriptor, that is, when the descriptor is closed, all resources which were allocated local to that descriptor are automatically released for you.

Local image descriptors

VIPS provides a function which will open a new image local to an existing image. im_open_local() has type:

IMAGE ⋆im_open_local( IMAGE ⋆im,  
  const char ⋆filename,  
  const char ⋆mode )

It behaves exactly as im_open(), except that you do not need to close the descriptor it returns. It will be closed automatically when its parent descriptor im is closed.


/⋆ Add another image to the accumulated total.  
 ⋆/  
static int  
sum1( IMAGE ⋆acc, IMAGE ⋆⋆in, int nin, IMAGE ⋆out )  
{  
    IMAGE ⋆t;  
 
    if( nin == 0 )  
        /⋆ All done ... copy to out.  
         ⋆/  
        return( im_copy( acc, out ) );  
 
    /⋆ Make a new intermediate, and add to it..  
     ⋆/  
    return( !(t = im_open_local( out, "sum1:1", "p" )) ||  
        im_add( acc, in[0], t ) ||  
        sum1( t, in + 1, nin - 1, out ) );  
}  
 
/⋆ Sum the array of images in[]. nin is the number of images in  
 ⋆ in[], out is the descriptor we write the final image to.  
 ⋆/  
int  
total( IMAGE ⋆⋆in, int nin, IMAGE ⋆out )  
{  
    /⋆ Check that we have at least one image.  
     ⋆/  
    if( nin <= 0 ) {  
        im_error( "total", "nin should be > 0" );  
        return( -1 );  
    }  
 
    /⋆ More than 1, sum recursively.  
     ⋆/  
    return( sum1( in[0], in + 1, nin - 1, out ) );  
}


Figure 2.6: Sum an array of images

Figure 2.6 is a function which will sum an array of images. We need never close any of the (unknown) number of intermediate images which we open. They will all be closed for us by our caller, when our caller finally closes out. VIPS lets local images themselves have local images and automatically makes sure that all are closed in the correct order.

It is very important that these intermediate images are made local to out rather than in, for reasons which should become apparent in the section on combining operations below.

There’s also im_open_local_array() for when you need a lot of local descriptors, see the man page.

Local memory allocation

VIPS includes a set of functions for memory allocation local to an image descriptor. The base memory allocation function is im_malloc(). It has type:

void ⋆im_malloc( IMAGE ⋆, size_t )

It operates exactly as the standard malloc() C library function, except that the area of memory it allocates is local to an image. If im_malloc() is unable to allocate memory, it returns NULL. If you pass NULL instead of a valid image descriptor, then im_malloc() allocates memory globally and you must free it yourself at some stage.

To free memory explicitly, use im_free():

int im_free( void ⋆ )

im_free() always returns 0, so you can use it as an argument to a callback.

Three macros make memory allocation even easier. IM_NEW() allocates a new object. You give it a descriptor and a type, and it returns a pointer to enough space to hold an object of that type. It has type:

type-name ⋆IM_NEW( IMAGE ⋆, type-name )

The second macro, IM_ARRAY(), is very similar, but allocates space for an array of objects. Note that, unlike the usual calloc() C library function, it does not initialise the array to zero. It has type:

type-name ⋆IM_ARRAY( IMAGE ⋆, int, type-name )

Finally, IM_NUMBER() returns the number of elements in an array of defined size. See the man pages for a series of examples, or see §2.3.1.

Other local operations

The above facilities are implemented with the VIPS core function im_add_close_callback(). You can use this facility to make your own local resource allocators for other types of object — see the manual page for more help.

2.2.11 Error handling

All VIPS operations return 0 on success and non-zero on error, setting im_error(). As a consequence, when a VIPS function fails, you do not need to generate an error message — you can simply propagate the error back up to your caller. If however you detect some error yourself (for example, the bad parameter in the example above), you must call im_error() to let your caller know what the problem was.

VIPS provides two more functions for error message handling: im_warn() and im_diag(). These are intended to be used for less serious messages, as their names suggest. Currently, they simply format and print their arguments to stderr, optionally suppressed by the setting of an environment variable. Future releases of VIPS may allow more sophisticated trapping of these functions to allow their text to be easily presented to the user by VIPS applications. See the manual pages.

2.2.12 Joining operations together

VIPS lets you join image processing operations together so that they behave as a single unit. Figure 2.7 shows the definition of the function im_Lab2disp() from the VIPS library. This function converts an image in CIE Lab colour space to an RGB image for a monitor. The monitorcharacteristics (gamma, phosphor type, etc.) are described by the im_col_display structure, see the man page for im_col_XYZ2rgb().


int  
im_Lab2disp( IMAGE ⋆in, IMAGE ⋆out, struct im_col_display ⋆disp )  
{  
    IMAGE ⋆t1;  
 
    if( !(t1 = im_open_local( out, "im_Lab2disp:1", "p" )) ||  
        im_Lab2XYZ( in, t1 ) ||  
        im_XYZ2disp( t1, out, disp ) )  
        return( -1 );  
 
    return( 0 );  
}


Figure 2.7: Two image-processing operations joined together

The special "p" mode (for partial) used to open the image descriptor used as the intermediate image in this function ‘glues’ the two operations together. When you use im_Lab2disp(), the two operations inside it will execute together and no extra storage is necessary for the intermediate image (t1 in this example). This is important if you want to process images larger than the amount of RAM you have on your machine.

As an added bonus, if you have more than one CPU in your computer, the work will be automatically spread across the processors for you. You can control this parallelization with the IM_CONCURRENCY environment variable, im_concurrency_set(), and with the --vips-concurrency command-line switch. See the man page for im_generate().

How it works

When a VIPS function is asked to output to a "p" image descriptor, all the fields in the descriptor are set (the output image size and type are set, for example), but no image data is actually generated. Instead, the function attaches callbacks to the image descriptor which VIPS can use later to generate any piece of the output image that might be needed.

When a VIPS function is asked to output to a "w" or a "t" descriptor (a real disc file or a real memory buffer), it evaluates immediately and its evaluation in turn forces the evaluation of any earlier "p" images.

In the example in Figure 2.7, whether or not any pixels are really processed when im_Lab2disp() is called depends upon the mode in which out was opened. If out is also a partial image, then no pixels will be calculated — instead, a pipeline of VIPS operations will be constructed behind the scenes and attached to out.

Conversely, if out is a real image (that is, either "w" or "t"), then the final VIPS operation in the function (im_XYZ2disp()) will output the entire image to out, causing the earlier parts of im_Lab2disp() (and indeed possibly some earlier pieces of program, if in was also a "p" image) to run.

When a VIPS pipeline does finally evaluate, all of the functions in the pipeline execute together, sucking image data through the system in small pieces. As a consequence, no intermediate images are generated, large amounts of RAM are not needed, and no slow disc I/O needs to be performed.

Since VIPS partial I/O is demand-driven rather than data-driven this works even if some of the operations perform coordinate transformations. We could, for example, include a call to im_affine(), which performs arbitrary rotation and scaling, and everything would still work correctly.

Pitfalls with partials

To go with all of the benefits that partial image I/O brings, there are also some problems. The most serious is that you are often not quite certain when computation will happen. This can cause problems if you close an input file, thinking that it is finished with, when in fact that file has not been processed yet. Doing this results in dangling pointers and an almost certain core-dump.

You can prevent this from happening with careful use of im_open_local(). If you always open local to your output image, you can be sure that the input will not be closed before the output has been generated to a file or memory buffer. You do not need to be so careful with non-image arguments. VIPS functions which take extra non-image arguments (a matrix, perhaps) are careful to make their own copy of the object before returning.

Non-image output

Some VIPS functions consume images, but make no image output. im_stats() for example, scans an image calculating various statistical values. When you use im_stats(), it behaves as a data sink, sucking image data through any earlier pipeline stages.

Calculating twice

In some circumstances, the same image data can be generated twice. Figure 2.8 is a function which finds the mean value of an image, and writes a new image in which pixels less than the mean are set to 0 and images greater than the mean are set to 255.


int  
threshold_at_mean( IMAGE ⋆in, IMAGE ⋆out )  
{  
    double mean;  
 
    if( im_avg( in, &mean ) ||  
        im_moreconst( in, out, mean ) )  
        return( -1 );  
 
    return( 0 );  
}


Figure 2.8: Threshold an image at the mean value

This seems straightforward — but consider if image in were a "p", and represented the output of a large pipeline of operations. The call to im_avg() would force the evaluation of the entire pipeline, and throw it all away, keeping only the average value. The subsequent call to im_moreconst() will cause the pipeline to be evaluated a second time.

When designing a program, it is sensible to pay attention to these issues. It might be faster, in some cases, to output to a file before calling im_avg(), find the average of the disc file, and then run im_moreconst() from that. There’s also im_cache() which can keep recent parts of a very large image.

Blocking computation

IMAGE descriptors have a flag called kill which can be used to block computation. If im->kill is set to a non-zero value, then any VIPS pipelines which use im as an intermediate will fail with an error message. This is useful for user-interface writers — suppose your interface is forced to close an image which many other images are using as a source of data. You can just set the kill flag in all of the deleted image’s immediate children and prevent any dangling pointers from being followed.

Limitations

Not all VIPS operations are partial-aware. These non-partial operations use a pre-VIPS 7.0 I/O scheme in which the whole of the input image has to be present at the same time. In some cases, this is because partial I/O simply makes no sense — for example, a Fourier Transform can produce no output until it has seen all of the input. im_fwfft() is therefore not a partial operation. In other cases, we have simply not got around to rewriting the old non-partial operation in the newer partial style.

You can mix partial and non-partial VIPS operations freely, without worrying about which type they are. The only effect will be on the time your pipeline takes to execute, and the memory requirements of the intermediate images. VIPS uses the following rules when you mix the two styles of operation:

  1. When a non-partial operation is asked to output to a partial image descriptor, the "p" descriptor is magically transformed into a "t" descriptor.
  2. When a non-partial operation is asked to read from a "p" descriptor, the "p" descriptor is turned into a "t" type, and any earlier stages in the pipeline forced to evaluate into that memory buffer.

    The non-partial operation then processes from the memory buffer.

These rules have the consequence that you may only process very large images if you only use partial operations. If you use any non-partial operations, then parts of your pipelines will fall back to old whole-image I/O and you will need to think carefully about where your intermediates should be stored.