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
|
2017-05-29 11:19:21 +02:00
|
|
|
* 29/5/17
|
|
|
|
* - don't cache (thanks tomasc)
|
2017-08-30 17:34:46 +02:00
|
|
|
* 30/8/17
|
|
|
|
* - add intent option, thanks kleisauke
|
2018-10-31 13:30:37 +01:00
|
|
|
* 31/10/18
|
|
|
|
* - deprecate auto_rotate, add no_rotate
|
2018-10-31 15:07:13 +01:00
|
|
|
* - implement shrink-on-load for openslide images
|
2018-11-16 19:00:25 +01:00
|
|
|
* 16/11/18
|
|
|
|
* - implement shrink-on-load for tiff pyramid
|
2019-02-03 13:27:58 +01:00
|
|
|
* 3/2/19 kleisauke
|
|
|
|
* - add option_string param to thumbnail_buffer
|
2019-04-23 12:35:13 +02:00
|
|
|
* 23/4/19
|
|
|
|
* - don't force import CMYK, since colourspace knows about it now
|
2019-04-24 18:04:31 +02:00
|
|
|
* 24/4/19
|
|
|
|
* - support multi-page (animated) images
|
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 ))
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Should be plenty.
|
|
|
|
*/
|
|
|
|
#define MAX_LEVELS (256)
|
|
|
|
|
2016-10-31 12:14:54 +01:00
|
|
|
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;
|
2018-10-31 13:30:37 +01:00
|
|
|
gboolean no_rotate;
|
2017-03-08 15:31:00 +01:00
|
|
|
VipsInteresting crop;
|
2016-11-02 10:29:19 +01:00
|
|
|
gboolean linear;
|
|
|
|
char *export_profile;
|
|
|
|
char *import_profile;
|
2017-08-30 17:34:46 +02:00
|
|
|
VipsIntent intent;
|
2016-11-02 10:29:19 +01:00
|
|
|
|
|
|
|
/* Bits of info we read from the input image when we get the header of
|
|
|
|
* the original.
|
|
|
|
*/
|
2018-10-31 15:07:13 +01:00
|
|
|
const char *loader; /* Eg. "VipsForeignLoadJpeg*" */
|
2016-11-02 10:29:19 +01:00
|
|
|
int input_width;
|
|
|
|
int input_height;
|
2019-02-19 18:27:23 +01:00
|
|
|
int page_height;
|
2016-11-02 10:29:19 +01:00
|
|
|
VipsAngle angle; /* From vips_autorot_get_angle() */
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* For openslide, we need to read out the size of each level too.
|
|
|
|
*/
|
|
|
|
int level_count;
|
|
|
|
int level_width[MAX_LEVELS];
|
|
|
|
int level_height[MAX_LEVELS];
|
|
|
|
|
2018-11-16 19:00:25 +01:00
|
|
|
/* Try to get n-pages too, for pyr tiff load.
|
|
|
|
*/
|
|
|
|
int n_pages;
|
|
|
|
|
2019-01-30 17:19:11 +01:00
|
|
|
/* For HEIF, try to fetch the size of the stored thumbnail.
|
|
|
|
*/
|
|
|
|
int heif_thumbnail_width;
|
|
|
|
int heif_thumbnail_height;
|
|
|
|
|
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
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Open with some kind of shrink or scale factor. Exactly what we pass
|
|
|
|
* and to what param depends on the loader. It'll be an integer shrink
|
|
|
|
* factor for vips_jpegload(), a double scale factor for vips_svgload().
|
|
|
|
*
|
|
|
|
* See VipsThumbnail::loader
|
2016-10-31 12:14:54 +01:00
|
|
|
*/
|
2018-10-31 15:07:13 +01:00
|
|
|
VipsImage *(*open)( VipsThumbnail *thumbnail, double factor );
|
2016-10-31 12:14:54 +01:00
|
|
|
|
|
|
|
} VipsThumbnailClass;
|
|
|
|
|
|
|
|
G_DEFINE_ABSTRACT_TYPE( VipsThumbnail, vips_thumbnail, VIPS_TYPE_OPERATION );
|
|
|
|
|
2017-01-10 15:12:24 +01:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Fetch an int openslide field from metadata. These are all represented as
|
2018-11-01 19:50:10 +01:00
|
|
|
* strings. Return the default value if there's any problem.
|
2018-10-31 15:07:13 +01:00
|
|
|
*/
|
|
|
|
static int
|
|
|
|
get_int( VipsImage *image, const char *field, int default_value )
|
|
|
|
{
|
|
|
|
const char *str;
|
|
|
|
|
|
|
|
if( vips_image_get_typeof( image, field ) &&
|
|
|
|
!vips_image_get_string( image, field, &str ) )
|
|
|
|
return( atoi( str ) );
|
|
|
|
|
|
|
|
return( default_value );
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
|
|
|
|
{
|
|
|
|
thumbnail->input_width = image->Xsize;
|
|
|
|
thumbnail->input_height = image->Ysize;
|
|
|
|
thumbnail->angle = vips_autorot_get_angle( image );
|
|
|
|
|
2019-03-02 23:13:55 +01:00
|
|
|
if( vips_image_get_typeof( image, VIPS_META_N_PAGES ) ) {
|
2018-11-16 19:00:25 +01:00
|
|
|
int n_pages;
|
|
|
|
|
2019-03-02 23:13:55 +01:00
|
|
|
if( !vips_image_get_int( image, VIPS_META_N_PAGES, &n_pages ) )
|
2018-11-16 19:00:25 +01:00
|
|
|
thumbnail->n_pages =
|
|
|
|
VIPS_CLIP( 1, n_pages, MAX_LEVELS );
|
|
|
|
}
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* For openslide, read out the level structure too.
|
|
|
|
*/
|
|
|
|
if( vips_isprefix( "VipsForeignLoadOpenslide", thumbnail->loader ) ) {
|
|
|
|
int level_count;
|
|
|
|
int level;
|
|
|
|
|
|
|
|
level_count = get_int( image, "openslide.level-count", 1 );
|
|
|
|
level_count = VIPS_CLIP( 1, level_count, MAX_LEVELS );
|
|
|
|
thumbnail->level_count = level_count;
|
|
|
|
|
|
|
|
for( level = 0; level < level_count; level++ ) {
|
|
|
|
char name[256];
|
|
|
|
|
|
|
|
vips_snprintf( name, 256,
|
|
|
|
"openslide.level[%d].width", level );
|
|
|
|
thumbnail->level_width[level] =
|
|
|
|
get_int( image, name, 0 );
|
|
|
|
vips_snprintf( name, 256,
|
|
|
|
"openslide.level[%d].height", level );
|
|
|
|
thumbnail->level_height[level] =
|
|
|
|
get_int( image, name, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 19:00:25 +01:00
|
|
|
/* This may not be a pyr tiff, so no error if we can't find the layers.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail )
|
|
|
|
{
|
|
|
|
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for( i = 0; i < thumbnail->n_pages; i++ ) {
|
|
|
|
VipsImage *page;
|
|
|
|
int level_width;
|
|
|
|
int level_height;
|
|
|
|
int expected_level_width;
|
|
|
|
int expected_level_height;
|
|
|
|
|
|
|
|
if( !(page = class->open( thumbnail, i )) )
|
|
|
|
return;
|
|
|
|
level_width = page->Xsize;
|
|
|
|
level_height = page->Ysize;
|
|
|
|
VIPS_UNREF( page );
|
|
|
|
|
|
|
|
/* Try to sanity-check the size of the pages. Do they look
|
|
|
|
* like a pyramid?
|
|
|
|
*/
|
|
|
|
expected_level_width = thumbnail->input_width / (1 << i);
|
|
|
|
expected_level_height = thumbnail->input_height / (1 << i);
|
|
|
|
|
|
|
|
/* Won't be exact due to rounding etc.
|
|
|
|
*/
|
|
|
|
if( abs( level_width - expected_level_width ) > 5 ||
|
|
|
|
level_width < 2 )
|
|
|
|
return;
|
|
|
|
if( abs( level_height - expected_level_height ) > 5 ||
|
|
|
|
level_height < 2 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
thumbnail->level_width[i] = level_width;
|
|
|
|
thumbnail->level_height[i] = level_height;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now set level_count. This signals that we've found a pyramid.
|
|
|
|
*/
|
|
|
|
#ifdef DEBUG
|
|
|
|
printf( "vips_thumbnail_get_tiff_pyramid: %d layer pyramid detected\n",
|
|
|
|
thumbnail->n_pages );
|
|
|
|
#endif /*DEBUG*/
|
|
|
|
thumbnail->level_count = thumbnail->n_pages;
|
|
|
|
}
|
|
|
|
|
2019-01-30 17:19:11 +01:00
|
|
|
static int
|
|
|
|
vips_thumbnail_get_heif_thumb_info( VipsThumbnail *thumbnail )
|
|
|
|
{
|
|
|
|
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
|
|
|
|
|
|
|
|
VipsImage *thumb;
|
|
|
|
|
|
|
|
if( !(thumb = class->open( thumbnail, 1 )) )
|
|
|
|
return( -1 );
|
|
|
|
|
|
|
|
if( thumb->Xsize < thumbnail->input_width ) {
|
|
|
|
thumbnail->heif_thumbnail_width = thumb->Xsize;
|
|
|
|
thumbnail->heif_thumbnail_height = thumb->Ysize;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIPS_UNREF( thumb );
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2016-10-31 12:14:54 +01:00
|
|
|
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
|
|
|
|
* and so on.
|
2017-05-05 10:45:21 +02:00
|
|
|
*
|
|
|
|
* 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
|
|
|
*/
|
2017-05-01 18:22:03 +02:00
|
|
|
static void
|
2016-10-31 12:14:54 +01:00
|
|
|
vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
|
2017-05-01 18:22:03 +02:00
|
|
|
int input_width, int input_height, double *hshrink, double *vshrink )
|
2016-10-31 12:14:54 +01:00
|
|
|
{
|
2017-05-05 10:45:21 +02:00
|
|
|
/* If we will be rotating, swap the target width and height.
|
|
|
|
*/
|
2016-11-02 10:29:19 +01:00
|
|
|
gboolean rotate =
|
2017-05-05 10:45:21 +02:00
|
|
|
(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.
|
|
|
|
*/
|
2017-05-05 10:45:21 +02:00
|
|
|
*hshrink = (double) input_width / target_width;
|
|
|
|
*vshrink = (double) input_height / target_height;
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2017-03-08 15:31:00 +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;
|
|
|
|
}
|
|
|
|
|
2017-05-01 18:22:03 +02:00
|
|
|
if( thumbnail->size != VIPS_SIZE_FORCE ) {
|
|
|
|
if( direction == VIPS_DIRECTION_HORIZONTAL )
|
2017-05-04 15:54:49 +02:00
|
|
|
*vshrink = *hshrink;
|
2017-05-01 18:22:03 +02:00
|
|
|
else
|
2017-05-04 15:54:49 +02:00
|
|
|
*hshrink = *vshrink;
|
2017-05-01 18:22:03 +02:00
|
|
|
}
|
|
|
|
|
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-01 18:22:03 +02:00
|
|
|
}
|
|
|
|
|
2017-05-04 15:54:49 +02:00
|
|
|
/* Just the common part of the shrink: the bit by which both axes must be
|
2017-05-01 18:22:03 +02:00
|
|
|
* shrunk.
|
|
|
|
*/
|
|
|
|
static double
|
|
|
|
vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail,
|
2017-05-04 15:54:49 +02:00
|
|
|
int width, int height )
|
2017-05-01 18:22:03 +02:00
|
|
|
{
|
|
|
|
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
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail,
|
|
|
|
int width, int height )
|
2016-10-31 12:14:54 +01:00
|
|
|
{
|
2017-05-01 18:22:03 +02:00
|
|
|
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
|
2017-03-03 15:34:22 +01:00
|
|
|
* 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 );
|
|
|
|
}
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Find the best openslide level.
|
|
|
|
*/
|
|
|
|
static int
|
2018-11-16 19:00:25 +01:00
|
|
|
vips_thumbnail_find_pyrlevel( VipsThumbnail *thumbnail,
|
2018-10-31 15:07:13 +01:00
|
|
|
int width, int height )
|
|
|
|
{
|
|
|
|
int level;
|
|
|
|
|
|
|
|
g_assert( thumbnail->level_count > 0 );
|
|
|
|
g_assert( thumbnail->level_count <= MAX_LEVELS );
|
|
|
|
|
|
|
|
for( level = thumbnail->level_count - 1; level >= 0; level-- )
|
|
|
|
if( vips_thumbnail_calculate_common_shrink( thumbnail,
|
|
|
|
thumbnail->level_width[level],
|
|
|
|
thumbnail->level_height[level] ) >= 1.0 )
|
|
|
|
return( level );
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2016-10-31 12:14:54 +01:00
|
|
|
/* 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;
|
2018-10-31 15:07:13 +01:00
|
|
|
double factor;
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2016-11-02 10:29:19 +01:00
|
|
|
if( class->get_info( thumbnail ) )
|
2016-10-31 12:14:54 +01:00
|
|
|
return( NULL );
|
2017-01-03 16:52:27 +01:00
|
|
|
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
|
|
|
|
2018-11-16 19:00:25 +01:00
|
|
|
/* For tiff, we need to make a separate get_info() for each page to
|
|
|
|
* get all the pyramid levels.
|
|
|
|
*/
|
|
|
|
if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) )
|
|
|
|
vips_thumbnail_get_tiff_pyramid( thumbnail );
|
|
|
|
|
2019-01-30 17:19:11 +01:00
|
|
|
/* For heif, we need to fetch the thumbnail size, in case we can use
|
|
|
|
* that as the source.
|
|
|
|
*/
|
|
|
|
if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) )
|
|
|
|
vips_thumbnail_get_heif_thumb_info( thumbnail );
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
factor = 1.0;
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2016-11-02 10:29:19 +01:00
|
|
|
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
|
2018-10-31 15:07:13 +01:00
|
|
|
factor = 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
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
g_info( "loading jpeg with factor %g pre-shrink", factor );
|
|
|
|
}
|
2018-11-16 19:00:25 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ||
|
|
|
|
vips_isprefix( "VipsForeignLoadOpenslide", thumbnail->loader ) ) {
|
|
|
|
factor = vips_thumbnail_find_pyrlevel( thumbnail,
|
2018-10-31 15:07:13 +01:00
|
|
|
thumbnail->input_width, thumbnail->input_height );
|
|
|
|
|
2018-11-16 19:00:25 +01:00
|
|
|
g_info( "loading pyr level %g", factor );
|
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 ) ) {
|
2018-10-31 15:07:13 +01:00
|
|
|
factor = 1.0 /
|
|
|
|
vips_thumbnail_calculate_common_shrink( thumbnail,
|
|
|
|
thumbnail->input_width,
|
|
|
|
thumbnail->input_height );
|
2017-05-01 18:22:03 +02:00
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
g_info( "loading PDF/SVG with factor %g pre-scale", factor );
|
2016-10-31 12:14:54 +01:00
|
|
|
}
|
2019-01-30 17:19:11 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
|
|
|
|
/* 'factor' is a gboolean which enables thumbnail load instead
|
|
|
|
* of image load.
|
|
|
|
*
|
|
|
|
* Use the thumbnail if it's larger than our target.
|
|
|
|
*/
|
|
|
|
if( thumbnail->heif_thumbnail_width >= thumbnail->width &&
|
|
|
|
thumbnail->heif_thumbnail_height >= thumbnail->height )
|
|
|
|
factor = 1.0;
|
|
|
|
else
|
|
|
|
factor = 0.0;
|
|
|
|
|
|
|
|
}
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2019-04-24 18:04:31 +02:00
|
|
|
/* Webp supports shrink-on-load, but unfortunately the filter is just
|
|
|
|
* too odd.
|
|
|
|
*
|
|
|
|
* Perhaps reenable this if webp improves.
|
|
|
|
*
|
|
|
|
* vips_thumbnail_file_open() and vips_thumbnail_buffer_open() would
|
|
|
|
* need additional cases as well.
|
|
|
|
*
|
|
|
|
else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
|
|
|
|
factor = VIPS_MAX( 1.0,
|
|
|
|
vips_thumbnail_calculate_common_shrink( thumbnail,
|
|
|
|
thumbnail->input_width,
|
|
|
|
thumbnail->input_height ) );
|
|
|
|
|
|
|
|
g_info( "loading webp with factor %g pre-shrink", factor );
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
if( !(im = class->open( thumbnail, factor )) )
|
2016-10-31 12:14:54 +01:00
|
|
|
return( NULL );
|
|
|
|
|
2018-11-16 19:00:25 +01:00
|
|
|
g_info( "pre-shrunk size is %d x %d", im->Xsize, im->Ysize );
|
|
|
|
|
2016-10-31 12:14:54 +01:00
|
|
|
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;
|
2017-05-01 18:22:03 +02:00
|
|
|
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 );
|
|
|
|
|
2018-10-31 13:30:37 +01:00
|
|
|
/* We have to support both no_rotate and auto_rotate optional args,
|
|
|
|
* with no_rotate being the new and not-deprecated one.
|
|
|
|
*
|
|
|
|
* If the new no_rotate flag has been set, that value overrides
|
|
|
|
* auto_rotate.
|
|
|
|
*/
|
|
|
|
if( vips_object_argument_isset( object, "no_rotate" ) )
|
|
|
|
thumbnail->auto_rotate = !thumbnail->no_rotate;
|
|
|
|
|
2016-11-02 12:07:30 +01:00
|
|
|
if( !vips_object_argument_isset( object, "height" ) )
|
|
|
|
thumbnail->height = thumbnail->width;
|
|
|
|
|
2019-02-19 18:27:23 +01:00
|
|
|
/* Open and do any pre-shrinking.
|
|
|
|
*/
|
2016-11-02 10:29:19 +01:00
|
|
|
if( !(t[0] = vips_thumbnail_open( thumbnail )) )
|
|
|
|
return( -1 );
|
|
|
|
in = t[0];
|
|
|
|
|
2019-02-19 18:27:23 +01:00
|
|
|
/* So page_height is after pre-shrink, but before the main shrink
|
|
|
|
* stage.
|
|
|
|
*/
|
|
|
|
thumbnail->page_height = vips_image_get_page_height( in );
|
|
|
|
|
2016-11-02 10:29:19 +01:00
|
|
|
/* RAD needs special unpacking.
|
|
|
|
*/
|
|
|
|
if( in->Coding == VIPS_CODING_RAD ) {
|
2017-01-03 16:52:27 +01:00
|
|
|
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
|
2019-02-19 18:27:23 +01:00
|
|
|
* vips_colourspace() (see below) doesn't let you specify the fallback
|
|
|
|
* profile.
|
2016-11-02 10:29:19 +01:00
|
|
|
*
|
|
|
|
* 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;
|
2019-04-23 12:35:13 +02:00
|
|
|
if( thumbnail->linear &&
|
2016-11-02 10:29:19 +01:00
|
|
|
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) ) {
|
2018-01-01 19:52:54 +01:00
|
|
|
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,
|
2017-08-30 17:34:46 +02:00
|
|
|
"intent", thumbnail->intent,
|
2016-11-02 10:29:19 +01:00
|
|
|
"pcs", VIPS_PCS_XYZ,
|
|
|
|
NULL ) )
|
|
|
|
return( -1 );
|
|
|
|
|
|
|
|
in = t[1];
|
|
|
|
|
|
|
|
have_imported = TRUE;
|
|
|
|
}
|
|
|
|
|
2019-04-23 18:09:20 +02:00
|
|
|
/* To the processing colourspace. This will unpack LABQ, import CMYK,
|
|
|
|
* etc.
|
|
|
|
*
|
|
|
|
* If this is a CMYK image, we need to set have_imported since we only
|
|
|
|
* want to export at the end.
|
2016-11-02 10:29:19 +01:00
|
|
|
*/
|
2019-04-23 18:09:20 +02:00
|
|
|
if( in->Type == VIPS_INTERPRETATION_CMYK )
|
|
|
|
have_imported = TRUE;
|
2017-01-03 16:52:27 +01:00
|
|
|
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 ) ) {
|
2017-01-03 16:52:27 +01:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2019-04-24 18:04:31 +02:00
|
|
|
/* Shrink to page_height, so we work for multi-page images.
|
2019-02-19 18:27:23 +01:00
|
|
|
*/
|
2017-05-04 15:54:49 +02:00
|
|
|
vips_thumbnail_calculate_shrink( thumbnail,
|
2019-02-19 18:27:23 +01:00
|
|
|
in->Xsize, thumbnail->page_height, &hshrink, &vshrink );
|
2016-11-02 10:29:19 +01:00
|
|
|
|
2019-04-27 23:33:20 +02:00
|
|
|
/* In toilet-roll mode, we must adjust vshrink so that we exactly hit
|
|
|
|
* page_height or we'll have pixels straddling pixel boundaries.
|
|
|
|
*/
|
|
|
|
if( in->Ysize > thumbnail->page_height ) {
|
|
|
|
int target_page_height = VIPS_RINT(
|
|
|
|
thumbnail->page_height / vshrink );
|
|
|
|
int target_image_height = target_page_height *
|
|
|
|
thumbnail->n_pages;
|
|
|
|
|
|
|
|
vshrink = (double) in->Ysize / target_image_height;
|
|
|
|
}
|
|
|
|
|
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];
|
|
|
|
|
2019-03-04 10:55:53 +01:00
|
|
|
thumbnail->page_height = VIPS_RINT( thumbnail->page_height / vshrink );
|
2019-02-19 18:27:23 +01:00
|
|
|
vips_image_set_int( in, VIPS_META_PAGE_HEIGHT, thumbnail->page_height );
|
|
|
|
|
2016-11-02 10:29:19 +01:00
|
|
|
if( have_premultiplied ) {
|
2017-01-03 16:52:27 +01:00
|
|
|
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 ) ) {
|
2017-01-03 16:52:27 +01:00
|
|
|
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,
|
2017-08-30 17:34:46 +02:00
|
|
|
"intent", thumbnail->intent,
|
2016-11-02 10:29:19 +01:00
|
|
|
NULL ) )
|
|
|
|
return( -1 );
|
|
|
|
in = t[7];
|
|
|
|
}
|
|
|
|
else {
|
2017-01-03 16:52:27 +01:00
|
|
|
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) ) {
|
2018-01-01 19:52:54 +01:00
|
|
|
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
|
|
|
|
2018-01-01 19:52:54 +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 );
|
|
|
|
|
2017-01-03 16:52:27 +01:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2017-02-23 21:26:59 +01:00
|
|
|
/* Crop after rotate so we don't need to rotate the crop box.
|
|
|
|
*/
|
2017-03-08 15:31:00 +01:00
|
|
|
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
|
2017-02-23 21:26:59 +01:00
|
|
|
g_info( "cropping to %dx%d",
|
|
|
|
thumbnail->width, thumbnail->height );
|
|
|
|
|
2017-03-08 15:31:00 +01:00
|
|
|
/* 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 ) )
|
2017-02-23 21:26:59 +01:00
|
|
|
return( -1 );
|
2017-03-08 15:31:00 +01:00
|
|
|
in = t[11];
|
2017-02-23 21:26:59 +01:00
|
|
|
}
|
|
|
|
|
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 );
|
2017-05-29 11:19:21 +02:00
|
|
|
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
|
2016-10-31 12:14:54 +01:00
|
|
|
|
2017-01-10 15:12:24 +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;
|
|
|
|
|
2017-05-29 11:19:21 +02:00
|
|
|
/* 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 );
|
|
|
|
|
2018-10-31 13:30:37 +01:00
|
|
|
VIPS_ARG_BOOL( class, "no_rotate", 115,
|
|
|
|
_( "No rotate" ),
|
|
|
|
_( "Don't use orientation tags to rotate image upright" ),
|
2016-11-02 10:29:19 +01:00
|
|
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
2018-10-31 13:30:37 +01:00
|
|
|
G_STRUCT_OFFSET( VipsThumbnail, no_rotate ),
|
|
|
|
FALSE );
|
2016-11-02 10:29:19 +01:00
|
|
|
|
2017-03-08 15:31:00 +01:00
|
|
|
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 ),
|
2017-03-08 15:31:00 +01:00
|
|
|
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 );
|
|
|
|
|
2017-08-30 17:34:46 +02:00
|
|
|
VIPS_ARG_ENUM( class, "intent", 120,
|
|
|
|
_( "Intent" ),
|
|
|
|
_( "Rendering intent" ),
|
|
|
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
|
|
|
G_STRUCT_OFFSET( VipsThumbnail, intent ),
|
|
|
|
VIPS_TYPE_INTENT, VIPS_INTENT_RELATIVE );
|
|
|
|
|
2018-10-31 13:30:37 +01:00
|
|
|
/* BOOL args which default TRUE arguments don't work with the
|
2018-10-31 15:09:57 +01:00
|
|
|
* command-line -- GOption does not allow --auto-rotate=false.
|
2018-10-31 13:30:37 +01:00
|
|
|
*
|
|
|
|
* This is now replaced (though still functional) with "no-rotate",
|
|
|
|
* see above.
|
|
|
|
*/
|
|
|
|
VIPS_ARG_BOOL( class, "auto_rotate", 121,
|
|
|
|
_( "Auto rotate" ),
|
|
|
|
_( "Use orientation tags to rotate image upright" ),
|
|
|
|
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
|
|
|
G_STRUCT_OFFSET( VipsThumbnail, auto_rotate ),
|
|
|
|
TRUE );
|
|
|
|
|
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;
|
2017-08-30 17:34:46 +02:00
|
|
|
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
|
|
|
|
2017-01-03 16:52:27 +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
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_read_header( thumbnail, 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
|
|
|
}
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Open an image, pre-shrinking as appropriate.
|
2016-11-02 10:29:19 +01:00
|
|
|
*/
|
|
|
|
static VipsImage *
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor )
|
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
|
|
|
|
2019-04-24 18:04:31 +02:00
|
|
|
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
2018-10-31 15:07:13 +01:00
|
|
|
"shrink", (int) factor,
|
2016-11-02 12:07:30 +01:00
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
|
|
|
else if( vips_isprefix( "VipsForeignLoadOpenslide",
|
|
|
|
thumbnail->loader ) ) {
|
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"level", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
|
|
|
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
|
2019-03-13 17:32:04 +01:00
|
|
|
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
2018-10-31 15:07:13 +01:00
|
|
|
"scale", factor,
|
2016-11-02 12:07:30 +01:00
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
2018-11-16 19:00:25 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
|
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"page", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
2019-01-30 17:19:11 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
|
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"thumbnail", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
2018-10-31 15:07:13 +01:00
|
|
|
else {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_file( file->filename,
|
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
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
|
2017-09-30 18:26:55 +02:00
|
|
|
* @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
|
2017-05-05 15:13:49 +02:00
|
|
|
* * @size: #VipsSize, upsize, downsize, both or force
|
2018-10-31 13:30:37 +01:00
|
|
|
* * @no_rotate: %gboolean, don't rotate upright using orientation tag
|
2017-03-08 15:31:00 +01:00
|
|
|
* * @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
|
2017-08-30 17:34:46 +02:00
|
|
|
* * @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
|
2017-03-08 15:31:00 +01:00
|
|
|
* @height rectangle, with any excess cropped away. See vips_smartcrop() for
|
|
|
|
* details on the cropping strategy.
|
2016-11-02 12:19:08 +01:00
|
|
|
*
|
2017-05-01 18:22:03 +02: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.
|
2017-05-01 18:22:03 +02:00
|
|
|
* 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
|
2018-10-31 13:30:37 +01:00
|
|
|
* interpreted to rotate the image upright. If you set @no_rotate to %TRUE,
|
2016-11-02 12:19:08 +01:00
|
|
|
* these tags will not be interpreted.
|
|
|
|
*
|
|
|
|
* Shrinking is normally done in sRGB colourspace. Set @linear to shrink in
|
2017-05-05 15:13:49 +02:00
|
|
|
* 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
|
|
|
*
|
2017-08-30 17:34:46 +02: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;
|
2019-02-03 12:29:54 +01:00
|
|
|
char *option_string;
|
2016-11-02 12:07:30 +01:00
|
|
|
} 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;
|
|
|
|
|
2017-01-03 16:52:27 +01:00
|
|
|
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(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string, NULL )) )
|
2016-11-02 12:07:30 +01:00
|
|
|
return( -1 );
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_read_header( thumbnail, image );
|
2016-11-02 12:07:30 +01:00
|
|
|
|
|
|
|
g_object_unref( image );
|
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
/* Open an image, scaling as appropriate.
|
2016-11-02 12:07:30 +01:00
|
|
|
*/
|
|
|
|
static VipsImage *
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor )
|
2016-11-02 12:07:30 +01:00
|
|
|
{
|
|
|
|
VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail;
|
|
|
|
|
2019-04-24 18:04:31 +02:00
|
|
|
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
|
2018-10-31 15:07:13 +01:00
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2018-10-31 15:07:13 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"shrink", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
|
|
|
else if( vips_isprefix( "VipsForeignLoadOpenslide",
|
|
|
|
thumbnail->loader ) ) {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2016-11-02 12:07:30 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
2018-10-31 15:07:13 +01:00
|
|
|
"level", (int) factor,
|
2016-11-02 12:07:30 +01:00
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
|
|
|
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
|
2019-03-13 17:32:04 +01:00
|
|
|
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2016-11-02 12:07:30 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
2018-10-31 15:07:13 +01:00
|
|
|
"scale", factor,
|
2016-11-02 12:07:30 +01:00
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
2018-11-16 19:00:25 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
|
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2018-11-16 19:00:25 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"page", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
2019-01-30 17:19:11 +01:00
|
|
|
else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
|
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2019-01-30 17:19:11 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
"thumbnail", (int) factor,
|
|
|
|
NULL ) );
|
|
|
|
}
|
2018-10-31 15:07:13 +01:00
|
|
|
else {
|
2016-11-02 12:07:30 +01:00
|
|
|
return( vips_image_new_from_buffer(
|
2019-03-04 10:55:53 +01:00
|
|
|
buffer->buf->data, buffer->buf->length,
|
|
|
|
buffer->option_string,
|
2016-11-02 12:07:30 +01:00
|
|
|
"access", VIPS_ACCESS_SEQUENTIAL,
|
|
|
|
NULL ) );
|
2018-10-31 15:07:13 +01:00
|
|
|
}
|
2016-11-02 12:07:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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 );
|
|
|
|
|
2019-02-03 12:29:54 +01:00
|
|
|
VIPS_ARG_STRING( class, "option_string", 20,
|
|
|
|
_( "Extra options" ),
|
|
|
|
_( "Options that are passed on to the underlying loader" ),
|
2019-02-02 09:26:12 +01:00
|
|
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
2019-02-03 12:29:54 +01:00
|
|
|
G_STRUCT_OFFSET( VipsThumbnailBuffer, option_string ),
|
2019-02-02 09:26:12 +01:00
|
|
|
"" );
|
2016-11-02 12:07:30 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
2017-09-30 18:26:55 +02:00
|
|
|
* @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
|
2017-05-05 15:13:49 +02:00
|
|
|
* * @size: #VipsSize, upsize, downsize, both or force
|
2018-10-31 13:30:37 +01:00
|
|
|
* * @no_rotate: %gboolean, don't rotate upright using orientation tag
|
2017-03-08 15:31:00 +01:00
|
|
|
* * @crop: #VipsInteresting, shrink and crop to fill target
|
2016-11-02 12:07:30 +01:00
|
|
|
* * @linear: %gboolean, perform shrink in linear light
|
2017-05-12 19:20:37 +02:00
|
|
|
* * @import_profile: %gchararray, fallback import ICC profile
|
|
|
|
* * @export_profile: %gchararray, export ICC profile
|
2017-08-30 17:34:46 +02:00
|
|
|
* * @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 );
|
|
|
|
}
|
2017-05-12 19:20:37 +02:00
|
|
|
|
|
|
|
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";
|
|
|
|
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_read_header( thumbnail, image->in );
|
2017-05-12 19:20:37 +02:00
|
|
|
|
|
|
|
return( 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open an image. We can't pre-shrink with an image source, sadly.
|
|
|
|
*/
|
|
|
|
static VipsImage *
|
2018-10-31 15:07:13 +01:00
|
|
|
vips_thumbnail_image_open( VipsThumbnail *thumbnail, double factor )
|
2017-05-12 19:20:37 +02:00
|
|
|
{
|
|
|
|
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 )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-09-30 18:26:55 +02:00
|
|
|
* vips_thumbnail_image: (method)
|
2017-05-12 19:20:37 +02:00
|
|
|
* @in: input image
|
2017-09-30 18:26:55 +02:00
|
|
|
* @out: (out): output image
|
2017-05-12 19:20:37 +02: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
|
2018-10-31 13:30:37 +01:00
|
|
|
* * @no_rotate: %gboolean, don't rotate upright using orientation tag
|
2017-05-12 19:20:37 +02:00
|
|
|
* * @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
|
2017-08-30 17:34:46 +02:00
|
|
|
* * @intent: #VipsIntent, rendering intent
|
2017-05-12 19:20:37 +02:00
|
|
|
*
|
|
|
|
* 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 );
|
|
|
|
}
|