| /* Convert OpenEXR to VIPS |
| * |
| * 1/5/06 |
| * - from im_png2vips.c |
| * 17/5/06 |
| * - oops, buffer calcs were wrong |
| * 19/5/06 |
| * - added tiled read, with a separate cache |
| * - removed *255 we had before, better to do something clever with |
| * chromaticities |
| * 4/2/10 |
| * - gtkdoc |
| |
| TODO |
| |
| - colour management |
| - attributes |
| - more of OpenEXR's pixel formats |
| - more than just RGBA channels |
| |
| the openexr C API is very limited ... it seems RGBA half pixels is |
| all you can do |
| |
| openexr lets you have different formats in different channels :-( |
| |
| there's no API to read the "chromaticities" attribute :-( |
| |
| */ |
| |
| /* |
| |
| 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> |
| |
| #ifndef HAVE_OPENEXR |
| |
| #include <vips/vips.h> |
| |
| int |
| im_exr2vips( const char *name, IMAGE *out ) |
| { |
| im_error( "im_exr2vips", "%s", |
| _( "OpenEXR support disabled" ) ); |
| return( -1 ); |
| } |
| |
| #else /*HAVE_OPENEXR*/ |
| |
| #include <stdio.h> |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <vips/vips.h> |
| #include <vips/thread.h> |
| #include <vips/internal.h> |
| |
| #include <ImfCRgbaFile.h> |
| |
| #ifdef WITH_DMALLOC |
| #include <dmalloc.h> |
| #endif /*WITH_DMALLOC*/ |
| |
| /* What we track during a OpenEXR read. |
| */ |
| typedef struct { |
| char *name; |
| IMAGE *out; |
| |
| ImfTiledInputFile *tiles; |
| ImfInputFile *lines; |
| const ImfHeader *header; |
| Rect window; |
| int tile_width; |
| int tile_height; |
| |
| /* Need to single-thread calls to ReadTile. |
| */ |
| GMutex *lock; |
| } Read; |
| |
| static void |
| get_imf_error( void ) |
| { |
| im_error( "im_exr2vips", _( "EXR error: %s" ), ImfErrorMessage() ); |
| } |
| |
| static void |
| read_destroy( Read *read ) |
| { |
| IM_FREE( read->name ); |
| |
| IM_FREEF( ImfCloseTiledInputFile, read->tiles ); |
| IM_FREEF( ImfCloseInputFile, read->lines ); |
| |
| IM_FREEF( g_mutex_free, read->lock ); |
| |
| im_free( read ); |
| } |
| |
| static Read * |
| read_new( const char *name, IMAGE *out ) |
| { |
| Read *read; |
| int xmin, ymin; |
| int xmax, ymax; |
| |
| if( !(read = IM_NEW( NULL, Read )) ) |
| return( NULL ); |
| |
| read->name = im_strdup( NULL, name ); |
| read->out = out; |
| read->tiles = NULL; |
| read->lines = NULL; |
| read->lock = NULL; |
| |
| if( im_add_close_callback( out, |
| (im_callback_fn) read_destroy, read, NULL ) ) { |
| read_destroy( read ); |
| return( NULL ); |
| } |
| |
| /* Try to open tiled first ... if that fails, fall back to scanlines. |
| |
| FIXME ... seems a bit ugly, but how else can you spot a tiled |
| EXR image? |
| |
| */ |
| if( !(read->tiles = ImfOpenTiledInputFile( read->name )) ) { |
| if( !(read->lines = ImfOpenInputFile( read->name )) ) { |
| get_imf_error(); |
| return( NULL ); |
| } |
| } |
| |
| #ifdef DEBUG |
| if( read->tiles ) |
| printf( "im_exr2vips: opening in tiled mode\n" ); |
| else |
| printf( "im_exr2vips: opening in scanline mode\n" ); |
| #endif /*DEBUG*/ |
| |
| if( read->tiles ) { |
| read->header = ImfTiledInputHeader( read->tiles ); |
| read->lock = g_mutex_new(); |
| read->tile_width = ImfTiledInputTileXSize( read->tiles ); |
| read->tile_height = ImfTiledInputTileYSize( read->tiles ); |
| } |
| else |
| read->header = ImfInputHeader( read->lines ); |
| |
| ImfHeaderDataWindow( read->header, &xmin, &ymin, &xmax, &ymax ); |
| read->window.left = xmin; |
| read->window.top = ymin; |
| read->window.width = xmax - xmin + 1; |
| read->window.height = ymax - ymin + 1; |
| |
| return( read ); |
| } |
| |
| /* Read a OpenEXR file (header) into a VIPS (header). |
| */ |
| static int |
| read_header( Read *read, IMAGE *out ) |
| { |
| /* |
| |
| FIXME ... not really sRGB. I think EXR is actually linear (no |
| gamma). We ought to read the chromaticities from the header, put |
| through a 3x3 matrix and output as XYZ |
| |
| */ |
| im_initdesc( out, |
| read->window.width, read->window.height, 4, |
| IM_BBITS_FLOAT, IM_BANDFMT_FLOAT, |
| IM_CODING_NONE, IM_TYPE_sRGB, 1.0, 1.0, 0, 0 ); |
| |
| return( 0 ); |
| } |
| |
| /* Read a OpenEXR file header into a VIPS header. |
| */ |
| static int |
| exr2vips_header( const char *name, IMAGE *out ) |
| { |
| Read *read; |
| |
| if( !(read = read_new( name, out )) || |
| read_header( read, out ) ) |
| return( -1 ); |
| |
| return( 0 ); |
| } |
| |
| /* Test for tiled EXR. |
| */ |
| static int |
| isexrtiled( const char *name ) |
| { |
| Read *read; |
| int tiled; |
| |
| if( !(read = read_new( name, NULL )) ) |
| return( -1 ); |
| tiled = read->tiles != NULL; |
| read_destroy( read ); |
| |
| return( tiled ); |
| } |
| |
| static int |
| fill_region( REGION *out, void *seq, void *a, void *b ) |
| { |
| ImfRgba *imf_buffer = (ImfRgba *) seq; |
| Read *read = (Read *) a; |
| Rect *r = &out->valid; |
| |
| const int tw = read->tile_width; |
| const int th = read->tile_height; |
| |
| /* Find top left of tiles we need. |
| */ |
| const int xs = (r->left / tw) * tw; |
| const int ys = (r->top / th) * th; |
| |
| int x, y, z; |
| Rect image; |
| |
| /* Area of image. |
| */ |
| image.left = 0; |
| image.top = 0; |
| image.width = read->out->Xsize; |
| image.height = read->out->Ysize; |
| |
| for( y = ys; y < IM_RECT_BOTTOM( r ); y += th ) |
| for( x = xs; x < IM_RECT_RIGHT( r ); x += tw ) { |
| Rect tile; |
| Rect hit; |
| int result; |
| |
| if( !ImfTiledInputSetFrameBuffer( read->tiles, |
| imf_buffer - |
| (read->window.left + x) - |
| (read->window.top + y) * tw, |
| 1, tw ) ) { |
| get_imf_error(); |
| return( -1 ); |
| } |
| |
| #ifdef DEBUG |
| printf( "im_exr2vips: requesting tile %d x %d\n", |
| x / tw, y / th ); |
| #endif /*DEBUG*/ |
| |
| g_mutex_lock( read->lock ); |
| result = ImfTiledInputReadTile( read->tiles, |
| x / tw, y / th, 0, 0 ); |
| g_mutex_unlock( read->lock ); |
| |
| if( !result ) { |
| get_imf_error(); |
| return( -1 ); |
| } |
| |
| /* The tile in the file, in VIPS coordinates. |
| */ |
| tile.left = x; |
| tile.top = y; |
| tile.width = tw; |
| tile.height = th; |
| im_rect_intersectrect( &tile, &image, &tile ); |
| |
| /* The part of this tile that hits the region. |
| */ |
| im_rect_intersectrect( &tile, r, &hit ); |
| |
| /* Convert to float and write to the region. |
| */ |
| for( z = 0; z < hit.height; z++ ) { |
| ImfRgba *p = imf_buffer + |
| (hit.left - tile.left) + |
| (hit.top - tile.top + z) * tw; |
| float *q = (float *) IM_REGION_ADDR( out, |
| hit.left, hit.top + z ); |
| |
| ImfHalfToFloatArray( 4 * hit.width, |
| (ImfHalf *) p, q ); |
| } |
| } |
| |
| return( 0 ); |
| } |
| |
| /* Allocate a tile buffer. |
| */ |
| static void * |
| seq_start( IMAGE *out, void *a, void *b ) |
| { |
| Read *read = (Read *) a; |
| ImfRgba *imf_buffer; |
| |
| if( !(imf_buffer = IM_ARRAY( out, |
| read->tile_width * read->tile_height, ImfRgba )) ) |
| return( NULL ); |
| |
| return( imf_buffer ); |
| } |
| |
| /* Read tilewise. |
| */ |
| static int |
| exr2vips_tiles( Read *read, IMAGE *out ) |
| { |
| if( read_header( read, out ) || |
| im_poutcheck( out ) || |
| im_demand_hint( out, IM_SMALLTILE, NULL ) || |
| im_generate( out, seq_start, fill_region, NULL, read, NULL ) ) |
| return( -1 ); |
| |
| return( 0 ); |
| } |
| |
| /* Read scanlinewise. |
| */ |
| static int |
| exr2vips_lines( Read *read, IMAGE *out ) |
| { |
| const int left = read->window.left; |
| const int top = read->window.top; |
| const int width = read->window.width; |
| const int height = read->window.height; |
| |
| ImfRgba *imf_buffer; |
| float *vips_buffer; |
| int y; |
| |
| if( !(imf_buffer = IM_ARRAY( out, width, ImfRgba )) || |
| !(vips_buffer = IM_ARRAY( out, 4 * width, float )) || |
| read_header( read, out ) || |
| im_outcheck( out ) || |
| im_setupout( out ) ) |
| return( -1 ); |
| |
| for( y = 0; y < height; y++ ) { |
| if( !ImfInputSetFrameBuffer( read->lines, |
| imf_buffer - left - (top + y) * width, |
| 1, width ) ) { |
| get_imf_error(); |
| return( -1 ); |
| } |
| if( !ImfInputReadPixels( read->lines, top + y, top + y ) ) { |
| get_imf_error(); |
| return( -1 ); |
| } |
| |
| ImfHalfToFloatArray( 4 * width, |
| (ImfHalf *) imf_buffer, vips_buffer ); |
| |
| if( im_writeline( y, out, (PEL *) vips_buffer ) ) |
| return( -1 ); |
| } |
| |
| return( 0 ); |
| } |
| |
| static int |
| exr2vips( Read *read ) |
| { |
| if( read->tiles ) { |
| IMAGE *raw; |
| |
| /* Tile cache: keep enough for two complete rows of tiles. |
| * This lets us do (smallish) area ops, like im_conv(), while |
| * still only hitting each OpenEXR tile once. |
| */ |
| if( !(raw = im_open_local( read->out, "cache", "p" )) ) |
| return( -1 ); |
| if( exr2vips_tiles( read, raw ) ) |
| return( -1 ); |
| if( im_tile_cache( raw, read->out, |
| read->tile_width, read->tile_height, |
| 2 * (1 + raw->Xsize / read->tile_width) ) ) |
| return( -1 ); |
| } |
| else { |
| if( exr2vips_lines( read, read->out ) ) |
| return( -1 ); |
| } |
| |
| return( 0 ); |
| } |
| |
| /** |
| * im_exr2vips: |
| * @filename: file to load |
| * @out: image to write to |
| * |
| * Read a OpenEXR file into a VIPS image. |
| * |
| * The reader can handle scanline and tiled OpenEXR images. It can't handle |
| * OpenEXR colour management, image attributes, many pixel formats, anything |
| * other than RGBA. |
| * |
| * This reader uses the rather limited OpenEXR C API. It should really be |
| * redone in C++. |
| * |
| * See also: #VipsFormat. |
| * |
| * Returns: 0 on success, -1 on error. |
| */ |
| int |
| im_exr2vips( const char *filename, IMAGE *out ) |
| { |
| Read *read; |
| |
| #ifdef DEBUG |
| printf( "im_exr2vips: reading \"%s\"\n", filename ); |
| #endif /*DEBUG*/ |
| |
| if( !(read = read_new( filename, out )) || |
| exr2vips( read ) ) |
| return( -1 ); |
| |
| return( 0 ); |
| } |
| |
| static int |
| isexr( const char *filename ) |
| { |
| unsigned char buf[4]; |
| |
| if( im__get_bytes( filename, buf, 4 ) ) |
| if( buf[0] == 0x76 && buf[1] == 0x2f && |
| buf[2] == 0x31 && buf[3] == 0x01 ) |
| return( 1 ); |
| |
| return( 0 ); |
| } |
| |
| static const char *exr_suffs[] = { ".exr", NULL }; |
| |
| static VipsFormatFlags |
| exr_flags( const char *filename ) |
| { |
| VipsFormatFlags flags; |
| |
| flags = 0; |
| if( isexrtiled( filename ) ) |
| flags |= VIPS_FORMAT_PARTIAL; |
| |
| return( flags ); |
| } |
| |
| /* exr format adds no new members. |
| */ |
| typedef VipsFormat VipsFormatExr; |
| typedef VipsFormatClass VipsFormatExrClass; |
| |
| static void |
| vips_format_exr_class_init( VipsFormatExrClass *class ) |
| { |
| VipsObjectClass *object_class = (VipsObjectClass *) class; |
| VipsFormatClass *format_class = (VipsFormatClass *) class; |
| |
| object_class->nickname = "exr"; |
| object_class->description = _( "OpenEXR" ); |
| |
| format_class->is_a = isexr; |
| format_class->header = exr2vips_header; |
| format_class->load = im_exr2vips; |
| format_class->get_flags = exr_flags; |
| format_class->suffs = exr_suffs; |
| } |
| |
| static void |
| vips_format_exr_init( VipsFormatExr *object ) |
| { |
| } |
| |
| G_DEFINE_TYPE( VipsFormatExr, vips_format_exr, VIPS_TYPE_FORMAT ); |
| |
| #endif /*HAVE_OPENEXR*/ |