From 8b469b4516f970b1e1715c203a0d5248a377b016 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 16 Jun 2020 18:58:27 +0100 Subject: [PATCH] adjust formatting of 2, 4 bit tiff load/save To make it libvipsey. Small fixes as well. --- ChangeLog | 2 + libvips/foreign/tiff2vips.c | 40 ++++++++++-------- libvips/foreign/tiffsave.c | 80 ++++++++++++++++-------------------- libvips/foreign/vips2tiff.c | 81 ++++++++++++++++++++----------------- 4 files changed, 103 insertions(+), 100 deletions(-) diff --git a/ChangeLog b/ChangeLog index e495c72f..9d365dc7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -31,6 +31,8 @@ - deprecate heifload autorotate -- it's now always on - revised resize improves accuracy [kleisauke] - add --vips-config flag to show configuration info +- add "bitdepth" param to tiff load and save, deprecate "squash" [MathemanFlo] +- tiff load and save now supports 2 and 4 bit data [MathemanFlo] 24/4/20 started 8.9.3 - better iiif tile naming [IllyaMoskvin] diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 546248b6..50c87934 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -199,8 +199,8 @@ * - better handling of aligned reads in multipage tiffs * 28/5/20 * - add subifd - * 06/6/20 - * - add load functionality for 2 and 4 bit greyscale tiffs + * 06/6/20 MathemanFlo + * - support 2 and 4 bit greyscale load */ /* @@ -1026,14 +1026,13 @@ 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; + int x, i, z; + VipsPel bits; + /* (sigh) how many times have I written this? */ x = 0; @@ -1051,6 +1050,7 @@ rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) */ if( n & 7 ) { bits = p[i]; + for( z = 0; z < (n & 7); z++ ) { q[x + z] = (bits & 128) ? white : black; bits <<= 1; @@ -1063,9 +1063,11 @@ rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) static void rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) { + int photometric_interpretation = + rtiff->header.photometric_interpretation; + int minisblack = photometric_interpretation == PHOTOMETRIC_MINISBLACK; + int x, i, z; - int minisblack = - rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; VipsPel twobits, fourbits, bits; x = 0; @@ -1073,7 +1075,8 @@ rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) bits = (VipsPel) minisblack ? p[i] : ~p[i]; for( z = 0; z < 4; z++ ) { - /* The grey shade is the value four times concatenated */ + /* The grey shade is the value four times concatenated. + */ twobits = bits >> 6; fourbits = twobits | (twobits << 2); q[x] = fourbits | (fourbits << 4); @@ -1086,7 +1089,8 @@ rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) */ if( n & 3 ) { bits = (VipsPel) minisblack ? p[i] : ~p[i]; - for( z = 0; z < (n & 3) ; z++ ) { + + for( z = 0; z < (n & 3); z++ ) { twobits = bits >> 6; fourbits = twobits | (twobits << 2); q[x + z] = fourbits | (fourbits << 4); @@ -1100,18 +1104,20 @@ rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) static void rtiff_fourbit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) { + int photometric_interpretation = + rtiff->header.photometric_interpretation; + int minisblack = photometric_interpretation == PHOTOMETRIC_MINISBLACK; + int x, i, z; - int minisblack = - rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; VipsPel bits; x = 0; - for( i = 0; i < (n >> 1); i++ ) { bits = (VipsPel) minisblack ? p[i] : ~p[i]; for( z = 0; z < 2; z++ ) { - /* The grey shade is the value two times concatenated */ + /* The grey shade is the value two times concatenated. + */ q[x] = (bits & 0xF0) | (bits >> 4); bits <<= 4; x += 1; @@ -1119,10 +1125,10 @@ rtiff_fourbit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) } /* Do last byte in line. - */ - if( n & 1) { + */ + if( n & 1 ) { bits = (VipsPel) minisblack ? p[i] : ~p[i]; - for( z = 0; z < (n & 1) ; z++ ) { + for( z = 0; z < (n & 1); z++ ) { q[x + z] = (bits & 0xF0) | (bits >> 4); bits <<= 4; } diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index 6e7096ad..83d9fd94 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -25,6 +25,7 @@ * - add "subifd" to create pyr layers as sub-directories * 8/6/20 * - add bitdepth support for 2 and 4 bit greyscale images + * - deprecate "squash" */ /* @@ -263,20 +264,6 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, pyramid ), FALSE ); - VIPS_ARG_BOOL( class, "squash", 14, - _( "Squash" ), - _( "Squash images down to 1 bit" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignSaveTiff, squash ), - FALSE ); - - VIPS_ARG_INT( class, "bitdepth", 14, - _( "bitdepth" ), - _( "Change greyscale image bit depth to 1,2, 4 or 8." ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveTiff, bitdepth ), - 1,8,8 ); - VIPS_ARG_BOOL( class, "miniswhite", 14, _( "Miniswhite" ), _( "Use 0 for white in 1-bit images" ), @@ -284,41 +271,41 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, miniswhite ), FALSE ); - VIPS_ARG_ENUM( class, "resunit", 15, + VIPS_ARG_INT( class, "bitdepth", 15, + _( "bitdepth" ), + _( "Write as a 1, 2, 4 or 8 bit image" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveTiff, bitdepth ), + 0, 8, 0 ); + + VIPS_ARG_ENUM( class, "resunit", 16, _( "Resolution unit" ), _( "Resolution unit" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, resunit ), VIPS_TYPE_FOREIGN_TIFF_RESUNIT, VIPS_FOREIGN_TIFF_RESUNIT_CM ); - VIPS_ARG_DOUBLE( class, "xres", 16, + VIPS_ARG_DOUBLE( class, "xres", 17, _( "Xres" ), _( "Horizontal resolution in pixels/mm" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, xres ), 0.001, 1000000, 1 ); - VIPS_ARG_DOUBLE( class, "yres", 17, + VIPS_ARG_DOUBLE( class, "yres", 18, _( "Yres" ), _( "Vertical resolution in pixels/mm" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, yres ), 0.001, 1000000, 1 ); - VIPS_ARG_BOOL( class, "bigtiff", 18, + VIPS_ARG_BOOL( class, "bigtiff", 19, _( "Bigtiff" ), _( "Write a bigtiff image" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, bigtiff ), FALSE ); - VIPS_ARG_BOOL( class, "rgbjpeg", 20, - _( "RGB JPEG" ), - _( "Output RGB JPEG rather than YCbCr" ), - VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, - G_STRUCT_OFFSET( VipsForeignSaveTiff, rgbjpeg ), - FALSE ); - VIPS_ARG_BOOL( class, "properties", 21, _( "Properties" ), _( "Write a properties document to IMAGEDESCRIPTION" ), @@ -361,6 +348,20 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ), FALSE ); + VIPS_ARG_BOOL( class, "rgbjpeg", 20, + _( "RGB JPEG" ), + _( "Output RGB JPEG rather than YCbCr" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSaveTiff, rgbjpeg ), + FALSE ); + + VIPS_ARG_BOOL( class, "squash", 14, + _( "Squash" ), + _( "Squash images down to 1 bit" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsForeignSaveTiff, squash ), + FALSE ); + } static void @@ -403,10 +404,12 @@ vips_foreign_save_tiff_file_build( VipsObject *object ) build( object ) ) return( -1 ); - /* handle the deprecated squash parameter */ - if( vips_object_argument_isset(object,"squash") ) { - tiff->bitdepth = 1; /*we set that even in the case of LAB to LABQ */ - } + /* Handle the deprecated squash parameter. + */ + if( vips_object_argument_isset( object, "squash" ) ) + /* We set that even in the case of LAB to LABQ. + */ + tiff->bitdepth = 1; if( vips__tiff_write( save->ready, file->filename, tiff->compression, tiff->Q, tiff->predictor, @@ -555,7 +558,6 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * * @tile_width: %gint for tile size * * @tile_height: %gint for tile size * * @pyramid: %gboolean, write an image pyramid - * * @squash: %gboolean, squash 8-bit images down to 1 bit * * @bitdepth: %int, change bit depth to 1,2, or 4 bit * * @miniswhite: %gboolean, write 1-bit images as MINISWHITE * * @resunit: #VipsForeignTiffResunit for resolution unit @@ -583,7 +585,7 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * can do. * * XYZ images are automatically saved as libtiff LOGLUV with SGILOG compression. - * Float LAB images are saved as float CIELAB. Set @squash to save as 8-bit + * Float LAB images are saved as float CIELAB. Set @bitdepth to save as 8-bit * CIELAB. * * Use @Q to set the JPEG compression factor. Default 75. @@ -617,16 +619,7 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * tile. Use @depth to stop when the image fits in one pixel, or to only write * a single layer. * - * Set @squash to make 8-bit uchar images write as 1-bit TIFFs. Values >128 - * are written as white, values <=128 as black. Normally vips will write - * MINISBLACK TIFFs where black is a 0 bit, but if you set @miniswhite, it - * will use 0 for a white bit. Many pre-press applications only work with - * images which use this sense. @miniswhite only affects one-bit images, it - * does nothing for greyscale images. Consider @bitdepth for depths 1, 2 and 4. - * - * Set @squash to squash 3-band float CIELAB images down to 8-bit CIELAB. - * - * Set @bitdepth to make 8-bit uchar images write as 1,2 or 4-bit TIFFs. + * Set @bitdepth to save 8-bit uchar images as 1, 2 or 4-bit TIFFs. * In case of depth 1: Values >128 are written as white, values <=128 as black. * Normally vips will write MINISBLACK TIFFs where black is a 0 bit, but if you * set @miniswhite, it will use 0 for a white bit. Many pre-press applications @@ -639,8 +632,6 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * In case of depth 4: values < 16 are written as black, and so on for the * lighter shades. In case @miniswhite is set to true this behavior is inverted. * - * Set @bitdepth to 8 to squash 3-band float CIELAB images down to 8-bit CIELAB. - * * Use @resunit to override the default resolution unit. * The default * resolution unit is taken from the header field @@ -704,8 +695,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... ) * * @tile_width: %gint for tile size * * @tile_height: %gint for tile size * * @pyramid: %gboolean, write an image pyramid - * * @squash: %gboolean, squash 8-bit images down to 1 bit - * * @bitdepth: %int, change bit depth to 1,2 or 4-bit or squash float to 8 bit + * * @bitdepth: %int, set write bit depth to 1, 2, 4 or 8 * * @miniswhite: %gboolean, write 1-bit images as MINISWHITE * * @resunit: #VipsForeignTiffResunit for resolution unit * * @xres: %gdouble horizontal resolution in pixels/mm diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index e7adff00..36c22f9e 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -195,9 +195,10 @@ * - add PAGENUMBER support * 23/5/20 * - add support for subifd pyramid layers - * 8/6/20 + * 06/6/20 MathemanFlo * - add bitdepth support for 2 and 4 bit greyscale images */ + /* This file is part of VIPS. @@ -1190,30 +1191,34 @@ wtiff_new( VipsImage *input, const char *filename, } } - if( wtiff->bitdepth && !(wtiff->bitdepth == 1 || wtiff->bitdepth == 2 - || wtiff->bitdepth == 4) ) { - g_warning( "%s", - _( "allows only bitdepth values 1,2 or 4. " - "-- disabling bitdepth") ); - wtiff->bitdepth = 0; - } - - /* Can only have byte fractional bit depths for 8 bit mono. - * 3-band float should have been packed above. + /* Depth 8 is handled above. */ - if( wtiff->bitdepth && !(wtiff->ready->Coding == VIPS_CODING_NONE && - wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && - wtiff->ready->Bands == 1) ) { + if( wtiff->bitdepth && + !(wtiff->bitdepth == 1 || + wtiff->bitdepth == 2 || + wtiff->bitdepth == 4) ) { g_warning( "%s", - ( "can only set bitdepth for 1-band uchar and " - "3-band float lab -- disabling bitdepth" ) ); - wtiff->bitdepth = 0; + _( "bitdepth 1, 2 or 4 only -- disabling bitdepth") ); + wtiff->bitdepth = 0; } - if( wtiff->bitdepth && + /* 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 1,2 or 4-bit JPEG -- disabling JPEG" ) ); + _( "can't have <8 bit JPEG -- disabling JPEG" ) ); wtiff->compression = COMPRESSION_NONE; } @@ -1285,14 +1290,14 @@ LabQ2LabC( VipsPel *q, VipsPel *p, int n ) static void eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - int x; - VipsPel bits; - /* Invert in miniswhite mode. */ int white = wtiff->miniswhite ? 0 : 1; int black = white ^ 1; + int x; + VipsPel bits; + bits = 0; for( x = 0; x < n; x++ ) { bits <<= 1; @@ -1318,12 +1323,12 @@ eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) static void eightbit2twobit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - int x; - VipsPel bits; VipsPel mask = wtiff->miniswhite ? 3 : 0; + int x; + VipsPel bits; + bits = 0; - for( x = 0; x < n; x++ ) { bits <<= 2; bits |= (p[x] >> 6) ^ mask; @@ -1335,9 +1340,9 @@ eightbit2twobit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) } /* Any left-over bits? Need to be left-aligned. - */ + */ if( (x & 0x3) != 0 ) - *q++ = (wtiff->miniswhite ? ~bits : bits) << (8 - ((x & 0x3) << 1)); + *q++ = bits << (8 - ((x & 0x3) << 1)); } /* Pack 8 bit VIPS to 4 bit TIFF. @@ -1345,26 +1350,26 @@ eightbit2twobit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) static void eightbit2fourbit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { - int x; - VipsPel bits; - VipsPel mask = wtiff->miniswhite ? 15 : 0; - bits = 0; + VipsPel mask = wtiff->miniswhite ? 0xf : 0; + + VipsPel bits; + int x; + bits = 0; for( x = 0; x < n; x++ ) { bits <<= 4; bits |= (p[x] >> 4) ^ mask; if( (x & 0x1) == 0x1 ) { - *q++ = bits; - bits = 0; + *q++ = bits; + bits = 0; } } - /* Any left-over bits? Need to be left-aligned. - */ - if( (x & 0x1) != 0 ) - *q++ = ((VipsPel)(wtiff->miniswhite ? ~bits : bits)) - << (8 - ((x & 0x1) << 2)); + /* Any left-over bits? Need to be left-aligned. + */ + if( (x & 0x1) != 0 ) + *q++ = bits << (8 - ((x & 0x1) << 2)); } /* Swap the sense of the first channel, if necessary.