/* @(#) 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 */ /* 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_IO */ #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #include #ifdef WITH_DMALLOC #include #endif /*WITH_DMALLOC*/ /* Progress feedback. Only really useful for testing, tbh. */ int im__progress = 0; void im_progress_set( int progress ) { im__progress = progress; } /* Delayed save: if we write to TIFF or to JPEG format, actually do the write * to a "p" and on preclose 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 preclose 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_preclose_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. */ typedef int (*OpenLazyFn)( const char *filename, IMAGE *im ); /* What we track during a delayed open. */ typedef struct _OpenLazy { char *filename; OpenLazyFn read_pixels; /* Read in pixels with this */ IMAGE *lazy_im; /* Image we read to .. copy from this */ } OpenLazy; /* 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 ) { OpenLazy *lazy = (OpenLazy *) a; if( !lazy->lazy_im ) { if( !(lazy->lazy_im = im_open_local( out, "read", "p" )) || lazy->read_pixels( lazy->filename, lazy->lazy_im ) ) { IM_FREEF( im_close, lazy->lazy_im ); return( NULL ); } } return( im_region_create( lazy->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( OpenLazyFn read_header, OpenLazyFn read_pixels, const char *filename, IMAGE *out ) { OpenLazy *lazy = IM_NEW( out, OpenLazy ); if( !lazy || !(lazy->filename = im_strdup( out, filename )) ) return( -1 ); lazy->read_pixels = read_pixels; lazy->lazy_im = NULL; if( read_header( filename, out ) ) return( -1 ); if( im_generate( out, open_lazy_start, open_lazy_generate, im_stop_one, lazy, NULL ) ) return( -1 ); return( 0 ); } static IMAGE * open_sub( OpenLazyFn read_header, OpenLazyFn read_pixels, const char *filename ) { IMAGE *im; if( !(im = im_open( filename, "p" )) || open_lazy( read_header, read_pixels, filename, 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 ); } 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->header, format->load, filename )) ) 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( "im_open:lw:1", "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 ) { 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_IO printf( "im_open: success for %s (%p)\n", im->filename, im ); #endif /*DEBUG_IO*/ return( im ); } /* Test an image for sanity. We could add many more tests here. */ static const char * image_sanity( IMAGE *im ) { if( !im ) return( "NULL descriptor" ); if( !im->filename ) return( "NULL filename" ); g_mutex_lock( im__global_lock ); if( !g_slist_find( im__open_images, im ) ) { g_mutex_unlock( im__global_lock ); return( "not on open image list" ); } g_mutex_unlock( im__global_lock ); if( im->Xsize <= 0 || im->Ysize <= 0 || im->Bands <= 0 ) return( "bad dimensions" ); if( im->BandFmt < -1 || im->BandFmt > IM_BANDFMT_DPCOMPLEX || (im->Coding != -1 && im->Coding != IM_CODING_NONE && im->Coding != IM_CODING_LABQ && im->Coding != IM_CODING_RAD) || im->Type < -1 || im->Type > IM_TYPE_GREY16 || im->dtype > IM_PARTIAL || im->dhint > IM_ANY ) return( "bad enum value" ); if( im->Xres < 0 || im->Xres < 0 ) return( "bad resolution" ); return( NULL ); } int im_image_sanity( IMAGE *im ) { const char *msg; if( (msg = image_sanity( im )) ) { im_warn( "im_image_sanity", "%p", im ); im_warn( "im_image_sanity", "\"%s\" %s", im ? (im->filename ? im->filename : "") : "", msg ); im_printdesc( im ); return( -1 ); } return( 0 ); } /* Just here for compatibility. */ IMAGE * im_open_header( const char *file ) { return( im_open( file, "r" ) ); }