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
1 changed files with 200 additions and 204 deletions

View File

@ -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 );