Next: 3.2 Region descriptors Up: 3. Programming PIO functions Previous: 3. Programming PIO functions Contents

#
3.1 Easy PIO with `im_wrapone()` and `im_wrapmany()`

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 );
    }

Next: 3.2 Region descriptors Up: 3. Programming PIO functions Previous: 3. Programming PIO functions Contents