diff --git a/ChangeLog b/ChangeLog
index 51504978..2f00ba1a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -6,6 +6,8 @@
- sRGB2scRGB and scRGB2sRGB scale 16-bit alpha to and from 8-bit
- tiff pyramid writer no longer copies base image
- added vips_region_shrink(), fast x2 shrinker
+- add magicload_buffer() [mcuelenaere]
+- added test_foreign.py, plus more test images
6/2/15 started 7.42.3
- bump version for back-compat ABI change
diff --git a/cplusplus/include/vips/vips-operators.h b/cplusplus/include/vips/vips-operators.h
index 301a444f..e2aa71f1 100644
--- a/cplusplus/include/vips/vips-operators.h
+++ b/cplusplus/include/vips/vips-operators.h
@@ -226,6 +226,8 @@ static VImage openslideload( char * filename , VOption *options = 0 )
throw( VError );
static VImage magickload( char * filename , VOption *options = 0 )
throw( VError );
+static VImage magickload_buffer( VipsBlob * buffer , VOption *options = 0 )
+ throw( VError );
static VImage fitsload( char * filename , VOption *options = 0 )
throw( VError );
static VImage openexrload( char * filename , VOption *options = 0 )
diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp
index e8a0687b..0bc154fd 100644
--- a/cplusplus/vips-operators.cpp
+++ b/cplusplus/vips-operators.cpp
@@ -1567,6 +1567,19 @@ VImage VImage::magickload( char * filename , VOption *options )
return( out );
}
+VImage VImage::magickload_buffer( VipsBlob * buffer , VOption *options )
+ throw( VError )
+{
+ VImage out;
+
+ call( "magickload_buffer" ,
+ (options ? options : VImage::option()) ->
+ set( "buffer", buffer ) ->
+ set( "out", &out ) );
+
+ return( out );
+}
+
VImage VImage::fitsload( char * filename , VOption *options )
throw( VError )
{
diff --git a/doc/function-list.xml b/doc/function-list.xml
index 6ed50238..f16e7b1e 100644
--- a/doc/function-list.xml
+++ b/doc/function-list.xml
@@ -641,6 +641,11 @@
load file with ImageMagick
vips_magickload()
+
+ magickload_buffer
+ load image from buffer with ImageMagick
+ vips_magickload_buffer()
+
fitsload
load a FITS image
diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c
index 61ccce47..21ffea29 100644
--- a/libvips/foreign/foreign.c
+++ b/libvips/foreign/foreign.c
@@ -1545,7 +1545,8 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_raw_get_type( void );
extern GType vips_foreign_save_raw_get_type( void );
extern GType vips_foreign_save_raw_fd_get_type( void );
- extern GType vips_foreign_load_magick_get_type( void );
+ extern GType vips_foreign_load_magick_file_get_type( void );
+ extern GType vips_foreign_load_magick_buffer_get_type( void );
extern GType vips_foreign_save_dz_get_type( void );
extern GType vips_foreign_load_webp_file_get_type( void );
extern GType vips_foreign_load_webp_buffer_get_type( void );
@@ -1609,7 +1610,8 @@ vips_foreign_operation_init( void )
#endif /*HAVE_OPENSLIDE*/
#ifdef HAVE_MAGICK
- vips_foreign_load_magick_get_type();
+ vips_foreign_load_magick_file_get_type();
+ vips_foreign_load_magick_buffer_get_type();
#endif /*HAVE_MAGICK*/
#ifdef HAVE_CFITSIO
@@ -1668,6 +1670,48 @@ vips_magickload( const char *filename, VipsImage **out, ... )
return( result );
}
+/**
+ * vips_magickload_buffer:
+ * @buf: memory area to load
+ * @len: size of memory area
+ * @out: image to write
+ * @...: %NULL-terminated list of optional named arguments
+ *
+ * Optional arguments:
+ *
+ * @all_frames: %gboolean, load all frames in sequence
+ * @density: string, canvas resolution for rendering vector formats like SVG
+ *
+ * Read an image memory block using libMagick into a VIPS image. Exactly as
+ * vips_magickload(), but read from a memory source.
+ *
+ * You must not free the buffer while @out is active. The
+ * #VipsObject::postclose signal on @out is a good place to free.
+ *
+ * See also: vips_magickload().
+ *
+ * Returns: 0 on success, -1 on error.
+ */
+int
+vips_magickload_buffer( void *buf, size_t len, VipsImage **out, ... )
+{
+ va_list ap;
+ VipsBlob *blob;
+ int result;
+
+ /* We don't take a copy of the data or free it.
+ */
+ blob = vips_blob_new( NULL, buf, len );
+
+ va_start( ap, out );
+ result = vips_call_split( "magickload_buffer", ap, blob, out );
+ va_end( ap );
+
+ vips_area_unref( VIPS_AREA( blob ) );
+
+ return( result );
+}
+
/**
* vips_tiffload:
* @filename: file to load
diff --git a/libvips/foreign/magick.h b/libvips/foreign/magick.h
index 80b98772..1d43b13c 100644
--- a/libvips/foreign/magick.h
+++ b/libvips/foreign/magick.h
@@ -40,6 +40,11 @@ int vips__magick_read( const char *filename,
int vips__magick_read_header( const char *filename,
VipsImage *out, gboolean all_frames, const char *density );
+int vips__magick_read_buffer( const void *buf, const size_t len,
+ VipsImage *out, gboolean all_frames, const char *density );
+int vips__magick_read_buffer_header( const void *buf, const size_t len,
+ VipsImage *out, gboolean all_frames, const char *density );
+
#ifdef __cplusplus
}
#endif /*__cplusplus*/
diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c
index e2df66ab..9dffc9ea 100644
--- a/libvips/foreign/magick2vips.c
+++ b/libvips/foreign/magick2vips.c
@@ -42,6 +42,8 @@
* - add @all_frames option, off by default
* 4/12/14 Lovell
* - add @density option
+ * 16/2/15 mcuelenaere
+ * - add blob read
*/
/*
@@ -160,7 +162,7 @@ read_new( const char *filename, VipsImage *im, gboolean all_frames,
if( !(read = VIPS_NEW( im, Read )) )
return( NULL );
- read->filename = g_strdup( filename );
+ read->filename = filename ? g_strdup( filename ) : NULL;
read->all_frames = all_frames;
read->im = im;
read->image = NULL;
@@ -176,7 +178,9 @@ read_new( const char *filename, VipsImage *im, gboolean all_frames,
if( !read->image_info )
return( NULL );
- vips_strncpy( read->image_info->filename, filename, MaxTextExtent );
+ if( filename )
+ vips_strncpy( read->image_info->filename,
+ filename, MaxTextExtent );
/* Canvas resolution for rendering vector formats like SVG.
*/
@@ -740,5 +744,88 @@ vips__magick_read_header( const char *filename, VipsImage *im,
return( 0 );
}
+int
+vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out,
+ gboolean all_frames, const char *density )
+{
+ Read *read;
+
+#ifdef DEBUG
+ printf( "magick2vips: vips__magick_read_buffer: %p %zu\n", buf, len );
+#endif /*DEBUG*/
+
+ if( !(read = read_new( NULL, out, all_frames, density )) )
+ return( -1 );
+
+#ifdef HAVE_SETIMAGEOPTION
+ /* When reading DICOM images, we want to ignore any
+ * window_center/_width setting, since it may put pixels outside the
+ * 0-65535 range and lose data.
+ *
+ * These window settings are attached as vips metadata, so our caller
+ * can interpret them if it wants.
+ */
+ SetImageOption( read->image_info, "dcm:display-range", "reset" );
+#endif /*HAVE_SETIMAGEOPTION*/
+
+#ifdef DEBUG
+ printf( "magick2vips: calling BlobToImage() ...\n" );
+#endif /*DEBUG*/
+
+ read->image = BlobToImage( read->image_info,
+ buf, len, &read->exception );
+ if( !read->image ) {
+ vips_error( "magick2vips", _( "unable to read buffer\n"
+ "libMagick error: %s %s" ),
+ read->exception.reason, read->exception.description );
+ return( -1 );
+ }
+
+ if( parse_header( read ) )
+ return( -1 );
+ if( vips_image_generate( out,
+ NULL, magick_fill_region, NULL, read, NULL ) )
+ return( -1 );
+
+ return( 0 );
+}
+
+int
+vips__magick_read_buffer_header( const void *buf, const size_t len,
+ VipsImage *im, gboolean all_frames, const char *density )
+{
+ Read *read;
+
+#ifdef DEBUG
+ printf( "vips__magick_read_buffer_header: %p %zu\n", buf, len );
+#endif /*DEBUG*/
+
+ if( !(read = read_new( NULL, im, all_frames, density )) )
+ return( -1 );
+
+#ifdef DEBUG
+ printf( "vips__magick_read_buffer_header: pinging blob ...\n" );
+#endif /*DEBUG*/
+
+ read->image = PingBlob( read->image_info, buf, len, &read->exception );
+ if( !read->image ) {
+ vips_error( "magick2vips", _( "unable to ping blob\n"
+ "libMagick error: %s %s" ),
+ read->exception.reason, read->exception.description );
+ return( -1 );
+ }
+
+ if( parse_header( read ) )
+ return( -1 );
+
+ if( im->Xsize <= 0 ||
+ im->Ysize <= 0 ) {
+ vips_error( "magick2vips", "%s", _( "bad image size" ) );
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
#endif /*HAVE_MAGICK*/
diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c
index e4d2c8e2..d2ec6ddb 100644
--- a/libvips/foreign/magickload.c
+++ b/libvips/foreign/magickload.c
@@ -59,7 +59,6 @@
typedef struct _VipsForeignLoadMagick {
VipsForeignLoad parent_object;
- char *filename;
gboolean all_frames;
char *density;
@@ -67,24 +66,9 @@ typedef struct _VipsForeignLoadMagick {
typedef VipsForeignLoadClass VipsForeignLoadMagickClass;
-G_DEFINE_TYPE( VipsForeignLoadMagick, vips_foreign_load_magick,
+G_DEFINE_ABSTRACT_TYPE( VipsForeignLoadMagick, vips_foreign_load_magick,
VIPS_TYPE_FOREIGN_LOAD );
-static gboolean
-ismagick( const char *filename )
-{
- VipsImage *t;
- int result;
-
- t = vips_image_new();
- vips_error_freeze();
- result = vips__magick_read_header( filename, t, FALSE, NULL );
- g_object_unref( t );
- vips_error_thaw();
-
- return( result == 0 );
-}
-
static VipsForeignFlags
vips_foreign_load_magick_get_flags_filename( const char *filename )
{
@@ -94,32 +78,7 @@ vips_foreign_load_magick_get_flags_filename( const char *filename )
static VipsForeignFlags
vips_foreign_load_magick_get_flags( VipsForeignLoad *load )
{
- VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load;
-
- return( vips_foreign_load_magick_get_flags_filename(
- magick->filename ) );
-}
-
-/*
- * Unfortunately, libMagick does not support header-only reads very well. See
- *
- * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017
- *
- * Test especially with BMP, GIF, TGA. So we are forced to read the entire
- * image in the @header() method.
- */
-static int
-vips_foreign_load_magick_header( VipsForeignLoad *load )
-{
- VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load;
-
- if( vips__magick_read( magick->filename,
- load->out, magick->all_frames, magick->density ) )
- return( -1 );
-
- VIPS_SETSTR( load->out->filename, magick->filename );
-
- return( 0 );
+ return( VIPS_FOREIGN_PARTIAL );
}
static void
@@ -133,27 +92,17 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class )
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
- object_class->nickname = "magickload";
- object_class->description = _( "load file with ImageMagick" );
+ object_class->nickname = "magickload_base";
+ object_class->description = _( "load with ImageMagick" );
- /* We need to be well to the back of the queue since the vips's
+ /* We need to be well to the back of the queue since vips's
* dedicated loaders are usually preferable.
*/
foreign_class->priority = -100;
- load_class->is_a = ismagick;
load_class->get_flags_filename =
vips_foreign_load_magick_get_flags_filename;
load_class->get_flags = vips_foreign_load_magick_get_flags;
- load_class->header = vips_foreign_load_magick_header;
- load_class->load = NULL;
-
- VIPS_ARG_STRING( class, "filename", 1,
- _( "Filename" ),
- _( "Filename to load from" ),
- VIPS_ARGUMENT_REQUIRED_INPUT,
- G_STRUCT_OFFSET( VipsForeignLoadMagick, filename ),
- NULL );
VIPS_ARG_BOOL( class, "all_frames", 3,
_( "all_frames" ),
@@ -175,4 +124,167 @@ vips_foreign_load_magick_init( VipsForeignLoadMagick *magick )
{
}
+typedef struct _VipsForeignLoadMagickFile {
+ VipsForeignLoadMagick parent_object;
+
+ char *filename;
+
+} VipsForeignLoadMagickFile;
+
+typedef VipsForeignLoadMagickClass VipsForeignLoadMagickFileClass;
+
+G_DEFINE_TYPE( VipsForeignLoadMagickFile, vips_foreign_load_magick_file,
+ vips_foreign_load_magick_get_type() );
+
+static gboolean
+ismagick( const char *filename )
+{
+ VipsImage *t;
+ int result;
+
+ t = vips_image_new();
+ vips_error_freeze();
+ result = vips__magick_read_header( filename, t, FALSE, NULL );
+ g_object_unref( t );
+ vips_error_thaw();
+
+ return( result == 0 );
+}
+
+/* Unfortunately, libMagick does not support header-only reads very well. See
+ *
+ * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017
+ *
+ * Test especially with BMP, GIF, TGA. So we are forced to read the entire
+ * image in the @header() method.
+ */
+static int
+vips_foreign_load_magick_file_header( VipsForeignLoad *load )
+{
+ VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load;
+ VipsForeignLoadMagickFile *magick_file =
+ (VipsForeignLoadMagickFile *) load;
+
+ if( vips__magick_read( magick_file->filename,
+ load->out, magick->all_frames, magick->density ) )
+ return( -1 );
+
+ VIPS_SETSTR( load->out->filename, magick_file->filename );
+
+ return( 0 );
+}
+
+static void
+vips_foreign_load_magick_file_class_init(
+ VipsForeignLoadMagickFileClass *class )
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS( class );
+ VipsObjectClass *object_class = (VipsObjectClass *) class;
+ VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
+
+ gobject_class->set_property = vips_object_set_property;
+ gobject_class->get_property = vips_object_get_property;
+
+ object_class->nickname = "magickload";
+ object_class->description = _( "load file with ImageMagick" );
+
+ load_class->is_a = ismagick;
+ load_class->header = vips_foreign_load_magick_file_header;
+ load_class->load = NULL;
+
+ VIPS_ARG_STRING( class, "filename", 1,
+ _( "Filename" ),
+ _( "Filename to load from" ),
+ VIPS_ARGUMENT_REQUIRED_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadMagickFile, filename ),
+ NULL );
+
+}
+
+static void
+vips_foreign_load_magick_file_init( VipsForeignLoadMagickFile *magick_file )
+{
+}
+
+typedef struct _VipsForeignLoadMagickBuffer {
+ VipsForeignLoadMagick parent_object;
+
+ VipsArea *buf;
+
+} VipsForeignLoadMagickBuffer;
+
+typedef VipsForeignLoadMagickClass VipsForeignLoadMagickBufferClass;
+
+G_DEFINE_TYPE( VipsForeignLoadMagickBuffer, vips_foreign_load_magick_buffer,
+ vips_foreign_load_magick_get_type() );
+
+static gboolean
+vips_foreign_load_magick_buffer_is_a_buffer ( void* buf, size_t len )
+{
+ VipsImage *t;
+ int result;
+
+ t = vips_image_new();
+ vips_error_freeze();
+ result = vips__magick_read_buffer_header( buf, len, t, FALSE, NULL );
+ g_object_unref( t );
+ vips_error_thaw();
+
+ return( result == 0 );
+}
+
+/* Unfortunately, libMagick does not support header-only reads very well. See
+ *
+ * http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017
+ *
+ * Test especially with BMP, GIF, TGA. So we are forced to read the entire
+ * image in the @header() method.
+ */
+static int
+vips_foreign_load_magick_buffer_header( VipsForeignLoad *load )
+{
+ VipsForeignLoadMagick *magick = (VipsForeignLoadMagick *) load;
+ VipsForeignLoadMagickBuffer *magick_buffer =
+ (VipsForeignLoadMagickBuffer *) load;
+
+ if( vips__magick_read_buffer(
+ magick_buffer->buf->data, magick_buffer->buf->length,
+ load->out, magick->all_frames, magick->density ) )
+ return( -1 );
+
+ return( 0 );
+}
+
+static void
+vips_foreign_load_magick_buffer_class_init(
+ VipsForeignLoadMagickBufferClass *class )
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS( class );
+ VipsObjectClass *object_class = (VipsObjectClass *) class;
+ VipsForeignLoadClass *load_class = (VipsForeignLoadClass *) class;
+
+ gobject_class->set_property = vips_object_set_property;
+ gobject_class->get_property = vips_object_get_property;
+
+ object_class->nickname = "magickload_buffer";
+ object_class->description = _( "load buffer with ImageMagick" );
+
+ load_class->is_a_buffer = vips_foreign_load_magick_buffer_is_a_buffer;
+ load_class->header = vips_foreign_load_magick_buffer_header;
+ load_class->load = NULL;
+
+ VIPS_ARG_BOXED( class, "buffer", 1,
+ _( "Buffer" ),
+ _( "Buffer to load from" ),
+ VIPS_ARGUMENT_REQUIRED_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadMagickBuffer, buf ),
+ VIPS_TYPE_BLOB );
+
+}
+
+static void
+vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer )
+{
+}
+
#endif /*HAVE_MAGICK*/
diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c
index 3d45f530..59dac8e1 100644
--- a/libvips/foreign/vips2tiff.c
+++ b/libvips/foreign/vips2tiff.c
@@ -205,13 +205,13 @@
#include "tiff.h"
-typedef struct pyramid_layer PyramidLayer;
-typedef struct tiff_write TiffWrite;
+typedef struct _Layer Layer;
+typedef struct _Write Write;
/* A layer in the pyramid.
*/
-struct pyramid_layer {
- TiffWrite *tw; /* Main TIFF write struct */
+struct _Layer {
+ Write *write; /* Main write struct */
int width, height; /* Layer size */
int sub; /* Subsample factor for this layer */
@@ -234,17 +234,17 @@ struct pyramid_layer {
VipsRegion *strip; /* The current strip of pixels */
VipsRegion *copy; /* Pixels we copy to the next strip */
- PyramidLayer *below; /* The smaller layer below us */
- PyramidLayer *above; /* The larger layer above */
+ Layer *below; /* The smaller layer below us */
+ Layer *above; /* The larger layer above */
};
/* A TIFF image in the process of being written.
*/
-struct tiff_write {
+struct _Write {
VipsImage *im; /* Original input image */
char *name; /* Name we write to */
- PyramidLayer *layer; /* Top of pyramid */
+ Layer *layer; /* Top of pyramid */
VipsPel *tbuf; /* TIFF output buffer */
int compression; /* Compression type */
@@ -265,10 +265,10 @@ struct tiff_write {
/* Open TIFF for output.
*/
static TIFF *
-tiff_openout( TiffWrite *tw, const char *name )
+tiff_openout( Write *write, const char *name )
{
TIFF *tif;
- const char *mode = tw->bigtiff ? "w8" : "w";
+ const char *mode = write->bigtiff ? "w8" : "w";
#ifdef DEBUG
printf( "TIFFOpen( \"%s\", \"%s\" )\n", name, mode );
@@ -368,7 +368,7 @@ LabS2Lab16( VipsPel *q, VipsPel *p, int n )
/* Pack a VIPS region into a TIFF tile buffer.
*/
static void
-pack2tiff( TiffWrite *tw, VipsRegion *in, VipsPel *q, VipsRect *area )
+pack2tiff( Write *write, VipsRegion *in, VipsPel *q, VipsRect *area )
{
int y;
@@ -379,17 +379,17 @@ pack2tiff( TiffWrite *tw, VipsRegion *in, VipsPel *q, VipsRect *area )
* Black out the tile first to make sure these edge pixels are always
* zero.
*/
- if( tw->compression == COMPRESSION_JPEG &&
- (area->width < tw->tilew ||
- area->height < tw->tileh) )
- memset( q, 0, TIFFTileSize( tw->tif ) );
+ if( write->compression == COMPRESSION_JPEG &&
+ (area->width < write->tilew ||
+ area->height < write->tileh) )
+ memset( q, 0, TIFFTileSize( write->tif ) );
for( y = area->top; y < VIPS_RECT_BOTTOM( area ); y++ ) {
VipsPel *p = (VipsPel *) VIPS_REGION_ADDR( in, area->left, y );
if( in->im->Coding == VIPS_CODING_LABQ )
LabQ2LabC( q, p, area->width );
- else if( tw->onebit )
+ else if( write->onebit )
eightbit2onebit( q, p, area->width );
else if( in->im->BandFmt == VIPS_FORMAT_SHORT &&
in->im->Type == VIPS_INTERPRETATION_LABS )
@@ -398,7 +398,7 @@ pack2tiff( TiffWrite *tw, VipsRegion *in, VipsPel *q, VipsRect *area )
memcpy( q, p,
area->width * VIPS_IMAGE_SIZEOF_PEL( in->im ) );
- q += tw->tls;
+ q += write->tls;
}
}
@@ -442,16 +442,16 @@ embed_profile_meta( TIFF *tif, VipsImage *im )
}
static int
-embed_profile( TiffWrite *tw, TIFF *tif )
+write_embed_profile( Write *write, TIFF *tif )
{
- if( tw->icc_profile &&
- strcmp( tw->icc_profile, "none" ) != 0 &&
- embed_profile_file( tif, tw->icc_profile ) )
+ if( write->icc_profile &&
+ strcmp( write->icc_profile, "none" ) != 0 &&
+ embed_profile_file( tif, write->icc_profile ) )
return( -1 );
- if( !tw->icc_profile &&
- vips_image_get_typeof( tw->im, VIPS_META_ICC_NAME ) &&
- embed_profile_meta( tif, tw->im ) )
+ if( !write->icc_profile &&
+ vips_image_get_typeof( write->im, VIPS_META_ICC_NAME ) &&
+ embed_profile_meta( tif, write->im ) )
return( -1 );
return( 0 );
@@ -460,14 +460,14 @@ embed_profile( TiffWrite *tw, TIFF *tif )
/* Embed any XMP metadata.
*/
static int
-embed_xmp( TiffWrite *tw, TIFF *tif )
+write_embed_xmp( Write *write, TIFF *tif )
{
void *data;
size_t data_length;
- if( !vips_image_get_typeof( tw->im, VIPS_META_XMP_NAME ) )
+ if( !vips_image_get_typeof( write->im, VIPS_META_XMP_NAME ) )
return( 0 );
- if( vips_image_get_blob( tw->im, VIPS_META_XMP_NAME,
+ if( vips_image_get_blob( write->im, VIPS_META_XMP_NAME,
&data, &data_length ) )
return( -1 );
TIFFSetField( tif, TIFFTAG_XMLPACKET, data_length, data );
@@ -483,7 +483,7 @@ embed_xmp( TiffWrite *tw, TIFF *tif )
* writing (it may have been shrunk).
*/
static int
-write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
+write_tiff_header( Write *write, Layer *layer )
{
TIFF *tif = layer->tif;
@@ -496,35 +496,35 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
TIFFSetField( tif, TIFFTAG_IMAGELENGTH, layer->height );
TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG );
TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT );
- TIFFSetField( tif, TIFFTAG_COMPRESSION, tw->compression );
+ TIFFSetField( tif, TIFFTAG_COMPRESSION, write->compression );
- if( tw->compression == COMPRESSION_JPEG )
- TIFFSetField( tif, TIFFTAG_JPEGQUALITY, tw->jpqual );
+ if( write->compression == COMPRESSION_JPEG )
+ TIFFSetField( tif, TIFFTAG_JPEGQUALITY, write->jpqual );
- if( tw->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
- TIFFSetField( tif, TIFFTAG_PREDICTOR, tw->predictor );
+ if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
+ TIFFSetField( tif, TIFFTAG_PREDICTOR, write->predictor );
/* Don't write mad resolutions (eg. zero), it confuses some programs.
*/
- TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, tw->resunit );
+ TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, write->resunit );
TIFFSetField( tif, TIFFTAG_XRESOLUTION,
- VIPS_CLIP( 0.01, tw->xres, 1000000 ) );
+ VIPS_CLIP( 0.01, write->xres, 1000000 ) );
TIFFSetField( tif, TIFFTAG_YRESOLUTION,
- VIPS_CLIP( 0.01, tw->yres, 1000000 ) );
+ VIPS_CLIP( 0.01, write->yres, 1000000 ) );
- if( embed_profile( tw, tif ) )
+ if( write_embed_profile( write, tif ) )
return( -1 );
- if( embed_xmp( tw, tif ) )
+ if( write_embed_xmp( write, tif ) )
return( -1 );
/* And colour fields.
*/
- if( tw->im->Coding == VIPS_CODING_LABQ ) {
+ if( write->im->Coding == VIPS_CODING_LABQ ) {
TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 );
TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB );
}
- else if( tw->onebit ) {
+ else if( write->onebit ) {
TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 );
TIFFSetField( tif,
@@ -533,15 +533,15 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
else {
int photometric;
- TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, tw->im->Bands );
+ TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, write->im->Bands );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE,
- vips_format_sizeof( tw->im->BandFmt ) << 3 );
+ vips_format_sizeof( write->im->BandFmt ) << 3 );
- switch( tw->im->Bands ) {
+ switch( write->im->Bands ) {
case 1:
case 2:
photometric = PHOTOMETRIC_MINISBLACK;
- if( tw->im->Bands == 2 ) {
+ if( write->im->Bands == 2 ) {
v[0] = EXTRASAMPLE_ASSOCALPHA;
TIFFSetField( tif, TIFFTAG_EXTRASAMPLES, 1, v );
}
@@ -552,18 +552,18 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
/* could be: RGB, RGBA, CMYK, LAB, LABA, generic
* multi-band image.
*/
- if( tw->im->Type == VIPS_INTERPRETATION_LAB ||
- tw->im->Type == VIPS_INTERPRETATION_LABS )
+ if( write->im->Type == VIPS_INTERPRETATION_LAB ||
+ write->im->Type == VIPS_INTERPRETATION_LABS )
photometric = PHOTOMETRIC_CIELAB;
- else if( tw->im->Type == VIPS_INTERPRETATION_CMYK ) {
+ else if( write->im->Type == VIPS_INTERPRETATION_CMYK ) {
photometric = PHOTOMETRIC_SEPARATED;
TIFFSetField( tif,
TIFFTAG_INKSET, INKSET_CMYK );
}
- else if( tw->compression == COMPRESSION_JPEG &&
- tw->im->Bands == 3 &&
- tw->im->BandFmt == VIPS_FORMAT_UCHAR &&
- (!tw->rgbjpeg && tw->jpqual < 90) ) {
+ else if( write->compression == COMPRESSION_JPEG &&
+ write->im->Bands == 3 &&
+ write->im->BandFmt == VIPS_FORMAT_UCHAR &&
+ (!write->rgbjpeg && write->jpqual < 90) ) {
/* This signals to libjpeg that it can do
* YCbCr chrominance subsampling from RGB, not
* that we will supply the image as YCbCr.
@@ -575,8 +575,8 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
else
photometric = PHOTOMETRIC_RGB;
- if( tw->im->Type != VIPS_INTERPRETATION_CMYK &&
- tw->im->Bands == 4 ) {
+ if( write->im->Type != VIPS_INTERPRETATION_CMYK &&
+ write->im->Bands == 4 ) {
v[0] = EXTRASAMPLE_ASSOCALPHA;
TIFFSetField( tif, TIFFTAG_EXTRASAMPLES, 1, v );
}
@@ -604,13 +604,13 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
/* Layout.
*/
- if( tw->tile ) {
- TIFFSetField( tif, TIFFTAG_TILEWIDTH, tw->tilew );
- TIFFSetField( tif, TIFFTAG_TILELENGTH, tw->tileh );
+ if( write->tile ) {
+ TIFFSetField( tif, TIFFTAG_TILEWIDTH, write->tilew );
+ TIFFSetField( tif, TIFFTAG_TILELENGTH, write->tileh );
}
else
TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, 16 );
- if( tif != tw->tif ) {
+ if( tif != write->tif ) {
/* Pyramid layer.
*/
TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE );
@@ -619,13 +619,13 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
/* Sample format.
*/
format = SAMPLEFORMAT_UINT;
- if( vips_band_format_isuint( tw->im->BandFmt ) )
+ if( vips_band_format_isuint( write->im->BandFmt ) )
format = SAMPLEFORMAT_UINT;
- else if( vips_band_format_isint( tw->im->BandFmt ) )
+ else if( vips_band_format_isint( write->im->BandFmt ) )
format = SAMPLEFORMAT_INT;
- else if( vips_band_format_isfloat( tw->im->BandFmt ) )
+ else if( vips_band_format_isfloat( write->im->BandFmt ) )
format = SAMPLEFORMAT_IEEEFP;
- else if( vips_band_format_iscomplex( tw->im->BandFmt ) )
+ else if( vips_band_format_iscomplex( write->im->BandFmt ) )
format = SAMPLEFORMAT_COMPLEXIEEEFP;
TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format );
@@ -633,13 +633,11 @@ write_tiff_header( TiffWrite *tw, PyramidLayer *layer )
return( 0 );
}
-/* Free a pyramid layer.
+/* Free a single pyramid layer.
*/
static void
-free_layer( PyramidLayer *layer )
+layer_free( Layer *layer )
{
- VIPS_FREE( layer->lname );
-
VIPS_UNREF( layer->strip );
VIPS_UNREF( layer->copy );
VIPS_UNREF( layer->image );
@@ -650,22 +648,21 @@ free_layer( PyramidLayer *layer )
/* Free an entire pyramid.
*/
static void
-free_pyramid( PyramidLayer *layer )
+pyramid_free( Layer *layer )
{
if( layer->below )
- free_pyramid( layer->below );
+ pyramid_free( layer->below );
- free_layer( layer );
+ layer_free( layer );
}
-static PyramidLayer *
-build_pyramid( TiffWrite *tw, PyramidLayer *above, int width, int height )
+static Layer *
+pyramid_new( Write *write, Layer *above, int width, int height )
{
- PyramidLayer *layer;
- int i;
+ Layer *layer;
- layer = VIPS_NEW( tw->im, PyramidLayer );
- layer->tw = tw;
+ layer = VIPS_NEW( write->im, Layer );
+ layer->write = write;
layer->width = width;
layer->height = height;
@@ -687,218 +684,61 @@ build_pyramid( TiffWrite *tw, PyramidLayer *above, int width, int height )
layer->below = NULL;
layer->above = above;
- if( tw->pyramid )
- if( layer->width > tw->tilew ||
- layer->height > tw->tileh )
- layer->below = build_pyramid( tw, layer,
+ if( write->pyramid )
+ if( layer->width > write->tilew ||
+ layer->height > write->tileh )
+ layer->below = pyramid_new( write, layer,
width / 2, height / 2 );
+ /* The name for the top layer is the output filename.
+ *
+ * We need lname to be freed automatically: it has to stay
+ * alive until after write_gather().
+ */
+ if( !above )
+ layer->lname = vips_strdup( write->im, write->filename );
+ else {
+ char *lname;
+
+ lname = vips__temp_name( "%s.tif" );
+ layer->lname = vips_strdup( write->im, lname );
+ g_free( lname );
+ }
+
return( layer );
}
- if( !(layer->lname = vips__temp_name( "%s.tif" )) )
- return( -1 );
-
- if( !(layer->tif = tiff_openout( tw, layer->lname )) )
- return( -1 );
-
- if( write_tiff_header( tw, layer ) )
- return( -1 );
-
- return( 0 );
-}
-
/* Write a tile from a layer.
*/
static int
-save_tile( TiffWrite *tw,
- TIFF *tif, VipsPel *tbuf, VipsRegion *reg, VipsRect *area )
+layer_write_tile( Layer *layer, VipsRegion *strip )
{
- /* Have to repack pixels.
- */
- pack2tiff( tw, reg, tbuf, area );
+ int x;
+
+ for( x = 0; x < im->Xsize; x += write->tilew ) {
+ /* Have to repack pixels.
+ */
+ pack2tiff( write, reg, tbuf, area );
#ifdef DEBUG_VERBOSE
- printf( "Writing %dx%d pixels at position %dx%d to image %s\n",
- tw->tilew, tw->tileh, area->left, area->top,
- TIFFFileName( tif ) );
+ printf( "Writing %dx%d pixels at position %dx%d to image %s\n",
+ write->tilew, write->tileh, area->left, area->top,
+ TIFFFileName( tif ) );
#endif /*DEBUG_VERBOSE*/
- /* Write to TIFF! easy.
- */
- if( TIFFWriteTile( tif, tbuf, area->left, area->top, 0, 0 ) < 0 ) {
- vips_error( "vips2tiff", "%s", _( "TIFF write tile failed" ) );
- return( -1 );
- }
-
- return( 0 );
-}
-
-/* A new tile has arrived! Shrink into this layer, if we fill a region, write
- * it and recurse.
- */
-static int
-new_tile( PyramidLayer *layer, VipsRegion *tile, VipsRect *area )
-{
- TiffWrite *tw = layer->tw;
- int xoff, yoff;
-
- int t, ri, bo;
- VipsRect out, new;
- PyramidBits bit;
-
- /* Calculate pos and size of new pixels we make inside this layer.
- */
- new.left = area->left / 2;
- new.top = area->top / 2;
- new.width = area->width / 2;
- new.height = area->height / 2;
-
- /* Has size fallen to zero? Can happen if this is a one-pixel-wide
- * strip.
- */
- if( vips_rect_isempty( &new ) )
- return( 0 );
-
- /* Offset into this tile ... ie. which quadrant we are writing.
- */
- xoff = new.left % layer->tw->tilew;
- yoff = new.top % layer->tw->tileh;
-
- /* Calculate pos for tile we shrink into in this layer.
- */
- out.left = new.left - xoff;
- out.top = new.top - yoff;
-
- /* Clip against edge of image.
- */
- ri = VIPS_MIN( layer->width, out.left + layer->tw->tilew );
- bo = VIPS_MIN( layer->height, out.top + layer->tw->tileh );
- out.width = ri - out.left;
- out.height = bo - out.top;
-
- if( (t = find_tile( layer, &out )) < 0 )
- return( -1 );
-
- /* Shrink into place.
- */
- if( tw->im->Coding == VIPS_CODING_NONE )
- shrink_region( tile, area,
- layer->tiles[t].tile, xoff, yoff );
- else
- shrink_region_labpack( tile, area,
- layer->tiles[t].tile, xoff, yoff );
-
- /* Set that bit.
- */
- if( xoff )
- if( yoff )
- bit = PYR_BR;
- else
- bit = PYR_TR;
- else
- if( yoff )
- bit = PYR_BL;
- else
- bit = PYR_TL;
- if( layer->tiles[t].bits & bit ) {
- vips_error( "vips2tiff",
- "%s", _( "internal error #9876345" ) );
- return( -1 );
- }
- layer->tiles[t].bits |= bit;
-
- if( layer->tiles[t].bits == PYR_ALL ) {
- /* Save this complete tile.
- */
- if( save_tile( tw, layer->tif, layer->tbuf,
- layer->tiles[t].tile, &layer->tiles[t].tile->valid ) )
- return( -1 );
-
- /* And recurse down the pyramid!
- */
- if( layer->below &&
- new_tile( layer->below,
- layer->tiles[t].tile,
- &layer->tiles[t].tile->valid ) )
+ if( TIFFWriteTile( tif, tbuf, area->left, area->top, 0, 0 ) < 0 ) {
+ vips_error( "vips2tiff", "%s", _( "TIFF write tile failed" ) );
return( -1 );
+ }
}
return( 0 );
}
-/* Write as tiles. This is called by vips_sink_tile() for every tile
- * generated.
- */
static int
-write_tif_tile( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
+layer_write_strip( Layer *layer, VipsRegion *strip )
{
- TiffWrite *tw = (TiffWrite *) a;
-
- g_mutex_lock( tw->write_lock );
-
- /* Write to TIFF.
- */
- if( save_tile( tw, tw->tif, tw->tbuf, out, &out->valid ) ) {
- g_mutex_unlock( tw->write_lock );
- return( -1 );
- }
-
- /* Is there a pyramid? Write to that too.
- */
- if( tw->layer &&
- new_tile( tw->layer, out, &out->valid ) ) {
- g_mutex_unlock( tw->write_lock );
- return( -1 );
- }
-
- g_mutex_unlock( tw->write_lock );
-
- return( 0 );
-}
-
-/* Write as tiles.
- */
-static int
-write_tif_tilewise( TiffWrite *tw )
-{
- VipsImage *im = tw->im;
-
- /* Double check: buffers should match in size, except for onebit and
- * labq modes.
- */
-{
- size_t vips_tile_size =
- VIPS_IMAGE_SIZEOF_PEL( im ) * tw->tilew * tw->tileh;
-
- if( tw->im->Coding != VIPS_CODING_LABQ &&
- !tw->onebit &&
- TIFFTileSize( tw->tif ) != vips_tile_size ) {
- vips_error( "vips2tiff",
- "%s", _( "unsupported image format" ) );
- return( -1 );
- }
-}
-
- g_assert( !tw->tbuf );
- if( !(tw->tbuf = vips_malloc( NULL, TIFFTileSize( tw->tif ) )) )
- return( -1 );
-
- g_assert( !tw->write_lock );
- tw->write_lock = vips_g_mutex_new();
-
- if( vips_sink_tile( im, tw->tilew, tw->tileh,
- NULL, write_tif_tile, NULL, tw, NULL ) )
- return( -1 );
-
- return( 0 );
-}
-
-static int
-write_tif_block( VipsRegion *region, VipsRect *area, void *a )
-{
- TiffWrite *tw = (TiffWrite *) a;
- VipsImage *im = tw->im;
+ VipsImage *im = write->im;
int y;
@@ -908,59 +748,97 @@ write_tif_block( VipsRegion *region, VipsRect *area, void *a )
/* Any repacking necessary.
*/
if( im->Coding == VIPS_CODING_LABQ ) {
- LabQ2LabC( tw->tbuf, p, im->Xsize );
- p = tw->tbuf;
+ LabQ2LabC( write->tbuf, p, im->Xsize );
+ p = write->tbuf;
}
else if( im->BandFmt == VIPS_FORMAT_SHORT &&
im->Type == VIPS_INTERPRETATION_LABS ) {
- LabS2Lab16( tw->tbuf, p, im->Xsize );
- p = tw->tbuf;
+ LabS2Lab16( write->tbuf, p, im->Xsize );
+ p = write->tbuf;
}
- else if( tw->onebit ) {
- eightbit2onebit( tw->tbuf, p, im->Xsize );
- p = tw->tbuf;
+ else if( write->onebit ) {
+ eightbit2onebit( write->tbuf, p, im->Xsize );
+ p = write->tbuf;
}
- if( TIFFWriteScanline( tw->tif, p, area->top + y, 0 ) < 0 )
+ if( TIFFWriteScanline( write->tif, p, area->top + y, 0 ) < 0 )
return( -1 );
}
return( 0 );
}
-/* Write as scan-lines.
+static int layer_strip_arrived( Layer *layer );
+
+/* Shrink what pixels we can from this strip into the layer below. If the
+ * strip below fills, recurse.
*/
static int
-write_tif_stripwise( TiffWrite *tw )
+layer_strip_shrink( Layer *layer )
{
- g_assert( !tw->tbuf );
+ VipsForeignSaveDz *dz = layer->dz;
+ VipsForeignSave *save = VIPS_FOREIGN_SAVE( dz );
+ Layer *below = layer->below;
+ VipsRegion *from = layer->strip;
+ VipsRegion *to = below->strip;
- /* Double check: buffers should match in size, except for onebit and
- * labq modes.
+ VipsRect target;
+ VipsRect source;
+
+ /* Our pixels might cross a strip boundary in the layer below, so we
+ * have to write repeatedly until we run out of pixels.
*/
- if( tw->im->Coding != VIPS_CODING_LABQ &&
- !tw->onebit &&
- TIFFScanlineSize( tw->tif ) !=
- VIPS_IMAGE_SIZEOF_LINE( tw->im ) ) {
- vips_error( "vips2tiff",
- "%s", _( "unsupported image format" ) );
- return( -1 );
+ for(;;) {
+ /* The pixels the layer below needs.
+ */
+ target.left = 0;
+ target.top = below->write_y;
+ target.width = below->image->Xsize;
+ target.height = to->valid.height;
+ vips_rect_intersectrect( &target, &to->valid, &target );
+
+ /* Those pixels need this area of this layer.
+ */
+ source.left = target.left * 2;
+ source.top = target.top * 2;
+ source.width = target.width * 2;
+ source.height = target.height * 2;
+
+ /* Of which we have these available.
+ */
+ vips_rect_intersectrect( &source, &from->valid, &source );
+
+ /* So these are the pixels in the layer below we can provide.
+ */
+ target.left = source.left / 2;
+ target.top = source.top / 2;
+ target.width = source.width / 2;
+ target.height = source.height / 2;
+
+ /* None? All done.
+ */
+ if( vips_rect_isempty( &target ) )
+ break;
+
+ if( save->ready->Coding == VIPS_CODING_NONE )
+ shrink_region_uncoded( from, to, &target );
+ else
+ shrink_region_labpack( from, to, &target );
+
+ below->write_y += target.height;
+
+ /* If we've filled the strip below, let it know.
+ * We can either fill the region, if it's somewhere half-way
+ * down the image, or, if it's at the bottom, get to the last
+ * real line of pixels.
+ */
+ if( below->write_y == VIPS_RECT_BOTTOM( &to->valid ) ||
+ below->write_y == below->height ) {
+ if( layer_strip_arrived( below ) )
+ return( -1 );
+ }
}
- if( !(tw->tbuf = vips_malloc( NULL, TIFFScanlineSize( tw->tif ) )) )
- return( -1 );
-
- if( vips_sink_disc( tw->im, write_tif_block, tw ) )
- return( -1 );
-
- return( 0 );
-}
-
-
-
-
-
-
static int strip_arrived( Layer *layer );
/* Shrink what pixels we can from this strip into the layer below. If the
@@ -1046,18 +924,20 @@ strip_shrink( Layer *layer )
* - copy the overlap with the previous strip
*/
static int
-strip_arrived( PyramidLayer *layer )
+layer_strip_arrived( Layer *layer )
{
- TiffWrite *tw = layer->tw;
+ Write *write = layer->write;
VipsRect new_strip;
VipsRect overlap;
- if( strip_save( layer ) )
- return( -1 );
+ if( write->tile )
+ res = layer_write_tile( layer, layer->strip );
+ else
+ res = layer_write_strip( layer, layer->strip );
if( layer->below &&
- strip_shrink( layer ) )
+ layer_strip_shrink( layer ) )
return( -1 );
/* Position our strip down the image.
@@ -1102,8 +982,8 @@ strip_arrived( PyramidLayer *layer )
static int
write_strip( VipsRegion *region, VipsRect *area, void *a )
{
- TiffWrite *tw = (TiffWrite *) a;
- PyramidLayer *layer = tw->layer;
+ Write *write = (Write *) a;
+ Layer *layer = write->layer;
#ifdef DEBUG
printf( "write_strip: strip at %d, height %d\n",
@@ -1148,7 +1028,7 @@ write_strip( VipsRegion *region, VipsRect *area, void *a )
*/
if( layer->write_y == VIPS_RECT_BOTTOM( to ) ||
layer->write_y == layer->height ) {
- if( strip_arrived( layer ) )
+ if( layer_strip_arrived( layer ) )
return( -1 );
}
}
@@ -1159,35 +1039,38 @@ write_strip( VipsRegion *region, VipsRect *area, void *a )
/* Delete any temp files we wrote.
*/
static void
-delete_files( TiffWrite *tw )
+write_delete_temps( Write *write )
{
- PyramidLayer *layer;
+ Layer *layer;
- for( layer = tw->layer; layer; layer = layer->below )
- if( layer->lname ) {
+ /* Don't delete the top layer: that's the output file.
+ */
+ if( write->layer &&
+ write->layer->below )
+ for( layer = write->layer->below; layer; layer = layer->below )
+ if( layer->lname ) {
#ifndef DEBUG
- unlink( layer->lname );
+ unlink( layer->lname );
#else
- printf( "delete_files: leaving %s\n", layer->lname );
+ printf( "write_delete_temps: leaving %s\n",
+ layer->lname );
#endif /*DEBUG*/
- layer->lname = NULL;
- }
+ layer->lname = NULL;
+ }
}
-/* Free a TiffWrite.
+/* Free a Write.
*/
static void
-free_tiff_write( TiffWrite *tw )
+write_free( Write *write )
{
- delete_files( tw );
+ write_delete_temps( write );
- VIPS_FREEF( TIFFClose, tw->tif );
- VIPS_FREEF( vips_free, tw->tbuf );
- VIPS_FREEF( vips_g_mutex_free, tw->write_lock );
- VIPS_FREEF( free_pyramid, tw->layer );
- VIPS_UNREF( tw->cache );
- VIPS_FREEF( vips_free, tw->icc_profile );
+ VIPS_FREEF( TIFFClose, write->tif );
+ VIPS_FREEF( vips_free, write->tbuf );
+ VIPS_FREEF( pyramid_free, write->layer );
+ VIPS_FREEF( vips_free, write->icc_profile );
}
/* Round N down to P boundary.
@@ -1242,10 +1125,10 @@ get_resunit( VipsForeignTiffResunit resunit )
return( -1 );
}
-/* Make and init a TiffWrite.
+/* Make and init a Write.
*/
-static TiffWrite *
-make_tiff_write( VipsImage *im, const char *filename,
+static Write *
+write_new( VipsImage *im, const char *filename,
VipsForeignTiffCompression compression, int Q,
VipsForeignTiffPredictor predictor,
char *profile,
@@ -1256,50 +1139,49 @@ make_tiff_write( VipsImage *im, const char *filename,
gboolean bigtiff,
gboolean rgbjpeg )
{
- TiffWrite *tw;
+ Write *write;
- if( !(tw = VIPS_NEW( im, TiffWrite )) )
+ if( !(write = VIPS_NEW( im, Write )) )
return( NULL );
- tw->im = im;
- tw->name = vips_strdup( VIPS_OBJECT( im ), filename );
- tw->tif = NULL;
- tw->layer = NULL;
- tw->tbuf = NULL;
- tw->compression = get_compression( compression );
- tw->jpqual = Q;
- tw->predictor = predictor;
- tw->tile = tile;
- tw->tilew = tile_width;
- tw->tileh = tile_height;
- tw->pyramid = pyramid;
- tw->onebit = squash;
- tw->icc_profile = profile;
- tw->bigtiff = bigtiff;
- tw->rgbjpeg = rgbjpeg;
- tw->write_lock = NULL;
- tw->cache = NULL;
+ write->im = im;
+ write->name = vips_strdup( VIPS_OBJECT( im ), filename );
+ write->tif = NULL;
+ write->layer = NULL;
+ write->tbuf = NULL;
+ write->compression = get_compression( compression );
+ write->jpqual = Q;
+ write->predictor = predictor;
+ write->tile = tile;
+ write->tilew = tile_width;
+ write->tileh = tile_height;
+ write->pyramid = pyramid;
+ write->onebit = squash;
+ write->icc_profile = profile;
+ write->bigtiff = bigtiff;
+ write->rgbjpeg = rgbjpeg;
+ write->cache = NULL;
- tw->resunit = get_resunit( resunit );
- tw->xres = xres;
- tw->yres = yres;
+ write->resunit = get_resunit( resunit );
+ write->xres = xres;
+ write->yres = yres;
- if( (tw->tilew & 0xf) != 0 ||
- (tw->tileh & 0xf) != 0 ) {
+ if( (write->tilew & 0xf) != 0 ||
+ (write->tileh & 0xf) != 0 ) {
vips_error( "vips2tiff",
"%s", _( "tile size not a multiple of 16" ) );
return( NULL );
}
- if( !tw->tile && tw->pyramid ) {
+ if( !write->tile && write->pyramid ) {
vips_warn( "vips2tiff",
"%s", _( "can't have strip pyramid -- "
"enabling tiling" ) );
- tw->tile = 1;
+ write->tile = 1;
}
/* We can only pyramid LABQ and non-complex images.
*/
- if( tw->pyramid ) {
+ if( write->pyramid ) {
if( im->Coding == VIPS_CODING_NONE &&
vips_band_format_iscomplex( im->BandFmt ) ) {
vips_error( "vips2tiff",
@@ -1311,37 +1193,37 @@ make_tiff_write( VipsImage *im, const char *filename,
/* Only 1-bit-ize 8 bit mono images.
*/
- if( tw->onebit &&
+ if( write->onebit &&
(im->Coding != VIPS_CODING_NONE ||
im->BandFmt != VIPS_FORMAT_UCHAR ||
im->Bands != 1) ) {
vips_warn( "vips2tiff",
"%s", _( "can only squash 1 band uchar images -- "
"disabling squash" ) );
- tw->onebit = 0;
+ write->onebit = 0;
}
- if( tw->onebit &&
- tw->compression == COMPRESSION_JPEG ) {
+ if( write->onebit &&
+ write->compression == COMPRESSION_JPEG ) {
vips_warn( "vips2tiff",
"%s", _( "can't have 1-bit JPEG -- disabling JPEG" ) );
- tw->compression = COMPRESSION_NONE;
+ write->compression = COMPRESSION_NONE;
}
/* Sizeof a line of bytes in the TIFF tile.
*/
if( im->Coding == VIPS_CODING_LABQ )
- tw->tls = tw->tilew * 3;
- else if( tw->onebit )
- tw->tls = ROUND_UP( tw->tilew, 8 ) / 8;
+ write->tls = write->tilew * 3;
+ else if( write->onebit )
+ write->tls = ROUND_UP( write->tilew, 8 ) / 8;
else
- tw->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * tw->tilew;
+ write->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * write->tilew;
/* We always need at least a base layer.
*/
- tw->layer = build_pyramid( tw, NULL, im->Xsize, im->Ysize );
+ write->layer = pyramid_new( write, NULL, im->Xsize, im->Ysize );
- return( tw );
+ return( write );
}
/* Copy fields.
@@ -1353,7 +1235,7 @@ make_tiff_write( VipsImage *im, const char *filename,
* we might have set.
*/
static int
-tiff_copy( TiffWrite *tw, TIFF *out, TIFF *in )
+write_copy_tiff( Write *write, TIFF *out, TIFF *in )
{
uint32 i32;
uint16 i16;
@@ -1380,23 +1262,23 @@ tiff_copy( TiffWrite *tw, TIFF *out, TIFF *in )
CopyField( TIFFTAG_ROWSPERSTRIP, i32 );
CopyField( TIFFTAG_SUBFILETYPE, i32 );
- if( tw->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
- TIFFSetField( out, TIFFTAG_PREDICTOR, tw->predictor );
+ if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
+ TIFFSetField( out, TIFFTAG_PREDICTOR, write->predictor );
/* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it.
- * Set explicitly from TiffWrite.
+ * Set explicitly from Write.
*/
- if( tw->compression == COMPRESSION_JPEG ) {
- TIFFSetField( out, TIFFTAG_JPEGQUALITY, tw->jpqual );
+ if( write->compression == COMPRESSION_JPEG ) {
+ TIFFSetField( out, TIFFTAG_JPEGQUALITY, write->jpqual );
/* Only for three-band, 8-bit images.
*/
- if( tw->im->Bands == 3 &&
- tw->im->BandFmt == VIPS_FORMAT_UCHAR ) {
+ if( write->im->Bands == 3 &&
+ write->im->BandFmt == VIPS_FORMAT_UCHAR ) {
/* Enable rgb->ycbcr conversion in the jpeg write.
*/
- if( !tw->rgbjpeg &&
- tw->jpqual < 90 )
+ if( !write->rgbjpeg &&
+ write->jpqual < 90 )
TIFFSetField( out,
TIFFTAG_JPEGCOLORMODE,
JPEGCOLORMODE_RGB );
@@ -1410,11 +1292,11 @@ tiff_copy( TiffWrite *tw, TIFF *out, TIFF *in )
}
}
- /* We can't copy profiles or xmp :( Set again from TiffWrite.
+ /* We can't copy profiles or xmp :( Set again from Write.
*/
- if( embed_profile( tw, out ) )
+ if( write_embed_profile( write, out ) )
return( -1 );
- if( embed_xmp( tw, out ) )
+ if( write_embed_xmp( write, out ) )
return( -1 );
buf = vips_malloc( NULL, TIFFTileSize( in ) );
@@ -1441,11 +1323,11 @@ tiff_copy( TiffWrite *tw, TIFF *out, TIFF *in )
/* Append all of the lower layers we wrote to the output.
*/
static int
-gather_pyramid( TiffWrite *tw )
+write_gather( Write *write )
{
- PyramidLayer *layer;
+ Layer *layer;
- for( layer = tw->layer; layer; layer = layer->below ) {
+ for( layer = write->layer; layer; layer = layer->below ) {
TIFF *in;
#ifdef DEBUG
@@ -1455,13 +1337,13 @@ gather_pyramid( TiffWrite *tw )
if( !(in = tiff_openin( layer->lname )) )
return( -1 );
- if( tiff_copy( tw, tw->tif, in ) ) {
+ if( write_copy_tiff( write, write->tif, in ) ) {
TIFFClose( in );
return( -1 );
}
TIFFClose( in );
- if( !TIFFWriteDirectory( tw->tif ) )
+ if( !TIFFWriteDirectory( write->tif ) )
return( -1 );
}
@@ -1480,8 +1362,8 @@ vips__tiff_write( VipsImage *in, const char *filename,
gboolean bigtiff,
gboolean rgbjpeg )
{
- TiffWrite *tw;
- PyramidLayer *layer;
+ Write *write;
+ Layer *layer;
int res;
#ifdef DEBUG
@@ -1495,39 +1377,45 @@ vips__tiff_write( VipsImage *in, const char *filename,
/* Make output image.
*/
- if( !(tw = make_tiff_write( in, filename,
+ if( !(write = write_new( in, filename,
compression, Q, predictor, profile,
tile, tile_width, tile_height, pyramid, squash,
resunit, xres, yres, bigtiff, rgbjpeg )) )
return( -1 );
- layer = tw->layer;
- layer->lname = g_strdup( filename );
- if( !(layer->tif = tiff_openout( tw, layer->lname )) ||
- write_tiff_header( tw, layer ) ||
- vips_sink_disc( tw->im, write_strip, tw ) ) {
- free_tiff_write( tw );
+ /* Make all the layer images.
+ */
+ for( layer = write->layer; layer; layer = layer->below ) {
+ if( !(layer->tif = tiff_openout( write, layer->lname )) ||
+ write_tiff_header( write, layer ) ) {
+ write_free( write );
+ return( -1 );
+ }
+
+ if( vips_sink_disc( write->im, write_strip, write ) ) {
+ write_free( write );
return( -1 );
}
- if( tw->pyramid ) {
- if( !TIFFWriteDirectory( layer->tif ) )
+ if( write->pyramid ) {
+ if( !TIFFWriteDirectory( write->layer->tif ) )
return( -1 );
- /* Free lower pyramid resources ... this will TIFFClose() the
- * smaller layers ready for us to read from them again.
+ /* Free lower pyramid resources ... this will TIFFClose() (but
+ * not delete) the smaller layers ready for us to read from
+ * them again.
*/
- free_pyramid( layer->below );
+ pyramid_free( write->layer->below );
/* Append smaller layers to the main file.
*/
- if( gather_pyramid( tw ) ) {
- free_tiff_write( tw );
+ if( write_gather( write ) ) {
+ write_free( write );
return( -1 );
}
}
- free_tiff_write( tw );
+ write_free( write );
return( 0 );
}
diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h
index a695d4e0..072a5edd 100644
--- a/libvips/include/vips/foreign.h
+++ b/libvips/include/vips/foreign.h
@@ -435,6 +435,8 @@ int vips_matrixprint( VipsImage *in, ... )
int vips_magickload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));
+int vips_magickload_buffer( void *buf, size_t len, VipsImage **out, ... )
+ __attribute__((sentinel));
/**
* VipsForeignPngFilter:
diff --git a/python/Vips.py b/python/Vips.py
index 0ce06eca..c6bf8fc5 100644
--- a/python/Vips.py
+++ b/python/Vips.py
@@ -993,6 +993,7 @@ class_methods = [
"tiffload_buffer",
"openslideload",
"magickload",
+ "magickload_buffer",
"fitsload",
"openexrload"]
diff --git a/test/Makefile.am b/test/Makefile.am
index c4eb9d7d..ee0b0c41 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -14,6 +14,7 @@ EXTRA_DIST = \
test_conversion.py \
test_convolution.py \
test_create.py \
+ test_foreign.py \
test_draw.py \
test_histogram.py \
test_morphology.py \
diff --git a/test/images/1.webp b/test/images/1.webp
new file mode 100644
index 00000000..122741b6
Binary files /dev/null and b/test/images/1.webp differ
diff --git a/test/images/CMU-1-Small-Region.svs b/test/images/CMU-1-Small-Region.svs
new file mode 100644
index 00000000..1125dcd0
Binary files /dev/null and b/test/images/CMU-1-Small-Region.svs differ
diff --git a/test/images/WFPC2u5780205r_c0fx.fits b/test/images/WFPC2u5780205r_c0fx.fits
new file mode 100644
index 00000000..cbe0da74
--- /dev/null
+++ b/test/images/WFPC2u5780205r_c0fx.fits
@@ -0,0 +1,1814 @@
+SIMPLE = T / file does conform to FITS standard BITPIX = -32 / number of bits per data pixel NAXIS = 3 / number of data axes NAXIS1 = 200 / length of data axis 1 NAXIS2 = 200 / length of data axis 2 NAXIS3 = 4 / length of data axis 3 EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H BSCALE = 1.0E0 / REAL = TAPE*BSCALE + BZERO BZERO = 0.0E0 / OPSIZE = 2112 / PSIZE of original image ORIGIN = 'STScI-STSDAS' / Fitsio version 21-Feb-1996 FITSDATE= '2004-01-09' / Date FITS file was created FILENAME= 'u5780205r_cvt.c0h' / Original filename ALLG-MAX= 3.777701E3 / Data max in all groups ALLG-MIN= -7.319537E1 / Data min in all groups ODATTYPE= 'FLOATING' / Original datatype: Single precision real SDASMGNU= 4 / Number of groups in original image CRVAL1 = 182.6311886308 CRVAL2 = 39.39633673411 CRPIX1 = 420. CRPIX2 = 424.5 CD1_1 = -1.067040E-6 CD1_2 = -1.259580E-5 CD2_1 = -1.260160E-5 CD2_2 = 1.066550E-6 DATAMIN = -7.319537E1 / DATA MIN DATAMAX = 3.777701E3 / DATA MAX MIR_REVR= T ORIENTAT= -85.16 FILLCNT = 0 ERRCNT = 0 FPKTTIME= 51229.798574 LPKTTIME= 51229.798742 CTYPE1 = 'RA---TAN' CTYPE2 = 'DEC--TAN' DETECTOR= 1 DEZERO = 316.6452 BIASEVEN= 316.6715 BIASODD = 316.6189 GOODMIN = -5.064006 GOODMAX = 2552.17 DATAMEAN= 0.4182382 GPIXELS = 632387 SOFTERRS= 0 CALIBDEF= 1466 STATICD = 0 ATODSAT = 16 DATALOST= 0 BADPIXEL= 0 OVERLAP = 0 PHOTMODE= 'WFPC2,1,A2D7,LRF#4877.0,,CAL' PHOTFLAM= 3.447460E-16 PHOTZPT = -21.1 PHOTPLAM= 4884.258 PHOTBW = 20.20996 MEDIAN = -0.175651 MEDSHADO= -0.121681 HISTWIDE= 1.033711 SKEWNESS= -1.983727 MEANC10 = 0.12958 MEANC25 = 0.3129676 MEANC50 = 0.4577668 MEANC100= 0.3916293 MEANC200= 0.3115222 MEANC300= 0.3295493 BACKGRND= -0.3676353 ORIGIN = 'NOAO-IRAF FITS Image Kernel December 2001' / FITS file originator DATE = '2004-01-09T03:26:36' IRAF-TLM= '03:26:36 (09/01/2004)' FILETYPE= 'SCI ' / type of data found in data file TELESCOP= 'HST' / telescope used to acquire data INSTRUME= 'WFPC2 ' / identifier for instrument used to acquire data EQUINOX = 2000.0 / equinox of celestial coord. system / WFPC-II DATA DESCRIPTOR KEYWORDS ROOTNAME= 'u5780205r' / rootname of the observation set PROCTIME= 5.301314019676E+04 / Pipeline processing time (MJD) OPUS_VER= 'OPUS 14.5a ' / OPUS software system version number CAL_VER = ' ' / CALWP2 code version / SCIENCE INSTRUMENT CONFIGURATION MODE = 'FULL' / instr. mode: FULL (full res.), AREA (area int.)SERIALS = 'OFF' / serial clocks: ON, OFF / IMAGE TYPE CHARACTERISTICS IMAGETYP= 'EXT ' / DARK/BIAS/IFLAT/UFLAT/VFLAT/KSPOT/EXT/ECAL CDBSFILE= 'NO ' / GENERIC/BIAS/DARK/PREF/FLAT/MASK/ATOD/NO PKTFMT = 96 / packet format code / FILTER CONFIGURATION FILTNAM1= 'FR533P15' / first filter name FILTNAM2= ' ' / second filter name FILTER1 = 69 / first filter number (0-48) FILTER2 = 0 / second filter number (0-48) FILTROT = 15.0 / partial filter rotation angle (degrees) LRFWAVE = 4877.000000 / linear ramp filter wavelength / INSTRUMENT STATUS USED IN DATA PROCESSING UCH1CJTM= -88.2569 / TEC cold junction #1 temperature (Celsius) UCH2CJTM= -88.6697 / TEC cold junction #2 temperature (Celsius) UCH3CJTM= -88.3028 / TEC cold junction #3 temperature (Celsius) UCH4CJTM= -88.7671 / TEC cold junction #4 temperature (Celsius) UBAY3TMP= 13.2302 / bay 3 A1 temperature (deg C) KSPOTS = 'OFF' / Status of Kelsall spot lamps: ON, OFF SHUTTER = 'A' / Shutter in place at beginning of the exposure ATODGAIN= 7.0 / Analog to Digital Gain (Electrons/DN) / RSDP CONTROL KEYWORDS MASKCORR= 'COMPLETE' / Do mask correction: PERFORM, OMIT, COMPLETE ATODCORR= 'COMPLETE' / Do A-to-D correction: PERFORM, OMIT, COMPLETE BLEVCORR= 'COMPLETE' / Do bias level correction BIASCORR= 'COMPLETE' / Do bias correction: PERFORM, OMIT, COMPLETE DARKCORR= 'COMPLETE' / Do dark correction: PERFORM, OMIT, COMPLETE FLATCORR= 'SKIPPED ' / Do flat field correction SHADCORR= 'OMIT ' / Do shaded shutter correction DOSATMAP= 'OMIT ' / Output saturated pixel map DOPHOTOM= 'COMPLETE' / Fill photometry keywords DOHISTOS= 'OMIT ' / Make histograms: PERFORM, OMIT, COMPLETE OUTDTYPE= 'REAL ' / Output image datatype: REAL, LONG, SHORT / CALIBRATION REFERENCE FILES MASKFILE= 'uref$f8213081u.r0h ' / name of the input DQF of known bad pixels ATODFILE= 'uref$dbu1405iu.r1h' / name of the A-to-D conversion file BLEVFILE= 'ucal$u5780205r.x0h ' / Engineering file with extended register daBLEVDFIL= 'ucal$u5780205r.q1h ' / Engineering file DQF BIASFILE= 'uref$j9a1612mu.r2h' / name of the bias frame reference file BIASDFIL= 'uref$j9a1612mu.b2h' / name of the bias frame reference DQF DARKFILE= 'uref$j2g1549cu.r3h' / name of the dark reference file DARKDFIL= 'uref$j2g1549cu.b3h' / name of the dark reference DQF FLATFILE= 'uref$f4i1559cu.r4h' / name of the flat field reference file FLATDFIL= 'uref$f4i1559cu.b4h' / name of the flat field reference DQF SHADFILE= 'uref$e371355eu.r5h' / name of the reference file for shutter shaPHOTTAB = 'u5780205r_c3t.fits' / name of the photometry calibration table GRAPHTAB= 'mtab$n9i1408hm_tmg.fits' / the HST graph table COMPTAB = 'mtab$nc809508m_tmc.fits' / the HST components table / DEFAULT KEYWORDS SET BY STSCI SATURATE= 4095 / Data value at which saturation occurs USCALE = 1.0 / Scale factor for output image UZERO = 0.0 / Zero point for output image / READOUT DURATION INFORMATION READTIME= 464 / Length of time for CCD readout in clock ticks / PLANETARY SCIENCE KEYWORDS PA_V3 = 49.936909 / position angle of V3-axis of HST (deg) RA_SUN = 3.337194516616E+02 / right ascension of the sun (deg) DEC_SUN = -1.086675160382E+01 / declination of the sun (deg) EQNX_SUN= 2000.0 / equinox of the sun MTFLAG = F / moving target flag; T if it is a moving target EQRADTRG= 0.000000 / equatorial radius of target (km) FLATNTRG= 0.000000 / flattening of target NPDECTRG= 0.000000 / north pole declination of target (deg) NPRATRG = 0.000000 / north pole right ascension of target (deg) ROTRTTRG= 0.000000 / rotation rate of target LONGPMER= 0.000000 / longitude of prime meridian (deg) EPLONGPM= 0.000000 / epoch of longitude of prime meridian (sec) SURFLATD= 0.000000 / surface feature latitude (deg) SURFLONG= 0.000000 / surface feature longitude (deg) SURFALTD= 0.000000 / surface feature altitude (km) / PODPS FILL VALUES PODPSFF = 0 / 0=(no podps fill); 1=(podps fill present) STDCFFF = 0 / 0=(no st dcf fill); 1=(st dcf fill present) STDCFFP = '0x5569' / st dcf fill pattern (hex) RSDPFILL= -100 / bad data fill value for calibrated images / EXPOSURE TIME AND RELATED INFORMATION UEXPODUR= 300 / commanded duration of exposure (sec) NSHUTA17= 1 / Number of AP17 shutter B closes DARKTIME= 3.000000000000E+02 / Dark time (seconds) UEXPOTIM= 16880 / Major frame pulse time preceding exposure startPSTRTIME= '1999.051:19:08:37 ' / predicted obs. start time (yyyy.ddd:hh:mm:ss) PSTPTIME= '1999.051:19:16:37 ' / predicted obs. stop time (yyyy.ddd:hh:mm:ss) / EXPOSURE INFORMATION SUNANGLE= 141.618347 / angle between sun and V1 axis MOONANGL= 126.698997 / angle between moon and V1 axis SUN_ALT = -31.523479 / altitude of the sun above Earth's limb FGSLOCK = 'FINE ' / commanded FGS lock (FINE,COARSE,GYROS,UNKNOWN) DATE-OBS= '1999-02-20' / UT date of start of observation (yyyy-mm-dd) TIME-OBS= '19:03:13' / UT time of start of observation (hh:mm:ss) EXPSTART= 5.122979390428E+04 / exposure start time (Modified Julian Date) EXPEND = 5.122979737650E+04 / exposure end time (Modified Julian Date) EXPTIME = 3.000000000000E+02 / exposure duration (seconds)--calculated EXPFLAG = 'NORMAL ' / Exposure interruption indicator / TARGET & PROPOSAL ID TARGNAME= 'NGC4151 ' / proposer's target name RA_TARG = 1.826355000000E+02 / right ascension of the target (deg) (J2000) DEC_TARG= 3.940576666667E+01 / declination of the target (deg) (J2000) ECL_LONG= 164.096619 / ecliptic longitude of the target (deg) (J2000) ECL_LAT = 36.623709 / ecliptic latitude of the target (deg) (J2000) GAL_LONG= 155.079532 / galactic longitude of the target (deg) (J2000) GAL_LAT = 75.062679 / galactic latitude of the target (deg) (J2000) PROPOSID= 8019 / PEP proposal identifier PEP_EXPO= '02-030 ' / PEP exposure identifier including sequence LINENUM = '02.030 ' / PEP proposal line number SEQLINE = ' ' / PEP line number of defined sequence SEQNAME = ' ' / PEP define/use sequence name HISTORY MASKFILE=uref$f8213081u.r0h MASKCORR=COMPLETED HISTORY PEDIGREE=INFLIGHT 01/01/1994 - 15/05/1995 HISTORY DESCRIP=STATIC MASK - INCLUDES CHARGE TRANSFER TRAPS HISTORY BIASFILE=uref$j9a1612mu.r2h BIASCORR=COMPLETED HISTORY PEDIGREE=INFLIGHT 29/08/98 - 21/08/99 HISTORY DESCRIP=not significantly different from j6e16008u. HISTORY DARKFILE=uref$j2g1549cu.r3h DARKCORR=COMPLETED HISTORY PEDIGREE=INFLIGHT 16/02/1999 - 16/02/1999 HISTORY DESCRIP=Pipeline dark: 120 frame superdark with hotpixels from HISTORY 16/02/99 HISTORY FLATFILE=uref$f4i1559cu.r4h FLATCORR=SKIPPED HISTORY PEDIGREE=DUMMY 18/04/1995 HISTORY DESCRIP=All pixels set to value of 1. Not flat-fielded. HISTORY PC1: bias jump level ~0.100 DN. HISTORY The following throughput tables were used: HISTORY crotacomp$hst_ota_007_syn.fits, crwfpc2comp$wfpc2_optics_006_syn.fits,HISTORY crwfpc2comp$wfpc2_lrf_004_syn.fits[wave#], HISTORY crwfpc2comp$wfpc2_dqepc1_005_syn.fits, HISTORY crwfpc2comp$wfpc2_a2d7pc1_004_syn.fits, HISTORY crwfpc2comp$wfpc2_flatpc1_003_syn.fits HISTORY The following throughput tables were used: HISTORY crotacomp$hst_ota_007_syn.fits, crwfpc2comp$wfpc2_optics_006_syn.fits,HISTORY crwfpc2comp$wfpc2_lrf_004_syn.fits[wave#], HISTORY crwfpc2comp$wfpc2_dqewfc2_005_syn.fits, HISTORY crwfpc2comp$wfpc2_a2d7wf2_004_syn.fits, HISTORY crwfpc2comp$wfpc2_flatwf2_003_syn.fits HISTORY The following throughput tables were used: HISTORY crotacomp$hst_ota_007_syn.fits, crwfpc2comp$wfpc2_optics_006_syn.fits,HISTORY crwfpc2comp$wfpc2_lrf_004_syn.fits[wave#], HISTORY crwfpc2comp$wfpc2_dqewfc3_005_syn.fits, HISTORY crwfpc2comp$wfpc2_a2d7wf3_004_syn.fits, HISTORY crwfpc2comp$wfpc2_flatwf3_003_syn.fits HISTORY The following throughput tables were used: HISTORY crotacomp$hst_ota_007_syn.fits, crwfpc2comp$wfpc2_optics_006_syn.fits,HISTORY crwfpc2comp$wfpc2_lrf_004_syn.fits[wave#], HISTORY crwfpc2comp$wfpc2_dqewfc4_005_syn.fits, HISTORY crwfpc2comp$wfpc2_a2d7wf4_004_syn.fits, HISTORY crwfpc2comp$wfpc2_flatwf4_003_syn.fits CTYPE3 = 'GROUP_NUMBER' / Extra dimension axis name CD3_3 = 1 / CD3_1 = 0 / CD1_3 = 0 / CD2_3 = 0 / CD3_2 = 0 / END ū?jÛ?]WO??[ٮ?` ɑn]?Oa?y࿊?WH?Yc?L?C>?XPH?rsĿjt#K!y?1wA
+?[