diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 56935afb..4c908bdd 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -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 || diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 63e338ac..0edcbe7e 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -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;