From 035e0bc7565d96e78705cc11f930fd3a71193435 Mon Sep 17 00:00:00 2001 From: Florian Heinrich Date: Tue, 9 Jun 2020 11:23:28 +0200 Subject: [PATCH 1/3] Added read and write functionality for 2 and 4 bit greyscale TIF images --- libvips/foreign/pforeign.h | 4 +- libvips/foreign/tiff2vips.c | 119 ++++++++++++++++++++++++++- libvips/foreign/tiffsave.c | 39 ++++++++- libvips/foreign/vips2tiff.c | 159 +++++++++++++++++++++++++++++------- 4 files changed, 285 insertions(+), 36 deletions(-) diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index a6b3b362..e5a5e07e 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -53,7 +53,7 @@ int vips__tiff_write( VipsImage *in, const char *filename, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -73,7 +73,7 @@ int vips__tiff_write_buf( VipsImage *in, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 34fefd75..e5228d86 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -199,6 +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 */ /* @@ -1056,6 +1058,76 @@ rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) } } +/* Per-scanline process function for 2 bit greyscale images. + */ +static void +rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) +{ + int x, i, z; + int minisblack = + rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; + VipsPel bits; + + x = 0; + for( i = 0; i < (n >> 2); i++ ) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; + + for( z = 0; z < 4; z++ ) { + /* The grey shade is the value four times concatenated */ + q[x] = (bits & 0xC0) | ((bits & 0xC0) >> 2) + | ((bits & 0xC0) >> 4) | (bits >> 6); + bits <<= 2; + x += 1; + } + } + + /* Do last byte in line. + */ + if( n & 3 ) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; + for( z = 0; z < (n & 3) ; z++ ) { + q[x + z] = (bits & 0xC0) | ((bits & 0xC0) >> 2) + | ((bits & 0xC0) >> 4) | (bits >> 6); + bits <<= 2; + } + } +} + +/* Per-scanline process function for 4 bit greyscale images. + */ +static void +rtiff_fourbit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) +{ + 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 */ + q[x] = (bits & 0xF0) | (bits >> 4); + bits <<= 4; + x += 1; + } + } + + /* Do last byte in line. + */ + if( n & 1) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; + for( z = 0; z < (n & 1) ; z++ ) { + q[x + z] = (bits & 0xF0) | (bits >> 4); + bits <<= 4; + } + } +} + + /* Read a 1-bit TIFF image. */ static int @@ -1075,6 +1147,44 @@ rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out ) return( 0 ); } +/* Read a 2-bit TIFF image. + */ +static int +rtiff_parse_twobit( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_samples( rtiff, 1 ) || + rtiff_check_bits( rtiff, 2 ) ) + return( -1 ); + + out->Bands = 1; + out->BandFmt = VIPS_FORMAT_UCHAR; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_B_W; + + rtiff->sfn = rtiff_twobit_line; + + return( 0 ); +} + +/* Read a 4-bit TIFF image. + */ +static int +rtiff_parse_fourbit( Rtiff *rtiff, VipsImage *out ) +{ + if( rtiff_check_samples( rtiff, 1 ) || + rtiff_check_bits( rtiff, 4 ) ) + return( -1 ); + + out->Bands = 1; + out->BandFmt = VIPS_FORMAT_UCHAR; + out->Coding = VIPS_CODING_NONE; + out->Type = VIPS_INTERPRETATION_B_W; + + rtiff->sfn = rtiff_fourbit_line; + + return( 0 ); +} + /* Swap the sense of the first channel, if necessary. */ #define GREY_LOOP( TYPE, MAX ) { \ @@ -1517,8 +1627,13 @@ rtiff_pick_reader( Rtiff *rtiff ) if( photometric_interpretation == PHOTOMETRIC_MINISWHITE || photometric_interpretation == PHOTOMETRIC_MINISBLACK ) { - if( bits_per_sample == 1 ) - return( rtiff_parse_onebit ); + + if( bits_per_sample == 1) + return ( rtiff_parse_onebit ); + else if ( bits_per_sample == 2 ) + return ( rtiff_parse_twobit); + else if ( bits_per_sample == 4 ) + return ( rtiff_parse_fourbit); else return( rtiff_parse_greyscale ); } diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index b66ce998..e1fce97a 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -23,6 +23,8 @@ * - add "depth" to set pyr depth * 12/5/20 * - add "subifd" to create pyr layers as sub-directories + * 8/6/20 + * - add bitdepth support for 2 and 4 bit greyscale images */ /* @@ -89,6 +91,7 @@ typedef struct _VipsForeignSaveTiff { int tile_height; gboolean pyramid; gboolean squash; + int bitdepth; gboolean miniswhite; VipsForeignTiffResunit resunit; double xres; @@ -263,9 +266,16 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) VIPS_ARG_BOOL( class, "squash", 14, _( "Squash" ), _( "Squash images down to 1 bit" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + 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" ), @@ -368,6 +378,7 @@ vips_foreign_save_tiff_init( VipsForeignSaveTiff *tiff ) tiff->level = 10; tiff->lossless = FALSE; tiff->depth = VIPS_FOREIGN_DZ_DEPTH_ONETILE; + tiff->bitdepth = 0; } typedef struct _VipsForeignSaveTiffFile { @@ -392,12 +403,17 @@ 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 */ + } + if( vips__tiff_write( save->ready, file->filename, tiff->compression, tiff->Q, tiff->predictor, tiff->profile, tiff->tile, tiff->tile_width, tiff->tile_height, tiff->pyramid, - tiff->squash, + tiff->bitdepth, tiff->miniswhite, tiff->resunit, tiff->xres, tiff->yres, tiff->bigtiff, @@ -470,7 +486,7 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object ) tiff->profile, tiff->tile, tiff->tile_width, tiff->tile_height, tiff->pyramid, - tiff->squash, + tiff->bitdepth, tiff->miniswhite, tiff->resunit, tiff->xres, tiff->yres, tiff->bigtiff, @@ -540,6 +556,7 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * * @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 * * @xres: %gdouble horizontal resolution in pixels/mm @@ -605,10 +622,23 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * 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. + * 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. + * 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 + * only work with images which use this sense. @miniswhite only affects one-bit + * images, it does nothing for greyscale images. + * In case of depth 2: The same holds but values < 64 are written as black. + * For 64 <= values < 128 they are written as dark grey, for 128 <= values < 192 + * they are written as light gray and values above are written as white. + * In case @miniswhite is set to true this behavior is inverted. + * 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. + * * Use @resunit to override the default resolution unit. * The default * resolution unit is taken from the header field @@ -673,6 +703,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... ) * * @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 * * @xres: %gdouble horizontal resolution in pixels/mm diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 84cf8538..1bf6d5cb 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -195,7 +195,8 @@ * - add PAGENUMBER support * 23/5/20 * - add support for subifd pyramid layers - */ + * 8/6/20 + * - add bitdepth support for 2 and 4 bit greyscale images /* @@ -337,7 +338,7 @@ struct _Wtiff { int tile; /* Tile or not */ int tilew, tileh; /* Tile size */ int pyramid; /* Wtiff pyramid */ - int squash; /* Write as small format */ + int bitdepth; /* Write as 1, 2 or 4 bit */ int miniswhite; /* Wtiff as 0 == white */ int resunit; /* Resolution unit (inches or cm) */ double xres; /* Resolution in X */ @@ -694,13 +695,14 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 ); TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB ); } - else if( wtiff->squash ) { + else if( wtiff->bitdepth == 1 || wtiff->bitdepth == 2 || + wtiff->bitdepth == 4 ) { TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 ); - TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 ); - TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, - wtiff->miniswhite ? - PHOTOMETRIC_MINISWHITE : - PHOTOMETRIC_MINISBLACK ); + TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, wtiff->bitdepth ); + TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, + wtiff->miniswhite ? + PHOTOMETRIC_MINISWHITE : + PHOTOMETRIC_MINISBLACK ); } else { int photometric; @@ -1039,13 +1041,13 @@ ready_to_write( Wtiff *wtiff ) /* "squash" float LAB down to LABQ. */ - if( wtiff->squash && + if( wtiff->bitdepth == 1 && wtiff->input->Bands == 3 && wtiff->input->BandFmt == VIPS_FORMAT_FLOAT && wtiff->input->Type == VIPS_INTERPRETATION_LAB ) { if( vips_Lab2LabQ( wtiff->input, &wtiff->ready, NULL ) ) return( -1 ); - wtiff->squash = 0; + wtiff->bitdepth = 0; } else { wtiff->ready = wtiff->input; @@ -1062,7 +1064,7 @@ wtiff_new( VipsImage *input, const char *filename, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -1091,7 +1093,7 @@ wtiff_new( VipsImage *input, const char *filename, wtiff->tilew = tile_width; wtiff->tileh = tile_height; wtiff->pyramid = pyramid; - wtiff->squash = squash; + wtiff->bitdepth = bitdepth; wtiff->miniswhite = miniswhite; wtiff->resunit = get_resunit( resunit ); wtiff->xres = xres; @@ -1188,23 +1190,32 @@ wtiff_new( VipsImage *input, const char *filename, } } - /* Can only squash 8 bit mono. 3-band float should have been squashed - * above. + 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. */ - if( wtiff->squash && - !(wtiff->ready->Coding == VIPS_CODING_NONE && + if( wtiff->bitdepth && (wtiff->bitdepth == 1 || wtiff->bitdepth == 2 + || wtiff->bitdepth == 4 ) + && !(wtiff->ready->Coding == VIPS_CODING_NONE && wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && wtiff->ready->Bands == 1) ) { g_warning( "%s", - _( "can only squash 1-band uchar and " - "3-band float lab -- disabling squash" ) ); - wtiff->squash = 0; + ( "can only set bitdepth for 1-band uchar and " + "3-band float lab -- disabling bitdepth" ) ); + wtiff->bitdepth = 0; } - if( wtiff->squash && + if( wtiff->bitdepth && wtiff->compression == COMPRESSION_JPEG ) { g_warning( "%s", - _( "can't have 1-bit JPEG -- disabling JPEG" ) ); + _( "can't have 1,2 or 4-bit JPEG -- disabling JPEG" ) ); wtiff->compression = COMPRESSION_NONE; } @@ -1239,8 +1250,12 @@ wtiff_new( VipsImage *input, const char *filename, */ if( wtiff->ready->Coding == VIPS_CODING_LABQ ) wtiff->tls = wtiff->tilew * 3; - else if( wtiff->squash ) + else if( wtiff->bitdepth == 1) wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8; + else if( wtiff->bitdepth == 2) + wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 4 ) / 4; + else if( wtiff->bitdepth == 4) + wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 2 ) / 2; else wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * wtiff->tilew; @@ -1300,6 +1315,82 @@ eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) *q++ = bits << (8 - (x & 0x7)); } +/* Pack 8 bit VIPS to 2 bit TIFF. + */ +static void +eightbit2twobit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) +{ + int x; + VipsPel bits; + + bits = 0; + if( !wtiff->miniswhite ) { //avoid unnecessary branches + for( x = 0; x < n; x++ ) { + bits <<= 2; + bits |= p[x] >> 6; + + if( (x & 0x3) == 0x3 ) { + *q++ = bits; + bits = 0; + } + } + } + else { + for( x = 0; x < n; x++ ) { + bits <<= 2; + bits |= p[x] >> 6; + + if( (x & 0x3) == 0x3 ) { + *q++ = ~bits; + bits = 0; + } + } + } + + /* Any left-over bits? Need to be left-aligned. + */ + if( (x & 0x3) != 0 ) + *q++ = (wtiff->miniswhite ? ~bits : bits) << (8 - ((x & 0x3) << 1)); +} + +/* Pack 8 bit VIPS to 4 bit TIFF. + */ +static void +eightbit2fourbit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) +{ + int x; + VipsPel bits; + + bits = 0; + if( !wtiff->miniswhite ) { //avoid unnecessary branches + for( x = 0; x < n; x++ ) { + bits <<= 4; + bits |= p[x] >> 4; + + if( (x & 0x1) == 0x1 ) { + *q++ = bits; + bits = 0; + } + } + } + else { + for( x = 0; x < n; x++ ) { + bits <<= 4; + bits |= p[x] >> 4; + + if( (x & 0x1) == 0x1 ) { + *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)); +} + /* Swap the sense of the first channel, if necessary. */ #define GREY_LOOP( TYPE, MAX ) { \ @@ -1444,8 +1535,12 @@ wtiff_pack2tiff( Wtiff *wtiff, Layer *layer, if( wtiff->ready->Coding == VIPS_CODING_LABQ ) LabQ2LabC( q, p, area->width ); - else if( wtiff->squash ) + else if( wtiff->bitdepth == 1 ) eightbit2onebit( wtiff, q, p, area->width ); + else if( wtiff->bitdepth == 2 ) + eightbit2twobit( wtiff, q, p, area->width ); + else if( wtiff->bitdepth == 4 ) + eightbit2fourbit( 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) && @@ -1543,10 +1638,18 @@ wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip ) XYZ2tiffxyz( wtiff->tbuf, p, im->Xsize, im->Bands ); p = wtiff->tbuf; } - else if( wtiff->squash ) { + else if( wtiff->bitdepth == 1 ) { eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize ); p = wtiff->tbuf; } + else if( wtiff->bitdepth == 2) { + eightbit2twobit( wtiff, wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; + } + else if( wtiff->bitdepth == 4) { + eightbit2fourbit( wtiff, wtiff->tbuf, p, im->Xsize ); + p = wtiff->tbuf; + } else if( (im->Bands == 1 || im->Bands == 2) && wtiff->miniswhite ) { invert_band0( wtiff, wtiff->tbuf, p, im->Xsize ); @@ -2042,7 +2145,7 @@ vips__tiff_write( VipsImage *input, const char *filename, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -2064,7 +2167,7 @@ vips__tiff_write( VipsImage *input, const char *filename, if( !(wtiff = wtiff_new( input, filename, compression, Q, predictor, profile, - tile, tile_width, tile_height, pyramid, squash, + tile, tile_width, tile_height, pyramid, bitdepth, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, properties, strip, region_shrink, level, lossless, depth, subifd )) ) @@ -2088,7 +2191,7 @@ vips__tiff_write_buf( VipsImage *input, char *profile, gboolean tile, int tile_width, int tile_height, gboolean pyramid, - gboolean squash, + int bitdepth, gboolean miniswhite, VipsForeignTiffResunit resunit, double xres, double yres, gboolean bigtiff, @@ -2106,7 +2209,7 @@ vips__tiff_write_buf( VipsImage *input, if( !(wtiff = wtiff_new( input, NULL, compression, Q, predictor, profile, - tile, tile_width, tile_height, pyramid, squash, + tile, tile_width, tile_height, pyramid, bitdepth, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, properties, strip, region_shrink, level, lossless, depth, subifd )) ) From 00bd91a3c42a04ca248dcb744f9c54c5172f5229 Mon Sep 17 00:00:00 2001 From: Florian Heinrich Date: Tue, 16 Jun 2020 16:21:52 +0200 Subject: [PATCH 2/3] Fixed issues with bitdepth --- libvips/foreign/tiff2vips.c | 98 +++++++++++++++++++------------------ libvips/foreign/tiffsave.c | 4 +- libvips/foreign/vips2tiff.c | 76 ++++++++++------------------ 3 files changed, 79 insertions(+), 99 deletions(-) diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index e5228d86..546248b6 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -1063,34 +1063,36 @@ 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 x, i, z; - int minisblack = - rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; - VipsPel bits; + int x, i, z; + int minisblack = + rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; + VipsPel twobits, fourbits, bits; - x = 0; - for( i = 0; i < (n >> 2); i++ ) { - bits = (VipsPel) minisblack ? p[i] : ~p[i]; + x = 0; + for( i = 0; i < (n >> 2); i++ ) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; - for( z = 0; z < 4; z++ ) { - /* The grey shade is the value four times concatenated */ - q[x] = (bits & 0xC0) | ((bits & 0xC0) >> 2) - | ((bits & 0xC0) >> 4) | (bits >> 6); - bits <<= 2; - x += 1; - } - } + for( z = 0; z < 4; z++ ) { + /* The grey shade is the value four times concatenated */ + twobits = bits >> 6; + fourbits = twobits | (twobits << 2); + q[x] = fourbits | (fourbits << 4); + bits <<= 2; + x += 1; + } + } - /* Do last byte in line. - */ - if( n & 3 ) { - bits = (VipsPel) minisblack ? p[i] : ~p[i]; - for( z = 0; z < (n & 3) ; z++ ) { - q[x + z] = (bits & 0xC0) | ((bits & 0xC0) >> 2) - | ((bits & 0xC0) >> 4) | (bits >> 6); - bits <<= 2; - } - } + /* Do last byte in line. + */ + if( n & 3 ) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; + for( z = 0; z < (n & 3) ; z++ ) { + twobits = bits >> 6; + fourbits = twobits | (twobits << 2); + q[x + z] = fourbits | (fourbits << 4); + bits <<= 2; + } + } } /* Per-scanline process function for 4 bit greyscale images. @@ -1098,33 +1100,33 @@ 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 x, i, z; - int minisblack = - rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; - VipsPel bits; + int x, i, z; + int minisblack = + rtiff->header.photometric_interpretation == PHOTOMETRIC_MINISBLACK; + VipsPel bits; - x = 0; + x = 0; - for( i = 0; i < (n >> 1); i++ ) { - bits = (VipsPel) minisblack ? p[i] : ~p[i]; + 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 */ - q[x] = (bits & 0xF0) | (bits >> 4); - bits <<= 4; - x += 1; - } - } + for( z = 0; z < 2; z++ ) { + /* The grey shade is the value two times concatenated */ + q[x] = (bits & 0xF0) | (bits >> 4); + bits <<= 4; + x += 1; + } + } - /* Do last byte in line. - */ - if( n & 1) { - bits = (VipsPel) minisblack ? p[i] : ~p[i]; - for( z = 0; z < (n & 1) ; z++ ) { - q[x + z] = (bits & 0xF0) | (bits >> 4); - bits <<= 4; - } - } + /* Do last byte in line. + */ + if( n & 1) { + bits = (VipsPel) minisblack ? p[i] : ~p[i]; + 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 e1fce97a..6e7096ad 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -638,6 +638,8 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * In case @miniswhite is set to true this behavior is inverted. * 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 @@ -703,7 +705,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... ) * * @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 + * * @bitdepth: %int, change bit depth to 1,2 or 4-bit or squash float to 8 bit * * @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 1bf6d5cb..191fdf41 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -197,7 +197,7 @@ * - add support for subifd pyramid layers * 8/6/20 * - add bitdepth support for 2 and 4 bit greyscale images - + */ /* This file is part of VIPS. @@ -1041,7 +1041,7 @@ ready_to_write( Wtiff *wtiff ) /* "squash" float LAB down to LABQ. */ - if( wtiff->bitdepth == 1 && + if( (wtiff->bitdepth && wtiff->input->Bands == 3 && wtiff->input->BandFmt == VIPS_FORMAT_FLOAT && wtiff->input->Type == VIPS_INTERPRETATION_LAB ) { @@ -1201,11 +1201,9 @@ wtiff_new( VipsImage *input, const char *filename, /* Can only have byte fractional bit depths for 8 bit mono. * 3-band float should have been packed above. */ - if( wtiff->bitdepth && (wtiff->bitdepth == 1 || wtiff->bitdepth == 2 - || wtiff->bitdepth == 4 ) - && !(wtiff->ready->Coding == VIPS_CODING_NONE && - wtiff->ready->BandFmt == VIPS_FORMAT_UCHAR && - wtiff->ready->Bands == 1) ) { + 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" ) ); @@ -1250,11 +1248,11 @@ wtiff_new( VipsImage *input, const char *filename, */ if( wtiff->ready->Coding == VIPS_CODING_LABQ ) wtiff->tls = wtiff->tilew * 3; - else if( wtiff->bitdepth == 1) + else if( wtiff->bitdepth == 1 ) wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8; - else if( wtiff->bitdepth == 2) + else if( wtiff->bitdepth == 2 ) wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 4 ) / 4; - else if( wtiff->bitdepth == 4) + else if( wtiff->bitdepth == 4 ) wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 2 ) / 2; else wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * @@ -1322,28 +1320,17 @@ eightbit2twobit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { int x; VipsPel bits; + VipsPel mask = wtiff->miniswhite ? 3 : 0; bits = 0; - if( !wtiff->miniswhite ) { //avoid unnecessary branches - for( x = 0; x < n; x++ ) { - bits <<= 2; - bits |= p[x] >> 6; + + for( x = 0; x < n; x++ ) { + bits <<= 2; + bits |= (p[x] >> 6) ^ mask; - if( (x & 0x3) == 0x3 ) { - *q++ = bits; - bits = 0; - } - } - } - else { - for( x = 0; x < n; x++ ) { - bits <<= 2; - bits |= p[x] >> 6; - - if( (x & 0x3) == 0x3 ) { - *q++ = ~bits; - bits = 0; - } + if( (x & 0x3) == 0x3 ) { + *q++ = bits; + bits = 0; } } @@ -1360,30 +1347,19 @@ eightbit2fourbit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) { int x; VipsPel bits; - + VipsPel mask = wtiff->miniswhite ? 15 : 0; bits = 0; - if( !wtiff->miniswhite ) { //avoid unnecessary branches - for( x = 0; x < n; x++ ) { - bits <<= 4; - bits |= p[x] >> 4; + + for( x = 0; x < n; x++ ) { + bits <<= 4; + bits |= (p[x] >> 4) ^ mask; - if( (x & 0x1) == 0x1 ) { - *q++ = bits; - bits = 0; - } + if( (x & 0x1) == 0x1 ) { + *q++ = bits; + bits = 0; } } - else { - for( x = 0; x < n; x++ ) { - bits <<= 4; - bits |= p[x] >> 4; - if( (x & 0x1) == 0x1 ) { - *q++ = ~bits; - bits = 0; - } - } - } /* Any left-over bits? Need to be left-aligned. */ if( (x & 0x1) != 0 ) @@ -1642,11 +1618,11 @@ wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip ) eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize ); p = wtiff->tbuf; } - else if( wtiff->bitdepth == 2) { + else if( wtiff->bitdepth == 2 ) { eightbit2twobit( wtiff, wtiff->tbuf, p, im->Xsize ); p = wtiff->tbuf; } - else if( wtiff->bitdepth == 4) { + else if( wtiff->bitdepth == 4 ) { eightbit2fourbit( wtiff, wtiff->tbuf, p, im->Xsize ); p = wtiff->tbuf; } From 0f0e8cb677d7d16f657bfed3e02bdd6f6cd62913 Mon Sep 17 00:00:00 2001 From: Florian Heinrich Date: Tue, 16 Jun 2020 16:27:11 +0200 Subject: [PATCH 3/3] Fixed typo in bitdepth code --- libvips/foreign/vips2tiff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 191fdf41..e7adff00 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -1041,7 +1041,7 @@ ready_to_write( Wtiff *wtiff ) /* "squash" float LAB down to LABQ. */ - if( (wtiff->bitdepth && + if( wtiff->bitdepth && wtiff->input->Bands == 3 && wtiff->input->BandFmt == VIPS_FORMAT_FLOAT && wtiff->input->Type == VIPS_INTERPRETATION_LAB ) {