diff --git a/ChangeLog b/ChangeLog index 98b7cff2..f114bb1a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ 7/5/15 started 8.1.0 +- add vips_premultiply(), vips_unpremultiply() +- change the alpha range rules for vips_flatten() to match vips_premultiply() +- vipsthumbnail uses vips_resize() rather than its own code +- vipsthumbnail uses vips_premultiply() for better alpha quality 4/5/15 started 8.0.2 - fix a refcount error in C++ wrapper, thanks huskier diff --git a/TODO b/TODO index 918a4e5d..11faea82 100644 --- a/TODO +++ b/TODO @@ -1,39 +1,3 @@ -- how odd, why is resize much faster? - - $ time vips resize wtc.tif small.tif 0.25 - memory: high-water mark 138.09 MB - real 0m0.430s - user 0m2.700s - sys 0m0.172s - $ time vipsthumbnail wtc.tif -o small.tif -s 2500 - memory: high-water mark 210.05 MB - real 0m0.959s - user 0m9.984s - sys 0m0.184s - - about the same speed for jpg - - $ time vips resize big.jpg[shrink=8] small.jpg 0.6666 - memory: high-water mark 47.21 MB - real 0m1.320s - user 0m2.216s - sys 0m0.060s - $ time vipsthumbnail big.jpg -s 2500 -o small.jpg - memory: high-water mark 58.65 MB - real 0m1.360s - user 0m2.392s - sys 0m0.100s - - seems to be because vipsthumbnail is not int shrinking enough: - - $ vipsthumbnail wtc.tif -o small.tif -s 2500 --vips-info - info: vipsthumbnail: integer shrink by 1 - info: vipsthumbnail: residual scale by 0.266752 - - should int shrink by 2! vips resize is doing this right, what's the - difference? - - - are the mosaic functions calling vips_fastcor()? it must be very slow add vips_fastcor_direct() diff --git a/libvips/conversion/Makefile.am b/libvips/conversion/Makefile.am index d28f6a7d..b3d5575b 100644 --- a/libvips/conversion/Makefile.am +++ b/libvips/conversion/Makefile.am @@ -7,6 +7,8 @@ libconversion_la_SOURCES = \ gamma.c \ sequential.c \ flatten.c \ + premultiply.c \ + unpremultiply.c \ cache.c \ copy.c \ embed.c \ diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 90c72cba..4892c08f 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -238,6 +238,8 @@ vips_conversion_operation_init( void ) extern GType vips_recomb_get_type( void ); extern GType vips_bandmean_get_type( void ); extern GType vips_flatten_get_type( void ); + extern GType vips_premultiply_get_type( void ); + extern GType vips_unpremultiply_get_type( void ); extern GType vips_bandbool_get_type( void ); extern GType vips_gaussnoise_get_type( void ); extern GType vips_grid_get_type( void ); @@ -277,6 +279,8 @@ vips_conversion_operation_init( void ) vips_recomb_get_type(); vips_bandmean_get_type(); vips_flatten_get_type(); + vips_premultiply_get_type(); + vips_unpremultiply_get_type(); vips_bandbool_get_type(); vips_gaussnoise_get_type(); vips_grid_get_type(); diff --git a/libvips/conversion/flatten.c b/libvips/conversion/flatten.c index ae971c4d..f05949e9 100644 --- a/libvips/conversion/flatten.c +++ b/libvips/conversion/flatten.c @@ -5,6 +5,8 @@ * * 4/1/14 * - better rounding + * 9/5/15 + * - add max_alpha to match vips_premultiply() etc. */ /* @@ -67,6 +69,10 @@ typedef struct _VipsFlatten { */ VipsPel *ink; + /* Use this to scale alpha to 0 - 1. + */ + double max_alpha; + } VipsFlatten; typedef VipsConversionClass VipsFlattenClass; @@ -75,7 +81,7 @@ G_DEFINE_TYPE( VipsFlatten, vips_flatten, VIPS_TYPE_CONVERSION ); /* Flatten with black background. */ -#define VIPS_FLATTEN_BLACK( TYPE, MAX ) { \ +#define VIPS_FLATTEN_BLACK( TYPE ) { \ TYPE * restrict p = (TYPE *) in; \ TYPE * restrict q = (TYPE *) out; \ \ @@ -84,7 +90,7 @@ G_DEFINE_TYPE( VipsFlatten, vips_flatten, VIPS_TYPE_CONVERSION ); int b; \ \ for( b = 0; b < bands - 1; b++ ) \ - q[b] = (p[b] * alpha) / (MAX); \ + q[b] = (p[b] * alpha) / max_alpha; \ \ p += bands; \ q += bands - 1; \ @@ -93,19 +99,19 @@ G_DEFINE_TYPE( VipsFlatten, vips_flatten, VIPS_TYPE_CONVERSION ); /* Flatten with any background. */ -#define VIPS_FLATTEN( TYPE, MAX ) { \ +#define VIPS_FLATTEN( TYPE ) { \ TYPE * restrict p = (TYPE *) in; \ TYPE * restrict q = (TYPE *) out; \ \ for( x = 0; x < width; x++ ) { \ TYPE alpha = p[bands - 1]; \ - TYPE nalpha = (MAX) - alpha; \ + TYPE nalpha = max_alpha - alpha; \ TYPE * restrict bg = (TYPE *) flatten->ink; \ int b; \ \ for( b = 0; b < bands - 1; b++ ) \ - q[b] = (p[b] * alpha) / (MAX) + \ - (bg[b] * nalpha) / (MAX); \ + q[b] = (p[b] * alpha) / max_alpha + \ + (bg[b] * nalpha) / max_alpha; \ \ p += bands; \ q += bands - 1; \ @@ -115,7 +121,7 @@ G_DEFINE_TYPE( VipsFlatten, vips_flatten, VIPS_TYPE_CONVERSION ); /* Same, but with float arithmetic. Necessary for int/uint to prevent * overflow. */ -#define VIPS_FLATTEN_BLACK_FLOAT( TYPE, MAX ) { \ +#define VIPS_FLATTEN_BLACK_FLOAT( TYPE ) { \ TYPE * restrict p = (TYPE *) in; \ TYPE * restrict q = (TYPE *) out; \ \ @@ -124,26 +130,26 @@ G_DEFINE_TYPE( VipsFlatten, vips_flatten, VIPS_TYPE_CONVERSION ); int b; \ \ for( b = 0; b < bands - 1; b++ ) \ - q[b] = ((double) p[b] * alpha) / (MAX); \ + q[b] = ((double) p[b] * alpha) / max_alpha; \ \ p += bands; \ q += bands - 1; \ } \ } -#define VIPS_FLATTEN_FLOAT( TYPE, MAX ) { \ +#define VIPS_FLATTEN_FLOAT( TYPE ) { \ TYPE * restrict p = (TYPE *) in; \ TYPE * restrict q = (TYPE *) out; \ \ for( x = 0; x < width; x++ ) { \ TYPE alpha = p[bands - 1]; \ - TYPE nalpha = (MAX) - alpha; \ + TYPE nalpha = max_alpha - alpha; \ TYPE * restrict bg = (TYPE *) flatten->ink; \ int b; \ \ for( b = 0; b < bands - 1; b++ ) \ - q[b] = ((double) p[b] * alpha) / (MAX) + \ - ((double) bg[b] * nalpha) / (MAX); \ + q[b] = ((double) p[b] * alpha) / max_alpha + \ + ((double) bg[b] * nalpha) / max_alpha; \ \ p += bands; \ q += bands - 1; \ @@ -159,6 +165,7 @@ vips_flatten_black_gen( VipsRegion *or, void *vseq, void *a, void *b, VipsRect *r = &or->valid; int width = r->width; int bands = ir->im->Bands; + double max_alpha = flatten->max_alpha; int x, y; @@ -171,37 +178,35 @@ vips_flatten_black_gen( VipsRegion *or, void *vseq, void *a, void *b, switch( flatten->in->BandFmt ) { case VIPS_FORMAT_UCHAR: - VIPS_FLATTEN_BLACK( unsigned char, UCHAR_MAX ); + VIPS_FLATTEN_BLACK( unsigned char ); break; case VIPS_FORMAT_CHAR: - /* Alpha is 0 - 127? No idea, really. - */ - VIPS_FLATTEN_BLACK( signed char, CHAR_MAX ); + VIPS_FLATTEN_BLACK( signed char ); break; case VIPS_FORMAT_USHORT: - VIPS_FLATTEN_BLACK( unsigned short, USHRT_MAX ); + VIPS_FLATTEN_BLACK( unsigned short ); break; case VIPS_FORMAT_SHORT: - VIPS_FLATTEN_BLACK( signed short, SHRT_MAX ); + VIPS_FLATTEN_BLACK( signed short ); break; case VIPS_FORMAT_UINT: - VIPS_FLATTEN_BLACK_FLOAT( unsigned int, UINT_MAX ); + VIPS_FLATTEN_BLACK_FLOAT( unsigned int ); break; case VIPS_FORMAT_INT: - VIPS_FLATTEN_BLACK_FLOAT( signed int, INT_MAX ); + VIPS_FLATTEN_BLACK_FLOAT( signed int ); break; case VIPS_FORMAT_FLOAT: - VIPS_FLATTEN_BLACK_FLOAT( float, 1.0 ); + VIPS_FLATTEN_BLACK_FLOAT( float ); break; case VIPS_FORMAT_DOUBLE: - VIPS_FLATTEN_BLACK_FLOAT( double, 1.0 ); + VIPS_FLATTEN_BLACK_FLOAT( double ); break; case VIPS_FORMAT_COMPLEX: @@ -225,6 +230,7 @@ vips_flatten_gen( VipsRegion *or, void *vseq, void *a, void *b, VipsRect *r = &or->valid; int width = r->width; int bands = ir->im->Bands; + double max_alpha = flatten->max_alpha; int x, y; @@ -237,37 +243,35 @@ vips_flatten_gen( VipsRegion *or, void *vseq, void *a, void *b, switch( flatten->in->BandFmt ) { case VIPS_FORMAT_UCHAR: - VIPS_FLATTEN( unsigned char, UCHAR_MAX ); + VIPS_FLATTEN( unsigned char ); break; case VIPS_FORMAT_CHAR: - /* Alpha is 0 - 127? No idea, really. - */ - VIPS_FLATTEN( signed char, CHAR_MAX ); + VIPS_FLATTEN( signed char ); break; case VIPS_FORMAT_USHORT: - VIPS_FLATTEN( unsigned short, USHRT_MAX ); + VIPS_FLATTEN( unsigned short ); break; case VIPS_FORMAT_SHORT: - VIPS_FLATTEN( signed short, SHRT_MAX ); + VIPS_FLATTEN( signed short ); break; case VIPS_FORMAT_UINT: - VIPS_FLATTEN_FLOAT( unsigned int, UINT_MAX ); + VIPS_FLATTEN_FLOAT( unsigned int ); break; case VIPS_FORMAT_INT: - VIPS_FLATTEN_FLOAT( signed int, INT_MAX ); + VIPS_FLATTEN_FLOAT( signed int ); break; case VIPS_FORMAT_FLOAT: - VIPS_FLATTEN_FLOAT( float, 1.0 ); + VIPS_FLATTEN_FLOAT( float ); break; case VIPS_FORMAT_DOUBLE: - VIPS_FLATTEN_FLOAT( double, 1.0 ); + VIPS_FLATTEN_FLOAT( double ); break; case VIPS_FORMAT_COMPLEX: @@ -304,7 +308,7 @@ vips_flatten_build( VipsObject *object ) /* Trivial case: fall back to copy(). */ - if( flatten->in->Bands == 1 ) + if( in->Bands == 1 ) return( vips_image_write( in, conversion->out ) ); if( vips_check_noncomplex( class->nickname, in ) ) @@ -380,12 +384,21 @@ vips_flatten_class_init( VipsFlattenClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsFlatten, background ), VIPS_TYPE_ARRAY_DOUBLE ); + + VIPS_ARG_DOUBLE( class, "max_alpha", 115, + _( "Maximum alpha" ), + _( "Maximum value of alpha channel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsFlatten, max_alpha ), + 0, 100000000, 255 ); + } static void vips_flatten_init( VipsFlatten *flatten ) { flatten->background = vips_array_double_newv( 1, 0.0 ); + flatten->max_alpha= 255.0; } /** @@ -397,19 +410,24 @@ vips_flatten_init( VipsFlatten *flatten ) * Optional arguments: * * @background: #VipsArrayDouble colour for new pixels + * @max_alpha: %gdouble, maximum value for alpha * * Take the last band of @in as an alpha and use it to blend the * remaining channels with @background. * - * The alpha channel is 0 - MAX for - * integer images and 0 - 1 for float images, where MAX means 100% image and 0 - * means 100% background. Non-complex images only. - * @background defaults to zero (black). MAX is the largest possible positive - * value for that int type. + * The alpha channel is 0 - @max_alpha, + * where 1 means 100% image and 0 + * means 100% background. + * Non-complex images only. + * @background defaults to zero (black). + * + * @max_alpha has the default value 255. You will need to set this to 65535 + * for images with a 16-bit alpha, or perhaps 1.0 for images with a float + * alpha. * * Useful for flattening PNG images to RGB. * - * See also: pngload(). + * See also: vips_premultiply(), vips_pngload(). * * Returns: 0 on success, -1 on error */ diff --git a/libvips/conversion/premultiply.c b/libvips/conversion/premultiply.c new file mode 100644 index 00000000..e1e5ebe6 --- /dev/null +++ b/libvips/conversion/premultiply.c @@ -0,0 +1,316 @@ +/* premultiply alpha + * + * Author: John Cupitt + * Written on: 7/5/15 + * + */ + +/* + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "pconversion.h" + +typedef struct _VipsPremultiply { + VipsConversion parent_instance; + + VipsImage *in; + + double max_alpha; + +} VipsPremultiply; + +typedef VipsConversionClass VipsPremultiplyClass; + +G_DEFINE_TYPE( VipsPremultiply, vips_premultiply, VIPS_TYPE_CONVERSION ); + +/* Premultiply an n-band image. + */ +#define PRE_MANY( IN, OUT ) { \ + IN * restrict p = (IN *) in; \ + OUT * restrict q = (OUT *) out; \ + \ + for( x = 0; x < width; x++ ) { \ + IN alpha = p[bands - 1]; \ + IN clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ + double nalpha = (double) clip_alpha / max_alpha; \ + \ + for( i = 0; i < bands - 1; i++ ) \ + q[i] = p[i] * nalpha; \ + q[i] = alpha; \ + \ + p += bands; \ + q += bands; \ + } \ +} + +/* Special case for RGBA, it's very common. + */ +#define PRE_RGB( IN, OUT ) { \ + IN * restrict p = (IN *) in; \ + OUT * restrict q = (OUT *) out; \ + \ + for( x = 0; x < width; x++ ) { \ + IN alpha = p[3]; \ + IN clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ + double nalpha = (double) clip_alpha / max_alpha; \ + \ + q[0] = p[0] * nalpha; \ + q[1] = p[1] * nalpha; \ + q[2] = p[2] * nalpha; \ + q[3] = alpha; \ + \ + p += 4; \ + q += 4; \ + } \ +} + +#define PRE( IN, OUT ) { \ + if( bands == 3 ) { \ + PRE_RGB( IN, OUT ); \ + } \ + else { \ + PRE_MANY( IN, OUT ); \ + } \ +} + +static int +vips_premultiply_gen( VipsRegion *or, void *vseq, void *a, void *b, + gboolean *stop ) +{ + VipsPremultiply *premultiply = (VipsPremultiply *) b; + VipsRegion *ir = (VipsRegion *) vseq; + VipsImage *im = ir->im; + VipsRect *r = &or->valid; + int width = r->width; + int bands = im->Bands; + double max_alpha = premultiply->max_alpha; + + int x, y, i; + + if( vips_region_prepare( ir, r ) ) + return( -1 ); + + for( y = 0; y < r->height; y++ ) { + VipsPel *in = VIPS_REGION_ADDR( ir, r->left, r->top + y ); + VipsPel *out = VIPS_REGION_ADDR( or, r->left, r->top + y ); + + switch( im->BandFmt ) { + case VIPS_FORMAT_UCHAR: + PRE( unsigned char, float ); + break; + + case VIPS_FORMAT_CHAR: + PRE( signed char, float ); + break; + + case VIPS_FORMAT_USHORT: + PRE( unsigned short, float ); + break; + + case VIPS_FORMAT_SHORT: + PRE( signed short, float ); + break; + + case VIPS_FORMAT_UINT: + PRE( unsigned int, float ); + break; + + case VIPS_FORMAT_INT: + PRE( signed int, float ); + break; + + case VIPS_FORMAT_FLOAT: + PRE( float, float ); + break; + + case VIPS_FORMAT_DOUBLE: + PRE( double, double ); + break; + + case VIPS_FORMAT_COMPLEX: + case VIPS_FORMAT_DPCOMPLEX: + default: + g_assert( 0 ); + } + } + + return( 0 ); +} + + +static int +vips_premultiply_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsConversion *conversion = VIPS_CONVERSION( object ); + VipsPremultiply *premultiply = (VipsPremultiply *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 1 ); + + VipsImage *in; + + if( VIPS_OBJECT_CLASS( vips_premultiply_parent_class )-> + build( object ) ) + return( -1 ); + + in = premultiply->in; + + if( vips_image_decode( in, &t[0] ) ) + return( -1 ); + in = t[0]; + + /* Trivial case: fall back to copy(). + */ + if( in->Bands == 1 ) + return( vips_image_write( in, conversion->out ) ); + + if( vips_check_noncomplex( class->nickname, in ) ) + return( -1 ); + + if( vips_image_pipelinev( conversion->out, + VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) + return( -1 ); + + if( in->BandFmt == VIPS_FORMAT_DOUBLE ) + conversion->out->BandFmt = VIPS_FORMAT_DOUBLE; + else + conversion->out->BandFmt = VIPS_FORMAT_FLOAT; + + if( vips_image_generate( conversion->out, + vips_start_one, vips_premultiply_gen, vips_stop_one, + in, premultiply ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_premultiply_class_init( VipsPremultiplyClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + VIPS_DEBUG_MSG( "vips_premultiply_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "premultiply"; + vobject_class->description = _( "premultiply image alpha" ); + vobject_class->build = vips_premultiply_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsPremultiply, in ) ); + + VIPS_ARG_DOUBLE( class, "max_alpha", 115, + _( "Maximum alpha" ), + _( "Maximum value of alpha channel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsPremultiply, max_alpha ), + 0, 100000000, 255 ); +} + +static void +vips_premultiply_init( VipsPremultiply *premultiply ) +{ + premultiply->max_alpha = 255.0; +} + +/** + * vips_premultiply: + * @in: input image + * @out: output image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * @max_alpha: %gdouble, maximum value for alpha + * + * Premultiplies any alpha channel. + * The final band is taken to be the alpha + * and the bands are transformed as: + * + * |[ + * alpha = clip( 0, in[in.bands - 1], @max_alpha ); + * norm = alpha / @max_alpha; + * out = [in[0] * norm, ..., in[in.bands - 1] * norm, alpha]; + * |] + * + * So for an N-band image, the first N - 1 bands are multiplied by the clipped + * and normalised final band, the final band is clipped. + * If there is only a single band, + * the image is passed through unaltered. + * + * The result is + * #VIPS_FORMAT_FLOAT unless the input format is #VIPS_FORMAT_DOUBLE, in which + * case the output is double as well. + * + * @max_alpha has the default value 255. You will need to set this to 65535 + * for images with a 16-bit alpha, or perhaps 1.0 for images with a float + * alpha. + * + * Non-complex images only. + * + * See also: vips_unpremultiply(), vips_flatten(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_premultiply( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "premultiply", ap, in, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/conversion/unpremultiply.c b/libvips/conversion/unpremultiply.c new file mode 100644 index 00000000..47e6f840 --- /dev/null +++ b/libvips/conversion/unpremultiply.c @@ -0,0 +1,331 @@ +/* unpremultiply alpha + * + * Author: John Cupitt + * Written on: 7/5/15 + * + */ + +/* + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define VIPS_DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "pconversion.h" + +typedef struct _VipsUnpremultiply { + VipsConversion parent_instance; + + VipsImage *in; + + double max_alpha; + +} VipsUnpremultiply; + +typedef VipsConversionClass VipsUnpremultiplyClass; + +G_DEFINE_TYPE( VipsUnpremultiply, vips_unpremultiply, VIPS_TYPE_CONVERSION ); + +/* Unpremultiply a greyscale (two band) image. + */ +#define UNPRE_MANY( IN, OUT ) { \ + IN * restrict p = (IN *) in; \ + OUT * restrict q = (OUT *) out; \ + \ + for( x = 0; x < width; x++ ) { \ + IN alpha = p[bands - 1]; \ + int clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ + double nalpha = (double) clip_alpha / max_alpha; \ + \ + if( clip_alpha == 0 ) \ + for( i = 0; i < bands - 1; i++ ) \ + q[i] = 0; \ + else \ + for( i = 0; i < bands - 1; i++ ) \ + q[i] = p[i] / nalpha; \ + q[i] = clip_alpha; \ + \ + p += bands; \ + q += bands; \ + } \ +} + +/* Unpremultiply an RGB (four band) image. + */ +#define UNPRE_RGBA( IN, OUT ) { \ + IN * restrict p = (IN *) in; \ + OUT * restrict q = (OUT *) out; \ + \ + for( x = 0; x < width; x++ ) { \ + IN alpha = p[3]; \ + int clip_alpha = VIPS_CLIP( 0, alpha, max_alpha ); \ + double nalpha = (double) clip_alpha / max_alpha; \ + \ + if( clip_alpha == 0 ) { \ + q[0] = 0; \ + q[1] = 0; \ + q[2] = 0; \ + } \ + else { \ + q[0] = p[0] / nalpha; \ + q[1] = p[1] / nalpha; \ + q[2] = p[2] / nalpha; \ + } \ + q[3] = clip_alpha; \ + \ + p += 4; \ + q += 4; \ + } \ +} + +#define UNPRE( IN, OUT ) { \ + if( bands == 4 ) { \ + UNPRE_RGBA( IN, OUT ); \ + } \ + else { \ + UNPRE_MANY( IN, OUT ); \ + } \ +} + +static int +vips_unpremultiply_gen( VipsRegion *or, void *vseq, void *a, void *b, + gboolean *stop ) +{ + VipsUnpremultiply *unpremultiply = (VipsUnpremultiply *) b; + VipsRegion *ir = (VipsRegion *) vseq; + VipsImage *im = ir->im; + VipsRect *r = &or->valid; + int width = r->width; + int bands = im->Bands; + double max_alpha = unpremultiply->max_alpha; + + int x, y, i; + + if( vips_region_prepare( ir, r ) ) + return( -1 ); + + for( y = 0; y < r->height; y++ ) { + VipsPel *in = VIPS_REGION_ADDR( ir, r->left, r->top + y ); + VipsPel *out = VIPS_REGION_ADDR( or, r->left, r->top + y ); + + switch( im->BandFmt ) { + case VIPS_FORMAT_UCHAR: + UNPRE( unsigned char, float ); + break; + + case VIPS_FORMAT_CHAR: + UNPRE( signed char, float ); + break; + + case VIPS_FORMAT_USHORT: + UNPRE( unsigned short, float ); + break; + + case VIPS_FORMAT_SHORT: + UNPRE( signed short, float ); + break; + + case VIPS_FORMAT_UINT: + UNPRE( unsigned int, float ); + break; + + case VIPS_FORMAT_INT: + UNPRE( signed int, float ); + break; + + case VIPS_FORMAT_FLOAT: + UNPRE( float, float ); + break; + + case VIPS_FORMAT_DOUBLE: + UNPRE( double, double ); + break; + + case VIPS_FORMAT_COMPLEX: + case VIPS_FORMAT_DPCOMPLEX: + default: + g_assert( 0 ); + } + } + + return( 0 ); +} + + +static int +vips_unpremultiply_build( VipsObject *object ) +{ + VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object ); + VipsConversion *conversion = VIPS_CONVERSION( object ); + VipsUnpremultiply *unpremultiply = (VipsUnpremultiply *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 1 ); + + VipsImage *in; + + if( VIPS_OBJECT_CLASS( vips_unpremultiply_parent_class )-> + build( object ) ) + return( -1 ); + + in = unpremultiply->in; + + if( vips_image_decode( in, &t[0] ) ) + return( -1 ); + in = t[0]; + + /* Trivial case: fall back to copy(). + */ + if( in->Bands == 1 ) + return( vips_image_write( in, conversion->out ) ); + + if( vips_check_noncomplex( class->nickname, in ) ) + return( -1 ); + + if( vips_image_pipelinev( conversion->out, + VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) + return( -1 ); + + if( in->BandFmt == VIPS_FORMAT_DOUBLE ) + conversion->out->BandFmt = VIPS_FORMAT_DOUBLE; + else + conversion->out->BandFmt = VIPS_FORMAT_FLOAT; + + if( vips_image_generate( conversion->out, + vips_start_one, vips_unpremultiply_gen, vips_stop_one, + in, unpremultiply ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_unpremultiply_class_init( VipsUnpremultiplyClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + VIPS_DEBUG_MSG( "vips_unpremultiply_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "unpremultiply"; + vobject_class->description = _( "unpremultiply image alpha" ); + vobject_class->build = vips_unpremultiply_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsUnpremultiply, in ) ); + + VIPS_ARG_DOUBLE( class, "max_alpha", 115, + _( "Maximum alpha" ), + _( "Maximum value of alpha channel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsUnpremultiply, max_alpha ), + 0, 100000000, 255 ); + +} + +static void +vips_unpremultiply_init( VipsUnpremultiply *unpremultiply ) +{ + unpremultiply->max_alpha = 255.0; +} + +/** + * vips_unpremultiply: + * @in: input image + * @out: output image + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * @max_alpha: %gdouble, maximum value for alpha + * + * Unpremultiplies any alpha channel. + * The final band is taken to be the alpha + * and the bands are transformed as: + * + * |[ + * alpha = (int) clip( 0, in[in.bands - 1], @max_alpha ); + * norm = (double) alpha / @max_alpha; + * if( alpha == 0 ) + * out = [0, ..., 0, alpha]; + * else + * out = [in[0] / norm, ..., in[in.bands - 1] / norm, alpha]; + * |] + * + * So for an N-band image, the first N - 1 bands are divided by the clipped + * and normalised final band, the final band is clipped. + * If there is only a single band, + * the image is passed through unaltered. + * + * The result is + * #VIPS_FORMAT_FLOAT unless the input format is #VIPS_FORMAT_DOUBLE, in which + * case the output is double as well. + * + * @max_alpha has the default value 255. You will need to set this to 65535 + * for images with a 16-bit alpha, or perhaps 1.0 for images with a float + * alpha. + * + * Non-complex images only. + * + * See also: vips_premultiply(), vips_flatten(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_unpremultiply( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "unpremultiply", ap, in, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index a6fb37de..b46e9ba3 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1202,6 +1202,9 @@ vips_foreign_convert_saveable( VipsForeignSave *save ) if( vips_flatten( in, &out, "background", save->background, + "max_alpha", + in->BandFmt == VIPS_FORMAT_USHORT ? + 65535.0 : 255.0, NULL ) ) { g_object_unref( in ); return( -1 ); diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index ca5d4e94..e8e336df 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -184,8 +184,13 @@ int vips_recomb( VipsImage *in, VipsImage **out, VipsImage *m, ... ) int vips_ifthenelse( VipsImage *cond, VipsImage *in1, VipsImage *in2, VipsImage **out, ... ) __attribute__((sentinel)); + int vips_flatten( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_premultiply( VipsImage *in, VipsImage **out, ... ) + __attribute__((sentinel)); +int vips_unpremultiply( VipsImage *in, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_falsecolour( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/test/test_conversion.py b/test/test_conversion.py index f4b0c469..cc737bf4 100755 --- a/test/test_conversion.py +++ b/test/test_conversion.py @@ -348,23 +348,12 @@ class TestConversion(unittest.TestCase): self.assertAlmostEqualObjects(pixel, [20, 0, 41]) def test_flatten(self): - max_value = {Vips.BandFormat.UCHAR: 0xff, - Vips.BandFormat.USHORT: 0xffff, - Vips.BandFormat.UINT: 0xffffffff, - Vips.BandFormat.CHAR: 0x7f, - Vips.BandFormat.SHORT: 0x7fff, - Vips.BandFormat.INT: 0x7fffffff, - Vips.BandFormat.FLOAT: 1.0, - Vips.BandFormat.DOUBLE: 1.0, - Vips.BandFormat.COMPLEX: 1.0, - Vips.BandFormat.DPCOMPLEX: 1.0} - black = self.mono * 0.0 - - for fmt in noncomplex_formats: - mx = max_value[fmt] + for fmt in unsigned_formats + [Vips.BandFormat.SHORT, + Vips.BandFormat.INT] + float_formats: + mx = 255 alpha = mx / 2.0 nalpha = mx - alpha - test = self.colour.bandjoin(black + alpha).cast(fmt) + test = self.colour.bandjoin(alpha).cast(fmt) pixel = test(30, 30) predict = [int(x) * alpha / mx for x in pixel[:-1]] @@ -389,6 +378,46 @@ class TestConversion(unittest.TestCase): for x, y in zip(pixel, predict): self.assertLess(abs(x - y), 2) + def test_premultiply(self): + for fmt in unsigned_formats + [Vips.BandFormat.SHORT, + Vips.BandFormat.INT] + float_formats: + mx = 255 + alpha = mx / 2.0 + nalpha = mx - alpha + test = self.colour.bandjoin(alpha).cast(fmt) + pixel = test(30, 30) + + predict = [int(x) * alpha / mx for x in pixel[:-1]] + [alpha] + + im = test.premultiply() + + self.assertEqual(im.bands, test.bands) + pixel = im(30, 30) + for x, y in zip(pixel, predict): + # we use float arithetic for int and uint, so the rounding + # differs ... don't require huge accuracy + self.assertLess(abs(x - y), 2) + + def test_unpremultiply(self): + for fmt in unsigned_formats + [Vips.BandFormat.SHORT, + Vips.BandFormat.INT] + float_formats: + mx = 255 + alpha = mx / 2.0 + nalpha = mx - alpha + test = self.colour.bandjoin(alpha).cast(fmt) + pixel = test(30, 30) + + predict = [int(x) / (alpha / mx) for x in pixel[:-1]] + [alpha] + + im = test.unpremultiply() + + self.assertEqual(im.bands, test.bands) + pixel = im(30, 30) + for x, y in zip(pixel, predict): + # we use float arithetic for int and uint, so the rounding + # differs ... don't require huge accuracy + self.assertLess(abs(x - y), 2) + def test_flip(self): for fmt in all_formats: test = self.colour.cast(fmt) diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 28f2c90a..a29651c5 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -65,6 +65,9 @@ * - exit with an error code if one or more conversions failed * 20/1/15 * - rename -o as -f, keep -o as a hidden flag + * 9/5/15 + * - use vips_resize() instead of our own code + * - premultiply alpha */ #ifdef HAVE_CONFIG_H @@ -94,7 +97,7 @@ static char *output_format = "tn_%s.jpg"; static char *interpolator = "bilinear"; static char *export_profile = NULL; static char *import_profile = NULL; -static char *convolution_mask = "mild"; +static char *convolution_mask = "none"; static gboolean delete_profile = FALSE; static gboolean linear_processing = FALSE; static gboolean crop_image = FALSE; @@ -159,25 +162,16 @@ static GOptionEntry options[] = { { NULL } }; -/* Calculate the shrink factors. - * - * We shrink in two stages: first, a shrink with a block average. This can - * only accurately shrink by integer factors. We then do a second shrink with - * a supplied interpolator to get the exact size we want. - * - * We aim to do the second shrink by roughly half the interpolator's - * window_size. +/* Calculate the shrink factor, taking into account auto-rotate, the fit mode, + * and so on. */ -static int -calculate_shrink( VipsImage *im, double *residual, - VipsInterpolate *interp ) +static double +calculate_shrink( VipsImage *im ) { VipsAngle angle = vips_autorot_get_angle( im ); gboolean rotate = angle == VIPS_ANGLE_D90 || angle == VIPS_ANGLE_D270; int width = rotate_image && rotate ? im->Ysize : im->Xsize; int height = rotate_image && rotate ? im->Xsize : im->Ysize; - const int window_size = - interp ? vips_interpolate_get_window_size( interp ) : 2; VipsDirection direction; @@ -203,42 +197,8 @@ calculate_shrink( VipsImage *im, double *residual, direction = VIPS_DIRECTION_HORIZONTAL; } - double factor = direction == VIPS_DIRECTION_HORIZONTAL ? - horizontal : vertical; - - /* If the shrink factor is <= 1.0, we need to zoom rather than shrink. - * Just set the factor to 1 in this case. - */ - double factor2 = factor < 1.0 ? 1.0 : factor; - - /* Int component of factor2. - * - * We want to shrink by less for interpolators with larger windows. - */ - int shrink = VIPS_MAX( 1, - floor( factor2 ) / VIPS_MAX( 1, window_size / 2 ) ); - - if( residual && - direction == VIPS_DIRECTION_HORIZONTAL ) { - /* Size after int shrink. - */ - int iwidth = width / shrink; - - /* Therefore residual scale factor is. - */ - double hresidual = (width / factor) / iwidth; - - *residual = hresidual; - } - else if( residual && - direction == VIPS_DIRECTION_VERTICAL ) { - int iheight = height / shrink; - double vresidual = (height / factor) / iheight; - - *residual = vresidual; - } - - return( shrink ); + return( direction == VIPS_DIRECTION_HORIZONTAL ? + horizontal : vertical ); } /* Find the best jpeg preload shrink. @@ -246,19 +206,22 @@ calculate_shrink( VipsImage *im, double *residual, static int thumbnail_find_jpegshrink( VipsImage *im ) { - int shrink = calculate_shrink( im, NULL, NULL ); + double shrink = calculate_shrink( im ); /* We can't use pre-shrunk images in linear mode. libjpeg shrinks in Y * (of YCbCR), not linear space. */ - if( linear_processing ) return( 1 ); - else if( shrink >= 8 ) + + /* We want to leave a bit of shrinking for our interpolator, we don't + * want to do all the shrinking with libjpeg. + */ + if( shrink >= 16 ) return( 8 ); - else if( shrink >= 4 ) + else if( shrink >= 8 ) return( 4 ); - else if( shrink >= 2 ) + else if( shrink >= 4 ) return( 2 ); else return( 1 ); @@ -326,17 +289,16 @@ thumbnail_open( VipsObject *process, const char *filename ) static VipsInterpolate * thumbnail_interpolator( VipsObject *process, VipsImage *in ) { - double residual; - VipsInterpolate *interp; + double shrink = calculate_shrink( in ); - calculate_shrink( in, &residual, NULL ); + VipsInterpolate *interp; /* For images smaller than the thumbnail, we upscale with nearest * neighbor. Otherwise we make thumbnails that look fuzzy and awful. */ if( !(interp = VIPS_INTERPOLATE( vips_object_new_from_string( g_type_class_ref( VIPS_TYPE_INTERPOLATE ), - residual > 1.0 ? "nearest" : interpolator ) )) ) + shrink <= 1.0 ? "nearest" : interpolator ) )) ) return( NULL ); vips_object_local( process, interp ); @@ -385,12 +347,15 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, */ gboolean have_imported; - int shrink; - double residual; - int tile_width; - int tile_height; - int nlines; - double sigma; + /* TRUE if we've premultiplied and need to unpremultiply. + */ + gboolean have_premultiplied; + + /* Sniff the incoming image and try to guess what the alpha max is. + */ + double max_alpha; + + double shrink; /* RAD needs special unpacking. */ @@ -404,6 +369,12 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, in = t[0]; } + /* Try to guess what the maximum alpha might be. + */ + max_alpha = 255; + if( in->BandFmt == VIPS_FORMAT_USHORT ) + max_alpha = 65535; + /* In linear mode, we import right at the start. * * We also have to import the whole image if it's CMYK, since @@ -448,72 +419,43 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, return( NULL ); in = t[2]; - shrink = calculate_shrink( in, &residual, interp ); - - vips_info( "vipsthumbnail", "integer shrink by %d", shrink ); - - if( vips_shrink( in, &t[3], shrink, shrink, NULL ) ) - return( NULL ); - in = t[3]; - - /* We want to make sure we read the image sequentially. - * However, the convolution we may be doing later will force us - * into SMALLTILE or maybe FATSTRIP mode and that will break - * sequentiality. - * - * So ... read into a cache where tiles are scanlines, and make sure - * we keep enough scanlines to be able to serve a line of tiles. - * - * We use a threaded tilecache to avoid a deadlock: suppose thread1, - * evaluating the top block of the output, is delayed, and thread2, - * evaluating the second block, gets here first (this can happen on - * a heavily-loaded system). - * - * With an unthreaded tilecache (as we had before), thread2 will get - * the cache lock and start evaling the second block of the shrink. - * When it reaches the png reader it will stall until the first block - * has been used ... but it never will, since thread1 will block on - * this cache lock. + /* If there's an alpha, we have to premultiply before shrinking. See + * https://github.com/jcupitt/libvips/issues/291 */ + have_premultiplied = FALSE; + if( in->Bands == 2 || + (in->Bands == 4 && in->Type != VIPS_INTERPRETATION_CMYK) || + in->Bands == 5 ) { + vips_info( "vipsthumbnail", "premultiplying alpha" ); + if( vips_premultiply( in, &t[3], + "max_alpha", max_alpha, + NULL ) ) + return( NULL ); + in = t[3]; + have_premultiplied = TRUE; + } - vips_get_tile_size( in, - &tile_width, &tile_height, &nlines ); - if( vips_tilecache( in, &t[4], - "tile_width", in->Xsize, - "tile_height", 10, - "max_tiles", 1 + (nlines * 2) / 10, - "access", VIPS_ACCESS_SEQUENTIAL, - "threaded", TRUE, - NULL ) ) + shrink = calculate_shrink( in ); + + vips_info( "vipsthumbnail", "shrink by %g", shrink ); + vips_info( "vipsthumbnail", "%s interpolation", + VIPS_OBJECT_GET_CLASS( interp )->nickname ); + + if( vips_resize( in, &t[4], 1.0 / shrink, + "interpolate", interp, + NULL ) ) return( NULL ); in = t[4]; - /* If the final affine will be doing a large downsample, we can get - * nasty aliasing on hard edges. Blur before affine to smooth this out. - * - * Don't blur for very small shrinks, blur with radius 1 for x1.5 - * shrinks, blur radius 2 for x2.5 shrinks and above, etc. - */ - sigma = ((1.0 / residual) - 0.5) / 1.5; - if( residual < 1.0 && - sigma > 0.1 ) { - if( vips_gaussblur( in, &t[5], sigma, NULL ) ) + if( have_premultiplied ) { + vips_info( "vipsthumbnail", "unpremultiplying alpha" ); + if( vips_unpremultiply( in, &t[5], + "max_alpha", max_alpha, + NULL ) ) return( NULL ); - vips_info( "vipsthumbnail", "anti-alias, sigma %g", - sigma ); in = t[5]; } - if( vips_affine( in, &t[6], residual, 0, 0, residual, - "interpolate", interp, - NULL ) ) - return( NULL ); - in = t[6]; - - vips_info( "vipsthumbnail", "residual scale by %g", residual ); - vips_info( "vipsthumbnail", "%s interpolation", - VIPS_OBJECT_GET_CLASS( interp )->nickname ); - /* Colour management. * * If we've already imported, just export. Otherwise, we're in @@ -593,8 +535,7 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, /* If we are upsampling, don't sharpen, since nearest looks dumb * sharpened. */ - if( shrink >= 1 && - residual <= 1.0 && + if( shrink > 1.0 && sharpen ) { vips_info( "vipsthumbnail", "sharpening thumbnail" ); if( vips_conv( in, &t[8], sharpen, NULL ) )