libvips/libvips/resample/thumbnail.c

1041 lines
29 KiB
C
Raw Normal View History

2016-10-31 12:14:54 +01:00
/* make a thumbnail ... wraps up the process of thumbnailing, including
* premultiply, colour management etc etc
2016-11-02 10:29:19 +01:00
*
* 2/11/16
* - from vipsthumbnail.c
2017-01-06 14:43:43 +01:00
* 6/1/17
* - add @size parameter
2017-05-04 15:54:49 +02:00
* 4/5/17
* - add FORCE
* 29/5/17
* - don't cache (thanks tomasc)
* 30/8/17
* - add intent option, thanks kleisauke
2016-10-31 12:14:54 +01:00
*/
/*
Copyright (C) 1991-2005 The National Gallery
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/*
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vips/vips.h>
#include <vips/internal.h>
#define VIPS_TYPE_THUMBNAIL (vips_thumbnail_get_type())
#define VIPS_THUMBNAIL( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), VIPS_TYPE_THUMBNAIL, VipsThumbnail ))
#define VIPS_THUMBNAIL_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_CAST( (klass), \
VIPS_TYPE_THUMBNAIL, VipsThumbnailClass))
#define VIPS_IS_THUMBNAIL( obj ) \
(G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_THUMBNAIL ))
#define VIPS_IS_THUMBNAIL_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_THUMBNAIL ))
#define VIPS_THUMBNAIL_GET_CLASS( obj ) \
(G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_THUMBNAIL, VipsThumbnailClass ))
typedef struct _VipsThumbnail {
VipsOperation parent_instance;
VipsImage *out;
int width;
int height;
2017-01-06 14:43:43 +01:00
VipsSize size;
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
gboolean auto_rotate;
VipsInteresting crop;
2016-11-02 10:29:19 +01:00
gboolean linear;
char *export_profile;
char *import_profile;
VipsIntent intent;
2016-11-02 10:29:19 +01:00
2016-10-31 12:14:54 +01:00
/* Set by subclasses to the input image.
*/
2016-11-02 10:29:19 +01:00
VipsImage *in;
/* Bits of info we read from the input image when we get the header of
* the original.
*/
const char *loader; /* Eg. "jpegload_buffer" */
int input_width;
int input_height;
VipsAngle angle; /* From vips_autorot_get_angle() */
2016-10-31 12:14:54 +01:00
} VipsThumbnail;
typedef struct _VipsThumbnailClass {
VipsOperationClass parent_class;
2016-11-02 10:29:19 +01:00
/* Fill out the info section of VipsThumbnail from the input object.
2016-10-31 12:14:54 +01:00
*/
2016-11-02 10:29:19 +01:00
int (*get_info)( VipsThumbnail *thumbnail );
2016-10-31 12:14:54 +01:00
/* Open, giving either a scale or a shrink. @shrink is an integer shrink
* factor suitable for vips_jpegload() or equivalent, @scale is a
* double scale factor, suitable for vips_svgload() or similar.
*/
VipsImage *(*open)( VipsThumbnail *thumbnail, int shrink, double scale );
} VipsThumbnailClass;
G_DEFINE_ABSTRACT_TYPE( VipsThumbnail, vips_thumbnail, VIPS_TYPE_OPERATION );
static void
vips_thumbnail_dispose( GObject *gobject )
{
#ifdef DEBUG
printf( "vips_thumbnail_dispose: " );
vips_object_print_name( VIPS_OBJECT( gobject ) );
printf( "\n" );
#endif /*DEBUG*/
G_OBJECT_CLASS( vips_thumbnail_parent_class )->dispose( gobject );
}
static void
vips_thumbnail_finalize( GObject *gobject )
{
#ifdef DEBUG
printf( "vips_thumbnail_finalize: " );
vips_object_print_name( VIPS_OBJECT( gobject ) );
printf( "\n" );
#endif /*DEBUG*/
G_OBJECT_CLASS( vips_thumbnail_parent_class )->finalize( gobject );
}
2016-10-31 12:14:54 +01:00
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
* and so on.
*
* The hshrink/vshrink are the amount to shrink the input image axes by in
* order for the output axes (ie. after rotation) to match the required
* thumbnail->width, thumbnail->height and fit mode.
2016-10-31 12:14:54 +01:00
*/
static void
2016-10-31 12:14:54 +01:00
vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
int input_width, int input_height, double *hshrink, double *vshrink )
2016-10-31 12:14:54 +01:00
{
/* If we will be rotating, swap the target width and height.
*/
2016-11-02 10:29:19 +01:00
gboolean rotate =
(thumbnail->angle == VIPS_ANGLE_D90 ||
thumbnail->angle == VIPS_ANGLE_D270) &&
thumbnail->auto_rotate;
int target_width = rotate ?
thumbnail->height : thumbnail->width;
int target_height = rotate ?
thumbnail->width : thumbnail->height;
2016-10-31 12:14:54 +01:00
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.
*/
*hshrink = (double) input_width / target_width;
*vshrink = (double) input_height / target_height;
2016-10-31 12:14:54 +01:00
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
2017-05-04 15:54:49 +02:00
if( *hshrink < *vshrink )
2016-10-31 12:14:54 +01:00
direction = VIPS_DIRECTION_HORIZONTAL;
else
direction = VIPS_DIRECTION_VERTICAL;
}
else {
2017-05-04 15:54:49 +02:00
if( *hshrink < *vshrink )
2016-10-31 12:14:54 +01:00
direction = VIPS_DIRECTION_VERTICAL;
else
direction = VIPS_DIRECTION_HORIZONTAL;
}
if( thumbnail->size != VIPS_SIZE_FORCE ) {
if( direction == VIPS_DIRECTION_HORIZONTAL )
2017-05-04 15:54:49 +02:00
*vshrink = *hshrink;
else
2017-05-04 15:54:49 +02:00
*hshrink = *vshrink;
}
2017-05-04 15:54:49 +02:00
if( thumbnail->size == VIPS_SIZE_UP ) {
*hshrink = VIPS_MIN( 1, *hshrink );
*vshrink = VIPS_MIN( 1, *vshrink );
}
else if( thumbnail->size == VIPS_SIZE_DOWN ) {
*hshrink = VIPS_MAX( 1, *hshrink );
*vshrink = VIPS_MAX( 1, *vshrink );
}
}
2017-05-04 15:54:49 +02:00
/* Just the common part of the shrink: the bit by which both axes must be
* shrunk.
*/
static double
vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail,
2017-05-04 15:54:49 +02:00
int width, int height )
{
double hshrink;
double vshrink;
vips_thumbnail_calculate_shrink( thumbnail, width, height,
&hshrink, &vshrink );
return( VIPS_MIN( hshrink, vshrink ) );
2016-10-31 12:14:54 +01:00
}
/* Find the best jpeg preload shrink.
*/
static int
vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail, int width, int height )
{
double shrink = vips_thumbnail_calculate_common_shrink( thumbnail,
width, height );
2016-10-31 12:14:54 +01:00
/* We can't use pre-shrunk images in linear mode. libjpeg shrinks in Y
* (of YCbCR), not linear space.
*/
2016-11-02 10:29:19 +01:00
if( thumbnail->linear )
2016-10-31 12:14:54 +01:00
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_shrink() / vips_reduce() to the
* final size.
2016-10-31 12:14:54 +01:00
*
* 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 the image, returning the best version for thumbnailing.
*
2016-11-02 10:29:19 +01:00
* For example, libjpeg supports fast shrink-on-read, so if we have a JPEG,
2016-10-31 12:14:54 +01:00
* we can ask VIPS to load a lower resolution version.
*/
static VipsImage *
vips_thumbnail_open( VipsThumbnail *thumbnail )
{
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
VipsImage *im;
2017-05-04 15:54:49 +02:00
double shrink;
2016-10-31 12:14:54 +01:00
double scale;
2016-11-02 10:29:19 +01:00
if( class->get_info( thumbnail ) )
2016-10-31 12:14:54 +01:00
return( NULL );
g_info( "selected loader is %s", thumbnail->loader );
g_info( "input size is %d x %d",
2016-11-02 12:07:30 +01:00
thumbnail->input_width, thumbnail->input_height );
2016-10-31 12:14:54 +01:00
2017-05-04 15:54:49 +02:00
shrink = 1.0;
2016-10-31 12:14:54 +01:00
scale = 1.0;
2016-11-02 10:29:19 +01:00
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
shrink = vips_thumbnail_find_jpegshrink( thumbnail,
2016-11-02 12:07:30 +01:00
thumbnail->input_width, thumbnail->input_height );
2017-05-04 15:54:49 +02:00
g_info( "loading jpeg with factor %g pre-shrink", shrink );
2016-10-31 12:14:54 +01:00
}
2016-11-02 10:29:19 +01:00
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
2017-09-15 11:39:06 +02:00
scale = 1.0 / vips_thumbnail_calculate_common_shrink( thumbnail,
2017-05-04 15:54:49 +02:00
thumbnail->input_width, thumbnail->input_height );
g_info( "loading PDF/SVG with factor %g pre-scale", scale );
2016-10-31 12:14:54 +01:00
}
2016-11-02 10:29:19 +01:00
else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
shrink = VIPS_MAX( 1.0,
2017-08-18 23:13:51 +02:00
vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width,
thumbnail->input_height ) );
2017-08-18 23:38:35 +02:00
g_info( "loading webp with factor %g pre-shrink", shrink );
2016-10-31 12:14:54 +01:00
}
2016-11-02 10:29:19 +01:00
if( !(im = class->open( thumbnail, shrink, scale )) )
2016-10-31 12:14:54 +01:00
return( NULL );
return( im );
}
static int
vips_thumbnail_build( VipsObject *object )
{
2016-11-02 10:29:19 +01:00
VipsThumbnail *thumbnail = VIPS_THUMBNAIL( object );
VipsImage **t = (VipsImage **) vips_object_local_array( object, 12 );
VipsInterpretation interpretation = thumbnail->linear ?
VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB;
VipsImage *in;
double hshrink;
double vshrink;
2016-11-02 10:29:19 +01:00
/* 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;
2016-10-31 12:14:54 +01:00
#ifdef DEBUG
printf( "vips_thumbnail_build: " );
vips_object_print_name( object );
printf( "\n" );
#endif /*DEBUG*/
if( VIPS_OBJECT_CLASS( vips_thumbnail_parent_class )->build( object ) )
return( -1 );
2016-11-02 12:07:30 +01:00
if( !vips_object_argument_isset( object, "height" ) )
thumbnail->height = thumbnail->width;
2016-11-02 10:29:19 +01:00
if( !(t[0] = vips_thumbnail_open( thumbnail )) )
return( -1 );
in = t[0];
/* RAD needs special unpacking.
*/
if( in->Coding == VIPS_CODING_RAD ) {
g_info( "unpacking Rad to float" );
2016-11-02 10:29:19 +01:00
/* rad is scrgb.
*/
if( vips_rad2float( in, &t[0], NULL ) )
return( -1 );
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( (thumbnail->linear ||
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 ) ||
thumbnail->import_profile) ) {
g_info( "importing to XYZ PCS" );
if( thumbnail->import_profile )
g_info( "fallback input profile %s",
2016-11-02 10:29:19 +01:00
thumbnail->import_profile );
if( vips_icc_import( in, &t[1],
"input_profile", thumbnail->import_profile,
"embedded", TRUE,
"intent", thumbnail->intent,
2016-11-02 10:29:19 +01:00
"pcs", VIPS_PCS_XYZ,
NULL ) )
return( -1 );
in = t[1];
have_imported = TRUE;
}
/* To the processing colourspace. This will unpack LABQ as well.
*/
g_info( "converting to processing space %s",
2016-11-02 10:29:19 +01:00
vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) );
if( vips_colourspace( in, &t[2], interpretation, NULL ) )
return( -1 );
in = t[2];
/* If there's an alpha, we have to premultiply before shrinking. See
2018-09-21 18:05:47 +02:00
* https://github.com/libvips/libvips/issues/291
2016-11-02 10:29:19 +01:00
*/
have_premultiplied = FALSE;
if( vips_image_hasalpha( in ) ) {
g_info( "premultiplying alpha" );
2016-11-02 10:29:19 +01:00
if( vips_premultiply( in, &t[3], NULL ) )
return( -1 );
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];
}
2017-05-04 15:54:49 +02:00
vips_thumbnail_calculate_shrink( thumbnail,
in->Xsize, in->Ysize, &hshrink, &vshrink );
2016-11-02 10:29:19 +01:00
2017-05-04 15:54:49 +02:00
if( vips_resize( in, &t[4], 1.0 / hshrink,
"vscale", 1.0 / vshrink,
2016-11-02 10:29:19 +01:00
NULL ) )
return( -1 );
in = t[4];
if( have_premultiplied ) {
g_info( "unpremultiplying alpha" );
2016-11-02 10:29:19 +01:00
if( vips_unpremultiply( in, &t[5], NULL ) ||
vips_cast( t[5], &t[6], unpremultiplied_format, NULL ) )
return( -1 );
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( thumbnail->export_profile ||
vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
g_info( "exporting to device space with a profile" );
2016-11-02 10:29:19 +01:00
if( vips_icc_export( in, &t[7],
"output_profile", thumbnail->export_profile,
"intent", thumbnail->intent,
2016-11-02 10:29:19 +01:00
NULL ) )
return( -1 );
in = t[7];
}
else {
g_info( "converting to sRGB" );
2016-11-02 10:29:19 +01:00
if( vips_colourspace( in, &t[7],
VIPS_INTERPRETATION_sRGB, NULL ) )
return( -1 );
in = t[7];
}
}
else if( thumbnail->export_profile &&
(vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ||
thumbnail->import_profile) ) {
g_info( "transforming to %s", thumbnail->export_profile );
if( thumbnail->import_profile )
g_info( "fallback input profile %s",
thumbnail->import_profile );
2016-11-02 10:29:19 +01:00
if( vips_icc_transform( in, &t[7],
thumbnail->export_profile,
"input_profile", thumbnail->import_profile,
"intent", thumbnail->intent,
"embedded", TRUE,
NULL ) )
return( -1 );
in = t[7];
2016-11-02 10:29:19 +01:00
}
if( thumbnail->auto_rotate &&
thumbnail->angle != VIPS_ANGLE_D0 ) {
VipsAngle angle = vips_autorot_get_angle( in );
g_info( "rotating by %s",
2016-11-02 10:29:19 +01:00
vips_enum_nick( VIPS_TYPE_ANGLE, angle ) );
/* Need to copy to memory, we have to stay seq.
*/
if( !(t[9] = vips_image_copy_memory( in )) ||
vips_rot( t[9], &t[10], angle, NULL ) )
return( -1 );
in = t[10];
vips_autorot_remove_angle( in );
}
/* Crop after rotate so we don't need to rotate the crop box.
*/
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
g_info( "cropping to %dx%d",
thumbnail->width, thumbnail->height );
/* Need to copy to memory, we have to stay seq.
*
* FIXME ... could skip the copy if we've rotated.
*/
if( !(t[8] = vips_image_copy_memory( in )) ||
vips_smartcrop( t[8], &t[11],
thumbnail->width, thumbnail->height,
"interesting", thumbnail->crop,
NULL ) )
return( -1 );
in = t[11];
}
2016-11-02 10:29:19 +01:00
g_object_set( thumbnail, "out", vips_image_new(), NULL );
if( vips_image_write( in, thumbnail->out ) )
return( -1 );
2016-10-31 12:14:54 +01:00
return( 0 );
}
static void
vips_thumbnail_class_init( VipsThumbnailClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
2016-10-31 12:14:54 +01:00
gobject_class->dispose = vips_thumbnail_dispose;
gobject_class->finalize = vips_thumbnail_finalize;
2016-10-31 12:14:54 +01:00
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "thumbnail_base";
vobject_class->description = _( "thumbnail generation" );
vobject_class->build = vips_thumbnail_build;
/* We mustn't cache these calls, since we open the file or buffer in
* sequential mode.
*/
operation_class->flags = VIPS_OPERATION_NOCACHE;
2016-10-31 12:14:54 +01:00
VIPS_ARG_IMAGE( class, "out", 2,
_( "Output" ),
_( "Output image" ),
VIPS_ARGUMENT_REQUIRED_OUTPUT,
G_STRUCT_OFFSET( VipsThumbnail, out ) );
2016-11-02 10:29:19 +01:00
VIPS_ARG_INT( class, "width", 3,
2016-10-31 12:14:54 +01:00
_( "Target width" ),
_( "Size to this width" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, width ),
2016-11-02 12:07:30 +01:00
1, VIPS_MAX_COORD, 1 );
2016-10-31 12:14:54 +01:00
VIPS_ARG_INT( class, "height", 113,
_( "Target height" ),
_( "Size to this height" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, height ),
2016-11-02 12:07:30 +01:00
1, VIPS_MAX_COORD, 1 );
2016-10-31 12:14:54 +01:00
2017-01-06 14:43:43 +01:00
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,
2016-11-02 10:29:19 +01:00
_( "Auto rotate" ),
_( "Use orientation tags to rotate image upright" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, auto_rotate ),
TRUE );
VIPS_ARG_ENUM( class, "crop", 116,
2016-11-02 10:29:19 +01:00
_( "Crop" ),
_( "Reduce to fill target rectangle, then crop" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, crop ),
VIPS_TYPE_INTERESTING, VIPS_INTERESTING_NONE );
2016-11-02 10:29:19 +01:00
2017-01-06 14:43:43 +01:00
VIPS_ARG_BOOL( class, "linear", 117,
2016-11-02 10:29:19 +01:00
_( "Linear" ),
_( "Reduce in linear light" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, linear ),
FALSE );
2017-01-06 14:43:43 +01:00
VIPS_ARG_STRING( class, "import_profile", 118,
2016-11-02 10:29:19 +01:00
_( "Import profile" ),
_( "Fallback import profile" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, import_profile ),
NULL );
2017-01-06 14:43:43 +01:00
VIPS_ARG_STRING( class, "export_profile", 119,
2016-11-02 10:29:19 +01:00
_( "Export profile" ),
_( "Fallback export profile" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, export_profile ),
NULL );
VIPS_ARG_ENUM( class, "intent", 120,
_( "Intent" ),
_( "Rendering intent" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsThumbnail, intent ),
VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE );
2016-10-31 12:14:54 +01:00
}
static void
vips_thumbnail_init( VipsThumbnail *thumbnail )
{
2016-11-02 12:07:30 +01:00
thumbnail->width = 1;
thumbnail->height = 1;
2016-11-02 10:29:19 +01:00
thumbnail->auto_rotate = TRUE;
thumbnail->intent = VIPS_INTENT_RELATIVE;
2016-10-31 12:14:54 +01:00
}
typedef struct _VipsThumbnailFile {
VipsThumbnail parent_object;
char *filename;
} VipsThumbnailFile;
typedef VipsThumbnailClass VipsThumbnailFileClass;
G_DEFINE_TYPE( VipsThumbnailFile, vips_thumbnail_file,
vips_thumbnail_get_type() );
2016-11-02 10:29:19 +01:00
/* Get the info from a file.
2016-10-31 12:14:54 +01:00
*/
2016-11-02 10:29:19 +01:00
static int
vips_thumbnail_file_get_info( VipsThumbnail *thumbnail )
2016-10-31 12:14:54 +01:00
{
2016-11-02 10:29:19 +01:00
VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
VipsImage *image;
2016-10-31 12:14:54 +01:00
g_info( "thumbnailing %s", file->filename );
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
if( !(thumbnail->loader = vips_foreign_find_load( file->filename )) ||
!(image = vips_image_new_from_file( file->filename, NULL )) )
return( -1 );
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
g_object_unref( image );
2016-10-31 12:14:54 +01:00
2016-11-02 10:29:19 +01:00
return( 0 );
2016-10-31 12:14:54 +01:00
}
2016-11-02 12:07:30 +01:00
/* Open an image, pre-shrinking as appropriate. Some formats use shrink, some
* scale, never both.
2016-11-02 10:29:19 +01:00
*/
static VipsImage *
vips_thumbnail_file_open( VipsThumbnail *thumbnail, int shrink, double scale )
2016-10-31 12:14:54 +01:00
{
2016-11-02 10:29:19 +01:00
VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
2016-10-31 12:14:54 +01:00
2017-09-15 11:39:06 +02:00
/* If both shrink and scale have been set, something is wrong. It
* should be one or the other (or neither).
*/
g_assert( shrink == 1 || scale == 1.0 );
2016-11-02 12:07:30 +01:00
if( shrink != 1 )
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
"shrink", shrink,
NULL ) );
else if( scale != 1.0 )
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
"scale", scale,
NULL ) );
else
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
2016-10-31 12:14:54 +01:00
}
static void
vips_thumbnail_file_class_init( VipsThumbnailClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
2016-11-02 10:29:19 +01:00
VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS( class );
2016-10-31 12:14:54 +01:00
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "thumbnail";
vobject_class->description = _( "generate thumbnail from file" );
2016-11-02 10:29:19 +01:00
thumbnail_class->get_info = vips_thumbnail_file_get_info;
thumbnail_class->open = vips_thumbnail_file_open;
2016-10-31 12:14:54 +01:00
VIPS_ARG_STRING( class, "filename", 1,
_( "Filename" ),
_( "Filename to read from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsThumbnailFile, filename ),
NULL );
}
static void
vips_thumbnail_file_init( VipsThumbnailFile *file )
{
}
/**
* vips_thumbnail:
* @filename: file to read from
* @out: (out): output image
2016-11-02 10:29:19 +01:00
* @width: target width in pixels
2016-10-31 12:14:54 +01:00
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
2016-11-02 10:29:19 +01:00
* * @height: %gint, target height in pixels
* * @size: #VipsSize, upsize, downsize, both or force
2016-11-02 10:29:19 +01:00
* * @auto_rotate: %gboolean, rotate upright using orientation tag
* * @crop: #VipsInteresting, shrink and crop to fill target
2016-11-02 10:29:19 +01:00
* * @linear: %gboolean, perform shrink in linear light
* * @import_profile: %gchararray, fallback import ICC profile
* * @export_profile: %gchararray, export ICC profile
* * @intent: #VipsIntent, rendering intent
2016-10-31 12:14:54 +01:00
*
2016-11-02 12:19:08 +01:00
* Make a thumbnail from a file. Shrinking is done in three stages: using any
* shrink-on-load features available in the file import library, using a block
* shrink, and using a lanczos3 shrink. At least the final 200% is done with
* lanczos3. The output should be high quality, and the operation should be
* quick.
*
* 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
2017-01-06 14:43:43 +01:00
* specify a separate height with the @height option.
2016-11-02 12:19:08 +01:00
*
* If you set @crop, then the output image will fill the whole of the @width x
* @height rectangle, with any excess cropped away. See vips_smartcrop() for
* details on the cropping strategy.
2016-11-02 12:19:08 +01:00
*
* Normally the operation will upsize or downsize as required to fit the image
* inside or outside the target size. If @size is set
2017-01-06 14:43:43 +01:00
* 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.
* If @size is #VIPS_SIZE_FORCE, the image aspect ratio will be broken and the
* image will be forced to fit the target.
2017-01-06 14:43:43 +01:00
*
2016-11-02 12:19:08 +01:00
* 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.
*
* Shrinking is normally done in sRGB colourspace. Set @linear to shrink in
* linear light colourspace instead. This can give better results, but can
2016-11-02 12:19:08 +01:00
* also be far slower, since tricks like JPEG shrink-on-load cannot be used in
* linear space.
*
2016-11-02 14:51:09 +01:00
* If you set @export_profile to the filename of an ICC profile, the image
* will be transformed to the target colourspace before writing to the
* output. You can also give an @import_profile which will be used if the
* input image has no ICC profile, or if the profile embedded in the
* input image is broken.
2016-10-31 12:14:54 +01:00
*
* Use @intent to set the rendering intent for any ICC transform. The default
* is #VIPS_INTENT_RELATIVE.
*
2016-10-31 12:14:54 +01:00
* See also: vips_thumbnail_buffer().
*
* Returns: 0 on success, -1 on error.
*/
int
2016-11-02 10:29:19 +01:00
vips_thumbnail( const char *filename, VipsImage **out, int width, ... )
2016-10-31 12:14:54 +01:00
{
va_list ap;
int result;
2016-11-02 10:29:19 +01:00
va_start( ap, width );
result = vips_call_split( "thumbnail", ap, filename, out, width );
2016-10-31 12:14:54 +01:00
va_end( ap );
return( result );
}
2016-11-02 12:07:30 +01:00
typedef struct _VipsThumbnailBuffer {
VipsThumbnail parent_object;
VipsArea *buf;
} VipsThumbnailBuffer;
typedef VipsThumbnailClass VipsThumbnailBufferClass;
G_DEFINE_TYPE( VipsThumbnailBuffer, vips_thumbnail_buffer,
vips_thumbnail_get_type() );
/* Get the info from a buffer.
*/
static int
vips_thumbnail_buffer_get_info( VipsThumbnail *thumbnail )
{
VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
VipsImage *image;
g_info( "thumbnailing %zd bytes of data", buffer->buf->length );
2016-11-02 12:07:30 +01:00
if( !(thumbnail->loader = vips_foreign_find_load_buffer(
buffer->buf->data, buffer->buf->length )) ||
!(image = vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "", NULL )) )
return( -1 );
thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
g_object_unref( image );
return( 0 );
}
/* Open an image, pre-shrinking as appropriate. Some formats use shrink, some
* scale, never both.
*/
static VipsImage *
2016-11-02 14:51:09 +01:00
vips_thumbnail_buffer_open( VipsThumbnail *thumbnail,
int shrink, double scale )
2016-11-02 12:07:30 +01:00
{
VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
/* We can't use UNBUFERRED safely on very-many-core systems.
*/
2016-11-02 12:07:30 +01:00
if( shrink != 1 )
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
"shrink", shrink,
NULL ) );
else if( scale != 1.0 )
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
"scale", scale,
NULL ) );
else
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length, "",
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
}
static void
vips_thumbnail_buffer_class_init( VipsThumbnailClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS( class );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "thumbnail_buffer";
vobject_class->description = _( "generate thumbnail from buffer" );
thumbnail_class->get_info = vips_thumbnail_buffer_get_info;
thumbnail_class->open = vips_thumbnail_buffer_open;
VIPS_ARG_BOXED( class, "buffer", 1,
_( "Buffer" ),
_( "Buffer to load from" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsThumbnailBuffer, buf ),
VIPS_TYPE_BLOB );
}
static void
vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer )
{
}
2016-10-31 12:14:54 +01:00
2016-11-02 12:07:30 +01:00
/**
* vips_thumbnail_buffer:
* @buf: (array length=len) (element-type guint8): memory area to load
* @len: (type gsize): size of memory area
* @out: (out): output image
2016-11-02 12:07:30 +01:00
* @width: target width in pixels
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @height: %gint, target height in pixels
* * @size: #VipsSize, upsize, downsize, both or force
2016-11-02 12:07:30 +01:00
* * @auto_rotate: %gboolean, rotate upright using orientation tag
* * @crop: #VipsInteresting, shrink and crop to fill target
2016-11-02 12:07:30 +01:00
* * @linear: %gboolean, perform shrink in linear light
* * @import_profile: %gchararray, fallback import ICC profile
* * @export_profile: %gchararray, export ICC profile
* * @intent: #VipsIntent, rendering intent
2016-11-02 12:07:30 +01:00
*
* Exacty as vips_thumbnail(), but read from a memory buffer.
*
* See also: vips_thumbnail().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_thumbnail_buffer( void *buf, size_t len, VipsImage **out, int width, ... )
{
va_list ap;
VipsBlob *blob;
int result;
/* We don't take a copy of the data or free it.
*/
blob = vips_blob_new( NULL, buf, len );
va_start( ap, width );
result = vips_call_split( "thumbnail_buffer", ap, blob, out, width );
va_end( ap );
vips_area_unref( VIPS_AREA( blob ) );
return( result );
}
typedef struct _VipsThumbnailImage {
VipsThumbnail parent_object;
VipsImage *in;
} VipsThumbnailImage;
typedef VipsThumbnailClass VipsThumbnailImageClass;
G_DEFINE_TYPE( VipsThumbnailImage, vips_thumbnail_image,
vips_thumbnail_get_type() );
/* Get the info from a image.
*/
static int
vips_thumbnail_image_get_info( VipsThumbnail *thumbnail )
{
VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail;
/* Doesn't really matter what we put here.
*/
thumbnail->loader = "image source";
thumbnail->input_width = image->in->Xsize;
thumbnail->input_height = image->in->Ysize;
thumbnail->angle = vips_autorot_get_angle( image->in );
return( 0 );
}
/* Open an image. We can't pre-shrink with an image source, sadly.
*/
static VipsImage *
vips_thumbnail_image_open( VipsThumbnail *thumbnail,
int shrink, double scale )
{
VipsThumbnailImage *image = (VipsThumbnailImage *) thumbnail;
g_object_ref( image->in );
return( image->in );
}
static void
vips_thumbnail_image_class_init( VipsThumbnailClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsThumbnailClass *thumbnail_class = VIPS_THUMBNAIL_CLASS( class );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "thumbnail_image";
vobject_class->description = _( "generate thumbnail from image" );
thumbnail_class->get_info = vips_thumbnail_image_get_info;
thumbnail_class->open = vips_thumbnail_image_open;
VIPS_ARG_IMAGE( class, "in", 1,
_( "Input" ),
_( "Input image argument" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsThumbnailImage, in ) );
}
static void
vips_thumbnail_image_init( VipsThumbnailImage *image )
{
}
/**
* vips_thumbnail_image: (method)
* @in: input image
* @out: (out): output image
* @width: target width in pixels
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* * @height: %gint, target height in pixels
* * @size: #VipsSize, upsize, downsize, both or force
* * @auto_rotate: %gboolean, rotate upright using orientation tag
* * @crop: #VipsInteresting, shrink and crop to fill target
* * @linear: %gboolean, perform shrink in linear light
* * @import_profile: %gchararray, fallback import ICC profile
* * @export_profile: %gchararray, export ICC profile
* * @intent: #VipsIntent, rendering intent
*
* Exacty as vips_thumbnail(), but read from an existing image.
*
* See also: vips_thumbnail().
*
* Returns: 0 on success, -1 on error.
*/
int
vips_thumbnail_image( VipsImage *in, VipsImage **out, int width, ... )
{
va_list ap;
int result;
va_start( ap, width );
result = vips_call_split( "thumbnail_image", ap, in, out, width );
va_end( ap );
return( result );
}