revise tiff2vips 1/2/4 bit load

simpler, faster, smaller
This commit is contained in:
John Cupitt 2020-06-17 13:59:02 +01:00
parent 99d7573ab6
commit 19077b53ac
8 changed files with 78 additions and 122 deletions

View File

@ -199,7 +199,7 @@
* - better handling of aligned reads in multipage tiffs * - better handling of aligned reads in multipage tiffs
* 28/5/20 * 28/5/20
* - add subifd * - add subifd
* 06/6/20 MathemanFlo * 6/6/20 MathemanFlo
* - support 2 and 4 bit greyscale load * - support 2 and 4 bit greyscale load
*/ */
@ -267,6 +267,7 @@ typedef struct _RtiffHeader {
int sample_format; int sample_format;
gboolean separate; gboolean separate;
int orientation; int orientation;
/* If there's a premultiplied alpha, the band we need to /* If there's a premultiplied alpha, the band we need to
* unpremultiply with. -1 for no unpremultiplication. * unpremultiply with. -1 for no unpremultiplication.
*/ */
@ -1019,122 +1020,61 @@ rtiff_parse_logluv( Rtiff *rtiff, VipsImage *out )
return( 0 ); 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 #define NBIT_LINE( N, EXPAND ) \
rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) static void \
{ rtiff_ ## N ## bit_line( Rtiff *rtiff, \
int photometric_interpretation = VipsPel *q, VipsPel *p, int n, void *flg ) \
rtiff->header.photometric_interpretation; { \
int black = photometric_interpretation == PHOTOMETRIC_MINISBLACK ? int photometric = rtiff->header.photometric_interpretation; \
0 : 255; int mask = photometric == PHOTOMETRIC_MINISBLACK ? 0 : 0xff; \
int white = black ^ 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; \
} \
}
int x, i, z; /* Expand the top bit down a byte. Use a sign-extending shift.
VipsPel bits;
/* (sigh) how many times have I written this?
*/ */
x = 0; #define EXPAND1( Q, BITS ) G_STMT_START { \
for( i = 0; i < (n >> 3); i++ ) { (Q) = (((signed char) (BITS & 128)) >> 7); \
bits = (VipsPel) p[i]; } G_STMT_END
for( z = 0; z < 8; z++ ) { /* Expand the top two bits down a byte. Shift down, then expand up.
q[x] = (bits & 128) ? white : black;
bits <<= 1;
x += 1;
}
}
/* Do last byte in line.
*/ */
if( n & 7 ) { #define EXPAND2( Q, BITS ) G_STMT_START { \
bits = p[i]; VipsPel twobits = BITS >> 6; \
VipsPel fourbits = twobits | (twobits << 2); \
Q = fourbits | (fourbits << 4); \
} G_STMT_END
for( z = 0; z < (n & 7); z++ ) { /* Expand the top four bits down a byte.
q[x + z] = (bits & 128) ? white : black;
bits <<= 1;
}
}
}
/* Per-scanline process function for 2 bit greyscale images.
*/ */
static void #define EXPAND4( Q, BITS ) G_STMT_START { \
rtiff_twobit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg ) Q = (BITS & 0xf0) | (BITS >> 4); \
{ } G_STMT_END
int photometric_interpretation =
rtiff->header.photometric_interpretation;
int minisblack = photometric_interpretation == PHOTOMETRIC_MINISBLACK;
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.
*/
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;
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;
}
}
}
NBIT_LINE( 1, EXPAND1 )
NBIT_LINE( 2, EXPAND2 )
NBIT_LINE( 4, EXPAND4 )
/* Read a 1-bit TIFF image. /* Read a 1-bit TIFF image.
*/ */
@ -1150,7 +1090,7 @@ rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out )
out->Coding = VIPS_CODING_NONE; out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_B_W; out->Type = VIPS_INTERPRETATION_B_W;
rtiff->sfn = rtiff_onebit_line; rtiff->sfn = rtiff_1bit_line;
return( 0 ); return( 0 );
} }
@ -1169,7 +1109,7 @@ rtiff_parse_twobit( Rtiff *rtiff, VipsImage *out )
out->Coding = VIPS_CODING_NONE; out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_B_W; out->Type = VIPS_INTERPRETATION_B_W;
rtiff->sfn = rtiff_twobit_line; rtiff->sfn = rtiff_2bit_line;
return( 0 ); return( 0 );
} }
@ -1188,7 +1128,7 @@ rtiff_parse_fourbit( Rtiff *rtiff, VipsImage *out )
out->Coding = VIPS_CODING_NONE; out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_B_W; out->Type = VIPS_INTERPRETATION_B_W;
rtiff->sfn = rtiff_fourbit_line; rtiff->sfn = rtiff_4bit_line;
return( 0 ); return( 0 );
} }

View File

@ -195,7 +195,7 @@
* - add PAGENUMBER support * - add PAGENUMBER support
* 23/5/20 * 23/5/20
* - add support for subifd pyramid layers * - add support for subifd pyramid layers
* 06/6/20 MathemanFlo * 6/6/20 MathemanFlo
* - add bitdepth support for 2 and 4 bit greyscale images * - 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; bits = 0;
for( x = 0; x < n; x++ ) { for( x = 0; x < n; x++ ) {
bits <<= 1; bits <<= 1;
if( p[x] > 128 ) if( p[x] >= 128 )
bits |= white; bits |= white;
else else
bits |= black; bits |= black;

View File

@ -14,8 +14,9 @@ SRGB_FILE = os.path.join(IMAGES, "sRGB.icm")
MATLAB_FILE = os.path.join(IMAGES, "sample.mat") MATLAB_FILE = os.path.join(IMAGES, "sample.mat")
PNG_FILE = os.path.join(IMAGES, "sample.png") PNG_FILE = os.path.join(IMAGES, "sample.png")
TIF_FILE = os.path.join(IMAGES, "sample.tif") TIF_FILE = os.path.join(IMAGES, "sample.tif")
TIF2_FILE = os.path.join(IMAGES, "result2Bit.tif") TIF1_FILE = os.path.join(IMAGES, "1bit.tif")
TIF4_FILE = os.path.join(IMAGES, "result4Bit.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") OME_FILE = os.path.join(IMAGES, "multi-channel-z-series.ome.tif")
ANALYZE_FILE = os.path.join(IMAGES, "t00740_tr1_segm.hdr") ANALYZE_FILE = os.path.join(IMAGES, "t00740_tr1_segm.hdr")
GIF_FILE = os.path.join(IMAGES, "cramps.gif") GIF_FILE = os.path.join(IMAGES, "cramps.gif")

Binary file not shown.

Binary file not shown.

View File

@ -17,7 +17,7 @@ from helpers import \
GIF_ANIM_DISPOSE_PREVIOUS_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_FILE, \
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \ GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
temp_filename, assert_almost_equal_objects, have, skip_if_no, \ temp_filename, assert_almost_equal_objects, have, skip_if_no, \
TIF2_FILE, TIF4_FILE TIF1_FILE, TIF2_FILE, TIF4_FILE
class TestForeign: class TestForeign:
@ -325,9 +325,22 @@ class TestForeign:
self.file_loader("tiffload", TIF_FILE, tiff_valid) self.file_loader("tiffload", TIF_FILE, tiff_valid)
self.buffer_loader("tiffload_buffer", 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): def tiff2_valid(im):
a = im(80, 0) a = im(127, 0)
assert_almost_equal_objects(a, [85.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.width == 256
assert im.height == 4 assert im.height == 4
assert im.bands == 1 assert im.bands == 1
@ -335,8 +348,10 @@ class TestForeign:
self.file_loader("tiffload", TIF2_FILE, tiff2_valid) self.file_loader("tiffload", TIF2_FILE, tiff2_valid)
def tiff4_valid(im): def tiff4_valid(im):
a = im(109, 0) a = im(127, 0)
assert_almost_equal_objects(a, [102.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.width == 256
assert im.height == 4 assert im.height == 4
assert im.bands == 1 assert im.bands == 1
@ -372,10 +387,10 @@ class TestForeign:
self.save_load_file(".tif", self.save_load_file(".tif",
"[tile,tile-width=256]", self.colour, 10) "[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) im = pyvips.Image.new_from_file(TIF2_FILE)
self.save_load_file(".tif", "[bitdepth=2]", im, 0) 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') filename = temp_filename(self.tempdir, '.tif')
x = pyvips.Image.new_from_file(TIF_FILE) x = pyvips.Image.new_from_file(TIF_FILE)