Fix gif dispose handling for DISPOSE_BACKGROUND and DISPOSE_PREVIOUS

- Add 'scratch' field to gif that holds temporary 'scratch buffer' used for rendering frames
- For DISPOSE_BACKGROUND: Set background color to transparent instead of 0
- For DISPOSE_BACKGROUND: Write background pixels into scratch after rendering current frame, so it will be used in next frame
- For DISPOSE_PREVIOUS: Save frames that are not disposed into 'previous' field in gif, when DISPOSE_PREVIOUS is specified start with that previous frame. see http://webreference.com/content/studio/disposal.html
- Add "ANIMEXTS1.0" to Application Extension parser
- Graphic Control Extension parser refactor
- Compare file contents to expected images for animated gifs in foreign tests
This commit is contained in:
alon-ne 2020-02-06 17:36:13 +02:00 committed by GitHub
parent 165a3a3855
commit f88dab9ccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 765 additions and 434 deletions

View File

@ -4,6 +4,17 @@
- add max and min to region shrink [rgluskin]
- allow \ as an escape character in vips_break_token() [akemrir]
- tiffsave has a "depth" param to set max pyr depth
- libtiff LOGLUV images load and save as libvips XYZ
- add gifload_source
- revise vipsthumbnail flags
- add VIPS_LEAK env var
- add vips_pipe_read_limit_set(), --vips-pipe-read-limit,
VIPS_PIPE_READ_LIMIT
31/1/19 started 8.9.2
- fix a deadlock with --vips-leak [DarthSim]
- better gifload behaviour for DISPOSAL_UNSPECIFIED [DarthSim]
- ban ppm max_value < 0
20/6/19 started 8.9.1
- don't use the new source loaders for new_from_file or new_from_buffer, it

View File

@ -1640,9 +1640,19 @@ vips_foreign_save_build( VipsObject *object )
save->background ) )
return( -1 );
if( save->page_height )
if( save->page_height ) {
VipsImage *x;
if( vips_copy( ready, &x, NULL ) ) {
VIPS_UNREF( ready );
return( -1 );
}
VIPS_UNREF( ready );
ready = x;
vips_image_set_int( ready,
VIPS_META_PAGE_HEIGHT, save->page_height );
}
VIPS_UNREF( save->ready );
save->ready = ready;
@ -2097,6 +2107,7 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_gif_file_get_type( void );
extern GType vips_foreign_load_gif_buffer_get_type( void );
extern GType vips_foreign_load_gif_source_get_type( void );
vips_foreign_load_csv_get_type();
vips_foreign_save_csv_get_type();
@ -2148,6 +2159,7 @@ vips_foreign_operation_init( void )
#ifdef HAVE_GIFLIB
vips_foreign_load_gif_file_get_type();
vips_foreign_load_gif_buffer_get_type();
vips_foreign_load_gif_source_get_type();
#endif /*HAVE_GIFLIB*/
#ifdef HAVE_GSF

File diff suppressed because it is too large Load Diff

View File

@ -35,6 +35,8 @@
* - redone with source/target
* - sequential load, plus mmap for filename sources
* - faster plus lower memory use
* 02/02/2020
* - ban max_vaue < 0
*/
/*
@ -259,6 +261,12 @@ vips_foreign_load_ppm_parse_header( VipsForeignLoadPpm *ppm )
if( get_int( ppm->sbuf, &ppm->max_value ) )
return( -1 );
/* max_value must be > 0 and <= 65535, according to
* the spec, but we allow up to 32 bits per pixel.
*/
if( ppm->max_value < 0 )
ppm->max_value = 0;
if( ppm->max_value > 255 )
ppm->bits = 16;
if( ppm->max_value > 65535 )

View File

@ -193,6 +193,8 @@
* - switch to source input
* 18/11/19
* - support ASSOCALPHA in any alpha band
* 27/1/20
* - read logluv images as XYZ
*/
/*
@ -294,6 +296,10 @@ typedef struct _RtiffHeader {
*/
uint32 read_height;
tsize_t read_size;
/* Scale factor to get absolute cd/m2 from XYZ.
*/
double stonits;
} RtiffHeader;
/* Scanline-type process function.
@ -911,6 +917,52 @@ rtiff_parse_labs( Rtiff *rtiff, VipsImage *out )
return( 0 );
}
/* libtiff delivers logluv as illuminant-free 0-1 XYZ in 3 x float.
*/
static void
rtiff_logluv_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
{
int samples_per_pixel = rtiff->header.samples_per_pixel;
float *p1;
float *q1;
int x;
int i;
p1 = (float *) p;
q1 = (float *) q;
for( x = 0; x < n; x++ ) {
q1[0] = VIPS_D65_X0 * p1[0];
q1[1] = VIPS_D65_Y0 * p1[1];
q1[2] = VIPS_D65_Z0 * p1[2];
for( i = 3; i < samples_per_pixel; i++ )
q1[i] = p1[i];
q1 += samples_per_pixel;
p1 += samples_per_pixel;
}
}
/* LOGLUV images arrive from libtiff as float xyz.
*/
static int
rtiff_parse_logluv( Rtiff *rtiff, VipsImage *out )
{
if( rtiff_check_min_samples( rtiff, 3 ) ||
rtiff_check_interpretation( rtiff, PHOTOMETRIC_LOGLUV ) )
return( -1 );
out->Bands = rtiff->header.samples_per_pixel;
out->BandFmt = VIPS_FORMAT_FLOAT;
out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_XYZ;
rtiff->sfn = rtiff_logluv_line;
return( 0 );
}
/* Per-scanline process function for 1 bit images.
*/
static void
@ -1406,6 +1458,9 @@ rtiff_pick_reader( Rtiff *rtiff )
return( rtiff_parse_labs );
}
if( photometric_interpretation == PHOTOMETRIC_LOGLUV )
return( rtiff_parse_logluv );
if( photometric_interpretation == PHOTOMETRIC_MINISWHITE ||
photometric_interpretation == PHOTOMETRIC_MINISBLACK ) {
if( bits_per_sample == 1 )
@ -1436,6 +1491,15 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out )
TIFFSetField( rtiff->tiff,
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
/* Ask for LOGLUV as 3 x float XYZ.
*/
if( rtiff->header.photometric_interpretation == PHOTOMETRIC_LOGLUV ) {
TIFFSetField( rtiff->tiff,
TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
vips_image_set_double( out, "stonits", rtiff->header.stonits );
}
out->Xsize = rtiff->header.width;
out->Ysize = rtiff->header.height * rtiff->n;
@ -2104,7 +2168,7 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
/* Double check: in memcpy mode, the vips linesize should exactly
* match the tiff line size.
*/
if( rtiff->memcpy ) {
if( rtiff->memcpy ) {
size_t vips_line_size;
/* Lines are smaller in plane-separated mode.
@ -2198,13 +2262,15 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
TIFFGetFieldDefaulted( rtiff->tiff,
TIFFTAG_COMPRESSION, &header->compression );
/* Request YCbCr expansion. libtiff complains if you do this for
* non-jpg images. We must set this here since it changes the result
* of scanline_size.
*/
if( header->compression == COMPRESSION_JPEG )
/* We want to always expand subsampled YCBCR images to full
* RGB.
*/
TIFFSetField( rtiff->tiff,
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
else if( header->photometric_interpretation == PHOTOMETRIC_YCBCR ) {
else if( header->photometric_interpretation == PHOTOMETRIC_YCBCR ) {
/* We rely on the jpg decompressor to upsample chroma
* subsampled images. If there is chroma subsampling but
* no jpg compression, we have to give up.
@ -2223,6 +2289,26 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
}
}
if( header->photometric_interpretation == PHOTOMETRIC_LOGLUV ) {
if( header->compression != COMPRESSION_SGILOG &&
header->compression != COMPRESSION_SGILOG24 ) {
vips_error( "tiff2vips",
"%s", _( "not SGI-compressed LOGLUV" ) );
return( -1 );
}
/* Always get LOGLUV as 3 x float XYZ. We must set this here
* since it'll change the value of scanline_size.
*/
TIFFSetField( rtiff->tiff,
TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
}
/* For logluv, the calibration factor to get to absolute luminance.
*/
if( !TIFFGetField( rtiff->tiff, TIFFTAG_STONITS, &header->stonits ) )
header->stonits = 1.0;
/* Arbitrary sanity-checking limits.
*/
if( header->width <= 0 ||

View File

@ -389,7 +389,7 @@ vips_foreign_load_tiff_buffer_build( VipsObject *object )
VIPS_AREA( buffer->blob )->length )) )
return( -1 );
if( VIPS_OBJECT_CLASS( vips_foreign_load_tiff_file_parent_class )->
if( VIPS_OBJECT_CLASS( vips_foreign_load_tiff_buffer_parent_class )->
build( object ) )
return( -1 );
@ -456,8 +456,8 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer )
* during load
*
* Read a TIFF file into a VIPS image. It is a full baseline TIFF 6 reader,
* with extensions for tiled images, multipage images, LAB colour space,
* pyramidal images and JPEG compression. including CMYK and YCbCr.
* with extensions for tiled images, multipage images, XYZ and LAB colour
* space, pyramidal images and JPEG compression, including CMYK and YCbCr.
*
* @page means load this page from the file. By default the first page (page
* 0) is read.

View File

@ -551,6 +551,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer )
* good for 1-bit images, and deflate is the best lossless compression TIFF
* can do.
*
* XYZ images are automatically saved as libtiff LOGLUV with SGILOG compression.
* Float LAB images are saved as float CIELAB. Set @squash to save as 8-bit
* CIELAB.
*
* Use @Q to set the JPEG compression factor. Default 75.
*
* User @level to set the ZSTD compression level. Use @lossless to

View File

@ -189,6 +189,8 @@
* - "squash" now squashes 3-band float LAB down to LABQ
* 26/1/20
* - add "depth" to set pyr depth
* 27/1/20
* - write XYZ images as logluv
*/
/*
@ -608,7 +610,6 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
{
TIFF *tif = layer->tif;
int format;
int orientation;
/* Output base header fields.
@ -702,6 +703,21 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
photometric = PHOTOMETRIC_CIELAB;
colour_bands = 3;
}
else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) {
double stonits;
photometric = PHOTOMETRIC_LOGLUV;
/* Tell libtiff we will write as float XYZ.
*/
TIFFSetField( tif,
TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
stonits = 1.0;
if( vips_image_get_typeof( wtiff->ready, "stonits" ) )
vips_image_get_double( wtiff->ready,
"stonits", &stonits );
TIFFSetField( tif, TIFFTAG_STONITS, stonits );
colour_bands = 3;
}
else if( wtiff->ready->Type == VIPS_INTERPRETATION_CMYK &&
wtiff->ready->Bands >= 4 ) {
photometric = PHOTOMETRIC_SEPARATED;
@ -764,17 +780,23 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE );
/* Sample format.
*
* Don't set for logluv: libtiff does this for us.
*/
format = SAMPLEFORMAT_UINT;
if( vips_band_format_isuint( wtiff->ready->BandFmt ) )
if( wtiff->input->Type != VIPS_INTERPRETATION_XYZ ) {
int format;
format = SAMPLEFORMAT_UINT;
else if( vips_band_format_isint( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_INT;
else if( vips_band_format_isfloat( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_IEEEFP;
else if( vips_band_format_iscomplex( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_COMPLEXIEEEFP;
TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format );
if( vips_band_format_isuint( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_UINT;
else if( vips_band_format_isint( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_INT;
else if( vips_band_format_isfloat( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_IEEEFP;
else if( vips_band_format_iscomplex( wtiff->ready->BandFmt ) )
format = SAMPLEFORMAT_COMPLEXIEEEFP;
TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format );
}
return( 0 );
}
@ -1039,6 +1061,11 @@ wtiff_new( VipsImage *input, const char *filename,
return( NULL );
}
/* XYZ images are written as libtiff LOGLUV.
*/
if( wtiff->ready->Type == VIPS_INTERPRETATION_XYZ )
wtiff->compression = COMPRESSION_SGILOG;
/* Multipage image?
*/
if( wtiff->page_height < wtiff->ready->Ysize ) {
@ -1331,6 +1358,31 @@ LabS2Lab16( VipsPel *q, VipsPel *p, int n, int samples_per_pixel )
}
}
/* Convert VIPS D65 XYZ to TIFF scaled float illuminant-free xyz.
*/
static void
XYZ2tiffxyz( VipsPel *q, VipsPel *p, int n, int samples_per_pixel )
{
float *p1 = (float *) p;
float *q1 = (float *) q;
int x;
for( x = 0; x < n; x++ ) {
int i;
q1[0] = p1[0] / VIPS_D65_X0;
q1[1] = p1[1] / VIPS_D65_Y0;
q1[2] = p1[2] / VIPS_D65_Z0;
for( i = 3; i < samples_per_pixel; i++ )
q1[i] = p1[i];
q1 += samples_per_pixel;
p1 += samples_per_pixel;
}
}
/* Pack the pixels in @area from @in into a TIFF tile buffer.
*/
static void
@ -1358,6 +1410,8 @@ wtiff_pack2tiff( Wtiff *wtiff, Layer *layer,
LabQ2LabC( q, p, area->width );
else if( wtiff->squash )
eightbit2onebit( wtiff, q, p, area->width );
else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ )
XYZ2tiffxyz( q, p, area->width, in->im->Bands );
else if( (in->im->Bands == 1 || in->im->Bands == 2) &&
wtiff->miniswhite )
invert_band0( wtiff, q, p, area->width );
@ -1449,6 +1503,10 @@ wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
LabS2Lab16( wtiff->tbuf, p, im->Xsize, im->Bands );
p = wtiff->tbuf;
}
else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) {
XYZ2tiffxyz( wtiff->tbuf, p, im->Xsize, im->Bands );
p = wtiff->tbuf;
}
else if( wtiff->squash ) {
eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize );
p = wtiff->tbuf;

View File

@ -89,6 +89,8 @@ GType vips_connection_get_type( void );
const char *vips_connection_filename( VipsConnection *connection );
const char *vips_connection_nick( VipsConnection *connection );
void vips_pipe_read_limit_set( gint64 limit );
#define VIPS_TYPE_SOURCE (vips_source_get_type())
#define VIPS_SOURCE( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \

View File

@ -597,6 +597,8 @@ int vips_gifload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));
int vips_gifload_buffer( void *buf, size_t len, VipsImage **out, ... )
__attribute__((sentinel));
int vips_gifload_source( VipsSource *source, VipsImage **out, ... )
__attribute__((sentinel));
int vips_heifload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));

View File

@ -192,6 +192,8 @@ int vips__view_image( struct _VipsImage *image );
*/
extern int _vips__argument_id;
void vips__meta_init( void );
#ifdef __cplusplus
}
#endif /*__cplusplus*/

View File

@ -62,6 +62,7 @@ extern "C" {
* @VIPS_REGION_SHRINK_MODE: use the mode
* @VIPS_REGION_SHRINK_MAX: use the maximum
* @VIPS_REGION_SHRINK_MIN: use the minimum
* @VIPS_REGION_SHRINK_NEAREST: use the top-left pixel
*
* How to calculate the output pixels when shrinking a 2x2 region.
*/
@ -71,6 +72,7 @@ typedef enum {
VIPS_REGION_SHRINK_MODE,
VIPS_REGION_SHRINK_MAX,
VIPS_REGION_SHRINK_MIN,
VIPS_REGION_SHRINK_NEAREST,
VIPS_REGION_SHRINK_LAST
} VipsRegionShrink;

View File

@ -905,6 +905,7 @@ vips_region_shrink_get_type( void )
{VIPS_REGION_SHRINK_MODE, "VIPS_REGION_SHRINK_MODE", "mode"},
{VIPS_REGION_SHRINK_MAX, "VIPS_REGION_SHRINK_MAX", "max"},
{VIPS_REGION_SHRINK_MIN, "VIPS_REGION_SHRINK_MIN", "min"},
{VIPS_REGION_SHRINK_NEAREST, "VIPS_REGION_SHRINK_NEAREST", "nearest"},
{VIPS_REGION_SHRINK_LAST, "VIPS_REGION_SHRINK_LAST", "last"},
{0, NULL, NULL}
};

View File

@ -36,6 +36,8 @@
* - add vips_image_get_n_pages()
* 20/6/19
* - add vips_image_get/set_array_int()
* 31/1/19
* - lock for metadata changes
*/
/*
@ -130,6 +132,11 @@
* these types, it can be copied between images efficiently.
*/
/* Use in various small places where we need a mutex and it's not worth
* making a private one.
*/
static GMutex *vips__meta_lock = NULL;
/* We have to keep the gtype as a string, since we statically init this.
*/
typedef struct _HeaderField {
@ -934,10 +941,10 @@ meta_cp( VipsImage *dst, const VipsImage *src )
/* We lock with vips_image_set() to stop races in highly-
* threaded applications.
*/
g_mutex_lock( vips__global_lock );
g_mutex_lock( vips__meta_lock );
vips_slist_map2( src->meta_traverse,
(VipsSListMap2Fn) meta_cp_field, dst, NULL );
g_mutex_unlock( vips__global_lock );
g_mutex_unlock( vips__meta_lock );
}
return( 0 );
@ -1025,13 +1032,6 @@ vips_image_set( VipsImage *image, const char *name, GValue *value )
g_assert( name );
g_assert( value );
/* If this image is shared, block metadata changes.
*/
if( G_OBJECT( image )->ref_count > 1 ) {
g_warning( "can't set metadata \"%s\" on shared image", name );
return;
}
meta_init( image );
/* We lock between modifying metadata and copying metadata between
@ -1041,9 +1041,9 @@ vips_image_set( VipsImage *image, const char *name, GValue *value )
* metadata copy on another -- this can lead to crashes in
* highly-threaded applications.
*/
g_mutex_lock( vips__global_lock );
g_mutex_lock( vips__meta_lock );
(void) meta_new( image, name, value );
g_mutex_unlock( vips__global_lock );
g_mutex_unlock( vips__meta_lock );
/* If we're setting an EXIF data block, we need to automatically expand
* out all the tags. This will set things like xres/yres too.
@ -1240,14 +1240,6 @@ vips_image_remove( VipsImage *image, const char *name )
result = FALSE;
/* If this image is shared, block metadata changes.
*/
if( G_OBJECT( image )->ref_count > 1 ) {
g_warning( "can't remove metadata \"%s\" on shared image",
name );
return( result );
}
if( image->meta ) {
/* We lock between modifying metadata and copying metadata
* between images, see meta_cp().
@ -1256,9 +1248,9 @@ vips_image_remove( VipsImage *image, const char *name )
* racing with metadata copy on another -- this can lead to
* crashes in highly-threaded applications.
*/
g_mutex_lock( vips__global_lock );
g_mutex_lock( vips__meta_lock );
result = g_hash_table_remove( image->meta, name );
g_mutex_unlock( vips__global_lock );
g_mutex_unlock( vips__meta_lock );
}
return( result );
@ -2027,3 +2019,12 @@ vips_image_get_history( VipsImage *image )
return( image->Hist ? image->Hist : "" );
}
/* Called during vips_init().
*/
void
vips__meta_init( void )
{
if( !vips__meta_lock )
vips__meta_lock = vips_g_mutex_new();
}

View File

@ -127,6 +127,8 @@ int vips__leak = 0;
GQuark vips__image_pixels_quark = 0;
#endif /*DEBUG_LEAK*/
static gint64 vips_pipe_read_limit = 1024 * 1024 * 1024;
/**
* vips_get_argv0:
*
@ -382,6 +384,7 @@ vips_init( const char *argv0 )
vips__threadpool_init();
vips__buffer_init();
vips__meta_init();
/* This does an unsynchronised static hash table init on first call --
* we have to make sure we do this single-threaded. See:
@ -427,19 +430,20 @@ vips_init( const char *argv0 )
g_free( locale );
bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" );
/* Deprecated, this is just for compat.
*/
if( g_getenv( "VIPS_INFO" ) ||
g_getenv( "IM_INFO" ) )
vips_info_set( TRUE );
if( g_getenv( "VIPS_PROFILE" ) )
vips_profile_set( TRUE );
/* Default various settings from env.
*/
if( g_getenv( "VIPS_LEAK" ) )
vips_leak_set( TRUE );
if( g_getenv( "VIPS_TRACE" ) )
vips_cache_set_trace( TRUE );
if( g_getenv( "VIPS_PIPE_READ_LIMIT" ) )
vips_pipe_read_limit =
g_ascii_strtoll( g_getenv( "VIPS_PIPE_READ_LIMIT" ),
NULL, 10 );
vips_pipe_read_limit_set( vips_pipe_read_limit );
/* Register base vips types.
*/
@ -814,6 +818,9 @@ static GOptionEntry option_entries[] = {
{ "vips-version", 0, G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_version_cb,
N_( "print libvips version" ), NULL },
{ "vips-pipe-read-limit", 0, 0,
G_OPTION_ARG_INT64, (gpointer) &vips_pipe_read_limit,
N_( "read at most this many bytes from a pipe" ), NULL },
{ NULL }
};
@ -1192,8 +1199,8 @@ vips_version( int flag )
* vips_leak_set:
* @leak: turn leak checking on or off
*
* Turn on or off vips leak checking. See also --vips-leak and
* vips_add_option_entries().
* Turn on or off vips leak checking. See also --vips-leak,
* vips_add_option_entries() and the `VIPS_LEAK` environment variable.
*
* You should call this very early in your program.
*/

View File

@ -1235,6 +1235,8 @@ vips_region_shrink_uncoded_mean( VipsRegion *from,
* IS stable with respect to the initial arrangement of input values
*/
#define SHRINK_TYPE_MEDIAN( TYPE ) { \
int ls = VIPS_REGION_LSKIP( from ); \
\
for( x = 0; x < target->width; x++ ) { \
TYPE *tp = (TYPE *) p; \
TYPE *tp1 = (TYPE *) (p + ls); \
@ -1265,6 +1267,8 @@ vips_region_shrink_uncoded_mean( VipsRegion *from,
* IS stable with respect to the initial arrangement of input values
*/
#define SHRINK_TYPE_MODE( TYPE ) { \
int ls = VIPS_REGION_LSKIP( from ); \
\
for( x = 0; x < target->width; x++ ) { \
TYPE *tp = (TYPE *) p; \
TYPE *tp1 = (TYPE *) (p + ls); \
@ -1283,14 +1287,14 @@ vips_region_shrink_uncoded_mean( VipsRegion *from,
tq[z] = v[index]; \
} \
\
/* Move on two pels in input. \
*/ \
p += ps << 1; \
q += ps; \
} \
}
#define SHRINK_TYPE_MAX( TYPE ) { \
int ls = VIPS_REGION_LSKIP( from ); \
\
for( x = 0; x < target->width; x++ ) { \
TYPE *tp = (TYPE *) p; \
TYPE *tp1 = (TYPE *) (p + ls); \
@ -1303,14 +1307,14 @@ vips_region_shrink_uncoded_mean( VipsRegion *from,
); \
} \
\
/* Move on two pels in input. \
*/ \
p += ps << 1; \
q += ps; \
} \
}
#define SHRINK_TYPE_MIN( TYPE ) { \
int ls = VIPS_REGION_LSKIP( from ); \
\
for( x = 0; x < target->width; x++ ) { \
TYPE *tp = (TYPE *) p; \
TYPE *tp1 = (TYPE *) (p + ls); \
@ -1323,8 +1327,19 @@ vips_region_shrink_uncoded_mean( VipsRegion *from,
); \
} \
\
/* Move on two pels in input. \
*/ \
p += ps << 1; \
q += ps; \
} \
}
#define SHRINK_TYPE_NEAREST( TYPE ) { \
for( x = 0; x < target->width; x++ ) { \
TYPE *tp = (TYPE *) p; \
TYPE *tq = (TYPE *) q; \
\
for( z = 0; z < nb; z++ ) \
tq[z] = tp[z]; \
\
p += ps << 1; \
q += ps; \
} \
@ -1335,7 +1350,6 @@ static void \
vips_region_shrink_uncoded_ ## OP( VipsRegion *from, \
VipsRegion *to, const VipsRect *target ) \
{ \
int ls = VIPS_REGION_LSKIP( from ); \
int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); \
int nb = from->im->Bands; \
\
@ -1377,6 +1391,7 @@ VIPS_REGION_SHRINK( MAX );
VIPS_REGION_SHRINK( MIN );
VIPS_REGION_SHRINK( MODE );
VIPS_REGION_SHRINK( MEDIAN );
VIPS_REGION_SHRINK( NEAREST );
/* Generate area @target in @to using pixels in @from. Non-complex.
*/
@ -1405,6 +1420,10 @@ vips_region_shrink_uncoded( VipsRegion *from,
vips_region_shrink_uncoded_MIN( from, to, target );
break;
case VIPS_REGION_SHRINK_NEAREST:
vips_region_shrink_uncoded_NEAREST( from, to, target );
break;
default:
g_assert_not_reached();
}

View File

@ -1,7 +1,10 @@
/* A byte source/sink .. it can be a pipe, file descriptor, memory area,
* socket, node.js stream, etc.
*
* J.Cupitt, 19/6/14
* 19/6/14
*
* 3/2/20
* - add vips_pipe_read_limit_set()
*/
/*
@ -84,10 +87,40 @@
#define MODE_READWRITE BINARYIZE (O_RDWR)
#define MODE_WRITE BINARYIZE (O_WRONLY | O_CREAT | O_TRUNC)
/* -1 on a pipe isn't actually unbounded. Have a limit to prevent
* huge sources accidentally filling memory.
*
* This can be configured with vips_pipe_read_limit_set().
*/
static gint64 vips__pipe_read_limit = 1024 * 1024 * 1024;
/**
* vips_pipe_read_limit_set:
* @limit: maximum number of bytes to buffer from a pipe
*
* If a source does not support mmap or seek and the source is
* used with a loader that can only work from memory, then the data will be
* automatically read into memory to EOF before the loader starts. This can
* produce high memory use if the descriptor represents a large object.
*
* Use vips_pipe_read_limit_set() to limit the size of object that
* will be read in this way. The default is 1GB.
*
* Set a value of -1 to mean no limit.
*
* See also: `--vips-pipe-read-limit` and the environment variable
* `VIPS_PIPE_READ_LIMIT`.
*/
void
vips_pipe_read_limit_set( gint64 limit )
{
vips__pipe_read_limit = limit;
}
G_DEFINE_TYPE( VipsSource, vips_source, VIPS_TYPE_CONNECTION );
/* We can't test for seekability or length during _build, since the read and
* seek signal handlers may not have been connected yet. Instead, we test
* seek signal handlers might not have been connected yet. Instead, we test
* when we first need to know.
*/
static int
@ -184,6 +217,9 @@ vips_source_sanity( VipsSource *source )
g_assert( source->length == -1 );
}
else {
/* Something like a seekable file.
*/
/* After we're done with the header, the sniff buffer should
* be gone.
*/
@ -377,6 +413,14 @@ vips_source_new_from_descriptor( int descriptor )
*
* Create an source attached to a file.
*
* If this descriptor does not support mmap and the source is
* used with a loader that can only work from memory, then the data will be
* automatically read into memory to EOF before the loader starts. This can
* produce high memory use if the descriptor represents a large object.
*
* Use vips_pipe_read_limit_set() to limit the size of object that
* will be read in this way. The default is 1GB.
*
* Returns: a new source.
*/
VipsSource *
@ -715,19 +759,14 @@ vips_source_read( VipsSource *source, void *buffer, size_t length )
return( total_read );
}
/* -1 on a pipe isn't actually unbounded. Have a limit to prevent
* huge sources accidentally filling memory.
*
* 1gb. Why not.
*/
static const int vips_pipe_read_limit = 1024 * 1024 * 1024;
/* Read to a position. -1 means read to end of source. Does not change
* read_position.
*/
static int
vips_source_pipe_read_to_position( VipsSource *source, gint64 target )
{
const char *nick = vips_connection_nick( VIPS_CONNECTION( source ) );
gint64 old_read_position;
unsigned char buffer[4096];
@ -742,7 +781,7 @@ vips_source_pipe_read_to_position( VipsSource *source, gint64 target )
(target < 0 ||
(source->length != -1 &&
target > source->length)) ) {
vips_error( vips_connection_nick( VIPS_CONNECTION( source ) ),
vips_error( nick,
_( "bad read to %" G_GINT64_FORMAT ), target );
return( -1 );
}
@ -760,9 +799,9 @@ vips_source_pipe_read_to_position( VipsSource *source, gint64 target )
break;
if( target == -1 &&
source->read_position > vips_pipe_read_limit ) {
vips_error( vips_connection_nick( VIPS_CONNECTION( source ) ),
"%s", _( "pipe too long" ) );
vips__pipe_read_limit != -1 &&
source->read_position > vips__pipe_read_limit ) {
vips_error( nick, "%s", _( "pipe too long" ) );
return( -1 );
}
}
@ -1013,6 +1052,7 @@ vips_source_map_blob( VipsSource *source )
gint64
vips_source_seek( VipsSource *source, gint64 offset, int whence )
{
const char *nick = vips_connection_nick( VIPS_CONNECTION( source ) );
VipsSourceClass *class = VIPS_SOURCE_GET_CLASS( source );
gint64 new_pos;
@ -1039,8 +1079,7 @@ vips_source_seek( VipsSource *source, gint64 offset, int whence )
break;
default:
vips_error( vips_connection_nick( VIPS_CONNECTION( source ) ),
"%s", _( "bad 'whence'" ) );
vips_error( nick, "%s", _( "bad 'whence'" ) );
return( -1 );
}
}
@ -1066,8 +1105,7 @@ vips_source_seek( VipsSource *source, gint64 offset, int whence )
break;
default:
vips_error( vips_connection_nick( VIPS_CONNECTION( source ) ),
"%s", _( "bad 'whence'" ) );
vips_error( nick, "%s", _( "bad 'whence'" ) );
return( -1 );
}
}
@ -1081,7 +1119,7 @@ vips_source_seek( VipsSource *source, gint64 offset, int whence )
if( new_pos < 0 ||
(source->length != -1 &&
new_pos > source->length) ) {
vips_error( vips_connection_nick( VIPS_CONNECTION( source ) ),
vips_error( nick,
_( "bad seek to %" G_GINT64_FORMAT ), new_pos );
return( -1 );
}

View File

@ -27,6 +27,11 @@ SVG_FILE = os.path.join(IMAGES, "logo.svg")
SVGZ_FILE = os.path.join(IMAGES, "logo.svgz")
SVG_GZ_FILE = os.path.join(IMAGES, "logo.svg.gz")
GIF_ANIM_FILE = os.path.join(IMAGES, "cogs.gif")
GIF_ANIM_EXPECTED_PNG_FILE = os.path.join(IMAGES, "cogs.png")
GIF_ANIM_DISPOSE_BACKGROUND_FILE = os.path.join(IMAGES, "dispose-background.gif")
GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE = os.path.join(IMAGES, "dispose-background.png")
GIF_ANIM_DISPOSE_PREVIOUS_FILE = os.path.join(IMAGES, "dispose-previous.gif")
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE = os.path.join(IMAGES, "dispose-previous.png")
DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm")
BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP")
NIFTI_FILE = os.path.join(IMAGES, "avg152T1_LR_nifti.nii.gz")

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,5 +1,5 @@
# vim: set fileencoding=utf-8 :
import filecmp
import sys
import os
import shutil
@ -12,6 +12,9 @@ from helpers import \
ANALYZE_FILE, GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \
PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, DICOM_FILE, \
BMP_FILE, NIFTI_FILE, ICO_FILE, HEIC_FILE, TRUNCATED_FILE, \
GIF_ANIM_EXPECTED_PNG_FILE, \
GIF_ANIM_DISPOSE_BACKGROUND_FILE, GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, \
GIF_ANIM_DISPOSE_PREVIOUS_FILE, GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
temp_filename, assert_almost_equal_objects, have, skip_if_no
@ -419,11 +422,14 @@ class TestForeign:
assert a.height == b.height
assert a.avg() == b.avg()
# region-shrink added in 8.7
x = pyvips.Image.new_from_file(TIF_FILE)
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mean")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mode")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="median")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="max")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="min")
buf = x.tiffsave_buffer(tile=True, pyramid=True,
region_shrink="nearest")
@skip_if_no("magickload")
def test_magickload(self):
@ -703,6 +709,38 @@ class TestForeign:
x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1)
assert x2.height == 4 * x1.height
animation = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('cogs.png')
assert filecmp.cmp(GIF_ANIM_EXPECTED_PNG_FILE, filename, shallow=False)
@skip_if_no("gifload")
def test_gifload_animation_dispose_background(self):
animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_BACKGROUND_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('dispose-background.png')
assert filecmp.cmp(GIF_ANIM_DISPOSE_BACKGROUND_EXPECTED_PNG_FILE, filename, shallow=False)
@skip_if_no("gifload")
def test_gifload_animation_dispose_previous(self):
animation = pyvips.Image.new_from_file(GIF_ANIM_DISPOSE_PREVIOUS_FILE, n=-1)
filename = temp_filename(self.tempdir, '.png')
animation.write_to_file(filename)
# Uncomment to see output file
# animation.write_to_file('dispose-previous.png')
assert filecmp.cmp(GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, filename, shallow=False)
@skip_if_no("svgload")
def test_svgload(self):
def svg_valid(im):

View File

@ -96,6 +96,10 @@
* - add --intent
* 23/10/17
* - --size Nx didn't work, argh ... thanks jrochkind
* 3/2/20
* - add --no-rotate
* - add --import-profile / --export-profile names
* - back to -o for output
*/
#ifdef HAVE_CONFIG_H
@ -127,8 +131,8 @@ static char *import_profile = NULL;
static gboolean delete_profile = FALSE;
static gboolean linear_processing = FALSE;
static gboolean crop_image = FALSE;
static gboolean no_rotate_image = FALSE;
static char *smartcrop_image = NULL;
static gboolean rotate_image = FALSE;
static char *thumbnail_intent = NULL;
/* Deprecated and unused.
@ -138,25 +142,22 @@ static gboolean nodelete_profile = FALSE;
static gboolean verbose = FALSE;
static char *convolution_mask = NULL;
static char *interpolator = NULL;
static gboolean rotate_image = FALSE;
static GOptionEntry options[] = {
{ "size", 's', 0,
G_OPTION_ARG_STRING, &thumbnail_size,
N_( "shrink to SIZE or to WIDTHxHEIGHT" ),
N_( "SIZE" ) },
{ "output", 'o', G_OPTION_FLAG_HIDDEN,
{ "output", 'o', 0,
G_OPTION_ARG_STRING, &output_format,
N_( "set output to FORMAT" ),
N_( "output to FORMAT" ),
N_( "FORMAT" ) },
{ "format", 'f', 0,
G_OPTION_ARG_STRING, &output_format,
N_( "set output format string to FORMAT" ),
N_( "FORMAT" ) },
{ "eprofile", 'e', 0,
{ "export-profile", 'e', 0,
G_OPTION_ARG_FILENAME, &export_profile,
N_( "export with PROFILE" ),
N_( "PROFILE" ) },
{ "iprofile", 'i', 0,
{ "import-profile", 'i', 0,
G_OPTION_ARG_FILENAME, &import_profile,
N_( "import untagged images with PROFILE" ),
N_( "PROFILE" ) },
@ -171,13 +172,28 @@ static GOptionEntry options[] = {
G_OPTION_ARG_STRING, &thumbnail_intent,
N_( "ICC transform with INTENT" ),
N_( "INTENT" ) },
{ "rotate", 't', 0,
G_OPTION_ARG_NONE, &rotate_image,
N_( "auto-rotate" ), NULL },
{ "delete", 'd', 0,
G_OPTION_ARG_NONE, &delete_profile,
N_( "delete profile from exported image" ), NULL },
{ "no-rotate", 0, 0,
G_OPTION_ARG_NONE, &no_rotate_image,
N_( "don't auto-rotate" ), NULL },
{ "format", 'f', G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_STRING, &output_format,
N_( "set output format string to FORMAT" ),
N_( "FORMAT" ) },
{ "eprofile", 0, G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_FILENAME, &export_profile,
N_( "export with PROFILE" ),
N_( "PROFILE" ) },
{ "iprofile", 0, G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_FILENAME, &import_profile,
N_( "import untagged images with PROFILE" ),
N_( "PROFILE" ) },
{ "rotate", 't', G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_NONE, &rotate_image,
N_( "(deprecated, does nothing)" ), NULL },
{ "crop", 'c', G_OPTION_FLAG_HIDDEN,
G_OPTION_ARG_NONE, &crop_image,
N_( "(deprecated, crop exactly to SIZE)" ), NULL },
@ -280,11 +296,11 @@ thumbnail_process( VipsObject *process, const char *filename )
if( vips_thumbnail( filename, &image, thumbnail_width,
"height", thumbnail_height,
"size", size_restriction,
"auto_rotate", rotate_image,
"no-rotate", no_rotate_image,
"crop", interesting,
"linear", linear_processing,
"import_profile", import_profile,
"export_profile", export_profile,
"import-profile", import_profile,
"export-profile", export_profile,
"intent", intent,
NULL ) )
return( -1 );