715 lines
17 KiB
C
715 lines
17 KiB
C
/* @(#) im_open: vips front end to file open functions
|
|
ARGS: filename and mode which is one of:
|
|
"r" read by mmap (im_mmapin)
|
|
"rw" read and write by mmap (im_mmapinrw)
|
|
"w" write for write (im_writeline)
|
|
"t" for temporary memory image
|
|
"p" for partial memory image
|
|
RETURNS: IMAGE pointer or 0 on error
|
|
Copyright: Kirk Martinez 29/5/92
|
|
|
|
Modified:
|
|
* 8/8/94 JC
|
|
* - ANSIfied
|
|
* - im_open_local added
|
|
* 16/8/94 JC
|
|
* - im_malloc() added
|
|
* 22/11/94 JC & KM
|
|
* - tiff added
|
|
* 1/2/95 JC
|
|
* - tiff removed again!
|
|
* - now just im_istiff(), im_tiff2vips() and im_vips2tiff()
|
|
* - applications have responsibility for making the translation
|
|
* 26/3/96 JC
|
|
* - im_open_local() now closes on close, not evalend
|
|
* 14/11/96 JC
|
|
* - tiff and jpeg added again
|
|
* - open for read used im_istiff() and im_isjpeg() to switch
|
|
* - open for write looks at suffix
|
|
* 23/4/97 JC
|
|
* - im_strdup() now allows NULL IMAGE parameter
|
|
* - subsample parameter added to im_tiff2vips()
|
|
* 29/10/98 JC
|
|
* - byte-swap stuff added
|
|
* 16/6/99 JC
|
|
* - 8x byte swap added for double/double complex
|
|
* - better error message if file does not exist
|
|
* - ignore case when testing suffix for save
|
|
* - fix im_mmapinrw() return test to stop coredump in edvips if
|
|
* unwritable
|
|
* 2/11/99 JC
|
|
* - malloc/strdup stuff moved to memory.c
|
|
* 5/8/00 JC
|
|
* - fixes for im_vips2tiff() changes
|
|
* 13/10/00 JC
|
|
* - ooops, missing 'else'
|
|
* 22/11/00 JC
|
|
* - ppm read/write added
|
|
* 12/2/01 JC
|
|
* - im__image_sanity() added
|
|
* 16/5/01 JC
|
|
* - now allows RW for non-native byte order, provided it's an 8-bit
|
|
* image
|
|
* 11/7/01 JC
|
|
* - im_tiff2vips() no longer has subsample option
|
|
* 25/3/02 JC
|
|
* - better im_open() error message
|
|
* 12/12/02 JC
|
|
* - sanity no longer returns errors, just warns
|
|
* 28/12/02 HB
|
|
* - Added PNG support
|
|
* 6/5/03 JC
|
|
* - added im_open_header() (from header.c)
|
|
* 22/5/03 JC
|
|
* - im__skip_dir() knows about ':' in filenames for vips2tiff etc.
|
|
* 27/11/03 JC
|
|
* - im_image_sanity() now insists x/y/bands all >0
|
|
* 6/1/04 JC
|
|
* - moved util funcs out to util.c
|
|
* 18/2/04 JC
|
|
* - added an im_init_world() to help old progs
|
|
* 16/4/04
|
|
* - oop, im_open() didn't know about input options in filenames
|
|
* 2/11/04
|
|
* - im_open( "r" ) is now lazier ... for non-VIPS formats, we delay
|
|
* read until the first ->generate()
|
|
* - so im_open_header() is now a no-op
|
|
* 22/6/05
|
|
* - if TIFF open fails, try going through libmagick
|
|
* 4/8/05
|
|
* - added analyze read
|
|
* 30/9/05
|
|
* - oops, lazy read error recovery didn't clear a pointer
|
|
* 1/5/06
|
|
* - added OpenEXR read
|
|
* 9/6/06
|
|
* - added CSV read/write
|
|
* 20/9/06
|
|
* - test for NULL filename/mode, common if you forget to check argc
|
|
* (thanks bruno)
|
|
* 7/11/07
|
|
* - use preclose, not evalend, for delayed save
|
|
* - add simple cmd-line progress feedback
|
|
* 9/8/08
|
|
* - lock global image list (thanks lee)
|
|
* 25/5/08
|
|
* - break file format stuff out to the new pluggable image format system
|
|
* 14/1/09
|
|
* - write to non-vips formats with a "written" callback
|
|
* 29/7/10
|
|
* - disc open threshold stuff, open to disc mode
|
|
*/
|
|
|
|
/*
|
|
|
|
This file is part of VIPS.
|
|
|
|
VIPS 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 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/debug.h>
|
|
#include <vips/internal.h>
|
|
|
|
#ifdef WITH_DMALLOC
|
|
#include <dmalloc.h>
|
|
#endif /*WITH_DMALLOC*/
|
|
|
|
/* Progress feedback. Only really useful for testing, tbh.
|
|
*/
|
|
int im__progress = 0;
|
|
|
|
/* A string giving the image size (in bytes of uncompressed image) above which
|
|
* we decompress to disc on open. Can be eg. "12m" for 12 megabytes.
|
|
*/
|
|
char *im__disc_threshold = NULL;
|
|
|
|
/* Delayed save: if we write to (eg.) TIFF, actually do the write
|
|
* to a "p" and on "written" do im_vips2tiff() or whatever. Track save
|
|
* parameters here.
|
|
*/
|
|
typedef struct {
|
|
int (*save_fn)(); /* Save function */
|
|
IMAGE *im; /* Image to save */
|
|
char *filename; /* Save args */
|
|
} SaveBlock;
|
|
|
|
/* From "written" callback: invoke a delayed save.
|
|
*/
|
|
static int
|
|
invoke_sb( SaveBlock *sb )
|
|
{
|
|
if( sb->save_fn( sb->im, sb->filename ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Attach a SaveBlock to an image.
|
|
*/
|
|
static int
|
|
attach_sb( IMAGE *out, int (*save_fn)(), const char *filename )
|
|
{
|
|
SaveBlock *sb = IM_NEW( out, SaveBlock );
|
|
|
|
if( !sb )
|
|
return( -1 );
|
|
sb->im = out;
|
|
sb->save_fn = save_fn;
|
|
sb->filename = im_strdup( out, filename );
|
|
|
|
if( im_add_written_callback( out,
|
|
(im_callback_fn) invoke_sb, (void *) sb, NULL ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Lazy open: init a descriptor from a filename (just read the header) but
|
|
* delay actually decoding pixels until the first call to a start function.
|
|
*/
|
|
|
|
/* What we track during a delayed open.
|
|
*/
|
|
typedef struct _Lazy {
|
|
IMAGE *out;
|
|
VipsFormatClass *format;/* Read in pixels with this */
|
|
gboolean disc; /* Read via disc requested */
|
|
IMAGE *im; /* The real decompressed image */
|
|
} Lazy;
|
|
|
|
static int
|
|
lazy_free( Lazy *lazy )
|
|
{
|
|
IM_FREEF( im_close, lazy->im );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static Lazy *
|
|
lazy_new( IMAGE *out, VipsFormatClass *format, gboolean disc )
|
|
{
|
|
Lazy *lazy;
|
|
|
|
if( !(lazy = IM_NEW( out, Lazy )) )
|
|
return( NULL );
|
|
lazy->out = out;
|
|
lazy->format = format;
|
|
lazy->disc = disc;
|
|
lazy->im = NULL;
|
|
|
|
if( im_add_close_callback( out,
|
|
(im_callback_fn) lazy_free, lazy, NULL ) ) {
|
|
lazy_free( lazy );
|
|
|
|
return( NULL );
|
|
}
|
|
|
|
return( lazy );
|
|
}
|
|
|
|
static size_t
|
|
guess_size( VipsFormatClass *format, const char *filename )
|
|
{
|
|
IMAGE *im;
|
|
size_t size;
|
|
|
|
if( !(im = im_open( "header", "p" )) )
|
|
return( 0 );
|
|
if( format->header( filename, im ) ) {
|
|
im_close( im );
|
|
return( 0 );
|
|
}
|
|
size = IM_IMAGE_SIZEOF_LINE( im ) * im->Ysize;
|
|
im_close( im );
|
|
|
|
return( size );
|
|
}
|
|
|
|
typedef struct {
|
|
const char unit;
|
|
int multiplier;
|
|
} Unit;
|
|
|
|
static size_t
|
|
parse_size( const char *size_string )
|
|
{
|
|
static Unit units[] = {
|
|
{ 'k', 1024 },
|
|
{ 'm', 1024 * 1024 },
|
|
{ 'g', 1024 * 1024 * 1024 }
|
|
};
|
|
|
|
size_t size;
|
|
int n;
|
|
int i, j;
|
|
char *unit;
|
|
|
|
/* An easy way to alloc a buffer large enough.
|
|
*/
|
|
unit = g_strdup( size_string );
|
|
n = sscanf( size_string, "%d %s", &i, unit );
|
|
if( n > 0 )
|
|
size = i;
|
|
if( n > 1 ) {
|
|
for( j = 0; j < IM_NUMBER( units ); j++ )
|
|
if( tolower( unit[0] ) == units[j].unit ) {
|
|
size *= units[j].multiplier;
|
|
break;
|
|
}
|
|
}
|
|
g_free( unit );
|
|
|
|
#ifdef DEBUG
|
|
printf( "parse_size: parsed \"%s\" as %zd\n", size_string, size );
|
|
#endif /*DEBUG*/
|
|
|
|
return( size );
|
|
}
|
|
|
|
static size_t
|
|
disc_threshold( void )
|
|
{
|
|
static gboolean done = FALSE;
|
|
static size_t threshold;
|
|
|
|
if( !done ) {
|
|
const char *env;
|
|
|
|
done = TRUE;
|
|
|
|
/* 100mb default.
|
|
*/
|
|
threshold = 100 * 1024 * 1024;
|
|
|
|
if( (env = g_getenv( "IM_DISC_THRESHOLD" )) )
|
|
threshold = parse_size( env );
|
|
|
|
if( im__disc_threshold )
|
|
threshold = parse_size( im__disc_threshold );
|
|
|
|
#ifdef DEBUG
|
|
printf( "disc_threshold: %zd bytes\n", threshold );
|
|
#endif /*DEBUG*/
|
|
}
|
|
|
|
return( threshold );
|
|
}
|
|
|
|
static IMAGE *
|
|
lazy_image( Lazy *lazy )
|
|
{
|
|
IMAGE *im;
|
|
|
|
/* We open to disc if:
|
|
* - 'disc' is set
|
|
* - disc_threshold() has not been set to zero
|
|
* - the format does not support lazy read
|
|
* - the image will be more than a megabyte, uncompressed
|
|
*/
|
|
im = NULL;
|
|
if( lazy->disc &&
|
|
disc_threshold() &&
|
|
!(vips_format_get_flags( lazy->format, lazy->out->filename ) &
|
|
VIPS_FORMAT_PARTIAL) ) {
|
|
size_t size;
|
|
|
|
size = guess_size( lazy->format, lazy->out->filename );
|
|
if( size > disc_threshold() ) {
|
|
if( !(im = im__open_temp( "%s.v" )) )
|
|
return( NULL );
|
|
|
|
#ifdef DEBUG
|
|
printf( "lazy_image: opening to disc file \"%s\"\n",
|
|
im->filename );
|
|
#endif /*DEBUG*/
|
|
}
|
|
}
|
|
|
|
/* Otherwise, fall back to a "p".
|
|
*/
|
|
if( !im &&
|
|
!(im = im_open( lazy->out->filename, "p" )) )
|
|
return( NULL );
|
|
|
|
return( im );
|
|
}
|
|
|
|
/* Our start function ... do the lazy open, if necessary, and return a region
|
|
* on the new image.
|
|
*/
|
|
static void *
|
|
open_lazy_start( IMAGE *out, void *a, void *dummy )
|
|
{
|
|
Lazy *lazy = (Lazy *) a;
|
|
|
|
if( !lazy->im ) {
|
|
if( !(lazy->im = lazy_image( lazy )) ||
|
|
lazy->format->load( lazy->out->filename, lazy->im ) ||
|
|
im_pincheck( lazy->im ) ) {
|
|
IM_FREEF( im_close, lazy->im );
|
|
return( NULL );
|
|
}
|
|
}
|
|
|
|
return( im_region_create( lazy->im ) );
|
|
}
|
|
|
|
/* Just copy.
|
|
*/
|
|
static int
|
|
open_lazy_generate( REGION *or, void *seq, void *a, void *b )
|
|
{
|
|
REGION *ir = (REGION *) seq;
|
|
|
|
Rect *r = &or->valid;
|
|
|
|
/* Ask for input we need.
|
|
*/
|
|
if( im_prepare( ir, r ) )
|
|
return( -1 );
|
|
|
|
/* Attach output region to that.
|
|
*/
|
|
if( im_region_region( or, ir, r, r->left, r->top ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Lazy open ... init the header with the first OpenLazyFn, delay actually
|
|
* decoding pixels with the second OpenLazyFn until the first generate().
|
|
*/
|
|
static int
|
|
open_lazy( VipsFormatClass *format, gboolean disc, IMAGE *out )
|
|
{
|
|
Lazy *lazy;
|
|
|
|
if( !(lazy = lazy_new( out, format, disc )) )
|
|
return( -1 );
|
|
|
|
/* Read header fields to init the return image. THINSTRIP since this is
|
|
* probably a disc file. We can't tell yet whether we will be opening
|
|
* to memory, sadly, so we can't suggest ANY.
|
|
*/
|
|
if( format->header( out->filename, out ) ||
|
|
im_demand_hint( out, IM_THINSTRIP, NULL ) )
|
|
return( -1 );
|
|
|
|
/* Then 'start' creates the real image and 'gen' paints 'out' with
|
|
* pixels from the real image on demand.
|
|
*/
|
|
if( im_generate( out,
|
|
open_lazy_start, open_lazy_generate, im_stop_one,
|
|
lazy, NULL ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static IMAGE *
|
|
open_sub( VipsFormatClass *format, const char *filename, gboolean disc )
|
|
{
|
|
IMAGE *im;
|
|
|
|
/* This is the 'im' we return which, when read from, will trigger the
|
|
* actual load.
|
|
*/
|
|
if( !(im = im_open( filename, "p" )) ||
|
|
open_lazy( format, disc, im ) ) {
|
|
im_close( im );
|
|
return( NULL );
|
|
}
|
|
|
|
return( im );
|
|
}
|
|
|
|
/* Progress feedback.
|
|
*/
|
|
|
|
/* What we track during an eval.
|
|
*/
|
|
typedef struct {
|
|
IMAGE *im;
|
|
|
|
int last_percent; /* The last %complete we displayed */
|
|
} Progress;
|
|
|
|
int
|
|
evalstart_cb( Progress *progress )
|
|
{
|
|
progress->last_percent = 0;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
int
|
|
eval_cb( Progress *progress )
|
|
{
|
|
IMAGE *im = progress->im;
|
|
|
|
if( im->time->percent != progress->last_percent ) {
|
|
printf( _( "%s %s: %d%% complete" ),
|
|
g_get_prgname(), im->filename, im->time->percent );
|
|
printf( "\r" );
|
|
fflush( stdout );
|
|
|
|
progress->last_percent = im->time->percent;
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
int
|
|
evalend_cb( Progress *progress )
|
|
{
|
|
IMAGE *im = progress->im;
|
|
|
|
/* Spaces at end help to erase the %complete message we overwrite.
|
|
*/
|
|
printf( _( "%s %s: done in %ds \n" ),
|
|
g_get_prgname(), im->filename, im->time->run );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/**
|
|
* im_open:
|
|
* @filename: file to open
|
|
* @mode: mode to open with
|
|
*
|
|
* im_open() examines the mode string, and creates an appropriate #IMAGE.
|
|
*
|
|
* <itemizedlist>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"r"</emphasis>
|
|
* opens the named file for reading. If the file is not in the native
|
|
* VIPS format for your machine, im_open() automatically converts the
|
|
* file for you in memory.
|
|
*
|
|
* For some large files (eg. TIFF) this may
|
|
* not be what you want, it can fill memory very quickly. Instead, you
|
|
* can either use "rd" mode (see below), or you can use the lower-level
|
|
* API and control the loading process yourself. See
|
|
* #VipsFormat.
|
|
*
|
|
* im_open() can read files in most formats.
|
|
*
|
|
* Note that <emphasis>"r"</emphasis> mode works in at least two stages.
|
|
* It should return quickly and let you check header fields. It will
|
|
* only actually read in pixels when you first access them.
|
|
* </para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"rd"</emphasis>
|
|
* opens the named file for reading. If the uncompressed image is larger
|
|
* than a threshold and the file format does not support random access,
|
|
* rather than uncompressing to memory, im_open() will uncompress to a
|
|
* temporary disc file. This file will be automatically deleted when the
|
|
* IMAGE is closed.
|
|
*
|
|
* See im_system_image() for an explanation of how VIPS selects a
|
|
* location for the temporary file.
|
|
*
|
|
* The disc threshold can be set with the "--vips-disc-threshold"
|
|
* command-line argument, or the IM_DISC_THRESHOLD environment variable.
|
|
* The value is a simple integer, but can take a unit postfix of "k",
|
|
* "m" or "g" to indicate kilobytes, megabytes or gigabytes.
|
|
*
|
|
* For example:
|
|
*
|
|
* |[
|
|
* vips --vips-disc-threshold "500m" im_copy fred.tif fred.v
|
|
* ]|
|
|
*
|
|
* will copy via disc if "fred.tif" is more than 500 Mbytes
|
|
* uncompressed. The default threshold is 100MB.
|
|
* </para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"w"</emphasis>
|
|
* opens the named file for writing. It looks at the file name
|
|
* suffix to determine the type to write -- for example:
|
|
*
|
|
* |[
|
|
* im_open( "fred.tif", "w" )
|
|
* ]|
|
|
*
|
|
* will write in TIFF format.
|
|
* </para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"t"</emphasis>
|
|
* creates a temporary memory buffer image.
|
|
* </para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"p"</emphasis>
|
|
* creates a "glue" descriptor you can use to join two image
|
|
* processing operations together.
|
|
* </para>
|
|
* </listitem>
|
|
* <listitem>
|
|
* <para>
|
|
* <emphasis>"rw"</emphasis>
|
|
* opens the named file for reading and writing. This will only work for
|
|
* VIPS files in a format native to your machine. It is only for
|
|
* paintbox-type applications.
|
|
* </para>
|
|
* </listitem>
|
|
* </itemizedlist>
|
|
*
|
|
* See also: im_close(), #VipsFormat
|
|
*
|
|
* Returns: the image descriptor on success and NULL on error.
|
|
*/
|
|
IMAGE *
|
|
im_open( const char *filename, const char *mode )
|
|
{
|
|
IMAGE *im;
|
|
VipsFormatClass *format;
|
|
|
|
/* Pass in a nonsense name for argv0 ... this init world is only here
|
|
* for old programs which are missing an im_init_world() call. We must
|
|
* have threads set up before we can process.
|
|
*/
|
|
if( im_init_world( "vips" ) )
|
|
im_error_clear();
|
|
|
|
if( !filename || !mode ) {
|
|
im_error( "im_open", "%s", _( "NULL filename or mode" ) );
|
|
return( NULL );
|
|
}
|
|
|
|
/*
|
|
|
|
we can't use the vips handler in the format system, since we
|
|
want to be able to open the image directly rather than
|
|
copying to an existing descriptor
|
|
|
|
if we don't do this, things like paintbox apps won't work
|
|
|
|
*/
|
|
|
|
switch( mode[0] ) {
|
|
case 'r':
|
|
if( (format = vips_format_for_file( filename )) ) {
|
|
if( strcmp( VIPS_OBJECT_CLASS( format )->nickname,
|
|
"vips" ) == 0 ) {
|
|
if( !(im = im_open_vips( filename )) )
|
|
return( NULL );
|
|
}
|
|
else if( !(im = open_sub( format, filename,
|
|
mode[1] == 'd' )) )
|
|
return( NULL );
|
|
}
|
|
else
|
|
return( NULL );
|
|
break;
|
|
|
|
case 'w':
|
|
if( (format = vips_format_for_name( filename )) ) {
|
|
if( strcmp( VIPS_OBJECT_CLASS( format )->nickname,
|
|
"vips" ) == 0 )
|
|
im = im_openout( filename );
|
|
else {
|
|
if( !(im = im_open( filename, "p" )) )
|
|
return( NULL );
|
|
if( attach_sb( im, format->save, filename ) ) {
|
|
im_close( im );
|
|
return( NULL );
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
char suffix[FILENAME_MAX];
|
|
|
|
im_filename_suffix( filename, suffix );
|
|
im_error( "im_open",
|
|
_( "unsupported filetype \"%s\"" ),
|
|
suffix );
|
|
|
|
return( NULL );
|
|
}
|
|
break;
|
|
|
|
case 't':
|
|
im = im_setbuf( filename );
|
|
break;
|
|
|
|
case 'p':
|
|
im = im_partial( filename );
|
|
break;
|
|
|
|
default:
|
|
im_error( "im_open", _( "bad mode \"%s\"" ), mode );
|
|
return( NULL );
|
|
}
|
|
|
|
/* Attach progress feedback, if required.
|
|
*/
|
|
if( im__progress || g_getenv( "IM_PROGRESS" ) ) {
|
|
Progress *progress = IM_NEW( im, Progress );
|
|
|
|
progress->im = im;
|
|
im_add_evalstart_callback( im,
|
|
(im_callback_fn) evalstart_cb, progress, NULL );
|
|
im_add_eval_callback( im,
|
|
(im_callback_fn) eval_cb, progress, NULL );
|
|
im_add_evalend_callback( im,
|
|
(im_callback_fn) evalend_cb, progress, NULL );
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf( "im_open: success for %s (%p)\n", im->filename, im );
|
|
#endif /*DEBUG*/
|
|
|
|
return( im );
|
|
}
|
|
|
|
/* Just here for compatibility.
|
|
*/
|
|
IMAGE *
|
|
im_open_header( const char *file )
|
|
{
|
|
return( im_open( file, "r" ) );
|
|
}
|