698 lines
18 KiB
TeX
698 lines
18 KiB
TeX
\section{Function dispatch and plug-ins}
|
|
|
|
(This chapter is on the verge of being deprecated. We have started building a
|
|
replacement based on \verb+GObject+, see \pref{sec:object}.)
|
|
|
|
As image processing libraries increase in size it becomes progressively more
|
|
difficult to build applications which present the operations the library
|
|
offers to the user. Every time a new operation is added, every user interface
|
|
needs to be adapted --- a job which can rapidly become unmanageable.
|
|
|
|
To address this problem VIPS includes a simple database which stores an
|
|
abstract description of every image processing operation. User interfaces,
|
|
rather than having special code wired into them for each operation, can
|
|
simply interrogate the database and present what they find to the user.
|
|
|
|
The operation database is extensible. You can define new operations, and even
|
|
new types, and add them to VIPS. These new operations will then automatically
|
|
appear in all VIPS user interfaces with no extra programming effort. Plugins
|
|
can extend the database at runtime: when VIPS starts, it loads all the plugins
|
|
in the VIPS library area.
|
|
|
|
\subsection{Simple plugin example}
|
|
|
|
As an example, consider this function:
|
|
|
|
\begin{verbatim}
|
|
#include <stdio.h>
|
|
|
|
#include <vips/vips.h>
|
|
|
|
/* The function we define. Call this
|
|
* from other parts of your C
|
|
* application.
|
|
*/
|
|
int
|
|
double_integer( int in )
|
|
{
|
|
return( in * 2 );
|
|
}
|
|
\end{verbatim}
|
|
|
|
\noindent
|
|
The source for all the example code in this section is in the vips-examples
|
|
package.
|
|
|
|
The first step is to make a layer over this function which will make it
|
|
look like a standard VIPS function. VIPS insists on the following pattern:
|
|
|
|
\begin{itemize}
|
|
|
|
\item
|
|
The function should be int-valued, and return 0 for success and non-zero
|
|
for error. It should set \verb+im_error()+.
|
|
|
|
\item
|
|
The function should take a single argument: a pointer to a
|
|
\verb+NULL+-terminated array of \verb+im_objects+.
|
|
|
|
\item
|
|
Each \verb+im_object+ represents one argument to the function (either output
|
|
or input) in the form specified by the corresponding entry in the function's
|
|
argument descriptor.
|
|
|
|
\end{itemize}
|
|
|
|
The argument descriptor is an array of structures, each describing one
|
|
argument. For this example, it is:
|
|
|
|
\begin{verbatim}
|
|
/* Describe the type of our function.
|
|
* One input int, and one output int.
|
|
*/
|
|
static im_arg_desc arg_types[] = {
|
|
IM_INPUT_INT( "in" ),
|
|
IM_OUTPUT_INT( "out" )
|
|
};
|
|
\end{verbatim}
|
|
|
|
\verb+IM_INPUT_INT()+ and \verb+IM_OUTPUT_INT()+ are macros defined in
|
|
\verb+<vips/dispatch.h>+ which make argument types easy to define. Other
|
|
macros available are listed in table~\ref{tab:type}.
|
|
|
|
\begin{tab2}
|
|
\begin{center}
|
|
\begin{tabular}{|l|l|l|}
|
|
\hline
|
|
Macro & Meaning &
|
|
\texttt{im\_object} has type \\
|
|
\hline
|
|
\texttt{IM\_INPUT\_INT} & Input int &
|
|
\texttt{int *} \\
|
|
\texttt{IM\_INPUT\_INTVEC} & Input vector of int &
|
|
\texttt{im\_intvec\_object *} \\
|
|
\texttt{IM\_INPUT\_IMASK} & Input int array &
|
|
\texttt{im\_mask\_object *} \\
|
|
|
|
\texttt{IM\_OUTPUT\_INT} & Output int &
|
|
\texttt{int *} \\
|
|
\texttt{IM\_INPUT\_INTVEC} & Output vector of int &
|
|
\texttt{im\_intvec\_object *} \\
|
|
\texttt{IM\_OUTPUT\_IMASK} & Output int array to file &
|
|
\texttt{im\_mask\_object *} \\
|
|
|
|
\texttt{IM\_INPUT\_DOUBLE} & Input double &
|
|
\texttt{double *} \\
|
|
\texttt{IM\_INPUT\_DOUBLEVEC} & Input vector of double &
|
|
\texttt{im\_realvec\_object *} \\
|
|
\texttt{IM\_INPUT\_DMASK} & Input double array &
|
|
\texttt{im\_mask\_object *} \\
|
|
|
|
\texttt{IM\_OUTPUT\_DOUBLE} & Output double &
|
|
\texttt{double *} \\
|
|
\texttt{IM\_OUTPUT\_DOUBLEVEC} & Output vector of double &
|
|
\texttt{im\_realvec\_object *} \\
|
|
\texttt{IM\_OUTPUT\_DMASK} & Output double array to file &
|
|
\texttt{im\_mask\_object *} \\
|
|
\texttt{IM\_OUTPUT\_DMASK\_STATS}& Output double array to screen &
|
|
\\
|
|
|
|
\texttt{IM\_OUTPUT\_COMPLEX} & Output complex &
|
|
\texttt{double *} \\
|
|
|
|
\texttt{IM\_INPUT\_STRING} & Input string &
|
|
\texttt{char *} \\
|
|
\texttt{IM\_OUTPUT\_STRING} & Output string &
|
|
\texttt{char *} \\
|
|
|
|
\texttt{IM\_INPUT\_IMAGE} & Input image &
|
|
\texttt{IMAGE *} \\
|
|
\texttt{IM\_INPUT\_IMAGEVEC} & Vector of input images &
|
|
\texttt{IMAGE **} \\
|
|
\texttt{IM\_OUTPUT\_IMAGE} & Output image &
|
|
\texttt{IMAGE *} \\
|
|
\texttt{IM\_RW\_IMAGE} & Read-write image &
|
|
\texttt{IMAGE *} \\
|
|
|
|
\texttt{IM\_INPUT\_DISPLAY} & Input display &
|
|
\texttt{im\_col\_display *} \\
|
|
\texttt{IM\_OUTPUT\_DISPLAY} & Output display &
|
|
\texttt{im\_col\_display *} \\
|
|
|
|
|
|
\texttt{IM\_INPUT\_GVALUE} & Input GValue &
|
|
\texttt{GValue *} \\
|
|
\texttt{IM\_OUTPUT\_GVALUE} & Output GValue &
|
|
\texttt{GValue *} \\
|
|
|
|
\texttt{IM\_INPUT\_INTERPOLATE} & Input VipsInterpolate &
|
|
\texttt{VipsInterpolate *} \\
|
|
\hline
|
|
\end{tabular}
|
|
\end{center}
|
|
\caption{Argument type macros\label{tab:type}}
|
|
\end{tab2}
|
|
|
|
The argument to the type macro is the name of the argument. These names
|
|
are used by user-interface programs to provide feedback, and sometimes as
|
|
variable names. The order in which you list the arguments is the order in
|
|
which user-interfaces will present them to the user. You should use the
|
|
following conventions when selecting names and an order for your arguments:
|
|
|
|
\begin{itemize}
|
|
|
|
\item
|
|
Names should be entirely in lower-case and contain no special characters,
|
|
apart from the digits 0-9 and the underscore character `\_'.
|
|
|
|
\item
|
|
Names should indicate the function of the argument. For example,
|
|
\verb+im_add()+ has the following argument names:
|
|
|
|
\begin{verbatim}
|
|
example% vips -help im_add
|
|
vips: args: in1 in2 out
|
|
where:
|
|
in1 is of type "image"
|
|
in2 is of type "image"
|
|
out is of type "image"
|
|
add two images, from package
|
|
"arithmetic"
|
|
flags:
|
|
(PIO function)
|
|
(no coordinate transformation)
|
|
(point-to-point operation)
|
|
\end{verbatim}
|
|
|
|
\item
|
|
You should order arguments with large input objects first, then output
|
|
objects, then any extra arguments or options. For example, \verb+im_extract()+
|
|
has the following sequence of arguments:
|
|
|
|
\begin{verbatim}
|
|
example% vips -help im_extract
|
|
vips: args: input output left top
|
|
width height channel
|
|
where:
|
|
input is of type "image"
|
|
output is of type "image"
|
|
left is of type "integer"
|
|
top is of type "integer"
|
|
width is of type "integer"
|
|
height is of type "integer"
|
|
channel is of type "integer"
|
|
extract area/band, from package
|
|
"conversion"
|
|
flags:
|
|
(PIO function)
|
|
(no coordinate transformation)
|
|
(point-to-point operation)
|
|
\end{verbatim}
|
|
|
|
\end{itemize}
|
|
|
|
This function sits over \verb+double_integer()+, providing VIPS with an
|
|
interface which it can call:
|
|
|
|
\begin{verbatim}
|
|
/* Call our function via a VIPS
|
|
* im_object vector.
|
|
*/
|
|
static int
|
|
double_vec( im_object *argv )
|
|
{
|
|
int *in = (int *) argv[0];
|
|
int *out = (int *) argv[1];
|
|
|
|
*out = double_integer( *in );
|
|
|
|
/* Always succeed.
|
|
*/
|
|
return( 0 );
|
|
}
|
|
\end{verbatim}
|
|
|
|
Finally, these two pieces of information (the argument description and
|
|
the VIPS-style function wrapper) can be gathered together into a function
|
|
description.
|
|
|
|
\begin{verbatim}
|
|
/* Description of double_integer.
|
|
*/
|
|
static im_function double_desc = {
|
|
"double_integer",
|
|
"double an integer",
|
|
0,
|
|
double_vec,
|
|
IM_NUMBER( arg_types ),
|
|
arg_types
|
|
};
|
|
\end{verbatim}
|
|
\label{sec:number}
|
|
|
|
\verb+IM_NUMBER()+ is a macro which returns the number of elements in a
|
|
static array. The \verb+flags+ field contains hints which user-interfaces
|
|
can use for various optimisations. At present, the possible values are:
|
|
|
|
\begin{description}
|
|
|
|
\item[\texttt{IM\_FN\_PIO}]
|
|
This function uses the VIPS PIO system (see \pref{sec:pio}).
|
|
|
|
\item[\texttt{IM\_FN\_TRANSFORM}]
|
|
This the function transforms coordinates.
|
|
|
|
\item[\texttt{IM\_FN\_PTOP}]
|
|
This is a point-to-point operation, that is, it can be replaced with a
|
|
look-up table.
|
|
|
|
\item[\texttt{IM\_FN\_NOCACHE}]
|
|
This operation has side effects and should not be cached. Useful for video
|
|
grabbers, for example.
|
|
|
|
\end{description}
|
|
|
|
This function description now needs to be added to the VIPS function database.
|
|
VIPS groups sets of related functions together in packages. There is only
|
|
a single function in this example, so we can just write:
|
|
|
|
\begin{verbatim}
|
|
/* Group up all the functions in this
|
|
* file.
|
|
*/
|
|
static im_function
|
|
*function_list[] = {
|
|
&double_desc
|
|
};
|
|
|
|
/* Define the package_table symbol.
|
|
* This is what VIPS looks for when
|
|
* loading the plugin.
|
|
*/
|
|
im_package package_table = {
|
|
"example",
|
|
IM_NUMBER( function_list ),
|
|
function_list
|
|
};
|
|
\end{verbatim}
|
|
|
|
The package has to be named \verb+package_table+, and has to be exported
|
|
from the file (that is, not a static). VIPS looks for a symbol of this name
|
|
when it opens your object file.
|
|
|
|
This file needs to be made into a dynamically loadable object. On my machine,
|
|
I can do this with:
|
|
|
|
\begin{verbatim}
|
|
example% gcc -fPIC -DPIC -c
|
|
`pkg-config vips-7.12 --cflags`
|
|
plug.c -o plug.o
|
|
example% gcc -shared plug.o
|
|
-o double.plg
|
|
\end{verbatim}
|
|
|
|
You can now use \verb+double.plg+ with any of the VIPS applications which
|
|
support function dispatch. For example:
|
|
|
|
\begin{verbatim}
|
|
example% vips -plugin double.plg \
|
|
double_integer 12
|
|
24
|
|
example%
|
|
\end{verbatim}
|
|
|
|
When VIPS starts up, it looks for a directory in the library directory called
|
|
\verb+vips-+, with the vips major and minor versions numbers as extensions,
|
|
and loads all files in there with the suffix \verb+.plg+. So for example, on
|
|
my machine, the plugin directory is \verb+/usr/lib/vips-7.16+ and any plugins
|
|
in that directory are automatically loaded into any VIPS programs on startup.
|
|
|
|
\subsection{A more complicated example}
|
|
|
|
This section lists the source for \verb+im_extract()+'s function
|
|
description. Almost all functions in the VIPS library have descriptors ---
|
|
if you are not sure how to write a description, it's usually easiest to
|
|
copy one from a similar function in the library.
|
|
|
|
\begin{verbatim}
|
|
/* Args to im_extract.
|
|
*/
|
|
static im_arg_desc
|
|
extract_args[] = {
|
|
IM_INPUT_IMAGE( "input" ),
|
|
IM_OUTPUT_IMAGE( "output" ),
|
|
IM_INPUT_INT( "left" ),
|
|
IM_INPUT_INT( "top" ),
|
|
IM_INPUT_INT( "width" ),
|
|
IM_INPUT_INT( "height" ),
|
|
IM_INPUT_INT( "channel" )
|
|
};
|
|
|
|
/* Call im_extract via arg vector.
|
|
*/
|
|
static int
|
|
extract_vec( im_object *argv )
|
|
{
|
|
IMAGE_BOX box;
|
|
|
|
box.xstart = *((int *) argv[2]);
|
|
box.ystart = *((int *) argv[3]);
|
|
box.xsize = *((int *) argv[4]);
|
|
box.ysize = *((int *) argv[5]);
|
|
box.chsel = *((int *) argv[6]);
|
|
|
|
return( im_extract(
|
|
argv[0], argv[1], &box ) );
|
|
}
|
|
|
|
/* Description of im_extract.
|
|
*/
|
|
static im_function
|
|
extract_desc = {
|
|
"im_extract",
|
|
"extract area/band",
|
|
IM_FN_PIO | IM_FN_TRANSFORM,
|
|
extract_vec,
|
|
NUMBER( extract_args ),
|
|
extract_args
|
|
};
|
|
\end{verbatim}
|
|
|
|
\subsection{Adding new types}
|
|
|
|
The VIPS type mechanism is extensible. User plug-ins can add new types
|
|
and user-interfaces can (to a certain extent) provide interfaces to these
|
|
user-defined types.
|
|
|
|
Here is the definition of \verb+im_arg_desc+:
|
|
|
|
\begin{verbatim}
|
|
/* Describe a VIPS command argument.
|
|
*/
|
|
typedef struct {
|
|
char *name;
|
|
im_type_desc *desc;
|
|
im_print_obj_fn print;
|
|
} im_arg_desc;
|
|
\end{verbatim}
|
|
|
|
The \verb+name+ field is the argument name above. The \verb+desc+ field
|
|
points to a structure defining the argument type, and the \verb+print+ field
|
|
is an (optionally \verb+NULL+) pointer to a function which VIPS will call
|
|
for output arguments after your function successfully completes and before
|
|
the object is destroyed. It can be used to print results to the terminal,
|
|
or to copy results into a user-interface layer.
|
|
|
|
\begin{verbatim}
|
|
/* Success on an argument. This is
|
|
* called if the image processing
|
|
* function succeeds and should be
|
|
* used to (for example) print
|
|
* output.
|
|
*/
|
|
typedef int (*im_print_obj_fn)
|
|
( im_object obj );
|
|
\end{verbatim}
|
|
|
|
\verb+im_type_desc+ is defined as:
|
|
|
|
\begin{verbatim}
|
|
/* Describe a VIPS type.
|
|
*/
|
|
typedef struct {
|
|
im_arg_type type;
|
|
int size;
|
|
im_type_flags flags;
|
|
im_init_obj_fn init;
|
|
im_dest_obj_fn dest;
|
|
} im_type_desc;
|
|
\end{verbatim}
|
|
|
|
Where \verb+im_arg_type+ is defined as
|
|
|
|
\begin{verbatim}
|
|
/* Type names. You may define your
|
|
* own, but if you use one of these,
|
|
* then you should use the built-in
|
|
* VIPS type converters.
|
|
*/
|
|
#define IM_TYPE_IMAGEVEC "imagevec"
|
|
#define IM_TYPE_DOUBLEVEC "doublevec"
|
|
#define IM_TYPE_INTVEC "intvec"
|
|
#define IM_TYPE_DOUBLE "double"
|
|
#define IM_TYPE_INT "integer"
|
|
#define IM_TYPE_COMPLEX "complex"
|
|
#define IM_TYPE_STRING "string"
|
|
#define IM_TYPE_IMASK "intmask"
|
|
#define IM_TYPE_DMASK "doublemask"
|
|
#define IM_TYPE_IMAGE "image"
|
|
#define IM_TYPE_DISPLAY "display"
|
|
#define IM_TYPE_GVALUE "gvalue"
|
|
typedef char *im_arg_type;
|
|
\end{verbatim}
|
|
|
|
In other words, it's just a string. When you add a new type, you just need
|
|
to choose a new unique string to name it. Be aware that the string is printed
|
|
to the user by various parts of VIPS, and so needs to be ``human-readable''.
|
|
The flags are:
|
|
|
|
\begin{verbatim}
|
|
/* These bits are ored together to
|
|
* make the flags in a type
|
|
* descriptor.
|
|
*
|
|
* IM_TYPE_OUTPUT: set to indicate
|
|
* output, otherwise input.
|
|
*
|
|
* IM_TYPE_ARG: Two ways of making
|
|
* an im_object --- with and without
|
|
* a command-line string to help you
|
|
* along. Arguments with a string
|
|
* are thing like IMAGE descriptors,
|
|
* which require a filename to
|
|
* initialise. Arguments without are
|
|
* things like output numbers, where
|
|
* making the object simply involves
|
|
* allocating storage.
|
|
*/
|
|
|
|
typedef enum {
|
|
IM_TYPE_OUTPUT = 0x1,
|
|
IM_TYPE_ARG = 0x2
|
|
} im_type_flags;
|
|
\end{verbatim}
|
|
|
|
And the \verb+init+ and \verb+destroy+ functions are:
|
|
|
|
\begin{verbatim}
|
|
/* Initialise and destroy objects.
|
|
* The "str" argument to the init
|
|
* function will not be supplied
|
|
* if this is not an ARG type.
|
|
*/
|
|
typedef int (*im_init_obj_fn)
|
|
( im_object *obj, char *str );
|
|
typedef int (*im_dest_obj_fn)
|
|
( im_object obj );
|
|
\end{verbatim}
|
|
|
|
As an example, here is the definition for a new type of unsigned
|
|
integers. First, we need to define the \verb+init+ and \verb+print+
|
|
functions. These transform objects of the type to and from string
|
|
representation.
|
|
|
|
\begin{verbatim}
|
|
/* Init function for unsigned int
|
|
* input.
|
|
*/
|
|
static int
|
|
uint_init( im_object *obj, char *str )
|
|
{
|
|
unsigned int *i = (int *) *obj;
|
|
|
|
if( sscanf( str, "%d", i ) != 1 ||
|
|
*i < 0 ) {
|
|
im_error( "uint_init",
|
|
"bad format" );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Print function for unsigned int
|
|
* output.
|
|
*/
|
|
static int
|
|
uint_print( im_object obj )
|
|
{
|
|
unsigned int *i =
|
|
(unsigned int *) obj;
|
|
|
|
printf( "%d\n", (int) *i );
|
|
|
|
return( 0 );
|
|
}
|
|
\end{verbatim}
|
|
|
|
Now we can define the type itself. We make two of these --- one for unsigned
|
|
int used as input, and one for output.
|
|
|
|
\begin{verbatim}
|
|
/* Name our type.
|
|
*/
|
|
#define TYPE_UINT "uint"
|
|
|
|
/* Input unsigned int type.
|
|
*/
|
|
static im_type_desc input_uint = {
|
|
TYPE_UINT, /* Its an int */
|
|
sizeof( unsigned int ),/* Memory */
|
|
IM_TYPE_ARG, /* Needs arg */
|
|
uint_init, /* Init */
|
|
NULL /* Destroy */
|
|
};
|
|
|
|
/* Output unsigned int type.
|
|
*/
|
|
static im_type_desc output_uint = {
|
|
TYPE_UINT, /* It's an int */
|
|
sizeof( unsigned int ),/* Memory */
|
|
IM_TYPE_OUTPUT, /* It's output */
|
|
NULL, /* Init */
|
|
NULL /* Destroy */
|
|
};
|
|
\end{verbatim}
|
|
|
|
Finally, we can define two macros to make structures of type
|
|
\verb+im_arg_desc+ for us.
|
|
|
|
\begin{verbatim}
|
|
#define INPUT_UINT( S ) \
|
|
{ S, &input_uint, NULL }
|
|
#define OUTPUT_UINT( S ) \
|
|
{ S, &output_uint, uint_print }
|
|
\end{verbatim}
|
|
|
|
For more examples, see the definitions for the built-in VIPS types.
|
|
|
|
\subsection{Using function dispatch in your application}
|
|
|
|
VIPS provides a set of functions for adding new image processing functions
|
|
to the VIPS function database, finding functions by name, and calling
|
|
functions. See the manual pages for full details.
|
|
|
|
\subsubsection{Adding and removing functions}
|
|
|
|
\begin{verbatim}
|
|
im_package *im_load_plugin(
|
|
const char *name );
|
|
\end{verbatim}
|
|
|
|
This function opens the named file, searches it for a symbol named
|
|
\verb+package_table+, and adds any functions it finds to the VIPS function
|
|
database. When you search for a function, any plug-ins are searched first,
|
|
so you can override standard VIPS function with your own code.
|
|
|
|
The function returns a pointer to the package it added, or \verb+NULL+
|
|
on error.
|
|
|
|
\begin{verbatim}
|
|
int im_close_plugins( void )
|
|
\end{verbatim}
|
|
|
|
This function closes all plug-ins, removing then from the VIPS function
|
|
database. It returns non-zero on error.
|
|
|
|
\subsubsection{Searching the function database}
|
|
|
|
\begin{verbatim}
|
|
void *im_map_packages(
|
|
im_list_map_fn fn, void *a )
|
|
\end{verbatim}
|
|
|
|
This function applies the argument function \verb+fn+ to every package
|
|
in the database, starting with the most recently added package. As with
|
|
\verb+im_list_map()+, the argument function should return \verb+NULL+
|
|
to continue searching, or non-\verb+NULL+ to terminate the search
|
|
early. \verb+im_map_packages()+ returns \verb+NULL+ if \verb+fn+ returned
|
|
\verb+NULL+ for all arguments. The extra argument \verb+a+ is carried around
|
|
by VIPS for your use.
|
|
|
|
For example, this fragment of code prints the names of all loaded packages
|
|
to \verb+fd+:
|
|
|
|
\begin{verbatim}
|
|
static void *
|
|
print_package_name( im_package *pack,
|
|
FILE *fp )
|
|
{
|
|
(void) fprintf( fp,
|
|
"package: \"%s\"\n",
|
|
pack->name );
|
|
|
|
/* Continue search.
|
|
*/
|
|
return( NULL );
|
|
}
|
|
|
|
static void
|
|
print_packages( FILE *fp )
|
|
{
|
|
(void) im_map_packages(
|
|
(im_list_map_fn)
|
|
print_package_name, fp );
|
|
}
|
|
\end{verbatim}
|
|
|
|
VIPS defines three convenience functions based on \verb+im_map_packages()+
|
|
which simplify searching for specific functions:
|
|
|
|
\begin{verbatim}
|
|
im_function *
|
|
im_find_function( char *name )
|
|
im_package *
|
|
im_find_package( char *name )
|
|
im_package *
|
|
im_package_of_function( char *name )
|
|
\end{verbatim}
|
|
|
|
\subsubsection{Building argument structures and running commands}
|
|
|
|
\begin{verbatim}
|
|
int im_free_vargv( im_function *fn,
|
|
im_object *vargv )
|
|
int im_allocate_vargv(
|
|
im_function *fn,
|
|
im_object *vargv )
|
|
\end{verbatim}
|
|
|
|
These two functions allocate space for and free VIPS argument lists. The
|
|
allocate function simply calls \verb+im_malloc()+ to allocate any store
|
|
that the types require (and also initializes it to zero). The free function
|
|
just calls \verb+im_free()+ for any storage that was allocated.
|
|
|
|
Note that neither of these functions calls the \verb+init+, \verb+dest+
|
|
or \verb+print+ functions for the types --- that's up to you.
|
|
|
|
\begin{verbatim}
|
|
int im_run_command( char *name,
|
|
int argc, char **argv )
|
|
\end{verbatim}
|
|
|
|
This function does everything. In effect,
|
|
|
|
\begin{verbatim}
|
|
im_run_command( "im_invert", 2,
|
|
{ "fred.v", "fred2.v", NULL } )
|
|
\end{verbatim}
|
|
|
|
is exactly equivalent to
|
|
|
|
\begin{verbatim}
|
|
system( "vips im_invert fred.v "
|
|
"fred2.v" )
|
|
\end{verbatim}
|
|
|
|
but no process is forked.
|