diff --git a/ChangeLog b/ChangeLog index 4e70da6f..05fd361e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,8 @@ - flood fill could stop half-way for some very complex shapes - better handling of unaligned reads in multipage tiffs [petoor] - add experimental libspng reader +- mark old --delete option to vipsthumbnail as deprecated [UweOhse] +- png save with a bad ICC profile just gives a warning 24/4/20 started 8.9.3 - better iiif tile naming [IllyaMoskvin] diff --git a/doc/Using-vipsthumbnail.md b/doc/Using-vipsthumbnail.md index c1a74d8d..80c987de 100644 --- a/doc/Using-vipsthumbnail.md +++ b/doc/Using-vipsthumbnail.md @@ -267,10 +267,11 @@ $ ls -l tn_shark.jpg -rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg ``` -Now encode with sRGB and delete any embedded profile: +Now transform to sRGB and don't attach a profile (you can also use `strip`, +though that will remove *all* metadata from the image): ``` -$ vipsthumbnail shark.jpg --eprofile srgb --delete +$ vipsthumbnail shark.jpg --eprofile srgb -o tn_shark.jpg[profile=none] $ ls -l tn_shark.jpg -rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg ``` @@ -307,6 +308,7 @@ Putting all this together, I suggest this as a sensible set of options: ``` $ vipsthumbnail fred.jpg \ --size 128 \ + --eprofile srgb \ -o tn_%s.jpg[optimize_coding,strip] \ --eprofile srgb ``` diff --git a/libvips/conversion/cast.c b/libvips/conversion/cast.c index 1fa86341..3efb0918 100644 --- a/libvips/conversion/cast.c +++ b/libvips/conversion/cast.c @@ -126,10 +126,11 @@ G_DEFINE_TYPE( VipsCast, vips_cast, VIPS_TYPE_CONVERSION ); #define CAST_SHORT( X ) VIPS_CLIP( SHRT_MIN, (X), SHRT_MAX ) /* We know the source cannot be the same as the dest, so we will only use - * CAST_UINT() for an INT source, and vice versa. + * CAST_UINT() for an INT source, and vice versa. We don't need to clip to + * INT_MAX, since float->int does that for us. */ #define CAST_UINT( X ) VIPS_MAX( 0, (X) ) -#define CAST_INT( X ) VIPS_MIN( (X), INT_MAX ) +#define CAST_INT( X ) (X) /* Rightshift an integer type, ie. sizeof(ITYPE) > sizeof(OTYPE). */ @@ -222,7 +223,7 @@ G_DEFINE_TYPE( VipsCast, vips_cast, VIPS_TYPE_CONVERSION ); OTYPE * restrict q = (OTYPE *) out; \ \ for( x = 0; x < sz; x++ ) \ - q[x] = p[x]; \ + q[x] = CAST( p[x] ); \ } /* Cast complex types to an int type. Just take the real part. @@ -232,7 +233,7 @@ G_DEFINE_TYPE( VipsCast, vips_cast, VIPS_TYPE_CONVERSION ); OTYPE * restrict q = (OTYPE *) out; \ \ for( x = 0; x < sz; x++ ) { \ - q[x] = p[0]; \ + q[x] = CAST( p[0] ); \ p += 2; \ } \ } diff --git a/libvips/foreign/exif.c b/libvips/foreign/exif.c index 319507ac..ced41bec 100644 --- a/libvips/foreign/exif.c +++ b/libvips/foreign/exif.c @@ -418,10 +418,8 @@ vips_image_resolution_from_exif( VipsImage *image, ExifData *ed ) switch( unit ) { case 1: - /* No unit ... just pass the fields straight to vips. + /* No units, instead xres / yres gives the pixel aspect ratio. */ - vips_image_set_string( image, - VIPS_META_RESOLUTION_UNIT, "none" ); break; case 2: diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 3b5766b4..a4d53fd8 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -106,6 +106,8 @@ * - restart after minimise * 14/10/19 * - revise for source IO + * 5/5/20 angelmixu + * - better handling of JFIF res unit 0 */ /* @@ -504,24 +506,28 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) break; } - /* Get the jfif resolution. exif may overwrite this later. + /* Get the jfif resolution. exif may overwrite this later. Default to + * 72dpi (as EXIF does). */ - xres = 1.0; - yres = 1.0; + xres = 72.0 / 25.4; + yres = 72.0 / 25.4; if( cinfo->saw_JFIF_marker && cinfo->X_density != 1U && cinfo->Y_density != 1U ) { #ifdef DEBUG - printf( "read_jpeg_header: seen jfif _density %d, %d\n", - cinfo->X_density, cinfo->Y_density ); + printf( "read_jpeg_header: jfif _density %d, %d, unit %d\n", + cinfo->X_density, cinfo->Y_density, + cinfo->density_unit ); #endif /*DEBUG*/ switch( cinfo->density_unit ) { case 0: - /* None. Just set. + /* X_density / Y_density gives the pixel aspect ratio. + * Leave xres, but adjust yres. */ - xres = cinfo->X_density; - yres = cinfo->Y_density; + if( cinfo->Y_density > 0 ) + yres = xres * cinfo->X_density / + cinfo->Y_density; break; case 1: diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c index a81a56af..f7292888 100644 --- a/libvips/foreign/magick7load.c +++ b/libvips/foreign/magick7load.c @@ -841,7 +841,7 @@ G_DEFINE_TYPE( VipsForeignLoadMagick7Buffer, vips_foreign_load_magick7_buffer, static gboolean vips_foreign_load_magick7_buffer_is_a_buffer( const void *buf, size_t len ) { - return( magick_ismagick( (const unsigned char *) buf, len ) ); + return( len > 10 && magick_ismagick( (const unsigned char *) buf, len ) ); } static int diff --git a/libvips/foreign/vipspng.c b/libvips/foreign/vipspng.c index ead58bf7..1d9d6330 100644 --- a/libvips/foreign/vipspng.c +++ b/libvips/foreign/vipspng.c @@ -77,6 +77,8 @@ * - restart after minimise * 14/10/19 * - revise for connection IO + * 11/5/20 + * - only warn for saving bad profiles, don't fail */ /* @@ -1047,10 +1049,28 @@ write_vips( Write *write, "of ICC profile\n", length ); #endif /*DEBUG*/ - png_set_iCCP( write->pPng, write->pInfo, "icc", - PNG_COMPRESSION_TYPE_BASE, - (void *) data, length ); + /* We need to ignore any errors from png_set_iCCP() + * since we want to drop incompatible profiles rather + * than simply failing. + */ + if( setjmp( png_jmpbuf( write->pPng ) ) ) { + /* Silent ignore of error. + */ + g_warning( "bad ICC profile not saved" ); + } + else { + /* This will jump back to the line above on + * error. + */ + png_set_iCCP( write->pPng, write->pInfo, "icc", + PNG_COMPRESSION_TYPE_BASE, + (void *) data, length ); + } + /* And restore the setjmp. + */ + if( setjmp( png_jmpbuf( write->pPng ) ) ) + return( -1 ); } if( vips_image_get_typeof( in, VIPS_META_XMP_NAME ) ) { diff --git a/libvips/histogram/hist_norm.c b/libvips/histogram/hist_norm.c index b4de016d..89029fd0 100644 --- a/libvips/histogram/hist_norm.c +++ b/libvips/histogram/hist_norm.c @@ -77,7 +77,7 @@ vips_hist_norm_build( VipsObject *object ) VipsHistNorm *norm = (VipsHistNorm *) object; VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); - guint64 px; + guint64 new_max; int bands; double *a, *b; int y; @@ -95,13 +95,13 @@ vips_hist_norm_build( VipsObject *object ) /* Scale each channel by px / channel max */ - px = VIPS_IMAGE_N_PELS( norm->in ); + new_max = VIPS_IMAGE_N_PELS( norm->in ) - 1; bands = norm->in->Bands; if( !(a = VIPS_ARRAY( object, bands, double )) || !(b = VIPS_ARRAY( object, bands, double )) ) return( -1 ); for( y = 0; y < bands; y++ ) { - a[y] = px / *VIPS_MATRIX( t[0], 1, y + 1 ); + a[y] = new_max / *VIPS_MATRIX( t[0], 1, y + 1 ); b[y] = 0; } @@ -110,9 +110,9 @@ vips_hist_norm_build( VipsObject *object ) /* Make output format as small as we can. */ - if( px <= 256 ) + if( new_max <= 255 ) fmt = VIPS_FORMAT_UCHAR; - else if( px <= 65536 ) + else if( new_max <= 65535 ) fmt = VIPS_FORMAT_USHORT; else fmt = VIPS_FORMAT_UINT; @@ -161,8 +161,9 @@ vips_hist_norm_init( VipsHistNorm *hist_norm ) * @out: (out): output image * @...: %NULL-terminated list of optional named arguments * - * Normalise histogram ... normalise range to make it square (ie. max == - * number of elements). Normalise each band separately. + * Normalise histogram. The maximum of each band becomes equal to the maximum + * index, so for example the max for a uchar image becomes 255. + * Normalise each band separately. * * See also: vips_hist_cum(). * diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 91c9c613..acb7bb31 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -101,19 +101,19 @@ vips_reduce_get_points( VipsKernel kernel, double shrink ) return( 1 ); case VIPS_KERNEL_LINEAR: - return( rint( 2 * shrink ) + 1 ); + return( 2 * rint( shrink ) + 1 ); case VIPS_KERNEL_CUBIC: case VIPS_KERNEL_MITCHELL: - return( rint( 4 * shrink ) + 1 ); + return( 2 * rint( 2 * shrink ) + 1 ); case VIPS_KERNEL_LANCZOS2: /* Needs to be in sync with calculate_coefficients_lanczos(). */ - return( rint( 2 * 2 * shrink ) + 1 ); + return( 2 * rint( 2 * shrink ) + 1 ); case VIPS_KERNEL_LANCZOS3: - return( rint( 2 * 3 * shrink ) + 1 ); + return( 2 * rint( 3 * shrink ) + 1 ); default: g_assert_not_reached(); diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index d7558bb2..b64737df 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -321,14 +321,15 @@ calculate_coefficients_triangle( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 2 * shrink ) + 1; + const int n_points = 2 * rint( shrink ) + 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 - (shrink - 0.5) - x) / shrink; + const double xp = (i - half) / shrink; double l; @@ -358,14 +359,15 @@ calculate_coefficients_cubic( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 4 * shrink ) + 1; + const int n_points = 2 * rint( 2 * shrink ) + 1; + const double half = x + n_points / 2.0 - 1; int i; double sum; sum = 0; for( i = 0; i < n_points; i++ ) { - const double xp = (i - (2 * shrink - 1) - x) / shrink; + const double xp = (i - half) / shrink; const double axp = VIPS_FABS( xp ); const double axp2 = axp * axp; const double axp3 = axp2 * axp; @@ -406,14 +408,15 @@ calculate_coefficients_lanczos( double *c, { /* Needs to be in sync with vips_reduce_get_points(). */ - const int n_points = rint( 2 * a * shrink ) + 1; + const int n_points = 2 * rint( a * shrink ) + 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 - (n_points - 2) / 2 - x) / shrink; + double xp = (i - half) / shrink; double l; diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index b4711da4..b2a8c282 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -1238,8 +1238,11 @@ vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer ) * * @import_profile: %gchararray, fallback import ICC profile * * @export_profile: %gchararray, export ICC profile * * @intent: #VipsIntent, rendering intent + * * @option_string: %gchararray, extra loader options * - * Exacty as vips_thumbnail(), but read from a memory buffer. + * Exacty as vips_thumbnail(), but read from a memory buffer. One extra + * optional argument, @option_string, lets you pass options to the underlying + * loader. * * See also: vips_thumbnail(). * @@ -1414,8 +1417,11 @@ vips_thumbnail_source_init( VipsThumbnailSource *source ) * * @import_profile: %gchararray, fallback import ICC profile * * @export_profile: %gchararray, export ICC profile * * @intent: #VipsIntent, rendering intent + * * @option_string: %gchararray, extra loader options * - * Exactly as vips_thumbnail(), but read from a source. + * Exacty as vips_thumbnail(), but read from a source. One extra + * optional argument, @option_string, lets you pass options to the underlying + * loader. * * See also: vips_thumbnail(). * @@ -1520,7 +1526,12 @@ vips_thumbnail_image_init( VipsThumbnailImage *image ) * * @export_profile: %gchararray, export ICC profile * * @intent: #VipsIntent, rendering intent * - * Exacty as vips_thumbnail(), but read from an existing image. + * Exacty as vips_thumbnail(), but read from an existing image. + * + * This operation + * is not able to exploit shrink-on-load features of image load libraries, so + * it can be much slower than `vips_thumbnail()` and produce poorer quality + * output. Only use it if you really have to. * * See also: vips_thumbnail(). * diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 3e9c37cc..d917ff7f 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -100,6 +100,8 @@ * - add --no-rotate * - add --import-profile / --export-profile names * - back to -o for output + * 29/2/20 + * - deprecate --delete */ #ifdef HAVE_CONFIG_H @@ -128,7 +130,6 @@ static VipsSize size_restriction = VIPS_SIZE_BOTH; static char *output_format = "tn_%s.jpg"; static char *export_profile = NULL; static char *import_profile = NULL; -static gboolean delete_profile = FALSE; static gboolean linear_processing = FALSE; static gboolean crop_image = FALSE; static gboolean no_rotate_image = FALSE; @@ -137,6 +138,7 @@ static char *thumbnail_intent = NULL; /* Deprecated and unused. */ +static gboolean delete_profile = FALSE; static gboolean nosharpen = FALSE; static gboolean nodelete_profile = FALSE; static gboolean verbose = FALSE; @@ -172,9 +174,9 @@ static GOptionEntry options[] = { G_OPTION_ARG_STRING, &thumbnail_intent, N_( "ICC transform with INTENT" ), N_( "INTENT" ) }, - { "delete", 'd', 0, + { "delete", 'd', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &delete_profile, - N_( "delete profile from exported image" ), NULL }, + N_( "(deprecated, does nothing)" ), NULL }, { "no-rotate", 0, 0, G_OPTION_ARG_NONE, &no_rotate_image, N_( "don't auto-rotate" ), NULL },