2288 lines
57 KiB
C
2288 lines
57 KiB
C
/* 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 );
|
|
|
|
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;
|
|
toff_t *subifd_offsets;
|
|
Layer *p;
|
|
|
|
#ifdef DEBUG
|
|
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->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 );
|
|
}
|
|
|
|
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*/
|