diff --git a/ChangeLog b/ChangeLog index b00adb01..49da14a8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -19,6 +19,8 @@ VIPS_LIBRARY_AGE - better support for bscale / bzero in fits images - deprecate vips_warn() / vips_info(); use g_warning() / g_info() instead +- vipsthumbnail supports much fancier geometry strings +- vips_thumbnail() has new @size option 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima diff --git a/TODO b/TODO index a7bd4ade..4b52fbee 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,35 @@ +- + + convert k2.jpg -resize "100x100<" x.jpg + + downsize, ie. factor < 1 + does not resize + + convert x.jpg -resize "200x200<" y.jpg + + upsize, ie. factor > 1 + resizes + + convert k2.jpg -resize "100x100>" x.jpg + + downsize, ie. factor < 1 + resizes + + convert x.jpg -resize "200x200>" y.jpg + + upsize, ie. factor > 1 + does not resize + + so: < means only resize if input size is less than, > means only resize if + input size is > than + + awkward to fit inside vipsthumbnail.c, since we'd have to copy/paste all the + resize calcs + + have to add another param to thumbnail ... up or down, only up, only down + + + - vipsdisp-tiny makes stripes if the image is not tagged correctly is one of the colourspace functions not reading format? diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index f1b132fc..2104012d 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -9,6 +9,8 @@ G_BEGIN_DECLS /* enumerations from "../../../libvips/include/vips/resample.h" */ GType vips_kernel_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_KERNEL (vips_kernel_get_type()) +GType vips_size_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_SIZE (vips_size_get_type()) /* enumerations from "../../../libvips/include/vips/foreign.h" */ GType vips_foreign_flags_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type()) diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 8af06a42..0a47e5ba 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -47,6 +47,13 @@ typedef enum { VIPS_KERNEL_LAST } VipsKernel; +typedef enum { + VIPS_SIZE_BOTH, + VIPS_SIZE_UP, + VIPS_SIZE_DOWN, + VIPS_SIZE_LAST +} VipsSize; + int vips_shrink( VipsImage *in, VipsImage **out, double hshrink, double vshrink, ... ) __attribute__((sentinel)); diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index 93f4564e..d89595ef 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -26,6 +26,25 @@ vips_kernel_get_type( void ) return( etype ); } +GType +vips_size_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_SIZE_BOTH, "VIPS_SIZE_BOTH", "both"}, + {VIPS_SIZE_UP, "VIPS_SIZE_UP", "up"}, + {VIPS_SIZE_DOWN, "VIPS_SIZE_DOWN", "down"}, + {VIPS_SIZE_LAST, "VIPS_SIZE_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsSize", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/foreign.h" */ GType vips_foreign_flags_get_type( void ) diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index b2da6639..88f42038 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -82,6 +82,18 @@ * Finally, vips_mapim() can apply arbitrary 2D image transforms to an image. */ +/** + * VipsSize: + * @VIPS_SIZE_BOTH: size both up and down + * @VIPS_SIZE_UP: only upsize + * @VIPS_SIZE_DOWN: only downsize + * + * Controls whether an operation should upsize, downsize, or both up and + * downsize. + * + * See also: vips_thumbnail(). + */ + G_DEFINE_ABSTRACT_TYPE( VipsResample, vips_resample, VIPS_TYPE_OPERATION ); static int diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index c90c5610..6ca715ee 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -3,6 +3,8 @@ * * 2/11/16 * - from vipsthumbnail.c + * 6/1/17 + * - add @size parameter */ /* @@ -68,6 +70,7 @@ typedef struct _VipsThumbnail { VipsImage *out; int width; int height; + VipsSize size; gboolean auto_rotate; gboolean crop; @@ -122,6 +125,7 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail, input_width : input_height; VipsDirection direction; + double shrink; /* Calculate the horizontal and vertical shrink we'd need to fit the * image to the bounding box, and pick the biggest. @@ -145,8 +149,17 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail, direction = VIPS_DIRECTION_HORIZONTAL; } - return( direction == VIPS_DIRECTION_HORIZONTAL ? - horizontal : vertical ); + shrink = direction == VIPS_DIRECTION_HORIZONTAL ? + horizontal : vertical; + + /* Restrict to only upsize, only downsize, or both. + */ + if( thumbnail->size == VIPS_SIZE_UP ) + shrink = VIPS_MIN( 1, shrink ); + if( thumbnail->size == VIPS_SIZE_DOWN ) + shrink = VIPS_MAX( 1, shrink ); + + return( shrink ); } /* Find the best jpeg preload shrink. @@ -497,35 +510,42 @@ vips_thumbnail_class_init( VipsThumbnailClass *class ) G_STRUCT_OFFSET( VipsThumbnail, height ), 1, VIPS_MAX_COORD, 1 ); - VIPS_ARG_BOOL( class, "auto_rotate", 114, + VIPS_ARG_ENUM( class, "size", 114, + _( "size" ), + _( "Only upsize, only downsize, or both" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsThumbnail, size ), + VIPS_TYPE_SIZE, VIPS_SIZE_BOTH ); + + VIPS_ARG_BOOL( class, "auto_rotate", 115, _( "Auto rotate" ), _( "Use orientation tags to rotate image upright" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsThumbnail, auto_rotate ), TRUE ); - VIPS_ARG_BOOL( class, "crop", 115, + VIPS_ARG_BOOL( class, "crop", 116, _( "Crop" ), _( "Reduce to fill target rectangle, then crop" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsThumbnail, crop ), FALSE ); - VIPS_ARG_BOOL( class, "linear", 116, + VIPS_ARG_BOOL( class, "linear", 117, _( "Linear" ), _( "Reduce in linear light" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsThumbnail, linear ), FALSE ); - VIPS_ARG_STRING( class, "import_profile", 117, + VIPS_ARG_STRING( class, "import_profile", 118, _( "Import profile" ), _( "Fallback import profile" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsThumbnail, import_profile ), NULL ); - VIPS_ARG_STRING( class, "export_profile", 118, + VIPS_ARG_STRING( class, "export_profile", 119, _( "Export profile" ), _( "Fallback export profile" ), VIPS_ARGUMENT_OPTIONAL_INPUT, @@ -643,6 +663,7 @@ vips_thumbnail_file_init( VipsThumbnailFile *file ) * Optional arguments: * * * @height: %gint, target height in pixels + * * @size: #VipsSize, upsize, downsize or both * * @auto_rotate: %gboolean, rotate upright using orientation tag * * @crop: %gboolean, shrink and crop to fill target * * @linear: %gboolean, perform shrink in linear light @@ -658,11 +679,18 @@ vips_thumbnail_file_init( VipsThumbnailFile *file ) * See vips_thumbnail_buffer() to thumbnail from a memory source. * * The output image will fit within a square of size @width x @width. You can - * specify a height with the @height option. + * specify a separate height with the @height option. * * If you set @crop, then the output image will fill the whole of the @width x * @height rectangle, with any excess cropped away. * + * Normally the operation will upsize or downsize as required. If @size is set + * to #VIPS_SIZE_UP, the operation will only upsize and will just + * copy if asked to downsize. + * If @size is set + * to #VIPS_SIZE_DOWN, the operation will only downsize and will just + * copy if asked to upsize. + * * Normally any orientation tags on the input image (such as EXIF tags) are * interpreted to rotate the image upright. If you set @auto_rotate to %FALSE, * these tags will not be interpreted. @@ -803,6 +831,7 @@ vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer ) * Optional arguments: * * * @height: %gint, target height in pixels + * * @size: #VipsSize, upsize, downsize or both * * @auto_rotate: %gboolean, rotate upright using orientation tag * * @crop: %gboolean, shrink and crop to fill target * * @linear: %gboolean, perform shrink in linear light diff --git a/man/vipsthumbnail.1 b/man/vipsthumbnail.1 index 1cc492a7..595b8504 100644 --- a/man/vipsthumbnail.1 +++ b/man/vipsthumbnail.1 @@ -26,7 +26,7 @@ and write thumbnails to the files and .B tn_jim.jpg. - $ vipsthumbnail --size=64 -f thumbnails/%s.png fred.jpg + $ vipsthumbnail --size=64 -o thumbnails/%s.png fred.jpg will read image file .B fred.jpg @@ -42,10 +42,12 @@ x .B N pixels. You can use MxN to specify a rectangular bounding box. The image is shrunk so that it just fits within this area, images -which are smaller than this are expanded. +which are smaller than this are expanded. Use "xN" or "Mx" to just resize on +one axis. Append "<" to only resize if the input image is smaller than the +target, append ">" to only resize if the input image is larger than the target. .TP -.B -f FORMAT, --format=FORMAT +.B -o FORMAT, --output=FORMAT Set the output format string. The input filename has any file type suffix removed, then that value is substitued into .B FORMAT @@ -65,27 +67,6 @@ prepended. You can add format options too, for example .B tn_%s.jpg[Q=20] will write JPEG images with Q set to 20. -.TP -.B -p I, --interpolator=I -Resample with interpolator -.B I. -Use -.B vips --list classes -to see a list of valid interpolators. The default is -.B bilinear. - -.TP -.B -r, --sharpen=none|mild|MASKFILE -Images can look a little soft after shrinking. This option lets you specify -a sharpening mask. Use "none" to disable sharpening, or "mild" to sharpen -lightly, or give the filename of a custom mask file to use. The default is -"mild". The built-in mild sharpen mask is: - - 3 3 24 0 - -1 -1 -1 - -1 32 -1 - -1 -1 -1 - .TP .B -e PROFILE, --eprofile=PROFILE Export thumbnails with this ICC profile. Images are only colour-transformed if @@ -114,10 +95,12 @@ Delete the output profile from the image. This can save a small amount of space. .TP -.B -v, --verbose -.B vipsthumbnail(1) -normally runs silently, except for warning and error messages. This option -makes it print a list of the operations it performs on each image. +.B -t, --rotate +Auto-rotate images using EXIF orientation tags. + +.TP +.B -a, --linear +Shrink images in linear light colour space. This can be much slower. .SH RETURN VALUE returns 0 on success and non-zero on error. Error can mean one or more diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 32008c5b..154bcf1b 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -85,6 +85,11 @@ * - use scRGB as the working space in linear mode * 15/8/16 * - can now remove 0.1 rounding adjustment + * 2/11/16 + * - use vips_thumbnail(), most code moved there + * 6/1/17 + * - fancy geometry strings + * - support VipSize restrictions */ #ifdef HAVE_CONFIG_H @@ -108,6 +113,7 @@ static char *thumbnail_size = "128"; static int thumbnail_width = 128; static int thumbnail_height = 128; +static VipsSize size_restriction = VIPS_SIZE_BOTH; static char *output_format = "tn_%s.jpg"; static char *export_profile = NULL; static char *import_profile = NULL; @@ -229,10 +235,11 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename ) static int thumbnail_process( VipsObject *process, const char *filename ) { - VipsImage *in; + VipsImage *image; - if( vips_thumbnail( filename, &in, thumbnail_width, + if( vips_thumbnail( filename, &image, thumbnail_width, "height", thumbnail_height, + "size", size_restriction, "auto_rotate", rotate_image, "crop", crop_image, "linear", linear_processing, @@ -241,52 +248,103 @@ thumbnail_process( VipsObject *process, const char *filename ) NULL ) ) return( -1 ); - if( thumbnail_write( process, in, filename ) ) { - g_object_unref( in ); + if( thumbnail_write( process, image, filename ) ) { + g_object_unref( image ); return( -1 ); } - g_object_unref( in ); + g_object_unref( image ); return( 0 ); } +/* Fetch a match_info string, NULL for "". + */ +static const char * +fetch_match( GMatchInfo *match_info, int n ) +{ + const char *str = g_match_info_fetch( match_info, n ); + + return( strcmp( str, "" ) == 0 ? NULL : str ); +} + /* Parse a geometry string and set thumbnail_width and thumbnail_height. */ static int thumbnail_parse_geometry( const char *geometry ) { - GRegEx *regex; - - regex = g_regex_new( "^(\d+)? (x)? (\d+)? ([<>])?$", - G_REGEX_CASELESS | G_REGEX_EXTENDED, 0, NULL ); - g_regex_match( regex, geometry, 0, &match_info ); - if( - - char *p; - - /* Up to 'x' - - - - int w, h; + GRegex *regex; + GMatchInfo *match_info; gboolean handled; handled = FALSE; - if( sscanf( geometry, "%d x %d <", &w, &h ) == 2 ) { - thumbnail_width = w; - thumbnail_height = h; - crop_image = FALSE; + regex = g_regex_new( "^(\\d+)? (x)? (\\d+)? ([<>])?$", + G_REGEX_CASELESS | G_REGEX_EXTENDED, 0, NULL ); + g_regex_match( regex, geometry, 0, &match_info ); + if( g_match_info_matches( match_info ) ) { + const char *w = fetch_match( match_info, 1 ); + const char *x = fetch_match( match_info, 2 ); + const char *h = fetch_match( match_info, 3 ); + const char *s = fetch_match( match_info, 4 ); + + /* If --crop is set, both width and height must be specified, + * since we'll need a complete bounding box to fill. + */ + if( crop_image && x && (!w || !h) ) { + vips_error( "thumbnail", + "both width and height must be given if " + "--crop is enabled" ); + return( -1 ); + } + + if( !x ) { + /* No "x" means we only allow a single number and it + * sets both width and height. + */ + if( w && !h ) { + thumbnail_width = thumbnail_height = atoi( w ); + handled = TRUE; + } + if( !w && h ) { + thumbnail_width = thumbnail_height = atoi( h ); + handled = TRUE; + } + } + else { + /* w or h missing means replace with a huuuge value + * to prevent reduction or enlargement in that axis. + */ + thumbnail_width = VIPS_MAX_COORD; + thumbnail_height = VIPS_MAX_COORD; + if( w ) { + thumbnail_width = atoi( w ); + handled = TRUE; + } + if( h ) { + thumbnail_height = atoi( h ); + handled = TRUE; + } + } + + if( s && strcmp( s, ">" ) == 0 ) + size_restriction = VIPS_SIZE_DOWN; + if( s && strcmp( s, "<" ) == 0 ) + size_restriction = VIPS_SIZE_UP; } - if( sscanf( geometry, "%d x %d >", &w, &h ) == 2 ) { - thumbnail_width = w; - thumbnail_height = h; - crop_image = TRUE; + g_match_info_free( match_info ); + g_regex_unref( regex ); + + if( !handled ) { + vips_error( "thumbnail", + "unable to parse size \"%s\" -- " + "use eg. 128 or 200x300>", geometry ); + + return( -1 ); } - + return( 0 ); } int @@ -334,40 +392,15 @@ main( int argc, char **argv ) g_option_context_free( context ); - /* - if( sscanf( thumbnail_size, "%d x %d", - &thumbnail_width, &thumbnail_height ) == 2 ) { - if( sscanf( thumbnail_size, "%d", &thumbnail_width ) != 1 ) - vips_error_exit( "unable to parse size \"%s\" -- " - "use eg. 128 or 200x300", thumbnail_size ); + if( thumbnail_parse_geometry( thumbnail_size ) ) + vips_error_exit( NULL ); - thumbnail_height = thumbnail_width; - } - */ - - if( sscanf( thumbnail_size, "%d x %d", - &thumbnail_width, &thumbnail_height ) == 2 ) { - if( sscanf( thumbnail_size, "%d", &thumbnail_width ) != 1 ) - vips_error_exit( "unable to parse size \"%s\" -- " - "use eg. 128 or 200x300", thumbnail_size ); - - thumbnail_height = thumbnail_width; - } - - if( sscanf( thumbnail_size, "%d", &thumbnail_width ) != 1 ) - - thumbnail_height = thumbnail_width; - } - vips_error_exit( "unable to parse size \"%s\" -- " - "use eg. 128 or 200x300", thumbnail_size ); - - if( rotate_image ) { #ifndef HAVE_EXIF + if( rotate_image ) g_warning( "%s", _( "auto-rotate disabled: " "libvips built without exif support" ) ); #endif /*!HAVE_EXIF*/ - } result = 0;