Merge branch 'master' of github.com:jcupitt/libvips
This commit is contained in:
commit
e09aa1d4be
@ -4,11 +4,15 @@
|
||||
- add vips_resize()
|
||||
- return of vips_init(), but just for bindings
|
||||
- revised type.c to make it more binding-friendly
|
||||
- add @background arg to save: the colour to flatten against
|
||||
|
||||
8/9/14 started 7.40.9
|
||||
- support jfif resunit "none"
|
||||
- support GRAY as an input and output ICC space
|
||||
- fix a read loop with setjmp() in png read, if the png file is broken
|
||||
- fix vipsthumbnail with both input cmyk and output rgb profiles specified
|
||||
- vipsthumbnail retries with specified input profile if embedded profile is
|
||||
broken
|
||||
|
||||
8/9/14 started 7.40.8
|
||||
- fix configure on rhel6 [Lovell]
|
||||
|
6
TODO
6
TODO
@ -1,6 +1,12 @@
|
||||
|
||||
- python:
|
||||
|
||||
- try
|
||||
|
||||
python test_arithmetic.py -v TestArithmetic.test_histfind
|
||||
|
||||
segv
|
||||
|
||||
- could import like this:
|
||||
|
||||
from gi.repository import Vips
|
||||
|
24
configure.ac
24
configure.ac
@ -103,26 +103,16 @@ headers="\
|
||||
foreign.h \
|
||||
interpolate.h \
|
||||
header.h \
|
||||
type.h \
|
||||
operation.h \
|
||||
buf.h \
|
||||
colour.h \
|
||||
enumtypes.h \
|
||||
conversion.h \
|
||||
convolution.h \
|
||||
create.h \
|
||||
draw.h \
|
||||
freqfilt.h \
|
||||
histogram.h \
|
||||
memory.h \
|
||||
morphology.h \
|
||||
mosaicing.h \
|
||||
arithmetic.h \
|
||||
rect.h \
|
||||
region.h \
|
||||
relational.h \
|
||||
resample.h"
|
||||
|
||||
#headers="basic.h vips.h object.h image.h error.h foreign.h interpolate.h header.h operation.h enumtypes.h arithmetic.h conversion.h type.h"
|
||||
colour.h \
|
||||
convolution.h \
|
||||
draw.h \
|
||||
morphology.h \
|
||||
type.h \
|
||||
region.h"
|
||||
|
||||
for name in $headers; do
|
||||
vips_introspection_sources="$vips_introspection_sources include/vips/$name"
|
||||
|
@ -380,6 +380,3 @@
|
||||
fun:rb_enc_str_new
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -32,6 +32,8 @@
|
||||
* - remove liboil
|
||||
* 24/8/11
|
||||
* - rewrite as a class
|
||||
* 12/9/14
|
||||
* - oops, fix complex avg
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -98,17 +100,11 @@ vips_avg_build( VipsObject *object )
|
||||
if( VIPS_OBJECT_CLASS( vips_avg_parent_class )->build( object ) )
|
||||
return( -1 );
|
||||
|
||||
/* Calculate average. For complex, we accumulate re*re +
|
||||
* im*im, so we need to sqrt.
|
||||
*/
|
||||
vals = (gint64)
|
||||
vips_image_get_width( statistic->in ) *
|
||||
vips_image_get_height( statistic->in ) *
|
||||
vips_image_get_bands( statistic->in );
|
||||
average = avg->sum / vals;
|
||||
if( vips_band_format_iscomplex(
|
||||
vips_image_get_format( statistic->in ) ) )
|
||||
average = sqrt( average );
|
||||
g_object_set( object, "out", average, NULL );
|
||||
|
||||
return( 0 );
|
||||
@ -151,12 +147,10 @@ vips_avg_stop( VipsStatistic *statistic, void *seq )
|
||||
TYPE *p = (TYPE *) in; \
|
||||
\
|
||||
for( i = 0; i < sz; i++ ) { \
|
||||
double mod; \
|
||||
\
|
||||
mod = p[0] * p[0] + p[1] * p[1]; \
|
||||
p += 2; \
|
||||
double mod = sqrt( p[0] * p[0] + p[1] * p[1] ); \
|
||||
\
|
||||
m += mod; \
|
||||
p += 2; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
@ -151,17 +151,17 @@ vips_getpoint_class_init( VipsGetpointClass *class )
|
||||
|
||||
VIPS_ARG_INT( class, "x", 5,
|
||||
_( "x" ),
|
||||
_( "Getpoint to read from" ),
|
||||
_( "Point to read" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsGetpoint, x ),
|
||||
1, RANGE, 1 );
|
||||
0, RANGE, 0 );
|
||||
|
||||
VIPS_ARG_INT( class, "y", 6,
|
||||
_( "y" ),
|
||||
_( "Getpoint to read from" ),
|
||||
_( "Point to read" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsGetpoint, y ),
|
||||
1, RANGE, 1 );
|
||||
0, RANGE, 0 );
|
||||
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ typedef struct _VipsEmbed {
|
||||
VipsImage *in;
|
||||
|
||||
VipsExtend extend;
|
||||
VipsArea *background;
|
||||
VipsArrayDouble *background;
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
@ -362,7 +362,8 @@ vips_embed_build( VipsObject *object )
|
||||
|
||||
if( !(embed->ink = vips__vector_to_ink(
|
||||
class->nickname, embed->in,
|
||||
embed->background->data, NULL, embed->background->n )) )
|
||||
VIPS_AREA( embed->background )->data, NULL,
|
||||
VIPS_AREA( embed->background )->n )) )
|
||||
return( -1 );
|
||||
|
||||
if( !vips_object_argument_isset( object, "extend" ) &&
|
||||
@ -614,9 +615,7 @@ static void
|
||||
vips_embed_init( VipsEmbed *embed )
|
||||
{
|
||||
embed->extend = VIPS_EXTEND_BLACK;
|
||||
embed->background =
|
||||
vips_area_new_array( G_TYPE_DOUBLE, sizeof( double ), 1 );
|
||||
((double *) (embed->background->data))[0] = 0;
|
||||
embed->background = vips_array_double_newv( 1, 0.0 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,7 +61,7 @@ typedef struct _VipsFlatten {
|
||||
|
||||
/* Background colour.
|
||||
*/
|
||||
VipsArea *background;
|
||||
VipsArrayDouble *background;
|
||||
|
||||
/* The [double] converted to the input image format.
|
||||
*/
|
||||
@ -319,8 +319,9 @@ vips_flatten_build( VipsObject *object )
|
||||
/* Is the background black? We have a special path for this.
|
||||
*/
|
||||
black = TRUE;
|
||||
for( i = 0; i < flatten->background->n; i++ )
|
||||
if( ((double *) flatten->background->data)[i] != 0.0 ) {
|
||||
for( i = 0; i < VIPS_AREA( flatten->background )->n; i++ )
|
||||
if( vips_array_double_get( flatten->background, NULL )[i] !=
|
||||
0.0 ) {
|
||||
black = FALSE;
|
||||
break;
|
||||
}
|
||||
@ -336,8 +337,8 @@ vips_flatten_build( VipsObject *object )
|
||||
*/
|
||||
if( !(flatten->ink = vips__vector_to_ink( class->nickname,
|
||||
conversion->out,
|
||||
flatten->background->data, NULL,
|
||||
flatten->background->n )) )
|
||||
VIPS_AREA( flatten->background )->data, NULL,
|
||||
VIPS_AREA( flatten->background )->n )) )
|
||||
return( -1 );
|
||||
|
||||
if( vips_image_generate( conversion->out,
|
||||
@ -384,9 +385,7 @@ vips_flatten_class_init( VipsFlattenClass *class )
|
||||
static void
|
||||
vips_flatten_init( VipsFlatten *flatten )
|
||||
{
|
||||
flatten->background =
|
||||
vips_area_new_array( G_TYPE_DOUBLE, sizeof( double ), 1 );
|
||||
((double *) (flatten->background->data))[0] = 0.0;
|
||||
flatten->background = vips_array_double_newv( 1, 0.0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -397,7 +396,7 @@ vips_flatten_init( VipsFlatten *flatten )
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* @background: colour for new pixels
|
||||
* @background: #VipsArrayDouble colour for new pixels
|
||||
*
|
||||
* Take the last band of @in as an alpha and use it to blend the
|
||||
* remaining channels with @background.
|
||||
|
@ -128,10 +128,13 @@ vips_create_operation_init( void )
|
||||
extern GType vips_invertlut_get_type( void );
|
||||
extern GType vips_tonelut_get_type( void );
|
||||
extern GType vips_identity_get_type( void );
|
||||
extern GType vips_mask_butterworth_get_type( void );
|
||||
extern GType vips_mask_butterworth_ring_get_type( void );
|
||||
extern GType vips_mask_butterworth_band_get_type( void );
|
||||
extern GType vips_mask_gaussian_get_type( void );
|
||||
extern GType vips_mask_gaussian_ring_get_type( void );
|
||||
extern GType vips_mask_gaussian_band_get_type( void );
|
||||
extern GType vips_mask_ideal_get_type( void );
|
||||
extern GType vips_mask_ideal_ring_get_type( void );
|
||||
extern GType vips_mask_ideal_band_get_type( void );
|
||||
extern GType vips_mask_fractal_get_type( void );
|
||||
|
@ -1146,7 +1146,9 @@ vips_foreign_convert_saveable( VipsForeignSave *save )
|
||||
class->saveable == VIPS_SAVEABLE_RGB_CMYK) ) {
|
||||
VipsImage *out;
|
||||
|
||||
if( vips_flatten( in, &out, 0, NULL ) ) {
|
||||
if( vips_flatten( in, &out,
|
||||
"background", save->background,
|
||||
NULL ) ) {
|
||||
g_object_unref( in );
|
||||
return( -1 );
|
||||
}
|
||||
@ -1414,11 +1416,19 @@ vips_foreign_save_class_init( VipsForeignSaveClass *class )
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSave, strip ),
|
||||
FALSE );
|
||||
|
||||
VIPS_ARG_BOXED( class, "background", 101,
|
||||
_( "Background" ),
|
||||
_( "Background value" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSave, background ),
|
||||
VIPS_TYPE_ARRAY_DOUBLE );
|
||||
}
|
||||
|
||||
static void
|
||||
vips_foreign_save_init( VipsForeignSave *object )
|
||||
vips_foreign_save_init( VipsForeignSave *save )
|
||||
{
|
||||
save->background = vips_array_double_newv( 1, 0.0 );
|
||||
}
|
||||
|
||||
/* Can we write this filename with this file?
|
||||
|
@ -256,10 +256,15 @@ typedef enum {
|
||||
typedef struct _VipsForeignSave {
|
||||
VipsForeign parent_object;
|
||||
|
||||
/* Dont't attach metadata.
|
||||
/* Don't attach metadata.
|
||||
*/
|
||||
gboolean strip;
|
||||
|
||||
/* If flattening out alpha, the background colour to use. Default to
|
||||
* 0 (black).
|
||||
*/
|
||||
VipsArrayDouble *background;
|
||||
|
||||
/*< public >*/
|
||||
|
||||
/* The image we are to save, as supplied by our caller.
|
||||
|
319
python/test_arithmetic.py
Executable file
319
python/test_arithmetic.py
Executable file
@ -0,0 +1,319 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import unittest
|
||||
import gc
|
||||
|
||||
#import logging
|
||||
#logging.basicConfig(level = logging.DEBUG)
|
||||
|
||||
from gi.repository import Vips
|
||||
from vips8 import vips
|
||||
|
||||
unsigned_formats = [Vips.BandFormat.UCHAR,
|
||||
Vips.BandFormat.USHORT,
|
||||
Vips.BandFormat.UINT]
|
||||
signed_formats = [Vips.BandFormat.CHAR,
|
||||
Vips.BandFormat.SHORT,
|
||||
Vips.BandFormat.INT]
|
||||
float_formats = [Vips.BandFormat.FLOAT,
|
||||
Vips.BandFormat.DOUBLE]
|
||||
complex_formats = [Vips.BandFormat.COMPLEX,
|
||||
Vips.BandFormat.DPCOMPLEX]
|
||||
int_formats = unsigned_formats + signed_formats
|
||||
noncomplex_formats = int_formats + float_formats
|
||||
all_formats = int_formats + float_formats + complex_formats
|
||||
|
||||
# an expanding zip ... if either of the args is not a list, duplicate it down
|
||||
# the other
|
||||
def zip_expand(x, y):
|
||||
if isinstance(x, list) and isinstance(y, list):
|
||||
return zip(x, y)
|
||||
elif isinstance(x, list):
|
||||
return [[i, y] for i in x]
|
||||
elif isinstance(y, list):
|
||||
return [[x, j] for j in y]
|
||||
else:
|
||||
return [[x, y]]
|
||||
|
||||
# run a 1-ary function on a thing -- loop over elements if the
|
||||
# thing is a list
|
||||
def run_fn(fn, x):
|
||||
if isinstance(x, list):
|
||||
return [fn(i) for i in x]
|
||||
else:
|
||||
return fn(x)
|
||||
|
||||
# run a 2-ary function on two things -- loop over elements pairwise if the
|
||||
# things are lists
|
||||
def run_fn2(fn, x, y):
|
||||
if isinstance(x, Vips.Image) or isinstance(y, Vips.Image):
|
||||
return fn(x, y)
|
||||
elif isinstance(x, list) or isinstance(y, list):
|
||||
return [fn(i, j) for i, j in zip_expand(x, y)]
|
||||
else:
|
||||
return fn(x, y)
|
||||
|
||||
class TestArithmetic(unittest.TestCase):
|
||||
# test a pair of things which can be lists for approx. equality
|
||||
def assertAlmostEqualObjects(self, a, b, msg = ''):
|
||||
for x, y in zip_expand(a, b):
|
||||
#print 'assertAlmostEqual %s = %s' % (x, y)
|
||||
self.assertAlmostEqual(x, y, places = 4, msg = msg)
|
||||
|
||||
# run a function on an image and on a single pixel, the results
|
||||
# should match
|
||||
def run_cmp(self, message, im, x, y, fn):
|
||||
a = im.getpoint(x, y)
|
||||
v1 = fn(a)
|
||||
im2 = fn(im)
|
||||
v2 = im2.getpoint(x, y)
|
||||
self.assertAlmostEqualObjects(v1, v2, msg = message)
|
||||
|
||||
# run a function on (image, constant), and on (constant, image).
|
||||
# 50,50 and 10,10 should have different values on the test image
|
||||
def run_testconst(self, message, fn, im, c):
|
||||
self.run_cmp(message, im, 50, 50, lambda x: run_fn2(fn, x, c))
|
||||
self.run_cmp(message, im, 50, 50, lambda x: run_fn2(fn, c, x))
|
||||
self.run_cmp(message, im, 10, 10, lambda x: run_fn2(fn, x, c))
|
||||
self.run_cmp(message, im, 10, 10, lambda x: run_fn2(fn, c, x))
|
||||
|
||||
def run_arith_const(self, fn, fmt = all_formats):
|
||||
[self.run_testconst(fn.func_name + ' scalar', fn, x.cast(y), 2)
|
||||
for x in self.all_images for y in fmt]
|
||||
[self.run_testconst(fn.func_name + ' vector', fn, self.colour.cast(y),
|
||||
[1, 2, 3])
|
||||
for y in fmt]
|
||||
|
||||
# run a function on a pair of images and on a pair of pixels, the results
|
||||
# should match
|
||||
def run_cmp2(self, message, left, right, x, y, fn):
|
||||
a = left.getpoint(x, y)
|
||||
b = right.getpoint(x, y)
|
||||
v1 = fn(a, b)
|
||||
after = fn(left, right)
|
||||
v2 = after.getpoint(x, y)
|
||||
self.assertAlmostEqualObjects(v1, v2, msg = message)
|
||||
|
||||
# run a function on a pair of images
|
||||
# 50,50 and 10,10 should have different values on the test image
|
||||
def run_test2(self, message, left, right, fn):
|
||||
self.run_cmp2(message, left, right, 50, 50,
|
||||
lambda x, y: run_fn2(fn, x, y))
|
||||
self.run_cmp2(message, left, right, 10, 10,
|
||||
lambda x, y: run_fn2(fn, x, y))
|
||||
|
||||
def run_arith(self, fn, fmt = all_formats):
|
||||
[self.run_test2(fn.func_name + ' image', x.cast(y), x.cast(z), fn)
|
||||
for x in self.all_images for y in fmt for z in fmt]
|
||||
|
||||
def setUp(self):
|
||||
im = Vips.Image.mask_ideal(100, 100, 0.5)
|
||||
self.colour = im * [1, 2, 3] + [2, 3, 4]
|
||||
self.mono = self.colour.extract_band(1)
|
||||
self.all_images = [self.mono, self.colour]
|
||||
|
||||
# test all operator overloads we define
|
||||
|
||||
def test_add(self):
|
||||
def add(x, y):
|
||||
return x + y
|
||||
|
||||
self.run_arith_const(add)
|
||||
self.run_arith(add)
|
||||
|
||||
def test_sub(self):
|
||||
def sub(x, y):
|
||||
return x - y
|
||||
|
||||
self.run_arith_const(sub)
|
||||
self.run_arith(sub)
|
||||
|
||||
def test_mul(self):
|
||||
def mul(x, y):
|
||||
return x * y
|
||||
|
||||
self.run_arith_const(mul)
|
||||
self.run_arith(mul)
|
||||
|
||||
def test_div(self):
|
||||
def div(x, y):
|
||||
return x / y
|
||||
|
||||
# (const / image) needs (image ** -1), which won't work for complex
|
||||
self.run_arith_const(div, fmt = noncomplex_formats)
|
||||
self.run_arith(div)
|
||||
|
||||
def test_floordiv(self):
|
||||
def my_floordiv(x, y):
|
||||
return x // y
|
||||
|
||||
# (const // image) needs (image ** -1), which won't work for complex
|
||||
self.run_arith_const(my_floordiv, fmt = noncomplex_formats)
|
||||
self.run_arith(my_floordiv, fmt = noncomplex_formats)
|
||||
|
||||
def test_pow(self):
|
||||
def my_pow(x, y):
|
||||
return x ** y
|
||||
|
||||
# (image ** x) won't work for complex images ... just test non-complex
|
||||
self.run_arith_const(my_pow, fmt = noncomplex_formats)
|
||||
self.run_arith(my_pow, fmt = noncomplex_formats)
|
||||
|
||||
def test_and(self):
|
||||
def my_and(x, y):
|
||||
# python doesn't allow bools on float
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
if isinstance(y, float):
|
||||
y = int(y)
|
||||
return x & y
|
||||
|
||||
self.run_arith_const(my_and, fmt = noncomplex_formats)
|
||||
self.run_arith(my_and, fmt = noncomplex_formats)
|
||||
|
||||
def test_or(self):
|
||||
def my_or(x, y):
|
||||
# python doesn't allow bools on float
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
if isinstance(y, float):
|
||||
y = int(y)
|
||||
return x | y
|
||||
|
||||
self.run_arith_const(my_or, fmt = noncomplex_formats)
|
||||
self.run_arith(my_or, fmt = noncomplex_formats)
|
||||
|
||||
def test_xor(self):
|
||||
def my_xor(x, y):
|
||||
# python doesn't allow bools on float
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
if isinstance(y, float):
|
||||
y = int(y)
|
||||
return x ^ y
|
||||
|
||||
self.run_arith_const(my_xor, fmt = noncomplex_formats)
|
||||
self.run_arith(my_xor, fmt = noncomplex_formats)
|
||||
|
||||
# run a function on an image,
|
||||
# 50,50 and 10,10 should have different values on the test image
|
||||
def run_testunary(self, message, im, fn):
|
||||
self.run_cmp(message, im, 50, 50, lambda x: run_fn(fn, x))
|
||||
self.run_cmp(message, im, 10, 10, lambda x: run_fn(fn, x))
|
||||
|
||||
def run_unary(self, images, fn, fmt = all_formats):
|
||||
[self.run_testunary(fn.func_name + ' image', x.cast(y), fn)
|
||||
for x in images for y in fmt]
|
||||
|
||||
def test_abs(self):
|
||||
def my_abs(x):
|
||||
return abs(x)
|
||||
|
||||
im = -self.colour
|
||||
self.run_unary([im], my_abs)
|
||||
|
||||
def test_lshift(self):
|
||||
def my_lshift(x):
|
||||
# python doesn't allow float << int
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
return x << 2
|
||||
|
||||
# we don't support constant << image, treat as a unary
|
||||
self.run_unary(self.all_images, my_lshift, fmt = noncomplex_formats)
|
||||
|
||||
def test_rshift(self):
|
||||
def my_rshift(x):
|
||||
# python doesn't allow float >> int
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
return x >> 2
|
||||
|
||||
# we don't support constant >> image, treat as a unary
|
||||
self.run_unary(self.all_images, my_rshift, fmt = noncomplex_formats)
|
||||
|
||||
def test_mod(self):
|
||||
def my_mod(x):
|
||||
return x % 2
|
||||
|
||||
# we don't support constant % image, treat as a unary
|
||||
self.run_unary(self.all_images, my_mod, fmt = noncomplex_formats)
|
||||
|
||||
def test_pos(self):
|
||||
def my_pos(x):
|
||||
return +x
|
||||
|
||||
self.run_unary(self.all_images, my_pos)
|
||||
|
||||
def test_neg(self):
|
||||
def my_neg(x):
|
||||
return -x
|
||||
|
||||
self.run_unary(self.all_images, my_neg)
|
||||
|
||||
def test_invert(self):
|
||||
def my_invert(x):
|
||||
if isinstance(x, float):
|
||||
x = int(x)
|
||||
return ~x & 0xff
|
||||
|
||||
# ~image is trimmed to image max so it's hard to test for all formats
|
||||
# just test uchar
|
||||
self.run_unary(self.all_images, my_invert,
|
||||
fmt = [Vips.BandFormat.UCHAR])
|
||||
|
||||
# test the rest of VipsArithmetic
|
||||
|
||||
def test_avg(self):
|
||||
im = Vips.Image.black(50, 100)
|
||||
test = im.insert(im + 100, 50, 0, expand = True)
|
||||
|
||||
for fmt in all_formats:
|
||||
self.assertAlmostEqual(test.cast(fmt).avg(), 50)
|
||||
|
||||
def test_deviate(self):
|
||||
im = Vips.Image.black(50, 100)
|
||||
test = im.insert(im + 100, 50, 0, expand = True)
|
||||
|
||||
for fmt in noncomplex_formats:
|
||||
self.assertAlmostEqual(test.cast(fmt).deviate(), 50, places = 2)
|
||||
|
||||
def test_polar(self):
|
||||
im = Vips.Image.black(100, 100) + 100
|
||||
im = im.complexform(im)
|
||||
|
||||
im = im.polar()
|
||||
|
||||
self.assertAlmostEqual(im.real().avg(), 100 * 2 ** 0.5)
|
||||
self.assertAlmostEqual(im.imag().avg(), 45)
|
||||
|
||||
def test_rect(self):
|
||||
im = Vips.Image.black(100, 100)
|
||||
im = (im + 100 * 2 ** 0.5).complexform(im + 45)
|
||||
|
||||
im = im.rect()
|
||||
|
||||
self.assertAlmostEqual(im.real().avg(), 100)
|
||||
self.assertAlmostEqual(im.imag().avg(), 100)
|
||||
|
||||
def test_conjugate(self):
|
||||
im = Vips.Image.black(100, 100) + 100
|
||||
im = im.complexform(im)
|
||||
|
||||
im = im.conj()
|
||||
|
||||
self.assertAlmostEqual(im.real().avg(), 100)
|
||||
self.assertAlmostEqual(im.imag().avg(), -100)
|
||||
|
||||
def test_histfind(self):
|
||||
im = Vips.Image.black(50, 100)
|
||||
test = im.insert(im + 100, 50, 0, expand = True)
|
||||
|
||||
for fmt in all_formats:
|
||||
hist = test.cast(fmt).hist_find()
|
||||
self.assertAlmostEqualObjects(hist.getpoint(0,0), [5000])
|
||||
self.assertAlmostEqualObjects(hist.getpoint(100,0), [5000])
|
||||
self.assertAlmostEqualObjects(hist.getpoint(12,0), [0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@ -10,8 +10,27 @@ from vips8 import vips
|
||||
|
||||
a = Vips.Image.new_from_file(sys.argv[1])
|
||||
|
||||
def should_equal(test, a, b):
|
||||
if abs(a - b) > 0.01:
|
||||
print '%s: seen %g and %g' % (test, a, b)
|
||||
sys.exit(1)
|
||||
|
||||
def bandsplit(a):
|
||||
return [a.extract_band(i) for i in range(0, a.bands)]
|
||||
|
||||
# test operator overloads
|
||||
|
||||
# addition
|
||||
b = a + 12
|
||||
should_equal('add constant', a.avg() + 12, b.avg())
|
||||
|
||||
b = a + [12, 0, 0]
|
||||
x = map (lambda x: x.avg()) bandsplit(a)
|
||||
y = map (lambda x: x.avg()) bandsplit(b)
|
||||
x[0] += 12
|
||||
should_equal('add multiband constant', sum(x), sum(y))
|
||||
|
||||
|
||||
b = a + [12, 0, 0]
|
||||
b = a + b
|
||||
b = 12 + a
|
||||
|
@ -21,6 +21,13 @@ vips_type_blob = GObject.GType.from_name("VipsBlob")
|
||||
vips_type_image = GObject.GType.from_name("VipsImage")
|
||||
vips_type_operation = GObject.GType.from_name("VipsOperation")
|
||||
|
||||
unpack_types = [Vips.Blob, Vips.ArrayDouble, Vips.ArrayImage, Vips.ArrayInt]
|
||||
def isunpack(obj):
|
||||
for t in unpack_types:
|
||||
if isinstance(obj, t):
|
||||
return True
|
||||
return False
|
||||
|
||||
class Error(Exception):
|
||||
|
||||
"""An error from vips.
|
||||
@ -81,10 +88,10 @@ class Argument:
|
||||
|
||||
logging.debug('read out %s from %s' % (value, self.name))
|
||||
|
||||
# turn VipsBlobs into strings
|
||||
# turn VipsBlobs into strings, VipsArrayDouble into lists etc.
|
||||
# FIXME ... this will involve a copy, we should use
|
||||
# buffer() instead
|
||||
if isinstance(value, Vips.Blob):
|
||||
if isunpack(value):
|
||||
value = value.get()
|
||||
|
||||
return value
|
||||
@ -185,6 +192,8 @@ def _call_base(name, required, optional, self = None, option_string = None):
|
||||
|
||||
if len(out) == 1:
|
||||
out = out[0]
|
||||
elif len(out) == 0:
|
||||
out = None
|
||||
|
||||
# unref everything now we have refs to all outputs we want
|
||||
op2.unref_outputs()
|
||||
@ -246,10 +255,6 @@ def vips_image_new_from_array(cls, array, scale = 1, offset = 0):
|
||||
|
||||
return image
|
||||
|
||||
# this is a class method
|
||||
def vips_black(cls, width, height, **kwargs):
|
||||
return _call_base("black", [width, height], kwargs)
|
||||
|
||||
def vips_image_getattr(self, name):
|
||||
logging.debug('Image.__getattr__ %s' % name)
|
||||
|
||||
@ -283,6 +288,9 @@ def vips_image_write_to_buffer(self, vips_filename, **kwargs):
|
||||
|
||||
return _call_base(saver, [], kwargs, self, option_string)
|
||||
|
||||
def vips_bandsplit(self):
|
||||
return [self.extract_band(i) for i in range(0, self.bands)]
|
||||
|
||||
# apply a function to a thing, or map over a list
|
||||
# we often need to do something like (1.0 / other) and need to work for lists
|
||||
# as well as scalars
|
||||
@ -323,7 +331,7 @@ def vips_rdiv(self, other):
|
||||
return (self ** -1) * other
|
||||
|
||||
def vips_floor(self):
|
||||
self.round(Vips.OperationRound.FLOOR)
|
||||
return self.round(Vips.OperationRound.FLOOR)
|
||||
|
||||
def vips_floordiv(self, other):
|
||||
if isinstance(other, Vips.Image):
|
||||
@ -391,6 +399,21 @@ def vips_abs(self):
|
||||
def vips_invert(self):
|
||||
return self ^ -1
|
||||
|
||||
def vips_real(self):
|
||||
return self.complexget(Vips.OperationComplexget.REAL)
|
||||
|
||||
def vips_imag(self):
|
||||
return self.complexget(Vips.OperationComplexget.IMAG)
|
||||
|
||||
def vips_polar(self):
|
||||
return self.complex(Vips.OperationComplex.POLAR)
|
||||
|
||||
def vips_rect(self):
|
||||
return self.complex(Vips.OperationComplex.RECT)
|
||||
|
||||
def vips_conj(self):
|
||||
return self.complex(Vips.OperationComplex.CONJ)
|
||||
|
||||
# paste our methods into Vips.Image
|
||||
|
||||
# class methods
|
||||
@ -398,9 +421,6 @@ setattr(Vips.Image, 'new_from_file', classmethod(vips_image_new_from_file))
|
||||
setattr(Vips.Image, 'new_from_buffer', classmethod(vips_image_new_from_buffer))
|
||||
setattr(Vips.Image, 'new_from_array', classmethod(vips_image_new_from_array))
|
||||
|
||||
# yuk, we should run these via a metaclass somehow
|
||||
#setattr(Vips.Image, 'black', classmethod(vips_black))
|
||||
|
||||
# Search for all VipsOperation which don't have an input image object ... these
|
||||
# become class methods
|
||||
|
||||
@ -413,13 +433,9 @@ def vips_image_class_method(name, args, kwargs):
|
||||
return _call_base(name, args, kwargs)
|
||||
|
||||
def define_class_methods(cls):
|
||||
if len(cls.children) > 0:
|
||||
for child in cls.children:
|
||||
# not easy to get at the deprecated flag in an abtract type?
|
||||
if cls.name != 'VipsWrap7':
|
||||
define_class_methods(child)
|
||||
elif cls.is_instantiatable():
|
||||
if not cls.is_abstract():
|
||||
op = Vips.Operation.new(cls.name)
|
||||
|
||||
found = False
|
||||
for prop in op.props:
|
||||
flags = op.get_argument_flags(prop.name)
|
||||
@ -435,6 +451,12 @@ def define_class_methods(cls):
|
||||
method = lambda *args, **kwargs: vips_image_class_method( nickname, args, kwargs)
|
||||
setattr(Vips.Image, nickname, classmethod(method))
|
||||
|
||||
if len(cls.children) > 0:
|
||||
for child in cls.children:
|
||||
# not easy to get at the deprecated flag in an abtract type?
|
||||
if cls.name != 'VipsWrap7':
|
||||
define_class_methods(child)
|
||||
|
||||
define_class_methods(vips_type_operation)
|
||||
|
||||
# instance methods
|
||||
@ -442,8 +464,16 @@ Vips.Image.write_to_file = vips_image_write_to_file
|
||||
Vips.Image.write_to_buffer = vips_image_write_to_buffer
|
||||
# we can use Vips.Image.write_to_memory() directly
|
||||
|
||||
# a few useful things
|
||||
Vips.Image.floor = vips_floor
|
||||
Vips.Image.bandsplit = vips_bandsplit
|
||||
Vips.Image.real = vips_real
|
||||
Vips.Image.imag = vips_imag
|
||||
Vips.Image.polar = vips_polar
|
||||
Vips.Image.rect = vips_rect
|
||||
Vips.Image.conj = vips_conj
|
||||
|
||||
# operator overloads
|
||||
Vips.Image.__getattr__ = vips_image_getattr
|
||||
Vips.Image.__add__ = vips_add
|
||||
Vips.Image.__radd__ = vips_add
|
||||
@ -454,7 +484,7 @@ Vips.Image.__rmul__ = vips_mul
|
||||
Vips.Image.__div__ = vips_div
|
||||
Vips.Image.__rdiv__ = vips_rdiv
|
||||
Vips.Image.__floordiv__ = vips_floordiv
|
||||
Vips.Image.__rfloordiv__ = vips_floordiv
|
||||
Vips.Image.__rfloordiv__ = vips_rfloordiv
|
||||
Vips.Image.__mod__ = vips_mod
|
||||
Vips.Image.__pow__ = vips_pow
|
||||
Vips.Image.__rpow__ = vips_rpow
|
||||
|
@ -58,6 +58,9 @@
|
||||
* - add an anti-alias filter between shrink and affine
|
||||
* - support CMYK
|
||||
* - use SEQ_UNBUF for a memory saving
|
||||
* 12/9/14
|
||||
* - try with embedded profile first, if that fails retry with fallback
|
||||
* profile
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
@ -397,6 +400,11 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
||||
VipsInterpretation interpretation = linear_processing ?
|
||||
VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB;
|
||||
|
||||
/* TRUE if we've done the import of an ICC transform and still need to
|
||||
* export.
|
||||
*/
|
||||
gboolean have_imported;
|
||||
|
||||
int shrink;
|
||||
double residual;
|
||||
int tile_width;
|
||||
@ -425,6 +433,7 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
||||
* an image in PCS which also has an attached profile, strange things
|
||||
* will happen.
|
||||
*/
|
||||
have_imported = FALSE;
|
||||
if( (linear_processing ||
|
||||
in->Type == VIPS_INTERPRETATION_CMYK) &&
|
||||
in->Coding == VIPS_CODING_NONE &&
|
||||
@ -447,6 +456,8 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
||||
return( NULL );
|
||||
|
||||
in = t[1];
|
||||
|
||||
have_imported = TRUE;
|
||||
}
|
||||
|
||||
/* To the processing colourspace. This will unpack LABQ as well.
|
||||
@ -533,10 +544,11 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
||||
|
||||
/* Colour management.
|
||||
*
|
||||
* In linear mode, just export. In device space mode, do a combined
|
||||
* If we've already imported, just export. Otherwise, we're in
|
||||
* device space device and we need a combined
|
||||
* import/export to transform to the target space.
|
||||
*/
|
||||
if( linear_processing ) {
|
||||
if( have_imported ) {
|
||||
if( export_profile ||
|
||||
vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
|
||||
vips_info( "vipsthumbnail",
|
||||
@ -558,23 +570,48 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
||||
else if( export_profile &&
|
||||
(vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ||
|
||||
import_profile) ) {
|
||||
if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) )
|
||||
vips_info( "vipsthumbnail",
|
||||
"importing with embedded profile" );
|
||||
else
|
||||
vips_info( "vipsthumbnail",
|
||||
"importing with profile %s", import_profile );
|
||||
VipsImage *out;
|
||||
|
||||
vips_info( "vipsthumbnail",
|
||||
"exporting with profile %s", export_profile );
|
||||
|
||||
/* We first try with the embedded profile, if any, then if
|
||||
* that fails try again with the supplied fallback profile.
|
||||
*/
|
||||
out = NULL;
|
||||
if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
|
||||
vips_info( "vipsthumbnail",
|
||||
"importing with embedded profile" );
|
||||
|
||||
if( vips_icc_transform( in, &t[7], export_profile,
|
||||
"embedded", TRUE,
|
||||
NULL ) ) {
|
||||
vips_warn( "vipsthumbnail",
|
||||
_( "unable to import with "
|
||||
"embedded profile: %s" ),
|
||||
vips_error_buffer() );
|
||||
|
||||
vips_error_clear();
|
||||
}
|
||||
else
|
||||
out = t[7];
|
||||
}
|
||||
|
||||
if( !out &&
|
||||
import_profile ) {
|
||||
vips_info( "vipsthumbnail",
|
||||
"importing with fallback profile" );
|
||||
|
||||
if( vips_icc_transform( in, &t[7], export_profile,
|
||||
"input_profile", import_profile,
|
||||
"embedded", TRUE,
|
||||
"embedded", FALSE,
|
||||
NULL ) )
|
||||
return( NULL );
|
||||
|
||||
in = t[7];
|
||||
out = t[7];
|
||||
}
|
||||
|
||||
in = out;
|
||||
}
|
||||
|
||||
/* If we are upsampling, don't sharpen, since nearest looks dumb
|
||||
|
Loading…
Reference in New Issue
Block a user