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:
John Cupitt 2020-05-24 15:21:41 +01:00
parent 6a8f128831
commit a2d196b736

View File

@ -193,6 +193,8 @@
* - write XYZ images as logluv * - write XYZ images as logluv
* 7/2/20 [jclavoie-jive] * 7/2/20 [jclavoie-jive]
* - add PAGENUMBER support * - add PAGENUMBER support
* 23/5/20
* - add support for subifd pyramid layers
*/ */
/* /*
@ -247,6 +249,13 @@
#include "pforeign.h" #include "pforeign.h"
#include "tiff.h" #include "tiff.h"
/* TODO:
*
* - have a layout enum rather than a bool
* - revise docs
* - revise tests
*/
/* Max number of alpha channels we allow. /* Max number of alpha channels we allow.
*/ */
#define MAX_ALPHA (64) #define MAX_ALPHA (64)
@ -396,40 +405,62 @@ embed_profile_meta( TIFF *tif, VipsImage *im )
return( 0 ); return( 0 );
} }
static Layer * static void
wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) wtiff_layer_init( Wtiff *wtiff, Layer **layer, Layer *above,
int width, int height )
{ {
Layer *layer; if( !*layer ) {
*layer = VIPS_NEW( wtiff->ready, Layer );
layer = VIPS_NEW( wtiff->ready, Layer ); (*layer)->wtiff = wtiff;
layer->wtiff = wtiff; (*layer)->width = width;
layer->width = width; (*layer)->height = height;
layer->height = height;
if( !above ) if( !above )
/* Top of pyramid. /* Top of pyramid.
*/ */
layer->sub = 1; (*layer)->sub = 1;
else else
layer->sub = above->sub * 2; (*layer)->sub = above->sub * 2;
layer->lname = NULL; (*layer)->lname = NULL;
layer->buf = NULL; (*layer)->buf = NULL;
layer->len = 0; (*layer)->len = 0;
layer->tif = NULL; (*layer)->tif = NULL;
layer->image = NULL; (*layer)->image = NULL;
layer->write_y = 0; (*layer)->write_y = 0;
layer->y = 0; (*layer)->y = 0;
layer->strip = NULL; (*layer)->strip = NULL;
layer->copy = NULL; (*layer)->copy = NULL;
layer->below = NULL; (*layer)->below = NULL;
layer->above = above; (*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().
*/
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 );
}
}
/* /*
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 ) { if( wtiff->pyramid ) {
int limitw, limith; 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 * Very tall or wide images might end up with a smallest layer
* larger than one tile. * larger than one tile.
*/ */
if( (layer->width > limitw || if( ((*layer)->width > limitw ||
layer->height > limith) && (*layer)->height > limith) &&
layer->width > 1 && (*layer)->width > 1 &&
layer->height > 1 ) (*layer)->height > 1 )
layer->below = wtiff_layer_new( wtiff, layer, wtiff_layer_init( wtiff, &(*layer)->below, *layer,
width / 2, height / 2 ); 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 static int
@ -846,7 +856,10 @@ wtiff_allocate_layers( Wtiff *wtiff )
{ {
Layer *layer; Layer *layer;
g_assert( wtiff->layer );
for( layer = wtiff->layer; layer; layer = layer->below ) { for( layer = wtiff->layer; layer; layer = layer->below ) {
if( !layer->image ) {
layer->image = vips_image_new(); layer->image = vips_image_new();
if( vips_image_pipelinev( layer->image, if( vips_image_pipelinev( layer->image,
VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) ) VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) )
@ -857,29 +870,42 @@ wtiff_allocate_layers( Wtiff *wtiff )
layer->strip = vips_region_new( layer->image ); layer->strip = vips_region_new( layer->image );
layer->copy = vips_region_new( layer->image ); layer->copy = vips_region_new( layer->image );
/* The regions will get used in the bg thread callback, so /* The regions will get used in the bg thread callback,
* make sure we don't own them. * so make sure we don't own them.
*/ */
vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->strip );
vips__region_no_ownership( layer->copy ); vips__region_no_ownership( layer->copy );
if( wtiff_layer_rewind( wtiff, layer ) )
return( -1 );
if( layer->lname ) if( layer->lname )
layer->tif = vips__tiff_openout( layer->tif = vips__tiff_openout(
layer->lname, wtiff->bigtiff ); layer->lname, wtiff->bigtiff );
else { else {
layer->tif = vips__tiff_openout_buffer( wtiff->ready, layer->tif = vips__tiff_openout_buffer(
wtiff->bigtiff, &layer->buf, &layer->len ); wtiff->ready, wtiff->bigtiff,
&layer->buf, &layer->len );
} }
if( !layer->tif ) if( !layer->tif )
return( -1 ); return( -1 );
}
if( wtiff_layer_rewind( wtiff, layer ) )
return( -1 );
if( wtiff_write_header( wtiff, layer ) ) if( wtiff_write_header( wtiff, layer ) )
return( -1 ); 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 ); return( 0 );
} }
@ -1099,28 +1125,6 @@ wtiff_new( VipsImage *input, const char *filename,
wtiff->toilet_roll = TRUE; wtiff->toilet_roll = TRUE;
wtiff->image_height = wtiff->page_height; wtiff->image_height = wtiff->page_height;
wtiff->n_pages = wtiff->ready->Ysize / 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. /* 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 /* Can only squash 8 bit mono. 3-band float should have been squashed
* above. * above.
*/ */
@ -1193,42 +1232,6 @@ wtiff_new( VipsImage *input, const char *filename,
wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) *
wtiff->tilew; 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 ); return( wtiff );
} }
@ -1876,8 +1879,8 @@ wtiff_gather( Wtiff *wtiff )
TIFF *in; TIFF *in;
#ifdef DEBUG #ifdef DEBUG
printf( "Appending layer %s ...\n", layer->lname );
#endif /*DEBUG*/ #endif /*DEBUG*/
printf( "appending layer %s ...\n", layer->lname );
if( layer->lname ) { if( layer->lname ) {
if( !(source = vips_source_new_from_file( if( !(source = vips_source_new_from_file(
@ -1911,77 +1914,64 @@ wtiff_gather( Wtiff *wtiff )
return( 0 ); return( 0 );
} }
/* Three types of write: single image, multipage and pyramid. /* Write one page from our input image, optionally pyramiding it.
*/ */
static int static int
wtiff_write_image( Wtiff *wtiff ) wtiff_write_page( Wtiff *wtiff, VipsImage *page )
{ {
if( wtiff->toilet_roll ) {
int y;
#ifdef DEBUG #ifdef DEBUG
printf( "wtiff_write_image: toilet-roll mode\n" ); printf( "wtiff_write_page:\n" );
#endif /*DEBUG*/ #endif /*DEBUG*/
y = 0; /* Init the pyramid framework for this page. This will just make a
for(;;) { * single layer if we're not pyramiding.
VipsImage *page; */
wtiff_layer_init( wtiff, &wtiff->layer, NULL,
page->Xsize, page->Ysize );
if( vips_crop( wtiff->ready, &page, /* Fill all the layers and write the TIFF headers.
0, y, wtiff->ready->Xsize, wtiff->page_height, */
NULL ) ) if( wtiff_allocate_layers( wtiff ) )
return( -1 ); return( -1 );
if( vips_sink_disc( page, write_strip, wtiff ) ) {
g_object_unref( page );
return( -1 );
}
g_object_unref( page );
wtiff->page_number += 1; /* In ifd mode, we write the pyramid layers as subdirectories of this
y += wtiff->page_height; * page.
if( y >= wtiff->ready->Ysize ) */
break; if( wtiff->subifd ) {
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 ) {
int n_layers; int n_layers;
toff_t *subifd_offsets; toff_t *subifd_offsets;
Layer *p; Layer *p;
#ifdef DEBUG #ifdef DEBUG
printf( "wtiff_write_image: OME pyr mode\n" ); printf( "wtiff_write_page: OME pyr mode\n" );
#endif /*DEBUG*/ #endif /*DEBUG*/
/* This magic tag makes the n_layers directories we write /* This magic tag makes the n_layers directories we write
* after this one into subdirectories. We set the offsets to 0 * after this one into subdirectories. We set the offsets to 0
* and libtiff will fill them in automatically. * 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; n_layers += 1;
subifd_offsets = VIPS_ARRAY( NULL, n_layers, toff_t ); subifd_offsets = VIPS_ARRAY( NULL, n_layers, toff_t );
memset( subifd_offsets, 0, n_layers * sizeof( toff_t ) ); memset( subifd_offsets, 0, n_layers * sizeof( toff_t ) );
TIFFSetField( wtiff->layer->tif, TIFFTAG_SUBIFD, TIFFSetField( wtiff->layer->tif, TIFFTAG_SUBIFD,
n_layers, subifd_offsets ); n_layers, subifd_offsets );
g_free( subifd_offsets );
}
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) ) if( vips_sink_disc( page, write_strip, wtiff ) )
return( -1 ); return( -1 );
if( !TIFFWriteDirectory( wtiff->layer->tif ) ) if( !TIFFWriteDirectory( wtiff->layer->tif ) )
return( -1 ); 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 * TIFFClose() (but not delete) the smaller layers
* ready for us to read from them again. * 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. /* Append smaller layers to the main file.
@ -1989,38 +1979,44 @@ wtiff_write_image( Wtiff *wtiff )
if( wtiff_gather( wtiff ) ) if( wtiff_gather( wtiff ) )
return( -1 ); 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 #ifdef DEBUG
printf( "wtiff_write_image: pyramid mode\n" ); printf( "writing page %d ...\n", wtiff->page_number );
#endif /*DEBUG*/ #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 ); return( -1 );
if( wtiff_write_page( wtiff, page ) ) {
if( !TIFFWriteDirectory( wtiff->layer->tif ) ) g_object_unref( page );
return( -1 ); 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 { g_object_unref( page );
#ifdef DEBUG
printf( "wtiff_write_image: single-image mode\n" );
#endif /*DEBUG*/
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) ) wtiff->page_number += 1;
return( -1 );
} }
return( 0 ); return( 0 );