diff --git a/ChangeLog b/ChangeLog index 8c54013c..210d03bc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -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] diff --git a/TODO b/TODO index 95787836..edc4b583 100644 --- a/TODO +++ b/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 diff --git a/configure.ac b/configure.ac index bd861ec9..4bc53358 100644 --- a/configure.ac +++ b/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" diff --git a/libvips.supp b/libvips.supp index 1e6c6ab1..aefca16d 100644 --- a/libvips.supp +++ b/libvips.supp @@ -380,6 +380,3 @@ fun:rb_enc_str_new } - - - diff --git a/libvips/arithmetic/avg.c b/libvips/arithmetic/avg.c index ca49214f..f2bdcf8b 100644 --- a/libvips/arithmetic/avg.c +++ b/libvips/arithmetic/avg.c @@ -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; \ } \ } diff --git a/libvips/arithmetic/getpoint.c b/libvips/arithmetic/getpoint.c index b3a55303..4604cb40 100644 --- a/libvips/arithmetic/getpoint.c +++ b/libvips/arithmetic/getpoint.c @@ -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 ); } diff --git a/libvips/conversion/embed.c b/libvips/conversion/embed.c index 8a5e9a8b..ac5ad8a4 100644 --- a/libvips/conversion/embed.c +++ b/libvips/conversion/embed.c @@ -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 ); } /** diff --git a/libvips/conversion/flatten.c b/libvips/conversion/flatten.c index f521f49c..df89a246 100644 --- a/libvips/conversion/flatten.c +++ b/libvips/conversion/flatten.c @@ -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. diff --git a/libvips/create/create.c b/libvips/create/create.c index 52ab9cb8..77ef0ccc 100644 --- a/libvips/create/create.c +++ b/libvips/create/create.c @@ -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 ); diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index f838ea47..e0a80667 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -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? diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index bc810423..ed096075 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -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. diff --git a/python/test_arithmetic.py b/python/test_arithmetic.py new file mode 100755 index 00000000..4d561f4f --- /dev/null +++ b/python/test_arithmetic.py @@ -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() diff --git a/python/try5.py b/python/try5.py index 0421160e..a17706cf 100755 --- a/python/try5.py +++ b/python/try5.py @@ -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 diff --git a/python/vips8/vips.py b/python/vips8/vips.py index 945079c2..b1b8de4e 100644 --- a/python/vips8/vips.py +++ b/python/vips8/vips.py @@ -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 diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index a946bc0e..6ebb8e99 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -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 ); - if( vips_icc_transform( in, &t[7], export_profile, - "input_profile", import_profile, - "embedded", TRUE, - NULL ) ) - return( NULL ); + /* 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" ); - in = t[7]; + 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", FALSE, + NULL ) ) + return( NULL ); + + out = t[7]; + } + + in = out; } /* If we are upsampling, don't sharpen, since nearest looks dumb