PIO is a very general image IO system, and because of this flexibility,
can be complicated to program. As a convenience, VIPS offers an easy-to-use
layer over PIO with the funtions im_wrapone()
and im_wrapmany()
.
If your image processing function is uninterested in coordinates, that is, if your input and output images are the same size, and each output pixel depends only upon the value of the corresponding pixel in the input image or images, then these functions are for you.
Consider the invert()
function of figure 2.2. First,
we have to write the core of this as a buffer-processing function:
#include <stdio.h> #include <stdlib.h> #include <vips/vips.h> #include <vips/region.h> /* p points to a buffer of pixels which * need inverting, q points to the buffer * we should write the result to, and n * is the number of pels present. */ static void invert_buffer( unsigned char *p, unsigned char *q, int n ) { int i; for( i = 0; i < n; i++ ) q[i] = 255 - p[i]; }
Now we have to wrap up this very primitive expression of the invert operation
as a PIO function. We use im_wrapone()
to do this. It has type:
int im_wrapone( IMAGE *in, IMAGE *out, im_wrapone_fn fn, void *a, void *b )
where:
void (*im_wrapone_fn)(void *in, void *out, int n, void *a, void *b )
almost the same type as our buffer-processing function above. The values
a
and b
are carried around by VIPS for whatever use you
fancy. invert()
can now be written as:
int invert( IMAGE *in, IMAGE *out ) { /* Check parameters. */ if( in->BandFmt != IM_BANDFMT_UCHAR || in->Bands != 1 || in->Coding != IM_CODING_NONE ) { im_errormsg( "invert: bad image" ); return( -1 ); } /* Set fields in output image. */ if( im_cp_desc( out, in ) ) return( -1 ); /* Process! We don't use either of the * user parameters in this function, * so leave them as NULL. */ if( im_wrapone( in, out, (im_wrapone_fn) invert_buffer, NULL, NULL ) ) return( -1 ); return( 0 ); }
And that's all there is to it. This function will have all of the desirable
properties of PIO functions, while being as easy to program as the WIO
invert()
earlier in this guide.
This version of invert()
is not very general: it will only accept
one-band unsigned char images. It is easy to modify for n-band images:
/* As before, but use one of the user * parameters to pass in the number of * bands in the image. */ static void invert_buffer( unsigned char *p, unsigned char *q, int n, int nb ) { int i; int sz = n * nb; for( i = 0; i < sz; i++ ) q[i] = 255 - p[i]; }
We must also modify invert()
:
int invert( IMAGE *in, IMAGE *out ) { /* Check parameters. */ if( in->BandFmt != IM_BANDFMT_UCHAR || in->Coding != IM_CODING_NONE ) { im_errormsg( "invert: bad image" ); return( -1 ); } /* Set fields in output image. */ if( im_cp_desc( out, in ) ) return( -1 ); /* Process! The first user-parameter * is the number of bands involved. */ if( im_wrapone( in, out, (im_wrapone_fn)invert_buffer, (void *)in->Bands, NULL ) ) return( -1 ); return( 0 ); }
There are two significant hidden traps here. First, inside the buffer
processing functions, you may only read the contents of the user parameters
a
and b
, you may not write to them. This is because on a
multi-CPU machine, several copies of your buffer-processing functions will
be run in parallel -- if they all write to the same place, there will be
complete confusion. If you need writeable parameters (for example, to count
and report overflows), you can't use im_wrapone()
, you'll have to
use the PIO system in all its gory details, see below.
Secondly, your buffer processing function may not be called immediately. VIPS
may decide to delay evaluation of your operation until long after the call
to invert()
has returned. As a result, care is needed to ensure
that you never read anything in your buffer-processing function that may
have been freed. The best way to ensure this is to use the local resource
allocators, such as im_open_local()
and im_malloc()
. This issue
is discussed at length in the sections below, and in the Application
Programmers' Guide.
im_wrapone()
is for operations which take exactly one input image. VIPS
provides a second function, im_wrapmany()
, which works for any number
of input images. The type of im_wrapmany()
is slightly different:
int im_wrapmany( IMAGE **in, IMAGE *out, im_wrapmany_fn fn, void *a, void *b )
void (*im_wrapmany_fn)( void **in, void *out, int n, void *a, void *b )
im_wrapmany()
takes a NULL
-terminated array of input images,
and creates a NULL
-terminated array of buffers for the use of your
buffer processing function. A function to add two IM_BANDFMT_UCHAR
images to make a IM_BANDFMT_UCHAR
image might be written as:
static void add_buffer( unsigned char **in, unsigned short *out, int n, int bands ) { int i; int sz = n * bands; unsigned char *p1 = in[0]; unsigned char *p2 = in[1]; for( i = 0; i < sz; i++ ) out[i] = p1[i] + p2[i]; }
This can be made into a PIO function with:
int add_uchar( IMAGE *i1, IMAGE *i2, IMAGE *out ) { IMAGE *invec[3]; /* Check parameters. We don't need to * check that i1 and i2 are the same * size, im_wrapmany() does that for * us. */ if( i1->BandFmt != IM_BANDFMT_UCHAR || i1->Coding != IM_CODING_NONE || i2->BandFmt != IM_BANDFMT_UCHAR || i2->Coding != IM_CODING_NONE || i1->Bands != i2->Bands ) { im_errormsg( "add_uchar: bad in" ); return( -1 ); } /* Set fields in output image. As * input image, but we want a USHORT. */ if( im_cp_desc( out, i1 ) ) return( -1 ); out->BandFmt = IM_BANDFMT_USHORT; out->Bbits = IM_BBITS_SHORT; /* Process! The first user-parameter * is the number of bands involved. * invec is a NULL-terminated array of * input images. */ invec[0] = i1; invec[1] = i2; invec[2] = NULL; if( im_wrapmany( invec, out, (im_wrapone_fn)add_buffer, (void *)i1->Bands, NULL ) ) return( -1 ); return( 0 ); }