add subifd support to the TIFF writer
A new subifd switch enables the writing of pyramids layers into subifds (rather than the default successive pages). This switch is enabled automatically for multi-page pyramids. seems to work in quick tests
This commit is contained in:
parent
6a8f128831
commit
a2d196b736
|
@ -193,6 +193,8 @@
|
|||
* - write XYZ images as logluv
|
||||
* 7/2/20 [jclavoie-jive]
|
||||
* - add PAGENUMBER support
|
||||
* 23/5/20
|
||||
* - add support for subifd pyramid layers
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -247,6 +249,13 @@
|
|||
#include "pforeign.h"
|
||||
#include "tiff.h"
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* - have a layout enum rather than a bool
|
||||
* - revise docs
|
||||
* - revise tests
|
||||
*/
|
||||
|
||||
/* Max number of alpha channels we allow.
|
||||
*/
|
||||
#define MAX_ALPHA (64)
|
||||
|
@ -396,40 +405,62 @@ embed_profile_meta( TIFF *tif, VipsImage *im )
|
|||
return( 0 );
|
||||
}
|
||||
|
||||
static Layer *
|
||||
wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height )
|
||||
static void
|
||||
wtiff_layer_init( Wtiff *wtiff, Layer **layer, Layer *above,
|
||||
int width, int height )
|
||||
{
|
||||
Layer *layer;
|
||||
if( !*layer ) {
|
||||
*layer = VIPS_NEW( wtiff->ready, Layer );
|
||||
(*layer)->wtiff = wtiff;
|
||||
(*layer)->width = width;
|
||||
(*layer)->height = height;
|
||||
|
||||
layer = VIPS_NEW( wtiff->ready, Layer );
|
||||
layer->wtiff = wtiff;
|
||||
layer->width = width;
|
||||
layer->height = height;
|
||||
if( !above )
|
||||
/* Top of pyramid.
|
||||
*/
|
||||
(*layer)->sub = 1;
|
||||
else
|
||||
(*layer)->sub = above->sub * 2;
|
||||
|
||||
if( !above )
|
||||
/* Top of pyramid.
|
||||
(*layer)->lname = NULL;
|
||||
(*layer)->buf = NULL;
|
||||
(*layer)->len = 0;
|
||||
(*layer)->tif = NULL;
|
||||
(*layer)->image = NULL;
|
||||
(*layer)->write_y = 0;
|
||||
(*layer)->y = 0;
|
||||
(*layer)->strip = NULL;
|
||||
(*layer)->copy = NULL;
|
||||
|
||||
(*layer)->below = NULL;
|
||||
(*layer)->above = above;
|
||||
|
||||
/* The name for the top layer is the output filename.
|
||||
*
|
||||
* We need lname to be freed automatically: it has to stay
|
||||
* alive until after wtiff_gather().
|
||||
*/
|
||||
layer->sub = 1;
|
||||
else
|
||||
layer->sub = above->sub * 2;
|
||||
if( wtiff->filename ) {
|
||||
if( !above )
|
||||
(*layer)->lname = vips_strdup(
|
||||
VIPS_OBJECT( wtiff->ready ),
|
||||
wtiff->filename );
|
||||
else {
|
||||
char *lname;
|
||||
|
||||
layer->lname = NULL;
|
||||
layer->buf = NULL;
|
||||
layer->len = 0;
|
||||
layer->tif = NULL;
|
||||
layer->image = NULL;
|
||||
layer->write_y = 0;
|
||||
layer->y = 0;
|
||||
layer->strip = NULL;
|
||||
layer->copy = NULL;
|
||||
lname = vips__temp_name( "%s.tif" );
|
||||
(*layer)->lname = vips_strdup(
|
||||
VIPS_OBJECT( wtiff->ready ),
|
||||
lname );
|
||||
g_free( lname );
|
||||
}
|
||||
}
|
||||
|
||||
layer->below = NULL;
|
||||
layer->above = above;
|
||||
|
||||
/*
|
||||
printf( "wtiff_layer_new: sub = %d, width = %d, height = %d\n",
|
||||
layer->sub, width, height );
|
||||
*/
|
||||
/*
|
||||
*/
|
||||
printf( "wtiff_layer_init: sub = %d, width = %d, height = %d\n",
|
||||
(*layer)->sub, width, height );
|
||||
}
|
||||
|
||||
if( wtiff->pyramid ) {
|
||||
int limitw, limith;
|
||||
|
@ -459,34 +490,13 @@ wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height )
|
|||
* Very tall or wide images might end up with a smallest layer
|
||||
* larger than one tile.
|
||||
*/
|
||||
if( (layer->width > limitw ||
|
||||
layer->height > limith) &&
|
||||
layer->width > 1 &&
|
||||
layer->height > 1 )
|
||||
layer->below = wtiff_layer_new( wtiff, layer,
|
||||
if( ((*layer)->width > limitw ||
|
||||
(*layer)->height > limith) &&
|
||||
(*layer)->width > 1 &&
|
||||
(*layer)->height > 1 )
|
||||
wtiff_layer_init( wtiff, &(*layer)->below, *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 wtiff_gather().
|
||||
*/
|
||||
if( wtiff->filename ) {
|
||||
if( !above )
|
||||
layer->lname = vips_strdup( VIPS_OBJECT( wtiff->ready ),
|
||||
wtiff->filename );
|
||||
else {
|
||||
char *lname;
|
||||
|
||||
lname = vips__temp_name( "%s.tif" );
|
||||
layer->lname = vips_strdup( VIPS_OBJECT( wtiff->ready ),
|
||||
lname );
|
||||
g_free( lname );
|
||||
}
|
||||
}
|
||||
|
||||
return( layer );
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -846,40 +856,56 @@ wtiff_allocate_layers( Wtiff *wtiff )
|
|||
{
|
||||
Layer *layer;
|
||||
|
||||
g_assert( wtiff->layer );
|
||||
|
||||
for( layer = wtiff->layer; layer; layer = layer->below ) {
|
||||
layer->image = vips_image_new();
|
||||
if( vips_image_pipelinev( layer->image,
|
||||
VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) )
|
||||
return( -1 );
|
||||
layer->image->Xsize = layer->width;
|
||||
layer->image->Ysize = layer->height;
|
||||
if( !layer->image ) {
|
||||
layer->image = vips_image_new();
|
||||
if( vips_image_pipelinev( layer->image,
|
||||
VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) )
|
||||
return( -1 );
|
||||
layer->image->Xsize = layer->width;
|
||||
layer->image->Ysize = layer->height;
|
||||
|
||||
layer->strip = vips_region_new( layer->image );
|
||||
layer->copy = vips_region_new( layer->image );
|
||||
layer->strip = vips_region_new( layer->image );
|
||||
layer->copy = vips_region_new( layer->image );
|
||||
|
||||
/* The regions will get used in the bg thread callback, so
|
||||
* make sure we don't own them.
|
||||
*/
|
||||
vips__region_no_ownership( layer->strip );
|
||||
vips__region_no_ownership( layer->copy );
|
||||
/* The regions will get used in the bg thread callback,
|
||||
* so make sure we don't own them.
|
||||
*/
|
||||
vips__region_no_ownership( layer->strip );
|
||||
vips__region_no_ownership( layer->copy );
|
||||
|
||||
if( layer->lname )
|
||||
layer->tif = vips__tiff_openout(
|
||||
layer->lname, wtiff->bigtiff );
|
||||
else {
|
||||
layer->tif = vips__tiff_openout_buffer(
|
||||
wtiff->ready, wtiff->bigtiff,
|
||||
&layer->buf, &layer->len );
|
||||
}
|
||||
if( !layer->tif )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( wtiff_layer_rewind( wtiff, layer ) )
|
||||
return( -1 );
|
||||
|
||||
if( layer->lname )
|
||||
layer->tif = vips__tiff_openout(
|
||||
layer->lname, wtiff->bigtiff );
|
||||
else {
|
||||
layer->tif = vips__tiff_openout_buffer( wtiff->ready,
|
||||
wtiff->bigtiff, &layer->buf, &layer->len );
|
||||
}
|
||||
if( !layer->tif )
|
||||
return( -1 );
|
||||
|
||||
if( wtiff_write_header( wtiff, layer ) )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( !wtiff->tbuf ) {
|
||||
if( wtiff->tile )
|
||||
wtiff->tbuf = vips_malloc( NULL,
|
||||
TIFFTileSize( wtiff->layer->tif ) );
|
||||
else
|
||||
wtiff->tbuf = vips_malloc( NULL,
|
||||
TIFFScanlineSize( wtiff->layer->tif ) );
|
||||
if( !wtiff->tbuf )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
|
@ -1099,28 +1125,6 @@ wtiff_new( VipsImage *input, const char *filename,
|
|||
wtiff->toilet_roll = TRUE;
|
||||
wtiff->image_height = wtiff->page_height;
|
||||
wtiff->n_pages = wtiff->ready->Ysize / wtiff->page_height;
|
||||
|
||||
/* We can't pyramid toilet roll images.
|
||||
*/
|
||||
if( wtiff->pyramid ) {
|
||||
g_warning( "%s",
|
||||
_( "can't pyramid multi page images --- "
|
||||
"disabling pyramid" ) );
|
||||
wtiff->pyramid = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/* In strip mode we use tileh to set rowsperstrip, and that does not
|
||||
* have the multiple-of-16 restriction.
|
||||
*/
|
||||
if( tile ) {
|
||||
if( (wtiff->tilew & 0xf) != 0 ||
|
||||
(wtiff->tileh & 0xf) != 0 ) {
|
||||
wtiff_free( wtiff );
|
||||
vips_error( "vips2tiff",
|
||||
"%s", _( "tile size not a multiple of 16" ) );
|
||||
return( NULL );
|
||||
}
|
||||
}
|
||||
|
||||
/* We can only pyramid LABQ and non-complex images.
|
||||
|
@ -1136,6 +1140,41 @@ wtiff_new( VipsImage *input, const char *filename,
|
|||
}
|
||||
}
|
||||
|
||||
/* Pyramid images must be tiled.
|
||||
*/
|
||||
if( wtiff->pyramid &&
|
||||
!wtiff->tile )
|
||||
wtiff->tile = TRUE;
|
||||
|
||||
/* Multi-page pyramids must be in subifd mode.
|
||||
*/
|
||||
if( wtiff->pyramid &&
|
||||
wtiff->toilet_roll )
|
||||
wtiff->subifd = TRUE;
|
||||
|
||||
/* If compression is off and we're writing a >4gb image, automatically
|
||||
* enable bigtiff.
|
||||
*
|
||||
* This won't always work. If the image data is just under 4gb but
|
||||
* there's a lot of metadata, we could be pushed over the 4gb limit.
|
||||
*/
|
||||
if( wtiff->compression == COMPRESSION_NONE &&
|
||||
VIPS_IMAGE_SIZEOF_IMAGE( wtiff->ready ) > UINT_MAX )
|
||||
wtiff->bigtiff = TRUE;
|
||||
|
||||
/* In strip mode we use tileh to set rowsperstrip, and that does not
|
||||
* have the multiple-of-16 restriction.
|
||||
*/
|
||||
if( wtiff->tile ) {
|
||||
if( (wtiff->tilew & 0xf) != 0 ||
|
||||
(wtiff->tileh & 0xf) != 0 ) {
|
||||
wtiff_free( wtiff );
|
||||
vips_error( "vips2tiff",
|
||||
"%s", _( "tile size not a multiple of 16" ) );
|
||||
return( NULL );
|
||||
}
|
||||
}
|
||||
|
||||
/* Can only squash 8 bit mono. 3-band float should have been squashed
|
||||
* above.
|
||||
*/
|
||||
|
@ -1193,42 +1232,6 @@ wtiff_new( VipsImage *input, const char *filename,
|
|||
wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) *
|
||||
wtiff->tilew;
|
||||
|
||||
/* If compression is off and we're writing a >4gb image, automatically
|
||||
* enable bigtiff.
|
||||
*
|
||||
* This won't always work. If the image data is just under 4gb but
|
||||
* there's a lot of metadata, we could be pushed over the 4gb limit.
|
||||
*/
|
||||
if( wtiff->compression == COMPRESSION_NONE &&
|
||||
VIPS_IMAGE_SIZEOF_IMAGE( wtiff->ready ) > UINT_MAX &&
|
||||
!wtiff->bigtiff ) {
|
||||
g_warning( "%s", _( "image over 4gb, enabling bigtiff" ) );
|
||||
wtiff->bigtiff = TRUE;
|
||||
}
|
||||
|
||||
/* Build the pyramid framework.
|
||||
*/
|
||||
wtiff->layer = wtiff_layer_new( wtiff, NULL,
|
||||
wtiff->ready->Xsize, wtiff->image_height );
|
||||
|
||||
/* Fill all the layers.
|
||||
*/
|
||||
if( wtiff_allocate_layers( wtiff ) ) {
|
||||
wtiff_free( wtiff );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
if( tile )
|
||||
wtiff->tbuf = vips_malloc( NULL,
|
||||
TIFFTileSize( wtiff->layer->tif ) );
|
||||
else
|
||||
wtiff->tbuf = vips_malloc( NULL,
|
||||
TIFFScanlineSize( wtiff->layer->tif ) );
|
||||
if( !wtiff->tbuf ) {
|
||||
wtiff_free( wtiff );
|
||||
return( NULL );
|
||||
}
|
||||
|
||||
return( wtiff );
|
||||
}
|
||||
|
||||
|
@ -1876,8 +1879,8 @@ wtiff_gather( Wtiff *wtiff )
|
|||
TIFF *in;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "Appending layer %s ...\n", layer->lname );
|
||||
#endif /*DEBUG*/
|
||||
printf( "appending layer %s ...\n", layer->lname );
|
||||
|
||||
if( layer->lname ) {
|
||||
if( !(source = vips_source_new_from_file(
|
||||
|
@ -1911,116 +1914,109 @@ wtiff_gather( Wtiff *wtiff )
|
|||
return( 0 );
|
||||
}
|
||||
|
||||
/* Three types of write: single image, multipage and pyramid.
|
||||
/* Write one page from our input image, optionally pyramiding it.
|
||||
*/
|
||||
static int
|
||||
wtiff_write_image( Wtiff *wtiff )
|
||||
wtiff_write_page( Wtiff *wtiff, VipsImage *page )
|
||||
{
|
||||
if( wtiff->toilet_roll ) {
|
||||
int y;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "wtiff_write_image: toilet-roll mode\n" );
|
||||
printf( "wtiff_write_page:\n" );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
y = 0;
|
||||
for(;;) {
|
||||
VipsImage *page;
|
||||
/* Init the pyramid framework for this page. This will just make a
|
||||
* single layer if we're not pyramiding.
|
||||
*/
|
||||
wtiff_layer_init( wtiff, &wtiff->layer, NULL,
|
||||
page->Xsize, page->Ysize );
|
||||
|
||||
if( vips_crop( wtiff->ready, &page,
|
||||
0, y, wtiff->ready->Xsize, wtiff->page_height,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
if( vips_sink_disc( page, write_strip, wtiff ) ) {
|
||||
g_object_unref( page );
|
||||
return( -1 );
|
||||
}
|
||||
g_object_unref( page );
|
||||
/* Fill all the layers and write the TIFF headers.
|
||||
*/
|
||||
if( wtiff_allocate_layers( wtiff ) )
|
||||
return( -1 );
|
||||
|
||||
wtiff->page_number += 1;
|
||||
y += wtiff->page_height;
|
||||
if( y >= wtiff->ready->Ysize )
|
||||
break;
|
||||
|
||||
if( !TIFFWriteDirectory( wtiff->layer->tif ) ||
|
||||
wtiff_layer_rewind( wtiff,
|
||||
wtiff->layer ) ||
|
||||
wtiff_write_header( wtiff,
|
||||
wtiff->layer ) )
|
||||
return( -1 );
|
||||
}
|
||||
}
|
||||
else if( wtiff->pyramid &&
|
||||
wtiff->subifd ) {
|
||||
/* In ifd mode, we write the pyramid layers as subdirectories of this
|
||||
* page.
|
||||
*/
|
||||
if( wtiff->subifd ) {
|
||||
int n_layers;
|
||||
toff_t *subifd_offsets;
|
||||
Layer *p;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "wtiff_write_image: OME pyr mode\n" );
|
||||
printf( "wtiff_write_page: OME pyr mode\n" );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
/* This magic tag makes the n_layers directories we write
|
||||
* after this one into subdirectories. We set the offsets to 0
|
||||
* and libtiff will fill them in automatically.
|
||||
*/
|
||||
for( n_layers = 0, p = wtiff->layer; p; p = p->below )
|
||||
for( n_layers = 0, p = wtiff->layer->below; p; p = p->below )
|
||||
n_layers += 1;
|
||||
subifd_offsets = VIPS_ARRAY( NULL, n_layers, toff_t );
|
||||
memset( subifd_offsets, 0, n_layers * sizeof( toff_t ) );
|
||||
TIFFSetField( wtiff->layer->tif, TIFFTAG_SUBIFD,
|
||||
n_layers, subifd_offsets );
|
||||
g_free( subifd_offsets );
|
||||
}
|
||||
|
||||
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) )
|
||||
return( -1 );
|
||||
if( vips_sink_disc( page, write_strip, wtiff ) )
|
||||
return( -1 );
|
||||
|
||||
if( !TIFFWriteDirectory( wtiff->layer->tif ) )
|
||||
return( -1 );
|
||||
if( !TIFFWriteDirectory( wtiff->layer->tif ) )
|
||||
return( -1 );
|
||||
|
||||
/* Free lower pyramid resources ... this will
|
||||
/* Append any pyr layers, if necessary.
|
||||
*/
|
||||
if( wtiff->layer->below ) {
|
||||
/* Free any lower pyramid resources ... this will
|
||||
* TIFFClose() (but not delete) the smaller layers
|
||||
* ready for us to read from them again.
|
||||
*/
|
||||
if( wtiff->layer->below )
|
||||
layer_free_all( wtiff->layer->below );
|
||||
layer_free_all( wtiff->layer->below );
|
||||
|
||||
/* Append smaller layers to the main file.
|
||||
*/
|
||||
if( wtiff_gather( wtiff ) )
|
||||
return( -1 );
|
||||
|
||||
/* We can delete any temps now ready for the next page.
|
||||
*/
|
||||
wtiff_delete_temps( wtiff );
|
||||
|
||||
/* And free all lower pyr layers ready to be rebuilt for the
|
||||
* next page.
|
||||
*/
|
||||
VIPS_FREEF( layer_free_all, wtiff->layer->below );
|
||||
}
|
||||
else if( wtiff->pyramid ) {
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
/* Write all pages.
|
||||
*/
|
||||
static int
|
||||
wtiff_write_image( Wtiff *wtiff )
|
||||
{
|
||||
int y;
|
||||
|
||||
for( y = 0; y < wtiff->ready->Ysize; y += wtiff->page_height ) {
|
||||
VipsImage *page;
|
||||
|
||||
#ifdef DEBUG
|
||||
printf( "wtiff_write_image: pyramid mode\n" );
|
||||
printf( "writing page %d ...\n", wtiff->page_number );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) )
|
||||
if( vips_crop( wtiff->ready, &page,
|
||||
0, y, wtiff->ready->Xsize, wtiff->page_height,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
if( wtiff_write_page( wtiff, page ) ) {
|
||||
g_object_unref( page );
|
||||
return( -1 );
|
||||
}
|
||||
g_object_unref( page );
|
||||
|
||||
if( !TIFFWriteDirectory( wtiff->layer->tif ) )
|
||||
return( -1 );
|
||||
|
||||
/* Free lower pyramid resources ... this will
|
||||
* TIFFClose() (but not delete) the smaller layers
|
||||
* ready for us to read from them again.
|
||||
*/
|
||||
if( wtiff->layer->below )
|
||||
layer_free_all( wtiff->layer->below );
|
||||
|
||||
/* Append smaller layers to the main file.
|
||||
*/
|
||||
if( wtiff_gather( wtiff ) )
|
||||
return( -1 );
|
||||
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG
|
||||
printf( "wtiff_write_image: single-image mode\n" );
|
||||
#endif /*DEBUG*/
|
||||
|
||||
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) )
|
||||
return( -1 );
|
||||
wtiff->page_number += 1;
|
||||
}
|
||||
|
||||
return( 0 );
|
||||
|
|
Loading…
Reference in New Issue