We were not checking for alignment correctly in multi-page tiff reads. Thanks petoor. See: https://github.com/libvips/pyvips/issues/172
2706 lines
64 KiB
C
2706 lines
64 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.
|
|
* -----------------------------
|
|
* Modifications for VIPS: Kirk Martinez 1994
|
|
* 22/11/94 JC
|
|
* - more general
|
|
* - memory leaks fixed
|
|
* 20/3/95 JC
|
|
* - TIFF error handler added
|
|
* - read errors detected correctly
|
|
*
|
|
* Modified to handle LAB in tiff format.
|
|
* It convert LAB-tiff format to VIPS_INTERPRETATION_LABQ in vips format.
|
|
* Copyright July-1995 Ahmed Abbood.
|
|
*
|
|
*
|
|
* 19/9/95 JC
|
|
* - now calls TIFFClose ... stupid
|
|
* 25/1/96 JC
|
|
* - typo on MINISBLACK ...
|
|
* 7/4/97 JC
|
|
* - completely redone for TIFF 6
|
|
* - now full baseline TIFF 6 reader, and does CIELAB as well
|
|
* 11/4/97 JC
|
|
* - added partial read for tiled images
|
|
* 23/4/97 JC
|
|
* - extra subsample parameter
|
|
* - im_istiffpyramid() added
|
|
* 5/12/97 JC
|
|
* - if loading YCbCr, convert to VIPS_CODING_LABQ
|
|
* 1/5/98 JC
|
|
* - now reads 16-bit greyscale and RGB
|
|
* 26/10/98 JC
|
|
* - now used "rb" mode on systems that need binary open
|
|
* 12/11/98 JC
|
|
* - no sub-sampling if sub == 1
|
|
* 26/2/99 JC
|
|
* - ooops, else missing for subsample stuff above
|
|
* 2/10/99 JC
|
|
* - tiled 16-bit greyscale read was broken
|
|
* - added mutex for TIFFReadTile() calls
|
|
* 11/5/00 JC
|
|
* - removed TIFFmalloc/TIFFfree usage
|
|
* 23/4/01 JC
|
|
* - HAVE_TIFF turns on TIFF goodness
|
|
* 24/5/01 JC
|
|
* - im_tiff2vips_header() added
|
|
* 11/7/01 JC
|
|
* - subsample now in input filename
|
|
* - ... and it's a page number (from 0) instead
|
|
* 21/8/02 JC
|
|
* - now reads CMYK
|
|
* - hmm, dpi -> ppm conversion was wrong!
|
|
* 10/9/02 JC
|
|
* - oops, handle TIFF errors better
|
|
* 2/12/02 JC
|
|
* - reads 8-bit RGBA
|
|
* 12/12/02 JC
|
|
* - reads 16-bit LAB
|
|
* 13/2/03 JC
|
|
* - pixels/cm res read was wrong
|
|
* 17/11/03 Andrey Kiselev
|
|
* - read 32-bit float greyscale and rgb
|
|
* 5/4/04
|
|
* - better handling of edge tiles (thanks Ruven)
|
|
* 16/4/04
|
|
* - cleanup
|
|
* - added broken tile read mode
|
|
* 18/5/04 Andrey Kiselev
|
|
* - better no resolution diagnostic
|
|
* 26/5/04
|
|
* - reads 16 bit RGBA
|
|
* 28/7/04
|
|
* - arrg, 16bit RGB was broken, thanks haida
|
|
* 26/11/04
|
|
* - add a TIFF warning handler, stops occasional libMagick exceptions
|
|
* 9/3/05
|
|
* - load 32-bit float LAB
|
|
* 8/4/05
|
|
* - onebit read no longer reads one byte too many on multiple of 8 wide
|
|
* images
|
|
* 22/6/05
|
|
* - 16 bit LAB read was broken
|
|
* 9/9/05
|
|
* - read any ICCPROFILE tag
|
|
* 8/5/06
|
|
* - set RGB16 and GREY16 Type
|
|
* 21/5/06
|
|
* - use external im_tile_cache() operation for great code shrinkage
|
|
* - less RAM usage too, esp. with >1 CPU
|
|
* - should be slightly faster
|
|
* - removed 'broken' read option
|
|
* 18/7/07 Andrey Kiselev
|
|
* - remove "b" option on TIFFOpen()
|
|
* 9/4/08
|
|
* - set VIPS_META_RESOLUTION_UNIT
|
|
* 17/4/08
|
|
* - allow CMYKA (thanks Doron)
|
|
* 17/7/08
|
|
* - convert YCbCr to RGB on read (thanks Ole)
|
|
* 15/8/08
|
|
* - reorganise for image format system
|
|
* 20/12/08
|
|
* - dont read with mmap: no performance advantage with libtiff, chews up
|
|
* VM wastefully
|
|
* 13/1/09
|
|
* - read strip-wise, not scanline-wise ... works with more compression /
|
|
* subsampling schemes (esp. subsampled YCbCr), and it's a bit quicker
|
|
* 4/2/10
|
|
* - gtkdoc
|
|
* 12/12/10
|
|
* - oops, we can just memcpy() now heh
|
|
* - avoid unpacking via buffers if we can: either read a tile directly
|
|
* into the output region, or writeline directly from the tiff buffer
|
|
* 4/4/11
|
|
* - argh int/uint mixup for rows_per_strip, thanks Bubba
|
|
* 21/4/11
|
|
* - palette read can do 1,2,4,8 bits per sample
|
|
* - palette read can do mono images
|
|
* 5/12/11
|
|
* - make into a simple function call ready to be wrapped as a new-style
|
|
* VipsForeign class
|
|
* 18/2/12
|
|
* - switch to sequential read
|
|
* - remove the lock ... tilecache does this for us
|
|
* 3/6/12
|
|
* - always offer THINSTRIP ... later stages can ask for something more
|
|
* relaxed if they wish
|
|
* 7/6/12
|
|
* - clip rows_per_strip down to image height to avoid overflows for huge
|
|
* values (thanks Nicolas)
|
|
* - better error msg for not PLANARCONFIG_CONTIG images
|
|
* 16/9/13
|
|
* - support alpha for 8, 16 and 32-bit greyscale images, thanks Robert
|
|
* 17/9/13
|
|
* - support separate planes for strip read
|
|
* - big cleanup
|
|
* - support for many more formats, eg. 32-bit int etc.
|
|
* 11/4/14
|
|
* - support 16 bits per sample palette images
|
|
* - palette images can have an alpha
|
|
* 22/4/14
|
|
* - add read from buffer
|
|
* 30/4/14
|
|
* - 1/2/4 bit palette images can have alpha
|
|
* 27/10/14 Lovell
|
|
* - better istiff detector spots bigtiff
|
|
* 3/12/14
|
|
* - read any XMP metadata
|
|
* 19/1/15
|
|
* - try to handle 8-bit colormaps
|
|
* 26/2/15
|
|
* - close the read down early for a header read ... this saves an
|
|
* fd during file read, handy for large numbers of input images
|
|
* 29/9/15
|
|
* - load IPTC metadata
|
|
* - load photoshop metadata
|
|
* 21/12/15
|
|
* - load TIFFTAG_IMAGEDESCRIPTION
|
|
* 11/4/16
|
|
* - non-int RGB images are tagged as scRGB ... matches photoshop
|
|
* convention
|
|
* 26/5/16
|
|
* - add autorotate support
|
|
* 17/11/16
|
|
* - add multi-page read
|
|
* 17/1/17
|
|
* - invalidate operation on read error
|
|
* 27/1/17
|
|
* - if rows_per_strip is large, read with scanline API instead
|
|
* 9/5/17
|
|
* - remove missing res warning
|
|
* 19/5/17
|
|
* - page > 0 could break edge tiles or strips
|
|
* 26/4/18
|
|
* - add n-pages metadata item
|
|
* 21/7/18
|
|
* - check for non-byte-multiple bits_per_sample [HongxuChen]
|
|
* 16/8/18
|
|
* - shut down the input file as soon as we can [kleisauke]
|
|
* 28/3/19 omira-sch
|
|
* - better buffer sizing
|
|
* - ban chroma-subsampled, non-jpg compressed images
|
|
* 7/6/19
|
|
* - istiff reads the first directory rather than just testing the magic
|
|
* number, so it ignores more TIFF-like, but not TIFF images
|
|
* 17/10/19
|
|
* - switch to source input
|
|
* 18/11/19
|
|
* - support ASSOCALPHA in any alpha band
|
|
* 27/1/20
|
|
* - read logluv images as XYZ
|
|
* 11/4/20 petoor
|
|
* - better handling of aligned reads in multipage tiffs
|
|
*/
|
|
|
|
/*
|
|
|
|
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 <vips/intl.h>
|
|
|
|
#ifdef HAVE_TIFF
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <vips/vips.h>
|
|
#include <vips/internal.h>
|
|
#include <vips/thread.h>
|
|
|
|
#include "pforeign.h"
|
|
#include "tiff.h"
|
|
|
|
/* What we read from the tiff dir to set our read strategy. For multipage
|
|
* read, we need to read and compare lots of these, so it needs to be broken
|
|
* out as a separate thing.
|
|
*/
|
|
typedef struct _RtiffHeader {
|
|
uint32 width;
|
|
uint32 height;
|
|
int samples_per_pixel;
|
|
int bits_per_sample;
|
|
int photometric_interpretation;
|
|
int inkset;
|
|
int sample_format;
|
|
gboolean separate;
|
|
int orientation;
|
|
/* If there's a premultiplied alpha, the band we need to
|
|
* unpremultiply with. -1 for no unpremultiplication.
|
|
*/
|
|
int alpha_band;
|
|
uint16 compression;
|
|
|
|
/* Result of TIFFIsTiled().
|
|
*/
|
|
gboolean tiled;
|
|
|
|
/* Fields for tiled images, as returned by libtiff.
|
|
*/
|
|
uint32 tile_width;
|
|
uint32 tile_height;
|
|
tsize_t tile_size;
|
|
tsize_t tile_row_size;
|
|
|
|
/* Fields for strip images, as returned by libtiff.
|
|
*/
|
|
uint32 rows_per_strip;
|
|
tsize_t strip_size;
|
|
tsize_t scanline_size;
|
|
int number_of_strips;
|
|
|
|
/* If read_scanlinewise is TRUE, the strips are too large to read in a
|
|
* single lump and we will use the scanline API.
|
|
*/
|
|
gboolean read_scanlinewise;
|
|
|
|
/* Strip read geometry. Number of lines we read at once (whole strip
|
|
* or 1) and size of the buffer we read to (a scanline, or a strip in
|
|
* size).
|
|
*/
|
|
uint32 read_height;
|
|
tsize_t read_size;
|
|
|
|
/* Scale factor to get absolute cd/m2 from XYZ.
|
|
*/
|
|
double stonits;
|
|
} RtiffHeader;
|
|
|
|
/* Scanline-type process function.
|
|
*/
|
|
struct _Rtiff;
|
|
typedef void (*scanline_process_fn)( struct _Rtiff *,
|
|
VipsPel *q, VipsPel *p, int n, void *client );
|
|
|
|
/* Stuff we track during a read.
|
|
*/
|
|
typedef struct _Rtiff {
|
|
/* Parameters.
|
|
*/
|
|
VipsSource *source;
|
|
VipsImage *out;
|
|
int page;
|
|
int n;
|
|
gboolean autorotate;
|
|
|
|
/* The TIFF we read.
|
|
*/
|
|
TIFF *tiff;
|
|
|
|
/* Number of pages (directories) in image.
|
|
*/
|
|
int n_pages;
|
|
|
|
/* The current page we have set.
|
|
*/
|
|
int current_page;
|
|
|
|
/* Process for this image type.
|
|
*/
|
|
scanline_process_fn sfn;
|
|
void *client;
|
|
|
|
/* Set this is the processfn is just doing a memcpy.
|
|
*/
|
|
gboolean memcpy;
|
|
|
|
/* Geometry as read from the TIFF header. This is read for the first
|
|
* page, and equal for all other pages.
|
|
*/
|
|
RtiffHeader header;
|
|
|
|
/* Hold a single strip or tile, possibly just an image plane.
|
|
*/
|
|
tdata_t plane_buf;
|
|
|
|
/* Hold a plane-assembled strip or tile ... a set of samples_per_pixel
|
|
* strips or tiles interleaved.
|
|
*/
|
|
tdata_t contig_buf;
|
|
|
|
/* The Y we are reading at. Used to verify strip read is sequential.
|
|
*/
|
|
int y_pos;
|
|
} Rtiff;
|
|
|
|
/* Test for field exists.
|
|
*/
|
|
static int
|
|
tfexists( TIFF *tif, ttag_t tag )
|
|
{
|
|
uint32 a, b;
|
|
|
|
if( TIFFGetField( tif, tag, &a, &b ) )
|
|
return( 1 );
|
|
else
|
|
return( 0 );
|
|
}
|
|
|
|
/* Get a uint32 field.
|
|
*/
|
|
static int
|
|
tfget32( TIFF *tif, ttag_t tag, uint32 *out )
|
|
{
|
|
uint32 fld;
|
|
|
|
if( !TIFFGetFieldDefaulted( tif, tag, &fld ) ) {
|
|
vips_error( "tiff2vips",
|
|
_( "required field %d missing" ), tag );
|
|
return( 0 );
|
|
}
|
|
|
|
*out = fld;
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
/* Get a uint16 field.
|
|
*/
|
|
static int
|
|
tfget16( TIFF *tif, ttag_t tag, int *out )
|
|
{
|
|
uint16 fld;
|
|
|
|
if( !TIFFGetFieldDefaulted( tif, tag, &fld ) ) {
|
|
vips_error( "tiff2vips",
|
|
_( "required field %d missing" ), tag );
|
|
return( 0 );
|
|
}
|
|
|
|
*out = fld;
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
static int
|
|
get_resolution( TIFF *tiff, VipsImage *out )
|
|
{
|
|
float x, y;
|
|
int ru;
|
|
|
|
if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) &&
|
|
TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) &&
|
|
tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) {
|
|
switch( ru ) {
|
|
case RESUNIT_NONE:
|
|
break;
|
|
|
|
case RESUNIT_INCH:
|
|
/* In pixels-per-inch ... convert to mm.
|
|
*/
|
|
x /= 10.0 * 2.54;
|
|
y /= 10.0 * 2.54;
|
|
vips_image_set_string( out,
|
|
VIPS_META_RESOLUTION_UNIT, "in" );
|
|
break;
|
|
|
|
case RESUNIT_CENTIMETER:
|
|
/* In pixels-per-centimetre ... convert to mm.
|
|
*/
|
|
x /= 10.0;
|
|
y /= 10.0;
|
|
vips_image_set_string( out,
|
|
VIPS_META_RESOLUTION_UNIT, "cm" );
|
|
break;
|
|
|
|
default:
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "unknown resolution unit" ) );
|
|
return( -1 );
|
|
}
|
|
}
|
|
else {
|
|
/* We used to warn about missing res data, but it happens so
|
|
* often and is so harmless, why bother.
|
|
*/
|
|
x = 1.0;
|
|
y = 1.0;
|
|
}
|
|
|
|
out->Xres = x;
|
|
out->Yres = y;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
get_sample_format( TIFF *tiff )
|
|
{
|
|
int sample_format;
|
|
uint16 v;
|
|
|
|
sample_format = SAMPLEFORMAT_INT;
|
|
|
|
if( TIFFGetFieldDefaulted( tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) {
|
|
/* Some images have this set to void, bizarre.
|
|
*/
|
|
if( v == SAMPLEFORMAT_VOID )
|
|
v = SAMPLEFORMAT_UINT;
|
|
|
|
sample_format = v;
|
|
}
|
|
|
|
return( sample_format );
|
|
}
|
|
|
|
static int
|
|
get_orientation( TIFF *tiff )
|
|
{
|
|
int orientation;
|
|
uint16 v;
|
|
|
|
orientation = ORIENTATION_TOPLEFT;
|
|
|
|
if( TIFFGetFieldDefaulted( tiff, TIFFTAG_ORIENTATION, &v ) )
|
|
/* Can have mad values.
|
|
*/
|
|
orientation = VIPS_CLIP( 1, v, 8 );
|
|
|
|
return( orientation );
|
|
}
|
|
|
|
/* Can be called many times.
|
|
*/
|
|
static void
|
|
rtiff_free( Rtiff *rtiff )
|
|
{
|
|
VIPS_FREEF( TIFFClose, rtiff->tiff );
|
|
VIPS_UNREF( rtiff->source );
|
|
}
|
|
|
|
static void
|
|
rtiff_close_cb( VipsObject *object, Rtiff *rtiff )
|
|
{
|
|
rtiff_free( rtiff );
|
|
}
|
|
|
|
static void
|
|
rtiff_minimise_cb( VipsImage *image, Rtiff *rtiff )
|
|
{
|
|
/* We must not minimised tiled images. These can be read from many
|
|
* threads, and this minimise handler is not inside the lock that our
|
|
* tilecache is using to guarantee single-threaded access to our
|
|
* source.
|
|
*/
|
|
if( !rtiff->header.tiled &&
|
|
rtiff->source )
|
|
vips_source_minimise( rtiff->source );
|
|
}
|
|
|
|
static Rtiff *
|
|
rtiff_new( VipsSource *source, VipsImage *out,
|
|
int page, int n, gboolean autorotate )
|
|
{
|
|
Rtiff *rtiff;
|
|
|
|
if( !(rtiff = VIPS_NEW( out, Rtiff )) )
|
|
return( NULL );
|
|
|
|
g_object_ref( source );
|
|
rtiff->source = source;
|
|
rtiff->out = out;
|
|
rtiff->page = page;
|
|
rtiff->n = n;
|
|
rtiff->autorotate = autorotate;
|
|
rtiff->tiff = NULL;
|
|
rtiff->n_pages = 0;
|
|
rtiff->current_page = -1;
|
|
rtiff->sfn = NULL;
|
|
rtiff->client = NULL;
|
|
rtiff->memcpy = FALSE;
|
|
rtiff->plane_buf = NULL;
|
|
rtiff->contig_buf = NULL;
|
|
rtiff->y_pos = 0;
|
|
|
|
g_signal_connect( out, "close",
|
|
G_CALLBACK( rtiff_close_cb ), rtiff );
|
|
g_signal_connect( out, "minimise",
|
|
G_CALLBACK( rtiff_minimise_cb ), rtiff );
|
|
|
|
if( rtiff->page < 0 ||
|
|
rtiff->page > 1000000 ) {
|
|
vips_error( "tiff2vips", _( "bad page number %d" ),
|
|
rtiff->page );
|
|
return( NULL );
|
|
}
|
|
|
|
/* We allow n == -1, meaning all pages. It gets swapped for a real n
|
|
* value when we open the TIFF.
|
|
*/
|
|
if( rtiff->n != -1 &&
|
|
(rtiff->n < 1 || rtiff->n > 1000000) ) {
|
|
vips_error( "tiff2vips", _( "bad number of pages %d" ),
|
|
rtiff->n );
|
|
return( NULL );
|
|
}
|
|
|
|
if( !(rtiff->tiff = vips__tiff_openin_source( source )) )
|
|
return( NULL );
|
|
|
|
return( rtiff );
|
|
}
|
|
|
|
static int
|
|
rtiff_strip_read( Rtiff *rtiff, int strip, tdata_t buf )
|
|
{
|
|
tsize_t length;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_strip_read: reading strip %d\n", strip );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
if( rtiff->header.read_scanlinewise )
|
|
length = TIFFReadScanline( rtiff->tiff,
|
|
buf, strip, (tsize_t) -1 );
|
|
else
|
|
length = TIFFReadEncodedStrip( rtiff->tiff,
|
|
strip, buf, (tsize_t) -1 );
|
|
|
|
if( length == -1 ) {
|
|
vips_foreign_load_invalidate( rtiff->out );
|
|
vips_error( "tiff2vips", "%s", _( "read error" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_set_page( Rtiff *rtiff, int page )
|
|
{
|
|
if( rtiff->current_page != page ) {
|
|
#ifdef DEBUG
|
|
printf( "rtiff_set_page: selecting page %d\n", page );
|
|
#endif /*DEBUG*/
|
|
|
|
if( !TIFFSetDirectory( rtiff->tiff, page ) ) {
|
|
vips_error( "tiff2vips",
|
|
_( "TIFF does not contain page %d" ), page );
|
|
return( -1 );
|
|
}
|
|
|
|
rtiff->current_page = page;
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_n_pages( Rtiff *rtiff )
|
|
{
|
|
int n;
|
|
|
|
(void) TIFFSetDirectory( rtiff->tiff, 0 );
|
|
|
|
for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ )
|
|
;
|
|
|
|
(void) TIFFSetDirectory( rtiff->tiff, rtiff->current_page );
|
|
|
|
#ifdef DEBUG
|
|
printf( "rtiff_n_pages: found %d pages\n", n );
|
|
#endif /*DEBUG*/
|
|
|
|
return( n );
|
|
}
|
|
|
|
static int
|
|
rtiff_check_samples( Rtiff *rtiff, int samples_per_pixel )
|
|
{
|
|
if( rtiff->header.samples_per_pixel != samples_per_pixel ) {
|
|
vips_error( "tiff2vips",
|
|
_( "not %d bands" ), samples_per_pixel );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Check n and n+1 so we can have an alpha.
|
|
*/
|
|
static int
|
|
rtiff_check_min_samples( Rtiff *rtiff, int samples_per_pixel )
|
|
{
|
|
if( rtiff->header.samples_per_pixel < samples_per_pixel ) {
|
|
vips_error( "tiff2vips",
|
|
_( "not at least %d samples per pixel" ),
|
|
samples_per_pixel );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Only allow samples which are whole bytes in size.
|
|
*/
|
|
static int
|
|
rtiff_non_fractional( Rtiff *rtiff )
|
|
{
|
|
if( rtiff->header.bits_per_sample % 8 != 0 ||
|
|
rtiff->header.bits_per_sample == 0 ) {
|
|
vips_error( "tiff2vips", "%s", _( "samples_per_pixel "
|
|
"not a whole number of bytes" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_check_interpretation( Rtiff *rtiff, int photometric_interpretation )
|
|
{
|
|
if( rtiff->header.photometric_interpretation !=
|
|
photometric_interpretation ) {
|
|
vips_error( "tiff2vips",
|
|
_( "not photometric interpretation %d" ),
|
|
photometric_interpretation );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_check_bits( Rtiff *rtiff, int bits_per_sample )
|
|
{
|
|
if( rtiff->header.bits_per_sample != bits_per_sample ) {
|
|
vips_error( "tiff2vips",
|
|
_( "not %d bits per sample" ), bits_per_sample );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_check_bits_palette( Rtiff *rtiff )
|
|
{
|
|
if( rtiff->header.bits_per_sample != 16 &&
|
|
rtiff->header.bits_per_sample != 8 &&
|
|
rtiff->header.bits_per_sample != 4 &&
|
|
rtiff->header.bits_per_sample != 2 &&
|
|
rtiff->header.bits_per_sample != 1 ) {
|
|
vips_error( "tiff2vips",
|
|
_( "%d bits per sample palette image not supported" ),
|
|
rtiff->header.bits_per_sample );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static VipsBandFormat
|
|
rtiff_guess_format( Rtiff *rtiff )
|
|
{
|
|
int bits_per_sample = rtiff->header.bits_per_sample;
|
|
int sample_format = rtiff->header.sample_format;
|
|
|
|
switch( bits_per_sample ) {
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
case 8:
|
|
if( sample_format == SAMPLEFORMAT_INT )
|
|
return( VIPS_FORMAT_CHAR );
|
|
if( sample_format == SAMPLEFORMAT_UINT )
|
|
return( VIPS_FORMAT_UCHAR );
|
|
break;
|
|
|
|
case 16:
|
|
if( sample_format == SAMPLEFORMAT_INT )
|
|
return( VIPS_FORMAT_SHORT );
|
|
if( sample_format == SAMPLEFORMAT_UINT )
|
|
return( VIPS_FORMAT_USHORT );
|
|
break;
|
|
|
|
case 32:
|
|
if( sample_format == SAMPLEFORMAT_INT )
|
|
return( VIPS_FORMAT_INT );
|
|
if( sample_format == SAMPLEFORMAT_UINT )
|
|
return( VIPS_FORMAT_UINT );
|
|
if( sample_format == SAMPLEFORMAT_IEEEFP )
|
|
return( VIPS_FORMAT_FLOAT );
|
|
break;
|
|
|
|
case 64:
|
|
if( sample_format == SAMPLEFORMAT_IEEEFP )
|
|
return( VIPS_FORMAT_DOUBLE );
|
|
if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
|
|
return( VIPS_FORMAT_COMPLEX );
|
|
break;
|
|
|
|
case 128:
|
|
if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
|
|
return( VIPS_FORMAT_DPCOMPLEX );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
vips_error( "tiff2vips", "%s", _( "unsupported tiff image type\n" ) );
|
|
|
|
return( VIPS_FORMAT_NOTSET );
|
|
}
|
|
|
|
/* Per-scanline process function for VIPS_CODING_LABQ.
|
|
*/
|
|
static void
|
|
rtiff_labpack_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
int x;
|
|
|
|
for( x = 0; x < n; x++ ) {
|
|
q[0] = p[0];
|
|
q[1] = p[1];
|
|
q[2] = p[2];
|
|
q[3] = 0;
|
|
|
|
q += 4;
|
|
p += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* Read an 8-bit LAB image.
|
|
*/
|
|
static int
|
|
rtiff_parse_labpack( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_min_samples( rtiff, 3 ) ||
|
|
rtiff_check_bits( rtiff, 8 ) ||
|
|
rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
|
|
return( -1 );
|
|
|
|
out->Bands = 4;
|
|
out->BandFmt = VIPS_FORMAT_UCHAR;
|
|
out->Coding = VIPS_CODING_LABQ;
|
|
out->Type = VIPS_INTERPRETATION_LAB;
|
|
|
|
rtiff->sfn = rtiff_labpack_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
|
|
/* Per-scanline process function for 8-bit VIPS_CODING_LAB to 16-bit LabS with
|
|
* alpha.
|
|
*/
|
|
static void
|
|
rtiff_lab_with_alpha_line( Rtiff *rtiff,
|
|
VipsPel *q, VipsPel *p, int n, void *dummy )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
unsigned char *p1;
|
|
short *q1;
|
|
int x;
|
|
|
|
p1 = (unsigned char *) p;
|
|
q1 = (short *) q;
|
|
for( x = 0; x < n; x++ ) {
|
|
int i;
|
|
|
|
q1[0] = ((unsigned int) p1[0]) * 32767 / 255;
|
|
q1[1] = ((short) p1[1]) << 8;
|
|
q1[2] = ((short) p1[2]) << 8;
|
|
|
|
for( i = 3; i < samples_per_pixel; i++ )
|
|
q1[i] = (p1[i] << 8) + p1[i];
|
|
|
|
q1 += samples_per_pixel;
|
|
p1 += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* Read an 8-bit LAB image with alpha bands into 16-bit LabS.
|
|
*/
|
|
static int
|
|
rtiff_parse_lab_with_alpha( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_min_samples( rtiff, 4 ) ||
|
|
rtiff_check_bits( rtiff, 8 ) ||
|
|
rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
|
|
return( -1 );
|
|
|
|
out->Bands = rtiff->header.samples_per_pixel;
|
|
out->BandFmt = VIPS_FORMAT_SHORT;
|
|
out->Coding = VIPS_CODING_NONE;
|
|
out->Type = VIPS_INTERPRETATION_LABS;
|
|
|
|
rtiff->sfn = rtiff_lab_with_alpha_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Per-scanline process function for LABS.
|
|
*/
|
|
static void
|
|
rtiff_labs_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
unsigned short *p1;
|
|
short *q1;
|
|
int x;
|
|
int i;
|
|
|
|
p1 = (unsigned short *) p;
|
|
q1 = (short *) q;
|
|
for( x = 0; x < n; x++ ) {
|
|
/* We use signed int16 for L.
|
|
*/
|
|
q1[0] = p1[0] >> 1;
|
|
|
|
for( i = 1; i < samples_per_pixel; i++ )
|
|
q1[i] = p1[i];
|
|
|
|
q1 += samples_per_pixel;
|
|
p1 += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* Read a 16-bit LAB image.
|
|
*/
|
|
static int
|
|
rtiff_parse_labs( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_min_samples( rtiff, 3 ) ||
|
|
rtiff_check_bits( rtiff, 16 ) ||
|
|
rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
|
|
return( -1 );
|
|
|
|
out->Bands = rtiff->header.samples_per_pixel;
|
|
out->BandFmt = VIPS_FORMAT_SHORT;
|
|
out->Coding = VIPS_CODING_NONE;
|
|
out->Type = VIPS_INTERPRETATION_LABS;
|
|
|
|
rtiff->sfn = rtiff_labs_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* libtiff delivers logluv as illuminant-free 0-1 XYZ in 3 x float.
|
|
*/
|
|
static void
|
|
rtiff_logluv_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
float *p1;
|
|
float *q1;
|
|
int x;
|
|
int i;
|
|
|
|
p1 = (float *) p;
|
|
q1 = (float *) q;
|
|
for( x = 0; x < n; x++ ) {
|
|
q1[0] = VIPS_D65_X0 * p1[0];
|
|
q1[1] = VIPS_D65_Y0 * p1[1];
|
|
q1[2] = VIPS_D65_Z0 * p1[2];
|
|
|
|
for( i = 3; i < samples_per_pixel; i++ )
|
|
q1[i] = p1[i];
|
|
|
|
q1 += samples_per_pixel;
|
|
p1 += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* LOGLUV images arrive from libtiff as float xyz.
|
|
*/
|
|
static int
|
|
rtiff_parse_logluv( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_min_samples( rtiff, 3 ) ||
|
|
rtiff_check_interpretation( rtiff, PHOTOMETRIC_LOGLUV ) )
|
|
return( -1 );
|
|
|
|
out->Bands = rtiff->header.samples_per_pixel;
|
|
out->BandFmt = VIPS_FORMAT_FLOAT;
|
|
out->Coding = VIPS_CODING_NONE;
|
|
out->Type = VIPS_INTERPRETATION_XYZ;
|
|
|
|
rtiff->sfn = rtiff_logluv_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Per-scanline process function for 1 bit images.
|
|
*/
|
|
static void
|
|
rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg )
|
|
{
|
|
int photometric_interpretation =
|
|
rtiff->header.photometric_interpretation;
|
|
|
|
int x, i, z;
|
|
VipsPel bits;
|
|
|
|
int black = photometric_interpretation == PHOTOMETRIC_MINISBLACK ?
|
|
0 : 255;
|
|
int white = black ^ 0xff;
|
|
|
|
/* (sigh) how many times have I written this?
|
|
*/
|
|
x = 0;
|
|
for( i = 0; i < (n >> 3); i++ ) {
|
|
bits = (VipsPel) p[i];
|
|
|
|
for( z = 0; z < 8; z++ ) {
|
|
q[x] = (bits & 128) ? white : black;
|
|
bits <<= 1;
|
|
x += 1;
|
|
}
|
|
}
|
|
|
|
/* Do last byte in line.
|
|
*/
|
|
if( n & 7 ) {
|
|
bits = p[i];
|
|
for( z = 0; z < (n & 7); z++ ) {
|
|
q[x + z] = (bits & 128) ? white : black;
|
|
bits <<= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Read a 1-bit TIFF image.
|
|
*/
|
|
static int
|
|
rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_samples( rtiff, 1 ) ||
|
|
rtiff_check_bits( rtiff, 1 ) )
|
|
return( -1 );
|
|
|
|
out->Bands = 1;
|
|
out->BandFmt = VIPS_FORMAT_UCHAR;
|
|
out->Coding = VIPS_CODING_NONE;
|
|
out->Type = VIPS_INTERPRETATION_B_W;
|
|
|
|
rtiff->sfn = rtiff_onebit_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* 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 < samples_per_pixel; i++ ) \
|
|
q1[i] = p1[i]; \
|
|
\
|
|
q1 += samples_per_pixel; \
|
|
p1 += samples_per_pixel; \
|
|
} \
|
|
}
|
|
|
|
/* Per-scanline process function for greyscale images.
|
|
*/
|
|
static void
|
|
rtiff_greyscale_line( Rtiff *rtiff,
|
|
VipsPel *q, VipsPel *p, int n, void *client )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
int photometric_interpretation =
|
|
rtiff->header.photometric_interpretation;
|
|
VipsBandFormat format = rtiff_guess_format( rtiff );
|
|
|
|
/* Swapping black and white doesn't make sense for the signed formats.
|
|
*/
|
|
gboolean invert =
|
|
photometric_interpretation == PHOTOMETRIC_MINISWHITE &&
|
|
vips_band_format_isuint( format );
|
|
|
|
int x, i;
|
|
|
|
switch( format ) {
|
|
case VIPS_FORMAT_CHAR:
|
|
GREY_LOOP( guchar, 0 );
|
|
break;
|
|
|
|
case VIPS_FORMAT_UCHAR:
|
|
GREY_LOOP( guchar, UCHAR_MAX );
|
|
break;
|
|
|
|
case VIPS_FORMAT_SHORT:
|
|
GREY_LOOP( gshort, 0 );
|
|
break;
|
|
|
|
case VIPS_FORMAT_USHORT:
|
|
GREY_LOOP( gushort, USHRT_MAX );
|
|
break;
|
|
|
|
case VIPS_FORMAT_INT:
|
|
GREY_LOOP( gint, 0 );
|
|
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();
|
|
}
|
|
}
|
|
|
|
/* Read a grey-scale TIFF image. We have to invert the first band if
|
|
* PHOTOMETRIC_MINISBLACK is set.
|
|
*/
|
|
static int
|
|
rtiff_parse_greyscale( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
if( rtiff_check_min_samples( rtiff, 1 ) ||
|
|
rtiff_non_fractional( rtiff ) )
|
|
return( -1 );
|
|
|
|
out->Bands = rtiff->header.samples_per_pixel;
|
|
out->BandFmt = rtiff_guess_format( rtiff );
|
|
if( out->BandFmt == VIPS_FORMAT_NOTSET )
|
|
return( -1 );
|
|
out->Coding = VIPS_CODING_NONE;
|
|
|
|
if( rtiff->header.bits_per_sample == 16 )
|
|
out->Type = VIPS_INTERPRETATION_GREY16;
|
|
else
|
|
out->Type = VIPS_INTERPRETATION_B_W;
|
|
|
|
/* rtiff_greyscale_line() doesn't do complex.
|
|
*/
|
|
if( vips_check_noncomplex( "tiff2vips", out ) )
|
|
return( -1 );
|
|
|
|
rtiff->sfn = rtiff_greyscale_line;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
typedef struct {
|
|
/* LUTs mapping image indexes to RGB.
|
|
*/
|
|
VipsPel *red8;
|
|
VipsPel *green8;
|
|
VipsPel *blue8;
|
|
|
|
guint16 *red16;
|
|
guint16 *green16;
|
|
guint16 *blue16;
|
|
|
|
/* All maps equal, so we write mono.
|
|
*/
|
|
gboolean mono;
|
|
} PaletteRead;
|
|
|
|
/* 1/2/4 bit samples with an 8-bit palette.
|
|
*/
|
|
static void
|
|
rtiff_palette_line_bit( Rtiff *rtiff,
|
|
VipsPel *q, VipsPel *p, int n, void *client )
|
|
{
|
|
PaletteRead *read = (PaletteRead *) client;
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
int bits_per_sample = rtiff->header.bits_per_sample;
|
|
|
|
int bit;
|
|
VipsPel data;
|
|
int x;
|
|
|
|
bit = 0;
|
|
data = 0;
|
|
for( x = 0; x < n * samples_per_pixel; x++ ) {
|
|
int i;
|
|
|
|
if( bit <= 0 ) {
|
|
data = *p++;
|
|
bit = 8;
|
|
}
|
|
|
|
i = data >> (8 - bits_per_sample);
|
|
data <<= bits_per_sample;
|
|
bit -= bits_per_sample;
|
|
|
|
/* The first band goes through the LUT, subsequent bands are
|
|
* left-justified and copied.
|
|
*/
|
|
if( x % samples_per_pixel == 0 ) {
|
|
if( read->mono )
|
|
*q++ = read->red8[i];
|
|
else {
|
|
q[0] = read->red8[i];
|
|
q[1] = read->green8[i];
|
|
q[2] = read->blue8[i];
|
|
q += 3;
|
|
}
|
|
}
|
|
else
|
|
*q++ = VIPS_LSHIFT_INT( i, 8 - bits_per_sample );
|
|
}
|
|
}
|
|
|
|
/* 8-bit samples with an 8-bit palette.
|
|
*/
|
|
static void
|
|
rtiff_palette_line8( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n,
|
|
void *client )
|
|
{
|
|
PaletteRead *read = (PaletteRead *) client;
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
int x;
|
|
int s;
|
|
|
|
for( x = 0; x < n; x++ ) {
|
|
int i = p[0];
|
|
|
|
if( read->mono )
|
|
q[0] = read->red8[i];
|
|
else {
|
|
q[0] = read->red8[i];
|
|
q[1] = read->green8[i];
|
|
q[2] = read->blue8[i];
|
|
q += 2;
|
|
}
|
|
|
|
for( s = 1; s < samples_per_pixel; s++ )
|
|
q[s] = p[s];
|
|
|
|
q += samples_per_pixel;
|
|
p += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* 16-bit samples with 16-bit data in the palette.
|
|
*/
|
|
static void
|
|
rtiff_palette_line16( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n,
|
|
void *client )
|
|
{
|
|
PaletteRead *read = (PaletteRead *) client;
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
guint16 *p16, *q16;
|
|
int x;
|
|
int s;
|
|
|
|
q16 = (guint16 *) q;
|
|
p16 = (guint16 *) p;
|
|
|
|
for( x = 0; x < n; x++ ) {
|
|
int i = p16[0];
|
|
|
|
if( read->mono )
|
|
q16[0] = read->red16[i];
|
|
else {
|
|
q16[0] = read->red16[i];
|
|
q16[1] = read->green16[i];
|
|
q16[2] = read->blue16[i];
|
|
q16 += 2;
|
|
}
|
|
|
|
for( s = 1; s < samples_per_pixel; s++ )
|
|
q16[s] = p16[s];
|
|
|
|
q16 += samples_per_pixel;
|
|
p16 += samples_per_pixel;
|
|
}
|
|
}
|
|
|
|
/* Read a palette-ised TIFF image.
|
|
*/
|
|
static int
|
|
rtiff_parse_palette( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
int bits_per_sample = rtiff->header.bits_per_sample;
|
|
|
|
int len;
|
|
PaletteRead *read;
|
|
int i;
|
|
|
|
if( rtiff_check_bits_palette( rtiff ) ||
|
|
rtiff_check_min_samples( rtiff, 1 ) )
|
|
return( -1 );
|
|
len = 1 << bits_per_sample;
|
|
|
|
if( !(read = VIPS_NEW( out, PaletteRead )) ||
|
|
!(read->red8 = VIPS_ARRAY( out, len, VipsPel )) ||
|
|
!(read->green8 = VIPS_ARRAY( out, len, VipsPel )) ||
|
|
!(read->blue8 = VIPS_ARRAY( out, len, VipsPel )) )
|
|
return( -1 );
|
|
|
|
/* Get maps, convert to 8-bit data.
|
|
*/
|
|
if( !TIFFGetField( rtiff->tiff,
|
|
TIFFTAG_COLORMAP,
|
|
&read->red16, &read->green16, &read->blue16 ) ) {
|
|
vips_error( "tiff2vips", "%s", _( "bad colormap" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Old-style colourmaps were 8-bit. If all the top bytes are zero,
|
|
* assume we have one of these.
|
|
*
|
|
* See: https://github.com/libvips/libvips/issues/220
|
|
*/
|
|
for( i = 0; i < len; i++ )
|
|
if( (read->red16[i] >> 8) |
|
|
(read->green16[i] >> 8) |
|
|
(read->blue16[i] >> 8) )
|
|
break;
|
|
if( i < len )
|
|
for( i = 0; i < len; i++ ) {
|
|
read->red8[i] = read->red16[i] >> 8;
|
|
read->green8[i] = read->green16[i] >> 8;
|
|
read->blue8[i] = read->blue16[i] >> 8;
|
|
}
|
|
else {
|
|
g_warning( "%s", _( "assuming 8-bit palette" ) );
|
|
|
|
for( i = 0; i < len; i++ ) {
|
|
read->red8[i] = read->red16[i] & 0xff;
|
|
read->green8[i] = read->green16[i] & 0xff;
|
|
read->blue8[i] = read->blue16[i] & 0xff;
|
|
}
|
|
}
|
|
|
|
/* Are all the maps equal? We have a mono image.
|
|
*/
|
|
read->mono = TRUE;
|
|
for( i = 0; i < len; i++ )
|
|
if( read->red16[i] != read->green16[i] ||
|
|
read->green16[i] != read->blue16[i] ) {
|
|
read->mono = FALSE;
|
|
break;
|
|
}
|
|
|
|
/* There's a TIFF extension, INDEXED, that is the preferred way to
|
|
* encode mono palette images, but few applications support it. So we
|
|
* just search the colormap.
|
|
*/
|
|
|
|
if( bits_per_sample <= 8 )
|
|
out->BandFmt = VIPS_FORMAT_UCHAR;
|
|
else
|
|
out->BandFmt = VIPS_FORMAT_USHORT;
|
|
out->Coding = VIPS_CODING_NONE;
|
|
|
|
if( read->mono ) {
|
|
out->Bands = samples_per_pixel;
|
|
if( bits_per_sample <= 8 )
|
|
out->Type = VIPS_INTERPRETATION_B_W;
|
|
else
|
|
out->Type = VIPS_INTERPRETATION_GREY16;
|
|
}
|
|
else {
|
|
out->Bands = samples_per_pixel + 2;
|
|
if( bits_per_sample <= 8 )
|
|
out->Type = VIPS_INTERPRETATION_sRGB;
|
|
else
|
|
out->Type = VIPS_INTERPRETATION_RGB16;
|
|
}
|
|
|
|
rtiff->client = read;
|
|
if( bits_per_sample < 8 )
|
|
rtiff->sfn = rtiff_palette_line_bit;
|
|
else if( bits_per_sample == 8 )
|
|
rtiff->sfn = rtiff_palette_line8;
|
|
else if( bits_per_sample == 16 )
|
|
rtiff->sfn = rtiff_palette_line16;
|
|
else
|
|
g_assert_not_reached();
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Per-scanline process function when we just need to copy.
|
|
*/
|
|
static void
|
|
rtiff_memcpy_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
|
|
{
|
|
VipsImage *im = (VipsImage *) client;
|
|
size_t len = n * VIPS_IMAGE_SIZEOF_PEL( im );
|
|
|
|
memcpy( q, p, len );
|
|
}
|
|
|
|
/* Read a regular multiband image where we can just copy pixels from the tiff
|
|
* buffer.
|
|
*/
|
|
static int
|
|
rtiff_parse_copy( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
int photometric_interpretation =
|
|
rtiff->header.photometric_interpretation;
|
|
int inkset = rtiff->header.inkset;
|
|
|
|
if( rtiff_non_fractional( rtiff ) )
|
|
return( -1 );
|
|
|
|
out->Bands = samples_per_pixel;
|
|
out->BandFmt = rtiff_guess_format( rtiff );
|
|
if( out->BandFmt == VIPS_FORMAT_NOTSET )
|
|
return( -1 );
|
|
out->Coding = VIPS_CODING_NONE;
|
|
|
|
if( samples_per_pixel >= 3 &&
|
|
(photometric_interpretation == PHOTOMETRIC_RGB ||
|
|
photometric_interpretation == PHOTOMETRIC_YCBCR) ) {
|
|
if( out->BandFmt == VIPS_FORMAT_USHORT )
|
|
out->Type = VIPS_INTERPRETATION_RGB16;
|
|
else if( !vips_band_format_isint( out->BandFmt ) )
|
|
/* Most float images use 0 - 1 for black - white.
|
|
* Photoshop uses 0 - 1 and no gamma.
|
|
*/
|
|
out->Type = VIPS_INTERPRETATION_scRGB;
|
|
else
|
|
out->Type = VIPS_INTERPRETATION_sRGB;
|
|
}
|
|
else if( samples_per_pixel >= 3 &&
|
|
photometric_interpretation == PHOTOMETRIC_CIELAB )
|
|
out->Type = VIPS_INTERPRETATION_LAB;
|
|
else if( photometric_interpretation == PHOTOMETRIC_SEPARATED &&
|
|
samples_per_pixel >= 4 &&
|
|
inkset == INKSET_CMYK )
|
|
out->Type = VIPS_INTERPRETATION_CMYK;
|
|
else
|
|
out->Type = VIPS_INTERPRETATION_MULTIBAND;
|
|
|
|
rtiff->sfn = rtiff_memcpy_line;
|
|
rtiff->client = out;
|
|
|
|
/* We expand YCBCR images to RGB using JPEGCOLORMODE_RGB, and this
|
|
* means we need a slightly larger read buffer for the edge pixels. In
|
|
* turn, this means we can't just memcpy to libvips regions.
|
|
*/
|
|
rtiff->memcpy = photometric_interpretation != PHOTOMETRIC_YCBCR;
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
typedef int (*reader_fn)( Rtiff *rtiff, VipsImage *out );
|
|
|
|
/* We have a range of output paths. Look at the tiff header and try to
|
|
* route the input image to the best output path.
|
|
*/
|
|
static reader_fn
|
|
rtiff_pick_reader( Rtiff *rtiff )
|
|
{
|
|
int bits_per_sample = rtiff->header.bits_per_sample;
|
|
int photometric_interpretation =
|
|
rtiff->header.photometric_interpretation;
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
|
|
if( photometric_interpretation == PHOTOMETRIC_CIELAB ) {
|
|
if( bits_per_sample == 8 ) {
|
|
if( samples_per_pixel > 3 )
|
|
return( rtiff_parse_lab_with_alpha );
|
|
else
|
|
return( rtiff_parse_labpack );
|
|
}
|
|
if( bits_per_sample == 16 )
|
|
return( rtiff_parse_labs );
|
|
}
|
|
|
|
if( photometric_interpretation == PHOTOMETRIC_LOGLUV )
|
|
return( rtiff_parse_logluv );
|
|
|
|
if( photometric_interpretation == PHOTOMETRIC_MINISWHITE ||
|
|
photometric_interpretation == PHOTOMETRIC_MINISBLACK ) {
|
|
if( bits_per_sample == 1 )
|
|
return( rtiff_parse_onebit );
|
|
else
|
|
return( rtiff_parse_greyscale );
|
|
}
|
|
|
|
if( photometric_interpretation == PHOTOMETRIC_PALETTE )
|
|
return( rtiff_parse_palette );
|
|
|
|
return( rtiff_parse_copy );
|
|
}
|
|
|
|
/* Set the header on @out from our rtiff. rtiff_header_read() has already been
|
|
* called.
|
|
*/
|
|
static int
|
|
rtiff_set_header( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
uint32 data_length;
|
|
void *data;
|
|
|
|
/* Request YCbCr expansion. libtiff complains if you do this for
|
|
* non-jpg images.
|
|
*/
|
|
if( rtiff->header.compression == COMPRESSION_JPEG )
|
|
TIFFSetField( rtiff->tiff,
|
|
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
|
|
|
|
/* Ask for LOGLUV as 3 x float XYZ.
|
|
*/
|
|
if( rtiff->header.photometric_interpretation == PHOTOMETRIC_LOGLUV ) {
|
|
TIFFSetField( rtiff->tiff,
|
|
TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
|
|
|
|
vips_image_set_double( out, "stonits", rtiff->header.stonits );
|
|
}
|
|
|
|
out->Xsize = rtiff->header.width;
|
|
out->Ysize = rtiff->header.height * rtiff->n;
|
|
|
|
VIPS_SETSTR( out->filename,
|
|
vips_connection_filename( VIPS_CONNECTION( rtiff->source ) ) );
|
|
|
|
if( rtiff->n > 1 )
|
|
vips_image_set_int( out,
|
|
VIPS_META_PAGE_HEIGHT, rtiff->header.height );
|
|
|
|
vips_image_set_int( out, VIPS_META_N_PAGES, rtiff->n_pages );
|
|
|
|
/* Even though we could end up serving tiled data, always hint
|
|
* THINSTRIP, since we're quite happy doing that too, and it could need
|
|
* a lot less memory.
|
|
*/
|
|
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
|
|
|
|
#ifdef DEBUG
|
|
printf( "rtiff_set_header: header.samples_per_pixel = %d\n",
|
|
rtiff->header.samples_per_pixel );
|
|
printf( "rtiff_set_header: header.bits_per_sample = %d\n",
|
|
rtiff->header.bits_per_sample );
|
|
printf( "rtiff_set_header: header.sample_format = %d\n",
|
|
rtiff->header.sample_format );
|
|
printf( "rtiff_set_header: header.orientation = %d\n",
|
|
rtiff->header.orientation );
|
|
#endif /*DEBUG*/
|
|
|
|
/* We have a range of output paths. Look at the tiff header and try to
|
|
* route the input image to the best output path.
|
|
*/
|
|
if( rtiff_pick_reader( rtiff )( rtiff, out ) )
|
|
return( -1 );
|
|
|
|
/* Read any ICC profile.
|
|
*/
|
|
if( TIFFGetField( rtiff->tiff,
|
|
TIFFTAG_ICCPROFILE, &data_length, &data ) ) {
|
|
vips_image_set_blob_copy( out,
|
|
VIPS_META_ICC_NAME, data, data_length );
|
|
}
|
|
|
|
/* Read any XMP metadata.
|
|
*/
|
|
if( TIFFGetField( rtiff->tiff,
|
|
TIFFTAG_XMLPACKET, &data_length, &data ) ) {
|
|
vips_image_set_blob_copy( out,
|
|
VIPS_META_XMP_NAME, data, data_length );
|
|
}
|
|
|
|
/* Read any IPTC metadata.
|
|
*/
|
|
if( TIFFGetField( rtiff->tiff,
|
|
TIFFTAG_RICHTIFFIPTC, &data_length, &data ) ) {
|
|
vips_image_set_blob_copy( out,
|
|
VIPS_META_IPTC_NAME, data, data_length );
|
|
|
|
/* Older versions of libvips used this misspelt name :-( attach
|
|
* under this name too for compatibility.
|
|
*/
|
|
vips_image_set_blob_copy( out, "ipct-data", data, data_length );
|
|
}
|
|
|
|
/* Read any photoshop metadata.
|
|
*/
|
|
if( TIFFGetField( rtiff->tiff,
|
|
TIFFTAG_PHOTOSHOP, &data_length, &data ) ) {
|
|
vips_image_set_blob_copy( out,
|
|
VIPS_META_PHOTOSHOP_NAME, data, data_length );
|
|
}
|
|
|
|
/* IMAGEDESCRIPTION often has useful metadata.
|
|
*/
|
|
if( TIFFGetField( rtiff->tiff, TIFFTAG_IMAGEDESCRIPTION, &data ) ) {
|
|
/* libtiff makes sure that data is null-terminated and contains
|
|
* no embedded null characters.
|
|
*/
|
|
vips_image_set_string( out,
|
|
VIPS_META_IMAGEDESCRIPTION, (char *) data );
|
|
}
|
|
|
|
if( get_resolution( rtiff->tiff, out ) )
|
|
return( -1 );
|
|
|
|
/* Set the "orientation" tag. This is picked up later by autorot, if
|
|
* requested.
|
|
*/
|
|
vips_image_set_int( out,
|
|
VIPS_META_ORIENTATION, rtiff->header.orientation );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Allocate a tile buffer. Have one of these for each thread so we can unpack
|
|
* to vips in parallel.
|
|
*/
|
|
static void *
|
|
rtiff_seq_start( VipsImage *out, void *a, void *b )
|
|
{
|
|
Rtiff *rtiff = (Rtiff *) a;
|
|
tdata_t *buf;
|
|
|
|
if( !(buf = vips_malloc( NULL, rtiff->header.tile_size )) )
|
|
return( NULL );
|
|
|
|
return( (void *) buf );
|
|
}
|
|
|
|
static int
|
|
rtiff_read_tile( Rtiff *rtiff, tdata_t *buf, int x, int y )
|
|
{
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_read_tile: x = %d, y = %d\n", x, y );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
if( TIFFReadTile( rtiff->tiff, buf, x, y, 0, 0 ) < 0 ) {
|
|
vips_foreign_load_invalidate( rtiff->out );
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Paint a tile from the file. This is a
|
|
* special-case for when a region is exactly a tiff tile, and pixels need no
|
|
* conversion. In this case, libtiff can read tiles directly to our output
|
|
* region.
|
|
*/
|
|
static int
|
|
rtiff_fill_region_aligned( VipsRegion *out,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
Rtiff *rtiff = (Rtiff *) a;
|
|
VipsRect *r = &out->valid;
|
|
int page_height = rtiff->header.height;
|
|
int page_no = r->top / page_height;
|
|
int page_y = r->top % page_height;
|
|
|
|
g_assert( (r->left % rtiff->header.tile_width) == 0 );
|
|
g_assert( (r->top % rtiff->header.tile_height) == 0 );
|
|
g_assert( r->width == rtiff->header.tile_width );
|
|
g_assert( r->height == rtiff->header.tile_height );
|
|
g_assert( VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) );
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_fill_region_aligned:\n" );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* Read that tile directly into the vips tile.
|
|
*/
|
|
if( rtiff_set_page( rtiff, rtiff->page + page_no ) ||
|
|
rtiff_read_tile( rtiff,
|
|
(tdata_t *) VIPS_REGION_ADDR( out, r->left, r->top ),
|
|
r->left, page_y ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Loop over the output region, painting in tiles from the file.
|
|
*/
|
|
static int
|
|
rtiff_fill_region_unaligned( VipsRegion *out,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
tdata_t *buf = (tdata_t *) seq;
|
|
Rtiff *rtiff = (Rtiff *) a;
|
|
int tile_width = rtiff->header.tile_width;
|
|
int tile_height = rtiff->header.tile_height;
|
|
int page_height = rtiff->header.height;
|
|
int tile_row_size = rtiff->header.tile_row_size;
|
|
VipsRect *r = &out->valid;
|
|
|
|
int x, y, z;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_fill_region_unaligned:\n" );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
y = 0;
|
|
while( y < r->height ) {
|
|
VipsRect tile, page, hit;
|
|
|
|
/* Not necessary, but it stops static analyzers complaining
|
|
* about a used-before-set.
|
|
*/
|
|
tile.height = 0;
|
|
|
|
x = 0;
|
|
while( x < r->width ) {
|
|
/* page_no is within this toilet roll image, not tiff
|
|
* file page number ... add the number of the start
|
|
* page to get that.
|
|
*/
|
|
int page_no = (r->top + y) / page_height;
|
|
int page_y = (r->top + y) % page_height;
|
|
|
|
/* Coordinate of the tile on this page that xy falls in.
|
|
*/
|
|
int xs = ((r->left + x) / tile_width) * tile_width;
|
|
int ys = (page_y / tile_height) * tile_height;
|
|
|
|
if( rtiff_set_page( rtiff, rtiff->page + page_no ) ||
|
|
rtiff_read_tile( rtiff, buf, xs, ys ) )
|
|
return( -1 );
|
|
|
|
/* Position of tile on the page.
|
|
*/
|
|
tile.left = xs;
|
|
tile.top = ys;
|
|
tile.width = tile_width;
|
|
tile.height = tile_height;
|
|
|
|
/* It'll be clipped by this page.
|
|
*/
|
|
page.left = 0;
|
|
page.top = 0;
|
|
page.width = rtiff->header.width;
|
|
page.height = rtiff->header.height;
|
|
vips_rect_intersectrect( &tile, &page, &tile );
|
|
|
|
/* To image coordinates.
|
|
*/
|
|
tile.top += page_no * page_height;
|
|
|
|
/* And clip again by this region.
|
|
*/
|
|
vips_rect_intersectrect( &tile, r, &hit );
|
|
|
|
/* We are inside a tilecache, so requests will always
|
|
* be aligned left-right to tile boundaries.
|
|
*
|
|
* this is not true vertically for toilet-roll images.
|
|
*/
|
|
g_assert( hit.left == tile.left );
|
|
|
|
/* Unpack to VIPS format.
|
|
* Just unpack the section of the tile we need.
|
|
*/
|
|
for( z = 0; z < hit.height; z++ ) {
|
|
VipsPel *p = (VipsPel *) buf +
|
|
(hit.top - tile.top + z) *
|
|
tile_row_size;
|
|
VipsPel *q = VIPS_REGION_ADDR( out,
|
|
hit.left, hit.top + z );
|
|
|
|
rtiff->sfn( rtiff,
|
|
q, p, hit.width, rtiff->client );
|
|
}
|
|
|
|
x += tile.width;
|
|
}
|
|
|
|
y += tile.height;
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Loop over the output region, painting in tiles from the file.
|
|
*/
|
|
static int
|
|
rtiff_fill_region( VipsRegion *out,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
Rtiff *rtiff = (Rtiff *) a;
|
|
int tile_width = rtiff->header.tile_width;
|
|
int tile_height = rtiff->header.tile_height;
|
|
int page_width = rtiff->header.width;
|
|
int page_height = rtiff->header.height;
|
|
VipsRect *r = &out->valid;
|
|
int page_no = r->top / page_height;
|
|
int page_y = r->top % page_height;
|
|
|
|
VipsGenerateFn generate;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_fill_region: left = %d, top = %d, "
|
|
"width = %d, height = %d\n",
|
|
r->left, r->top, r->width, r->height );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* Special case: we are filling a single cache tile exactly sized to
|
|
* match the tiff tile, and we have no repacking to do for this format.
|
|
*
|
|
* If we are not on the first page, pages must be a multiple of the
|
|
* tile size of we'll miss alignment.
|
|
*/
|
|
if( (page_no == 0 || page_height % tile_height == 0) &&
|
|
r->left % tile_width == 0 &&
|
|
r->top % tile_height == 0 &&
|
|
r->width == tile_width &&
|
|
r->height == tile_height &&
|
|
r->left + tile_width <= page_width &&
|
|
page_y + tile_height <= page_height &&
|
|
VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) )
|
|
generate = rtiff_fill_region_aligned;
|
|
else
|
|
generate = rtiff_fill_region_unaligned;
|
|
|
|
VIPS_GATE_START( "rtiff_fill_region: work" );
|
|
|
|
if( generate( out, seq, a, b, stop ) ) {
|
|
VIPS_GATE_STOP( "rtiff_fill_region: work" );
|
|
return( -1 );
|
|
}
|
|
|
|
VIPS_GATE_STOP( "rtiff_fill_region: work" );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_seq_stop( void *seq, void *a, void *b )
|
|
{
|
|
vips_free( seq );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Auto-rotate handling.
|
|
*/
|
|
static int
|
|
rtiff_autorotate( Rtiff *rtiff, VipsImage *in, VipsImage **out )
|
|
{
|
|
VipsAngle angle = vips_autorot_get_angle( in );
|
|
|
|
if( rtiff->autorotate &&
|
|
angle != VIPS_ANGLE_D0 ) {
|
|
/* Need to copy to memory or disc, we have to stay seq.
|
|
*/
|
|
const guint64 image_size = VIPS_IMAGE_SIZEOF_IMAGE( in );
|
|
const guint64 disc_threshold = vips_get_disc_threshold();
|
|
|
|
VipsImage *im;
|
|
VipsImage *x;
|
|
|
|
if( image_size > disc_threshold )
|
|
im = vips_image_new_temp_file( "%s.v" );
|
|
else
|
|
im = vips_image_new_memory();
|
|
|
|
if( vips_image_write( in, im ) ) {
|
|
g_object_unref( im );
|
|
return( -1 );
|
|
}
|
|
|
|
if( vips_rot( im, &x, angle, NULL ) ) {
|
|
g_object_unref( im );
|
|
return( -1 );
|
|
}
|
|
g_object_unref( im );
|
|
im = x;
|
|
|
|
if( vips_copy( im, out, NULL ) ) {
|
|
g_object_unref( im );
|
|
return( -1 );
|
|
}
|
|
g_object_unref( im );
|
|
|
|
/* We must remove the tag to prevent accidental
|
|
* double rotations.
|
|
*/
|
|
vips_autorot_remove_angle( *out );
|
|
}
|
|
else {
|
|
*out = in;
|
|
g_object_ref( in );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Unpremultiply associative alpha, if any.
|
|
*/
|
|
static int
|
|
rtiff_unpremultiply( Rtiff *rtiff, VipsImage *in, VipsImage **out )
|
|
{
|
|
if( rtiff->header.alpha_band != -1 ) {
|
|
VipsImage *x;
|
|
|
|
if(
|
|
vips_unpremultiply( in, &x,
|
|
"alpha_band", rtiff->header.alpha_band,
|
|
NULL ) ||
|
|
vips_cast( x, out, in->BandFmt, NULL ) ) {
|
|
g_object_unref( x );
|
|
return( -1 );
|
|
}
|
|
g_object_unref( x );
|
|
}
|
|
else {
|
|
*out = in;
|
|
g_object_ref( in );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Tile-type TIFF reader core - pass in a per-tile transform. Generate into
|
|
* the im and do it all partially.
|
|
*/
|
|
static int
|
|
rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
int tile_width = rtiff->header.tile_width;
|
|
int tile_height = rtiff->header.tile_height;
|
|
VipsImage **t = (VipsImage **)
|
|
vips_object_local_array( VIPS_OBJECT( out ), 4 );
|
|
|
|
#ifdef DEBUG
|
|
printf( "tiff2vips: rtiff_read_tilewise\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
/* I don't have a sample images for tiled + separate, ban it for now.
|
|
*/
|
|
if( rtiff->header.separate ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "tiled separate planes not supported" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Read to this image, then cache to out, see below.
|
|
*/
|
|
t[0] = vips_image_new();
|
|
|
|
if( rtiff_set_header( rtiff, t[0] ) )
|
|
return( -1 );
|
|
|
|
/* Double check: in memcpy mode, the vips tilesize should exactly
|
|
* match the tifftile size.
|
|
*/
|
|
if( rtiff->memcpy ) {
|
|
size_t vips_tile_size = VIPS_IMAGE_SIZEOF_PEL( t[0] ) *
|
|
tile_width * tile_height;
|
|
|
|
if( rtiff->header.tile_size != vips_tile_size ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "unsupported tiff image type" ) );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
/* Even though this is a tiled reader, we hint thinstrip since with
|
|
* the cache we are quite happy serving that if anything downstream
|
|
* would like it.
|
|
*/
|
|
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
|
|
|
|
if( vips_image_generate( t[0],
|
|
rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop,
|
|
rtiff, NULL ) )
|
|
return( -1 );
|
|
|
|
/* Copy to out, adding a cache. Enough tiles for two complete rows.
|
|
*/
|
|
if( vips_tilecache( t[0], &t[1],
|
|
"tile_width", tile_width,
|
|
"tile_height", tile_height,
|
|
"max_tiles", 2 * (1 + t[0]->Xsize / tile_width),
|
|
NULL ) ||
|
|
rtiff_autorotate( rtiff, t[1], &t[2] ) ||
|
|
rtiff_unpremultiply( rtiff, t[2], &t[3] ) ||
|
|
vips_image_write( t[3], out ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Read a strip. If the image is in separate planes, read each plane and
|
|
* interleave to the output.
|
|
*
|
|
* strip is the number of this strip in this page.
|
|
*/
|
|
static int
|
|
rtiff_strip_read_interleaved( Rtiff *rtiff, tstrip_t strip, tdata_t buf )
|
|
{
|
|
int samples_per_pixel = rtiff->header.samples_per_pixel;
|
|
int read_height = rtiff->header.read_height;
|
|
int bits_per_sample = rtiff->header.bits_per_sample;
|
|
int strip_y = strip * read_height;
|
|
|
|
if( rtiff->header.separate ) {
|
|
int page_width = rtiff->header.width;
|
|
int page_height = rtiff->header.height;
|
|
int strips_per_plane = 1 + (page_height - 1) / read_height;
|
|
int strip_height = VIPS_MIN( read_height,
|
|
page_height - strip_y );
|
|
int pels_per_strip = page_width * strip_height;
|
|
int bytes_per_sample = bits_per_sample >> 3;
|
|
|
|
int i, j, k;
|
|
|
|
for( i = 0; i < samples_per_pixel; i++ ) {
|
|
VipsPel *p;
|
|
VipsPel *q;
|
|
|
|
if( rtiff_strip_read( rtiff,
|
|
strips_per_plane * i + strip,
|
|
rtiff->plane_buf ) )
|
|
return( -1 );
|
|
|
|
p = (VipsPel *) rtiff->plane_buf;
|
|
q = i * bytes_per_sample + (VipsPel *) buf;
|
|
for( j = 0; j < pels_per_strip; j++ ) {
|
|
for( k = 0; k < bytes_per_sample; k++ )
|
|
q[k] = p[k];
|
|
|
|
p += bytes_per_sample;
|
|
q += bytes_per_sample * samples_per_pixel;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if( rtiff_strip_read( rtiff, strip, buf ) )
|
|
return( -1 );
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_stripwise_generate( VipsRegion *or,
|
|
void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
VipsImage *out = or->im;
|
|
Rtiff *rtiff = (Rtiff *) a;
|
|
int read_height = rtiff->header.read_height;
|
|
int page_height = rtiff->header.height;
|
|
tsize_t scanline_size = rtiff->header.scanline_size;
|
|
VipsRect *r = &or->valid;
|
|
|
|
int y;
|
|
|
|
#ifdef DEBUG_VERBOSE
|
|
printf( "rtiff_stripwise_generate: top = %d, height = %d\n",
|
|
r->top, r->height );
|
|
printf( "rtiff_stripwise_generate: y_top = %d\n", rtiff->y_pos );
|
|
#endif /*DEBUG_VERBOSE*/
|
|
|
|
/* We're inside a tilecache where tiles are the full image width, so
|
|
* this should always be true.
|
|
*/
|
|
g_assert( r->left == 0 );
|
|
g_assert( r->width == or->im->Xsize );
|
|
g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize );
|
|
|
|
/* If we're reading more than one page, tiles won't fall on strip
|
|
* boundaries.
|
|
*/
|
|
|
|
/* Tiles should always be a strip in height, unless it's the final
|
|
* strip in the image.
|
|
*/
|
|
g_assert( r->height ==
|
|
VIPS_MIN( read_height, or->im->Ysize - r->top ) );
|
|
|
|
/* And check that y_pos is correct. It should be, since we are inside
|
|
* a vips_sequential().
|
|
*/
|
|
if( r->top != rtiff->y_pos ) {
|
|
vips_error( "tiff2vips",
|
|
_( "out of order read -- at line %d, "
|
|
"but line %d requested" ), rtiff->y_pos, r->top );
|
|
return( -1 );
|
|
}
|
|
|
|
VIPS_GATE_START( "rtiff_stripwise_generate: work" );
|
|
|
|
y = 0;
|
|
while( y < r->height ) {
|
|
/* page_no is within this toilet roll image, not tiff
|
|
* file page number ... add the number of the start
|
|
* page to get that.
|
|
*/
|
|
int page_no = (r->top + y) / page_height;
|
|
int y_page = (r->top + y) % page_height;
|
|
|
|
/* Strip number.
|
|
*/
|
|
tstrip_t strip_no = y_page / read_height;
|
|
|
|
VipsRect image, page, strip, hit;
|
|
|
|
/* Our four (including the output region) rects, all in
|
|
* output image coordinates.
|
|
*/
|
|
image.left = 0;
|
|
image.top = 0;
|
|
image.width = out->Xsize;
|
|
image.height = out->Ysize;
|
|
|
|
page.left = 0;
|
|
page.top = page_height * ((r->top + y) / page_height);
|
|
page.width = out->Xsize;
|
|
page.height = page_height;
|
|
|
|
strip.left = 0;
|
|
strip.top = page.top + strip_no * read_height;
|
|
strip.width = out->Xsize;
|
|
strip.height = read_height;
|
|
|
|
/* Clip strip against page and image ... the final strip will
|
|
* be smaller.
|
|
*/
|
|
vips_rect_intersectrect( &strip, &image, &strip );
|
|
vips_rect_intersectrect( &strip, &page, &strip );
|
|
|
|
/* Now the bit that overlaps with the region we are filling.
|
|
*/
|
|
vips_rect_intersectrect( &strip, r, &hit );
|
|
|
|
g_assert( hit.height > 0 );
|
|
|
|
if( rtiff_set_page( rtiff, rtiff->page + page_no ) ) {
|
|
VIPS_GATE_STOP( "rtiff_stripwise_generate: work" );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Read directly into the image if we can. Otherwise, we must
|
|
* read to a temp buffer then unpack into the image.
|
|
*
|
|
* We need to read via a buffer if we need to reformat pixels,
|
|
* or if this strip is not aligned on a tile boundary.
|
|
*/
|
|
if( rtiff->memcpy &&
|
|
hit.top == strip.top &&
|
|
hit.height == strip.height ) {
|
|
if( rtiff_strip_read_interleaved( rtiff, strip_no,
|
|
VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) {
|
|
VIPS_GATE_STOP(
|
|
"rtiff_stripwise_generate: work" );
|
|
return( -1 );
|
|
}
|
|
}
|
|
else {
|
|
VipsPel *p;
|
|
VipsPel *q;
|
|
int z;
|
|
|
|
/* Read and interleave the entire strip.
|
|
*/
|
|
if( rtiff_strip_read_interleaved( rtiff, strip_no,
|
|
rtiff->contig_buf ) ) {
|
|
VIPS_GATE_STOP(
|
|
"rtiff_stripwise_generate: work" );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Do any repacking to generate pixels in vips layout.
|
|
*/
|
|
p = (VipsPel *) rtiff->contig_buf +
|
|
(hit.top - strip.top) * scanline_size;
|
|
q = VIPS_REGION_ADDR( or, 0, r->top + y );
|
|
for( z = 0; z < hit.height; z++ ) {
|
|
rtiff->sfn( rtiff,
|
|
q, p, or->im->Xsize, rtiff->client );
|
|
|
|
p += scanline_size;
|
|
q += VIPS_REGION_LSKIP( or );
|
|
}
|
|
}
|
|
|
|
y += hit.height;
|
|
rtiff->y_pos += hit.height;
|
|
}
|
|
|
|
VIPS_GATE_STOP( "rtiff_stripwise_generate: work" );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Stripwise reading.
|
|
*
|
|
* We could potentially read strips in any order, but this would give
|
|
* catastrophic performance for operations like 90 degress rotate on a
|
|
* large image. Only offer sequential read.
|
|
*/
|
|
static int
|
|
rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
VipsImage **t = (VipsImage **)
|
|
vips_object_local_array( VIPS_OBJECT( out ), 4 );
|
|
|
|
#ifdef DEBUG
|
|
printf( "tiff2vips: rtiff_read_stripwise\n" );
|
|
#endif /*DEBUG*/
|
|
|
|
t[0] = vips_image_new();
|
|
if( rtiff_set_header( rtiff, t[0] ) )
|
|
return( -1 );
|
|
|
|
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
|
|
|
|
#ifdef DEBUG
|
|
printf( "rtiff_read_stripwise: header.rows_per_strip = %u\n",
|
|
rtiff->header.rows_per_strip );
|
|
printf( "rtiff_read_stripwise: header.strip_size = %zd\n",
|
|
rtiff->header.strip_size );
|
|
printf( "rtiff_read_stripwise: header.number_of_strips = %d\n",
|
|
rtiff->header.number_of_strips );
|
|
printf( "rtiff_read_stripwise: header.read_height = %u\n",
|
|
rtiff->header.read_height );
|
|
printf( "rtiff_read_stripwise: header.read_size = %zd\n",
|
|
rtiff->header.read_size );
|
|
#endif /*DEBUG*/
|
|
|
|
/* Double check: in memcpy mode, the vips linesize should exactly
|
|
* match the tiff line size.
|
|
*/
|
|
if( rtiff->memcpy ) {
|
|
size_t vips_line_size;
|
|
|
|
/* Lines are smaller in plane-separated mode.
|
|
*/
|
|
if( rtiff->header.separate )
|
|
vips_line_size = VIPS_IMAGE_SIZEOF_ELEMENT( t[0] ) *
|
|
t[0]->Xsize;
|
|
else
|
|
vips_line_size = VIPS_IMAGE_SIZEOF_LINE( t[0] );
|
|
|
|
if( vips_line_size != rtiff->header.scanline_size ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "unsupported tiff image type" ) );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
/* If we have separate image planes, we must read to a plane buffer,
|
|
* then interleave to the output.
|
|
*
|
|
* We don't need a separate buffer per thread since the _generate()
|
|
* function runs inside the cache lock.
|
|
*/
|
|
if( rtiff->header.separate ) {
|
|
if( !(rtiff->plane_buf = vips_malloc( VIPS_OBJECT( out ),
|
|
rtiff->header.read_size )) )
|
|
return( -1 );
|
|
}
|
|
|
|
/* If we need to manipulate pixels, we must read to an interleaved
|
|
* plane buffer before repacking to the output.
|
|
*
|
|
* If we are doing a multi-page read, we need a strip buffer, since
|
|
* strips may not be aligned on tile boundaries.
|
|
*
|
|
* We don't need a separate buffer per thread since the _generate()
|
|
* function runs inside the cache lock.
|
|
*/
|
|
if( !rtiff->memcpy ||
|
|
rtiff->n > 1 ) {
|
|
tsize_t size;
|
|
|
|
size = rtiff->header.read_size;
|
|
if( rtiff->header.separate )
|
|
size *= rtiff->header.samples_per_pixel;
|
|
|
|
if( !(rtiff->contig_buf =
|
|
vips_malloc( VIPS_OBJECT( out ), size )) )
|
|
return( -1 );
|
|
}
|
|
|
|
/* rows_per_strip can be very large if this is a separate plane image,
|
|
* beware.
|
|
*/
|
|
if(
|
|
vips_image_generate( t[0],
|
|
NULL, rtiff_stripwise_generate, NULL,
|
|
rtiff, NULL ) ||
|
|
vips_sequential( t[0], &t[1],
|
|
"tile_height", rtiff->header.read_height,
|
|
NULL ) ||
|
|
rtiff_autorotate( rtiff, t[1], &t[2] ) ||
|
|
rtiff_unpremultiply( rtiff, t[2], &t[3] ) ||
|
|
vips_image_write( t[3], out ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* Load from a tiff dir into one of our tiff header structs.
|
|
*/
|
|
static int
|
|
rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
|
|
{
|
|
uint16 extra_samples_count;
|
|
uint16 *extra_samples_types;
|
|
|
|
if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH,
|
|
&header->width ) ||
|
|
!tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH,
|
|
&header->height ) ||
|
|
!tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL,
|
|
&header->samples_per_pixel ) ||
|
|
!tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE,
|
|
&header->bits_per_sample ) ||
|
|
!tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC,
|
|
&header->photometric_interpretation ) ||
|
|
!tfget16( rtiff->tiff, TIFFTAG_INKSET,
|
|
&header->inkset ) )
|
|
return( -1 );
|
|
|
|
TIFFGetFieldDefaulted( rtiff->tiff,
|
|
TIFFTAG_COMPRESSION, &header->compression );
|
|
|
|
/* Request YCbCr expansion. libtiff complains if you do this for
|
|
* non-jpg images. We must set this here since it changes the result
|
|
* of scanline_size.
|
|
*/
|
|
if( header->compression == COMPRESSION_JPEG )
|
|
TIFFSetField( rtiff->tiff,
|
|
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
|
|
else if( header->photometric_interpretation == PHOTOMETRIC_YCBCR ) {
|
|
/* We rely on the jpg decompressor to upsample chroma
|
|
* subsampled images. If there is chroma subsampling but
|
|
* no jpg compression, we have to give up.
|
|
*
|
|
* tiffcp fails for images like this too.
|
|
*/
|
|
uint16 hsub, vsub;
|
|
|
|
TIFFGetFieldDefaulted( rtiff->tiff,
|
|
TIFFTAG_YCBCRSUBSAMPLING, &hsub, &vsub );
|
|
if( hsub != 1 ||
|
|
vsub != 1 ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "subsampled images not supported" ) );
|
|
return( -1 );
|
|
}
|
|
}
|
|
|
|
if( header->photometric_interpretation == PHOTOMETRIC_LOGLUV ) {
|
|
if( header->compression != COMPRESSION_SGILOG &&
|
|
header->compression != COMPRESSION_SGILOG24 ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "not SGI-compressed LOGLUV" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Always get LOGLUV as 3 x float XYZ. We must set this here
|
|
* since it'll change the value of scanline_size.
|
|
*/
|
|
TIFFSetField( rtiff->tiff,
|
|
TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT );
|
|
}
|
|
|
|
/* For logluv, the calibration factor to get to absolute luminance.
|
|
*/
|
|
if( !TIFFGetField( rtiff->tiff, TIFFTAG_STONITS, &header->stonits ) )
|
|
header->stonits = 1.0;
|
|
|
|
/* Arbitrary sanity-checking limits.
|
|
*/
|
|
if( header->width <= 0 ||
|
|
header->width > VIPS_MAX_COORD ||
|
|
header->height <= 0 ||
|
|
header->height > VIPS_MAX_COORD ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "width/height out of range" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
if( header->samples_per_pixel <= 0 ||
|
|
header->samples_per_pixel > 10000 ||
|
|
header->bits_per_sample <= 0 ||
|
|
header->bits_per_sample > 32 ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "samples out of range" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
header->sample_format = get_sample_format( rtiff->tiff );
|
|
header->orientation = get_orientation( rtiff->tiff );
|
|
|
|
header->separate = FALSE;
|
|
if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) {
|
|
int v;
|
|
|
|
if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) )
|
|
return( -1 );
|
|
if( v == PLANARCONFIG_SEPARATE )
|
|
header->separate = TRUE;
|
|
}
|
|
|
|
/* Tiles and strip images have slightly different fields.
|
|
*/
|
|
header->tiled = TIFFIsTiled( rtiff->tiff );
|
|
|
|
if( header->tiled ) {
|
|
if( !tfget32( rtiff->tiff,
|
|
TIFFTAG_TILEWIDTH, &header->tile_width ) ||
|
|
!tfget32( rtiff->tiff,
|
|
TIFFTAG_TILELENGTH, &header->tile_height ) )
|
|
return( -1 );
|
|
|
|
/* Arbitrary sanity-checking limits.
|
|
*/
|
|
if( header->tile_width <= 0 ||
|
|
header->tile_width > 10000 ||
|
|
header->tile_height <= 0 ||
|
|
header->tile_height > 10000 ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "tile size out of range" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
header->tile_size = TIFFTileSize( rtiff->tiff );
|
|
header->tile_row_size = TIFFTileRowSize( rtiff->tiff );
|
|
|
|
/* Fuzzed TIFFs can give crazy values for tile_size. Sanity
|
|
* check at 100mb per tile.
|
|
*/
|
|
if( header->tile_size <= 0 ||
|
|
header->tile_size > 100 * 1000 * 1000 ||
|
|
header->tile_row_size <= 0 ||
|
|
header->tile_row_size > 100 * 1000 * 1000 ) {
|
|
vips_error( "tiff2vips",
|
|
"%s", _( "tile size out of range" ) );
|
|
return( -1 );
|
|
}
|
|
|
|
/* Stop some compiler warnings.
|
|
*/
|
|
header->rows_per_strip = 0;
|
|
header->strip_size = 0;
|
|
header->number_of_strips = 0;
|
|
header->read_height = 0;
|
|
header->read_size = 0;
|
|
}
|
|
else {
|
|
if( !tfget32( rtiff->tiff,
|
|
TIFFTAG_ROWSPERSTRIP, &header->rows_per_strip ) )
|
|
return( -1 );
|
|
header->strip_size = TIFFStripSize( rtiff->tiff );
|
|
header->scanline_size = TIFFScanlineSize( rtiff->tiff );
|
|
header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff );
|
|
|
|
/* libtiff has two strip-wise readers. TIFFReadEncodedStrip()
|
|
* decompresses an entire strip to memory. It's fast, but it
|
|
* will need a lot of ram if the strip is large.
|
|
* TIFFReadScanline() reads a single scanline. It's slower, but
|
|
* will save a lot of memory if strips are large.
|
|
*
|
|
* If this image has a strip size of over 128 lines, fall back
|
|
* to TIFFReadScanline(), otherwise use TIFFReadEncodedStrip().
|
|
*
|
|
* Don't do this in plane-separate mode. TIFFReadScanline() is
|
|
* too fiddly to use in this case.
|
|
*
|
|
* Don't try scanline reading for YCbCr images.
|
|
* TIFFScanlineSize() will not work in this case due to
|
|
* chroma subsampling.
|
|
*/
|
|
if( header->rows_per_strip > 128 &&
|
|
!header->separate &&
|
|
header->photometric_interpretation !=
|
|
PHOTOMETRIC_YCBCR ) {
|
|
header->read_scanlinewise = TRUE;
|
|
header->read_height = 1;
|
|
header->read_size = rtiff->header.scanline_size;
|
|
}
|
|
else {
|
|
header->read_scanlinewise = FALSE;
|
|
|
|
/* rows_per_strip can be 2 ** 32 - 1, meaning the
|
|
* whole image. Clip this down to height to avoid
|
|
* confusing vips.
|
|
*
|
|
* And it musn't be zero.
|
|
*/
|
|
header->read_height = VIPS_CLIP( 1,
|
|
header->rows_per_strip, header->height );
|
|
header->read_size = header->strip_size;
|
|
}
|
|
|
|
/* Stop some compiler warnings.
|
|
*/
|
|
header->tile_width = 0;
|
|
header->tile_height = 0;
|
|
header->tile_size = 0;
|
|
header->tile_row_size = 0;
|
|
}
|
|
|
|
TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_EXTRASAMPLES,
|
|
&extra_samples_count, &extra_samples_types );
|
|
|
|
header->alpha_band = -1;
|
|
if( extra_samples_count > 0 ) {
|
|
/* There must be exactly one band which is
|
|
* EXTRASAMPLE_ASSOCALPHA. Note which one it is so we can
|
|
* unpremultiply with the right channel.
|
|
*/
|
|
int i;
|
|
|
|
for( i = 0; i < extra_samples_count; i++ )
|
|
if( extra_samples_types[i] == EXTRASAMPLE_ASSOCALPHA ) {
|
|
if( header->alpha_band != -1 )
|
|
g_warning( "%s", _( "more than one "
|
|
"alpha -- ignoring" ) );
|
|
|
|
header->alpha_band = header->samples_per_pixel -
|
|
extra_samples_count + i;
|
|
}
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 )
|
|
{
|
|
if( h1->width != h2->width ||
|
|
h1->height != h2->height ||
|
|
h1->samples_per_pixel != h2->samples_per_pixel ||
|
|
h1->bits_per_sample != h2->bits_per_sample ||
|
|
h1->photometric_interpretation !=
|
|
h2->photometric_interpretation ||
|
|
h1->sample_format != h2->sample_format ||
|
|
h1->separate != h2->separate ||
|
|
h1->tiled != h2->tiled ||
|
|
h1->orientation != h2->orientation )
|
|
return( 0 );
|
|
|
|
if( h1->tiled ) {
|
|
if( h1->tile_width != h2->tile_width ||
|
|
h1->tile_height != h2->tile_height )
|
|
return( 0 );
|
|
}
|
|
else {
|
|
if( h1->read_height != h2->read_height ||
|
|
h1->read_size != h2->read_size ||
|
|
h1->number_of_strips != h2->number_of_strips )
|
|
return( 0 );
|
|
}
|
|
|
|
return( 1 );
|
|
}
|
|
|
|
static int
|
|
rtiff_header_read_all( Rtiff *rtiff )
|
|
{
|
|
#ifdef DEBUG
|
|
printf( "tiff2vips: reading header for page %d ...\n", rtiff->page );
|
|
#endif /*DEBUG*/
|
|
|
|
if( rtiff_set_page( rtiff, rtiff->page ) ||
|
|
rtiff_header_read( rtiff, &rtiff->header ) )
|
|
return( -1 );
|
|
|
|
/* -1 means "to the end".
|
|
*/
|
|
rtiff->n_pages = rtiff_n_pages( rtiff );
|
|
if( rtiff->n == -1 )
|
|
rtiff->n = rtiff->n_pages - rtiff->page;
|
|
|
|
/* If we're to read many pages, verify that they are all identical.
|
|
*/
|
|
if( rtiff->n > 1 ) {
|
|
int i;
|
|
|
|
for( i = 1; i < rtiff->n; i++ ) {
|
|
RtiffHeader header;
|
|
|
|
#ifdef DEBUG
|
|
printf( "tiff2vips: verifying header for page %d ...\n",
|
|
rtiff->page + i );
|
|
#endif /*DEBUG*/
|
|
|
|
if( rtiff_set_page( rtiff, rtiff->page + i ) ||
|
|
rtiff_header_read( rtiff, &header ) )
|
|
return( -1 );
|
|
|
|
if( !rtiff_header_equal( &rtiff->header, &header ) ) {
|
|
vips_error( "tiff2vips",
|
|
_( "page %d differs from page %d" ),
|
|
rtiff->page + i, rtiff->page );
|
|
return( -1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* On a header-only read, we can just swap width/height if orientation is 6 or
|
|
* 8.
|
|
*/
|
|
static void
|
|
vips__tiff_read_header_orientation( Rtiff *rtiff, VipsImage *out )
|
|
{
|
|
int orientation;
|
|
|
|
if( rtiff->autorotate &&
|
|
vips_image_get_typeof( out, VIPS_META_ORIENTATION ) &&
|
|
!vips_image_get_int( out,
|
|
VIPS_META_ORIENTATION, &orientation ) ) {
|
|
if( orientation == 3 ||
|
|
orientation == 6 )
|
|
VIPS_SWAP( int, out->Xsize, out->Ysize );
|
|
|
|
/* We must remove VIPS_META_ORIENTATION to prevent accidental
|
|
* double rotations.
|
|
*/
|
|
vips_image_remove( out, VIPS_META_ORIENTATION );
|
|
}
|
|
}
|
|
|
|
typedef gboolean (*TiffPropertyFn)( TIFF *tif );
|
|
|
|
static gboolean
|
|
vips__testtiff_source( VipsSource *source, TiffPropertyFn fn )
|
|
{
|
|
TIFF *tif;
|
|
gboolean property;
|
|
|
|
vips__tiff_init();
|
|
|
|
if( !(tif = vips__tiff_openin_source( source )) ) {
|
|
vips_error_clear();
|
|
return( FALSE );
|
|
}
|
|
|
|
property = fn ? fn( tif ) : TRUE;
|
|
|
|
TIFFClose( tif );
|
|
|
|
return( property );
|
|
}
|
|
|
|
gboolean
|
|
vips__istiff_source( VipsSource *source )
|
|
{
|
|
return( vips__testtiff_source( source, NULL ) );
|
|
}
|
|
|
|
gboolean
|
|
vips__istifftiled_source( VipsSource *source )
|
|
{
|
|
return( vips__testtiff_source( source, TIFFIsTiled ) );
|
|
}
|
|
|
|
int
|
|
vips__tiff_read_header_source( VipsSource *source, VipsImage *out,
|
|
int page, int n, gboolean autorotate )
|
|
{
|
|
Rtiff *rtiff;
|
|
|
|
vips__tiff_init();
|
|
|
|
if( !(rtiff = rtiff_new( source, out, page, n, autorotate )) ||
|
|
rtiff_header_read_all( rtiff ) )
|
|
return( -1 );
|
|
|
|
if( rtiff_set_header( rtiff, out ) )
|
|
return( -1 );
|
|
|
|
vips__tiff_read_header_orientation( rtiff, out );
|
|
|
|
/* We never call vips_source_decode() since we need to be able to
|
|
* seek() the whole way through the file. Just minimise instead,
|
|
*/
|
|
vips_source_minimise( source );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
int
|
|
vips__tiff_read_source( VipsSource *source, VipsImage *out,
|
|
int page, int n, gboolean autorotate )
|
|
{
|
|
Rtiff *rtiff;
|
|
|
|
#ifdef DEBUG
|
|
printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() );
|
|
#endif /*DEBUG*/
|
|
|
|
vips__tiff_init();
|
|
|
|
if( !(rtiff = rtiff_new( source, out, page, n, autorotate )) ||
|
|
rtiff_header_read_all( rtiff ) )
|
|
return( -1 );
|
|
|
|
if( rtiff->header.tiled ) {
|
|
if( rtiff_read_tilewise( rtiff, out ) )
|
|
return( -1 );
|
|
}
|
|
else {
|
|
if( rtiff_read_stripwise( rtiff, out ) )
|
|
return( -1 );
|
|
}
|
|
|
|
/* We never call vips_source_decode() since we need to be able to
|
|
* seek() the whole way through the file. Just minimise instead,
|
|
*/
|
|
vips_source_minimise( source );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
#endif /*HAVE_TIFF*/
|