add a sequential read mode to pngload

helpful for thumbnailing
This commit is contained in:
John Cupitt 2012-02-07 14:30:54 +00:00
parent b51faee8ca
commit b52785eef7
9 changed files with 288 additions and 86 deletions

View File

@ -3,6 +3,7 @@
- added vips_foreign_find_save_options()/vips_foreign_find_load_options()
- delayed write to foreign via a "w" image was not working
- support operations with many returns in Python
- sequential read mode for pngload
20/8/11 started 7.27.0
- version bump for new dev cycle

16
TODO
View File

@ -1,8 +1,24 @@
- add a sequential mode to all readers
- swig is not wrapping im_project() correctly ... returns an extra VImage via
a param
- fft with odd width or height is broken ... DC ends up in the wring place
- performance regression over 7.26
$ valgrind --tool=callgrind ./vips.py wtc_tiled_small.tif x.tif
vips_executor_run() 7.26 78k times
vips_executor_run() master 294k times
vips_interpolate_bilinear() 7.26 18.8m
vips_interpolate_bilinear() master 19.2m
total of about 0.5s user time difference

View File

@ -277,7 +277,7 @@ tile_find( VipsTileCache *cache, VipsRegion *in, int x, int y )
return( tile );
}
/* Copy rect from from to to.
/* Copy rect from @from to @to.
*/
static void
copy_region( VipsRegion *from, VipsRegion *to, VipsRect *area )

View File

@ -45,11 +45,26 @@
#include <vips/internal.h>
int
im_png2vips( const char *filename, IMAGE *out )
im_png2vips( const char *name, IMAGE *out )
{
char filename[FILENAME_MAX];
char mode[FILENAME_MAX];
char *p, *q;
gboolean sequential;
VipsImage *x;
if( vips_pngload( filename, &x, NULL ) )
im_filename_split( name, filename, mode );
sequential = FALSE;
p = &mode[0];
if( (q = im_getnextoption( &p )) ) {
if( im_isprefix( "seq", q ) )
sequential = TRUE;
}
if( vips_pngload( filename, &x,
"sequential", sequential,
NULL ) )
return( -1 );
if( vips_image_write( x, out ) ) {

View File

@ -27,6 +27,10 @@
*/
/*
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
@ -671,15 +675,29 @@ vips_foreign_load_start( VipsImage *out, void *a, void *dummy )
if( load->disc &&
disc_threshold &&
!(load->flags & VIPS_FOREIGN_PARTIAL) &&
image_size > disc_threshold )
image_size > disc_threshold ) {
#ifdef DEBUG
printf( "vips_foreign_load_start: making disc temp\n" );
#endif /*DEBUG*/
if( !(load->real = vips_image_new_disc_temp( "%s.v" )) )
return( NULL );
}
/* Otherwise, fall back to a "p".
*/
if( !load->real &&
!(load->real = vips_image_new()) )
return( NULL );
if( !load->real ) {
#ifdef DEBUG
printf( "vips_foreign_load_start: making 'p' temp\n" );
#endif /*DEBUG*/
if( !(load->real = vips_image_new()) )
return( NULL );
}
#ifdef DEBUG
printf( "vips_foreign_load_start: triggering ->load()\n" );
#endif /*DEBUG*/
/* Read the image in.
*/
@ -740,6 +758,10 @@ vips_foreign_load_build( VipsObject *object )
VipsForeignFlags flags;
#ifdef DEBUG
printf( "vips_foreign_load_build:\n" );
#endif /*DEBUG*/
flags = 0;
if( class->get_flags )
flags |= class->get_flags( load );
@ -751,6 +773,10 @@ vips_foreign_load_build( VipsObject *object )
g_object_set( object, "out", vips_image_new(), NULL );
#ifdef DEBUG
printf( "vips_foreign_load_build: triggering ->header()\n" );
#endif /*DEBUG*/
/* Read the header into @out.
*/
if( class->header &&
@ -762,6 +788,10 @@ vips_foreign_load_build( VipsObject *object )
* convert pixels on demand.
*/
if( class->load ) {
#ifdef DEBUG
printf( "vips_foreign_load_build: triggering ->load()\n" );
#endif /*DEBUG*/
/* ->header() should set the dhint. It'll default to the safe
* SMALLTILE if header() did not set it.
*/
@ -2018,9 +2048,20 @@ vips_fitssave( VipsImage *in, const char *filename, ... )
* @out: decompressed image
* @...: %NULL-terminated list of optional named arguments
*
* Optional arguments:
*
* @sequential: sequential read only
*
* Read a PNG file into a VIPS image. It can read all png images, including 8-
* and 16-bit images, 1 and 3 channel, with and without an alpha channel.
*
* Setting @sequential to %TRUE means you promise to only demand tiles from
* this image top-top-bottom, ie. to read sequentially. This means the png
* loader can read directly from the image without having to generate a
* random-access intermediate. This can save a lot of time and memory for
* large images, but limits the sorts of operation you can perform. It's
* useful for things like generating thumbnails.
*
* There is no support for embedded ICC profiles.
*
* See also: vips_image_new_from_file().

View File

@ -58,6 +58,10 @@ typedef struct _VipsForeignLoadPng {
*/
char *filename;
/* Setting this means "I promise to only read sequentially from this
* image".
*/
gboolean sequential;
} VipsForeignLoadPng;
typedef VipsForeignLoadClass VipsForeignLoadPngClass;
@ -68,6 +72,8 @@ G_DEFINE_TYPE( VipsForeignLoadPng, vips_foreign_load_png,
static VipsForeignFlags
vips_foreign_load_png_get_flags_filename( const char *filename )
{
/* We can't tell just from the file. Always refuse.
*/
return( 0 );
}
@ -75,8 +81,15 @@ static VipsForeignFlags
vips_foreign_load_png_get_flags( VipsForeignLoad *load )
{
VipsForeignLoadPng *png = (VipsForeignLoadPng *) load;
VipsForeignFlags flags;
return( vips_foreign_load_png_get_flags_filename( png->filename ) );
flags = 0;
/* If sequential is set, try to read partially.
*/
if( png->sequential )
flags |= VIPS_FOREIGN_PARTIAL;
return( flags );
}
static int
@ -95,7 +108,7 @@ vips_foreign_load_png_load( VipsForeignLoad *load )
{
VipsForeignLoadPng *png = (VipsForeignLoadPng *) load;
if( vips__png_read( png->filename, load->real ) )
if( vips__png_read( png->filename, load->real, png->sequential ) )
return( -1 );
return( 0 );
@ -130,6 +143,13 @@ vips_foreign_load_png_class_init( VipsForeignLoadPngClass *class )
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadPng, filename ),
NULL );
VIPS_ARG_BOOL( class, "sequential", 10,
_( "Sequential" ),
_( "Sequential read only" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadPng, sequential ),
FALSE );
}
static void

View File

@ -33,6 +33,9 @@
* - argh gamma was wrong when viewed in firefox
* 19/12/11
* - rework as a set of fns ready for wrapping as a class
* 7/2/12
* - mild refactoring
* - add support for sequential reads
*/
/*
@ -110,6 +113,11 @@ typedef struct {
png_infop pInfo;
png_bytep *row_pointer;
png_bytep data;
/* During sequential read we keep track of the current y position in
* the file to check sequentiality.
*/
int y_pos;
} Read;
static void
@ -137,6 +145,7 @@ read_new( const char *name, VipsImage *out )
read->pInfo = NULL;
read->row_pointer = NULL;
read->data = NULL;
read->y_pos = 0;
g_signal_connect( out, "close",
G_CALLBACK( read_destroy ), read );
@ -160,68 +169,10 @@ read_new( const char *name, VipsImage *out )
return( read );
}
/* Yuk! Have to malloc enough space for the whole image. Interlaced PNG
* is not really suitable for large objects ...
/* Read a png header.
*/
static int
png2vips_interlace( Read *read )
{
const int rowbytes = VIPS_IMAGE_SIZEOF_LINE( read->out );
const int height = png_get_image_height( read->pPng, read->pInfo );
int y;
if( !(read->row_pointer = VIPS_ARRAY( NULL, height, png_bytep )) )
return( -1 );
if( !(read->data = (png_bytep)
vips_malloc( NULL, height * rowbytes )) )
return( -1 );
for( y = 0; y < (int) height; y++ )
read->row_pointer[y] = read->data + y * rowbytes;
if( setjmp( png_jmpbuf( read->pPng ) ) )
return( -1 );
png_read_image( read->pPng, read->row_pointer );
for( y = 0; y < height; y++ )
if( vips_image_write_line( read->out,
y, read->row_pointer[y] ) )
return( -1 );
return( 0 );
}
/* Noninterlaced images can be read without needing enough RAM for the whole
* image.
*/
static int
png2vips_noninterlace( Read *read )
{
const int rowbytes = VIPS_IMAGE_SIZEOF_LINE( read->out );
const int height = png_get_image_height( read->pPng, read->pInfo );
int y;
if( !(read->data = (png_bytep) vips_malloc( NULL, rowbytes )) )
return( -1 );
if( setjmp( png_jmpbuf( read->pPng ) ) )
return( -1 );
for( y = 0; y < height; y++ ) {
png_read_row( read->pPng, read->data, NULL );
if( vips_image_write_line( read->out, y, read->data ) )
return( -1 );
}
return( 0 );
}
/* Read a PNG file (header) into a VIPS (header).
*/
static int
png2vips( Read *read, int header_only )
png2vips_header( Read *read, VipsImage *out )
{
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
@ -330,23 +281,17 @@ png2vips( Read *read, int header_only )
/* Set VIPS header.
*/
vips_image_init_fields( read->out,
vips_image_init_fields( out,
width, height, bands,
bit_depth > 8 ?
VIPS_FORMAT_USHORT : VIPS_FORMAT_UCHAR,
VIPS_CODING_NONE, interpretation,
Xres, Yres );
if( !header_only ) {
if( png_set_interlace_handling( read->pPng ) > 1 ) {
if( png2vips_interlace( read ) )
return( -1 );
}
else {
if( png2vips_noninterlace( read ) )
return( -1 );
}
}
/* We're always supposed to set dhint.
*/
vips_demand_hint( out,
VIPS_DEMAND_STYLE_THINSTRIP, NULL );
return( 0 );
}
@ -359,14 +304,139 @@ vips__png_header( const char *name, VipsImage *out )
Read *read;
if( !(read = read_new( name, out )) ||
png2vips( read, 1 ) )
png2vips_header( read, out ) )
return( -1 );
return( 0 );
}
/* Yuk! Have to malloc enough space for the whole image. Interlaced PNG
* is not really suitable for large objects ...
*/
static int
png2vips_interlace( Read *read, VipsImage *out )
{
const int rowbytes = VIPS_IMAGE_SIZEOF_LINE( out );
const int height = png_get_image_height( read->pPng, read->pInfo );
int y;
if( !(read->row_pointer = VIPS_ARRAY( NULL, height, png_bytep )) )
return( -1 );
if( !(read->data = (png_bytep)
vips_malloc( NULL, height * rowbytes )) )
return( -1 );
for( y = 0; y < (int) height; y++ )
read->row_pointer[y] = read->data + y * rowbytes;
if( setjmp( png_jmpbuf( read->pPng ) ) )
return( -1 );
png_read_image( read->pPng, read->row_pointer );
for( y = 0; y < height; y++ )
if( vips_image_write_line( out, y, read->row_pointer[y] ) )
return( -1 );
return( 0 );
}
static int
png2vips_generate( VipsRegion *out,
void *seq, void *a, void *b, gboolean *stop )
{
Read *read = (Read *) a;
int y;
#ifdef DEBUG
printf( "png2vips_generate: line %d, %d rows\n",
out->valid.top, out->valid.height );
printf( "png2vips_generate: thread %p\n", g_thread_self() );
#endif /*DEBUG*/
/* The y pos of the request must be the same as our current file
* position.
*/
if( out->valid.top != read->y_pos ) {
vips_error( "png2vips", _( "non-sequential read --- "
"at position %d in file, but position %d requested" ),
read->y_pos, out->valid.top );
return( -1 );
}
if( setjmp( png_jmpbuf( read->pPng ) ) )
return( -1 );
/* We're inside a tilecache where tiles are the fill image width, so
* this should always be true.
*/
g_assert( out->valid.left == 0 );
g_assert( out->valid.width == out->im->Xsize );
g_assert( VIPS_RECT_BOTTOM( &out->valid ) <= out->im->Ysize );
for( y = 0; y < out->valid.height; y++ ) {
png_bytep q = (png_bytep)
VIPS_REGION_ADDR( out, 0, out->valid.top + y );
png_read_row( read->pPng, q, NULL );
}
read->y_pos += out->valid.height;
#ifdef DEBUG
printf( "png2vips_generate: done for thread %p\n", g_thread_self() );
#endif /*DEBUG*/
return( 0 );
}
/* Build with vips_image_generate(), but fail if out-of-order tiles are
* requested.
*
* We are behind a tile cache, so we can assume that vips_image_generate()
* will always be asked for tiles which are the full image width. We can also
* assume we are single-threaded.
*/
static int
png2vips_sequential( Read *read, VipsImage *out )
{
if( vips_image_generate( out,
NULL, png2vips_generate, NULL,
read, NULL ) )
return( -1 );
return( 0 );
}
/* Noninterlaced images can be read without needing enough RAM for the whole
* image.
*/
static int
png2vips_noninterlace( Read *read, VipsImage *out )
{
const int rowbytes = VIPS_IMAGE_SIZEOF_LINE( out );
const int height = png_get_image_height( read->pPng, read->pInfo );
int y;
if( !(read->data = (png_bytep) vips_malloc( NULL, rowbytes )) )
return( -1 );
if( setjmp( png_jmpbuf( read->pPng ) ) )
return( -1 );
for( y = 0; y < height; y++ ) {
png_read_row( read->pPng, read->data, NULL );
if( vips_image_write_line( out, y, read->data ) )
return( -1 );
}
return( 0 );
}
int
vips__png_read( const char *name, VipsImage *out )
vips__png_read( const char *name, VipsImage *out, int sequential )
{
Read *read;
@ -374,10 +444,49 @@ vips__png_read( const char *name, VipsImage *out )
printf( "png2vips: reading \"%s\"\n", name );
#endif /*DEBUG*/
if( !(read = read_new( name, out )) ||
png2vips( read, 0 ) )
if( !(read = read_new( name, out )) )
return( -1 );
if( png_set_interlace_handling( read->pPng ) > 1 ) {
if( png2vips_header( read, out ) ||
png2vips_interlace( read, out ) )
return( -1 );
}
else if( sequential ) {
VipsImage *raw;
VipsImage *t;
/* Read to this image, then cache to out.
*/
raw = vips_image_new();
vips_object_local( out, raw );
if( png2vips_header( read, raw ) ||
png2vips_sequential( read, raw ) )
return( -1 );
/* Copy to out, adding a cache.
* Enough tiles for two complete rows.
*/
if( vips_tilecache( raw, &t,
"tile_width", raw->Xsize,
"tile_height", 32,
"max_tiles", 10,
NULL ) )
return( -1 );
if( vips_image_write( t, out ) ) {
g_object_unref( t );
return( -1 );
}
g_object_unref( t );
}
else {
if( png2vips_header( read, out ) ||
png2vips_noninterlace( read, out ) )
return( -1 );
}
return( 0 );
}

View File

@ -35,7 +35,7 @@ extern "C" {
#endif /*__cplusplus*/
int vips__png_header( const char *name, VipsImage *out );
int vips__png_read( const char *name, VipsImage *out );
int vips__png_read( const char *name, VipsImage *out, int sequential );
int vips__png_ispng( const char *filename );
extern const char *vips__png_suffs[];

View File

@ -178,7 +178,7 @@ typedef struct _VipsForeignLoadClass {
/* Read the whole image into @real. It gets copied to @out later.
*
* You can omit this method if you define a @header() method which
* loads the while file.
* loads the whole file.
*
* Return 0 for success, -1 for error, setting
* vips_error().