diff --git a/test/test_resample.py b/test/test_resample.py index 4385e0db..da8e209f 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -222,6 +222,12 @@ class TestResample(unittest.TestCase): self.assertEqual(im.width, 100) self.assertEqual(im.height, 300) + im1 = Vips.Image.thumbnail(self.jpeg_file, 100) + with open(self.jpeg_file, 'rb') as f: + buf = f.read() + im2 = Vips.Image.thumbnail_buffer(buf, 100) + self.assertLess(abs(im1.avg() - im2.avg()), 1) + def test_similarity(self): im = Vips.Image.new_from_file(self.jpeg_file) im2 = im.similarity(angle = 90) diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index edd80ec3..e09c8421 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -176,424 +176,6 @@ static GOptionEntry options[] = { { NULL } }; -/* Calculate the shrink factor, taking into account auto-rotate, the fit mode, - * and so on. - */ -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; - - VipsDirection direction; - - /* Calculate the horizontal and vertical shrink we'd need to fit the - * image to the bounding box, and pick the biggest. - * - * In crop mode, we aim to fill the bounding box, so we must use the - * smaller axis. - */ - double horizontal = (double) width / thumbnail_width; - double vertical = (double) height / thumbnail_height; - - if( crop_image ) { - if( horizontal < vertical ) - direction = VIPS_DIRECTION_HORIZONTAL; - else - direction = VIPS_DIRECTION_VERTICAL; - } - else { - if( horizontal < vertical ) - direction = VIPS_DIRECTION_VERTICAL; - else - direction = VIPS_DIRECTION_HORIZONTAL; - } - - return( direction == VIPS_DIRECTION_HORIZONTAL ? - horizontal : vertical ); -} - -/* Find the best jpeg preload shrink. - */ -static int -thumbnail_find_jpegshrink( VipsImage *im ) -{ - 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 ); - - /* Shrink-on-load is a simple block shrink and will add quite a bit of - * extra sharpness to the image. We want to block shrink to a - * bit above our target, then vips_resize() to the final size. - * - * Leave at least a factor of two for the final resize step. - */ - if( shrink >= 16 ) - return( 8 ); - else if( shrink >= 8 ) - return( 4 ); - else if( shrink >= 4 ) - return( 2 ); - else - return( 1 ); -} - -/* Open an image, returning the best version of that image for thumbnailing. - * - * libjpeg supports fast shrink-on-read, so if we have a JPEG, we can ask - * VIPS to load a lower resolution version. - */ -static VipsImage * -thumbnail_open( VipsObject *process, const char *filename ) -{ - const char *loader; - VipsImage *im; - - vips_info( "vipsthumbnail", "thumbnailing %s", filename ); - - if( linear_processing ) - vips_info( "vipsthumbnail", "linear mode" ); - - if( !(loader = vips_foreign_find_load( filename )) ) - return( NULL ); - - vips_info( "vipsthumbnail", "selected loader is %s", loader ); - - if( strcmp( loader, "VipsForeignLoadJpegFile" ) == 0 ) { - int jpegshrink; - - /* This will just read in the header and is quick. - */ - if( !(im = vips_image_new_from_file( filename, NULL )) ) - return( NULL ); - - jpegshrink = thumbnail_find_jpegshrink( im ); - - g_object_unref( im ); - - vips_info( "vipsthumbnail", - "loading jpeg with factor %d pre-shrink", - jpegshrink ); - - /* We can't use UNBUFERRED safely on very-many-core systems. - */ - if( !(im = vips_image_new_from_file( filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "shrink", jpegshrink, - NULL )) ) - return( NULL ); - } - else if( strcmp( loader, "VipsForeignLoadPdfFile" ) == 0 || - strcmp( loader, "VipsForeignLoadSvgFile" ) == 0 ) { - double shrink; - - /* This will just read in the header and is quick. - */ - if( !(im = vips_image_new_from_file( filename, NULL )) ) - return( NULL ); - - shrink = calculate_shrink( im ); - - g_object_unref( im ); - - vips_info( "vipsthumbnail", - "loading PDF/SVG with factor %g pre-shrink", - shrink ); - - /* We can't use UNBUFERRED safely on very-many-core systems. - */ - if( !(im = vips_image_new_from_file( filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "scale", 1.0 / shrink, - NULL )) ) - return( NULL ); - } - else if( strcmp( loader, "VipsForeignLoadWebpFile" ) == 0 ) { - double shrink; - - /* This will just read in the header and is quick. - */ - if( !(im = vips_image_new_from_file( filename, NULL )) ) - return( NULL ); - - shrink = calculate_shrink( im ); - - g_object_unref( im ); - - vips_info( "vipsthumbnail", - "loading webp with factor %g pre-shrink", - shrink ); - - /* We can't use UNBUFERRED safely on very-many-core systems. - */ - if( !(im = vips_image_new_from_file( filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "shrink", (int) shrink, - NULL )) ) - return( NULL ); - } - else { - /* All other formats. We can't use UNBUFERRED safely on - * very-many-core systems. - */ - if( !(im = vips_image_new_from_file( filename, - "access", VIPS_ACCESS_SEQUENTIAL, - NULL )) ) - return( NULL ); - } - - vips_object_local( process, im ); - - return( im ); -} - -static VipsImage * -thumbnail_shrink( VipsObject *process, VipsImage *in ) -{ - VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); - VipsInterpretation interpretation = linear_processing ? - VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; - - /* TRUE if we've done the import of an ICC transform and still need to - * export. - */ - gboolean have_imported; - - /* TRUE if we've premultiplied and need to unpremultiply. - */ - gboolean have_premultiplied; - VipsBandFormat unpremultiplied_format; - - double shrink; - - /* RAD needs special unpacking. - */ - if( in->Coding == VIPS_CODING_RAD ) { - vips_info( "vipsthumbnail", "unpacking Rad to float" ); - - /* rad is scrgb. - */ - if( vips_rad2float( in, &t[0], NULL ) ) - return( NULL ); - in = t[0]; - } - - /* In linear mode, we import right at the start. - * - * We also have to import the whole image if it's CMYK, since - * vips_colourspace() (see below) doesn't know about CMYK. - * - * This is only going to work for images in device space. If you have - * 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 && - (in->BandFmt == VIPS_FORMAT_UCHAR || - in->BandFmt == VIPS_FORMAT_USHORT) && - (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 ); - - if( vips_icc_import( in, &t[1], - "input_profile", import_profile, - "embedded", TRUE, - "pcs", VIPS_PCS_XYZ, - NULL ) ) - return( NULL ); - - in = t[1]; - - have_imported = TRUE; - } - - /* To the processing colourspace. This will unpack LABQ as well. - */ - vips_info( "vipsthumbnail", "converting to processing space %s", - vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); - if( vips_colourspace( in, &t[2], interpretation, NULL ) ) - return( NULL ); - in = t[2]; - - /* If there's an alpha, we have to premultiply before shrinking. See - * https://github.com/jcupitt/libvips/issues/291 - */ - have_premultiplied = FALSE; - if( vips_image_hasalpha( in ) ) { - vips_info( "vipsthumbnail", "premultiplying alpha" ); - if( vips_premultiply( in, &t[3], NULL ) ) - return( NULL ); - have_premultiplied = TRUE; - - /* vips_premultiply() makes a float image. When we - * vips_unpremultiply() below, we need to cast back to the - * pre-premultiply format. - */ - unpremultiplied_format = in->BandFmt; - in = t[3]; - } - - shrink = calculate_shrink( in ); - - if( vips_resize( in, &t[4], 1.0 / shrink, "centre", TRUE, NULL ) ) - return( NULL ); - in = t[4]; - - if( have_premultiplied ) { - vips_info( "vipsthumbnail", "unpremultiplying alpha" ); - if( vips_unpremultiply( in, &t[5], NULL ) || - vips_cast( t[5], &t[6], unpremultiplied_format, NULL ) ) - return( NULL ); - in = t[6]; - } - - /* Colour management. - * - * If we've already imported, just export. Otherwise, we're in - * device space and we need a combined import/export to transform to - * the target space. - */ - if( have_imported ) { - if( export_profile || - vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { - vips_info( "vipsthumbnail", - "exporting to device space with a profile" ); - if( vips_icc_export( in, &t[7], - "output_profile", export_profile, - NULL ) ) - return( NULL ); - in = t[7]; - } - else { - vips_info( "vipsthumbnail", "converting to sRGB" ); - if( vips_colourspace( in, &t[7], - VIPS_INTERPRETATION_sRGB, NULL ) ) - return( NULL ); - in = t[7]; - } - } - else if( export_profile && - (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || - import_profile) ) { - VipsImage *out; - - vips_info( "vipsthumbnail", - "exporting with profile %s", export_profile ); - - /* 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" ); - - 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]; - } - - /* If the embedded profile failed and there's no fallback or - * the fallback failed, out will still be NULL. - */ - if( out ) - in = out; - } - - if( delete_profile && - vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { - vips_info( "vipsthumbnail", - "deleting profile from output image" ); - if( !vips_image_remove( in, VIPS_META_ICC_NAME ) ) - return( NULL ); - } - - return( in ); -} - -/* Crop down to the final size, if crop_image is set. - */ -static VipsImage * -thumbnail_crop( VipsObject *process, VipsImage *im ) -{ - VipsImage **t = (VipsImage **) vips_object_local_array( process, 2 ); - - if( crop_image ) { - int left = (im->Xsize - thumbnail_width) / 2; - int top = (im->Ysize - thumbnail_height) / 2; - - if( vips_extract_area( im, &t[0], left, top, - thumbnail_width, thumbnail_height, NULL ) ) - return( NULL ); - im = t[0]; - } - - return( im ); -} - -/* Auto-rotate, if rotate_image is set. - */ -static VipsImage * -thumbnail_rotate( VipsObject *process, VipsImage *im ) -{ - VipsImage **t = (VipsImage **) vips_object_local_array( process, 2 ); - VipsAngle angle = vips_autorot_get_angle( im ); - - if( rotate_image && - angle != VIPS_ANGLE_D0 ) { - vips_info( "vipsthumbnail", "rotating by %s", - vips_enum_nick( VIPS_TYPE_ANGLE, angle ) ); - - /* Need to copy to memory, we have to stay seq. - */ - t[0] = vips_image_new_memory(); - if( vips_image_write( im, t[0] ) || - vips_rot( t[0], &t[1], angle, NULL ) ) - return( NULL ); - im = t[1]; - - vips_autorot_remove_angle( im ); - } - - return( im ); -} - /* Given (eg.) "/poop/somefile.png", write @im to the thumbnail name, * (eg.) "/poop/tn_somefile.jpg". */ @@ -649,17 +231,24 @@ static int thumbnail_process( VipsObject *process, const char *filename ) { VipsImage *in; - VipsImage *thumbnail; - VipsImage *crop; - VipsImage *rotate; - if( !(in = thumbnail_open( process, filename )) || - !(thumbnail = thumbnail_shrink( process, in )) || - !(crop = thumbnail_crop( process, thumbnail )) || - !(rotate = thumbnail_rotate( process, crop )) || - thumbnail_write( process, rotate, filename ) ) + if( vips_thumbnail( filename, &in, thumbnail_width, + "height", thumbnail_height, + "auto_rotate", rotate_image, + "crop", crop_image, + "linear", linear_processing, + "import_profile", import_profile, + "export_profile", export_profile, + NULL ) ) return( -1 ); + if( thumbnail_write( process, in, filename ) ) { + g_object_unref( in ); + return( -1 ); + } + + g_object_unref( in ); + return( 0 ); }