add LOGLUV TIFF support

libvips XYZ images load and save as libtiff LOGLUV

see https://github.com/libvips/libvips/issues/1506
This commit is contained in:
John Cupitt 2020-01-29 17:47:08 +00:00
parent 2e3eca7e29
commit a158b15b97
2 changed files with 160 additions and 15 deletions

View File

@ -193,6 +193,8 @@
* - switch to source input
* 18/11/19
* - support ASSOCALPHA in any alpha band
* 27/1/20
* - read logluv images as XYZ
*/
/*
@ -294,6 +296,10 @@ typedef struct _RtiffHeader {
*/
uint32 read_height;
tsize_t read_size;
/* Scale factor to get absolute cd/m2 from XYZ.
*/
double stonits;
} RtiffHeader;
/* Scanline-type process function.
@ -911,6 +917,52 @@ rtiff_parse_labs( Rtiff *rtiff, VipsImage *out )
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
@ -1406,6 +1458,9 @@ rtiff_pick_reader( Rtiff *rtiff )
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 )
@ -1436,6 +1491,15 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out )
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;
@ -2104,7 +2168,7 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
/* Double check: in memcpy mode, the vips linesize should exactly
* match the tiff line size.
*/
if( rtiff->memcpy ) {
if( rtiff->memcpy ) {
size_t vips_line_size;
/* Lines are smaller in plane-separated mode.
@ -2198,13 +2262,16 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
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 )
/* We want to always expand subsampled YCBCR images to full
* RGB.
*/
TIFFSetField( rtiff->tiff,
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
else if( header->photometric_interpretation == PHOTOMETRIC_YCBCR ) {
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.
@ -2223,6 +2290,26 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
}
}
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 ||

View File

@ -189,6 +189,8 @@
* - "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
*/
/*
@ -608,7 +610,6 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
{
TIFF *tif = layer->tif;
int format;
int orientation;
/* Output base header fields.
@ -702,6 +703,21 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
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;
@ -764,17 +780,23 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
TIFFSetField( tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE );
/* Sample format.
*
* Don't set for logluv: libtiff does this for us.
*/
format = SAMPLEFORMAT_UINT;
if( vips_band_format_isuint( wtiff->ready->BandFmt ) )
if( wtiff->input->Type != VIPS_INTERPRETATION_XYZ ) {
int format;
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 );
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 );
}
@ -1039,6 +1061,11 @@ wtiff_new( VipsImage *input, const char *filename,
return( NULL );
}
/* XYZ images are written as libtiff LOGLUV.
*/
if( wtiff->ready->Type == VIPS_INTERPRETATION_XYZ )
wtiff->compression = COMPRESSION_SGILOG;
/* Multipage image?
*/
if( wtiff->page_height < wtiff->ready->Ysize ) {
@ -1331,6 +1358,31 @@ LabS2Lab16( VipsPel *q, VipsPel *p, int n, int 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
@ -1358,6 +1410,8 @@ wtiff_pack2tiff( Wtiff *wtiff, Layer *layer,
LabQ2LabC( q, p, area->width );
else if( wtiff->squash )
eightbit2onebit( 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 );
@ -1449,6 +1503,10 @@ wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
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->squash ) {
eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize );
p = wtiff->tbuf;