blob: badf1f9a85154a321358bd97e06b4a01d6b607d5 [file] [log] [blame]
/* Read and write a VIPS file into an IMAGE *
*
* 22/5/08
* - from im_open.c, im_openin.c, im_desc_hd.c, im_readhist.c,
* im_openout.c
* 19/3/09
* - block mmaps of nodata images
* 12/5/09
* - fix signed/unsigned warnings
* 12/10/09
* - heh argh reading history always stopped after the first line
* 9/12/09
* - only wholly map input files on im_incheck() ... this reduces VM use,
* especially with large numbers of small files
*/
/*
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 <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#ifdef HAVE_SYS_FILE_H
#include <sys/file.h>
#endif /*HAVE_SYS_FILE_H*/
#include <sys/stat.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#ifdef HAVE_IO_H
#include <io.h>
#endif /*HAVE_IO_H*/
#include <libxml/parser.h>
#include <errno.h>
#ifdef OS_WIN32
#include <windows.h>
#endif /*OS_WIN32*/
#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
/**
* SECTION: image
* @short_description: the VIPS image class
* @stability: Stable
* @see_also: <link linkend="libvips-region">region</link>
* @include: vips/vips.h
*
* The VIPS image class and associated types and macros.
*/
/**
* IM_MAGIC_INTEL:
*
* The first four bytes of a VIPS file in Intel byte ordering.
*/
/**
* IM_MAGIC_SPARC:
*
* The first four bytes of a VIPS file in SPARC byte ordering.
*/
/**
* VipsDemandStyle:
* @IM_SMALLTILE: demand in small (typically 64x64 pixel) tiles
* @IM_FATSTRIP: demand in fat (typically 10 pixel high) strips
* @IM_THINSTRIP: demand in thin (typically 1 pixel high) strips
* @IM_ANY: demand geometry does not matter
*
* See im_demand_hint(). Operations can hint to the VIPS image IO system about
* the kind of demand geometry they prefer.
*
* These demand styles are given below in order of increasing
* restrictiveness. When demanding output from a pipeline, im_generate()
* will use the most restrictive of the styles requested by the operations
* in the pipeline.
*
* IM_THINSTRIP --- This operation would like to output strips the width
* of the image and a few pels high. This is option suitable for
* point-to-point operations, such as those in the arithmetic package.
*
* This option is only efficient for cases where each output pel depends
* upon the pel in the corresponding position in the input image.
*
* IM_FATSTRIP --- This operation would like to output strips the width
* of the image and as high as possible. This option is suitable for area
* operations which do not violently transform coordinates, such as im_conv().
*
* IM_SMALLTILE --- This is the most general demand format.
* Output is demanded in small (around 100x100 pel) sections. This style works
* reasonably efficiently, even for bizzare operations like 45 degree rotate.
*
* IM_ANY --- This image is not being demand-read from a disc file (even
* indirectly) so any demand style is OK. It's used for things like
* im_black() where the pixels are calculated.
*
* See also: im_demand_hint().
*/
/**
* VipsType:
* @IM_TYPE_MULTIBAND: generic many-band image
* @IM_TYPE_B_W: some kind of single-band image
* @IM_TYPE_HISTOGRAM: a 1D image such as a histogram or lookup table
* @IM_TYPE_FOURIER: image is in fourier space
* @IM_TYPE_XYZ: the first three bands are colours in CIE XYZ colourspace
* @IM_TYPE_LAB: pixels are in CIE Lab space
* @IM_TYPE_CMYK: the first four bands are in CMYK space
* @IM_TYPE_LABQ: implies %IM_CODING_LABQ
* @IM_TYPE_RGB: generic RGB space
* @IM_TYPE_UCS: a uniform colourspace based on CMC
* @IM_TYPE_LCH: pixels are in CIE LCh space
* @IM_TYPE_LABS: pixels are CIE LAB coded as three signed 16-bit values
* @IM_TYPE_sRGB: pixels are sRGB
* @IM_TYPE_YXY: pixels are CIE Yxy
* @IM_TYPE_RGB16: generic 16-bit RGB
* @IM_TYPE_GREY16: generic 16-bit mono
*
* These values are set by operations as hints to user-interfaces built on top
* of VIPS to help them show images to the user in a meaningful way.
* Operations do not use these values to decide their action.
*/
/**
* VipsBandFmt:
* @IM_BANDFMT_UCHAR: unsigned char format
* @IM_BANDFMT_CHAR: char format
* @IM_BANDFMT_USHORT: unsigned short format
* @IM_BANDFMT_SHORT: short format
* @IM_BANDFMT_UINT: unsigned int format
* @IM_BANDFMT_INT: int format
* @IM_BANDFMT_FLOAT: float format
* @IM_BANDFMT_COMPLEX: complex (two floats) format
* @IM_BANDFMT_DOUBLE: double float format
* @IM_BANDFMT_DPCOMPLEX: double complex (two double) format
*
* The format used for each band element.
*
* Each corresponnds to a native C type for the current machine. For example,
* %IM_BANDFMT_USHORT is <type>unsigned short</type>.
*/
/**
* VipsCoding:
* @IM_CODING_NONE: pixels are not coded
* @IM_CODING_LABQ: pixels encode 3 float CIELAB values as 4 uchar
* @IM_CODING_RAD: pixels encode 3 float RGB as 4 uchar (Radiance coding)
*
* How pixels are coded.
*
* Normally, pixels are uncoded and can be manipulated as you would expect.
* However some file formats code pixels for compression, and sometimes it's
* useful to be able to manipulate images in the coded format.
*/
/**
* VipsProgress:
* @run: Time we have been running
* @eta: Estimated seconds of computation left
* @tpels: Number of pels we expect to calculate
* @npels: Number of pels calculated so far
* @percent: Percent complete
* @start: Start time
*
* A structure available to eval callbacks giving information on evaluation
* progress. See im_add_eval_callback().
*/
/**
* VipsImage:
* @Xsize: image width, in pixels
* @Ysize: image height, in pixels
* @Bands: number of image bands
* @BandFmt: #VipsFormat describing the pixel type
* @Coding: #VipsCoding describing the pixel coding type
* @Type: a #VipsType hinting how the image pixels should be interpreted
* @Xres: horizontal pixels per millimetre
* @Yres: vertical pixels per millimetre
* @Xoffset: a hint giving the position of the origin in the image
* @Yoffset: a hint giving the position of the origin in the image
* @filename: the disc file associated with this image, or %NULL
* @data: the pixel data associated with this image, or %NULL
* @time: the evaluation progress associated with this image, or %NULL
* @kill: set this to non-zero to block evaluation of this image
*
* An image. These can represent an image on disc, a memory buffer, an image
* in the process of being written to disc or a partially evaluated image
* in memory.
*/
/**
* IM_IMAGE_SIZEOF_ELEMENT:
* @I: a #VipsImage
*
* Returns: sizeof() a band element.
*/
/**
* IM_IMAGE_SIZEOF_PEL:
* @I: a #VipsImage
*
* Returns: sizeof() a pixel.
*/
/**
* IM_IMAGE_SIZEOF_LINE:
* @I: a #VipsImage
*
* Returns: sizeof() a scanline of pixels.
*/
/**
* IM_IMAGE_N_ELEMENTS:
* @I: a #VipsImage
*
* Returns: The number of band elements in a scanline.
*/
/**
* IM_IMAGE_ADDR:
* @I: a #VipsImage
* @X: x coordinate
* @Y: y coordinate
*
* This macro returns a pointer to a pixel in an image. It only works for
* images which are fully available in memory, so memory buffers and small
* mapped images only.
*
* If DEBUG is defined, you get a version that checks bounds for you.
*
* Returns: The address of pixel (x,y) in the image.
*/
/**
* im_open_local_array:
* @IM: image to open local to
* @OUT: array to fill with #IMAGE
* @N: array size
* @NAME: filename to open
* @MODE: mode to open with
*
* Just like im_open(), but opens an array of images. Handy for creating a set
* of temporary images for a function.
*
* Example:
*
* |[
* IMAGE *t[5];
*
* if( im_open_local_array( out, t, 5, "some-temps", "p" ) ||
* im_add( a, b, t[0] ) ||
* im_invert( t[0], t[1] ) ||
* im_add( t[1], t[0], t[2] ) ||
* im_costra( t[2], out ) )
* return( -1 );
* ]|
*
* See also: im_open(), im_open_local(), im_local_array().
*
* Returns: 0 on sucess, or -1 on error
*/
/**
* im_open_local:
* @IM: image to open local to
* @NAME: filename to open
* @MODE: mode to open with
*
* Just like im_open(), but the #IMAGE will be closed for you automatically
* when @IM is closed.
*
* See also: im_open(), im_close(), im_local().
*
* Returns: a new #IMAGE, or NULL on error
*/
/* Try to make an O_BINARY ... sometimes need the leading '_'.
*/
#ifdef BINARY_OPEN
#ifndef O_BINARY
#ifdef _O_BINARY
#define O_BINARY _O_BINARY
#endif /*_O_BINARY*/
#endif /*!O_BINARY*/
#endif /*BINARY_OPEN*/
/* Our XML namespace.
*/
#define NAMESPACE "http://www.vips.ecs.soton.ac.uk/vips"
/* Sort of open for read for image files. Shared with im_binfile().
*/
int
im__open_image_file( const char *filename )
{
int fd;
/* Try to open read-write, so that calls to im_makerw() will work.
* When we later mmap this file, we set read-only, so there
* is little danger of scrubbing over files we own.
*/
#ifdef BINARY_OPEN
if( (fd = open( filename, O_RDWR | O_BINARY )) == -1 ) {
#else /*BINARY_OPEN*/
if( (fd = open( filename, O_RDWR )) == -1 ) {
#endif /*BINARY_OPEN*/
/* Open read-write failed. Fall back to open read-only.
*/
#ifdef BINARY_OPEN
if( (fd = open( filename, O_RDONLY | O_BINARY )) == -1 ) {
#else /*BINARY_OPEN*/
if( (fd = open( filename, O_RDONLY )) == -1 ) {
#endif /*BINARY_OPEN*/
im_error( "im__open_image_file",
_( "unable to open \"%s\", %s" ),
filename, strerror( errno ) );
return( -1 );
}
}
return( fd );
}
/* Predict the size of the header plus pixel data. Don't use off_t,
* it's sometimes only 32 bits (eg. on many windows build environments) and we
* want to always be 64 bit.
*/
gint64
im__image_pixel_length( IMAGE *im )
{
gint64 psize;
switch( im->Coding ) {
case IM_CODING_LABQ:
case IM_CODING_RAD:
case IM_CODING_NONE:
psize = (gint64) IM_IMAGE_SIZEOF_LINE( im ) * im->Ysize;
break;
default:
psize = im->Length;
break;
}
return( psize + im->sizeof_header );
}
/* Read short/int/float LSB and MSB first.
*/
void
im__read_4byte( int msb_first, unsigned char *to, unsigned char **from )
{
unsigned char *p = *from;
int out;
if( msb_first )
out = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
else
out = p[3] << 24 | p[2] << 16 | p[1] << 8 | p[0];
*from += 4;
*((guint32 *) to) = out;
}
void
im__read_2byte( int msb_first, unsigned char *to, unsigned char **from )
{
int out;
unsigned char *p = *from;
if( msb_first )
out = p[0] << 8 | p[1];
else
out = p[1] << 8 | p[0];
*from += 2;
*((guint16 *) to) = out;
}
/* We always write in native byte order.
*/
void
im__write_4byte( unsigned char **to, unsigned char *from )
{
*((guint32 *) *to) = *((guint32 *) from);
*to += 4;
}
void
im__write_2byte( unsigned char **to, unsigned char *from )
{
*((guint16 *) *to) = *((guint16 *) from);
*to += 2;
}
/* offset, read, write functions.
*/
typedef struct _FieldIO {
glong offset;
void (*read)( int msb_first, unsigned char *to, unsigned char **from );
void (*write)( unsigned char **to, unsigned char *from );
} FieldIO;
static FieldIO fields[] = {
{ G_STRUCT_OFFSET( IMAGE, Xsize ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Ysize ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Bands ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Bbits ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, BandFmt ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Coding ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Type ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Xres ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Yres ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Length ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Compression ),
im__read_2byte, im__write_2byte },
{ G_STRUCT_OFFSET( IMAGE, Level ),
im__read_2byte, im__write_2byte },
{ G_STRUCT_OFFSET( IMAGE, Xoffset ),
im__read_4byte, im__write_4byte },
{ G_STRUCT_OFFSET( IMAGE, Yoffset ),
im__read_4byte, im__write_4byte }
};
int
im__read_header_bytes( IMAGE *im, unsigned char *from )
{
int msb_first;
int i;
im__read_4byte( 1, (unsigned char *) &im->magic, &from );
if( im->magic != IM_MAGIC_INTEL && im->magic != IM_MAGIC_SPARC ) {
im_error( "im_open", _( "\"%s\" is not a VIPS image" ),
im->filename );
return( -1 );
}
msb_first = im->magic == IM_MAGIC_SPARC;
for( i = 0; i < IM_NUMBER( fields ); i++ )
fields[i].read( msb_first,
&G_STRUCT_MEMBER( unsigned char, im, fields[i].offset ),
&from );
/* Set this ourselves ... bbits is deprecated in the file format.
*/
im->Bbits = im_bits_of_fmt( im->BandFmt );
return( 0 );
}
int
im__write_header_bytes( IMAGE *im, unsigned char *to )
{
guint32 magic;
int i;
unsigned char *q;
/* Always write the magic number MSB first.
*/
magic = im_amiMSBfirst() ? IM_MAGIC_SPARC : IM_MAGIC_INTEL;
to[0] = magic >> 24;
to[1] = magic >> 16;
to[2] = magic >> 8;
to[3] = magic;
q = to + 4;
for( i = 0; i < IM_NUMBER( fields ); i++ )
fields[i].write( &q,
&G_STRUCT_MEMBER( unsigned char, im,
fields[i].offset ) );
/* Pad spares with zeros.
*/
while( q - to < im->sizeof_header )
*q++ = 0;
return( 0 );
}
/* Read a chunk of an fd into memory. Add a '\0' at the end.
*/
static char *
read_chunk( int fd, gint64 offset, size_t length )
{
char *buf;
if( im__seek( fd, offset ) )
return( NULL );
if( !(buf = im_malloc( NULL, length + 1 )) )
return( NULL );
if( read( fd, buf, length ) != (ssize_t) length ) {
im_free( buf );
im_error( "im_readhist", "%s", _( "unable to read history" ) );
return( NULL );
}
buf[length] = '\0';
return( buf );
}
/* Does it look like an image has an extension block?
*/
int
im__has_extension_block( IMAGE *im )
{
gint64 psize;
psize = im__image_pixel_length( im );
g_assert( im->file_length > 0 );
return( im->file_length - psize > 0 );
}
/* Read everything after the pixels into memory.
*/
void *
im__read_extension_block( IMAGE *im, int *size )
{
gint64 psize;
void *buf;
psize = im__image_pixel_length( im );
g_assert( im->file_length > 0 );
if( im->file_length - psize > 10 * 1024 * 1024 ) {
im_error( "im_readhist",
"%s", _( "more than a 10 megabytes of XML? "
"sufferin' succotash!" ) );
return( NULL );
}
if( im->file_length - psize == 0 )
return( NULL );
if( !(buf = read_chunk( im->fd, psize, im->file_length - psize )) )
return( NULL );
if( size )
*size = im->file_length - psize;
#ifdef DEBUG
printf( "im__read_extension_block: read %d bytes from %s\n",
(int) (im->file_length - psize), im->filename );
printf( "data: \"%s\"\n", (char *) buf );
#endif /*DEBUG*/
return( buf );
}
/* Read everything after the pixels into memory.
FIXME ... why can't we use xmlParserInputBufferCreateFd and parse
directly from the fd rather than having to read the stupid thing into
memory
the libxml API docs are impossible to decipher
*/
static xmlDoc *
read_xml( IMAGE *im )
{
void *buf;
int size;
xmlDoc *doc;
xmlNode *node;
if( !(buf = im__read_extension_block( im, &size )) )
return( NULL );
if( !(doc = xmlParseMemory( buf, size )) ) {
im_free( buf );
return( NULL );
}
im_free( buf );
if( !(node = xmlDocGetRootElement( doc )) ||
!node->nsDef ||
!im_isprefix( NAMESPACE, (char *) node->nsDef->href ) ) {
im_error( "im__readhist",
"%s", _( "incorrect namespace in XML" ) );
xmlFreeDoc( doc );
return( NULL );
}
#ifdef DEBUG
printf( "read_xml: namespace == %s\n", node->nsDef->href );
#endif /*DEBUG*/
return( doc );
}
/* Find the first child node with a name.
*/
static xmlNode *
get_node( xmlNode *base, const char *name )
{
xmlNode *i;
for( i = base->children; i; i = i->next )
if( strcmp( (char *) i->name, name ) == 0 )
return( i );
return( NULL );
}
/* Read a string property to a buffer. TRUE for success.
*/
static int
get_sprop( xmlNode *xnode, const char *name, char *buf, int sz )
{
char *value = (char *) xmlGetProp( xnode, (xmlChar *) name );
if( !value )
return( 0 );
im_strncpy( buf, value, sz );
IM_FREEF( xmlFree, value );
return( 1 );
}
/* Chop history into lines, add each one as a refstring.
*/
static void
set_history( IMAGE *im, char *history )
{
GSList *history_list;
char *p, *q;
/* There can be history there already if we're rewinding.
*/
IM_FREEF( im__gslist_gvalue_free, im->history_list );
history_list = NULL;
for( p = history; *p; p = q ) {
if( (q = strchr( p, '\n' )) ) {
*q = '\0';
q += 1;
}
else
q = p + strlen( p );
history_list = g_slist_prepend( history_list,
im__gvalue_ref_string_new( p ) );
}
im->history_list = g_slist_reverse( history_list );
}
/* Load header fields.
*/
static int
rebuild_header_builtin( IMAGE *im, xmlNode *i )
{
char name[256];
if( get_sprop( i, "name", name, 256 ) ) {
if( strcmp( name, "Hist" ) == 0 ) {
char *history;
/* Have to take (another) copy, since we need to free
* with xmlFree().
*/
history = (char *) xmlNodeGetContent( i );
set_history( im, history );
xmlFree( history );
}
}
return( 0 );
}
/* Load meta fields.
*/
static int
rebuild_header_meta( IMAGE *im, xmlNode *i )
{
char name[256];
char type[256];
if( get_sprop( i, "name", name, 256 ) &&
get_sprop( i, "type", type, 256 ) ) {
GType gtype = g_type_from_name( type );
/* Can we convert from IM_SAVE_STRING to type?
*/
if( gtype &&
g_value_type_transformable(
IM_TYPE_SAVE_STRING, gtype ) ) {
char *content;
GValue save_value = { 0 };
GValue value = { 0 };
content = (char *) xmlNodeGetContent( i );
g_value_init( &save_value, IM_TYPE_SAVE_STRING );
im_save_string_set( &save_value, content );
xmlFree( content );
g_value_init( &value, gtype );
if( !g_value_transform( &save_value, &value ) ) {
g_value_unset( &save_value );
im_error( "im__readhist",
"%s", _( "error transforming from "
"save format" ) );
return( -1 );
}
if( im_meta_set( im, name, &value ) ) {
g_value_unset( &save_value );
g_value_unset( &value );
return( -1 );
}
g_value_unset( &save_value );
g_value_unset( &value );
}
}
return( 0 );
}
static xmlDoc *
get_xml( IMAGE *im )
{
if( im_header_get_typeof( im, IM_META_XML ) ) {
xmlDoc *doc;
if( im_meta_get_area( im, IM_META_XML, (void *) &doc ) )
return( NULL );
return( doc );
}
return( NULL );
}
/* Rebuild header fields that depend on stuff saved in xml.
*/
static int
rebuild_header( IMAGE *im )
{
xmlDoc *doc;
if( (doc = get_xml( im )) ) {
xmlNode *root;
xmlNode *block;
if( !(root = xmlDocGetRootElement( doc )) )
return( -1 );
if( (block = get_node( root, "header" )) ) {
xmlNode *i;
for( i = block->children; i; i = i->next )
if( strcmp( (char *) i->name, "field" ) == 0 )
if( rebuild_header_builtin( im, i ) )
return( -1 );
}
if( (block = get_node( root, "meta" )) ) {
xmlNode *i;
for( i = block->children; i; i = i->next )
if( strcmp( (char *) i->name, "field" ) == 0 )
if( rebuild_header_meta( im, i ) )
return( -1 );
}
}
return( 0 );
}
/* Called at the end of im_openin() ... get any XML after the pixel data
* and read it in.
*/
static int
im__readhist( IMAGE *im )
{
/* Junk any old xml meta.
*/
if( im_header_get_typeof( im, IM_META_XML ) )
im_meta_set_area( im, IM_META_XML, NULL, NULL );
if( im__has_extension_block( im ) ) {
xmlDoc *doc;
if( !(doc = read_xml( im )) )
return( -1 );
if( im_meta_set_area( im, IM_META_XML,
(im_callback_fn) xmlFreeDoc, doc ) ) {
xmlFreeDoc( doc );
return( -1 );
}
}
if( rebuild_header( im ) )
return( -1 );
return( 0 );
}
#define MAX_STRSIZE (32768) /* Max size of text for stack strings */
static int
set_prop( xmlNode *node, const char *name, const char *fmt, ... )
{
va_list ap;
char value[MAX_STRSIZE];
va_start( ap, fmt );
(void) im_vsnprintf( value, MAX_STRSIZE, fmt, ap );
va_end( ap );
if( !xmlSetProp( node, (xmlChar *) name, (xmlChar *) value ) ) {
im_error( "im_writehist", _( "unable to set property \"%s\" "
"to value \"%s\"." ),
name, value );
return( -1 );
}
return( 0 );
}
static int
set_sprop( xmlNode *node, const char *name, const char *value )
{
if( value && set_prop( node, name, "%s", value ) )
return( -1 );
return( 0 );
}
static int
set_field( xmlNode *node,
const char *name, const char *type, const char *content )
{
xmlNode *field;
if( !(field = xmlNewChild( node, NULL, (xmlChar *) "field", NULL )) ||
set_sprop( field, "type", type ) ||
set_sprop( field, "name", name ) )
return( -1 );
xmlNodeSetContent( field, (xmlChar *) content );
return( 0 );
}
static void *
save_fields_meta( Meta *meta, xmlNode *node )
{
GType type = G_VALUE_TYPE( &meta->value );
/* If we can transform to IM_TYPE_SAVE_STRING and back, we can save and
* restore.
*/
if( g_value_type_transformable( type, IM_TYPE_SAVE_STRING ) &&
g_value_type_transformable( IM_TYPE_SAVE_STRING, type ) ) {
GValue save_value = { 0 };
g_value_init( &save_value, IM_TYPE_SAVE_STRING );
if( !g_value_transform( &meta->value, &save_value ) ) {
im_error( "im__writehist", "%s",
_( "error transforming to save format" ) );
return( node );
}
if( set_field( node, meta->field, g_type_name( type ),
im_save_string_get( &save_value ) ) ) {
g_value_unset( &save_value );
return( node );
}
g_value_unset( &save_value );
}
return( NULL );
}
static int
save_fields( IMAGE *im, xmlNode *node )
{
xmlNode *this;
/* Save header fields.
*/
if( !(this = xmlNewChild( node, NULL, (xmlChar *) "header", NULL )) )
return( -1 );
if( set_field( this, "Hist",
g_type_name( IM_TYPE_REF_STRING ), im_history_get( im ) ) )
return( -1 );
if( !(this = xmlNewChild( node, NULL, (xmlChar *) "meta", NULL )) )
return( -1 );
if( im->Meta_traverse &&
im_slist_map2( im->Meta_traverse,
(VSListMap2Fn) save_fields_meta, this, NULL ) )
return( -1 );
return( 0 );
}
int
im__write_extension_block( IMAGE *im, void *buf, int size )
{
gint64 length;
gint64 psize;
psize = im__image_pixel_length( im );
if( (length = im_file_length( im->fd )) == -1 )
return( -1 );
if( length - psize < 0 ) {
im_error( "im__write_extension_block",
"%s", _( "file has been truncated" ) );
return( -1 );
}
if( im__ftruncate( im->fd, psize ) ||
im__seek( im->fd, psize ) )
return( -1 );
if( im__write( im->fd, buf, size ) )
return( -1 );
#ifdef DEBUG
printf( "im__write_extension_block: written %d bytes of XML to %s\n",
size, im->filename );
#endif /*DEBUG*/
return( 0 );
}
#ifdef DEBUG
/* Return a string of n characters. Buffer is zapped each time!
*/
const char *
rpt( char ch, int n )
{
int i;
static char buf[200];
n = IM_MIN( 190, n );
for( i = 0; i < n; i++ )
buf[i] = ch;
buf[i] = '\0';
return( buf );
}
/* Return a string of n spaces. Buffer is zapped each time!
*/
const char *
spc( int n )
{
return( rpt( ' ', n ) );
}
static void
prettify_tree_sub( xmlNode *xnode, int indent )
{
xmlNode *txt;
xmlNode *next;
for(;;) {
next = xnode->next;
/* According to memprof, this leaks :-( If you cut it out into
* a separate prog though, it's OK
FIXME ... how odd
*/
txt = xmlNewText( "\n" );
xmlAddPrevSibling( xnode, txt );
txt = xmlNewText( spc( indent ) );
xmlAddPrevSibling( xnode, txt );
if( xnode->children )
prettify_tree_sub( xnode->children, indent + 2 );
if( !next )
break;
xnode = next;
}
txt = xmlNewText( spc( indent - 2 ) );
xmlAddNextSibling( xnode, txt );
txt = xmlNewText( "\n" );
xmlAddNextSibling( xnode, txt );
}
/* Walk an XML document, adding extra blank text elements so that it's easier
* to read. Don't call me twice!
*/
void
prettify_tree( xmlDoc *xdoc )
{
xmlNode *xnode = xmlDocGetRootElement( xdoc );
prettify_tree_sub( xnode, 0 );
}
#endif /*DEBUG*/
/* Append XML to output fd.
*/
int
im__writehist( IMAGE *im )
{
xmlDoc *doc;
char namespace[256];
char *dump;
int dump_size;
assert( im->dtype == IM_OPENOUT );
assert( im->fd != -1 );
if( !(doc = xmlNewDoc( (xmlChar *) "1.0" )) )
return( -1 );
im_snprintf( namespace, 256, "%s/%d.%d.%d",
NAMESPACE,
IM_MAJOR_VERSION, IM_MINOR_VERSION, IM_MICRO_VERSION );
if( !(doc->children = xmlNewDocNode( doc,
NULL, (xmlChar *) "root", NULL )) ||
set_sprop( doc->children, "xmlns", namespace ) ||
save_fields( im, doc->children ) ) {
im_error( "im__writehist", "%s", _( "xml save error" ) );
xmlFreeDoc( doc );
return( -1 );
}
/* Bizarre double-cast stops a bogus gcc 4.1 compiler warning.
*/
xmlDocDumpMemory( doc, (xmlChar **) ((char *) &dump), &dump_size );
if( !dump ) {
im_error( "im__writehist", "%s", _( "xml save error" ) );
xmlFreeDoc( doc );
return( -1 );
}
if( im__write_extension_block( im, dump, dump_size ) ) {
xmlFreeDoc( doc );
xmlFree( dump );
return( -1 );
}
#ifdef DEBUG
{
char *dump2;
int dump_size2;
/* Uncomment to have XML pretty-printed. Can be annoying during
* debugging tho'
*/
prettify_tree( doc );
xmlDocDumpMemory( doc, (xmlChar **) &dump2, &dump_size2 );
if( !dump2 ) {
im_error( "im__writehist", "%s", _( "xml save error" ) );
xmlFreeDoc( doc );
xmlFree( dump );
return( -1 );
}
printf( "im__writehist: saved XML is: \"%s\"", dump2 );
xmlFree( dump2 );
}
#endif /*DEBUG*/
xmlFreeDoc( doc );
xmlFree( dump );
return( 0 );
}
/* Open the filename, read the header, some sanity checking.
*/
int
im_openin( IMAGE *image )
{
/* We don't use im->sizeof_header here, but we know we're reading a
* VIPS image anyway.
*/
unsigned char header[IM_SIZEOF_HEADER];
gint64 psize;
gint64 rsize;
image->dtype = IM_OPENIN;
if( (image->fd = im__open_image_file( image->filename )) == -1 )
return( -1 );
if( read( image->fd, header, IM_SIZEOF_HEADER ) != IM_SIZEOF_HEADER ||
im__read_header_bytes( image, header ) ) {
im_error( "im__read_header",
_( "unable to read header for \"%s\", %s" ),
image->filename, strerror( errno ) );
return( -1 );
}
/* Predict and check the file size.
*/
psize = im__image_pixel_length( image );
if( (rsize = im_file_length( image->fd )) == -1 )
return( -1 );
image->file_length = rsize;
if( psize > rsize )
im_warn( "im__read_header",
_( "unable to read data for \"%s\", %s" ),
image->filename, _( "file has been truncated" ) );
/* Set demand style. Allow the most permissive sort.
*/
image->dhint = IM_THINSTRIP;
/* Set the history part of im descriptor. Don't return an error if this
* fails (due to eg. corrupted XML) because it's probably mostly
* harmless.
*/
if( im__readhist( image ) ) {
im_warn( "im__read_header", _( "error reading XML: %s" ),
im_error_buffer() );
im_error_clear();
}
return( 0 );
}
/* Open, then mmap() read/write. This is old and deprecated API, use
* im_vips_open() in preference.
*/
int
im_openinrw( IMAGE *image )
{
if( im_openin( image ) )
return( -1 );
if( im_mapfilerw( image ) )
return( -1 );
image->data = image->baseaddr + image->sizeof_header;
image->dtype = IM_MMAPINRW;
#ifdef DEBUG
printf( "im_openinrw: completely mmap()ing \"%s\" read-write\n",
image->filename );
#endif /*DEBUG*/
return( 0 );
}
/* Open a VIPS image for reading and byte-swap the image data if necessary. A
* ":w" at the end of the filename means we open read-write.
*/
IMAGE *
im_open_vips( const char *filename )
{
char name[FILENAME_MAX];
char mode[FILENAME_MAX];
IMAGE *im;
im_filename_split( filename, name, mode );
if( !(im = im_init( name )) )
return( NULL );
if( mode[0] == 'w' ) {
if( im_openinrw( im ) ) {
im_close( im );
return( NULL );
}
if( im_isMSBfirst( im ) != im_amiMSBfirst() ) {
im_close( im );
im_error( "im_open_vips", "%s",
_( "open for read-write for "
"native format images only" ) );
return( NULL );
}
}
else {
if( im_openin( im ) ) {
im_close( im );
return( NULL );
}
}
/* Not in native format? And needs swapping?
*/
if( im_isMSBfirst( im ) != im_amiMSBfirst() &&
im->Coding == IM_CODING_NONE &&
im->BandFmt != IM_BANDFMT_CHAR &&
im->BandFmt != IM_BANDFMT_UCHAR ) {
IMAGE *im2;
if( !(im2 = im_open( filename, "p" )) ) {
im_close( im );
return( NULL );
}
if( im_add_close_callback( im2,
(im_callback_fn) im_close, im, NULL ) ) {
im_close( im );
im_close( im2 );
return( NULL );
}
if( im_copy_swap( im, im2 ) ) {
im_close( im2 );
return( NULL );
}
im = im2;
}
return( im );
}
IMAGE *
im_openout( const char *filename )
{
IMAGE *image;
if( !(image = im_init( filename )) )
return( NULL );
image->dtype = IM_OPENOUT;
return( image );
}