Merge branch 'master' of github.com:jcupitt/libvips

This commit is contained in:
John Cupitt 2014-09-15 11:30:37 +01:00
commit e09aa1d4be
15 changed files with 494 additions and 82 deletions

View File

@ -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
View File

@ -1,6 +1,12 @@
- python:
- try
python test_arithmetic.py -v TestArithmetic.test_histfind
segv
- could import like this:
from gi.repository import Vips

View File

@ -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"

View File

@ -380,6 +380,3 @@
fun:rb_enc_str_new
}

View File

@ -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; \
} \
}

View File

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

View File

@ -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 );
}
/**

View File

@ -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.

View File

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

View File

@ -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?

View 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
View 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()

View File

@ -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

View File

@ -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

View File

@ -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