add a sequential read mode to pngload
helpful for thumbnailing
This commit is contained in:
parent
b51faee8ca
commit
b52785eef7
@ -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
16
TODO
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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 ) ) {
|
||||
|
@ -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().
|
||||
|
@ -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
|
||||
|
@ -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 );
|
||||
}
|
||||
|
||||
|
@ -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[];
|
||||
|
||||
|
@ -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().
|
||||
|
Loading…
Reference in New Issue
Block a user