merge orc branch
This commit is contained in:
parent
50b9e5c855
commit
f3b5bfde80
|
@ -39,6 +39,8 @@
|
||||||
- mask gtk-doc done
|
- mask gtk-doc done
|
||||||
- add cfitsio dependancy
|
- add cfitsio dependancy
|
||||||
- add FITS reader
|
- add FITS reader
|
||||||
|
- land the vector branmch ... we have SSE erode/dilate/add/conv
|
||||||
|
- add IM_SWAP
|
||||||
|
|
||||||
12/5/10 started 7.22.2
|
12/5/10 started 7.22.2
|
||||||
- the conditional image of ifthenelse can be any format, a (!=0) is added if
|
- the conditional image of ifthenelse can be any format, a (!=0) is added if
|
||||||
|
|
7
TODO
7
TODO
|
@ -1,4 +1,9 @@
|
||||||
- add IM_SWAP, see orc branch util.h
|
- lab [100,0,0] -> srgb [255, 255, 254]? how odd
|
||||||
|
|
||||||
|
- scrap im_convsep_f() ... just use im_conv_f() twice
|
||||||
|
|
||||||
|
- make im_rank() use IM_SWAP()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- test fits reader more ... colour?
|
- test fits reader more ... colour?
|
||||||
|
|
21
configure.in
21
configure.in
|
@ -375,6 +375,21 @@ if test x"$with_magick" != "xno"; then
|
||||||
LIBS=$save_LIBS
|
LIBS=$save_LIBS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# orc
|
||||||
|
AC_ARG_WITH([orc],
|
||||||
|
AS_HELP_STRING([--without-orc], [build without orc (default: test)]))
|
||||||
|
|
||||||
|
if test x"$with_orc" != "xno"; then
|
||||||
|
# we use loadpw etc.
|
||||||
|
PKG_CHECK_MODULES(ORC, orc-0.4 >= 0.4.11,
|
||||||
|
[AC_DEFINE(HAVE_ORC,1,[define if you have orc-0.4.11 or later installed.])
|
||||||
|
with_orc=yes
|
||||||
|
PACKAGES_USED="$PACKAGES_USED orc-0.4"],
|
||||||
|
[AC_MSG_WARN([orc-0.4.11 or later not found; disabling orc support])
|
||||||
|
with_orc=no
|
||||||
|
])
|
||||||
|
fi
|
||||||
|
|
||||||
# lcms ... look for lcms2 first, it has better threading support
|
# lcms ... look for lcms2 first, it has better threading support
|
||||||
AC_ARG_WITH([lcms],
|
AC_ARG_WITH([lcms],
|
||||||
AS_HELP_STRING([--without-lcms], [build without lcms (default: test)]))
|
AS_HELP_STRING([--without-lcms], [build without lcms (default: test)]))
|
||||||
|
@ -570,14 +585,14 @@ fi
|
||||||
# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS and VIPS_CXX_LIBS
|
# Gather all up for VIPS_CFLAGS, VIPS_INCLUDES, VIPS_LIBS and VIPS_CXX_LIBS
|
||||||
# sort includes to get longer, more specific dirs first
|
# sort includes to get longer, more specific dirs first
|
||||||
# helps, for example, selecting graphicsmagick over imagemagick
|
# helps, for example, selecting graphicsmagick over imagemagick
|
||||||
VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $PANGOFT2_CFLAGS $FFTW3_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $OPENEXR_CFLAGS
|
VIPS_CFLAGS=`for i in $VIPS_CFLAGS $GTHREAD_CFLAGS $REQUIRED_CFLAGS $PANGOFT2_CFLAGS $FFTW3_CFLAGS $MAGICK_CFLAGS $PNG_CFLAGS $EXIF_CFLAGS $MATIO_CFLAGS $CFITSIO_CFLAGS $OPENEXR_CFLAGS $ORC_CFLAGS
|
||||||
do
|
do
|
||||||
echo $i
|
echo $i
|
||||||
done | sort -ru`
|
done | sort -ru`
|
||||||
VIPS_CFLAGS=`echo $VIPS_CFLAGS`
|
VIPS_CFLAGS=`echo $VIPS_CFLAGS`
|
||||||
VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS"
|
VIPS_CFLAGS="$VIPS_DEBUG_FLAGS $VIPS_CFLAGS"
|
||||||
VIPS_INCLUDES="$PNG_INCLUDES $TIFF_INCLUDES $ZIP_INCLUDES $JPEG_INCLUDES $FFTW_INCLUDES $LCMS_INCLUDES"
|
VIPS_INCLUDES="$PNG_INCLUDES $TIFF_INCLUDES $ZIP_INCLUDES $JPEG_INCLUDES $FFTW_INCLUDES $LCMS_INCLUDES"
|
||||||
VIPS_LIBS="$MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $ZIP_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $PANGOFT2_LIBS $FFTW3_LIBS $FFTW_LIBS $LCMS_LIBS $OPENEXR_LIBS $CFITSIO_LIBS $MATIO_LIBS $EXIF_LIBS -lm"
|
VIPS_LIBS="$MAGICK_LIBS $PNG_LIBS $TIFF_LIBS $ZIP_LIBS $JPEG_LIBS $GTHREAD_LIBS $REQUIRED_LIBS $PANGOFT2_LIBS $FFTW3_LIBS $FFTW_LIBS $ORC_LIBS $LCMS_LIBS $OPENEXR_LIBS $CFITSIO_LIBS $MATIO_LIBS $EXIF_LIBS -lm"
|
||||||
# need -lstdc++ for (eg.) the C++ format loaders
|
# need -lstdc++ for (eg.) the C++ format loaders
|
||||||
VIPS_CXX_LIBS="-lstdc++"
|
VIPS_CXX_LIBS="-lstdc++"
|
||||||
|
|
||||||
|
@ -660,6 +675,8 @@ build docs with gtkdoc $enable_gtk_doc
|
||||||
use fftw3 for FFT: $with_fftw3
|
use fftw3 for FFT: $with_fftw3
|
||||||
Magick package: $with_magickpackage
|
Magick package: $with_magickpackage
|
||||||
file import with libMagick: $with_magick
|
file import with libMagick: $with_magick
|
||||||
|
accelerate loops with orc: $with_orc
|
||||||
|
(needs orc-0.4.11 or later)
|
||||||
ICC profile support with lcms: $with_lcms (version $with_lcms_ver)
|
ICC profile support with lcms: $with_lcms (version $with_lcms_ver)
|
||||||
file import with OpenEXR: $with_OpenEXR
|
file import with OpenEXR: $with_OpenEXR
|
||||||
file import with matio: $with_matio
|
file import with matio: $with_matio
|
||||||
|
|
|
@ -30,6 +30,8 @@
|
||||||
* - more of operation scaffold moved inside
|
* - more of operation scaffold moved inside
|
||||||
* 25/7/10
|
* 25/7/10
|
||||||
* - remove oil support again ... we'll try Orc instead
|
* - remove oil support again ... we'll try Orc instead
|
||||||
|
* 29/10/10
|
||||||
|
* - move to VipsVector for Orc support
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -69,6 +71,7 @@
|
||||||
|
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
#include <vips/internal.h>
|
#include <vips/internal.h>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
#ifdef WITH_DMALLOC
|
||||||
#include <dmalloc.h>
|
#include <dmalloc.h>
|
||||||
|
@ -83,6 +86,8 @@
|
||||||
q[x] = p1[x] + p2[x]; \
|
q[x] = p1[x] + p2[x]; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static VipsVector *add_vectors[IM_BANDFMT_LAST] = { NULL };
|
||||||
|
|
||||||
static void
|
static void
|
||||||
add_buffer( PEL **in, PEL *out, int width, IMAGE *im )
|
add_buffer( PEL **in, PEL *out, int width, IMAGE *im )
|
||||||
{
|
{
|
||||||
|
@ -91,32 +96,49 @@ add_buffer( PEL **in, PEL *out, int width, IMAGE *im )
|
||||||
const int sz = width * im->Bands *
|
const int sz = width * im->Bands *
|
||||||
(vips_bandfmt_iscomplex( im->BandFmt ) ? 2 : 1);
|
(vips_bandfmt_iscomplex( im->BandFmt ) ? 2 : 1);
|
||||||
|
|
||||||
int x;
|
if( vips_vector_get_enabled() &&
|
||||||
|
add_vectors[im->BandFmt] ) {
|
||||||
|
VipsExecutor ex;
|
||||||
|
|
||||||
/* Add all input types. Keep types here in sync with bandfmt_add[]
|
vips_executor_set_program( &ex, add_vectors[im->BandFmt], sz );
|
||||||
* below.
|
vips_executor_set_source( &ex, 1, in[0] );
|
||||||
*/
|
vips_executor_set_source( &ex, 2, in[1] );
|
||||||
switch( im->BandFmt ) {
|
vips_executor_set_destination( &ex, out );
|
||||||
case IM_BANDFMT_UCHAR: LOOP( unsigned char, unsigned short ); break;
|
|
||||||
case IM_BANDFMT_CHAR: LOOP( signed char, signed short ); break;
|
|
||||||
case IM_BANDFMT_USHORT: LOOP( unsigned short, unsigned int ); break;
|
|
||||||
case IM_BANDFMT_SHORT: LOOP( signed short, signed int ); break;
|
|
||||||
case IM_BANDFMT_UINT: LOOP( unsigned int, unsigned int ); break;
|
|
||||||
case IM_BANDFMT_INT: LOOP( signed int, signed int ); break;
|
|
||||||
|
|
||||||
case IM_BANDFMT_FLOAT:
|
vips_executor_run( &ex );
|
||||||
case IM_BANDFMT_COMPLEX:
|
}
|
||||||
LOOP( float, float );
|
else {
|
||||||
break;
|
int x;
|
||||||
|
|
||||||
case IM_BANDFMT_DOUBLE:
|
/* Add all input types. Keep types here in sync with
|
||||||
case IM_BANDFMT_DPCOMPLEX:
|
* bandfmt_add[] below.
|
||||||
LOOP( double, double );
|
*/
|
||||||
break;
|
switch( im->BandFmt ) {
|
||||||
|
case IM_BANDFMT_UCHAR:
|
||||||
|
LOOP( unsigned char, unsigned short ); break;
|
||||||
|
case IM_BANDFMT_CHAR:
|
||||||
|
LOOP( signed char, signed short ); break;
|
||||||
|
case IM_BANDFMT_USHORT:
|
||||||
|
LOOP( unsigned short, unsigned int ); break;
|
||||||
|
case IM_BANDFMT_SHORT:
|
||||||
|
LOOP( signed short, signed int ); break;
|
||||||
|
case IM_BANDFMT_UINT:
|
||||||
|
LOOP( unsigned int, unsigned int ); break;
|
||||||
|
case IM_BANDFMT_INT:
|
||||||
|
LOOP( signed int, signed int ); break;
|
||||||
|
|
||||||
default:
|
case IM_BANDFMT_FLOAT:
|
||||||
g_assert( 0 );
|
case IM_BANDFMT_COMPLEX:
|
||||||
}
|
LOOP( float, float ); break;
|
||||||
|
|
||||||
|
case IM_BANDFMT_DOUBLE:
|
||||||
|
case IM_BANDFMT_DPCOMPLEX:
|
||||||
|
LOOP( double, double ); break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert( 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Save a bit of typing.
|
/* Save a bit of typing.
|
||||||
|
@ -311,6 +333,106 @@ static int bandfmt_add[10] = {
|
||||||
US, S, UI, I, UI, I, F, X, D, DX
|
US, S, UI, I, UI, I, F, X, D, DX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
im__init_programs( VipsVector *vectors[IM_BANDFMT_LAST],
|
||||||
|
int format_table[IM_BANDFMT_LAST] )
|
||||||
|
{
|
||||||
|
int fmt;
|
||||||
|
|
||||||
|
for( fmt = 0; fmt < IM_BANDFMT_LAST; fmt++ ) {
|
||||||
|
int isize = im__sizeof_bandfmt[fmt];
|
||||||
|
int osize = im__sizeof_bandfmt[format_table[fmt]];
|
||||||
|
|
||||||
|
char source[256];
|
||||||
|
VipsVector *v;
|
||||||
|
|
||||||
|
/* float and double are not handled (well) by ORC.
|
||||||
|
*/
|
||||||
|
if( fmt == IM_BANDFMT_DOUBLE ||
|
||||||
|
fmt == IM_BANDFMT_FLOAT ||
|
||||||
|
fmt == IM_BANDFMT_COMPLEX ||
|
||||||
|
fmt == IM_BANDFMT_DPCOMPLEX )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
v = vectors[fmt] =
|
||||||
|
vips_vector_new_ds( "binary arith", osize, isize );
|
||||||
|
vips_vector_source( v, source, 2, isize );
|
||||||
|
|
||||||
|
vips_vector_temporary( v, "t1", osize );
|
||||||
|
vips_vector_temporary( v, "t2", osize );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
im__compile_programs( VipsVector *vectors[IM_BANDFMT_LAST] )
|
||||||
|
{
|
||||||
|
int fmt;
|
||||||
|
|
||||||
|
for( fmt = 0; fmt < IM_BANDFMT_LAST; fmt++ ) {
|
||||||
|
if( vectors[fmt] &&
|
||||||
|
!vips_vector_compile( vectors[fmt] ) )
|
||||||
|
IM_FREEF( vips_vector_free, vectors[fmt] );
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "im__compile_programs: " );
|
||||||
|
for( fmt = 0; fmt < IM_BANDFMT_LAST; fmt++ )
|
||||||
|
if( vectors[fmt] )
|
||||||
|
printf( "%s ", im_BandFmt2char( fmt ) );
|
||||||
|
printf( "\n" );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
build_programs( void )
|
||||||
|
{
|
||||||
|
static gboolean done = FALSE;
|
||||||
|
|
||||||
|
VipsVector *v;
|
||||||
|
|
||||||
|
if( done )
|
||||||
|
return;
|
||||||
|
done = TRUE;
|
||||||
|
|
||||||
|
im__init_programs( add_vectors, bandfmt_add );
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_UCHAR];
|
||||||
|
vips_vector_asm2( v, "convubw", "t1", "s1" );
|
||||||
|
vips_vector_asm2( v, "convubw", "t2", "s2" );
|
||||||
|
vips_vector_asm3( v, "addw", "d1", "t1", "t2" );
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_CHAR];
|
||||||
|
vips_vector_asm2( v, "convsbw", "t1", "s1" );
|
||||||
|
vips_vector_asm2( v, "convsbw", "t2", "s2" );
|
||||||
|
vips_vector_asm3( v, "addw", "d1", "t1", "t2" );
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
only the 8-bit ones have a useful speedup, with orc-0.4.11
|
||||||
|
on a c2d anyway
|
||||||
|
|
||||||
|
test this again at some point I guess
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_USHORT];
|
||||||
|
vips_vector_asm2( v, "convuwl", "t1", "s1" );
|
||||||
|
vips_vector_asm2( v, "convuwl", "t2", "s2" );
|
||||||
|
vips_vector_asm3( v, "addl", "d1", "t1", "t2" );
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_SHORT];
|
||||||
|
vips_vector_asm2( v, "convswl", "t1", "s1" );
|
||||||
|
vips_vector_asm2( v, "convswl", "t2", "s2" );
|
||||||
|
vips_vector_asm3( v, "addl", "d1", "t1", "t2" );
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_UINT];
|
||||||
|
vips_vector_asm3( v, "addl", "d1", "s1", "s2" );
|
||||||
|
|
||||||
|
v = add_vectors[IM_BANDFMT_INT];
|
||||||
|
vips_vector_asm3( v, "addl", "d1", "s1", "s2" );
|
||||||
|
*/
|
||||||
|
|
||||||
|
im__compile_programs( add_vectors );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* im_add:
|
* im_add:
|
||||||
* @in1: input image
|
* @in1: input image
|
||||||
|
@ -387,6 +509,9 @@ static int bandfmt_add[10] = {
|
||||||
* In other words, the output type is just large enough to hold the whole
|
* In other words, the output type is just large enough to hold the whole
|
||||||
* range of possible values.
|
* range of possible values.
|
||||||
*
|
*
|
||||||
|
* Operations on 8-bit images are performed using the processor's vector unit,
|
||||||
|
* if possible. Disable this with --vips-novector or IM_NOVECTOR.
|
||||||
|
*
|
||||||
* See also: im_subtract(), im_lintra().
|
* See also: im_subtract(), im_lintra().
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, -1 on error
|
* Returns: 0 on success, -1 on error
|
||||||
|
@ -394,6 +519,9 @@ static int bandfmt_add[10] = {
|
||||||
int
|
int
|
||||||
im_add( IMAGE *in1, IMAGE *in2, IMAGE *out )
|
im_add( IMAGE *in1, IMAGE *in2, IMAGE *out )
|
||||||
{
|
{
|
||||||
|
if( vips_vector_get_enabled() )
|
||||||
|
build_programs();
|
||||||
|
|
||||||
return( im__arith_binary( "im_add",
|
return( im__arith_binary( "im_add",
|
||||||
in1, in2, out,
|
in1, in2, out,
|
||||||
bandfmt_add,
|
bandfmt_add,
|
||||||
|
|
|
@ -6,7 +6,6 @@ libconvolution_la_SOURCES = \
|
||||||
im_compass.c \
|
im_compass.c \
|
||||||
im_conv.c \
|
im_conv.c \
|
||||||
im_conv_f.c \
|
im_conv_f.c \
|
||||||
im_convsep.c \
|
|
||||||
im_convsep_f.c \
|
im_convsep_f.c \
|
||||||
im_contrast_surface.c \
|
im_contrast_surface.c \
|
||||||
im_fastcor.c \
|
im_fastcor.c \
|
||||||
|
|
|
@ -55,6 +55,12 @@
|
||||||
* - add a special case for 3x3 masks, about 20% faster
|
* - add a special case for 3x3 masks, about 20% faster
|
||||||
* 1/10/10
|
* 1/10/10
|
||||||
* - support complex (just double the bands)
|
* - support complex (just double the bands)
|
||||||
|
* 18/10/10
|
||||||
|
* - add experimental Orc path
|
||||||
|
* 29/10/10
|
||||||
|
* - use VipsVector
|
||||||
|
* - get rid of im_convsep(), just call this twice, no longer worth
|
||||||
|
* keeping two versions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -83,6 +89,43 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/* Show sample pixels as they are transformed.
|
||||||
|
#define DEBUG_PIXELS
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define DEBUG
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
- will this change make much difference to the vips benchmark?
|
||||||
|
|
||||||
|
- would setting params by index rather than name be any quicker?
|
||||||
|
|
||||||
|
- fix up a signed 8-bit code path?
|
||||||
|
|
||||||
|
- try a path with a 32-bit sum for larger matrices / scale / offset,
|
||||||
|
much slower?
|
||||||
|
|
||||||
|
- try a 16-bit path, though the speedup might not be worthwhile
|
||||||
|
|
||||||
|
- with a 5x5 matrix:
|
||||||
|
|
||||||
|
5 5 62 0
|
||||||
|
0 1 1 1 0
|
||||||
|
1 4 6 4 1
|
||||||
|
1 6 10 6 1
|
||||||
|
1 4 6 4 1
|
||||||
|
0 1 1 1 0
|
||||||
|
|
||||||
|
Orc is no faster than C, argh, multipass is not worthwhile for
|
||||||
|
large matrices
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
#include <config.h>
|
#include <config.h>
|
||||||
#endif /*HAVE_CONFIG_H*/
|
#endif /*HAVE_CONFIG_H*/
|
||||||
|
@ -93,6 +136,7 @@
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
|
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
#ifdef WITH_DMALLOC
|
||||||
#include <dmalloc.h>
|
#include <dmalloc.h>
|
||||||
|
@ -112,12 +156,26 @@ typedef struct {
|
||||||
|
|
||||||
int underflow; /* Global underflow/overflow counts */
|
int underflow; /* Global underflow/overflow counts */
|
||||||
int overflow;
|
int overflow;
|
||||||
|
|
||||||
|
/* The convolver we generate for this mask. We have to split the
|
||||||
|
* convolve and clip into two phases.
|
||||||
|
*/
|
||||||
|
VipsVector *convolve;
|
||||||
|
VipsVector *clip;
|
||||||
} Conv;
|
} Conv;
|
||||||
|
|
||||||
|
static void
|
||||||
|
conv_vector_free( Conv *conv )
|
||||||
|
{
|
||||||
|
IM_FREEF( vips_vector_free, conv->convolve );
|
||||||
|
IM_FREEF( vips_vector_free, conv->clip );
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
conv_close( Conv *conv )
|
conv_close( Conv *conv )
|
||||||
{
|
{
|
||||||
IM_FREEF( im_free_imask, conv->mask );
|
IM_FREEF( im_free_imask, conv->mask );
|
||||||
|
conv_vector_free( conv );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
@ -146,6 +204,197 @@ conv_evalend( Conv *conv )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define TEMP( N, S ) vips_vector_temporary( v, N, S )
|
||||||
|
#define SRC( N, P, S ) vips_vector_source( v, N, P, S )
|
||||||
|
#define CONST( N, V, S ) vips_vector_constant( v, N, V, S )
|
||||||
|
#define ASM2( OP, A, B ) vips_vector_asm2( v, OP, A, B )
|
||||||
|
#define ASM3( OP, A, B, C ) vips_vector_asm3( v, OP, A, B, C )
|
||||||
|
|
||||||
|
/* Generate code for a 3x3 mask. Just do multiply-add, a second pass does the
|
||||||
|
* round and clip.
|
||||||
|
*
|
||||||
|
* 0 for success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
conv_compile_convolution_u8s16( Conv *conv )
|
||||||
|
{
|
||||||
|
INTMASK *mask = conv->mask;
|
||||||
|
|
||||||
|
double min, max;
|
||||||
|
int i;
|
||||||
|
VipsVector *v;
|
||||||
|
char zero[256];
|
||||||
|
char offset[256];
|
||||||
|
char source[256];
|
||||||
|
char coeff[256];
|
||||||
|
|
||||||
|
if( conv->in->BandFmt != IM_BANDFMT_UCHAR )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
/* Don't test mask size, it's very hard to predict when we will
|
||||||
|
* exhaust the program space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Can the accumulator overflow or underflow at any stage? Since
|
||||||
|
* matrix elements are signed, we need to calculate a running
|
||||||
|
* possible min and max.
|
||||||
|
*/
|
||||||
|
min = 0;
|
||||||
|
max = 0;
|
||||||
|
for( i = 0; i < mask->xsize * mask->ysize; i++ ) {
|
||||||
|
int v = 255 * mask->coeff[i];
|
||||||
|
|
||||||
|
if( min + v < min )
|
||||||
|
min += v;
|
||||||
|
else if( min + v > max )
|
||||||
|
max += v;
|
||||||
|
|
||||||
|
if( max > SHRT_MAX )
|
||||||
|
return( -1 );
|
||||||
|
if( min < SHRT_MIN )
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start with a single source scanline, we add more as we need them.
|
||||||
|
*/
|
||||||
|
conv->convolve = v = vips_vector_new_ds( "conv", 2, 1 );
|
||||||
|
|
||||||
|
/* The value we fetch from the image, the product with the matrix
|
||||||
|
* value, the accumulated sum.
|
||||||
|
*/
|
||||||
|
TEMP( "value", 1 );
|
||||||
|
TEMP( "product", 2 );
|
||||||
|
TEMP( "sum", 2 );
|
||||||
|
|
||||||
|
CONST( zero, 0, 2 );
|
||||||
|
ASM2( "copyw", "sum", zero );
|
||||||
|
|
||||||
|
for( i = 0; i < mask->xsize * mask->ysize; i++ ) {
|
||||||
|
int x = i % mask->xsize;
|
||||||
|
int y = i / mask->xsize;
|
||||||
|
|
||||||
|
if( !mask->coeff[i] )
|
||||||
|
/* Exclude zero elements.
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* The source. s1 is the first scanline in the mask.
|
||||||
|
*/
|
||||||
|
SRC( source, y + 1, 1 );
|
||||||
|
|
||||||
|
/* The offset, only for non-first-columns though.
|
||||||
|
*/
|
||||||
|
if( x > 0 )
|
||||||
|
CONST( offset, conv->in->Bands * x, 1 );
|
||||||
|
|
||||||
|
/* The coefficient. Only for non-1 coeffs though, we skip the
|
||||||
|
* mul for them.
|
||||||
|
*
|
||||||
|
* We need to do 8-bit unsigned pixel * signed mask, so we
|
||||||
|
* have to cast the pixel up to 16-bit then do a mult against a
|
||||||
|
* 16-bit constant. We know the result will fit in the botom
|
||||||
|
* 16 bits.
|
||||||
|
*/
|
||||||
|
if( mask->coeff[i] != 1 )
|
||||||
|
CONST( coeff, mask->coeff[i], 2 );
|
||||||
|
|
||||||
|
/* Two factors:
|
||||||
|
* - element is in the first column, ie. has a zero offset
|
||||||
|
* - mask coeff is 1, ie. we can skip the multiply
|
||||||
|
*
|
||||||
|
* We could combine some of these cases, but it's simpler
|
||||||
|
* and safer to spell them all out.
|
||||||
|
*/
|
||||||
|
if( x == 0 )
|
||||||
|
ASM2( "loadb", "value", source );
|
||||||
|
else
|
||||||
|
ASM3( "loadoffb", "value", source, offset );
|
||||||
|
|
||||||
|
ASM2( "convubw", "product", "value" );
|
||||||
|
|
||||||
|
if( mask->coeff[i] != 1 )
|
||||||
|
ASM3( "mullw", "product", "product", coeff );
|
||||||
|
|
||||||
|
ASM3( "addssw", "sum", "sum", "product" );
|
||||||
|
|
||||||
|
/* If we run out of space, fall back to C.
|
||||||
|
*/
|
||||||
|
if( vips_vector_full( v ) )
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
ASM2( "copyw", "d1", "sum" );
|
||||||
|
|
||||||
|
if( !vips_vector_compile( v ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
vips_vector_print( v );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate the program that does (sum + rounding) / scale + offset
|
||||||
|
* from a s16 intermediate back to a u8 output.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
conv_compile_scale_s16u8( Conv *conv )
|
||||||
|
{
|
||||||
|
INTMASK *mask = conv->mask;
|
||||||
|
|
||||||
|
VipsVector *v;
|
||||||
|
char scale[256];
|
||||||
|
char offset[256];
|
||||||
|
char zero[256];
|
||||||
|
|
||||||
|
/* Scale and offset must be in range.
|
||||||
|
*/
|
||||||
|
if( mask->scale > 255 ||
|
||||||
|
mask->scale < 0 ||
|
||||||
|
mask->offset > SHRT_MAX ||
|
||||||
|
mask->offset < SHRT_MIN )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
conv->clip = v = vips_vector_new_ds( "clip", 1, 2 );
|
||||||
|
|
||||||
|
TEMP( "t1", 2 );
|
||||||
|
TEMP( "t2", 2 );
|
||||||
|
|
||||||
|
/* We can only do unsigned divide, so we must add the offset before
|
||||||
|
* dividing by the scale. We need to scale the offset up.
|
||||||
|
*
|
||||||
|
* We can build the rounding into the offset as well.
|
||||||
|
* You might think this should be (scale + 1) / 2, but then we'd be
|
||||||
|
* adding one for scale == 1.
|
||||||
|
*/
|
||||||
|
CONST( scale, mask->scale, 1 );
|
||||||
|
CONST( offset, mask->offset * mask->scale + mask->scale / 2, 2 );
|
||||||
|
CONST( zero, 0, 2 );
|
||||||
|
|
||||||
|
/* Offset and scale.
|
||||||
|
*/
|
||||||
|
ASM3( "addssw", "t1", "s1", offset );
|
||||||
|
|
||||||
|
/* We need to convert the signed result of the
|
||||||
|
* offset to unsigned for the div, ie. we want to set anything <0 to 0.
|
||||||
|
*/
|
||||||
|
ASM3( "cmpgtsw", "t2", "t1", zero );
|
||||||
|
ASM3( "andw", "t1", "t1", "t2" );
|
||||||
|
|
||||||
|
ASM3( "divluw", "t1", "t1", scale );
|
||||||
|
ASM2( "convuuswb", "d1", "t1" );
|
||||||
|
|
||||||
|
if( !vips_vector_compile( v ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
vips_vector_print( v );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
static Conv *
|
static Conv *
|
||||||
conv_new( IMAGE *in, IMAGE *out, INTMASK *mask )
|
conv_new( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
{
|
{
|
||||||
|
@ -165,6 +414,9 @@ conv_new( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
conv->underflow = 0;
|
conv->underflow = 0;
|
||||||
conv->overflow = 0;
|
conv->overflow = 0;
|
||||||
|
|
||||||
|
conv->convolve = NULL;
|
||||||
|
conv->clip = NULL;
|
||||||
|
|
||||||
if( im_add_close_callback( out,
|
if( im_add_close_callback( out,
|
||||||
(im_callback_fn) conv_close, conv, NULL ) ||
|
(im_callback_fn) conv_close, conv, NULL ) ||
|
||||||
im_add_close_callback( out,
|
im_add_close_callback( out,
|
||||||
|
@ -194,6 +446,14 @@ conv_new( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
conv->nnz = 1;
|
conv->nnz = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Generate code for this mask / image, if possible.
|
||||||
|
*/
|
||||||
|
if( vips_vector_get_enabled() ) {
|
||||||
|
if( conv_compile_convolution_u8s16( conv ) ||
|
||||||
|
conv_compile_scale_s16u8( conv ) )
|
||||||
|
conv_vector_free( conv );
|
||||||
|
}
|
||||||
|
|
||||||
return( conv );
|
return( conv );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,6 +470,11 @@ typedef struct {
|
||||||
int overflow;
|
int overflow;
|
||||||
|
|
||||||
int last_bpl; /* Avoid recalcing offsets, if we can */
|
int last_bpl; /* Avoid recalcing offsets, if we can */
|
||||||
|
|
||||||
|
/* We need an intermediate buffer to keep the result of the conv in
|
||||||
|
* before we clip it.
|
||||||
|
*/
|
||||||
|
void *sum;
|
||||||
} ConvSequence;
|
} ConvSequence;
|
||||||
|
|
||||||
/* Free a sequence value.
|
/* Free a sequence value.
|
||||||
|
@ -227,6 +492,8 @@ conv_stop( void *vseq, void *a, void *b )
|
||||||
|
|
||||||
IM_FREEF( im_region_free, seq->ir );
|
IM_FREEF( im_region_free, seq->ir );
|
||||||
|
|
||||||
|
IM_FREE( seq->sum );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,13 +517,15 @@ conv_start( IMAGE *out, void *a, void *b )
|
||||||
seq->underflow = 0;
|
seq->underflow = 0;
|
||||||
seq->overflow = 0;
|
seq->overflow = 0;
|
||||||
seq->last_bpl = -1;
|
seq->last_bpl = -1;
|
||||||
|
seq->sum = NULL;
|
||||||
|
|
||||||
/* Attach region and arrays.
|
/* Attach region and arrays.
|
||||||
*/
|
*/
|
||||||
seq->ir = im_region_create( in );
|
seq->ir = im_region_create( in );
|
||||||
seq->offsets = IM_ARRAY( out, conv->nnz, int );
|
seq->offsets = IM_ARRAY( out, conv->nnz, int );
|
||||||
seq->pts = IM_ARRAY( out, conv->nnz, PEL * );
|
seq->pts = IM_ARRAY( out, conv->nnz, PEL * );
|
||||||
if( !seq->ir || !seq->offsets || !seq->pts ) {
|
seq->sum = IM_ARRAY( NULL, IM_IMAGE_N_ELEMENTS( in ), short );
|
||||||
|
if( !seq->ir || !seq->offsets || !seq->pts || !seq->sum ) {
|
||||||
conv_stop( seq, in, conv );
|
conv_stop( seq, in, conv );
|
||||||
return( NULL );
|
return( NULL );
|
||||||
}
|
}
|
||||||
|
@ -333,8 +602,7 @@ conv_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
int le = r->left;
|
int le = r->left;
|
||||||
int to = r->top;
|
int to = r->top;
|
||||||
int bo = IM_RECT_BOTTOM( r );
|
int bo = IM_RECT_BOTTOM( r );
|
||||||
int sz = IM_REGION_N_ELEMENTS( or ) *
|
int sz = IM_REGION_N_ELEMENTS( or ) * (im_iscomplex( in ) ? 2 : 1);
|
||||||
(vips_bandfmt_iscomplex( in->BandFmt ) ? 2 : 1);
|
|
||||||
|
|
||||||
int x, y, z, i;
|
int x, y, z, i;
|
||||||
|
|
||||||
|
@ -428,13 +696,13 @@ conv_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
sum = 0; \
|
sum = 0; \
|
||||||
sum += m[0] * p0[0]; \
|
sum += m[0] * p0[0]; \
|
||||||
sum += m[1] * p0[bands]; \
|
sum += m[1] * p0[bands]; \
|
||||||
sum += m[2] * p0[bands << 1]; \
|
sum += m[2] * p0[bands * 2]; \
|
||||||
sum += m[3] * p1[0]; \
|
sum += m[3] * p1[0]; \
|
||||||
sum += m[4] * p1[bands]; \
|
sum += m[4] * p1[bands]; \
|
||||||
sum += m[5] * p1[bands << 1]; \
|
sum += m[5] * p1[bands * 2]; \
|
||||||
sum += m[6] * p2[0]; \
|
sum += m[6] * p2[0]; \
|
||||||
sum += m[7] * p2[bands]; \
|
sum += m[7] * p2[bands]; \
|
||||||
sum += m[8] * p2[bands << 1]; \
|
sum += m[8] * p2[bands * 2]; \
|
||||||
\
|
\
|
||||||
p0 += 1; \
|
p0 += 1; \
|
||||||
p1 += 1; \
|
p1 += 1; \
|
||||||
|
@ -462,13 +730,13 @@ conv_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
sum = 0; \
|
sum = 0; \
|
||||||
sum += m[0] * p0[0]; \
|
sum += m[0] * p0[0]; \
|
||||||
sum += m[1] * p0[bands]; \
|
sum += m[1] * p0[bands]; \
|
||||||
sum += m[2] * p0[bands << 1]; \
|
sum += m[2] * p0[bands * 2]; \
|
||||||
sum += m[3] * p1[0]; \
|
sum += m[3] * p1[0]; \
|
||||||
sum += m[4] * p1[bands]; \
|
sum += m[4] * p1[bands]; \
|
||||||
sum += m[5] * p1[bands << 1]; \
|
sum += m[5] * p1[bands * 2]; \
|
||||||
sum += m[6] * p2[0]; \
|
sum += m[6] * p2[0]; \
|
||||||
sum += m[7] * p2[bands]; \
|
sum += m[7] * p2[bands]; \
|
||||||
sum += m[8] * p2[bands << 1]; \
|
sum += m[8] * p2[bands * 2]; \
|
||||||
\
|
\
|
||||||
p0 += 1; \
|
p0 += 1; \
|
||||||
p1 += 1; \
|
p1 += 1; \
|
||||||
|
@ -502,8 +770,7 @@ conv3x3_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
int le = r->left;
|
int le = r->left;
|
||||||
int to = r->top;
|
int to = r->top;
|
||||||
int bo = IM_RECT_BOTTOM( r );
|
int bo = IM_RECT_BOTTOM( r );
|
||||||
int sz = IM_REGION_N_ELEMENTS( or ) *
|
int sz = IM_REGION_N_ELEMENTS( or ) * (im_iscomplex( in ) ? 2 : 1);
|
||||||
(vips_bandfmt_iscomplex( in->BandFmt ) ? 2 : 1);
|
|
||||||
int bands = in->Bands;
|
int bands = in->Bands;
|
||||||
|
|
||||||
Rect s;
|
Rect s;
|
||||||
|
@ -568,6 +835,79 @@ conv3x3_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The VipsVector codepath.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
convvec_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
ConvSequence *seq = (ConvSequence *) vseq;
|
||||||
|
IMAGE *in = (IMAGE *) a;
|
||||||
|
Conv *conv = (Conv *) b;
|
||||||
|
INTMASK *mask = conv->mask;
|
||||||
|
REGION *ir = seq->ir;
|
||||||
|
|
||||||
|
Rect *r = &or->valid;
|
||||||
|
int sz = IM_REGION_N_ELEMENTS( or ) * (im_iscomplex( in ) ? 2 : 1);
|
||||||
|
|
||||||
|
Rect s;
|
||||||
|
int y, j;
|
||||||
|
VipsExecutor convolve;
|
||||||
|
VipsExecutor clip;
|
||||||
|
|
||||||
|
/* Prepare the section of the input image we need. A little larger
|
||||||
|
* than the section of the output image we are producing.
|
||||||
|
*/
|
||||||
|
s = *r;
|
||||||
|
s.width += mask->xsize - 1;
|
||||||
|
s.height += mask->ysize - 1;
|
||||||
|
if( im_prepare( ir, &s ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
vips_executor_set_program( &convolve, conv->convolve, sz );
|
||||||
|
vips_executor_set_program( &clip, conv->clip, sz );
|
||||||
|
|
||||||
|
/* Link the combiner to the intermediate buffer.
|
||||||
|
*/
|
||||||
|
vips_executor_set_array( &convolve, "d1", seq->sum );
|
||||||
|
vips_executor_set_array( &clip, "s1", seq->sum );
|
||||||
|
|
||||||
|
for( y = 0; y < r->height; y++ ) {
|
||||||
|
#ifdef DEBUG_PIXELS
|
||||||
|
{
|
||||||
|
int h, v;
|
||||||
|
|
||||||
|
printf( "before convolve: %d, %d\n", r->left, r->top + y );
|
||||||
|
for( v = 0; v < mask->ysize; v++ ) {
|
||||||
|
for( h = 0; h < mask->xsize; h++ )
|
||||||
|
printf( "%3d ", *((PEL *) IM_REGION_ADDR( ir,
|
||||||
|
r->left + h, r->top + y + v )) );
|
||||||
|
printf( "\n" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif /*DEBUG_PIXELS*/
|
||||||
|
|
||||||
|
for( j = 0; j < mask->ysize; j++ )
|
||||||
|
vips_executor_set_source( &convolve, j + 1,
|
||||||
|
IM_REGION_ADDR( ir, r->left, r->top + y + j ) );
|
||||||
|
vips_executor_run( &convolve );
|
||||||
|
|
||||||
|
#ifdef DEBUG_PIXELS
|
||||||
|
printf( "before clip: %3d\n", *((signed short *) seq->sum) );
|
||||||
|
#endif /*DEBUG_PIXELS*/
|
||||||
|
|
||||||
|
vips_executor_set_array( &clip, "d1",
|
||||||
|
IM_REGION_ADDR( or, r->left, r->top + y ) );
|
||||||
|
vips_executor_run( &clip );
|
||||||
|
|
||||||
|
#ifdef DEBUG_PIXELS
|
||||||
|
printf( "after clip: %d\n",
|
||||||
|
*((PEL *) IM_REGION_ADDR( or, r->left, r->top + y )) );
|
||||||
|
#endif /*DEBUG_PIXELS*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
im_conv_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
im_conv_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
{
|
{
|
||||||
|
@ -599,7 +939,14 @@ im_conv_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mask->xsize == 3 && mask->ysize == 3 )
|
if( conv->convolve ) {
|
||||||
|
generate = convvec_gen;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "im_conv_raw: using vector path\n" );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
}
|
||||||
|
else if( mask->xsize == 3 && mask->ysize == 3 )
|
||||||
generate = conv3x3_gen;
|
generate = conv3x3_gen;
|
||||||
else
|
else
|
||||||
generate = conv_gen;
|
generate = conv_gen;
|
||||||
|
@ -631,6 +978,10 @@ im_conv_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
* and offset are part of @mask. For integer @in, the division by scale
|
* and offset are part of @mask. For integer @in, the division by scale
|
||||||
* includes round-to-nearest.
|
* includes round-to-nearest.
|
||||||
*
|
*
|
||||||
|
* Small convolutions on unsigned 8-bit images are performed using the
|
||||||
|
* processor's vector unit,
|
||||||
|
* if possible. Disable this with --vips-novector or IM_NOVECTOR.
|
||||||
|
*
|
||||||
* See also: im_conv_f(), im_convsep(), im_create_imaskv().
|
* See also: im_conv_f(), im_convsep(), im_create_imaskv().
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, -1 on error
|
* Returns: 0 on success, -1 on error
|
||||||
|
@ -652,3 +1003,75 @@ im_conv( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
im_convsep_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
IMAGE *t;
|
||||||
|
INTMASK *rmask;
|
||||||
|
|
||||||
|
if( mask->xsize != 1 && mask->ysize != 1 ) {
|
||||||
|
im_error( "im_convsep",
|
||||||
|
"%s", _( "expect 1xN or Nx1 input mask" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !(t = im_open_local( out, "im_convsep", "p" )) ||
|
||||||
|
!(rmask = (INTMASK *) im_local( out,
|
||||||
|
(im_construct_fn) im_dup_imask,
|
||||||
|
(im_callback_fn) im_free_imask, mask, mask->filename, NULL )) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
rmask->xsize = mask->ysize;
|
||||||
|
rmask->ysize = mask->xsize;
|
||||||
|
|
||||||
|
if( im_conv_raw( in, t, mask ) ||
|
||||||
|
im_conv_raw( t, out, rmask ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im_convsep:
|
||||||
|
* @in: input image
|
||||||
|
* @out: output image
|
||||||
|
* @mask: convolution mask
|
||||||
|
*
|
||||||
|
* Perform a separable convolution of @in with @mask using integer arithmetic.
|
||||||
|
*
|
||||||
|
* The mask must be 1xn or nx1 elements.
|
||||||
|
* The output image
|
||||||
|
* always has the same #VipsBandFmt as the input image.
|
||||||
|
*
|
||||||
|
* The image is convolved twice: once with @mask and then again with @mask
|
||||||
|
* rotated by 90 degrees. This is much faster for certain types of mask
|
||||||
|
* (gaussian blur, for example) than doing a full 2D convolution.
|
||||||
|
*
|
||||||
|
* Each output pixel is
|
||||||
|
* calculated as sigma[i]{pixel[i] * mask[i]} / scale + offset, where scale
|
||||||
|
* and offset are part of @mask. For integer @in, the division by scale
|
||||||
|
* includes round-to-nearest.
|
||||||
|
*
|
||||||
|
* See also: im_convsep_f(), im_conv(), im_create_imaskv().
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
im_convsep( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
IMAGE *t1 = im_open_local( out, "im_convsep intermediate", "p" );
|
||||||
|
int size = mask->xsize * mask->ysize;
|
||||||
|
|
||||||
|
if( !t1 ||
|
||||||
|
im_embed( in, t1, 1, size / 2, size / 2,
|
||||||
|
in->Xsize + size - 1,
|
||||||
|
in->Ysize + size - 1 ) ||
|
||||||
|
im_convsep_raw( t1, out, mask ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
out->Xoffset = 0;
|
||||||
|
out->Yoffset = 0;
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
|
@ -1,461 +0,0 @@
|
||||||
/* im_convsep
|
|
||||||
*
|
|
||||||
* Copyright: 1990, N. Dessipris.
|
|
||||||
*
|
|
||||||
* Author: Nicos Dessipris
|
|
||||||
* Written on: 29/04/1991
|
|
||||||
* Modified on: 29/4/93 K.Martinez for Sys5
|
|
||||||
* 9/3/01 JC
|
|
||||||
* - rewritten using im_conv()
|
|
||||||
* 27/7/01 JC
|
|
||||||
* - rejects masks with scale == 0
|
|
||||||
* 7/4/04
|
|
||||||
* - now uses im_embed() with edge stretching on the input, not
|
|
||||||
* the output
|
|
||||||
* - sets Xoffset / Yoffset
|
|
||||||
* 21/4/04
|
|
||||||
* - scale down int convolves at 1/2 way mark, much less likely to integer
|
|
||||||
* overflow on intermediates
|
|
||||||
* 12/5/08
|
|
||||||
* - int rounding was +1 too much, argh
|
|
||||||
* 3/2/10
|
|
||||||
* - gtkdoc
|
|
||||||
* - more cleanups
|
|
||||||
* 1/10/10
|
|
||||||
* - support complex (just double the bands)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
This file is part of VIPS.
|
|
||||||
|
|
||||||
VIPS is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include <config.h>
|
|
||||||
#endif /*HAVE_CONFIG_H*/
|
|
||||||
#include <vips/intl.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
#include <vips/vips.h>
|
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
|
||||||
#include <dmalloc.h>
|
|
||||||
#endif /*WITH_DMALLOC*/
|
|
||||||
|
|
||||||
/* Our parameters ... we take a copy of the mask argument.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
IMAGE *in;
|
|
||||||
IMAGE *out;
|
|
||||||
INTMASK *mask; /* Copy of mask arg */
|
|
||||||
|
|
||||||
int size; /* N for our 1xN or Nx1 mask */
|
|
||||||
int scale; /* Our scale ... we have to square mask->scale */
|
|
||||||
|
|
||||||
int underflow; /* Global underflow/overflow counts */
|
|
||||||
int overflow;
|
|
||||||
} Conv;
|
|
||||||
|
|
||||||
/* End of evaluation --- print overflows and underflows.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
conv_destroy( Conv *conv )
|
|
||||||
{
|
|
||||||
/* Print underflow/overflow count.
|
|
||||||
*/
|
|
||||||
if( conv->overflow || conv->underflow )
|
|
||||||
im_warn( "im_convsep", _( "%d overflows and %d underflows "
|
|
||||||
"detected" ), conv->overflow, conv->underflow );
|
|
||||||
|
|
||||||
if( conv->mask ) {
|
|
||||||
(void) im_free_imask( conv->mask );
|
|
||||||
conv->mask = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
static Conv *
|
|
||||||
conv_new( IMAGE *in, IMAGE *out, INTMASK *mask )
|
|
||||||
{
|
|
||||||
Conv *conv = IM_NEW( out, Conv );
|
|
||||||
|
|
||||||
if( !conv )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
conv->in = in;
|
|
||||||
conv->out = out;
|
|
||||||
conv->mask = NULL;
|
|
||||||
conv->size = mask->xsize * mask->ysize;
|
|
||||||
conv->scale = mask->scale * mask->scale;
|
|
||||||
conv->underflow = 0;
|
|
||||||
conv->overflow = 0;
|
|
||||||
|
|
||||||
if( im_add_close_callback( out,
|
|
||||||
(im_callback_fn) conv_destroy, conv, NULL ) ||
|
|
||||||
!(conv->mask = im_dup_imask( mask, "conv_mask" )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
return( conv );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Our sequence value.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
Conv *conv;
|
|
||||||
REGION *ir; /* Input region */
|
|
||||||
|
|
||||||
PEL *sum; /* Line buffer */
|
|
||||||
|
|
||||||
int underflow; /* Underflow/overflow counts */
|
|
||||||
int overflow;
|
|
||||||
} ConvSequence;
|
|
||||||
|
|
||||||
/* Free a sequence value.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
conv_stop( void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
ConvSequence *seq = (ConvSequence *) vseq;
|
|
||||||
Conv *conv = (Conv *) b;
|
|
||||||
|
|
||||||
/* Add local under/over counts to global counts.
|
|
||||||
*/
|
|
||||||
conv->overflow += seq->overflow;
|
|
||||||
conv->underflow += seq->underflow;
|
|
||||||
|
|
||||||
IM_FREEF( im_region_free, seq->ir );
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convolution start function.
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
conv_start( IMAGE *out, void *a, void *b )
|
|
||||||
{
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
Conv *conv = (Conv *) b;
|
|
||||||
ConvSequence *seq;
|
|
||||||
|
|
||||||
if( !(seq = IM_NEW( out, ConvSequence )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
/* Init!
|
|
||||||
*/
|
|
||||||
seq->conv = conv;
|
|
||||||
seq->ir = NULL;
|
|
||||||
seq->sum = NULL;
|
|
||||||
seq->underflow = 0;
|
|
||||||
seq->overflow = 0;
|
|
||||||
|
|
||||||
/* Attach region and arrays.
|
|
||||||
*/
|
|
||||||
seq->ir = im_region_create( in );
|
|
||||||
if( vips_bandfmt_isint( conv->out->BandFmt ) )
|
|
||||||
seq->sum = (PEL *)
|
|
||||||
IM_ARRAY( out, IM_IMAGE_N_ELEMENTS( in ), int );
|
|
||||||
else
|
|
||||||
seq->sum = (PEL *)
|
|
||||||
IM_ARRAY( out, IM_IMAGE_N_ELEMENTS( in ), double );
|
|
||||||
if( !seq->ir || !seq->sum ) {
|
|
||||||
conv_stop( seq, in, conv );
|
|
||||||
return( NULL );
|
|
||||||
}
|
|
||||||
|
|
||||||
return( (void *) seq );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* What we do for every point in the mask, for each pixel.
|
|
||||||
*/
|
|
||||||
#define VERTICAL_CONV { z -= 1; li -= lskip; sum += coeff[z] * vfrom[li]; }
|
|
||||||
#define HORIZONTAL_CONV { z -= 1; li -= bands; sum += coeff[z] * hfrom[li]; }
|
|
||||||
|
|
||||||
/* INT and FLOAT inner loops.
|
|
||||||
*/
|
|
||||||
#define CONV_INT( TYPE, IM_CLIP ) { \
|
|
||||||
TYPE *vfrom; \
|
|
||||||
int *vto; \
|
|
||||||
int *hfrom; \
|
|
||||||
TYPE *hto; \
|
|
||||||
\
|
|
||||||
/* Convolve to sum array. We convolve the full width of \
|
|
||||||
* this input line. \
|
|
||||||
*/ \
|
|
||||||
vfrom = (TYPE *) IM_REGION_ADDR( ir, le, y ); \
|
|
||||||
vto = (int *) seq->sum; \
|
|
||||||
for( x = 0; x < isz; x++ ) { \
|
|
||||||
int sum; \
|
|
||||||
\
|
|
||||||
z = conv->size; \
|
|
||||||
li = lskip * z; \
|
|
||||||
sum = 0; \
|
|
||||||
\
|
|
||||||
IM_UNROLL( z, VERTICAL_CONV ); \
|
|
||||||
\
|
|
||||||
sum = ((sum + rounding) / mask->scale) + mask->offset; \
|
|
||||||
\
|
|
||||||
vto[x] = sum; \
|
|
||||||
vfrom += 1; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
/* Convolve sums to output. \
|
|
||||||
*/ \
|
|
||||||
hfrom = (int *) seq->sum; \
|
|
||||||
hto = (TYPE *) IM_REGION_ADDR( or, le, y ); \
|
|
||||||
for( x = 0; x < osz; x++ ) { \
|
|
||||||
int sum; \
|
|
||||||
\
|
|
||||||
z = conv->size; \
|
|
||||||
li = bands * z; \
|
|
||||||
sum = 0; \
|
|
||||||
\
|
|
||||||
IM_UNROLL( z, HORIZONTAL_CONV ); \
|
|
||||||
\
|
|
||||||
sum = ((sum + rounding) / mask->scale) + mask->offset; \
|
|
||||||
\
|
|
||||||
IM_CLIP; \
|
|
||||||
\
|
|
||||||
hto[x] = sum; \
|
|
||||||
hfrom += 1; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CONV_FLOAT( TYPE ) { \
|
|
||||||
TYPE *vfrom; \
|
|
||||||
double *vto; \
|
|
||||||
double *hfrom; \
|
|
||||||
TYPE *hto; \
|
|
||||||
\
|
|
||||||
/* Convolve to sum array. We convolve the full width of \
|
|
||||||
* this input line. \
|
|
||||||
*/ \
|
|
||||||
vfrom = (TYPE *) IM_REGION_ADDR( ir, le, y ); \
|
|
||||||
vto = (double *) seq->sum; \
|
|
||||||
for( x = 0; x < isz; x++ ) { \
|
|
||||||
double sum; \
|
|
||||||
\
|
|
||||||
z = conv->size; \
|
|
||||||
li = lskip * z; \
|
|
||||||
sum = 0; \
|
|
||||||
\
|
|
||||||
IM_UNROLL( z, VERTICAL_CONV ); \
|
|
||||||
\
|
|
||||||
vto[x] = sum; \
|
|
||||||
vfrom += 1; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
/* Convolve sums to output. \
|
|
||||||
*/ \
|
|
||||||
hfrom = (double *) seq->sum; \
|
|
||||||
hto = (TYPE *) IM_REGION_ADDR( or, le, y ); \
|
|
||||||
for( x = 0; x < osz; x++ ) { \
|
|
||||||
double sum; \
|
|
||||||
\
|
|
||||||
z = conv->size; \
|
|
||||||
li = bands * z; \
|
|
||||||
sum = 0; \
|
|
||||||
\
|
|
||||||
IM_UNROLL( z, HORIZONTAL_CONV ); \
|
|
||||||
\
|
|
||||||
sum = (sum / conv->scale) + mask->offset; \
|
|
||||||
\
|
|
||||||
hto[x] = sum; \
|
|
||||||
hfrom += 1; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Convolve!
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
conv_gen( REGION *or, void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
ConvSequence *seq = (ConvSequence *) vseq;
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
Conv *conv = (Conv *) b;
|
|
||||||
REGION *ir = seq->ir;
|
|
||||||
INTMASK *mask = conv->mask;
|
|
||||||
|
|
||||||
/* You might think this should be (scale+1)/2, but then we'd be adding
|
|
||||||
* one for scale == 1.
|
|
||||||
*/
|
|
||||||
int rounding = mask->scale / 2;
|
|
||||||
|
|
||||||
int bands = in->Bands;
|
|
||||||
int *coeff = conv->mask->coeff;
|
|
||||||
|
|
||||||
Rect *r = &or->valid;
|
|
||||||
int le = r->left;
|
|
||||||
int to = r->top;
|
|
||||||
int bo = IM_RECT_BOTTOM(r);
|
|
||||||
int osz = IM_REGION_N_ELEMENTS( or ) *
|
|
||||||
(vips_bandfmt_iscomplex( in->BandFmt ) ? 2 : 1);
|
|
||||||
|
|
||||||
Rect s;
|
|
||||||
int lskip;
|
|
||||||
int isz;
|
|
||||||
int x, y, z, li;
|
|
||||||
|
|
||||||
/* Prepare the section of the input image we need. A little larger
|
|
||||||
* than the section of the output image we are producing.
|
|
||||||
*/
|
|
||||||
s = *r;
|
|
||||||
s.width += conv->size - 1;
|
|
||||||
s.height += conv->size - 1;
|
|
||||||
if( im_prepare( ir, &s ) )
|
|
||||||
return( -1 );
|
|
||||||
lskip = IM_REGION_LSKIP( ir ) / IM_IMAGE_SIZEOF_ELEMENT( in );
|
|
||||||
isz = IM_REGION_N_ELEMENTS( ir );
|
|
||||||
|
|
||||||
for( y = to; y < bo; y++ ) {
|
|
||||||
switch( in->BandFmt ) {
|
|
||||||
case IM_BANDFMT_UCHAR:
|
|
||||||
CONV_INT( unsigned char, IM_CLIP_UCHAR( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_CHAR:
|
|
||||||
CONV_INT( signed char, IM_CLIP_CHAR( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_USHORT:
|
|
||||||
CONV_INT( unsigned short, IM_CLIP_USHORT( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_SHORT:
|
|
||||||
CONV_INT( signed short, IM_CLIP_SHORT( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_UINT:
|
|
||||||
CONV_INT( unsigned int, IM_CLIP_NONE( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_INT:
|
|
||||||
CONV_INT( signed int, IM_CLIP_NONE( sum, seq ) );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_FLOAT:
|
|
||||||
case IM_BANDFMT_COMPLEX:
|
|
||||||
CONV_FLOAT( float );
|
|
||||||
break;
|
|
||||||
case IM_BANDFMT_DOUBLE:
|
|
||||||
case IM_BANDFMT_DPCOMPLEX:
|
|
||||||
CONV_FLOAT( double );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
g_assert( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
im_convsep_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
|
||||||
{
|
|
||||||
Conv *conv;
|
|
||||||
|
|
||||||
/* Check parameters.
|
|
||||||
*/
|
|
||||||
if( im_piocheck( in, out ) ||
|
|
||||||
im_check_uncoded( "im_convsep", in ) ||
|
|
||||||
im_check_imask( "im_convsep", mask ) )
|
|
||||||
return( -1 );
|
|
||||||
if( mask->xsize != 1 && mask->ysize != 1 ) {
|
|
||||||
im_error( "im_convsep",
|
|
||||||
"%s", _( "expect 1xN or Nx1 input mask" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
if( mask->scale == 0 ) {
|
|
||||||
im_error( "im_convsep", "%s", "mask scale must be non-zero" );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
if( !(conv = conv_new( in, out, mask )) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output
|
|
||||||
* would be 1x1.
|
|
||||||
*/
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
out->Xsize -= conv->size - 1;
|
|
||||||
out->Ysize -= conv->size - 1;
|
|
||||||
if( out->Xsize <= 0 || out->Ysize <= 0 ) {
|
|
||||||
im_error( "im_convsep", "%s", _( "image too small for mask" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SMALLTILE seems the fastest in benchmarks.
|
|
||||||
*/
|
|
||||||
if( im_demand_hint( out, IM_SMALLTILE, in, NULL ) ||
|
|
||||||
im_generate( out, conv_start, conv_gen, conv_stop, in, conv ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = -mask->xsize / 2;
|
|
||||||
out->Yoffset = -mask->ysize / 2;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* im_convsep:
|
|
||||||
* @in: input image
|
|
||||||
* @out: output image
|
|
||||||
* @mask: convolution mask
|
|
||||||
*
|
|
||||||
* Perform a separable convolution of @in with @mask using integer arithmetic.
|
|
||||||
*
|
|
||||||
* The mask must be 1xn or nx1 elements.
|
|
||||||
* The output image
|
|
||||||
* always has the same #VipsBandFmt as the input image.
|
|
||||||
*
|
|
||||||
* The image is convolved twice: once with @mask and then again with @mask
|
|
||||||
* rotated by 90 degrees. This is much faster for certain types of mask
|
|
||||||
* (gaussian blur, for example) than doing a full 2D convolution.
|
|
||||||
*
|
|
||||||
* Each output pixel is
|
|
||||||
* calculated as sigma[i]{pixel[i] * mask[i]} / scale + offset, where scale
|
|
||||||
* and offset are part of @mask. For integer @in, the division by scale
|
|
||||||
* includes round-to-nearest.
|
|
||||||
*
|
|
||||||
* See also: im_convsep_f(), im_conv(), im_create_imaskv().
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, -1 on error
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_convsep( IMAGE *in, IMAGE *out, INTMASK *mask )
|
|
||||||
{
|
|
||||||
IMAGE *t1 = im_open_local( out, "im_convsep intermediate", "p" );
|
|
||||||
int size = mask->xsize * mask->ysize;
|
|
||||||
|
|
||||||
if( !t1 ||
|
|
||||||
im_embed( in, t1, 1, size / 2, size / 2,
|
|
||||||
in->Xsize + size - 1,
|
|
||||||
in->Ysize + size - 1 ) ||
|
|
||||||
im_convsep_raw( t1, out, mask ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = 0;
|
|
||||||
out->Yoffset = 0;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
|
@ -43,6 +43,7 @@ pkginclude_HEADERS = \
|
||||||
transform.h \
|
transform.h \
|
||||||
util.h \
|
util.h \
|
||||||
version.h \
|
version.h \
|
||||||
|
vector.h \
|
||||||
vips.h
|
vips.h
|
||||||
|
|
||||||
vipsc++.h:
|
vipsc++.h:
|
||||||
|
|
|
@ -87,7 +87,8 @@ typedef enum {
|
||||||
IM_BANDFMT_FLOAT = 6,
|
IM_BANDFMT_FLOAT = 6,
|
||||||
IM_BANDFMT_COMPLEX = 7,
|
IM_BANDFMT_COMPLEX = 7,
|
||||||
IM_BANDFMT_DOUBLE = 8,
|
IM_BANDFMT_DOUBLE = 8,
|
||||||
IM_BANDFMT_DPCOMPLEX = 9
|
IM_BANDFMT_DPCOMPLEX = 9,
|
||||||
|
IM_BANDFMT_LAST = 10
|
||||||
} VipsBandFmt;
|
} VipsBandFmt;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
|
|
@ -56,6 +56,13 @@ extern "C" {
|
||||||
#define IM_CLIP(A,V,B) IM_MAX( (A), IM_MIN( (B), (V) ) )
|
#define IM_CLIP(A,V,B) IM_MAX( (A), IM_MIN( (B), (V) ) )
|
||||||
#define IM_NUMBER(R) ((int)(sizeof(R)/sizeof(R[0])))
|
#define IM_NUMBER(R) ((int)(sizeof(R)/sizeof(R[0])))
|
||||||
|
|
||||||
|
#define IM_SWAP( TYPE, A, B ) \
|
||||||
|
G_STMT_START { \
|
||||||
|
TYPE t = (A); \
|
||||||
|
(A) = (B); \
|
||||||
|
(B) = t; \
|
||||||
|
} G_STMT_END
|
||||||
|
|
||||||
#define IM_FREEF( F, S ) \
|
#define IM_FREEF( F, S ) \
|
||||||
G_STMT_START \
|
G_STMT_START \
|
||||||
if( S ) { \
|
if( S ) { \
|
||||||
|
@ -90,7 +97,8 @@ G_STMT_START { \
|
||||||
|
|
||||||
/* Duff's device. Do OPERation N times in a 16-way unrolled loop.
|
/* Duff's device. Do OPERation N times in a 16-way unrolled loop.
|
||||||
*/
|
*/
|
||||||
#define IM_UNROLL( N, OPER ) { \
|
#define IM_UNROLL( N, OPER ) \
|
||||||
|
G_STMT_START \
|
||||||
if( (N) ) { \
|
if( (N) ) { \
|
||||||
int duff_count = ((N) + 15) / 16; \
|
int duff_count = ((N) + 15) / 16; \
|
||||||
\
|
\
|
||||||
|
@ -114,7 +122,7 @@ G_STMT_START { \
|
||||||
} while( --duff_count > 0 ); \
|
} while( --duff_count > 0 ); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
}
|
G_STMT_END
|
||||||
|
|
||||||
/* Round a float to the nearest integer. Much faster than rint().
|
/* Round a float to the nearest integer. Much faster than rint().
|
||||||
*/
|
*/
|
||||||
|
@ -122,7 +130,8 @@ G_STMT_START { \
|
||||||
|
|
||||||
/* Various integer range clips. Record over/under flows.
|
/* Various integer range clips. Record over/under flows.
|
||||||
*/
|
*/
|
||||||
#define IM_CLIP_UCHAR( V, SEQ ) { \
|
#define IM_CLIP_UCHAR( V, SEQ ) \
|
||||||
|
G_STMT_START \
|
||||||
if( (V) < 0 ) { \
|
if( (V) < 0 ) { \
|
||||||
(SEQ)->underflow++; \
|
(SEQ)->underflow++; \
|
||||||
(V) = 0; \
|
(V) = 0; \
|
||||||
|
@ -131,9 +140,10 @@ G_STMT_START { \
|
||||||
(SEQ)->overflow++; \
|
(SEQ)->overflow++; \
|
||||||
(V) = UCHAR_MAX; \
|
(V) = UCHAR_MAX; \
|
||||||
} \
|
} \
|
||||||
}
|
G_STMT_END
|
||||||
|
|
||||||
#define IM_CLIP_USHORT( V, SEQ ) { \
|
#define IM_CLIP_USHORT( V, SEQ ) \
|
||||||
|
G_STMT_START \
|
||||||
if( (V) < 0 ) { \
|
if( (V) < 0 ) { \
|
||||||
(SEQ)->underflow++; \
|
(SEQ)->underflow++; \
|
||||||
(V) = 0; \
|
(V) = 0; \
|
||||||
|
@ -142,9 +152,10 @@ G_STMT_START { \
|
||||||
(SEQ)->overflow++; \
|
(SEQ)->overflow++; \
|
||||||
(V) = USHRT_MAX; \
|
(V) = USHRT_MAX; \
|
||||||
} \
|
} \
|
||||||
}
|
G_STMT_END
|
||||||
|
|
||||||
#define IM_CLIP_CHAR( V, SEQ ) { \
|
#define IM_CLIP_CHAR( V, SEQ ) \
|
||||||
|
G_STMT_START \
|
||||||
if( (V) < SCHAR_MIN ) { \
|
if( (V) < SCHAR_MIN ) { \
|
||||||
(SEQ)->underflow++; \
|
(SEQ)->underflow++; \
|
||||||
(V) = SCHAR_MIN; \
|
(V) = SCHAR_MIN; \
|
||||||
|
@ -153,9 +164,10 @@ G_STMT_START { \
|
||||||
(SEQ)->overflow++; \
|
(SEQ)->overflow++; \
|
||||||
(V) = SCHAR_MAX; \
|
(V) = SCHAR_MAX; \
|
||||||
} \
|
} \
|
||||||
}
|
G_STMT_END
|
||||||
|
|
||||||
#define IM_CLIP_SHORT( V, SEQ ) { \
|
#define IM_CLIP_SHORT( V, SEQ ) \
|
||||||
|
G_STMT_START \
|
||||||
if( (V) < SHRT_MIN ) { \
|
if( (V) < SHRT_MIN ) { \
|
||||||
(SEQ)->underflow++; \
|
(SEQ)->underflow++; \
|
||||||
(V) = SHRT_MIN; \
|
(V) = SHRT_MIN; \
|
||||||
|
@ -164,7 +176,7 @@ G_STMT_START { \
|
||||||
(SEQ)->overflow++; \
|
(SEQ)->overflow++; \
|
||||||
(V) = SHRT_MAX; \
|
(V) = SHRT_MAX; \
|
||||||
} \
|
} \
|
||||||
}
|
G_STMT_END
|
||||||
|
|
||||||
#define IM_CLIP_NONE( V, SEQ ) {}
|
#define IM_CLIP_NONE( V, SEQ ) {}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
/* helper stuff for Orc
|
||||||
|
*
|
||||||
|
* 29/10/10
|
||||||
|
* - from im_dilate hackery
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This file is part of VIPS.
|
||||||
|
|
||||||
|
VIPS is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IM_VECTOR_H
|
||||||
|
#define IM_VECTOR_H
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
#include <orc/orc.h>
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif /*__cplusplus*/
|
||||||
|
|
||||||
|
/* An Orc program.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
/* Handy for debugging.
|
||||||
|
*/
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
/* How many resources we've used so far in this codegen.
|
||||||
|
*/
|
||||||
|
int n_temp;
|
||||||
|
int n_source;
|
||||||
|
int n_destination;
|
||||||
|
int n_constant;
|
||||||
|
int n_parameter;
|
||||||
|
int n_instruction;
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
/* The code we have generated.
|
||||||
|
*/
|
||||||
|
OrcProgram *program;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
|
||||||
|
/* Compiled successfully.
|
||||||
|
*/
|
||||||
|
gboolean compiled;
|
||||||
|
} VipsVector;
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
typedef OrcExecutor VipsExecutor;
|
||||||
|
#else /*!HAVE_ORC*/
|
||||||
|
typedef int VipsExecutor;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
|
||||||
|
/* Set from the command-line.
|
||||||
|
*/
|
||||||
|
extern gboolean im__vector_enabled;
|
||||||
|
|
||||||
|
void vips_vector_init( void );
|
||||||
|
gboolean vips_vector_get_enabled( void );
|
||||||
|
void vips_vector_set_enabled( gboolean enabled );
|
||||||
|
|
||||||
|
void vips_vector_free( VipsVector *vector );
|
||||||
|
VipsVector *vips_vector_new_ds( const char *name, int size1, int size2 );
|
||||||
|
|
||||||
|
void vips_vector_constant( VipsVector *vector,
|
||||||
|
char *name, int value, int size );
|
||||||
|
void vips_vector_source_name( VipsVector *vector, char *name, int size );
|
||||||
|
void vips_vector_source( VipsVector *vector, char *name, int number, int size );
|
||||||
|
void vips_vector_temporary( VipsVector *vector, char *name, int size );
|
||||||
|
void vips_vector_asm2( VipsVector *vector,
|
||||||
|
const char *op, const char *a, const char *b );
|
||||||
|
void vips_vector_asm3( VipsVector *vector,
|
||||||
|
const char *op, const char *a, const char *b, const char *c );
|
||||||
|
gboolean vips_vector_full( VipsVector *vector );
|
||||||
|
|
||||||
|
gboolean vips_vector_compile( VipsVector *vector );
|
||||||
|
|
||||||
|
void vips_vector_print( VipsVector *vector );
|
||||||
|
|
||||||
|
void vips_executor_set_program( VipsExecutor *executor,
|
||||||
|
VipsVector *vector, int n );
|
||||||
|
void vips_executor_set_source( VipsExecutor *executor, int n, void *value );
|
||||||
|
void vips_executor_set_destination( VipsExecutor *executor, void *value );
|
||||||
|
void vips_executor_set_array( VipsExecutor *executor, char *name, void *value );
|
||||||
|
|
||||||
|
void vips_executor_run( VipsExecutor *executor );
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif /*__cplusplus*/
|
||||||
|
|
||||||
|
#endif /*IM_VECTOR_H*/
|
|
@ -73,12 +73,6 @@
|
||||||
#include <dmalloc.h>
|
#include <dmalloc.h>
|
||||||
#endif /*WITH_DMALLOC*/
|
#endif /*WITH_DMALLOC*/
|
||||||
|
|
||||||
#define SWAP( TYPE, A, B ) { \
|
|
||||||
TYPE t = (A); \
|
|
||||||
(A) = (B); \
|
|
||||||
(B) = t; \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Size of a scanline buffer. We allocate a list of these to hold scanlines
|
/* Size of a scanline buffer. We allocate a list of these to hold scanlines
|
||||||
* we need to visit.
|
* we need to visit.
|
||||||
*/
|
*/
|
||||||
|
@ -346,7 +340,7 @@ flood_all( Flood *flood, int x, int y )
|
||||||
p->n = 0;
|
p->n = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SWAP( Buffer *, flood->in, flood->out );
|
IM_SWAP( Buffer *, flood->in, flood->out );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,8 +64,6 @@
|
||||||
#include <dmalloc.h>
|
#include <dmalloc.h>
|
||||||
#endif /*WITH_DMALLOC*/
|
#endif /*WITH_DMALLOC*/
|
||||||
|
|
||||||
#define SWAP(A,B) {int t; t = (A); (A) = (B); (B) = t;}
|
|
||||||
|
|
||||||
typedef struct _Line {
|
typedef struct _Line {
|
||||||
Draw draw;
|
Draw draw;
|
||||||
|
|
||||||
|
@ -112,14 +110,14 @@ line_new( VipsImage *im, int x1, int y1, int x2, int y2, PEL *ink )
|
||||||
* right. Do diagonals here .. just have up and right and down
|
* right. Do diagonals here .. just have up and right and down
|
||||||
* and right now.
|
* and right now.
|
||||||
*/
|
*/
|
||||||
SWAP( x1, x2 );
|
IM_SWAP( int, x1, x2 );
|
||||||
SWAP( y1, y2 );
|
IM_SWAP( int, y1, y2 );
|
||||||
}
|
}
|
||||||
else if( abs( line->dx ) < abs( line->dy ) && line->dy < 0 ) {
|
else if( abs( line->dx ) < abs( line->dy ) && line->dy < 0 ) {
|
||||||
/* Swap to get all y greater cases going down the screen.
|
/* Swap to get all y greater cases going down the screen.
|
||||||
*/
|
*/
|
||||||
SWAP( x1, x2 );
|
IM_SWAP( int, x1, x2 );
|
||||||
SWAP( y1, y2 );
|
IM_SWAP( int, y1, y2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Recalculate dx, dy.
|
/* Recalculate dx, dy.
|
||||||
|
|
|
@ -44,6 +44,7 @@ libiofuncs_la_SOURCES = \
|
||||||
im_init_world.c \
|
im_init_world.c \
|
||||||
buf.c \
|
buf.c \
|
||||||
window.c \
|
window.c \
|
||||||
|
vector.c \
|
||||||
buffer.c \
|
buffer.c \
|
||||||
time.c
|
time.c
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
#include <vips/thread.h>
|
#include <vips/thread.h>
|
||||||
#include <vips/internal.h>
|
#include <vips/internal.h>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
#ifdef WITH_DMALLOC
|
||||||
#include <dmalloc.h>
|
#include <dmalloc.h>
|
||||||
|
@ -223,6 +224,10 @@ im_init_world( const char *argv0 )
|
||||||
*/
|
*/
|
||||||
im__buffer_init();
|
im__buffer_init();
|
||||||
|
|
||||||
|
/* Get the run-time compiler going.
|
||||||
|
*/
|
||||||
|
vips_vector_init();
|
||||||
|
|
||||||
done = TRUE;
|
done = TRUE;
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
|
@ -268,6 +273,9 @@ static GOptionEntry option_entries[] = {
|
||||||
{ "vips-disc-threshold", 'd', 0, G_OPTION_ARG_STRING,
|
{ "vips-disc-threshold", 'd', 0, G_OPTION_ARG_STRING,
|
||||||
&im__disc_threshold,
|
&im__disc_threshold,
|
||||||
N_( "image size above which to decompress to disc" ), NULL },
|
N_( "image size above which to decompress to disc" ), NULL },
|
||||||
|
{ "vips-novector", 't', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,
|
||||||
|
&im__vector_enabled,
|
||||||
|
N_( "disable vectorised versions of operations" ), NULL },
|
||||||
{ NULL }
|
{ NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,330 @@
|
||||||
|
/* helper functions for Orc
|
||||||
|
*
|
||||||
|
* 29/10/10
|
||||||
|
* - from morph hacking
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This file is part of VIPS.
|
||||||
|
|
||||||
|
VIPS is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
- would setting params by index rather than name be any quicker?
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Verbose messages from Orc (or use ORC_DEBUG=99 on the command-line).
|
||||||
|
#define DEBUG_ORC
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define DEBUG
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif /*HAVE_CONFIG_H*/
|
||||||
|
#include <vips/intl.h>
|
||||||
|
|
||||||
|
#include <vips/vips.h>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
|
#ifdef WITH_DMALLOC
|
||||||
|
#include <dmalloc.h>
|
||||||
|
#endif /*WITH_DMALLOC*/
|
||||||
|
|
||||||
|
/* Cleared by the command-line --vips-novector switch and the IM_NOVECTOR env
|
||||||
|
* var.
|
||||||
|
*/
|
||||||
|
gboolean im__vector_enabled = TRUE;
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_init( void )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_init();
|
||||||
|
|
||||||
|
#ifdef DEBUG_ORC
|
||||||
|
/* You can also do ORC_DEBUG=99 at the command-line.
|
||||||
|
*/
|
||||||
|
orc_debug_set_level( 99 );
|
||||||
|
#endif /*DEBUG_ORC*/
|
||||||
|
|
||||||
|
/* Look for the environment variable IM_NOVECTOR and use that to turn
|
||||||
|
* off as well.
|
||||||
|
*/
|
||||||
|
if( g_getenv( "IM_NOVECTOR" ) )
|
||||||
|
im__vector_enabled = FALSE;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
vips_vector_get_enabled( void )
|
||||||
|
{
|
||||||
|
return( im__vector_enabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_set_enabled( gboolean enabled )
|
||||||
|
{
|
||||||
|
im__vector_enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_free( VipsVector *vector )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
IM_FREEF( orc_program_free, vector->program );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
IM_FREE( vector );
|
||||||
|
}
|
||||||
|
|
||||||
|
VipsVector *
|
||||||
|
vips_vector_new_ds( const char *name, int size1, int size2 )
|
||||||
|
{
|
||||||
|
VipsVector *vector;
|
||||||
|
|
||||||
|
if( !(vector = IM_NEW( NULL, VipsVector )) )
|
||||||
|
return( NULL );
|
||||||
|
vector->name = name;
|
||||||
|
vector->n_temp = 0;
|
||||||
|
vector->n_source = 0;
|
||||||
|
vector->n_destination = 0;
|
||||||
|
vector->n_constant = 0;
|
||||||
|
vector->n_parameter = 0;
|
||||||
|
vector->n_instruction = 0;
|
||||||
|
vector->compiled = FALSE;
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
vector->program = orc_program_new_ds( size1, size2 );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
vector->n_source += 1;
|
||||||
|
vector->n_destination += 1;
|
||||||
|
|
||||||
|
return( vector );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_asm2( VipsVector *vector,
|
||||||
|
const char *op, const char *a, const char *b )
|
||||||
|
{
|
||||||
|
vector->n_instruction += 1;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( " %s %s %s\n", op, a, b );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_program_append_ds_str( vector->program, op, a, b );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_asm3( VipsVector *vector,
|
||||||
|
const char *op, const char *a, const char *b, const char *c )
|
||||||
|
{
|
||||||
|
vector->n_instruction += 1;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( " %s %s %s %s\n", op, a, b, c );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_program_append_str( vector->program, op, a, b, c );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_constant( VipsVector *vector, char *name, int value, int size )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
char *sname;
|
||||||
|
|
||||||
|
if( size == 1 )
|
||||||
|
sname = "b";
|
||||||
|
else if( size == 2 )
|
||||||
|
sname = "w";
|
||||||
|
else if( size == 4 )
|
||||||
|
sname = "l";
|
||||||
|
else {
|
||||||
|
printf( "vips_vector_constant: bad constant size\n" );
|
||||||
|
|
||||||
|
/* Not really correct, heh.
|
||||||
|
*/
|
||||||
|
sname = "x";
|
||||||
|
}
|
||||||
|
|
||||||
|
if( value > 0 )
|
||||||
|
im_snprintf( name, 256, "c%d%s", value, sname );
|
||||||
|
else
|
||||||
|
im_snprintf( name, 256, "cm%d%s", -value, sname );
|
||||||
|
|
||||||
|
if( orc_program_find_var_by_name( vector->program, name ) == -1 ) {
|
||||||
|
orc_program_add_constant( vector->program, size, value, name );
|
||||||
|
vector->n_constant += 1;
|
||||||
|
}
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_source_name( VipsVector *vector, char *name, int size )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
#ifdef DEBUG
|
||||||
|
if( orc_program_find_var_by_name( vector->program, name ) != -1 )
|
||||||
|
printf( "argh! source %s defined twice\n", name );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
orc_program_add_source( vector->program, size, name );
|
||||||
|
vector->n_source += 1;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_source( VipsVector *vector, char *name, int number, int size )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
im_snprintf( name, 256, "s%d", number );
|
||||||
|
|
||||||
|
if( orc_program_find_var_by_name( vector->program, name ) == -1 )
|
||||||
|
vips_vector_source_name( vector, name, size );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_temporary( VipsVector *vector, char *name, int size )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_program_add_temporary( vector->program, size, name );
|
||||||
|
vector->n_temp += 1;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
vips_vector_full( VipsVector *vector )
|
||||||
|
{
|
||||||
|
/* We can need a max of 2 constants plus one source per
|
||||||
|
* coefficient, so stop if we're sure we don't have enough.
|
||||||
|
* We need to stay under the 100 instruction limit too.
|
||||||
|
*/
|
||||||
|
if( vector->n_constant > 16 - 2 )
|
||||||
|
return( TRUE );
|
||||||
|
if( vector->n_source > 8 - 1 )
|
||||||
|
return( TRUE );
|
||||||
|
if( vector->n_instruction > 50 )
|
||||||
|
return( TRUE );
|
||||||
|
|
||||||
|
return( FALSE );
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
vips_vector_compile( VipsVector *vector )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
OrcCompileResult result;
|
||||||
|
|
||||||
|
result = orc_program_compile( vector->program );
|
||||||
|
if( !ORC_COMPILE_RESULT_IS_SUCCESSFUL( result ) ) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "*** error compiling %s\n", vector->name );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
return( FALSE );
|
||||||
|
}
|
||||||
|
|
||||||
|
vector->compiled = TRUE;
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
|
||||||
|
return( TRUE );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_vector_print( VipsVector *vector )
|
||||||
|
{
|
||||||
|
printf( "%s: ", vector->name );
|
||||||
|
if( vector->compiled )
|
||||||
|
printf( "successfully compiled\n" );
|
||||||
|
else
|
||||||
|
printf( "not compiled successfully\n" );
|
||||||
|
printf( " n_source = %d\n", vector->n_source );
|
||||||
|
printf( " n_parameter = %d\n", vector->n_parameter );
|
||||||
|
printf( " n_destination = %d\n", vector->n_destination );
|
||||||
|
printf( " n_constant = %d\n", vector->n_constant );
|
||||||
|
printf( " n_temp = %d\n", vector->n_temp );
|
||||||
|
printf( " n_instruction = %d\n", vector->n_instruction );
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_executor_set_program( VipsExecutor *executor, VipsVector *vector, int n )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_executor_set_program( executor, vector->program );
|
||||||
|
orc_executor_set_n( executor, n );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_executor_set_source( VipsExecutor *executor, int n, void *value )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
char name[256];
|
||||||
|
OrcProgram *program = executor->program;
|
||||||
|
|
||||||
|
im_snprintf( name, 256, "s%d", n );
|
||||||
|
if( orc_program_find_var_by_name( program, name ) != -1 )
|
||||||
|
orc_executor_set_array_str( executor, name, value );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_executor_set_destination( VipsExecutor *executor, void *value )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_executor_set_array_str( executor, "d1", value );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_executor_set_array( VipsExecutor *executor, char *name, void *value )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
OrcProgram *program = executor->program;
|
||||||
|
|
||||||
|
if( orc_program_find_var_by_name( program, name ) != -1 )
|
||||||
|
orc_executor_set_array_str( executor, name, value );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
vips_executor_run( VipsExecutor *executor )
|
||||||
|
{
|
||||||
|
#ifdef HAVE_ORC
|
||||||
|
orc_executor_run( executor );
|
||||||
|
#endif /*HAVE_ORC*/
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ noinst_LTLIBRARIES = libmorphology.la
|
||||||
|
|
||||||
libmorphology_la_SOURCES = \
|
libmorphology_la_SOURCES = \
|
||||||
im_cntlines.c \
|
im_cntlines.c \
|
||||||
im_dilate.c\
|
morphology.c\
|
||||||
im_erode.c\
|
|
||||||
im_rank.c \
|
im_rank.c \
|
||||||
im_rank_image.c \
|
im_rank_image.c \
|
||||||
im_zerox.c \
|
im_zerox.c \
|
||||||
|
|
|
@ -1,349 +0,0 @@
|
||||||
/* @(#) Function which dilates a binary VASARI format picture with a mask.
|
|
||||||
* @(#) The mask coefficients are either 255 (object) or 0 (bk) or 128 (any).
|
|
||||||
* @(#) Input image are binary images with either 0 or 255 values, one channel
|
|
||||||
* @(#) only. The program dilates a white object on a black background.
|
|
||||||
* @(#) The center of the mask is at location (m->xsize/2, m->ysize/2)
|
|
||||||
* @(#) integer division. The mask is expected to have an odd width and
|
|
||||||
* @(#) height.
|
|
||||||
* @(#)
|
|
||||||
* @(#) int im_dilate(in, out, m)
|
|
||||||
* @(#) IMAGE *in, *out;
|
|
||||||
* @(#) INTMASK *m;
|
|
||||||
* @(#)
|
|
||||||
* @(#) Returns either 0 (sucess) or -1 (fail)
|
|
||||||
*
|
|
||||||
* 19/9/95 JC
|
|
||||||
* - rewritten
|
|
||||||
* 6/7/99 JC
|
|
||||||
* - small tidies
|
|
||||||
* 7/4/04
|
|
||||||
* - now uses im_embed() with edge stretching on the input, not
|
|
||||||
* the output
|
|
||||||
* - sets Xoffset / Yoffset
|
|
||||||
* 21/4/08
|
|
||||||
* - only rebuild the buffer offsets if bpl changes
|
|
||||||
* - small cleanups
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
This file is part of VIPS.
|
|
||||||
|
|
||||||
VIPS is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
#define DEBUG
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include <config.h>
|
|
||||||
#endif /*HAVE_CONFIG_H*/
|
|
||||||
#include <vips/intl.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <vips/vips.h>
|
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
|
||||||
#include <dmalloc.h>
|
|
||||||
#endif /*WITH_DMALLOC*/
|
|
||||||
|
|
||||||
/* Our sequence value.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
REGION *ir; /* Input region */
|
|
||||||
|
|
||||||
int *soff; /* Offsets we check for set */
|
|
||||||
int ss; /* ... and number we check for set */
|
|
||||||
int *coff; /* Offsets we check for clear */
|
|
||||||
int cs; /* ... and number we check for clear */
|
|
||||||
int last_bpl; /* Avoid recalcing offsets, if we can */
|
|
||||||
} SeqInfo;
|
|
||||||
|
|
||||||
/* Stop function.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
dilate_stop( void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
|
|
||||||
IM_FREEF( im_region_free, seq->ir );
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start function.
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
dilate_start( IMAGE *out, void *a, void *b )
|
|
||||||
{
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
INTMASK *msk = (INTMASK *) b;
|
|
||||||
int sz = msk->xsize * msk->ysize;
|
|
||||||
SeqInfo *seq;
|
|
||||||
|
|
||||||
if( !(seq = IM_NEW( out, SeqInfo )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
/* Init!
|
|
||||||
*/
|
|
||||||
seq->ir = NULL;
|
|
||||||
seq->soff = NULL;
|
|
||||||
seq->ss = 0;
|
|
||||||
seq->coff = NULL;
|
|
||||||
seq->cs = 0;
|
|
||||||
seq->last_bpl = -1;
|
|
||||||
|
|
||||||
/* Attach region and arrays.
|
|
||||||
*/
|
|
||||||
seq->ir = im_region_create( in );
|
|
||||||
seq->soff = IM_ARRAY( out, sz, int );
|
|
||||||
seq->coff = IM_ARRAY( out, sz, int );
|
|
||||||
if( !seq->ir || !seq->soff || !seq->coff ) {
|
|
||||||
dilate_stop( seq, in, NULL );
|
|
||||||
return( NULL );
|
|
||||||
}
|
|
||||||
|
|
||||||
return( seq );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dilate!
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
dilate_gen( REGION *or, void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
INTMASK *msk = (INTMASK *) b;
|
|
||||||
REGION *ir = seq->ir;
|
|
||||||
|
|
||||||
int *soff = seq->soff;
|
|
||||||
int *coff = seq->coff;
|
|
||||||
|
|
||||||
Rect *r = &or->valid;
|
|
||||||
Rect s;
|
|
||||||
int le = r->left;
|
|
||||||
int to = r->top;
|
|
||||||
int bo = IM_RECT_BOTTOM(r);
|
|
||||||
int sz = IM_REGION_N_ELEMENTS( or );
|
|
||||||
|
|
||||||
int *t;
|
|
||||||
|
|
||||||
int x, y;
|
|
||||||
int result, i;
|
|
||||||
|
|
||||||
/* Prepare the section of the input image we need. A little larger
|
|
||||||
* than the section of the output image we are producing.
|
|
||||||
*/
|
|
||||||
s = *r;
|
|
||||||
s.width += msk->xsize - 1;
|
|
||||||
s.height += msk->ysize - 1;
|
|
||||||
if( im_prepare( ir, &s ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
printf( "erode_gen: preparing %dx%d pixels\n", s.width, s.height );
|
|
||||||
#endif /*DEBUG*/
|
|
||||||
|
|
||||||
/* Scan mask, building offsets we check when processing. Only do this
|
|
||||||
* if the bpl has changed since the previous im_prepare().
|
|
||||||
*/
|
|
||||||
if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) {
|
|
||||||
seq->last_bpl = IM_REGION_LSKIP( ir );
|
|
||||||
|
|
||||||
seq->ss = 0;
|
|
||||||
seq->cs = 0;
|
|
||||||
for( t = msk->coeff, y = 0; y < msk->ysize; y++ )
|
|
||||||
for( x = 0; x < msk->xsize; x++, t++ )
|
|
||||||
switch( *t ) {
|
|
||||||
case 255:
|
|
||||||
soff[seq->ss++] =
|
|
||||||
IM_REGION_ADDR( ir,
|
|
||||||
x + le, y + to ) -
|
|
||||||
IM_REGION_ADDR( ir, le, to );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 128:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
coff[seq->cs++] =
|
|
||||||
IM_REGION_ADDR( ir,
|
|
||||||
x + le, y + to ) -
|
|
||||||
IM_REGION_ADDR( ir, le, to );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
im_error( "im_dilate",
|
|
||||||
_( "bad mask element (%d "
|
|
||||||
"should be 0, 128 or 255)" ),
|
|
||||||
*t );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dilate!
|
|
||||||
*/
|
|
||||||
for( y = to; y < bo; y++ ) {
|
|
||||||
PEL *p = (PEL *) IM_REGION_ADDR( ir, le, y );
|
|
||||||
PEL *q = (PEL *) IM_REGION_ADDR( or, le, y );
|
|
||||||
|
|
||||||
/* Loop along line.
|
|
||||||
*/
|
|
||||||
for( x = 0; x < sz; x++, q++, p++ ) {
|
|
||||||
/* Search for a hit on the set list.
|
|
||||||
*/
|
|
||||||
result = 0;
|
|
||||||
for( i = 0; i < seq->ss; i++ )
|
|
||||||
if( p[soff[i]] ) {
|
|
||||||
/* Found a match!
|
|
||||||
*/
|
|
||||||
result = 255;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* No set pixels ... search for a hit in the clear
|
|
||||||
* pixels.
|
|
||||||
*/
|
|
||||||
if( !result )
|
|
||||||
for( i = 0; i < seq->cs; i++ )
|
|
||||||
if( !p[coff[i]] ) {
|
|
||||||
/* Found a match!
|
|
||||||
*/
|
|
||||||
result = 255;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
*q = result;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dilate an image.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_dilate_raw( IMAGE *in, IMAGE *out, INTMASK *m )
|
|
||||||
{
|
|
||||||
INTMASK *msk;
|
|
||||||
|
|
||||||
/* Check mask has odd number of elements in width and height.
|
|
||||||
*/
|
|
||||||
if( m->xsize < 1 || !(m->xsize & 0x1) ||
|
|
||||||
m->ysize < 1 || !(m->ysize & 0x1) ) {
|
|
||||||
im_error( "im_dilate", "%s", _( "mask size not odd" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Standard checks.
|
|
||||||
*/
|
|
||||||
if( im_piocheck( in, out ) )
|
|
||||||
return( -1 );
|
|
||||||
if( in->Coding != IM_CODING_NONE ||
|
|
||||||
in->BandFmt != IM_BANDFMT_UCHAR ) {
|
|
||||||
im_error( "im_dilate", "%s", _( "uchar uncoded only" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output
|
|
||||||
* would be 1x1.
|
|
||||||
*/
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
out->Xsize -= m->xsize - 1;
|
|
||||||
out->Ysize -= m->ysize - 1;
|
|
||||||
if( out->Xsize <= 0 || out->Ysize <= 0 ) {
|
|
||||||
im_error( "im_dilate", "%s", _( "image too small for mask" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Take a copy of m.
|
|
||||||
*/
|
|
||||||
if( !(msk = im_dup_imask( m, "conv_mask" )) )
|
|
||||||
return( -1 );
|
|
||||||
if( im_add_close_callback( out,
|
|
||||||
(im_callback_fn) im_free_imask, msk, NULL ) ) {
|
|
||||||
im_free_imask( msk );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause
|
|
||||||
* too many recalculations on overlaps.
|
|
||||||
*/
|
|
||||||
if( im_demand_hint( out, IM_FATSTRIP, in, NULL ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Generate!
|
|
||||||
*/
|
|
||||||
if( im_generate( out, dilate_start, dilate_gen, dilate_stop, in, msk ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = -m->xsize / 2;
|
|
||||||
out->Yoffset = -m->ysize / 2;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* im_dilate:
|
|
||||||
* @in: input image
|
|
||||||
* @out: output image
|
|
||||||
* @m: dilate mask
|
|
||||||
*
|
|
||||||
* Dilate an image with a mask.
|
|
||||||
* The mask coefficients are either 255 (object) or 0 (black) or 128 (don't
|
|
||||||
* care).
|
|
||||||
* Input image are binary images with either 0 or 255 values, one channel
|
|
||||||
* only. The program dilates a white object on a black background.
|
|
||||||
* The center of the mask is at location (m->xsize/2, m->ysize/2)
|
|
||||||
* integer division. The mask is expected to have an odd width and
|
|
||||||
* height.
|
|
||||||
|
|
||||||
sets pixels in the output if
|
|
||||||
*
|
|
||||||
* See also: im_erode().
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, -1 on error
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_dilate( IMAGE *in, IMAGE *out, INTMASK *m )
|
|
||||||
{
|
|
||||||
IMAGE *t1 = im_open_local( out, "im_dilate:1", "p" );
|
|
||||||
|
|
||||||
if( !t1 ||
|
|
||||||
im_embed( in, t1, 1, m->xsize / 2, m->ysize / 2,
|
|
||||||
in->Xsize + m->xsize - 1,
|
|
||||||
in->Ysize + m->ysize - 1 ) ||
|
|
||||||
im_dilate_raw( t1, out, m ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = 0;
|
|
||||||
out->Yoffset = 0;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
|
@ -1,325 +0,0 @@
|
||||||
/* @(#) Function which erodes a binary VASARI format picture with a mask.
|
|
||||||
* @(#) The mask coefficients are either 255 (object) or 0 (bk) or 128 (any).
|
|
||||||
* @(#) Input image are binary images with either 0 or 255 values, one channel
|
|
||||||
* @(#) only. The program erodes a white object on a black background.
|
|
||||||
* @(#) The center of the mask is at location (m->xsize/2, m->ysize/2)
|
|
||||||
* @(#) integer division. The mask is expected to have an odd width and
|
|
||||||
* @(#) height.
|
|
||||||
* @(#)
|
|
||||||
* @(#) int im_erode(in, out, m)
|
|
||||||
* @(#) IMAGE *in, *out;
|
|
||||||
* @(#) INTMASK *m;
|
|
||||||
* @(#)
|
|
||||||
* @(#) Returns either 0 (sucess) or -1 (fail)
|
|
||||||
*
|
|
||||||
* 19/9/95 JC
|
|
||||||
* - rewrite
|
|
||||||
* 6/7/99 JC
|
|
||||||
* - checks and small tidies
|
|
||||||
* 7/4/04
|
|
||||||
* - now uses im_embed() with edge stretching on the input, not
|
|
||||||
* the output
|
|
||||||
* - sets Xoffset / Yoffset
|
|
||||||
* 21/4/08
|
|
||||||
* - only rebuild the buffer offsets if bpl changes
|
|
||||||
* - small cleanups
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
This file is part of VIPS.
|
|
||||||
|
|
||||||
VIPS is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Lesser General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
#define DEBUG
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include <config.h>
|
|
||||||
#endif /*HAVE_CONFIG_H*/
|
|
||||||
#include <vips/intl.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
#include <vips/vips.h>
|
|
||||||
|
|
||||||
#ifdef WITH_DMALLOC
|
|
||||||
#include <dmalloc.h>
|
|
||||||
#endif /*WITH_DMALLOC*/
|
|
||||||
|
|
||||||
/* Our sequence value.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
REGION *ir; /* Input region */
|
|
||||||
|
|
||||||
int *soff; /* Offsets we check for set */
|
|
||||||
int ss; /* ... and number we check for set */
|
|
||||||
int *coff; /* Offsets we check for clear */
|
|
||||||
int cs; /* ... and number we check for clear */
|
|
||||||
int last_bpl; /* Avoid recalcing offsets, if we can */
|
|
||||||
} SeqInfo;
|
|
||||||
|
|
||||||
/* Stop function.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
erode_stop( void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
|
|
||||||
IM_FREEF( im_region_free, seq->ir );
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Start function.
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
erode_start( IMAGE *out, void *a, void *b )
|
|
||||||
{
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
INTMASK *msk = (INTMASK *) b;
|
|
||||||
SeqInfo *seq;
|
|
||||||
int sz = msk->xsize * msk->ysize;
|
|
||||||
|
|
||||||
if( !(seq = IM_NEW( out, SeqInfo )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
/* Init!
|
|
||||||
*/
|
|
||||||
seq->ir = NULL;
|
|
||||||
seq->soff = NULL;
|
|
||||||
seq->ss = 0;
|
|
||||||
seq->coff = NULL;
|
|
||||||
seq->cs = 0;
|
|
||||||
seq->last_bpl = -1;
|
|
||||||
|
|
||||||
/* Attach region and arrays.
|
|
||||||
*/
|
|
||||||
seq->ir = im_region_create( in );
|
|
||||||
seq->soff = IM_ARRAY( out, sz, int );
|
|
||||||
seq->coff = IM_ARRAY( out, sz, int );
|
|
||||||
if( !seq->ir || !seq->soff || !seq->coff ) {
|
|
||||||
erode_stop( seq, in, NULL );
|
|
||||||
return( NULL );
|
|
||||||
}
|
|
||||||
|
|
||||||
return( (void *) seq );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Erode!
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
erode_gen( REGION *or, void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
INTMASK *msk = (INTMASK *) b;
|
|
||||||
REGION *ir = seq->ir;
|
|
||||||
|
|
||||||
int *soff = seq->soff;
|
|
||||||
int *coff = seq->coff;
|
|
||||||
|
|
||||||
Rect *r = &or->valid;
|
|
||||||
Rect s;
|
|
||||||
int le = r->left;
|
|
||||||
int to = r->top;
|
|
||||||
int bo = IM_RECT_BOTTOM(r);
|
|
||||||
int sz = IM_REGION_N_ELEMENTS( or );
|
|
||||||
|
|
||||||
int *t;
|
|
||||||
|
|
||||||
int x, y;
|
|
||||||
int result, i;
|
|
||||||
|
|
||||||
/* Prepare the section of the input image we need. A little larger
|
|
||||||
* than the section of the output image we are producing.
|
|
||||||
*/
|
|
||||||
s = *r;
|
|
||||||
s.width += msk->xsize - 1;
|
|
||||||
s.height += msk->ysize - 1;
|
|
||||||
if( im_prepare( ir, &s ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
#ifdef DEBUG
|
|
||||||
printf( "erode_gen: preparing %dx%d pixels\n", s.width, s.height );
|
|
||||||
#endif /*DEBUG*/
|
|
||||||
|
|
||||||
/* Scan mask, building offsets we check when processing. Only do this
|
|
||||||
* if the bpl has changed since the previous im_prepare().
|
|
||||||
*/
|
|
||||||
if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) {
|
|
||||||
seq->last_bpl = IM_REGION_LSKIP( ir );
|
|
||||||
|
|
||||||
seq->ss = 0;
|
|
||||||
seq->cs = 0;
|
|
||||||
for( t = msk->coeff, y = 0; y < msk->ysize; y++ )
|
|
||||||
for( x = 0; x < msk->xsize; x++, t++ )
|
|
||||||
switch( *t ) {
|
|
||||||
case 255:
|
|
||||||
soff[seq->ss++] =
|
|
||||||
IM_REGION_ADDR( ir,
|
|
||||||
x + le, y + to ) -
|
|
||||||
IM_REGION_ADDR( ir, le, to );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 128:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 0:
|
|
||||||
coff[seq->cs++] =
|
|
||||||
IM_REGION_ADDR( ir,
|
|
||||||
x + le, y + to ) -
|
|
||||||
IM_REGION_ADDR( ir, le, to );
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
im_error( "im_erode",
|
|
||||||
_( "bad mask element (%d "
|
|
||||||
"should be 0, 128 or 255)" ),
|
|
||||||
*t );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Erode!
|
|
||||||
*/
|
|
||||||
for( y = to; y < bo; y++ ) {
|
|
||||||
PEL *p = (PEL *) IM_REGION_ADDR( ir, le, y );
|
|
||||||
PEL *q = (PEL *) IM_REGION_ADDR( or, le, y );
|
|
||||||
|
|
||||||
/* Loop along line.
|
|
||||||
*/
|
|
||||||
for( x = 0; x < sz; x++, q++, p++ ) {
|
|
||||||
/* Check all set pixels are set.
|
|
||||||
*/
|
|
||||||
result = 255;
|
|
||||||
for( i = 0; i < seq->ss; i++ )
|
|
||||||
if( !p[soff[i]] ) {
|
|
||||||
/* Found a mismatch!
|
|
||||||
*/
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check all clear pixels are clear.
|
|
||||||
*/
|
|
||||||
if( result )
|
|
||||||
for( i = 0; i < seq->cs; i++ )
|
|
||||||
if( p[coff[i]] ) {
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
*q = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Erode an image.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_erode_raw( IMAGE *in, IMAGE *out, INTMASK *m )
|
|
||||||
{
|
|
||||||
INTMASK *msk;
|
|
||||||
|
|
||||||
/* Check mask has odd number of elements in width and height.
|
|
||||||
*/
|
|
||||||
if( m->xsize < 1 || !(m->xsize & 0x1) ||
|
|
||||||
m->ysize < 1 || !(m->ysize & 0x1) ) {
|
|
||||||
im_error( "im_erode", "%s", _( "mask size not odd" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Standard checks.
|
|
||||||
*/
|
|
||||||
if( im_piocheck( in, out ) )
|
|
||||||
return( -1 );
|
|
||||||
if( in->Coding != IM_CODING_NONE ||
|
|
||||||
in->BandFmt != IM_BANDFMT_UCHAR ) {
|
|
||||||
im_error( "im_erode", "%s", _( "1-band uchar uncoded only" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output
|
|
||||||
* would be 1x1.
|
|
||||||
*/
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
out->Xsize -= m->xsize - 1;
|
|
||||||
out->Ysize -= m->ysize - 1;
|
|
||||||
if( out->Xsize <= 0 || out->Ysize <= 0 ) {
|
|
||||||
im_error( "im_erode", "%s", _( "image too small for mask" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Take a copy of m.
|
|
||||||
*/
|
|
||||||
if( !(msk = im_dup_imask( m, "conv_mask" )) )
|
|
||||||
return( -1 );
|
|
||||||
if( im_add_close_callback( out,
|
|
||||||
(im_callback_fn) im_free_imask, msk, NULL ) ) {
|
|
||||||
im_free_imask( msk );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause
|
|
||||||
* too many recalculations on overlaps.
|
|
||||||
*/
|
|
||||||
if( im_demand_hint( out, IM_FATSTRIP, in, NULL ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Generate!
|
|
||||||
*/
|
|
||||||
if( im_generate( out, erode_start, erode_gen, erode_stop, in, msk ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = -m->xsize / 2;
|
|
||||||
out->Yoffset = -m->ysize / 2;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The above, with a border to make out the same size as in.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_erode( IMAGE *in, IMAGE *out, INTMASK *m )
|
|
||||||
{
|
|
||||||
IMAGE *t1 = im_open_local( out, "im_erode:1", "p" );
|
|
||||||
|
|
||||||
if( !t1 ||
|
|
||||||
im_embed( in, t1, 1, m->xsize / 2, m->ysize / 2,
|
|
||||||
in->Xsize + m->xsize - 1,
|
|
||||||
in->Ysize + m->ysize - 1 ) ||
|
|
||||||
im_erode_raw( t1, out, m ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = 0;
|
|
||||||
out->Yoffset = 0;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
|
@ -0,0 +1,823 @@
|
||||||
|
/* morphological operators
|
||||||
|
*
|
||||||
|
* 19/9/95 JC
|
||||||
|
* - rewritten
|
||||||
|
* 6/7/99 JC
|
||||||
|
* - small tidies
|
||||||
|
* 7/4/04
|
||||||
|
* - now uses im_embed() with edge stretching on the input, not
|
||||||
|
* the output
|
||||||
|
* - sets Xoffset / Yoffset
|
||||||
|
* 21/4/08
|
||||||
|
* - only rebuild the buffer offsets if bpl changes
|
||||||
|
* - small cleanups
|
||||||
|
* 25/10/10
|
||||||
|
* - start again from the Orc'd im_conv
|
||||||
|
* 29/10/10
|
||||||
|
* - use VipsVector
|
||||||
|
* - do erode as well
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
This file is part of VIPS.
|
||||||
|
|
||||||
|
VIPS is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define DEBUG
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif /*HAVE_CONFIG_H*/
|
||||||
|
#include <vips/intl.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include <vips/vips.h>
|
||||||
|
#include <vips/vector.h>
|
||||||
|
|
||||||
|
#ifdef WITH_DMALLOC
|
||||||
|
#include <dmalloc.h>
|
||||||
|
#endif /*WITH_DMALLOC*/
|
||||||
|
|
||||||
|
/* The two operators we implement. They are more hit-miss, really.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
ERODE,
|
||||||
|
DILATE
|
||||||
|
} MorphOp;
|
||||||
|
|
||||||
|
/* We can't run more than this many passes. Larger than this and we
|
||||||
|
* fall back to C.
|
||||||
|
*/
|
||||||
|
#define MAX_PASSES (10)
|
||||||
|
|
||||||
|
/* A pass with a vector.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
int first; /* The index of the first mask coff we use */
|
||||||
|
int last; /* The index of the last mask coff we use */
|
||||||
|
|
||||||
|
/* The code we generate for this section of this mask.
|
||||||
|
*/
|
||||||
|
VipsVector *vector;
|
||||||
|
} Pass;
|
||||||
|
|
||||||
|
/* Our parameters.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
IMAGE *in;
|
||||||
|
IMAGE *out;
|
||||||
|
INTMASK *mask; /* Copy of mask arg */
|
||||||
|
MorphOp op;
|
||||||
|
|
||||||
|
/* The passes we generate for this mask.
|
||||||
|
*/
|
||||||
|
int n_pass;
|
||||||
|
Pass pass[MAX_PASSES];
|
||||||
|
} Morph;
|
||||||
|
|
||||||
|
static void
|
||||||
|
pass_free( Morph *morph )
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for( i = 0; i < morph->n_pass; i++ )
|
||||||
|
IM_FREEF( vips_vector_free, morph->pass[i].vector );
|
||||||
|
morph->n_pass = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
morph_close( Morph *morph )
|
||||||
|
{
|
||||||
|
IM_FREEF( im_free_imask, morph->mask );
|
||||||
|
pass_free( morph );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
#define TEMP( N, S ) vips_vector_temporary( v, N, S )
|
||||||
|
#define SRC( N, P, S ) vips_vector_source( v, N, P, S )
|
||||||
|
#define CONST( N, V, S ) vips_vector_constant( v, N, V, S )
|
||||||
|
#define ASM2( OP, A, B ) vips_vector_asm2( v, OP, A, B )
|
||||||
|
#define ASM3( OP, A, B, C ) vips_vector_asm3( v, OP, A, B, C )
|
||||||
|
|
||||||
|
/* Generate code for a section of the mask. first is the index we start
|
||||||
|
* at, we set last to the index of the last one we use before we run
|
||||||
|
* out of intermediates / constants / parameters / sources or mask
|
||||||
|
* coefficients.
|
||||||
|
*
|
||||||
|
* 0 for success, -1 on error.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pass_compile_section( Morph *morph, int first, int *last )
|
||||||
|
{
|
||||||
|
INTMASK *mask = morph->mask;
|
||||||
|
const int n_mask = mask->xsize * mask->ysize;
|
||||||
|
|
||||||
|
Pass *pass;
|
||||||
|
VipsVector *v;
|
||||||
|
char offset[256];
|
||||||
|
char source[256];
|
||||||
|
char zero[256];
|
||||||
|
char one[256];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Skip any don't-care coefficients at the start of the mask region.
|
||||||
|
*/
|
||||||
|
for( ; mask->coeff[first] == 128 && first < n_mask; first++ )
|
||||||
|
;
|
||||||
|
if( first == n_mask )
|
||||||
|
return( 0 );
|
||||||
|
|
||||||
|
/* Allocate space for another pass.
|
||||||
|
*/
|
||||||
|
if( morph->n_pass == MAX_PASSES )
|
||||||
|
return( -1 );
|
||||||
|
pass = &morph->pass[morph->n_pass];
|
||||||
|
morph->n_pass += 1;
|
||||||
|
pass->first = first;
|
||||||
|
|
||||||
|
/* Start with a single source scanline, we add more as we need them.
|
||||||
|
*/
|
||||||
|
pass->vector = v = vips_vector_new_ds( "morph", 1, 1 );
|
||||||
|
|
||||||
|
/* The value we fetch from the image,
|
||||||
|
* the accumulated sum.
|
||||||
|
*/
|
||||||
|
TEMP( "value", 1 );
|
||||||
|
TEMP( "sum", 1 );
|
||||||
|
|
||||||
|
CONST( zero, 0, 1 );
|
||||||
|
CONST( one, 255, 1 );
|
||||||
|
|
||||||
|
/* Init the sum. If this is the first pass, it's a constant. If this
|
||||||
|
* is a later pass, we have to init the sum from the result
|
||||||
|
* of the previous pass.
|
||||||
|
*/
|
||||||
|
if( morph->n_pass == 1 ) {
|
||||||
|
if( morph->op == DILATE )
|
||||||
|
ASM2( "copyb", "sum", zero );
|
||||||
|
else
|
||||||
|
ASM2( "copyb", "sum", one );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* "r" is the result of the previous pass.
|
||||||
|
*/
|
||||||
|
vips_vector_source_name( v, "r", 1 );
|
||||||
|
ASM2( "loadb", "sum", "r" );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( i = first; i < n_mask; i++ ) {
|
||||||
|
int x = i % mask->xsize;
|
||||||
|
int y = i / mask->xsize;
|
||||||
|
|
||||||
|
/* Exclude don't-care elements.
|
||||||
|
*/
|
||||||
|
if( mask->coeff[i] == 128 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* The source. s1 is the first scanline in the mask.
|
||||||
|
*/
|
||||||
|
vips_vector_source( v, source, y + 1, 1 );
|
||||||
|
|
||||||
|
/* The offset, only for non-first-columns though.
|
||||||
|
*/
|
||||||
|
if( x > 0 ) {
|
||||||
|
CONST( offset, morph->in->Bands * x, 1 );
|
||||||
|
ASM3( "loadoffb", "value", source, offset );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ASM2( "loadb", "value", source );
|
||||||
|
|
||||||
|
/* Join to our sum. If the mask element is zero, we have to
|
||||||
|
* add an extra negate.
|
||||||
|
*/
|
||||||
|
if( morph->op == DILATE ) {
|
||||||
|
if( !mask->coeff[i] )
|
||||||
|
ASM3( "xorb", "value", "value", one );
|
||||||
|
ASM3( "orb", "sum", "sum", "value" );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if( !mask->coeff[i] )
|
||||||
|
ASM3( "andnb", "sum", "sum", "value" );
|
||||||
|
else
|
||||||
|
ASM3( "andb", "sum", "sum", "value" );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( vips_vector_full( v ) )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pass->last = i;
|
||||||
|
*last = i;
|
||||||
|
|
||||||
|
ASM2( "copyb", "d1", "sum" );
|
||||||
|
|
||||||
|
if( !vips_vector_compile( v ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "done matrix coeffs %d to %d\n", pass->first, pass->last );
|
||||||
|
vips_vector_print( v );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generate a set of passes.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
pass_compile( Morph *morph )
|
||||||
|
{
|
||||||
|
INTMASK *mask = morph->mask;
|
||||||
|
const int n_mask = mask->xsize * mask->ysize;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "morph: generating vector code\n" );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
/* Generate passes until we've used up the whole mask.
|
||||||
|
*/
|
||||||
|
for( i = 0;;) {
|
||||||
|
int last;
|
||||||
|
|
||||||
|
if( pass_compile_section( morph, i, &last ) )
|
||||||
|
return( -1 );
|
||||||
|
i = last + 1;
|
||||||
|
|
||||||
|
if( i >= n_mask )
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static Morph *
|
||||||
|
morph_new( IMAGE *in, IMAGE *out, INTMASK *mask, MorphOp op )
|
||||||
|
{
|
||||||
|
const int n_mask = mask->xsize * mask->ysize;
|
||||||
|
|
||||||
|
Morph *morph;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if( im_piocheck( in, out ) ||
|
||||||
|
im_check_uncoded( "morph", in ) ||
|
||||||
|
im_check_format( "morph", in, IM_BANDFMT_UCHAR ) ||
|
||||||
|
im_check_imask( "morph", mask ) )
|
||||||
|
return( NULL );
|
||||||
|
for( i = 0; i < n_mask; i++ )
|
||||||
|
if( mask->coeff[i] != 0 &&
|
||||||
|
mask->coeff[i] != 128 &&
|
||||||
|
mask->coeff[i] != 255 ) {
|
||||||
|
im_error( "morph",
|
||||||
|
_( "bad mask element (%d "
|
||||||
|
"should be 0, 128 or 255)" ),
|
||||||
|
mask->coeff[i] );
|
||||||
|
return( NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !(morph = IM_NEW( out, Morph )) )
|
||||||
|
return( NULL );
|
||||||
|
|
||||||
|
morph->in = in;
|
||||||
|
morph->out = out;
|
||||||
|
morph->mask = NULL;
|
||||||
|
morph->op = op;
|
||||||
|
|
||||||
|
morph->n_pass = 0;
|
||||||
|
for( i = 0; i < MAX_PASSES; i++ )
|
||||||
|
morph->pass[i].vector = NULL;
|
||||||
|
|
||||||
|
if( im_add_close_callback( out,
|
||||||
|
(im_callback_fn) morph_close, morph, NULL ) ||
|
||||||
|
!(morph->mask = im_dup_imask( mask, "morph" )) )
|
||||||
|
return( NULL );
|
||||||
|
|
||||||
|
/* Generate code for this mask / image, if possible.
|
||||||
|
*/
|
||||||
|
if( vips_vector_get_enabled() ) {
|
||||||
|
if( pass_compile( morph ) )
|
||||||
|
pass_free( morph );
|
||||||
|
}
|
||||||
|
|
||||||
|
return( morph );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Our sequence value.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
Morph *morph;
|
||||||
|
REGION *ir; /* Input region */
|
||||||
|
|
||||||
|
int *soff; /* Offsets we check for set */
|
||||||
|
int ss; /* ... and number we check for set */
|
||||||
|
int *coff; /* Offsets we check for clear */
|
||||||
|
int cs; /* ... and number we check for clear */
|
||||||
|
|
||||||
|
int last_bpl; /* Avoid recalcing offsets, if we can */
|
||||||
|
|
||||||
|
/* In vector mode we need a pair of intermediate buffers to keep the
|
||||||
|
* results of each pass in.
|
||||||
|
*/
|
||||||
|
void *t1;
|
||||||
|
void *t2;
|
||||||
|
} MorphSequence;
|
||||||
|
|
||||||
|
/* Free a sequence value.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
morph_stop( void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
MorphSequence *seq = (MorphSequence *) vseq;
|
||||||
|
|
||||||
|
IM_FREEF( im_region_free, seq->ir );
|
||||||
|
IM_FREE( seq->t1 );
|
||||||
|
IM_FREE( seq->t2 );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Morph start function.
|
||||||
|
*/
|
||||||
|
static void *
|
||||||
|
morph_start( IMAGE *out, void *a, void *b )
|
||||||
|
{
|
||||||
|
IMAGE *in = (IMAGE *) a;
|
||||||
|
Morph *morph = (Morph *) b;
|
||||||
|
int n_mask = morph->mask->xsize * morph->mask->ysize;
|
||||||
|
int sz = IM_IMAGE_N_ELEMENTS( in );
|
||||||
|
|
||||||
|
MorphSequence *seq;
|
||||||
|
|
||||||
|
if( !(seq = IM_NEW( out, MorphSequence )) )
|
||||||
|
return( NULL );
|
||||||
|
|
||||||
|
/* Init!
|
||||||
|
*/
|
||||||
|
seq->morph = morph;
|
||||||
|
seq->ir = NULL;
|
||||||
|
seq->soff = NULL;
|
||||||
|
seq->ss = 0;
|
||||||
|
seq->coff = NULL;
|
||||||
|
seq->cs = 0;
|
||||||
|
seq->last_bpl = -1;
|
||||||
|
seq->t1 = NULL;
|
||||||
|
seq->t2 = NULL;
|
||||||
|
|
||||||
|
/* Attach region and arrays.
|
||||||
|
*/
|
||||||
|
seq->ir = im_region_create( in );
|
||||||
|
seq->soff = IM_ARRAY( out, n_mask, int );
|
||||||
|
seq->coff = IM_ARRAY( out, n_mask, int );
|
||||||
|
seq->t1 = IM_ARRAY( NULL, sz, PEL );
|
||||||
|
seq->t2 = IM_ARRAY( NULL, sz, PEL );
|
||||||
|
if( !seq->ir || !seq->soff || !seq->coff || !seq->t1 || !seq->t2 ) {
|
||||||
|
morph_stop( seq, in, NULL );
|
||||||
|
return( NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
return( seq );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dilate!
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
dilate_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
MorphSequence *seq = (MorphSequence *) vseq;
|
||||||
|
Morph *morph = (Morph *) b;
|
||||||
|
INTMASK *mask = morph->mask;
|
||||||
|
REGION *ir = seq->ir;
|
||||||
|
|
||||||
|
int *soff = seq->soff;
|
||||||
|
int *coff = seq->coff;
|
||||||
|
|
||||||
|
Rect *r = &or->valid;
|
||||||
|
Rect s;
|
||||||
|
int le = r->left;
|
||||||
|
int to = r->top;
|
||||||
|
int bo = IM_RECT_BOTTOM( r );
|
||||||
|
int sz = IM_REGION_N_ELEMENTS( or );
|
||||||
|
|
||||||
|
int *t;
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
int result, i;
|
||||||
|
|
||||||
|
/* Prepare the section of the input image we need. A little larger
|
||||||
|
* than the section of the output image we are producing.
|
||||||
|
*/
|
||||||
|
s = *r;
|
||||||
|
s.width += mask->xsize - 1;
|
||||||
|
s.height += mask->ysize - 1;
|
||||||
|
if( im_prepare( ir, &s ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
/* Scan mask, building offsets we check when processing. Only do this
|
||||||
|
* if the bpl has changed since the previous im_prepare().
|
||||||
|
*/
|
||||||
|
if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) {
|
||||||
|
seq->last_bpl = IM_REGION_LSKIP( ir );
|
||||||
|
|
||||||
|
seq->ss = 0;
|
||||||
|
seq->cs = 0;
|
||||||
|
for( t = mask->coeff, y = 0; y < mask->ysize; y++ )
|
||||||
|
for( x = 0; x < mask->xsize; x++, t++ )
|
||||||
|
switch( *t ) {
|
||||||
|
case 255:
|
||||||
|
soff[seq->ss++] =
|
||||||
|
IM_REGION_ADDR( ir,
|
||||||
|
x + le, y + to ) -
|
||||||
|
IM_REGION_ADDR( ir, le, to );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 128:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
coff[seq->cs++] =
|
||||||
|
IM_REGION_ADDR( ir,
|
||||||
|
x + le, y + to ) -
|
||||||
|
IM_REGION_ADDR( ir, le, to );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert( 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dilate!
|
||||||
|
*/
|
||||||
|
for( y = to; y < bo; y++ ) {
|
||||||
|
PEL *p = (PEL *) IM_REGION_ADDR( ir, le, y );
|
||||||
|
PEL *q = (PEL *) IM_REGION_ADDR( or, le, y );
|
||||||
|
|
||||||
|
/* Loop along line.
|
||||||
|
*/
|
||||||
|
for( x = 0; x < sz; x++, q++, p++ ) {
|
||||||
|
/* Search for a hit on the set list.
|
||||||
|
*/
|
||||||
|
result = 0;
|
||||||
|
for( i = 0; i < seq->ss; i++ )
|
||||||
|
if( p[soff[i]] ) {
|
||||||
|
/* Found a match!
|
||||||
|
*/
|
||||||
|
result = 255;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No set pixels ... search for a hit in the clear
|
||||||
|
* pixels.
|
||||||
|
*/
|
||||||
|
if( !result )
|
||||||
|
for( i = 0; i < seq->cs; i++ )
|
||||||
|
if( !p[coff[i]] ) {
|
||||||
|
/* Found a match!
|
||||||
|
*/
|
||||||
|
result = 255;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*q = result;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Erode!
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
erode_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
MorphSequence *seq = (MorphSequence *) vseq;
|
||||||
|
INTMASK *msk = (INTMASK *) b;
|
||||||
|
REGION *ir = seq->ir;
|
||||||
|
|
||||||
|
int *soff = seq->soff;
|
||||||
|
int *coff = seq->coff;
|
||||||
|
|
||||||
|
Rect *r = &or->valid;
|
||||||
|
Rect s;
|
||||||
|
int le = r->left;
|
||||||
|
int to = r->top;
|
||||||
|
int bo = IM_RECT_BOTTOM(r);
|
||||||
|
int sz = IM_REGION_N_ELEMENTS( or );
|
||||||
|
|
||||||
|
int *t;
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
int result, i;
|
||||||
|
|
||||||
|
/* Prepare the section of the input image we need. A little larger
|
||||||
|
* than the section of the output image we are producing.
|
||||||
|
*/
|
||||||
|
s = *r;
|
||||||
|
s.width += msk->xsize - 1;
|
||||||
|
s.height += msk->ysize - 1;
|
||||||
|
if( im_prepare( ir, &s ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "erode_gen: preparing %dx%d pixels\n", s.width, s.height );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
|
||||||
|
/* Scan mask, building offsets we check when processing. Only do this
|
||||||
|
* if the bpl has changed since the previous im_prepare().
|
||||||
|
*/
|
||||||
|
if( seq->last_bpl != IM_REGION_LSKIP( ir ) ) {
|
||||||
|
seq->last_bpl = IM_REGION_LSKIP( ir );
|
||||||
|
|
||||||
|
seq->ss = 0;
|
||||||
|
seq->cs = 0;
|
||||||
|
for( t = msk->coeff, y = 0; y < msk->ysize; y++ )
|
||||||
|
for( x = 0; x < msk->xsize; x++, t++ )
|
||||||
|
switch( *t ) {
|
||||||
|
case 255:
|
||||||
|
soff[seq->ss++] =
|
||||||
|
IM_REGION_ADDR( ir,
|
||||||
|
x + le, y + to ) -
|
||||||
|
IM_REGION_ADDR( ir, le, to );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 128:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
coff[seq->cs++] =
|
||||||
|
IM_REGION_ADDR( ir,
|
||||||
|
x + le, y + to ) -
|
||||||
|
IM_REGION_ADDR( ir, le, to );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert( 0 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Erode!
|
||||||
|
*/
|
||||||
|
for( y = to; y < bo; y++ ) {
|
||||||
|
PEL *p = (PEL *) IM_REGION_ADDR( ir, le, y );
|
||||||
|
PEL *q = (PEL *) IM_REGION_ADDR( or, le, y );
|
||||||
|
|
||||||
|
/* Loop along line.
|
||||||
|
*/
|
||||||
|
for( x = 0; x < sz; x++, q++, p++ ) {
|
||||||
|
/* Check all set pixels are set.
|
||||||
|
*/
|
||||||
|
result = 255;
|
||||||
|
for( i = 0; i < seq->ss; i++ )
|
||||||
|
if( !p[soff[i]] ) {
|
||||||
|
/* Found a mismatch!
|
||||||
|
*/
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check all clear pixels are clear.
|
||||||
|
*/
|
||||||
|
if( result )
|
||||||
|
for( i = 0; i < seq->cs; i++ )
|
||||||
|
if( p[coff[i]] ) {
|
||||||
|
result = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
*q = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
pass_run( Morph *morph, Pass *pass, VipsExecutor *executor,
|
||||||
|
REGION *ir, void *t1, void *t2, int x, int y )
|
||||||
|
{
|
||||||
|
INTMASK *mask = morph->mask;
|
||||||
|
int top = pass->first / mask->xsize;
|
||||||
|
int bottom = pass->last / mask->xsize;
|
||||||
|
|
||||||
|
PEL *p = (PEL *) IM_REGION_ADDR( ir, x, y );
|
||||||
|
int lsk = IM_REGION_LSKIP( ir );
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* Generate all the scanline pointers this prog needs.
|
||||||
|
*/
|
||||||
|
for( i = top; i <= bottom; i++ )
|
||||||
|
vips_executor_set_source( executor, i + 1, p + i * lsk );
|
||||||
|
|
||||||
|
/* It might need the result from a previous pass.
|
||||||
|
*/
|
||||||
|
vips_executor_set_array( executor, "r", t1 );
|
||||||
|
|
||||||
|
vips_executor_set_array( executor, "d1", t2 );
|
||||||
|
|
||||||
|
vips_executor_run( executor );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The vector codepath.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
morph_vector_gen( REGION *or, void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
MorphSequence *seq = (MorphSequence *) vseq;
|
||||||
|
Morph *morph = (Morph *) b;
|
||||||
|
INTMASK *mask = morph->mask;
|
||||||
|
REGION *ir = seq->ir;
|
||||||
|
Rect *r = &or->valid;
|
||||||
|
int sz = IM_REGION_N_ELEMENTS( or );
|
||||||
|
|
||||||
|
Rect s;
|
||||||
|
int y, j;
|
||||||
|
VipsExecutor executor[MAX_PASSES];
|
||||||
|
|
||||||
|
/* Prepare the section of the input image we need. A little larger
|
||||||
|
* than the section of the output image we are producing.
|
||||||
|
*/
|
||||||
|
s = *r;
|
||||||
|
s.width += mask->xsize - 1;
|
||||||
|
s.height += mask->ysize - 1;
|
||||||
|
if( im_prepare( ir, &s ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
for( j = 0; j < morph->n_pass; j++ )
|
||||||
|
vips_executor_set_program( &executor[j],
|
||||||
|
morph->pass[j].vector, sz );
|
||||||
|
|
||||||
|
for( y = 0; y < r->height; y++ ) {
|
||||||
|
for( j = 0; j < morph->n_pass; j++ ) {
|
||||||
|
void *d;
|
||||||
|
|
||||||
|
/* The last pass goes to the output image,
|
||||||
|
* intermediate passes go to t2.
|
||||||
|
*/
|
||||||
|
if( j == morph->n_pass - 1 )
|
||||||
|
d = IM_REGION_ADDR( or, r->left, r->top + y );
|
||||||
|
else
|
||||||
|
d = seq->t2;
|
||||||
|
|
||||||
|
pass_run( morph, &morph->pass[j], &executor[j],
|
||||||
|
ir, seq->t1, d, r->left, r->top + y );
|
||||||
|
|
||||||
|
IM_SWAP( void *, seq->t1, seq->t2 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Morph an image.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
morphology( IMAGE *in, IMAGE *out, INTMASK *mask, MorphOp op )
|
||||||
|
{
|
||||||
|
Morph *morph;
|
||||||
|
im_generate_fn generate;
|
||||||
|
|
||||||
|
/* Check parameters.
|
||||||
|
*/
|
||||||
|
if( !(morph = morph_new( in, out, mask, op )) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
/* Prepare output. Consider a 7x7 mask and a 7x7 image --- the output
|
||||||
|
* would be 1x1.
|
||||||
|
*/
|
||||||
|
if( im_cp_desc( out, in ) )
|
||||||
|
return( -1 );
|
||||||
|
out->Xsize -= mask->xsize - 1;
|
||||||
|
out->Ysize -= mask->ysize - 1;
|
||||||
|
if( out->Xsize <= 0 || out->Ysize <= 0 ) {
|
||||||
|
im_error( "morph", "%s", _( "image too small for mask" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if( morph->n_pass ) {
|
||||||
|
generate = morph_vector_gen;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
printf( "morph_vector_gen: %d passes\n", morph->n_pass );
|
||||||
|
#endif /*DEBUG*/
|
||||||
|
}
|
||||||
|
else if( op == DILATE )
|
||||||
|
generate = dilate_gen;
|
||||||
|
else
|
||||||
|
generate = erode_gen;
|
||||||
|
|
||||||
|
/* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause
|
||||||
|
* too many recalculations on overlaps.
|
||||||
|
*/
|
||||||
|
if( im_demand_hint( out, IM_FATSTRIP, in, NULL ) ||
|
||||||
|
im_generate( out,
|
||||||
|
morph_start, generate, morph_stop, in, morph ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
out->Xoffset = -mask->xsize / 2;
|
||||||
|
out->Yoffset = -mask->ysize / 2;
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
im_dilate_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
return( morphology( in, out, mask, DILATE ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
im_erode_raw( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
return( morphology( in, out, mask, ERODE ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im_dilate:
|
||||||
|
* @in: input image
|
||||||
|
* @out: output image
|
||||||
|
* @mask: mask
|
||||||
|
*
|
||||||
|
* Operations are performed using the processor's vector unit,
|
||||||
|
* if possible. Disable this with --vips-novector or IM_NOVECTOR.
|
||||||
|
*
|
||||||
|
* See also:
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
im_dilate( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
IMAGE *t1 = im_open_local( out, "im_dilate:1", "p" );
|
||||||
|
|
||||||
|
if( !t1 ||
|
||||||
|
im_embed( in, t1, 1, mask->xsize / 2, mask->ysize / 2,
|
||||||
|
in->Xsize + mask->xsize - 1,
|
||||||
|
in->Ysize + mask->ysize - 1 ) ||
|
||||||
|
morphology( in, out, mask, DILATE ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
out->Xoffset = 0;
|
||||||
|
out->Yoffset = 0;
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* im_erode:
|
||||||
|
* @in: input image
|
||||||
|
* @out: output image
|
||||||
|
* @mask: mask
|
||||||
|
*
|
||||||
|
* Operations are performed using the processor's vector unit,
|
||||||
|
* if possible. Disable this with --vips-novector or IM_NOVECTOR.
|
||||||
|
*
|
||||||
|
* See also:
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
im_erode( IMAGE *in, IMAGE *out, INTMASK *mask )
|
||||||
|
{
|
||||||
|
IMAGE *t1 = im_open_local( out, "im_erode:1", "p" );
|
||||||
|
|
||||||
|
if( !t1 ||
|
||||||
|
im_embed( in, t1, 1, mask->xsize / 2, mask->ysize / 2,
|
||||||
|
in->Xsize + mask->xsize - 1,
|
||||||
|
in->Ysize + mask->ysize - 1 ) ||
|
||||||
|
morphology( in, out, mask, ERODE ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
out->Xoffset = 0;
|
||||||
|
out->Yoffset = 0;
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
|
@ -156,8 +156,6 @@ copydevpoints( TIE_POINTS *pnew, TIE_POINTS *pold )
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SWAP( A, B ) { void *t = (A); A = B; B = t; }
|
|
||||||
|
|
||||||
int
|
int
|
||||||
im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints )
|
im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints )
|
||||||
{
|
{
|
||||||
|
@ -182,7 +180,7 @@ im__improve( TIE_POINTS *inpoints, TIE_POINTS *outpoints )
|
||||||
|
|
||||||
/* And loop.
|
/* And loop.
|
||||||
*/
|
*/
|
||||||
SWAP( p, q );
|
IM_SWAP( void *, p, q );
|
||||||
}
|
}
|
||||||
|
|
||||||
/* q has the output - copy to outpoints.
|
/* q has the output - copy to outpoints.
|
||||||
|
|
Loading…
Reference in New Issue