From 19077b53ac213510dda1d2d8553a7334333fc7a8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 17 Jun 2020 13:59:02 +0100 Subject: [PATCH] revise tiff2vips 1/2/4 bit load simpler, faster, smaller --- libvips/foreign/tiff2vips.c | 164 ++++++------------ libvips/foreign/vips2tiff.c | 4 +- test/test-suite/helpers/helpers.py | 5 +- test/test-suite/images/1bit.tif | Bin 0 -> 338 bytes test/test-suite/images/2bit.tif | Bin 0 -> 466 bytes .../images/{result4Bit.tif => 4bit.tif} | Bin test/test-suite/images/result2Bit.tif | Bin 1234 -> 0 bytes test/test-suite/test_foreign.py | 27 ++- 8 files changed, 78 insertions(+), 122 deletions(-) create mode 100644 test/test-suite/images/1bit.tif create mode 100644 test/test-suite/images/2bit.tif rename test/test-suite/images/{result4Bit.tif => 4bit.tif} (100%) delete mode 100644 test/test-suite/images/result2Bit.tif diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index 50c87934..fc524483 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -199,7 +199,7 @@ * - better handling of aligned reads in multipage tiffs * 28/5/20 * - add subifd - * 06/6/20 MathemanFlo + * 6/6/20 MathemanFlo * - support 2 and 4 bit greyscale load */ @@ -267,6 +267,7 @@ typedef struct _RtiffHeader { int sample_format; gboolean separate; int orientation; + /* If there's a premultiplied alpha, the band we need to * unpremultiply with. -1 for no unpremultiplication. */ @@ -1019,122 +1020,61 @@ rtiff_parse_logluv( Rtiff *rtiff, VipsImage *out ) return( 0 ); } -/* Per-scanline process function for 1 bit images. +/* Make a N-bit scanline process function. We pass in the code to expand the + * bits down the byte since this does not generalize well. */ -static void -rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) -{ - int photometric_interpretation = - rtiff->header.photometric_interpretation; - 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; - 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; - } - } +#define NBIT_LINE( N, EXPAND ) \ +static void \ +rtiff_ ## N ## bit_line( Rtiff *rtiff, \ + VipsPel *q, VipsPel *p, int n, void *flg ) \ +{ \ + int photometric = rtiff->header.photometric_interpretation; \ + int mask = photometric == PHOTOMETRIC_MINISBLACK ? 0 : 0xff; \ + int bps = rtiff->header.bits_per_sample; \ + int load = 8 / bps - 1; \ + \ + int x; \ + VipsPel bits; \ + \ + /* Stop a compiler warning. \ + */ \ + bits = 0; \ + \ + for( x = 0; x < n; x++ ) { \ + if( (x & load) == 0 ) \ + /* Flip the bits for miniswhite. \ + */ \ + bits = *p++ ^ mask; \ + \ + EXPAND( q[x], bits ); \ + \ + bits <<= bps; \ + } \ } -/* Per-scanline process function for 2 bit greyscale images. +/* Expand the top bit down a byte. Use a sign-extending shift. */ -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; +#define EXPAND1( Q, BITS ) G_STMT_START { \ + (Q) = (((signed char) (BITS & 128)) >> 7); \ +} G_STMT_END - int x, i, z; - VipsPel twobits, fourbits, 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. - */ - 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++ ) { - twobits = bits >> 6; - fourbits = twobits | (twobits << 2); - q[x + z] = fourbits | (fourbits << 4); - bits <<= 2; - } - } -} - -/* Per-scanline process function for 4 bit greyscale images. +/* Expand the top two bits down a byte. Shift down, then expand up. */ -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; +#define EXPAND2( Q, BITS ) G_STMT_START { \ + VipsPel twobits = BITS >> 6; \ + VipsPel fourbits = twobits | (twobits << 2); \ + Q = fourbits | (fourbits << 4); \ +} G_STMT_END - int x, i, z; - 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; - } - } -} +/* Expand the top four bits down a byte. + */ +#define EXPAND4( Q, BITS ) G_STMT_START { \ + Q = (BITS & 0xf0) | (BITS >> 4); \ +} G_STMT_END +NBIT_LINE( 1, EXPAND1 ) +NBIT_LINE( 2, EXPAND2 ) +NBIT_LINE( 4, EXPAND4 ) /* Read a 1-bit TIFF image. */ @@ -1150,7 +1090,7 @@ rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_B_W; - rtiff->sfn = rtiff_onebit_line; + rtiff->sfn = rtiff_1bit_line; return( 0 ); } @@ -1169,7 +1109,7 @@ rtiff_parse_twobit( Rtiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_B_W; - rtiff->sfn = rtiff_twobit_line; + rtiff->sfn = rtiff_2bit_line; return( 0 ); } @@ -1188,7 +1128,7 @@ rtiff_parse_fourbit( Rtiff *rtiff, VipsImage *out ) out->Coding = VIPS_CODING_NONE; out->Type = VIPS_INTERPRETATION_B_W; - rtiff->sfn = rtiff_fourbit_line; + rtiff->sfn = rtiff_4bit_line; return( 0 ); } diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 36c22f9e..d4a395d1 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -195,7 +195,7 @@ * - add PAGENUMBER support * 23/5/20 * - add support for subifd pyramid layers - * 06/6/20 MathemanFlo + * 6/6/20 MathemanFlo * - add bitdepth support for 2 and 4 bit greyscale images */ @@ -1301,7 +1301,7 @@ eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n ) bits = 0; for( x = 0; x < n; x++ ) { bits <<= 1; - if( p[x] > 128 ) + if( p[x] >= 128 ) bits |= white; else bits |= black; diff --git a/test/test-suite/helpers/helpers.py b/test/test-suite/helpers/helpers.py index 175954fd..abc70204 100644 --- a/test/test-suite/helpers/helpers.py +++ b/test/test-suite/helpers/helpers.py @@ -14,8 +14,9 @@ SRGB_FILE = os.path.join(IMAGES, "sRGB.icm") MATLAB_FILE = os.path.join(IMAGES, "sample.mat") PNG_FILE = os.path.join(IMAGES, "sample.png") TIF_FILE = os.path.join(IMAGES, "sample.tif") -TIF2_FILE = os.path.join(IMAGES, "result2Bit.tif") -TIF4_FILE = os.path.join(IMAGES, "result4Bit.tif") +TIF1_FILE = os.path.join(IMAGES, "1bit.tif") +TIF2_FILE = os.path.join(IMAGES, "2bit.tif") +TIF4_FILE = os.path.join(IMAGES, "4bit.tif") OME_FILE = os.path.join(IMAGES, "multi-channel-z-series.ome.tif") ANALYZE_FILE = os.path.join(IMAGES, "t00740_tr1_segm.hdr") GIF_FILE = os.path.join(IMAGES, "cramps.gif") diff --git a/test/test-suite/images/1bit.tif b/test/test-suite/images/1bit.tif new file mode 100644 index 0000000000000000000000000000000000000000..c95a1ab22005244252ad415d6aab809384e9fe25 GIT binary patch literal 338 zcmebD)MDshzy$x%0J;h^>Hn0<^D{6oGBYrOi~$miP&NyY%LHYEe7 I9Goy30Eo0s)c^nh literal 0 HcmV?d00001 diff --git a/test/test-suite/images/2bit.tif b/test/test-suite/images/2bit.tif new file mode 100644 index 0000000000000000000000000000000000000000..0c06fe4ad3efe9eeb80b70d30fc715b3563cc4e5 GIT binary patch literal 466 zcmebD)MDUZWWWHSXkZl@_>TtA4M5W#il%)Pn)d$#t)HKPfsvU391uW)5z1x(I*SR) zW&*OAp=^*kHYBznBMVqB2T)uHNn8|(Ee2&b0M&>?)q^xiF|va79s;tZq3lyYwhWS9 Y4Jex#s5cl%9LQw=h2-8^4o(;i0Q=95m;e9( literal 0 HcmV?d00001 diff --git a/test/test-suite/images/result4Bit.tif b/test/test-suite/images/4bit.tif similarity index 100% rename from test/test-suite/images/result4Bit.tif rename to test/test-suite/images/4bit.tif diff --git a/test/test-suite/images/result2Bit.tif b/test/test-suite/images/result2Bit.tif deleted file mode 100644 index 68849e977dac4f965401a57b4287d2d1c486870b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1234 zcmebD)MDUZVW1dw$DC=CD#y&p=U_g7Kq{r{u>rz`}9di?VAR`M{FG!6LlDH@mTMWu>0ICs(s%HRNAjQZE)_VxZ hmWHxV0ogK0dNrVIW}x0+Byk{@0hIFh)^c#dXaLM+e0 diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index fe5911d3..698cc835 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -17,7 +17,7 @@ from helpers import \ GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \ temp_filename, assert_almost_equal_objects, have, skip_if_no, \ - TIF2_FILE, TIF4_FILE + TIF1_FILE, TIF2_FILE, TIF4_FILE class TestForeign: @@ -325,9 +325,22 @@ class TestForeign: self.file_loader("tiffload", TIF_FILE, tiff_valid) self.buffer_loader("tiffload_buffer", TIF_FILE, tiff_valid) + def tiff1_valid(im): + a = im(127, 0) + assert_almost_equal_objects(a, [0.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [255.0]) + assert im.width == 256 + assert im.height == 4 + assert im.bands == 1 + + self.file_loader("tiffload", TIF1_FILE, tiff1_valid) + def tiff2_valid(im): - a = im(80, 0) + a = im(127, 0) assert_almost_equal_objects(a, [85.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [170.0]) assert im.width == 256 assert im.height == 4 assert im.bands == 1 @@ -335,8 +348,10 @@ class TestForeign: self.file_loader("tiffload", TIF2_FILE, tiff2_valid) def tiff4_valid(im): - a = im(109, 0) - assert_almost_equal_objects(a, [102.0]) + a = im(127, 0) + assert_almost_equal_objects(a, [119.0]) + a = im(128, 0) + assert_almost_equal_objects(a, [136.0]) assert im.width == 256 assert im.height == 4 assert im.bands == 1 @@ -372,10 +387,10 @@ class TestForeign: self.save_load_file(".tif", "[tile,tile-width=256]", self.colour, 10) - im = pyvips.Image.new_from_file(TIF4_FILE) - self.save_load_file(".tif", "[bitdepth=4]", im, 0) im = pyvips.Image.new_from_file(TIF2_FILE) self.save_load_file(".tif", "[bitdepth=2]", im, 0) + im = pyvips.Image.new_from_file(TIF4_FILE) + self.save_load_file(".tif", "[bitdepth=4]", im, 0) filename = temp_filename(self.tempdir, '.tif') x = pyvips.Image.new_from_file(TIF_FILE)