Merge branch 'master' into libspng-experiment

This commit is contained in:
John Cupitt 2020-06-12 12:42:11 +01:00 committed by GitHub
commit d5fcb6baaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 298 additions and 350 deletions

View File

@ -28,6 +28,8 @@
- handle all EXIF orientation cases, deprecate
vips_autorot_get_angle() [Elad-Laufer]
- load PNGs with libspng, if possible
- deprecate heifload autorotate -- it's now always on
- revised resize improves accuracy [kleisauke]
24/4/20 started 8.9.3
- better iiif tile naming [IllyaMoskvin]

View File

@ -928,16 +928,16 @@ if test x"$with_heif" = x"yes"; then
LIBS="$save_LIBS"
fi
# fetch untransformed width/height added in 1.3.4
# heif_decoding_options.convert_hdr_to_8bit added in 1.7.0
if test x"$with_heif" = x"yes"; then
save_LIBS="$LIBS"
LIBS="$LIBS $HEIF_LIBS"
AC_CHECK_FUNCS(heif_image_handle_get_ispe_width,[
AC_DEFINE(HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH,1,
[define if you have heif_image_handle_get_ispe_width.])
],[]
)
LIBS="$save_LIBS"
save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $HEIF_CFLAGS"
AC_CHECK_MEMBER([struct heif_decoding_options.convert_hdr_to_8bit],[
AC_DEFINE(HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT,1,
[define if you have heif_decoding_options.convert_hdr_to_8bit])
],[],
[#include <libheif/heif.h>])
CFLAGS="$save_CFLAGS"
fi
# pdfium

View File

@ -16,6 +16,8 @@
* - restart after minimise
* 15/3/20
* - revise for new VipsSource API
* 10/5/20
* - deprecate autorotate -- it's too difficult to support properly
*/
/*
@ -99,6 +101,11 @@ typedef struct _VipsForeignLoadHeif {
gboolean thumbnail;
/* Apply any orientation tags in the header.
*
* This is deprecated and does nothing. Non-autorotated reads from
* libheif are surprisingly hard to support well, since orientation can
* be represented in several different ways in HEIC files and devices
* vary in how they do this.
*/
gboolean autorotate;
@ -364,7 +371,7 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
#endif /*DEBUG*/
bands = heif->has_alpha ? 4 : 3;
/* FIXME .. need to test XMP and IPCT.
/* FIXME .. IPTC as well?
*/
n_metadata = heif_image_handle_get_list_of_metadata_block_IDs(
heif->handle, NULL, id, VIPS_NUMBER( id ) );
@ -414,16 +421,26 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
vips_image_set_blob( out, name,
(VipsCallbackFn) NULL, data, length );
if( g_ascii_strcasecmp( type, "exif" ) == 0 )
(void) vips__exif_parse( out );
/* image_set will automatically parse EXIF, if necessary.
*/
}
/* We use libheif's autorotate, so we need to remove any EXIF
* orientaion tags.
*
* According to the HEIF standard, EXIF orientation tags are only
* informational and images should not be rotated because of them.
* Unless we strip these tags, there's a danger downstream processing
* could double-rotate.
*/
vips_autorot_remove_angle( out );
#ifdef HAVE_HEIF_COLOR_PROFILE
#ifdef DEBUG
{
enum heif_color_profile_type profile_type =
heif_image_handle_get_color_profile_type( heif->handle );
#ifdef DEBUG
{
printf( "profile type = " );
switch( profile_type ) {
case heif_color_profile_type_not_present:
@ -450,10 +467,10 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
}
#endif /*DEBUG*/
/* FIXME should probably check the profile type ... lcms seems to be
* able to load at least rICC and prof.
/* lcms can load standard (prof) and reduced (rICC) profiles
*/
if( heif_image_handle_get_color_profile_type( heif->handle ) ) {
if( profile_type == heif_color_profile_type_prof ||
profile_type == heif_color_profile_type_rICC ) {
size_t length = heif_image_handle_get_raw_color_profile_size(
heif->handle );
@ -475,15 +492,11 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
vips_image_set_blob( out, VIPS_META_ICC_NAME,
(VipsCallbackFn) NULL, data, length );
}
else if( profile_type == heif_color_profile_type_nclx ) {
g_warning( "heifload: ignoring nclx profile" );
}
#endif /*HAVE_HEIF_COLOR_PROFILE*/
/* If we are using libheif's autorotate, remove the exif one.
*/
#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH
if( heif->autorotate )
vips_autorot_remove_angle( out );
#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/
vips_image_set_int( out, "heif-primary", heif->primary_page );
vips_image_set_int( out, "n-pages", heif->n_top );
if( vips_object_argument_isset( VIPS_OBJECT( heif ), "n" ) )
@ -505,40 +518,6 @@ vips_foreign_load_heif_set_header( VipsForeignLoadHeif *heif, VipsImage *out )
return( 0 );
}
static int
vips_foreign_load_heif_get_width( VipsForeignLoadHeif *heif,
struct heif_image_handle *handle )
{
int width;
/* _get_ipse_width() fetches the untransformed dimension, but was only
* added in 1.3.4. Without it, we just use the transformed dimension
* and have to autorotate.
*/
width = heif_image_handle_get_width( handle );
#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH
if( !heif->autorotate )
width = heif_image_handle_get_ispe_width( handle );
#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/
return( width );
}
static int
vips_foreign_load_heif_get_height( VipsForeignLoadHeif *heif,
struct heif_image_handle *handle )
{
int height;
height = heif_image_handle_get_height( handle );
#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH
if( !heif->autorotate )
height = heif_image_handle_get_ispe_height( handle );
#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/
return( height );
}
static int
vips_foreign_load_heif_header( VipsForeignLoad *load )
{
@ -582,10 +561,6 @@ vips_foreign_load_heif_header( VipsForeignLoad *load )
}
#ifdef DEBUG
#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH
if( !heif->autorotate )
printf( "using _get_ispe_width() / _height()\n" );
#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/
for( i = heif->page; i < heif->page + heif->n; i++ ) {
heif_item_id thumb_ids[1];
int n_items;
@ -616,11 +591,9 @@ vips_foreign_load_heif_header( VipsForeignLoad *load )
printf( " thumb %d\n", j );
printf( " width = %d\n",
vips_foreign_load_heif_get_width( heif,
thumb_handle ) );
heif_image_handle_get_width( thumb_handle ) );
printf( " height = %d\n",
vips_foreign_load_heif_get_height( heif,
thumb_handle ) );
heif_image_handle_get_height( thumb_handle ) );
}
}
#endif /*DEBUG*/
@ -630,18 +603,16 @@ vips_foreign_load_heif_header( VipsForeignLoad *load )
if( vips_foreign_load_heif_set_page( heif,
heif->page, heif->thumbnail ) )
return( -1 );
heif->page_width = vips_foreign_load_heif_get_width( heif,
heif->handle );
heif->page_height = vips_foreign_load_heif_get_height( heif,
heif->handle );
heif->page_width = heif_image_handle_get_width( heif->handle );
heif->page_height = heif_image_handle_get_height( heif->handle );
for( i = heif->page + 1; i < heif->page + heif->n; i++ ) {
if( vips_foreign_load_heif_set_page( heif,
i, heif->thumbnail ) )
return( -1 );
if( vips_foreign_load_heif_get_width( heif,
heif->handle ) != heif->page_width ||
vips_foreign_load_heif_get_height( heif,
heif->handle ) != heif->page_height ) {
if( heif_image_handle_get_width( heif->handle )
!= heif->page_width ||
heif_image_handle_get_height( heif->handle )
!= heif->page_height ) {
vips_error( class->nickname, "%s",
_( "not all pages are the same size" ) );
return( -1 );
@ -655,11 +626,9 @@ vips_foreign_load_heif_header( VipsForeignLoad *load )
if( vips_foreign_load_heif_set_page( heif, i, FALSE ) )
return( -1 );
printf( " width = %d\n",
vips_foreign_load_heif_get_width( heif,
heif->handle ) );
heif_image_handle_get_width( heif->handle ) );
printf( " height = %d\n",
vips_foreign_load_heif_get_height( heif,
heif->handle ) );
heif_image_handle_get_height( heif->handle ) );
printf( " has_depth = %d\n",
heif_image_handle_has_depth_image( heif->handle ) );
printf( " has_alpha = %d\n",
@ -710,13 +679,12 @@ vips_foreign_load_heif_generate( VipsRegion *or,
heif_chroma_interleaved_RGBA :
heif_chroma_interleaved_RGB;
/* Only disable transforms if we have been able to fetch the
* untransformed dimensions.
*/
options = heif_decoding_options_alloc();
#ifdef HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH
options->ignore_transformations = !heif->autorotate;
#endif /*HAVE_HEIF_IMAGE_HANDLE_GET_ISPE_WIDTH*/
#ifdef HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT
/* VIPS_FORMAT_UCHAR is assumed so downsample HDR to 8bpc
*/
options->convert_hdr_to_8bit = TRUE;
#endif /*HAVE_HEIF_DECODING_OPTIONS_CONVERT_HDR_TO_8BIT*/
error = heif_decode_image( heif->handle, &heif->img,
heif_colorspace_RGB, chroma,
options );
@ -883,7 +851,7 @@ vips_foreign_load_heif_class_init( VipsForeignLoadHeifClass *class )
VIPS_ARG_BOOL( class, "autorotate", 21,
_( "Autorotate" ),
_( "Rotate image using exif orientation" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignLoadHeif, autorotate ),
FALSE );
@ -905,9 +873,16 @@ vips_foreign_load_heif_read( void *data, size_t size, void *userdata )
gint64 result;
result = vips_source_read( heif->source, data, size );
if( result == 0 &&
/* On EOF, make a note of the file length.
*
* libheif can sometimes ask for zero bytes, be careful not to
* interpret that as EOF.
*/
if( size > 0 &&
result == 0 &&
heif->length == -1 )
heif->length = vips_source_seek( heif->source, 0L, SEEK_CUR );
result = heif->length =
vips_source_seek( heif->source, 0L, SEEK_CUR );
if( result < 0 )
return( -1 );
@ -1203,7 +1178,6 @@ vips_foreign_load_heif_source_init( VipsForeignLoadHeifSource *source )
* * @page: %gint, page (top-level image number) to read
* * @n: %gint, load this many pages
* * @thumbnail: %gboolean, fetch thumbnail instead of image
* * @autorotate: %gboolean, rotate image upright during load
*
* Read a HEIF image file into a VIPS image.
*
@ -1220,17 +1194,6 @@ vips_foreign_load_heif_source_init( VipsForeignLoadHeifSource *source )
* If @thumbnail is %TRUE, then fetch a stored thumbnail rather than the
* image.
*
* Setting @autorotate to %TRUE will make the loader interpret the
* orientation tag and automatically rotate the image appropriately during
* load.
*
* If @autorotate is %FALSE, the metadata field #VIPS_META_ORIENTATION is set
* to the value of the orientation tag. Applications may read and interpret
* this field
* as they wish later in processing. See vips_autorot(). Save
* operations will use #VIPS_META_ORIENTATION, if present, to set the
* orientation of output images.
*
* See also: vips_image_new_from_file().
*
* Returns: 0 on success, -1 on error.
@ -1260,7 +1223,6 @@ vips_heifload( const char *filename, VipsImage **out, ... )
* * @page: %gint, page (top-level image number) to read
* * @n: %gint, load this many pages
* * @thumbnail: %gboolean, fetch thumbnail instead of image
* * @autorotate: %gboolean, rotate image upright during load
*
* Read a HEIF image file into a VIPS image.
* Exactly as vips_heifload(), but read from a memory buffer.
@ -1303,7 +1265,6 @@ vips_heifload_buffer( void *buf, size_t len, VipsImage **out, ... )
* * @page: %gint, page (top-level image number) to read
* * @n: %gint, load this many pages
* * @thumbnail: %gboolean, fetch thumbnail instead of image
* * @autorotate: %gboolean, rotate image upright during load
*
* Exactly as vips_heifload(), but read from a source.
*

View File

@ -79,16 +79,15 @@ typedef VipsInterpolate VipsInterpolateBicubic;
typedef VipsInterpolateClass VipsInterpolateBicubicClass;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely.
* sizes up to short), and double (for all others).
*/
/* We could keep a large set of 2d 4x4 matricies, but this actually
* works out slower since for many resizes the thing will no longer
* fit in L1.
*/
static int vips_bicubic_matrixi[VIPS_TRANSFORM_SCALE + 1][4];
static double vips_bicubic_matrixf[VIPS_TRANSFORM_SCALE + 1][4];
static int vips_bicubic_matrixi[VIPS_TRANSFORM_SCALE][4];
static double vips_bicubic_matrixf[VIPS_TRANSFORM_SCALE][4];
/* We need C linkage for this.
*/
@ -498,19 +497,13 @@ static void
vips_interpolate_bicubic_interpolate( VipsInterpolate *interpolate,
void *out, VipsRegion *in, double x, double y )
{
/* Find the mask index. We round-to-nearest, so we need to generate
* indexes in 0 to VIPS_TRANSFORM_SCALE, 2^n + 1 values. We multiply
* by 2 more than we need to, add one, mask, then shift down again to
* get the extra range.
/* Find the mask index.
*/
const int sx = x * VIPS_TRANSFORM_SCALE * 2;
const int sy = y * VIPS_TRANSFORM_SCALE * 2;
const int sx = x * VIPS_TRANSFORM_SCALE;
const int sy = y * VIPS_TRANSFORM_SCALE;
const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int tx = (six + 1) >> 1;
const int ty = (siy + 1) >> 1;
const int tx = sx & (VIPS_TRANSFORM_SCALE - 1);
const int ty = sy & (VIPS_TRANSFORM_SCALE - 1);
/* We know x/y are always positive, so we can just (int) them.
*/
@ -643,7 +636,7 @@ vips_interpolate_bicubic_class_init( VipsInterpolateBicubicClass *iclass )
/* Build the tables of pre-computed coefficients.
*/
for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) {
for( int x = 0; x < VIPS_TRANSFORM_SCALE; x++ ) {
calculate_coefficients_catmull( vips_bicubic_matrixf[x],
(float) x / VIPS_TRANSFORM_SCALE );

View File

@ -6,6 +6,8 @@
* - rename xshrink -> hshrink for greater consistency
* 9/9/16
* - add @centre option
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
*/
/*
@ -78,14 +80,10 @@
* Optional arguments:
*
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
*
* Reduce @in vertically by a float factor. The pixels in @out are
* interpolated with a 1D mask generated by @kernel.
*
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* This is a very low-level operation: see vips_resize() for a more
* convenient way to resize images.
*
@ -107,14 +105,10 @@
* Optional arguments:
*
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
*
* Reduce @in horizontally by a float factor. The pixels in @out are
* interpolated with a 1D mask generated by @kernel.
*
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* This is a very low-level operation: see vips_resize() for a more
* convenient way to resize images.
*
@ -136,7 +130,7 @@ typedef struct _VipsReduce {
*/
VipsKernel kernel;
/* Use centre rather than corner sampling convention.
/* Deprecated.
*/
gboolean centre;
@ -152,18 +146,16 @@ vips_reduce_build( VipsObject *object )
VipsResample *resample = VIPS_RESAMPLE( object );
VipsReduce *reduce = (VipsReduce *) object;
VipsImage **t = (VipsImage **)
vips_object_local_array( object, 3 );
vips_object_local_array( object, 2 );
if( VIPS_OBJECT_CLASS( vips_reduce_parent_class )->build( object ) )
return( -1 );
if( vips_reducev( resample->in, &t[0], reduce->vshrink,
"kernel", reduce->kernel,
"centre", reduce->centre,
NULL ) ||
vips_reduceh( t[0], &t[1], reduce->hshrink,
"kernel", reduce->kernel,
"centre", reduce->centre,
NULL ) ||
vips_image_write( t[1], resample->out ) )
return( -1 );
@ -210,13 +202,6 @@ vips_reduce_class_init( VipsReduceClass *class )
G_STRUCT_OFFSET( VipsReduce, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduce, centre ),
FALSE );
/* The old names .. now use h and v everywhere.
*/
VIPS_ARG_DOUBLE( class, "xshrink", 8,
@ -233,6 +218,15 @@ vips_reduce_class_init( VipsReduceClass *class )
G_STRUCT_OFFSET( VipsReduce, vshrink ),
1.0, 1000000.0, 1.0 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReduce, centre ),
FALSE );
}
static void
@ -252,14 +246,10 @@ vips_reduce_init( VipsReduce *reduce )
* Optional arguments:
*
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
* * @centre: %gboolean use centre rather than corner sampling convention
*
* Reduce @in by a pair of factors with a pair of 1D kernels. This
* will not work well for shrink factors greater than three.
*
* Set @centre to use centre rather than corner sampling convention. Centre
* convention can be useful to match the behaviour of other systems.
*
* This is a very low-level operation: see vips_resize() for a more
* convenient way to resize images.
*

View File

@ -8,6 +8,10 @@
* - rename xshrink as hshrink for consistency
* 9/9/16
* - add @centre option
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
* - fix pixel shift
* - remove unnecessary round-to-nearest behaviour
*/
/*
@ -66,20 +70,23 @@ typedef struct _VipsReduceh {
*/
VipsKernel kernel;
/* Use centre rather than corner sampling convention.
*/
gboolean centre;
/* Number of points in kernel.
*/
int n_point;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely.
/* Horizontal displacement.
*/
int *matrixi[VIPS_TRANSFORM_SCALE + 1];
double *matrixf[VIPS_TRANSFORM_SCALE + 1];
double hoffset;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others).
*/
int *matrixi[VIPS_TRANSFORM_SCALE];
double *matrixf[VIPS_TRANSFORM_SCALE];
/* Deprecated.
*/
gboolean centre;
} VipsReduceh;
@ -194,13 +201,11 @@ reduceh_signed_int_tab( VipsReduceh *reduceh,
for( int z = 0; z < bands; z++ ) {
int sum;
sum = reduce_sum<T, int>( in, bands, cx, n );
sum = reduce_sum<T, int>( in + z, bands, cx, n );
sum = signed_fixed_round( sum );
sum = VIPS_CLIP( min_value, sum, max_value );
out[z] = sum;
in += 1;
}
}
@ -216,10 +221,8 @@ reduceh_float_tab( VipsReduceh *reduceh,
const T* restrict in = (T *) pin;
const int n = reduceh->n_point;
for( int z = 0; z < bands; z++ ) {
out[z] = reduce_sum<T, double>( in, bands, cx, n );
in += 1;
}
for( int z = 0; z < bands; z++ )
out[z] = reduce_sum<T, double>( in + z, bands, cx, n );
}
/* 32-bit int output needs a double intermediate.
@ -238,10 +241,8 @@ reduceh_unsigned_int32_tab( VipsReduceh *reduceh,
for( int z = 0; z < bands; z++ ) {
double sum;
sum = reduce_sum<T, double>( in, bands, cx, n );
out[z] = VIPS_CLIP( 0, sum, max_value );
in += 1;
sum = reduce_sum<T, double>( in + z, bands, cx, n );
out[z] = VIPS_CLIP( 0, sum, max_value );
}
}
@ -258,11 +259,9 @@ reduceh_signed_int32_tab( VipsReduceh *reduceh,
for( int z = 0; z < bands; z++ ) {
double sum;
sum = reduce_sum<T, double>( in, bands, cx, n );
sum = reduce_sum<T, double>( in + z, bands, cx, n );
sum = VIPS_CLIP( min_value, sum, max_value );
out[z] = sum;
in += 1;
}
}
@ -282,11 +281,8 @@ reduceh_notab( VipsReduceh *reduceh,
vips_reduce_make_mask( cx, reduceh->kernel, reduceh->hshrink, x );
for( int z = 0; z < bands; z++ ) {
out[z] = reduce_sum<T, double>( in, bands, cx, n );
in += 1;
}
for( int z = 0; z < bands; z++ )
out[z] = reduce_sum<T, double>( in + z, bands, cx, n );
}
/* Tried a vector path (see reducev) but it was slower. The vectors for
@ -315,18 +311,16 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
r->width, r->height, r->left, r->top );
#endif /*DEBUG*/
s.left = r->left * reduceh->hshrink;
s.left = r->left * reduceh->hshrink - reduceh->hoffset;
s.top = r->top;
s.width = r->width * reduceh->hshrink + reduceh->n_point;
s.height = r->height;
if( reduceh->centre )
s.width += 1;
if( vips_region_prepare( ir, &s ) )
return( -1 );
VIPS_GATE_START( "vips_reduceh_gen: work" );
for( int y = 0; y < r->height; y ++ ) {
for( int y = 0; y < r->height; y++ ) {
VipsPel *p0;
VipsPel *q;
@ -334,9 +328,8 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
q = VIPS_REGION_ADDR( out_region, r->left, r->top + y );
X = r->left * reduceh->hshrink;
if( reduceh->centre )
X += 0.5;
X = (r->left + 0.5) * reduceh->hshrink - 0.5 -
reduceh->hoffset;
/* We want p0 to be the start (ie. x == 0) of the input
* scanline we are reading from. We can then calculate the p we
@ -351,11 +344,10 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq,
ir->valid.left * ps;
for( int x = 0; x < r->width; x++ ) {
int ix = (int) X;
const int ix = (int) X;
VipsPel *p = p0 + ix * ps;
const int sx = X * VIPS_TRANSFORM_SCALE * 2;
const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int tx = (six + 1) >> 1;
const int sx = X * VIPS_TRANSFORM_SCALE;
const int tx = sx & (VIPS_TRANSFORM_SCALE - 1);
const int *cxi = reduceh->matrixi[tx];
const double *cxf = reduceh->matrixf[tx];
@ -441,7 +433,7 @@ vips_reduceh_build( VipsObject *object )
vips_object_local_array( object, 2 );
VipsImage *in;
int width;
double width, extra_pixels;
if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) )
return( -1 );
@ -457,8 +449,6 @@ vips_reduceh_build( VipsObject *object )
if( reduceh->hshrink == 1 )
return( vips_image_write( in, resample->out ) );
/* Build the tables of pre-computed coefficients.
*/
reduceh->n_point =
vips_reduce_get_points( reduceh->kernel, reduceh->hshrink );
g_info( "reduceh: %d point mask", reduceh->n_point );
@ -467,7 +457,29 @@ vips_reduceh_build( VipsObject *object )
"%s", _( "reduce factor too large" ) );
return( -1 );
}
for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) {
/* Output size. We need to always round to nearest, so round(), not
* rint().
*/
width = VIPS_ROUND_UINT(
(double) resample->in->Xsize / reduceh->hshrink );
/* How many pixels we are inventing in the input, -ve for
* discarding.
*/
extra_pixels =
width * reduceh->hshrink - resample->in->Xsize;
/* If we are rounding down, we are not using some input
* pixels. We need to move the origin *inside* the input image
* by half that distance so that we discard pixels equally
* from left and right.
*/
reduceh->hoffset = (1 + extra_pixels) / 2.0;
/* Build the tables of pre-computed coefficients.
*/
for( int x = 0; x < VIPS_TRANSFORM_SCALE; x++ ) {
reduceh->matrixf[x] =
VIPS_ARRAY( object, reduceh->n_point, double );
reduceh->matrixi[x] =
@ -485,7 +497,7 @@ vips_reduceh_build( VipsObject *object )
VIPS_INTERPOLATE_SCALE;
#ifdef DEBUG
printf( "vips_reduceh_build: mask %d\n ", x );
printf( "vips_reduceh_build: mask %d\n ", x );
for( int i = 0; i < reduceh->n_point; i++ )
printf( "%d ", reduceh->matrixi[x][i] );
printf( "\n" );
@ -499,15 +511,10 @@ vips_reduceh_build( VipsObject *object )
in = t[0];
/* Add new pixels around the input so we can interpolate at the edges.
* In centre mode, we read 0.5 pixels more to the right, so we must
* enlarge a little further.
*/
width = in->Xsize + reduceh->n_point - 1;
if( reduceh->centre )
width += 1;
if( vips_embed( in, &t[1],
reduceh->n_point / 2 - 1, 0,
width, in->Ysize,
in->Xsize + reduceh->n_point, in->Ysize,
"extend", VIPS_EXTEND_COPY,
(void *) NULL ) )
return( -1 );
@ -524,8 +531,7 @@ vips_reduceh_build( VipsObject *object )
* example, vipsthumbnail knows the true reduce factor (including the
* fractional part), we just see the integer part here.
*/
resample->out->Xsize = VIPS_ROUND_UINT(
resample->in->Xsize / reduceh->hshrink );
resample->out->Xsize = width;
if( resample->out->Xsize <= 0 ) {
vips_error( object_class->nickname,
"%s", _( "image has shrunk to nothing" ) );
@ -574,20 +580,13 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
G_STRUCT_OFFSET( VipsReduceh, hshrink ),
1, 1000000, 1 );
VIPS_ARG_ENUM( reduceh_class, "kernel", 3,
VIPS_ARG_ENUM( reduceh_class, "kernel", 4,
_( "Kernel" ),
_( "Resampling kernel" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduceh, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( reduceh_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReduceh, centre ),
FALSE );
/* Old name.
*/
VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3,
@ -597,6 +596,15 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
G_STRUCT_OFFSET( VipsReduceh, hshrink ),
1, 1000000, 1 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( reduceh_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReduceh, centre ),
FALSE );
}
static void

View File

@ -17,6 +17,11 @@
* - add @centre option
* 7/3/17
* - add a seq line cache
* 6/6/20 kleisauke
* - deprecate @centre option, it's now always on
* - fix pixel shift
* - speed up the mask construction for uchar/ushort images
* - remove unnecessary round-to-nearest behaviour
*/
/*
@ -104,30 +109,33 @@ typedef struct _VipsReducev {
*/
VipsKernel kernel;
/* Use centre rather than corner sampling convention.
*/
gboolean centre;
/* Number of points in kernel.
*/
int n_point;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely.
/* Vertical displacement.
*/
int *matrixi[VIPS_TRANSFORM_SCALE + 1];
double *matrixf[VIPS_TRANSFORM_SCALE + 1];
double voffset;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others).
*/
int *matrixi[VIPS_TRANSFORM_SCALE];
double *matrixf[VIPS_TRANSFORM_SCALE];
/* And another set for orc: we want 2.6 precision.
*/
int *matrixo[VIPS_TRANSFORM_SCALE + 1];
int *matrixo[VIPS_TRANSFORM_SCALE];
/* The passes we generate for this mask.
*/
int n_pass;
Pass pass[MAX_PASS];
/* Deprecated.
*/
gboolean centre;
} VipsReducev;
typedef VipsResampleClass VipsReducevClass;
@ -146,7 +154,7 @@ vips_reducev_finalize( GObject *gobject )
for( int i = 0; i < reducev->n_pass; i++ )
VIPS_FREEF( vips_vector_free, reducev->pass[i].vector );
reducev->n_pass = 0;
for( int i = 0; i < VIPS_TRANSFORM_SCALE + 1; i++ ) {
for( int i = 0; i < VIPS_TRANSFORM_SCALE; i++ ) {
VIPS_FREE( reducev->matrixf[i] );
VIPS_FREE( reducev->matrixi[i] );
VIPS_FREE( reducev->matrixo[i] );
@ -531,25 +539,24 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq,
#endif /*DEBUG*/
s.left = r->left;
s.top = r->top * reducev->vshrink;
s.top = r->top * reducev->vshrink - reducev->voffset;
s.width = r->width;
s.height = r->height * reducev->vshrink + reducev->n_point;
if( reducev->centre )
s.height += 1;
if( vips_region_prepare( ir, &s ) )
return( -1 );
VIPS_GATE_START( "vips_reducev_gen: work" );
for( int y = 0; y < r->height; y ++ ) {
double Y = (r->top + 0.5) * reducev->vshrink - 0.5 -
reducev->voffset;
for( int y = 0; y < r->height; y++ ) {
VipsPel *q =
VIPS_REGION_ADDR( out_region, r->left, r->top + y );
const double Y = (r->top + y) * reducev->vshrink +
(reducev->centre ? 0.5 : 0.0);
VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y );
const int sy = Y * VIPS_TRANSFORM_SCALE * 2;
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int ty = (siy + 1) >> 1;
const int py = (int) Y;
VipsPel *p = VIPS_REGION_ADDR( ir, r->left, py );
const int sy = Y * VIPS_TRANSFORM_SCALE;
const int ty = sy & (VIPS_TRANSFORM_SCALE - 1);
const int *cyi = reducev->matrixi[ty];
const double *cyf = reducev->matrixf[ty];
const int lskip = VIPS_REGION_LSKIP( ir );
@ -606,13 +613,15 @@ vips_reducev_gen( VipsRegion *out_region, void *vseq,
case VIPS_FORMAT_DPCOMPLEX:
case VIPS_FORMAT_DOUBLE:
reducev_notab<double>( reducev,
q, p, ne, lskip, Y - (int) Y );
q, p, ne, lskip, Y - py );
break;
default:
g_assert_not_reached();
break;
}
Y += reducev->vshrink;
}
VIPS_GATE_STOP( "vips_reducev_gen: work" );
@ -644,11 +653,9 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
#endif /*DEBUG_PIXELS*/
s.left = r->left;
s.top = r->top * reducev->vshrink;
s.top = r->top * reducev->vshrink - reducev->voffset;
s.width = r->width;
s.height = r->height * reducev->vshrink + reducev->n_point;
if( reducev->centre )
s.height += 1;
if( vips_region_prepare( ir, &s ) )
return( -1 );
@ -663,15 +670,15 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
VIPS_GATE_START( "vips_reducev_vector_gen: work" );
for( int y = 0; y < r->height; y ++ ) {
double Y = (r->top + 0.5) * reducev->vshrink - 0.5 -
reducev->voffset;
for( int y = 0; y < r->height; y++ ) {
VipsPel *q =
VIPS_REGION_ADDR( out_region, r->left, r->top + y );
const double Y = (r->top + y) * reducev->vshrink +
(reducev->centre ? 0.5 : 0.0);
const int py = (int) Y;
const int sy = Y * VIPS_TRANSFORM_SCALE * 2;
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int ty = (siy + 1) >> 1;
const int py = (int) Y;
const int sy = Y * VIPS_TRANSFORM_SCALE;
const int ty = sy & (VIPS_TRANSFORM_SCALE - 1);
const int *cyo = reducev->matrixo[ty];
#ifdef DEBUG_PIXELS
@ -682,7 +689,7 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
printf( "first column of pixel values:\n" );
for( int i = 0; i < reducev->n_point; i++ )
printf( "\t%d - %d\n", i,
*VIPS_REGION_ADDR( ir, r->left, r->top + y + i ) );
*VIPS_REGION_ADDR( ir, r->left, py ) );
#endif /*DEBUG_PIXELS*/
/* We run our n passes to generate this scanline.
@ -709,6 +716,8 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
printf( "pixel result:\n" );
printf( "\t%d\n", *q );
#endif /*DEBUG_PIXELS*/
Y += reducev->vshrink;
}
VIPS_GATE_STOP( "vips_reducev_vector_gen: work" );
@ -726,45 +735,11 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out )
VipsGenerateFn generate;
/* Build masks.
*/
for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) {
reducev->matrixf[y] =
VIPS_ARRAY( NULL, reducev->n_point, double );
if( !reducev->matrixf[y] )
return( -1 );
vips_reduce_make_mask( reducev->matrixf[y],
reducev->kernel, reducev->vshrink,
(float) y / VIPS_TRANSFORM_SCALE );
#ifdef DEBUG
printf( "%6.2g", (double) y / VIPS_TRANSFORM_SCALE );
for( int i = 0; i < reducev->n_point; i++ )
printf( ", %6.2g", reducev->matrixf[y][i] );
printf( "\n" );
#endif /*DEBUG*/
}
/* uchar and ushort need an int version of the masks.
*/
if( VIPS_IMAGE_SIZEOF_ELEMENT( in ) <= 2 )
for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) {
reducev->matrixi[y] =
VIPS_ARRAY( NULL, reducev->n_point, int );
if( !reducev->matrixi[y] )
return( -1 );
vips_vector_to_fixed_point(
reducev->matrixf[y], reducev->matrixi[y],
reducev->n_point, VIPS_INTERPOLATE_SCALE );
}
/* And we need an 2.6 version if we will use the vector path.
/* We need an 2.6 version if we will use the vector path.
*/
if( in->BandFmt == VIPS_FORMAT_UCHAR &&
vips_vector_isenabled() )
for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) {
for( int y = 0; y < VIPS_TRANSFORM_SCALE; y++ ) {
reducev->matrixo[y] =
VIPS_ARRAY( NULL, reducev->n_point, int );
if( !reducev->matrixo[y] )
@ -787,7 +762,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out )
*out = vips_image_new();
if( vips_image_pipelinev( *out,
VIPS_DEMAND_STYLE_FATSTRIP, in, (void *) NULL ) )
VIPS_DEMAND_STYLE_THINSTRIP, in, (void *) NULL ) )
return( -1 );
/* Size output. We need to always round to nearest, so round(), not
@ -830,7 +805,7 @@ vips_reducev_build( VipsObject *object )
VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 );
VipsImage *in;
int height;
double height, extra_pixels;
if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) )
return( -1 );
@ -855,6 +830,52 @@ vips_reducev_build( VipsObject *object )
return( -1 );
}
/* Output size. We need to always round to nearest, so round(), not
* rint().
*/
height = VIPS_ROUND_UINT(
(double) resample->in->Ysize / reducev->vshrink );
/* How many pixels we are inventing in the input, -ve for
* discarding.
*/
extra_pixels =
height * reducev->vshrink - resample->in->Ysize;
/* If we are rounding down, we are not using some input
* pixels. We need to move the origin *inside* the input image
* by half that distance so that we discard pixels equally
* from left and right.
*/
reducev->voffset = (1 + extra_pixels) / 2.0;
/* Build the tables of pre-computed coefficients.
*/
for( int y = 0; y < VIPS_TRANSFORM_SCALE; y++ ) {
reducev->matrixf[y] =
VIPS_ARRAY( NULL, reducev->n_point, double );
reducev->matrixi[y] =
VIPS_ARRAY( NULL, reducev->n_point, int );
if( !reducev->matrixf[y] ||
!reducev->matrixi[y] )
return( -1 );
vips_reduce_make_mask( reducev->matrixf[y],
reducev->kernel, reducev->vshrink,
(float) y / VIPS_TRANSFORM_SCALE );
for( int i = 0; i < reducev->n_point; i++ )
reducev->matrixi[y][i] = reducev->matrixf[y][i] *
VIPS_INTERPOLATE_SCALE;
#ifdef DEBUG
printf( "vips_reducev_build: mask %d\n ", y );
for( int i = 0; i < reducev->n_point; i++ )
printf( "%d ", reducev->matrixi[y][i] );
printf( "\n" );
#endif /*DEBUG*/
}
/* Unpack for processing.
*/
if( vips_image_decode( in, &t[0] ) )
@ -863,12 +884,9 @@ vips_reducev_build( VipsObject *object )
/* Add new pixels around the input so we can interpolate at the edges.
*/
height = in->Ysize + reducev->n_point - 1;
if( reducev->centre )
height += 1;
if( vips_embed( in, &t[1],
0, reducev->n_point / 2 - 1,
in->Xsize, height,
in->Xsize, in->Ysize + reducev->n_point,
"extend", VIPS_EXTEND_COPY,
(void *) NULL ) )
return( -1 );
@ -939,13 +957,6 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
G_STRUCT_OFFSET( VipsReducev, kernel ),
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
VIPS_ARG_BOOL( reducev_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsReducev, centre ),
FALSE );
/* Old name.
*/
VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3,
@ -955,6 +966,15 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
G_STRUCT_OFFSET( VipsReducev, vshrink ),
1, 1000000, 1 );
/* We used to let people pick centre or corner, but it's automatic now.
*/
VIPS_ARG_BOOL( reducev_class, "centre", 7,
_( "Centre" ),
_( "Use centre sampling convention" ),
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsReducev, centre ),
FALSE );
}
static void

View File

@ -33,6 +33,8 @@
* affine nearest interpolator is always centre
* 7/7/19 [lovell]
* - don't let either axis drop below 1px
* 12/7/20
* - much better handling of "nearest"
*/
/*
@ -105,45 +107,6 @@ typedef VipsResampleClass VipsResizeClass;
G_DEFINE_TYPE( VipsResize, vips_resize, VIPS_TYPE_RESAMPLE );
/* How much of a scale should be by an integer shrink factor?
*
* This depends on the scale and the kernel we will use for residual resizing.
* For upsizing and nearest-neighbour downsize, we want no shrinking.
*
* The others are adaptive: the size of the kernel changes with the shrink
* factor. We will get the best quality (but be the slowest) if we let
* reduce do all the work. Leave it the final 200 - 300% to do as a compromise
* for efficiency.
*
* FIXME: this is rather ugly. Kernel should be a class and this info should be
* stored in there.
*/
static int
vips_resize_int_shrink( VipsResize *resize, double scale )
{
int shrink;
if( scale > 1.0 )
shrink = 1;
else
switch( resize->kernel ) {
case VIPS_KERNEL_NEAREST:
shrink = 1;
break;
case VIPS_KERNEL_LINEAR:
case VIPS_KERNEL_CUBIC:
case VIPS_KERNEL_MITCHELL:
case VIPS_KERNEL_LANCZOS2:
case VIPS_KERNEL_LANCZOS3:
default:
shrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (scale * 2) ) );
break;
}
return( shrink );
}
/* Suggest a VipsInterpolate which corresponds to a VipsKernel. We use
* this to pick a thing for affine().
*/
@ -192,10 +155,10 @@ vips_resize_build( VipsObject *object )
else
vscale = resize->scale;
/* The int part of our scale.
/* The int part of our scale. Leave the final 200 - 300% to reduce.
*/
int_hshrink = vips_resize_int_shrink( resize, hscale );
int_vshrink = vips_resize_int_shrink( resize, vscale );
int_hshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (hscale * 2) ) );
int_vshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (vscale * 2) ) );
/* Unpack for processing.
*/
@ -203,22 +166,38 @@ vips_resize_build( VipsObject *object )
return( -1 );
in = t[5];
if( int_vshrink > 1 ) {
g_info( "shrinkv by %d", int_vshrink );
if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) )
return( -1 );
in = t[0];
if( resize->kernel == VIPS_KERNEL_NEAREST ) {
if( int_vshrink > 1 ||
int_hshrink > 1 ) {
g_info( "subsample by %d, %d",
int_hshrink, int_vshrink );
if( vips_subsample( in, &t[0],
int_hshrink, int_vshrink, NULL ) )
return( -1 );
in = t[0];
vscale *= int_vshrink;
}
hscale *= int_hshrink;
vscale *= int_vshrink;
}
}
else {
if( int_vshrink > 1 ) {
g_info( "shrinkv by %d", int_vshrink );
if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) )
return( -1 );
in = t[0];
if( int_hshrink > 1 ) {
g_info( "shrinkh by %d", int_hshrink );
if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) )
return( -1 );
in = t[1];
vscale *= int_vshrink;
}
hscale *= int_hshrink;
if( int_hshrink > 1 ) {
g_info( "shrinkh by %d", int_hshrink );
if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) )
return( -1 );
in = t[1];
hscale *= int_hshrink;
}
}
/* Don't let either axis drop below 1 px.
@ -232,7 +211,6 @@ vips_resize_build( VipsObject *object )
g_info( "residual reducev by %g", vscale );
if( vips_reducev( in, &t[2], 1.0 / vscale,
"kernel", resize->kernel,
"centre", TRUE,
NULL ) )
return( -1 );
in = t[2];
@ -243,7 +221,6 @@ vips_resize_build( VipsObject *object )
hscale );
if( vips_reduceh( in, &t[3], 1.0 / hscale,
"kernel", resize->kernel,
"centre", TRUE,
NULL ) )
return( -1 );
in = t[3];

View File

@ -322,7 +322,7 @@ calculate_coefficients_triangle( double *c,
/* Needs to be in sync with vips_reduce_get_points().
*/
const int n_points = 2 * rint( shrink ) + 1;
const double half = x + n_points / 2.0 - 1;
const double half = x + n_points / 2.0 - 1;
int i;
double sum;
@ -360,7 +360,7 @@ calculate_coefficients_cubic( double *c,
/* Needs to be in sync with vips_reduce_get_points().
*/
const int n_points = 2 * rint( 2 * shrink ) + 1;
const double half = x + n_points / 2.0 - 1;
const double half = x + n_points / 2.0 - 1;
int i;
double sum;
@ -409,14 +409,14 @@ calculate_coefficients_lanczos( double *c,
/* Needs to be in sync with vips_reduce_get_points().
*/
const int n_points = 2 * rint( a * shrink ) + 1;
const double half = x + n_points / 2.0 - 1;
const double half = x + n_points / 2.0 - 1;
int i;
double sum;
sum = 0;
for( i = 0; i < n_points; i++ ) {
double xp = (i - half) / shrink;
const double xp = (i - half) / shrink;
double l;

View File

@ -36,7 +36,7 @@ DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm")
BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP")
NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz")
ICO_FILE = os.path.join(IMAGES, "favicon.ico")
HEIC_FILE = os.path.join(IMAGES, "Example1.heic")
HEIC_FILE = os.path.join(IMAGES, "heic-orientation-6.heic")
unsigned_formats = [pyvips.BandFormat.UCHAR,
pyvips.BandFormat.USHORT,

Binary file not shown.

View File

@ -968,9 +968,9 @@ class TestForeign:
a = im(10, 10)
# different versions of HEIC decode have slightly different
# rounding
assert_almost_equal_objects(a, [75.0, 86.0, 81.0], threshold=2)
assert im.width == 4032
assert im.height == 3024
assert_almost_equal_objects(a, [197.0, 181.0, 158.0], threshold=2)
assert im.width == 3024
assert im.height == 4032
assert im.bands == 3
self.file_loader("heifload", HEIC_FILE, heif_valid)

View File

@ -171,9 +171,6 @@ class TestResample:
im = pyvips.Image.new_from_file(HEIC_FILE)
thumb = pyvips.Image.thumbnail(HEIC_FILE, 100)
# original is landscape
assert im.width > im.height
# thumb should be portrait
assert thumb.width < thumb.height
assert thumb.height == 100