/* TIFF PARTS:
 * Copyright (c) 1988, 1990 by Sam Leffler.
 * All rights reserved.
 *
 * This file is provided for unrestricted use provided that this
 * legend is included on all tape media and as a part of the
 * software program in whole or part.  Users may copy, modify or
 * distribute this file at will.
 *
 * MODIFICATION FOR VIPS Copyright 1991, K.Martinez 
 *
 * software may be distributed FREE, with these copyright notices
 * no responsibility/warantee is implied or given
 *
 *
 * Modified and added im_LabQ2LabC() function. It can write IM_TYPE_LABQ image
 * in vips format  to LAB in tiff format.
 *  Copyright 1994 Ahmed Abbood.
 *
 * 19/9/95 JC
 *	- calls TIFFClose() more reliably
 *	- tidied up
 * 12/4/97 JC
 *	- thrown away and rewritten for TIFF 6 lib
 * 22/4/97 JC
 *	- writes a pyramid!
 *	- to separate TIFF files tho'
 * 23/4/97 JC
 *	- does 2nd gather pass to put pyramid into a single TIFF file
 *	- ... and shrinks IM_CODING_LABQ too
 * 26/10/98 JC
 *	- binary open for stupid systems
 * 7/6/99 JC
 *	- 16bit TIFF write too
 * 9/7/99 JC
 *	- ZIP tiff added
 * 11/5/00 JC
 *	- removed TIFFmalloc/TIFFfree
 * 5/8/00 JC
 *	- mode string now part of filename
 * 23/4/01 JC
 *	- HAVE_TIFF turns on TIFFness
 * 19/3/02 ruven
 *	- pyramid stops at tile size, not 64x64
 * 29/4/02 JC
 * 	- write any number of bands (but still with photometric RGB, so not
 * 	  very useful)
 * 10/9/02 JC
 *	- oops, handle TIFF errors better
 *	- now writes CMYK correctly
 * 13/2/03 JC
 *	- tries not to write mad resolutions
 * 7/5/03 JC
 *	- only write CMYK if Type == CMYK
 *	- writes EXTRASAMPLES ALPHA for bands == 2 or 4 (if we're writing RGB)
 * 17/11/03 JC
 *	- write float too
 * 28/11/03 JC
 *	- read via a "p" so we work from mmap window images
 *	- uses threadgroups for speedup
 * 9/3/04 JC
 *	- 1 bit write mode added
 * 5/4/04
 *	- better handling of edge tiles (thanks Ruven)
 * 18/5/04 Andrey Kiselev
 *	- added res_inch/res_cm option
 * 20/5/04 JC
 *	- allow single res number too
 * 19/7/04
 *	- write several scanlines at once, good speed up for some cases
 * 22/9/04
 *	- got rid of wrapper image so nip gets progress feedback 
 * 	- fixed tiny read-beyond-buffer issue for edge tiles
 * 7/10/04
 * 	- added ICC profile embedding
 * 13/12/04
 *	- can now pyramid any non-complex type (thanks Ruven)
 * 27/1/05
 *	- added ccittfax4 as a compression option
 * 9/3/05
 *	- set PHOTOMETRIC_CIELAB for vips TYPE_LAB images ... so we can write
 *	  float LAB as well as float RGB
 *	- also LABS images 
 * 22/6/05
 *	- 16 bit LAB write was broken
 * 9/9/05
 * 	- write any icc profile from meta 
 * 3/3/06
 * 	- raise tile buffer limit (thanks Ruven)
 * 11/11/06
 * 	- set ORIENTATION_TOPLEFT (thanks Josef)
 * 18/7/07 Andrey Kiselev
 * 	- remove "b" option on TIFFOpen()
 * 	- support TIFFTAG_PREDICTOR types for lzw and deflate compression
 * 3/11/07
 * 	- use im_wbuffer() for background writes
 * 15/2/08
 * 	- set TIFFTAG_JPEGQUALITY explicitly when we copy TIFF files, since 
 * 	  libtiff doesn't keep this in the header (thanks Joe)
 * 20/2/08
 * 	- use tiff error handler from im_tiff2vips.c
 * 27/2/08
 * 	- don't try to copy icc profiles when building pyramids (thanks Joe)
 * 9/4/08
 * 	- use IM_META_RESOLUTION_UNIT to set default resunit
 * 17/4/08
 * 	- allow CMYKA (thanks Doron)
 * 5/9/08
 *	- trigger eval callbacks during tile write
 * 4/2/10
 * 	- gtkdoc
 * 26/2/10
 * 	- option to turn on bigtiff output
 * 16/4/10
 * 	- use vips_sink_*() instead of threadgroup and friends
 * 22/6/10
 * 	- make no-owner regions for the tile cache, since we share these
 * 	  between threads
 * 12/7/11
 * 	- use im__temp_name() for intermediates rather than polluting the
 * 	  output directory
 * 5/9/11
 * 	- enable YCbCr compression for jpeg write
 * 23/11/11
 * 	- set reduced-resolution subfile type on pyramid layers
 * 2/12/11
 * 	- make into a simple function call ready to be wrapped as a new-style
 * 	  VipsForeign class
 * 21/3/12
 * 	- bump max layer buffer up
 * 2/6/12
 * 	- copy jpeg pyramid in gather in RGB mode ... tiff4 doesn't do ycbcr
 * 	  mode
 * 7/8/12
 * 	- be more cautious enabling YCbCr mode
 * 24/9/13
 * 	- support many more vips formats, eg. complex, 32-bit int, any number
 * 	  of bands, etc., see the tiff loader
 * 26/1/14
 * 	- add RGB as well as YCbCr write
 * 20/11/14
 * 	- cache input in tile write mode to keep us sequential
 * 3/12/14
 * 	- embed XMP in output
 * 10/12/14
 * 	- zero out edge tile buffers before jpeg wtiff, thanks iwbh15
 * 19/1/15
 * 	- disable chroma subsample if Q >= 90
 * 13/2/15
 * 	- append later layers, don't copy the base image
 * 	- use the nice dzsave pyramid code, much faster and simpler
 * 	- we now allow strip pyramids
 * 27/3/15
 * 	- squash >128 rather than >0, nicer results for shrink
 * 	- add miniswhite option
 * 29/9/15
 * 	- try to write IPTC metadata
 * 	- try to write photoshop metadata
 * 11/11/15
 * 	- better alpha handling, thanks sadaqatullahn
 * 21/12/15
 * 	- write TIFFTAG_IMAGEDESCRIPTION
 * 2/6/16
 * 	- support strip option
 * 4/7/16
 * 	- tag alpha as UNASSALPHA since it's not pre-multiplied, thanks Peter
 * 17/8/16
 * 	- use wchar_t TIFFOpen on Windows
 * 14/10/16
 * 	- add buffer output
 * 29/1/17
 * 	- enable bigtiff automatically for large, uncompressed writes, thanks 
 * 	  AndreasSchmid1 
 * 26/8/17
 * 	- support pyramid creation to buffer, thanks bubba
 * 24/10/17
 * 	- no error on page-height not a factor of image height, just don't
 * 	  write multipage
 * 13/6/18
 * 	- add region_shrink
 * 2/7/18
 * 	- copy EXTRASAMPLES to pyramid layers
 * 21/12/18
 * 	- stop pyr layers if width or height drop to 1
 * 8/7/19
 * 	- add webp and zstd support
 * 	- add @level and @lossless
 * 18/12/19
 * 	- "squash" now squashes 3-band float LAB down to LABQ
 * 26/1/20
 * 	- add "depth" to set pyr depth
 * 27/1/20
 * 	- write XYZ images as logluv
 * 7/2/20 [jclavoie-jive]
 * 	- add PAGENUMBER support
 * 23/5/20
 * 	- add support for subifd pyramid layers
 * 6/6/20 MathemanFlo
 * 	- add bitdepth support for 2 and 4 bit greyscale images
 * 29/9/21 LionelArn2
 * 	- loop for the whole output image, rather than per page
 * 20/10/21 [jacopoabramo]
 * 	- subifd enables pyramid
 * 	- add support for page_height param
 * 11/5/22
 * 	- switch to terget API for output
 */

/*

    This file is part of VIPS.
    
    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/* 
#define DEBUG_VERBOSE
#define DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#ifdef HAVE_TIFF

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif /*HAVE_UNISTD_H*/
#include <string.h>

#include <vips/vips.h>
#include <vips/internal.h>

#include "pforeign.h"
#include "tiff.h"

/* TODO:
 *
 * - add a flag for plane-separate write
 *
 *   	At the moment, we write bioformats-style TIFFs by splitting bands up,
 *   	making a toilet-roll image and writing out in pages. The TIFFs we make
 *   	are not tagged as plane-separate and do not have (eg.) RGB photometric
 *   	interpretation. Moreover, when working from an RGB source, we'll end
 *   	up reading the input three times.
 *
 *   	A write-plane-separate flag to the TIFF writer could let us set the
 *   	photometric interpretation correctly, and save all planes in a single
 *   	pass before doing a final gather sweep.
 */

/* Max number of alpha channels we allow.
 */
#define MAX_ALPHA (64)

/* Bioformats uses this tag for lossy jp2k compressed tiles.
 */
#define JP2K_LOSSY 33004

/* Compression types we handle ourselves.
 */
static int wtiff_we_compress[] = {
	JP2K_LOSSY
};

typedef struct _Layer Layer;
typedef struct _Wtiff Wtiff;

/* A layer in the pyramid.
 */
struct _Layer {
	Wtiff *wtiff;			/* Main wtiff struct */

	/* The temp target for this layer.
	 */
	VipsTarget *target;

	int width, height;		/* Layer size */
	int sub;			/* Subsample factor for this layer */
	TIFF *tif;			/* TIFF file we write this layer to */

	/* The image we build. We only keep a few scanlines of this around in
	 * @strip. 
	 */
	VipsImage *image;

	/* The y position of strip in image.
	 */
	int y;

	/* The next line we write to in strip. 
	 */
	int write_y;

	VipsRegion *strip;		/* The current strip of pixels */
	VipsRegion *copy;		/* Pixels we copy to the next strip */

	Layer *below;			/* The smaller layer below us */
	Layer *above;			/* The larger layer above */
};

/* A TIFF image in the process of being written.
 */
struct _Wtiff {
	VipsImage *input;		/* Original input image */

	/* Image transformed ready for write.
	 */
	VipsImage *ready;

	/* Target to write to.
	 */
	VipsTarget *target;

	Layer *layer;			/* Top of pyramid */
	VipsPel *tbuf;			/* TIFF output buffer */
	int tls;			/* Tile line size */

	int compression;		/* libtiff compression type */
	int Q;				/* JPEG q-factor, webp level */
	int predictor;			/* libtiff predictor type */
	int tile;			/* Tile or not */
	int tilew, tileh;		/* Tile size */
	int pyramid;			/* Wtiff pyramid */
	int bitdepth;                   /* Write as 1, 2 or 4 bit */
	int miniswhite;			/* Wtiff as 0 == white */
        int resunit;                    /* Resolution unit (inches or cm) */
        double xres;                   	/* Resolution in X */
        double yres;                   	/* Resolution in Y */
	const char *profile;		/* Profile to embed */
	int bigtiff;			/* True for bigtiff write */
	int rgbjpeg;			/* True for RGB not YCbCr */
	int properties;			/* Set to save XML props */
	int strip;			/* Don't write metadata */
	VipsRegionShrink region_shrink; /* How to shrink regions */
	int level;			/* zstd compression level */
	gboolean lossless;		/* lossless mode */
	VipsForeignDzDepth depth;	/* Pyr depth */
	gboolean subifd;		/* Write pyr layers into subifds */
	gboolean premultiply;		/* Premultiply alpha */

	/* True if we've detected a toilet-roll image, plus the page height,
	 * which has been checked to be a factor of im->Ysize. page_number
	 * starts at zero and ticks up as we write each page.
	 */
	gboolean toilet_roll;
	int page_height;
	int page_number;
	int n_pages;

	/* The height of the TIFF we write. Equal to page_height in toilet
	 * roll mode.
	 */
	int image_height;

	/* TRUE if the compression type is not supported by libtiff directly
	 * and we must compress ourselves. 
	 */
	gboolean we_compress;
};

/* Write an ICC Profile from a file into the JPEG stream.
 */
static int
embed_profile_file( TIFF *tif, const char *profile )
{
	VipsBlob *blob;

	if( vips_profile_load( profile, &blob, NULL ) )
		return( -1 );

	if( blob ) {
		size_t length;
		const void *data = vips_blob_get( blob, &length );

		TIFFSetField( tif, TIFFTAG_ICCPROFILE, length, data );

#ifdef DEBUG
		printf( "vips2tiff: attached profile \"%s\"\n", profile );
#endif /*DEBUG*/

		vips_area_unref( (VipsArea *) blob );
	}

	return( 0 );
}

/* Embed an ICC profile from VipsImage metadata.
 */
static int
embed_profile_meta( TIFF *tif, VipsImage *im )
{
	const void *data;
	size_t length;

	if( vips_image_get_blob( im, VIPS_META_ICC_NAME, &data, &length ) )
		return( -1 );
	TIFFSetField( tif, TIFFTAG_ICCPROFILE, length, data );

#ifdef DEBUG
	printf( "vips2tiff: attached profile from meta\n" );
#endif /*DEBUG*/

	return( 0 );
}

static void
wtiff_layer_init( Wtiff *wtiff, Layer **layer, Layer *above, 
	int width, int height )
{
	if( !*layer ) {
		*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;

		(*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 target we write to. The base layer writes to the main
		 * output, each layer smaller writes to a memory temp.
		 */
		if( !above ) {
			(*layer)->target = wtiff->target;
			g_object_ref( (*layer)->target );
		}
		else 
			(*layer)->target = 
				vips_target_new_temp( wtiff->target );

		/*
		printf( "wtiff_layer_init: sub = %d, width = %d, height = %d\n",
			(*layer)->sub, width, height );
		 */
	}

	if( wtiff->pyramid ) {
		int limitw, limith;

		switch( wtiff->depth ) {
		case VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL:
			limitw = limith = 1;
			break;

		case VIPS_FOREIGN_DZ_DEPTH_ONETILE:
			limitw = wtiff->tilew;
			limith = wtiff->tileh;
			break;

		case VIPS_FOREIGN_DZ_DEPTH_ONE:
			limitw = wtiff->ready->Xsize;
			limith = wtiff->ready->Ysize;
			break;

		default:
			g_assert_not_reached();
		}

		/* We make another layer if the image is too large to fit in a
		 * single tile, and if neither axis is greater than 1.
		 *
		 * 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 ) 
			wtiff_layer_init( wtiff, &(*layer)->below, *layer, 
				width / 2, height / 2 );
	}
}

static int
wtiff_embed_profile( Wtiff *wtiff, TIFF *tif )
{
	if( wtiff->profile &&
		embed_profile_file( tif, wtiff->profile ) )
		return( -1 );

	if( !wtiff->profile && 
		vips_image_get_typeof( wtiff->ready, VIPS_META_ICC_NAME ) &&
		embed_profile_meta( tif, wtiff->ready ) )
		return( -1 );

	return( 0 );
}

static int
wtiff_embed_xmp( Wtiff *wtiff, TIFF *tif )
{
	const void *data;
	size_t size;

	if( !vips_image_get_typeof( wtiff->ready, VIPS_META_XMP_NAME ) )
		return( 0 );
	if( vips_image_get_blob( wtiff->ready, VIPS_META_XMP_NAME, 
		&data, &size ) )
		return( -1 );
	TIFFSetField( tif, TIFFTAG_XMLPACKET, size, data );

#ifdef DEBUG
	printf( "vips2tiff: attached XMP from meta\n" );
#endif /*DEBUG*/

	return( 0 );
}

static int
wtiff_embed_iptc( Wtiff *wtiff, TIFF *tif )
{
	const void *data;
	size_t size;

	if( !vips_image_get_typeof( wtiff->ready, VIPS_META_IPTC_NAME ) )
		return( 0 );
	if( vips_image_get_blob( wtiff->ready, VIPS_META_IPTC_NAME, 
		&data, &size ) )
		return( -1 );

	/* For no very good reason, libtiff stores IPTC as an array of
	 * long, not byte.
	 */
	if( size & 3 ) {
		g_warning( "%s", _( "rounding up IPTC data length" ) );
		size /= 4;
		size += 1;
	}
	else
		size /= 4;

	TIFFSetField( tif, TIFFTAG_RICHTIFFIPTC, size, data );

#ifdef DEBUG
	printf( "vips2tiff: attached IPTC from meta\n" );
#endif /*DEBUG*/

	return( 0 );
}

static int
wtiff_embed_photoshop( Wtiff *wtiff, TIFF *tif )
{
	const void *data;
	size_t size;

	if( !vips_image_get_typeof( wtiff->ready, VIPS_META_PHOTOSHOP_NAME ) )
		return( 0 );
	if( vips_image_get_blob( wtiff->ready, VIPS_META_PHOTOSHOP_NAME, 
		&data, &size ) )
		return( -1 );
	TIFFSetField( tif, TIFFTAG_PHOTOSHOP, size, data );

#ifdef DEBUG
	printf( "vips2tiff: attached photoshop data from meta\n" );
#endif /*DEBUG*/

	return( 0 );
}

/* Set IMAGEDESCRIPTION, if it's there.  If @properties is TRUE, set from
 * vips' metadata.
 */
static int
wtiff_embed_imagedescription( Wtiff *wtiff, TIFF *tif )
{
	if( wtiff->properties ) {
		char *doc;

		if( !(doc = vips__xml_properties( wtiff->ready )) )
			return( -1 );
		TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, doc );
		g_free( doc );
	}
	else {
		const char *imagedescription;

		if( !vips_image_get_typeof( wtiff->ready,
			VIPS_META_IMAGEDESCRIPTION ) )
			return( 0 );
		if( vips_image_get_string( wtiff->ready,
			VIPS_META_IMAGEDESCRIPTION, &imagedescription ) )
			return( -1 );
		TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, imagedescription );
	}

#ifdef DEBUG
	printf( "vips2tiff: attached imagedescription from meta\n" );
#endif /*DEBUG*/

	return( 0 );
}

/* Write a TIFF header for this layer. 
 */
static int
wtiff_write_header( Wtiff *wtiff, Layer *layer )
{
	TIFF *tif = layer->tif;

	int i;
	int orientation; 

#ifdef DEBUG
	printf( "wtiff_write_header: sub %d, width %d, height %d\n",
		layer->sub, layer->width, layer->height );
#endif /*DEBUG*/

	/* Output base header fields.
	 */
	TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width );
	TIFFSetField( tif, TIFFTAG_IMAGELENGTH, layer->height );
	TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG );
	TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT );
	TIFFSetField( tif, TIFFTAG_COMPRESSION, wtiff->compression );

	if( wtiff->compression == COMPRESSION_JPEG ) 
		TIFFSetField( tif, TIFFTAG_JPEGQUALITY, wtiff->Q );

#ifdef HAVE_TIFF_COMPRESSION_WEBP
	if( wtiff->compression == COMPRESSION_WEBP ) {
		TIFFSetField( tif, TIFFTAG_WEBP_LEVEL, wtiff->Q );
		TIFFSetField( tif, TIFFTAG_WEBP_LOSSLESS, wtiff->lossless );
	}
	if( wtiff->compression == COMPRESSION_ZSTD ) {
		TIFFSetField( tif, TIFFTAG_ZSTD_LEVEL, wtiff->level );
		if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) 
			TIFFSetField( tif, 
				TIFFTAG_PREDICTOR, wtiff->predictor );
	}
#endif /*HAVE_TIFF_COMPRESSION_WEBP*/

	if( (wtiff->compression == COMPRESSION_ADOBE_DEFLATE ||
		wtiff->compression == COMPRESSION_LZW) &&
		wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) 
		TIFFSetField( tif, TIFFTAG_PREDICTOR, wtiff->predictor );

	for( i = 0; i < VIPS_NUMBER( wtiff_we_compress ); i++ )
		if( wtiff->compression == wtiff_we_compress[i] ) {
			wtiff->we_compress = TRUE;
			break;
		}

	/* Don't write mad resolutions (eg. zero), it confuses some programs.
	 */
	TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, wtiff->resunit );
	TIFFSetField( tif, TIFFTAG_XRESOLUTION, 
		VIPS_FCLIP( 0.01, wtiff->xres, 1000000 ) );
	TIFFSetField( tif, TIFFTAG_YRESOLUTION, 
		VIPS_FCLIP( 0.01, wtiff->yres, 1000000 ) );

	if( !wtiff->strip ) 
		if( wtiff_embed_profile( wtiff, tif ) ||
			wtiff_embed_xmp( wtiff, tif ) ||
			wtiff_embed_iptc( wtiff, tif ) ||
			wtiff_embed_photoshop( wtiff, tif ) ||
			wtiff_embed_imagedescription( wtiff, tif ) )
			return( -1 ); 

	if( vips_image_get_typeof( wtiff->ready, VIPS_META_ORIENTATION ) &&
		!vips_image_get_int( wtiff->ready, 
			VIPS_META_ORIENTATION, &orientation ) )
		TIFFSetField( tif, TIFFTAG_ORIENTATION, orientation );

	/* And colour fields.
	 */
	if( wtiff->ready->Coding == VIPS_CODING_LABQ ) {
		TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 );
		TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 );
		TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB );
	}
	else if( wtiff->bitdepth == 1 || wtiff->bitdepth == 2 ||
                 wtiff->bitdepth == 4 ) {
		TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 );
		TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, wtiff->bitdepth );
		TIFFSetField( tif, TIFFTAG_PHOTOMETRIC,
			wtiff->miniswhite ?
				PHOTOMETRIC_MINISWHITE :
				PHOTOMETRIC_MINISBLACK );
	}
	else {
		int photometric;

		/* Number of bands that have colour in .. other bands are saved
		 * as alpha.
		 */
		int colour_bands;

		int alpha_bands;

		TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 
			wtiff->ready->Bands );
		TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 
			vips_format_sizeof( wtiff->ready->BandFmt ) << 3 );

		if( wtiff->ready->Type == VIPS_INTERPRETATION_B_W ||
			wtiff->ready->Type == VIPS_INTERPRETATION_GREY16 ||
			wtiff->ready->Bands < 3 ) { 
			/* Mono or mono + alpha.
			 */
			photometric = wtiff->miniswhite ?
				PHOTOMETRIC_MINISWHITE :
				PHOTOMETRIC_MINISBLACK;
			colour_bands = 1;
		}
		else if( wtiff->ready->Type == VIPS_INTERPRETATION_LAB || 
			wtiff->ready->Type == VIPS_INTERPRETATION_LABS ) {
			photometric = PHOTOMETRIC_CIELAB;
			colour_bands = 3;
		}
		else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) { 
			double stonits;

			photometric = PHOTOMETRIC_LOGLUV;
			/* Tell libtiff we will write as float XYZ.
			 */
			TIFFSetField( tif, 
				TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
			stonits = 1.0;
			if( vips_image_get_typeof( wtiff->ready, "stonits" ) )
				vips_image_get_double( wtiff->ready, 
					"stonits", &stonits );
			TIFFSetField( tif, TIFFTAG_STONITS, stonits );
			colour_bands = 3;
		}
		else if( wtiff->ready->Type == VIPS_INTERPRETATION_CMYK &&
			wtiff->ready->Bands >= 4 ) {
			photometric = PHOTOMETRIC_SEPARATED;
			TIFFSetField( tif, TIFFTAG_INKSET, INKSET_CMYK );
			colour_bands = 4;
		}
		else if( wtiff->compression == COMPRESSION_JPEG &&
			wtiff->ready->Bands == 3 &&
			wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR &&
			(!wtiff->rgbjpeg && wtiff->Q < 90) ) { 
			/* This signals to libjpeg that it can do
			 * YCbCr chrominance subsampling from RGB, not
			 * that we will supply the image as YCbCr.
			 */
			photometric = PHOTOMETRIC_YCBCR;
			TIFFSetField( tif, TIFFTAG_JPEGCOLORMODE, 
				JPEGCOLORMODE_RGB );
			colour_bands = 3;
		}
		else {
			/* Some kind of generic multi-band image with three or
			 * more bands ... save the first three bands as RGB, 
			 * the rest as alpha.
			 */
			photometric = PHOTOMETRIC_RGB;
			colour_bands = 3;
		}

		alpha_bands = VIPS_CLIP( 0, 
			wtiff->ready->Bands - colour_bands, MAX_ALPHA );
		if( alpha_bands > 0 ) { 
			guint16 v[MAX_ALPHA];
			int i;

			/* EXTRASAMPLE_UNASSALPHA means generic extra
			 * alpha-like channels. ASSOCALPHA means pre-multipled
			 * alpha only. 
			 *
			 * Make the first channel the premultiplied alpha, if
			 * we are premultiplying.
			 */
			for( i = 0; i < alpha_bands; i++ )
				v[i] = i == 0 && wtiff->premultiply ? 
					EXTRASAMPLE_ASSOCALPHA :
					EXTRASAMPLE_UNASSALPHA;
			TIFFSetField( tif, 
				TIFFTAG_EXTRASAMPLES, alpha_bands, v );
		}

		TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, photometric );
	}

	/* Layout.
	 */
	if( wtiff->tile ) {
		TIFFSetField( tif, TIFFTAG_TILEWIDTH, wtiff->tilew );
		TIFFSetField( tif, TIFFTAG_TILELENGTH, wtiff->tileh );
	}
	else
		TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, wtiff->tileh );


	if( layer->above ) 
		/* Pyramid layer.
		 */
		TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE );
	else if( wtiff->toilet_roll ) {
		/* One page of many.
		 */
		TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE );

		TIFFSetField( tif, TIFFTAG_PAGENUMBER, 
			wtiff->page_number, wtiff->n_pages );
	}


	/* Sample format.
	 *
	 * Don't set for logluv: libtiff does this for us.
	 */
	if( wtiff->input->Type != VIPS_INTERPRETATION_XYZ ) { 
		int format; 

		format = SAMPLEFORMAT_UINT;
		if( vips_band_format_isuint( wtiff->ready->BandFmt ) )
			format = SAMPLEFORMAT_UINT;
		else if( vips_band_format_isint( wtiff->ready->BandFmt ) )
			format = SAMPLEFORMAT_INT;
		else if( vips_band_format_isfloat( wtiff->ready->BandFmt ) )
			format = SAMPLEFORMAT_IEEEFP;
		else if( vips_band_format_iscomplex( wtiff->ready->BandFmt ) )
			format = SAMPLEFORMAT_COMPLEXIEEEFP;
		TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format );
	}

	return( 0 );
}

static int
wtiff_layer_rewind( Wtiff *wtiff, Layer *layer )
{
	VipsRect strip_size;

	/* Build a line of tiles here. 
	 *
	 * Expand the strip if necessary to make sure we have an even 
	 * number of lines. 
	 */
	strip_size.left = 0;
	strip_size.top = 0;
	strip_size.width = layer->image->Xsize;
	strip_size.height = wtiff->tileh;
	if( (strip_size.height & 1) == 1 )
		strip_size.height += 1;
	if( vips_region_buffer( layer->strip, &strip_size ) ) 
		return( -1 );

	layer->y = 0;
	layer->write_y = 0;

	return( 0 );
}

static int
wtiff_allocate_layers( Wtiff *wtiff )
{
	Layer *layer;

	g_assert( wtiff->layer );

	for( layer = wtiff->layer; layer; layer = layer->below ) {
		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 );

			/* 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 );

			layer->tif = vips__tiff_openout_target( layer->target, 
				wtiff->bigtiff );
			if( !layer->tif ) 
				return( -1 );
		}

		if( wtiff_layer_rewind( wtiff, layer ) )
			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 );
}

/* Free a single pyramid layer.
 */
static void
layer_free( Layer *layer )
{
	/* Don't unref the target for this layer -- we'll need it for gather.
	 */
	VIPS_FREEF( TIFFClose, layer->tif );
	VIPS_UNREF( layer->strip );
	VIPS_UNREF( layer->copy );
	VIPS_UNREF( layer->image );
}

/* Free an entire pyramid.
 */
static void
layer_free_all( Layer *layer )
{
	if( layer->below ) 
		layer_free_all( layer->below );

	layer_free( layer );
}

static void
wtiff_free( Wtiff *wtiff )
{
	Layer *layer;

	/* unref all the targets, including the base layer.
	 */
	for( layer = wtiff->layer; layer; layer = layer->below )
		VIPS_UNREF( layer->target );

	VIPS_UNREF( wtiff->ready );
	VIPS_FREE( wtiff->tbuf );
	VIPS_FREEF( layer_free_all, wtiff->layer );
	VIPS_FREE( wtiff );
}

static int
get_compression( VipsForeignTiffCompression compression )
{
	switch( compression ) {
	case VIPS_FOREIGN_TIFF_COMPRESSION_NONE:
		return( COMPRESSION_NONE );
	case VIPS_FOREIGN_TIFF_COMPRESSION_JPEG:
		return( COMPRESSION_JPEG );
	case VIPS_FOREIGN_TIFF_COMPRESSION_DEFLATE:
		return( COMPRESSION_ADOBE_DEFLATE );
	case VIPS_FOREIGN_TIFF_COMPRESSION_PACKBITS:
		return( COMPRESSION_PACKBITS );
	case VIPS_FOREIGN_TIFF_COMPRESSION_CCITTFAX4:
		return( COMPRESSION_CCITTFAX4 );
	case VIPS_FOREIGN_TIFF_COMPRESSION_LZW:
		return( COMPRESSION_LZW );
#ifdef HAVE_TIFF_COMPRESSION_WEBP
	case VIPS_FOREIGN_TIFF_COMPRESSION_WEBP:
		return( COMPRESSION_WEBP );
	case VIPS_FOREIGN_TIFF_COMPRESSION_ZSTD:
		return( COMPRESSION_ZSTD );
#endif /*HAVE_TIFF_COMPRESSION_WEBP*/
	case VIPS_FOREIGN_TIFF_COMPRESSION_JP2K:
		return( JP2K_LOSSY );
	
	default:
		return( COMPRESSION_NONE );
	}
}

static int
get_resunit( VipsForeignTiffResunit resunit )
{
	switch( resunit ) {
	case VIPS_FOREIGN_TIFF_RESUNIT_CM:
		return( RESUNIT_CENTIMETER );
	case VIPS_FOREIGN_TIFF_RESUNIT_INCH:
		return( RESUNIT_INCH );

	default:
		g_assert_not_reached();
	}

	/* Keep -Wall happy.
	 */
	return( -1 );
}

/* Get the image ready to be written.
 */
static int
ready_to_write( Wtiff *wtiff )
{
	VipsImage *input;
	VipsImage *x;

	input = wtiff->input;
	g_object_ref( input );

	if( vips_check_coding_known( "vips2tiff", input ) ) {
		VIPS_UNREF( input );
		return( -1 );
	}

	/* Premultiply any alpha, if necessary.
	 */
	if( wtiff->premultiply &&
		vips_image_hasalpha( input ) ) {
		VipsBandFormat start_format = input->BandFmt;

		if( vips_premultiply( input, &x, NULL ) ) {
			VIPS_UNREF( input );
			return( -1 );
		}
		VIPS_UNREF( input );
		input = x;

		/* Premultiply always makes a float -- cast back again.
		 */
		if( vips_cast( input, &x, start_format, NULL ) ) {
			VIPS_UNREF( input );
			return( -1 );
		}
		VIPS_UNREF( input );
		input = x;
	}

	/* "squash" float LAB down to LABQ.
	 */
	if( wtiff->bitdepth &&
		input->Bands == 3 &&
		input->BandFmt == VIPS_FORMAT_FLOAT &&
		input->Type == VIPS_INTERPRETATION_LAB ) {
		if( vips_Lab2LabQ( input, &x, NULL ) ) {
			VIPS_UNREF( input );
			return( -1 );
		}
		VIPS_UNREF( input );
		input = x;
	}

	wtiff->ready = input;

	return( 0 );
}

static Wtiff *
wtiff_new( VipsImage *input, VipsTarget *target,
	VipsForeignTiffCompression compression, int Q, 
	VipsForeignTiffPredictor predictor,
	const char *profile,
	gboolean tile, int tile_width, int tile_height,
	gboolean pyramid,
	int bitdepth,
	gboolean miniswhite,
	VipsForeignTiffResunit resunit, double xres, double yres,
	gboolean bigtiff,
	gboolean rgbjpeg,
	gboolean properties,
	gboolean strip,
	VipsRegionShrink region_shrink,
	int level, 
	gboolean lossless,
	VipsForeignDzDepth depth, 
	gboolean subifd,
	gboolean premultiply,
	int page_height )
{
	Wtiff *wtiff;

	if( !(wtiff = VIPS_NEW( NULL, Wtiff )) )
		return( NULL );
	wtiff->input = input;
	wtiff->ready = NULL;
	wtiff->target = target;
	wtiff->layer = NULL;
	wtiff->tbuf = NULL;
	wtiff->compression = get_compression( compression );
	wtiff->Q = Q;
	wtiff->predictor = predictor;
	wtiff->tile = tile;
	wtiff->tilew = tile_width;
	wtiff->tileh = tile_height;
	wtiff->pyramid = pyramid;
	wtiff->bitdepth = bitdepth;
	wtiff->miniswhite = miniswhite;
	wtiff->resunit = get_resunit( resunit );
	wtiff->xres = xres;
	wtiff->yres = yres;
	wtiff->profile = profile;
	wtiff->bigtiff = bigtiff;
	wtiff->rgbjpeg = rgbjpeg;
	wtiff->properties = properties;
	wtiff->strip = strip;
	wtiff->region_shrink = region_shrink;
	wtiff->level = level;
	wtiff->lossless = lossless;
	wtiff->depth = depth;
	wtiff->subifd = subifd;
	wtiff->premultiply = premultiply;
	wtiff->toilet_roll = FALSE;
	wtiff->page_height = page_height;
	wtiff->page_number = 0;
	wtiff->n_pages = 1;
	wtiff->image_height = input->Ysize;

	/* Any pre-processing on the image.
	 */
	if( ready_to_write( wtiff ) ) {
		wtiff_free( wtiff );
		return( NULL );
	}

	/* XYZ images are written as libtiff LOGLUV.
	 */
	if( wtiff->ready->Type == VIPS_INTERPRETATION_XYZ ) 
		wtiff->compression = COMPRESSION_SGILOG;

	/* Multipage image? 0 is the default for this argument.
	 */
	if( wtiff->page_height == 0 )
		wtiff->page_height = vips_image_get_page_height( input );
	if( wtiff->page_height > 0 &&
		wtiff->page_height < wtiff->ready->Ysize &&
		wtiff->ready->Ysize % wtiff->page_height == 0 ) {
#ifdef DEBUG
		printf( "wtiff_new: detected toilet roll image, "
			"page-height=%d\n", 
			wtiff->page_height );
		printf( "wtiff_new: pages=%d\n", 
			wtiff->ready->Ysize / wtiff->page_height );
#endif/*DEBUG*/

		wtiff->toilet_roll = TRUE;
		wtiff->image_height = wtiff->page_height;
		wtiff->n_pages = wtiff->ready->Ysize / wtiff->page_height;
	}

	/* subifd turns on pyramid mode.
	 */
	if( wtiff->subifd )
		wtiff->pyramid = TRUE;

	/* Pyramid images must be tiled.
	 */
	if( wtiff->pyramid )
		wtiff->tile = TRUE;

	/* Multi-page pyramids must be in subifd mode.
	 */
	if( wtiff->pyramid &&
		wtiff->toilet_roll )
		wtiff->subifd = TRUE;

	/* We can only pyramid LABQ and non-complex images. 
	 */
	if( wtiff->pyramid ) {
		if( wtiff->ready->Coding == VIPS_CODING_NONE && 
			vips_band_format_iscomplex( wtiff->ready->BandFmt ) ) {
			wtiff_free( wtiff );
			vips_error( "vips2tiff", 
				"%s", _( "can only pyramid LABQ and "
				"non-complex images" ) );
			return( NULL );
		}
	}

	/* 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 );
		}
	}

	/* Depth 8 is handled above.
	 */
	if( wtiff->bitdepth && 
		!(wtiff->bitdepth == 1 || 
		  wtiff->bitdepth == 2 || 
		  wtiff->bitdepth == 4) ) {
		g_warning( "%s",
			_( "bitdepth 1, 2 or 4 only -- disabling bitdepth") );
		wtiff->bitdepth = 0;
	}

	/* Can only have byte fractional bit depths for 8 bit mono.
	 * 3-band float should have been packed above.
	 */
	if( wtiff->bitdepth && 
		!(wtiff->ready->Coding == VIPS_CODING_NONE &&
		  wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && 
		  wtiff->ready->Bands == 1) ) { 
		g_warning( "%s",
			( "can only set bitdepth for 1-band uchar and "
                        "3-band float lab -- disabling bitdepth" ) );
		wtiff->bitdepth = 0;
	}

	if( wtiff->bitdepth &&
		wtiff->compression == COMPRESSION_JPEG ) {
		g_warning( "%s", 
			_( "can't have <8 bit JPEG -- disabling JPEG" ) );
		wtiff->compression = COMPRESSION_NONE;
	}
 
	/* We can only MINISWHITE non-complex images of 1 or 2 bands.
	 */
	if( wtiff->miniswhite &&
		(wtiff->ready->Coding != VIPS_CODING_NONE || 
			vips_band_format_iscomplex( wtiff->ready->BandFmt ) ||
			wtiff->ready->Bands > 2) ) {
		g_warning( "%s", 
			_( "can only save non-complex greyscale images "
				"as miniswhite -- disabling miniswhite" ) );
		wtiff->miniswhite = FALSE;
	}

	/* Sizeof a line of bytes in the TIFF tile.
	 */
	if( wtiff->ready->Coding == VIPS_CODING_LABQ )
		wtiff->tls = wtiff->tilew * 3;
	else if( wtiff->bitdepth == 1 )
		wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8;
	else if( wtiff->bitdepth == 2 )
		wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 4 ) / 4;
	else if( wtiff->bitdepth == 4 )
		wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 2 ) / 2;
	else
		wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * 
			wtiff->tilew;

	return( wtiff );
}

/* Convert VIPS LabQ to TIFF LAB. Just take the first three bands.
 */
static void
LabQ2LabC( VipsPel *q, VipsPel *p, int n )
{
	int x;

	for( x = 0; x < n; x++ ) {
		/* Get most significant 8 bits of lab.
		 */
		q[0] = p[0];
		q[1] = p[1];
		q[2] = p[2];

		p += 4;
		q += 3;
	}
}

/* Pack 8 bit VIPS to N bit TIFF.
 */
static void
eightbit2nbit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n )
{
	/* Invert in miniswhite mode.
	 */
	VipsPel mask = wtiff->miniswhite ? 255 : 0;
	int pixel_mask = 8 / wtiff->bitdepth - 1;
	int shift = 8 - wtiff->bitdepth;

	VipsPel bits;
        int x;

	bits = 0;
        for( x = 0; x < n; x++ ) {
		bits <<= wtiff->bitdepth;
		bits |= p[x] >> shift;

		if( (x & pixel_mask) == pixel_mask ) 
			*q++ = bits ^ mask;
        }

	/* Any left-over bits? Need to be left-aligned.
	 */
	if( (x & pixel_mask) != 0 ) {
		/* The number of bits we've collected in bits and must
		 * left-align and flush.
		 */
		int collected_bits = (x & pixel_mask) << (wtiff->bitdepth - 1);

		*q++ = (bits ^ mask) << (8 - collected_bits);
	}
}

/* Swap the sense of the first channel, if necessary. 
 */
#define GREY_LOOP( TYPE, MAX ) { \
	TYPE *p1; \
	TYPE *q1; \
	\
	p1 = (TYPE *) p; \
	q1 = (TYPE *) q; \
	for( x = 0; x < n; x++ ) { \
		if( invert ) \
			q1[0] = MAX - p1[0]; \
		else \
			q1[0] = p1[0]; \
		\
		for( i = 1; i < im->Bands; i++ ) \
			q1[i] = p1[i]; \
		\
		q1 += im->Bands; \
		p1 += im->Bands; \
	} \
}

/* If we're writing a 1 or 2 band image as a greyscale and MINISWHITE, we need
 * to swap the sense of the first band. See tiff2vips.c, greyscale_line() for
 * the opposite conversion.
 */
static void
invert_band0( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n )
{
	VipsImage *im = wtiff->ready;
	gboolean invert = wtiff->miniswhite;

        int x, i;

	switch( im->BandFmt ) {
	case VIPS_FORMAT_UCHAR:
	case VIPS_FORMAT_CHAR:
		GREY_LOOP( guchar, UCHAR_MAX ); 
		break;

	case VIPS_FORMAT_SHORT:
		GREY_LOOP( gshort, SHRT_MAX ); 
		break;

	case VIPS_FORMAT_USHORT:
		GREY_LOOP( gushort, USHRT_MAX ); 
		break;

	case VIPS_FORMAT_INT:
		GREY_LOOP( gint, INT_MAX ); 
		break;

	case VIPS_FORMAT_UINT:
		GREY_LOOP( guint, UINT_MAX ); 
		break;

	case VIPS_FORMAT_FLOAT:
		GREY_LOOP( float, 1.0 ); 
		break;

	case VIPS_FORMAT_DOUBLE:
		GREY_LOOP( double, 1.0 ); 
		break;

	default:
		g_assert_not_reached();
	}
}

/* Convert VIPS LABS to TIFF 16 bit LAB.
 */
static void
LabS2Lab16( VipsPel *q, VipsPel *p, int n, int samples_per_pixel )
{
	short *p1 = (short *) p;
	unsigned short *q1 = (unsigned short *) q;

	int x;

        for( x = 0; x < n; x++ ) {
		int i;

                /* LABS L can be negative.
                 */
                q1[0] = VIPS_LSHIFT_INT( VIPS_MAX( 0, p1[0] ), 1 );

		for( i = 1; i < samples_per_pixel; i++ )
			q1[i] = p1[i];

		q1 += samples_per_pixel;
		p1 += samples_per_pixel;
	}
}

/* Convert VIPS D65 XYZ to TIFF scaled float illuminant-free xyz.
 */
static void
XYZ2tiffxyz( VipsPel *q, VipsPel *p, int n, int samples_per_pixel )
{
	float *p1 = (float *) p;
	float *q1 = (float *) q;

	int x;

        for( x = 0; x < n; x++ ) {
		int i;

                q1[0] = p1[0] / VIPS_D65_X0;
                q1[1] = p1[1] / VIPS_D65_Y0;
                q1[2] = p1[2] / VIPS_D65_Z0;

		for( i = 3; i < samples_per_pixel; i++ )
			q1[i] = p1[i];

		q1 += samples_per_pixel;
		p1 += samples_per_pixel;
	}
}

/* Pack the pixels in @area from @in into a TIFF tile buffer.
 */
static void
wtiff_pack2tiff( Wtiff *wtiff, Layer *layer, 
	VipsRegion *in, VipsRect *area, VipsPel *q )
{
	int y;

	/* JPEG compression can read outside the pixel area for edge tiles. It
	 * always compresses 8x8 blocks, so if the image width or height is
	 * not a multiple of 8, it can look beyond the pixels we will write.
	 *
	 * Black out the tile first to make sure these edge pixels are always
	 * zero.
	 */
	if( wtiff->compression == COMPRESSION_JPEG &&
		(area->width < wtiff->tilew || 
		 area->height < wtiff->tileh) )
		memset( q, 0, TIFFTileSize( layer->tif ) );

	for( y = area->top; y < VIPS_RECT_BOTTOM( area ); y++ ) {
		VipsPel *p = (VipsPel *) VIPS_REGION_ADDR( in, area->left, y );

		if( wtiff->ready->Coding == VIPS_CODING_LABQ )
			LabQ2LabC( q, p, area->width );
		else if( wtiff->bitdepth > 0 )
			eightbit2nbit( wtiff, q, p, area->width );
		else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ )
			XYZ2tiffxyz( q, p, area->width, in->im->Bands );
		else if( (in->im->Bands == 1 || in->im->Bands == 2) && 
			wtiff->miniswhite ) 
			invert_band0( wtiff, q, p, area->width );
		else if( wtiff->ready->BandFmt == VIPS_FORMAT_SHORT &&
			wtiff->ready->Type == VIPS_INTERPRETATION_LABS )
			LabS2Lab16( q, p, area->width, in->im->Bands );
		else
			memcpy( q, p, 
				area->width * 
					VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) );

		q += wtiff->tls;
	}
}

/* Write a set of tiles across the strip.
 */
static int
wtiff_layer_write_tiles( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
{
	VipsImage *im = layer->image;
	VipsRect *area = &strip->valid;

	VipsRect image;
	int x;

	image.left = 0;
	image.top = 0;
	image.width = im->Xsize;
	image.height = im->Ysize;

	for( x = 0; x < im->Xsize; x += wtiff->tilew ) {
		VipsRect tile;

		tile.left = x;
		tile.top = area->top;
		tile.width = wtiff->tilew;
		tile.height = wtiff->tileh;
		vips_rect_intersectrect( &tile, &image, &tile );

#ifdef DEBUG_VERBOSE
		printf( "Writing %dx%d tile at position %dx%d to image %s\n",
			tile.width, tile.height, tile.left, tile.top,
			TIFFFileName( layer->tif ) );
#endif /*DEBUG_VERBOSE*/

		if( wtiff->we_compress ) {
			ttile_t tile_no = TIFFComputeTile( layer->tif,
				tile.left, tile.top, 0, 0 );

			VipsTarget *target;
			int result;
			unsigned char *buffer;
			size_t length;

			target = vips_target_new_to_memory();

			switch( wtiff->compression ) {
			case JP2K_LOSSY:
				/* Sadly chroma subsample seems not to work
				 * for edge tiles in tiff with jp2k
				 * compression, so we always pass FALSE
				 * instead of:
				 *
				 * 	!wtiff->rgbjpeg && wtiff->Q < 90,
				 *
				 * I've verified that the libvips jp2k
				 * encode and decode subsample operations fill
				 * the comps[i].data arrays correctly, so it
				 * seems to be a openjpeg bug.
				 *
				 * FIXME ... try again with openjpeg 2.5,
				 * when that comes.
				 */
				result = vips__foreign_load_jp2k_compress( 
					strip, &tile, target,
					wtiff->tilew, wtiff->tileh,
					!wtiff->rgbjpeg,
				 	// !wtiff->rgbjpeg && wtiff->Q < 90,
					FALSE,
					wtiff->lossless, 
					wtiff->Q );
				break;

			default:
				result = -1;
				g_assert_not_reached();
				break;
			}

			if( result ) {
				g_object_unref( target );
				return( -1 );
			}

			buffer = vips_target_steal( target, &length );

			g_object_unref( target );

			result = TIFFWriteRawTile( layer->tif, tile_no, 
				buffer, length );

			g_free( buffer );
		
			if( result < 0 ) {
				vips_error( "vips2tiff", 
					"%s", _( "TIFF write tile failed" ) );
				return( -1 );
			}
		}
		else {
			/* Have to repack pixels for libtiff.
			 */
			wtiff_pack2tiff( wtiff, 
				layer, strip, &tile, wtiff->tbuf );

			if( TIFFWriteTile( layer->tif, wtiff->tbuf, 
				tile.left, tile.top, 0, 0 ) < 0 ) {
				vips_error( "vips2tiff", 
					"%s", _( "TIFF write tile failed" ) );
				return( -1 );
			}
		}
	}

	return( 0 );
}

/* Write tileh scanlines, less for the last strip.
 */
static int
wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
{
	VipsImage *im = layer->image;
	VipsRect *area = &strip->valid;
	int height = VIPS_MIN( wtiff->tileh, area->height ); 

	int y;

#ifdef DEBUG_VERBOSE
	printf( "wtiff_layer_write_strip: top %d, height %d, file %s\n",
		area->top, height, TIFFFileName( layer->tif ) );
#endif /*DEBUG_VERBOSE*/

	for( y = 0; y < height; y++ ) {
		VipsPel *p = VIPS_REGION_ADDR( strip, 0, area->top + y );

		/* Any repacking necessary.
		 */
		if( im->Coding == VIPS_CODING_LABQ ) {
			LabQ2LabC( wtiff->tbuf, p, im->Xsize );
			p = wtiff->tbuf;
		}
		else if( im->BandFmt == VIPS_FORMAT_SHORT &&
			im->Type == VIPS_INTERPRETATION_LABS ) {
			LabS2Lab16( wtiff->tbuf, p, im->Xsize, im->Bands );
			p = wtiff->tbuf;
		}
		else if( wtiff->input->Type == VIPS_INTERPRETATION_XYZ ) {
			XYZ2tiffxyz( wtiff->tbuf, p, im->Xsize, im->Bands );
			p = wtiff->tbuf;
		}
		else if( wtiff->bitdepth > 0 ) {
			eightbit2nbit( wtiff, wtiff->tbuf, p, im->Xsize );
			p = wtiff->tbuf;
		}
		else if( (im->Bands == 1 || im->Bands == 2) && 
			wtiff->miniswhite ) {
			invert_band0( wtiff, wtiff->tbuf, p, im->Xsize );
			p = wtiff->tbuf;
		}

		if( TIFFWriteScanline( layer->tif, p, area->top + y, 0 ) < 0 ) 
			return( -1 );
	}

	return( 0 );
}

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
layer_strip_shrink( Layer *layer )
{
	Layer *below = layer->below;
	VipsRegion *from = layer->strip;
	VipsRegion *to = below->strip;

	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.
	 */
	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;

		(void) vips_region_shrink_method( from, to, &target,
			layer->wtiff->region_shrink );

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

	return( 0 );
}

/* A new strip has arrived! The strip has at least enough pixels in to 
 * write a line of tiles or a set of scanlines.  
 *
 * - write a line of tiles / set of scanlines
 * - shrink what we can to the layer below
 * - move our strip down by the tile height
 * - copy the overlap with the previous strip
 */
static int
layer_strip_arrived( Layer *layer )
{
	Wtiff *wtiff = layer->wtiff;

	int result;
	VipsRect new_strip;
	VipsRect overlap;
	VipsRect image_area;

	if( wtiff->tile ) 
		result = wtiff_layer_write_tiles( wtiff, layer, layer->strip );
	else
		result = wtiff_layer_write_strip( wtiff, layer, layer->strip );
	if( result )
		return( -1 );

	if( layer->below &&
		layer_strip_shrink( layer ) ) 
		return( -1 );

	/* Position our strip down the image.  
	 *
	 * Expand the strip if necessary to make sure we have an even 
	 * number of lines. 
	 */
	layer->y += wtiff->tileh;
	new_strip.left = 0;
	new_strip.top = layer->y;
	new_strip.width = layer->image->Xsize;
	new_strip.height = wtiff->tileh;

	image_area.left = 0;
	image_area.top = 0;
	image_area.width = layer->image->Xsize;
	image_area.height = layer->image->Ysize;
	vips_rect_intersectrect( &new_strip, &image_area, &new_strip ); 

	if( (new_strip.height & 1) == 1 )
		new_strip.height += 1;

	/* What pixels that we will need do we already have? Save them in 
	 * overlap.
	 */
	vips_rect_intersectrect( &new_strip, &layer->strip->valid, &overlap );
	if( !vips_rect_isempty( &overlap ) ) {
		if( vips_region_buffer( layer->copy, &overlap ) )
			return( -1 );
		vips_region_copy( layer->strip, layer->copy, 
			&overlap, overlap.left, overlap.top );
	}

	if( !vips_rect_isempty( &new_strip ) ) {
		if( vips_region_buffer( layer->strip, &new_strip ) ) 
			return( -1 );

		/* And copy back again.
		 */
		if( !vips_rect_isempty( &overlap ) ) 
			vips_region_copy( layer->copy, layer->strip, 
				&overlap, overlap.left, overlap.top );
	}

	return( 0 );
}

/* Another few scanlines of pixels. We know the scanlines are all within the 
 * current page.
 */
static int
wtiff_write_lines( Wtiff *wtiff, VipsRegion *region, VipsRect *lines )
{
	Layer *layer = wtiff->layer; 
	int page_top = wtiff->page_number * wtiff->page_height;

#ifdef DEBUG_VERBOSE
	printf( "wtiff_write_lines: top %d, height %d\n", 
		lines->top, lines->height );
#endif/*DEBUG_VERBOSE*/

	/* Keep filling the current strip of the top layer. Each time it
	 * fills, write a chunk of pyramid.
	 *
	 * lines is in 
	 */
	for(;;) {
		VipsRect *to = &layer->strip->valid;
		VipsRect target;
		VipsRect page_lines;

		/* The bit of strip that needs filling.
		 */
		target.left = 0;
		target.top = layer->write_y;
		target.width = layer->image->Xsize;
		target.height = to->height;
		vips_rect_intersectrect( &target, to, &target );

		/* region and lines are in world coordinates, we must subtract
		 * the top of the current page to get layer coordinates.
		 */
		page_lines = *lines;
		page_lines.top -= page_top;

		/* Clip against the lines we've been given.
		 */
		vips_rect_intersectrect( &target, &page_lines, &target );

		/* Are we empty? All done.
		 */
		if( vips_rect_isempty( &target ) ) 
			break;

		/* And copy those pixels in.
		 *
		 * FIXME: If the strip fits inside the region we've just 
		 * received, we could skip the copy. Will this happen very
		 * often? Unclear.
		 */
		target.top += page_top;
		vips_region_copy( region, layer->strip, 
			&target, target.left, target.top - page_top );

		layer->write_y += target.height;

		/* We can either fill the strip, 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( layer->write_y == VIPS_RECT_BOTTOM( to ) ||
			layer->write_y == layer->height ) {
			if( layer_strip_arrived( layer ) ) 
				return( -1 );
		}
	}

	return( 0 );
}

/* Copy fields.
 */
#define CopyField( tag, v ) \
	if( TIFFGetField( in, tag, &v ) ) TIFFSetField( out, tag, v )

static int
wtiff_copy_tiles( Wtiff *wtiff, TIFF *out, TIFF *in )
{
	const ttile_t n_tiles = TIFFNumberOfTiles( in );

	tsize_t tile_size;
	tdata_t buf;
	ttile_t i;

	if( wtiff->compression == COMPRESSION_JPEG ) 
		tile_size = TIFFTileSize( in );
	else 
		/* If we will be copying raw tiles we need a buffer large 
		 * enough to hold the largest compressed tile in any page.
		 *
		 * Allocate a buffer 2x the uncompressed tile size ... much 
		 * simpler than searching every page for the largest tile with
		 * TIFFTAG_TILEBYTECOUNTS.
		 */
		tile_size = 2 * wtiff->tls * wtiff->tileh;
	buf = vips_malloc( NULL, tile_size );

	for( i = 0; i < n_tiles; i++ ) {
		tsize_t len;

		/* If this is a JPEG-compressed TIFF, we need to decompress 
		 * and recompress, since tiles are actually written in several 
		 * places (coefficients go in the tile, huffman tables go 
		 * elsewhere).
		 *
		 * For all other compression types, we can just use 
		 * TIFFReadRawTile()/TIFFWriteRawTile().
		 */
		if( wtiff->compression == COMPRESSION_JPEG ) {
			len = TIFFReadEncodedTile( in, i, buf, tile_size );
			if( len <= 0 ||
				TIFFWriteEncodedTile( out, i, buf, len ) < 0 ) {
				g_free( buf );
				return( -1 );
			}
		}
		else {
			len = TIFFReadRawTile( in, i, buf, tile_size );
			if( len <= 0 ||
				TIFFWriteRawTile( out, i, buf, len ) < 0 ) {
				g_free( buf );
				return( -1 );
			}
		}
	}

	g_free( buf );

	return( 0 );
}

/* Copy a TIFF file ... we know we wrote it, so just copy the tags we know 
 * we might have set.
 */
static int
wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in )
{
	guint32 ui32;
	guint16 ui16;
	guint16 ui16_2;
	float f;
	guint16 *a;

	/* All the fields we might have set.
	 */
	CopyField( TIFFTAG_IMAGEWIDTH, ui32 );
	CopyField( TIFFTAG_IMAGELENGTH, ui32 );
	CopyField( TIFFTAG_PLANARCONFIG, ui16 );
	CopyField( TIFFTAG_ORIENTATION, ui16 );
	CopyField( TIFFTAG_XRESOLUTION, f );
	CopyField( TIFFTAG_YRESOLUTION, f );
	CopyField( TIFFTAG_RESOLUTIONUNIT, ui16 );
	CopyField( TIFFTAG_COMPRESSION, ui16 );
	CopyField( TIFFTAG_SAMPLESPERPIXEL, ui16 );
	CopyField( TIFFTAG_BITSPERSAMPLE, ui16 );
	CopyField( TIFFTAG_PHOTOMETRIC, ui16 );
	CopyField( TIFFTAG_ORIENTATION, ui16 );
	CopyField( TIFFTAG_TILEWIDTH, ui32 );
	CopyField( TIFFTAG_TILELENGTH, ui32 );
	CopyField( TIFFTAG_ROWSPERSTRIP, ui32 );
	CopyField( TIFFTAG_SUBFILETYPE, ui32 );

	if( TIFFGetField( in, TIFFTAG_EXTRASAMPLES, &ui16, &a ) ) 
		TIFFSetField( out, TIFFTAG_EXTRASAMPLES, ui16, a );

	if( TIFFGetField( in, TIFFTAG_PAGENUMBER, &ui16, &ui16_2 ) ) 
		TIFFSetField( out, TIFFTAG_PAGENUMBER, ui16, ui16_2 );

	/* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it.
	 * Set explicitly from Wtiff.
	 */
	if( wtiff->compression == COMPRESSION_JPEG ) {
		TIFFSetField( out, TIFFTAG_JPEGQUALITY, wtiff->Q );

		/* Only for three-band, 8-bit images.
		 */
		if( wtiff->ready->Bands == 3 &&
			wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR ) { 
			/* Enable rgb->ycbcr conversion in the jpeg write. 
			 */
			if( !wtiff->rgbjpeg &&
				wtiff->Q < 90 ) 
				TIFFSetField( out, 
					TIFFTAG_JPEGCOLORMODE, 
						JPEGCOLORMODE_RGB );

			/* And we want ycbcr expanded to rgb on read. Otherwise
			 * TIFFTileSize() will give us the size of a chrominance
			 * subsampled tile.
			 */
			TIFFSetField( in, 
				TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
		}
	}

#ifdef HAVE_TIFF_COMPRESSION_WEBP
	/* More pseudotags we can't copy.
	 */
	if( wtiff->compression == COMPRESSION_WEBP ) {
		TIFFSetField( out, TIFFTAG_WEBP_LEVEL, wtiff->Q );
		TIFFSetField( out, TIFFTAG_WEBP_LOSSLESS, wtiff->lossless );
	}
	if( wtiff->compression == COMPRESSION_ZSTD ) {
		TIFFSetField( out, TIFFTAG_ZSTD_LEVEL, wtiff->level );
		if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) 
			TIFFSetField( out, 
				TIFFTAG_PREDICTOR, wtiff->predictor );
	}
#endif /*HAVE_TIFF_COMPRESSION_WEBP*/

	if( (wtiff->compression == COMPRESSION_ADOBE_DEFLATE ||
		wtiff->compression == COMPRESSION_LZW) &&
		wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE ) 
		TIFFSetField( out, TIFFTAG_PREDICTOR, wtiff->predictor );

	/* We can't copy profiles or xmp :( Set again from wtiff.
	 */
	if( !wtiff->strip ) 
		if( wtiff_embed_profile( wtiff, out ) ||
			wtiff_embed_xmp( wtiff, out ) ||
			wtiff_embed_iptc( wtiff, out ) ||
			wtiff_embed_photoshop( wtiff, out ) ||
			wtiff_embed_imagedescription( wtiff, out ) )
			return( -1 );

	if( wtiff_copy_tiles( wtiff, out, in ) )
		return( -1 );

	return( 0 );
}

/* Append all of the layers we wrote to the output.
 */
static int
wtiff_gather( Wtiff *wtiff )
{
	Layer *layer;

#ifdef DEBUG
	printf( "wtiff_gather:\n" );
#endif /*DEBUG*/

	if( wtiff->layer &&
		wtiff->layer->below )
		for( layer = wtiff->layer->below; layer; 
			layer = layer->below ) {
			VipsSource *source;
			TIFF *in;

#ifdef DEBUG
			printf( "appending layer sub = %d ...\n", layer->sub );
#endif /*DEBUG*/

			if( !(source = 
				vips_source_new_from_target( layer->target )) ) 
				return( -1 );

			if( !(in = vips__tiff_openin_source( source )) ) {
				VIPS_UNREF( source );
				return( -1 );
			}

			VIPS_UNREF( source );

			if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) {
				TIFFClose( in );
				return( -1 );
			}

			TIFFClose( in );

			if( !TIFFWriteDirectory( wtiff->layer->tif ) ) 
				return( -1 );
		}

	return( 0 );
}

static int
wtiff_page_start( Wtiff *wtiff )
{
#ifdef DEBUG
	printf( "wtiff_page_start: page %d\n", wtiff->page_number ); 
#endif /*DEBUG*/

	/* 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, 
		wtiff->ready->Xsize, wtiff->page_height );

	/* Fill all the layers and write the TIFF headers.
	 */
	if( wtiff_allocate_layers( wtiff ) ) 
		return( -1 );

	/* In ifd mode, we write the pyramid layers as subdirectories of this
	 * page.
	 */
	if( wtiff->subifd ) {
		int n_layers;
		Layer *p;

#ifdef DEBUG
		printf( "wtiff_write_page: OME pyr mode\n" ); 
#endif /*DEBUG*/

		for( n_layers = 0, p = wtiff->layer->below; p; p = p->below )
			n_layers += 1;

		if( n_layers > 0 ) {
                        toff_t *subifd_offsets;

                        /* 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.
                         */
                        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 );
		}
	}

	return( 0 );
}

static int
wtiff_page_end( Wtiff *wtiff )
{
#ifdef DEBUG
	printf( "wtiff_page_end: page %d\n", wtiff->page_number ); 
#endif /*DEBUG*/

	if( !TIFFWriteDirectory( wtiff->layer->tif ) ) 
		return( -1 );

	/* Append any pyr layers, if necessary.
	 */
	if( wtiff->layer->below ) {
		Layer *layer;

		/* Free any lower pyramid resources ... this will 
		 * TIFFClose() (but not delete) the smaller layers 
		 * ready for us to read from them again.
		 */
		layer_free_all( wtiff->layer->below );

		/* Append smaller layers to the main file.
		 */
		if( wtiff_gather( wtiff ) ) 
			return( -1 );

		/* unref all the lower targets.
		 */
		for( layer = wtiff->layer->below; layer; layer = layer->below )
			VIPS_UNREF( layer->target );

		/* ... ready for the next page. 
		 */
		wtiff->layer->below = NULL;
	}

	wtiff->page_number += 1;

	return( 0 );
}

/* A strip of pixels has come in from libvips. Split these strips into pages,
 * and run the page start / end code.
 */
static int
wtiff_sink_disc_strip( VipsRegion *region, VipsRect *area, void *a )
{
	Wtiff *wtiff = ( Wtiff *) a;

	VipsRect pixels;

#ifdef DEBUG_VERBOSE
	printf( "wtiff_sink_disc_strip: top %d, height %d\n", 
	     	area->top, area->height ); 
#endif /*DEBUG_VERBOSE*/

	g_assert( area->width == wtiff->ready->Xsize );

	/* Loop down this as we write scanlines into pages.
	 */
	pixels = *area;

	do {
		VipsRect page;
		VipsRect lines;

		/* The rect for the current page.
		 */
		page.left = 0;
		page.top = wtiff->page_height * wtiff->page_number;
		page.width = wtiff->ready->Xsize;
		page.height = wtiff->page_height;

		/* The scanlines we have for this page.
		 */
		vips_rect_intersectrect( &page, &pixels, &lines );

		/* At the top of the page? Run the page start code.
		 */
		if( lines.top == page.top &&
			wtiff_page_start( wtiff ) ) 
			return( -1 );

		/* Write the scanlines into the page.
		 */
		if( wtiff_write_lines( wtiff, region, &lines ) )
			return( -1 );

		/* Hit the end of the page? Run the page end code.
		 */
		if( VIPS_RECT_BOTTOM( &page ) == VIPS_RECT_BOTTOM( &lines ) &&
			wtiff_page_end( wtiff ) ) 
			return( -1 );

		/* Remove the pixels we've written and loop if we have some
		 * still to write.
		 */
		pixels.top += lines.height;
		pixels.height -= lines.height;
	} while( !vips_rect_isempty( &pixels ) );

	return( 0 );
}

int 
vips__tiff_write_target( VipsImage *input, VipsTarget *target,
	VipsForeignTiffCompression compression, int Q, 
	VipsForeignTiffPredictor predictor,
	const char *profile,
	gboolean tile, int tile_width, int tile_height,
	gboolean pyramid,
	int bitdepth,
	gboolean miniswhite,
	VipsForeignTiffResunit resunit, double xres, double yres,
	gboolean bigtiff,
	gboolean rgbjpeg,
	gboolean properties, gboolean strip, 
	VipsRegionShrink region_shrink,
	int level, 
	gboolean lossless,
	VipsForeignDzDepth depth,
	gboolean subifd,
	gboolean premultiply,
	int page_height )
{
	Wtiff *wtiff;

	vips__tiff_init();

	if( !(wtiff = wtiff_new( input, target, 
		compression, Q, predictor, profile,
                tile, tile_width, tile_height, pyramid, bitdepth,
		miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, 
		properties, strip, region_shrink, level, lossless, depth,
		subifd, premultiply, page_height )) )
		return( -1 );

	if( vips_sink_disc( wtiff->ready, wtiff_sink_disc_strip, wtiff ) ) {
		wtiff_free( wtiff );
		return( -1 );
	}

	wtiff_free( wtiff );

	return( 0 );
}

#endif /*HAVE_TIFF*/
