/* 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 ); }
static int thumbnail3( IMAGE *in, IMAGE *out ) { int shrink; double residual; VipsInterpolate *interp; int result; shrink = calculate_shrink( in->Xsize, in->Ysize, &residual ); /* For images smaller than the thumbnail, we upscale with nearest * neighbor. Otherwise we makes 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 ) )) ) return( -1 ); if( verbose ) { printf( "integer shrink by %d\n", shrink ); printf( "residual scale by %g\n", residual ); printf( "%s interpolation\n", VIPS_OBJECT_GET_CLASS( interp )->nickname ); } result = shrink_factor( in, out, shrink, residual, interp ); g_object_unref( interp ); return( result ); }
/* Try to read an embedded thumbnail. */ static VipsImage * thumbnail_get_thumbnail( VipsImage *im ) { void *ptr; size_t size; VipsImage *thumb; double residual; int jpegshrink; if( !vips_image_get_typeof( im, THUMBNAIL ) || vips_image_get_blob( im, THUMBNAIL, &ptr, &size ) || vips_jpegload_buffer( ptr, size, &thumb, NULL ) ) { vips_info( "vipsthumbnail", "no jpeg thumbnail" ); return( NULL ); } calculate_shrink( thumb->Xsize, thumb->Ysize, &residual ); if( residual > 1.0 ) { vips_info( "vipsthumbnail", "jpeg thumbnail too small" ); g_object_unref( thumb ); return( NULL ); } /* Reload with the correct downshrink. */ jpegshrink = thumbnail_find_jpegshrink( thumb ); vips_info( "vipsthumbnail", "loading jpeg thumbnail with factor %d pre-shrink", jpegshrink ); g_object_unref( thumb ); if( vips_jpegload_buffer( ptr, size, &thumb, "shrink", jpegshrink, NULL ) ) { vips_info( "vipsthumbnail", "jpeg thumbnail reload failed" ); return( NULL ); } vips_info( "vipsthumbnail", "using %dx%d jpeg thumbnail", thumb->Xsize, thumb->Ysize ); return( thumb ); }
/* JPEGs get special treatment. libjpeg supports fast shrink-on-read, * so if we have a JPEG, we can ask VIPS to load a lower resolution * version. */ static int thumbnail( const char *filename ) { VipsFormatClass *format; int shrink; if( verbose ) printf( "thumbnailing %s\n", filename ); if( !(format = vips_format_for_file( filename )) ) return( -1 ); if( verbose ) printf( "detected format as %s\n", VIPS_OBJECT_CLASS( format )->nickname ); shrink = 1; if( strcmp( VIPS_OBJECT_CLASS( format )->nickname, "jpeg" ) == 0 ) { IMAGE *im; /* This will just read in the header and is quick. */ if( !(im = im_open( filename, "r" )) ) return( -1 ); shrink = calculate_shrink( im->Xsize, im->Ysize, NULL ); im_close( im ); if( shrink > 8 ) shrink = 8; else if( shrink > 4 ) shrink = 4; else if( shrink > 2 ) shrink = 2; else shrink = 1; if( verbose ) printf( "using fast jpeg shrink, factor %d\n", shrink ); } return( thumbnail2( filename, shrink ) ); }
static VipsInterpolate * thumbnail_interpolator( VipsObject *process, VipsImage *in ) { double residual; VipsInterpolate *interp; calculate_shrink( in, &residual ); /* For images smaller than the thumbnail, we upscale with nearest * neighbor. Otherwise we makes 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 ) )) ) return( NULL ); vips_object_local( process, interp ); return( interp ); }
/* Find the best jpeg preload shrink. */ static int thumbnail_find_jpegshrink( VipsImage *im ) { int shrink = calculate_shrink( im, NULL ); /* 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 ) return( 8 ); else if( shrink >= 4 ) return( 4 ); else if( shrink >= 2 ) return( 2 ); else return( 1 ); }
static VipsImage * thumbnail_shrink( VipsObject *process, VipsImage *in, VipsInterpolate *interp, VipsImage *sharpen ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB; int shrink; double residual; int tile_width; int tile_height; int nlines; /* 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. * * 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. */ if( linear_processing && 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]; } /* 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]; shrink = calculate_shrink( in, &residual ); 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. */ 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", (nlines * 2) / 10, "access", VIPS_ACCESS_SEQUENTIAL, "threaded", TRUE, NULL ) || vips_affine( t[4], &t[5], residual, 0, 0, residual, "interpolate", interp, NULL ) ) return( NULL ); in = t[5]; vips_info( "vipsthumbnail", "residual scale by %g", residual ); vips_info( "vipsthumbnail", "%s interpolation", VIPS_OBJECT_GET_CLASS( interp )->nickname ); /* Colour management. * * In linear mode, just export. In device space mode, do a combined * import/export to transform to the target space. */ if( linear_processing ) { 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[6], VIPS_INTERPRETATION_sRGB, NULL ) ) return( NULL ); in = t[6]; } } else if( export_profile && (vips_image_get_typeof( in, VIPS_META_ICC_NAME ) || import_profile) ) { if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) vips_info( "vipsthumbnail", "importing with embedded profile" ); else vips_info( "vipsthumbnail", "importing with profile %s", import_profile ); vips_info( "vipsthumbnail", "exporting with profile %s", export_profile ); if( vips_icc_transform( in, &t[6], export_profile, "input_profile", import_profile, "embedded", TRUE, NULL ) ) return( NULL ); in = t[6]; } /* If we are upsampling, don't sharpen, since nearest looks dumb * sharpened. */ if( shrink >= 1 && residual <= 1.0 && sharpen ) { vips_info( "vipsthumbnail", "sharpening thumbnail" ); if( vips_conv( in, &t[8], sharpen, NULL ) ) return( NULL ); in = t[8]; } 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 ); }
static VipsImage * thumbnail_shrink( VipsObject *process, VipsImage *in ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? VIPS_INTERPRETATION_XYZ : VIPS_INTERPRETATION_sRGB; /* TRUE if we've done the import of an ICC transform and still need to * export. */ gboolean have_imported; /* TRUE if we've premultiplied and need to unpremultiply. */ gboolean have_premultiplied; VipsBandFormat unpremultiplied_format; /* Sniff the incoming image and try to guess what the alpha max is. */ double max_alpha; 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]; } /* 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 * 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( 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 ); 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, NULL ) ) return( NULL ); in = t[4]; if( have_premultiplied ) { vips_info( "vipsthumbnail", "unpremultiplying alpha" ); if( vips_unpremultiply( in, &t[5], "max_alpha", max_alpha, 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 ); }
/* 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 ); }