From c3977c451316a8d66d51129012b9c85652bff4d4 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Tue, 10 Mar 2026 00:11:45 -0600 Subject: [PATCH] test: expand test suite to 74 images with libtiff reference decoder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 19 new libtiff-generated test images and a reference decoder (verify_seq_tiff.c) that independently verifies all checksums using libtiff's own read path, giving three-way agreement: libtiff writer → libtiff reader → image-tiff reader New coverage: - Standard sub-byte: 1-bit, 2-bit, 4-bit grayscale (BIZ + WIZ) - Multi-strip: RowsPerStrip=4 (gray 8/16, RGB 8) - Tiled + compressed: LZW and Deflate tiles - Float predictor: f32/f64 + Deflate + predictor=3 - Unassociated alpha: RGBA 8-bit with EXTRASAMPLE_UNASSALPHA - Sub-byte RGB: 5-bit and 7-bit contiguous - Signed RGB: i8 and i16 (3-channel, decoded as Multiband) - Float f64 RGB - Tiled BigTIFF verify_seq_tiff.c: reads each TIFF with libtiff, computes matching checksums (handling packed sub-byte, unpacked extended, float, signed), and verifies 68/68 cases pass. Total: 74 test images (66.8 KB), 317 tests all passing. --- tests/decode_images.rs | 122 +++++ tests/gen_seq_tiff.c | 370 ++++++++++++++ tests/images/seq-1c-16b-multistrip.tiff | Bin 0 -> 682 bytes tests/images/seq-1c-1b-miniswhite.tiff | Bin 0 -> 178 bytes tests/images/seq-1c-1b.tiff | Bin 0 -> 178 bytes tests/images/seq-1c-2b.tiff | Bin 0 -> 210 bytes tests/images/seq-1c-32f-deflate-fpredict.tiff | Bin 0 -> 298 bytes tests/images/seq-1c-4b-miniswhite.tiff | Bin 0 -> 274 bytes tests/images/seq-1c-4b.tiff | Bin 0 -> 274 bytes tests/images/seq-1c-64f-deflate-fpredict.tiff | Bin 0 -> 318 bytes tests/images/seq-1c-8b-multistrip.tiff | Bin 0 -> 426 bytes tests/images/seq-1c-8b-tiled-bigtiff.tiff | Bin 0 -> 528 bytes tests/images/seq-1c-8b-tiled-deflate.tiff | Bin 0 -> 426 bytes tests/images/seq-1c-8b-tiled-lzw.tiff | Bin 0 -> 450 bytes tests/images/seq-3c-5b-contig.tiff | Bin 0 -> 638 bytes tests/images/seq-3c-64f.tiff | Bin 0 -> 6302 bytes tests/images/seq-3c-7b-contig.tiff | Bin 0 -> 830 bytes tests/images/seq-3c-8b-multistrip.tiff | Bin 0 -> 950 bytes tests/images/seq-3c-i16.tiff | Bin 0 -> 1694 bytes tests/images/seq-3c-i8.tiff | Bin 0 -> 926 bytes tests/images/seq-4c-8b-rgba-unassoc.tiff | Bin 0 -> 1198 bytes tests/verify_seq_tiff.c | 460 ++++++++++++++++++ 22 files changed, 952 insertions(+) create mode 100644 tests/images/seq-1c-16b-multistrip.tiff create mode 100644 tests/images/seq-1c-1b-miniswhite.tiff create mode 100644 tests/images/seq-1c-1b.tiff create mode 100644 tests/images/seq-1c-2b.tiff create mode 100644 tests/images/seq-1c-32f-deflate-fpredict.tiff create mode 100644 tests/images/seq-1c-4b-miniswhite.tiff create mode 100644 tests/images/seq-1c-4b.tiff create mode 100644 tests/images/seq-1c-64f-deflate-fpredict.tiff create mode 100644 tests/images/seq-1c-8b-multistrip.tiff create mode 100644 tests/images/seq-1c-8b-tiled-bigtiff.tiff create mode 100644 tests/images/seq-1c-8b-tiled-deflate.tiff create mode 100644 tests/images/seq-1c-8b-tiled-lzw.tiff create mode 100644 tests/images/seq-3c-5b-contig.tiff create mode 100644 tests/images/seq-3c-64f.tiff create mode 100644 tests/images/seq-3c-7b-contig.tiff create mode 100644 tests/images/seq-3c-8b-multistrip.tiff create mode 100644 tests/images/seq-3c-i16.tiff create mode 100644 tests/images/seq-3c-i8.tiff create mode 100644 tests/images/seq-4c-8b-rgba-unassoc.tiff create mode 100644 tests/verify_seq_tiff.c diff --git a/tests/decode_images.rs b/tests/decode_images.rs index 1170f25..5ccc79c 100644 --- a/tests/decode_images.rs +++ b/tests/decode_images.rs @@ -1318,3 +1318,125 @@ fn test_seq_1c_16b_bigendian() { fn test_seq_3c_8b_bigendian() { test_image_sum_u8("seq-3c-8b-bigendian.tiff", ColorType::RGB(8), 97920); } + +// --- Standard sub-byte depths --- +#[test] +fn test_seq_1c_1b() { + test_image_sum_u8("seq-1c-1b.tiff", ColorType::Gray(1), 2720); +} + +#[test] +fn test_seq_1c_2b() { + test_image_sum_u8("seq-1c-2b.tiff", ColorType::Gray(2), 1728); +} + +#[test] +fn test_seq_1c_4b() { + test_image_sum_u8("seq-1c-4b.tiff", ColorType::Gray(4), 15360); +} + +#[test] +fn test_seq_1c_1b_miniswhite() { + test_image_sum_u8("seq-1c-1b-miniswhite.tiff", ColorType::Gray(1), 2720); +} + +#[test] +fn test_seq_1c_4b_miniswhite() { + test_image_sum_u8("seq-1c-4b-miniswhite.tiff", ColorType::Gray(4), 15360); +} + +// --- Multi-strip (RowsPerStrip=4) --- +#[test] +fn test_seq_1c_8b_multistrip() { + test_image_sum_u8("seq-1c-8b-multistrip.tiff", ColorType::Gray(8), 32640); +} + +#[test] +fn test_seq_1c_16b_multistrip() { + test_image_sum_u16("seq-1c-16b-multistrip.tiff", ColorType::Gray(16), 32640); +} + +#[test] +fn test_seq_3c_8b_multistrip() { + test_image_sum_u8("seq-3c-8b-multistrip.tiff", ColorType::RGB(8), 97920); +} + +// --- Tiled + compressed --- +#[test] +fn test_seq_1c_8b_tiled_lzw() { + test_image_sum_u8("seq-1c-8b-tiled-lzw.tiff", ColorType::Gray(8), 32640); +} + +#[test] +fn test_seq_1c_8b_tiled_deflate() { + test_image_sum_u8("seq-1c-8b-tiled-deflate.tiff", ColorType::Gray(8), 32640); +} + +// --- Float + floating-point predictor --- +#[test] +fn test_seq_1c_32f_deflate_fpredict() { + test_image_sum_f32("seq-1c-32f-deflate-fpredict.tiff", ColorType::Gray(32), 128.00009); +} + +#[test] +fn test_seq_1c_64f_deflate_fpredict() { + test_image_sum_f64( + "seq-1c-64f-deflate-fpredict.tiff", + ColorType::Gray(64), + 127.99999999999986, + ); +} + +// --- Unassociated alpha --- +#[test] +fn test_seq_4c_8b_rgba_unassoc() { + test_image_sum_u8("seq-4c-8b-rgba-unassoc.tiff", ColorType::RGBA(8), 130560); +} + +// --- Sub-byte RGB contiguous --- +#[test] +fn test_seq_3c_5b_contig() { + test_image_sum_u8("seq-3c-5b-contig.tiff", ColorType::RGB(5), 56832); +} + +#[test] +fn test_seq_3c_7b_contig() { + test_image_sum_u8("seq-3c-7b-contig.tiff", ColorType::RGB(7), 74976); +} + +// --- Signed RGB (decoded as Multiband since PHOTOMETRIC_MINISBLACK) --- +#[test] +fn test_seq_3c_i8() { + test_image_sum_i8( + "seq-3c-i8.tiff", + ColorType::Multiband { + bit_depth: 8, + num_samples: 3, + }, + -384, + ); +} + +#[test] +fn test_seq_3c_i16() { + test_image_sum_i16( + "seq-3c-i16.tiff", + ColorType::Multiband { + bit_depth: 16, + num_samples: 3, + }, + -384, + ); +} + +// --- Float f64 RGB --- +#[test] +fn test_seq_3c_64f() { + test_image_sum_f64("seq-3c-64f.tiff", ColorType::RGB(64), 383.99999999999994); +} + +// --- Tiled BigTIFF --- +#[test] +fn test_seq_1c_8b_tiled_bigtiff() { + test_image_sum_u8("seq-1c-8b-tiled-bigtiff.tiff", ColorType::Gray(8), 32640); +} diff --git a/tests/gen_seq_tiff.c b/tests/gen_seq_tiff.c index 462f649..89f5661 100644 --- a/tests/gen_seq_tiff.c +++ b/tests/gen_seq_tiff.c @@ -671,6 +671,230 @@ static int write_rgba(const char *path, int bits, const char *mode) return 0; } +/* Write a standard-depth grayscale TIFF with configurable RowsPerStrip (multi-strip). */ +static int write_gray_multistrip(const char *path, int bits, int rows_per_strip) +{ + TIFF *tif = TIFFOpen(path, "w"); + if (!tif) { fprintf(stderr, "Cannot open %s\n", path); return -1; } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip); + + uint32_t max_val = (bits < 32) ? ((1u << bits) - 1) : 0xFFFFFFFFu; + tsize_t scanline_size = TIFFScanlineSize(tif); + uint8_t *buf = (uint8_t *)_TIFFmalloc(scanline_size); + if (!buf) { TIFFClose(tif); return -1; } + + for (int y = 0; y < H; y++) { + memset(buf, 0, scanline_size); + for (int x = 0; x < W; x++) { + uint32_t val = (y * W + x) % (max_val + 1); + if (bits == 8) + buf[x] = (uint8_t)val; + else if (bits == 16) + ((uint16_t *)buf)[x] = (uint16_t)val; + } + if (TIFFWriteScanline(tif, buf, y, 0) < 0) { + fprintf(stderr, "Error writing scanline %d to %s\n", y, path); + _TIFFfree(buf); TIFFClose(tif); return -1; + } + } + + _TIFFfree(buf); + TIFFClose(tif); + return 0; +} + +/* Write a standard-depth RGB TIFF with configurable RowsPerStrip. */ +static int write_rgb_multistrip(const char *path, int bits, int rows_per_strip) +{ + TIFF *tif = TIFFOpen(path, "w"); + if (!tif) { fprintf(stderr, "Cannot open %s\n", path); return -1; } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rows_per_strip); + + uint32_t max_val = (bits < 32) ? ((1u << bits) - 1) : 0xFFFFFFFFu; + tsize_t scanline_size = TIFFScanlineSize(tif); + uint8_t *buf = (uint8_t *)_TIFFmalloc(scanline_size); + if (!buf) { TIFFClose(tif); return -1; } + + for (int y = 0; y < H; y++) { + memset(buf, 0, scanline_size); + for (int x = 0; x < W; x++) { + for (int c = 0; c < 3; c++) { + uint32_t idx = (y * W + x) * 3 + c; + uint32_t val = idx % (max_val + 1); + if (bits == 8) + buf[x * 3 + c] = (uint8_t)val; + else if (bits == 16) + ((uint16_t *)buf)[x * 3 + c] = (uint16_t)val; + } + } + if (TIFFWriteScanline(tif, buf, y, 0) < 0) { + fprintf(stderr, "Error writing scanline %d to %s\n", y, path); + _TIFFfree(buf); TIFFClose(tif); return -1; + } + } + + _TIFFfree(buf); + TIFFClose(tif); + return 0; +} + +/* Write a tiled grayscale TIFF with compression. */ +static int write_gray_tiled_comp(const char *path, int bits, + uint16_t compression, const char *mode) +{ + TIFF *tif = TIFFOpen(path, mode); + if (!tif) { fprintf(stderr, "Cannot open %s\n", path); return -1; } + + int tw = 16, th = 16; + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + TIFFSetField(tif, TIFFTAG_COMPRESSION, compression); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_TILEWIDTH, tw); + TIFFSetField(tif, TIFFTAG_TILELENGTH, th); + + uint32_t max_val = (bits < 32) ? ((1u << bits) - 1) : 0xFFFFFFFFu; + tsize_t tile_size = TIFFTileSize(tif); + uint8_t *tile_buf = (uint8_t *)_TIFFmalloc(tile_size); + if (!tile_buf) { TIFFClose(tif); return -1; } + + for (int ty = 0; ty < H; ty += th) { + for (int tx = 0; tx < W; tx += tw) { + memset(tile_buf, 0, tile_size); + for (int row = 0; row < th; row++) { + for (int col = 0; col < tw; col++) { + int y = ty + row, x = tx + col; + uint32_t val = (y * W + x) % (max_val + 1); + if (bits == 8) + tile_buf[row * tw + col] = (uint8_t)val; + else if (bits == 16) + ((uint16_t *)tile_buf)[row * tw + col] = (uint16_t)val; + } + } + if (TIFFWriteTile(tif, tile_buf, tx, ty, 0, 0) < 0) { + fprintf(stderr, "Error writing tile at (%d,%d) to %s\n", tx, ty, path); + _TIFFfree(tile_buf); TIFFClose(tif); return -1; + } + } + } + + _TIFFfree(tile_buf); + TIFFClose(tif); + return 0; +} + +/* Write a grayscale float TIFF with compression + predictor. + * Values are (y*W+x)/255.0 */ +static int write_gray_float_comp(const char *path, int float_bits, + uint16_t compression, uint16_t predictor, + const char *mode) +{ + TIFF *tif = TIFFOpen(path, mode); + if (!tif) { fprintf(stderr, "Cannot open %s\n", path); return -1; } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, float_bits); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + TIFFSetField(tif, TIFFTAG_COMPRESSION, compression); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, H); + if (predictor != PREDICTOR_NONE) + TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor); + + tsize_t scanline_size = TIFFScanlineSize(tif); + uint8_t *buf = (uint8_t *)_TIFFmalloc(scanline_size); + if (!buf) { TIFFClose(tif); return -1; } + + for (int y = 0; y < H; y++) { + memset(buf, 0, scanline_size); + for (int x = 0; x < W; x++) { + double val = (double)(y * W + x) / 255.0; + if (float_bits == 32) + ((float *)buf)[x] = (float)val; + else + ((double *)buf)[x] = val; + } + if (TIFFWriteScanline(tif, buf, y, 0) < 0) { + fprintf(stderr, "Error writing scanline %d to %s\n", y, path); + _TIFFfree(buf); TIFFClose(tif); return -1; + } + } + + _TIFFfree(buf); + TIFFClose(tif); + return 0; +} + +/* Write a signed integer RGB TIFF. Values cycle -128..127 per channel. */ +static int write_rgb_signed(const char *path, int bits, const char *mode) +{ + TIFF *tif = TIFFOpen(path, mode); + if (!tif) { fprintf(stderr, "Cannot open %s\n", path); return -1; } + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bits); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_INT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, H); + + tsize_t scanline_size = TIFFScanlineSize(tif); + uint8_t *buf = (uint8_t *)_TIFFmalloc(scanline_size); + if (!buf) { TIFFClose(tif); return -1; } + + for (int y = 0; y < H; y++) { + memset(buf, 0, scanline_size); + for (int x = 0; x < W; x++) { + for (int c = 0; c < 3; c++) { + int32_t idx = (y * W + x) * 3 + c; + if (bits == 8) { + int8_t val = (int8_t)((idx % 256) - 128); + ((int8_t *)buf)[x * 3 + c] = val; + } else if (bits == 16) { + int16_t val = (int16_t)((idx % 256) - 128); + ((int16_t *)buf)[x * 3 + c] = val; + } + } + } + if (TIFFWriteScanline(tif, buf, y, 0) < 0) { + fprintf(stderr, "Error writing scanline %d to %s\n", y, path); + _TIFFfree(buf); TIFFClose(tif); return -1; + } + } + + _TIFFfree(buf); + TIFFClose(tif); + return 0; +} + /* Write a multi-page TIFF with 3 gray pages. Page n has values offset by n*50. */ static int write_multipage(const char *path, int bits, const char *mode) { @@ -976,6 +1200,152 @@ int main(int argc, char **argv) printf(" OK seq-3c-8b-bigendian.tiff\n"); else { printf(" FAIL seq-3c-8b-bigendian.tiff\n"); ok = 0; } + printf("\n--- Standard sub-byte depths ---\n"); + + /* 1-bit, 2-bit, 4-bit BlackIsZero */ + { + int stdbits[] = {1, 2, 4}; + for (int i = 0; i < 3; i++) { + int b = stdbits[i]; + snprintf(path, sizeof(path), "%s/seq-1c-%db.tiff", dir, b); + if (write_gray(path, b, PHOTOMETRIC_MINISBLACK, PREDICTOR_NONE) == 0) + printf(" OK seq-1c-%db.tiff\n", b); + else { printf(" FAIL seq-1c-%db.tiff\n", b); ok = 0; } + } + } + + /* 1-bit, 4-bit WhiteIsZero */ + { + int wbits[] = {1, 4}; + for (int i = 0; i < 2; i++) { + int b = wbits[i]; + snprintf(path, sizeof(path), "%s/seq-1c-%db-miniswhite.tiff", dir, b); + if (write_gray(path, b, PHOTOMETRIC_MINISWHITE, PREDICTOR_NONE) == 0) + printf(" OK seq-1c-%db-miniswhite.tiff\n", b); + else { printf(" FAIL seq-1c-%db-miniswhite.tiff\n", b); ok = 0; } + } + } + + printf("\n--- Multi-strip (RowsPerStrip=4) ---\n"); + + snprintf(path, sizeof(path), "%s/seq-1c-8b-multistrip.tiff", dir); + if (write_gray_multistrip(path, 8, 4) == 0) + printf(" OK seq-1c-8b-multistrip.tiff\n"); + else { printf(" FAIL seq-1c-8b-multistrip.tiff\n"); ok = 0; } + + snprintf(path, sizeof(path), "%s/seq-1c-16b-multistrip.tiff", dir); + if (write_gray_multistrip(path, 16, 4) == 0) + printf(" OK seq-1c-16b-multistrip.tiff\n"); + else { printf(" FAIL seq-1c-16b-multistrip.tiff\n"); ok = 0; } + + snprintf(path, sizeof(path), "%s/seq-3c-8b-multistrip.tiff", dir); + if (write_rgb_multistrip(path, 8, 4) == 0) + printf(" OK seq-3c-8b-multistrip.tiff\n"); + else { printf(" FAIL seq-3c-8b-multistrip.tiff\n"); ok = 0; } + + printf("\n--- Tiled + compressed ---\n"); + + snprintf(path, sizeof(path), "%s/seq-1c-8b-tiled-lzw.tiff", dir); + if (write_gray_tiled_comp(path, 8, COMPRESSION_LZW, "w") == 0) + printf(" OK seq-1c-8b-tiled-lzw.tiff\n"); + else { printf(" FAIL seq-1c-8b-tiled-lzw.tiff\n"); ok = 0; } + + snprintf(path, sizeof(path), "%s/seq-1c-8b-tiled-deflate.tiff", dir); + if (write_gray_tiled_comp(path, 8, COMPRESSION_DEFLATE, "w") == 0) + printf(" OK seq-1c-8b-tiled-deflate.tiff\n"); + else { printf(" FAIL seq-1c-8b-tiled-deflate.tiff\n"); ok = 0; } + + printf("\n--- Float + predictor ---\n"); + + snprintf(path, sizeof(path), "%s/seq-1c-32f-deflate-fpredict.tiff", dir); + if (write_gray_float_comp(path, 32, COMPRESSION_DEFLATE, PREDICTOR_FLOATINGPOINT, "w") == 0) + printf(" OK seq-1c-32f-deflate-fpredict.tiff\n"); + else { printf(" FAIL seq-1c-32f-deflate-fpredict.tiff\n"); ok = 0; } + + snprintf(path, sizeof(path), "%s/seq-1c-64f-deflate-fpredict.tiff", dir); + if (write_gray_float_comp(path, 64, COMPRESSION_DEFLATE, PREDICTOR_FLOATINGPOINT, "w") == 0) + printf(" OK seq-1c-64f-deflate-fpredict.tiff\n"); + else { printf(" FAIL seq-1c-64f-deflate-fpredict.tiff\n"); ok = 0; } + + printf("\n--- Unassociated alpha ---\n"); + + /* Reuse write_rgba but with unassociated alpha */ + { + snprintf(path, sizeof(path), "%s/seq-4c-8b-rgba-unassoc.tiff", dir); + TIFF *tif = TIFFOpen(path, "w"); + if (tif) { + uint16_t extra[] = { EXTRASAMPLE_UNASSALPHA }; + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, W); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, H); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 4); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, extra); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, H); + tsize_t sl = TIFFScanlineSize(tif); + uint8_t *b2 = (uint8_t *)_TIFFmalloc(sl); + int good = 1; + if (b2) { + for (int y = 0; y < H && good; y++) { + memset(b2, 0, sl); + for (int x = 0; x < W; x++) + for (int c = 0; c < 4; c++) { + uint32_t idx = (y * W + x) * 4 + c; + b2[x * 4 + c] = (uint8_t)(idx % 256); + } + if (TIFFWriteScanline(tif, b2, y, 0) < 0) good = 0; + } + _TIFFfree(b2); + } else good = 0; + TIFFClose(tif); + if (good) printf(" OK seq-4c-8b-rgba-unassoc.tiff\n"); + else { printf(" FAIL seq-4c-8b-rgba-unassoc.tiff\n"); ok = 0; } + } else { printf(" FAIL seq-4c-8b-rgba-unassoc.tiff\n"); ok = 0; } + } + + printf("\n--- Sub-byte RGB ---\n"); + + /* 5-bit and 7-bit RGB contiguous */ + { + int rgb_sub[] = {5, 7}; + for (int i = 0; i < 2; i++) { + int b = rgb_sub[i]; + snprintf(path, sizeof(path), "%s/seq-3c-%db-contig.tiff", dir, b); + if (write_rgb_contig(path, b) == 0) + printf(" OK seq-3c-%db-contig.tiff\n", b); + else { printf(" FAIL seq-3c-%db-contig.tiff\n", b); ok = 0; } + } + } + + printf("\n--- Signed RGB ---\n"); + + snprintf(path, sizeof(path), "%s/seq-3c-i8.tiff", dir); + if (write_rgb_signed(path, 8, "w") == 0) + printf(" OK seq-3c-i8.tiff\n"); + else { printf(" FAIL seq-3c-i8.tiff\n"); ok = 0; } + + snprintf(path, sizeof(path), "%s/seq-3c-i16.tiff", dir); + if (write_rgb_signed(path, 16, "w") == 0) + printf(" OK seq-3c-i16.tiff\n"); + else { printf(" FAIL seq-3c-i16.tiff\n"); ok = 0; } + + printf("\n--- Float f64 RGB ---\n"); + + snprintf(path, sizeof(path), "%s/seq-3c-64f.tiff", dir); + if (write_rgb_float(path, 64, "w") == 0) + printf(" OK seq-3c-64f.tiff\n"); + else { printf(" FAIL seq-3c-64f.tiff\n"); ok = 0; } + + printf("\n--- Tiled BigTIFF ---\n"); + + snprintf(path, sizeof(path), "%s/seq-1c-8b-tiled-bigtiff.tiff", dir); + if (write_gray_tiled_comp(path, 8, COMPRESSION_NONE, "w8") == 0) + printf(" OK seq-1c-8b-tiled-bigtiff.tiff\n"); + else { printf(" FAIL seq-1c-8b-tiled-bigtiff.tiff\n"); ok = 0; } + printf("\nDone. %s\n", ok ? "All succeeded." : "Some failed!"); return ok ? 0 : 1; } diff --git a/tests/images/seq-1c-16b-multistrip.tiff b/tests/images/seq-1c-16b-multistrip.tiff new file mode 100644 index 0000000000000000000000000000000000000000..58b842ae63fa9af584fe68c5c4995ce1aaac542e GIT binary patch literal 682 zcmYk)Wsuip6vgq+?h;EVxrCsk#F7FEDgsinAl=d-DIlPTprpXkNOyOqbccdLhtkp^ zAPQ2(Z%1DI?=yFv@7#N4?u&C`VxmQc3o1;w2=T<1KthocNi2z^Uhtx1l1m|_R8otQ zMq25l_mT`Udf6*7$t;VkvdQjMIpmZ}Zqf3{E1&!dDCjkX6jsFR-cVFAZ+gqyihD;1 zC6)57(#j~SobujNL5zwjd0%B!R8>uNHPlo~ZFSUDPkkR~prJ+@Yoe)Unroq@54F-- z8*R1I-bXsF#qq^z?-<_0n4(ef87dR|XhpkXVBaG1M@_jWE(E zUmIKI-nQn%eW|?h{x#pQ~frS=XY>B0oS#E`uR#|P0wbuE@dK+xC z$!1$@was=r?6k{nd+fE(x4yIA0pI(461Em-Mz=ao- literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-1b.tiff b/tests/images/seq-1c-1b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..d3c15432706ea3afc7e03e3c5cf70e2a2c5121a1 GIT binary patch literal 178 zcmebD)MC(JU|kl@7Je3((8oNRO<@rf($ z%lZCybPEaSIQGBgnEUnmiB5m{HLv^!QW=+2UW+dY*ss+3i#KrTOs)$*UOZ%G;LXuj zN24#cv2r{yO^>P5&qDbOmP&UX+aj2Re UAX^4WjV+YT3=|JWVgtDh0C0&%j{pDw literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-4b-miniswhite.tiff b/tests/images/seq-1c-4b-miniswhite.tiff new file mode 100644 index 0000000000000000000000000000000000000000..e282a37902e4b47db88404683df4b9ddf1019072 GIT binary patch literal 274 zcmebD)MDshU|{%nXV;9f5F>$sqPZCu7?~Lufi4jMVn!r36O_#Y6laFALF(9`Y#@_C ikdXzfmjlQaMN%UMWrNHVhpK4+vSpCe1S7G5QValgGe(yH literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-4b.tiff b/tests/images/seq-1c-4b.tiff new file mode 100644 index 0000000000000000000000000000000000000000..0a8aa9976b6391c2ffd13f5e1781d4c40ba0b8f6 GIT binary patch literal 274 zcmebD)MDshU|?WWc1`bGefIr8(cC~MFf%X$T_ON-4HBCP%4PwIGeg-Rb!(i-ko*LjxyM6N`jJ9mCG}1%KQ_o-K|%A8?|Bi|OJI z`ITqfx9^|WD9Bo6X0qsjm)Fk~cic*TTeIKz*XeMkqa{~XMn-0$!P6Rd5y6t*%lS6^ zH3mxUWX}2Tq;ZyG=~qz&2SKmX!Hw%XRNC_XKVsZg|B~VM{+foj@sQ3dE6!KfYq+!8ZTid5an5xs4+8@uGXo>gr2;_Ah{R@svK@fp%ux2G1_lN;C>x|l okdXzfmjlQaMG_Z-vO#8wL)9z*vSpCe*h1ONK=EKCHjv8z0M9~DJ^%m! literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-8b-multistrip.tiff b/tests/images/seq-1c-8b-multistrip.tiff new file mode 100644 index 0000000000000000000000000000000000000000..4fd3fbf978177d68ccc20ca0f32d576b0a533c61 GIT binary patch literal 426 zcmebD)MDUZWME)mWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{ z)Y8_`)zddHG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`< zKXCBS;UhtEX>ZXk=_+YG!U>X=QC=YiIA^=;Z9;>gMj@>E-R?>*pU37!({58WtWA z85JE98yBCDn3SB7nwFlCnU$TBo0nfuSX5k6T2@|BSyf$ATUX!E*woz8+ScCD+11_C z+t)u~;-tw_rcRqaW9F>cbLP&QzhL2_#Y>hhTfSoDs?}@Ou3NuhAjpEMiVbVlcNjV-a&<6u@w^E0P#YG8joO%zY4-GcW)E(Hnxb literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-8b-tiled-deflate.tiff b/tests/images/seq-1c-8b-tiled-deflate.tiff new file mode 100644 index 0000000000000000000000000000000000000000..aac05d0388e1ef72d2db093ba848480bc2a119da GIT binary patch literal 426 zcmebD)M5}}WMHV6!^pt+{~rS*6Eh1d8#@Ol7dHqnoxOvjle3Gfo4bdnm$#3vpMOALP;f|S zSa?KaRCG*iTzo=eQgTXaT6#uiR(4KqUVcGgQE^FWS$RceRdr2mU427iQ*%peTYE=m zS9ecuU;l)OlO|7@I&J!lnX_iknLBU(f`y9~FIl>5`HGdRRKIRx^4T8 zox67L*}HH5frEz*A31vL_=%IJPM z`HPpYUcY(!?)`_4pFV&2`tAFVpTBt7lnpY|8Hw$}$O5(tWTq>W%?>7)dWsiU9!4EQx{u literal 0 HcmV?d00001 diff --git a/tests/images/seq-1c-8b-tiled-lzw.tiff b/tests/images/seq-1c-8b-tiled-lzw.tiff new file mode 100644 index 0000000000000000000000000000000000000000..e577eeb5918a824a1d5c889282a9a49230f20434 GIT binary patch literal 450 zcmebD)MC(KWMF7uU{Fwy5a42CZemne5Mdz2$KJxEs4ycyON_gXS#iOQ0xLQG4i+Uv zi3wh6!d52ESDJ#ynkYy&_$F98a#)m39`3W2}s?2&Gzxv{v zAAZ%@FA&gBkrL!;b6zBUb?>o33k@mIb4 z3l0Vz1_nlE24G+@2mmo75}OIi<^YN_L)olAHXD=;(j$t*mO)}WLD?WPosrlsj4WWg SKxVo^*~&nJf|2wBr5FHvo^o&i literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-5b-contig.tiff b/tests/images/seq-3c-5b-contig.tiff new file mode 100644 index 0000000000000000000000000000000000000000..c3e98602d94bb97e882fa9c4a7497d43da1072dd GIT binary patch literal 638 zcmebD)M9wS$iTqhVkC0hDP)`J`Ic0xuksuF){!E-Us5` literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-64f.tiff b/tests/images/seq-3c-64f.tiff new file mode 100644 index 0000000000000000000000000000000000000000..4b7725a28c59abd952be91b753dfc3e2381706bb GIT binary patch literal 6302 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 U2;=~YoB-kupi&Q@Rg7Q=08KlG;Q#;t literal 0 HcmV?d00001 diff --git a/tests/images/seq-3c-i16.tiff b/tests/images/seq-3c-i16.tiff new file mode 100644 index 0000000000000000000000000000000000000000..20f416e3903c33dc96c48816010acdd4fcdc9810 GIT binary patch literal 1694 zcmeH|>6^}F7=};pXc~l>$`+xd82c7d)-01H4B3|uNx_^ zj&_V=9cO~$o!~?#IoU*~IMt8+#A!}TCJU-+e4-R5?`a)&$p+Hc(DZuj`Dd)?=L5BQzm`-4CFlRtaV zLmu`Qk9gE$9`{#I_?y3b(o>%H56^hkbN=ahFL=>QUiOMtz2;y3?REe0hBv+CZU6N@ z?|4^*RD_5iH4hNUgG7;rw4~!9(vyLVL=(fqJi?=7A~RXYN;V!NJ2`lqoa7=mPw*sp zc#6C{%`@a9Kd}^`AcZJQ5sFfb;*_8yrFfRol%XuoQI6+%f$~(KB9*926<(w&)u>Jl z;&_Rg)S@RUzF>}`JIdkXDU$Ah|;w4L$ zEnl&6)#^2C*R9{Mant54TeofBv2)k%J$v`MC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{ z)Y8_`)zddHG%_|ZH8Z!cw6eCbwX=6{baHlab#wRd^z!!c_45x13RUzF>}`JIdkXDU$Ah|;w4L$Enl&6)#^2C*R9{Mant54TeofBv2)k%J$v`< zKXCBS;UhC#>1%UvlFhF860okAeVjhrS zhKhsKu|e5PK(-(w3s^4)kSz)o2k8-mvO#8wL)Cx`lR+{k2+5paARDA_8HfM^4k%;< Hnht^hIBo(B literal 0 HcmV?d00001 diff --git a/tests/verify_seq_tiff.c b/tests/verify_seq_tiff.c new file mode 100644 index 0000000..2f177ba --- /dev/null +++ b/tests/verify_seq_tiff.c @@ -0,0 +1,460 @@ +/* + * verify_seq_tiff.c — Read seq-* test TIFFs with libtiff and verify decoded values. + * + * This is the authoritative cross-check: libtiff wrote these files, and now + * libtiff reads them back. The checksums computed here must match what + * image-tiff's Rust decoder produces (in decode_images.rs tests). + * + * For sub-byte depths (1-7 bit): sums packed bytes (matching image-tiff behavior). + * For extended depths (9-31 bit): unpacks MSB-first to u16/u32, sums those. + * For standard depths: sums native sample values. + * For float: sums as float/double. + * For signed: sums as int64. + * + * Usage: ./verify_seq_tiff + * Compile: cc -O2 -o verify_seq_tiff verify_seq_tiff.c -ltiff -lm + */ + +#include +#include +#include +#include +#include +#include + +#define W 16 +#define H 16 + +/* Extract N bits from MSB-first packed byte stream starting at bit_offset. */ +static uint32_t extract_bits_msb(const uint8_t *buf, int bit_offset, int nbits) +{ + uint32_t val = 0; + for (int i = nbits - 1; i >= 0; i--) { + int byte_idx = bit_offset / 8; + int bit_in_byte = bit_offset % 8; + if (buf[byte_idx] & (0x80 >> bit_in_byte)) + val |= (1u << i); + bit_offset++; + } + return val; +} + +typedef enum { + SUM_U8_PACKED, /* Sum raw packed bytes (sub-byte depths) */ + SUM_U8, /* Sum u8 samples */ + SUM_U16, /* Sum u16 samples */ + SUM_U16_UNPACK, /* Unpack from packed bits, sum as u16 */ + SUM_U32, /* Sum u32 samples */ + SUM_U32_UNPACK, /* Unpack from packed bits, sum as u32 */ + SUM_U64, /* Sum u64 samples */ + SUM_I8, /* Sum i8 samples */ + SUM_I16, /* Sum i16 samples */ + SUM_I32, /* Sum i32 samples */ + SUM_F32, /* Sum f32 samples */ + SUM_F64, /* Sum f64 samples */ +} SumMode; + +typedef struct { + const char *filename; + SumMode mode; + int bits_per_sample; + int samples_per_pixel; + /* Expected checksums — same as Rust test expectations */ + union { + uint64_t u; + int64_t i; + double f; + } expected; +} TestCase; + +static int verify_stripped(const char *dir, const TestCase *tc) +{ + char path[512]; + snprintf(path, sizeof(path), "%s/%s", dir, tc->filename); + + TIFF *tif = TIFFOpen(path, "r"); + if (!tif) { + fprintf(stderr, " SKIP %s (cannot open)\n", tc->filename); + return 0; /* skip, not fail */ + } + + uint32_t w, h; + uint16_t bps, spp, sf, pc; + TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w); + TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h); + TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps); + TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); + if (!TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sf)) + sf = SAMPLEFORMAT_UINT; + if (!TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &pc)) + pc = PLANARCONFIG_CONTIG; + + int is_tiled = TIFFIsTiled(tif); + tsize_t scanline_size = TIFFScanlineSize(tif); + + /* For planar images, read_image() only reads plane 0 */ + int nplanes = (pc == PLANARCONFIG_SEPARATE) ? 1 : 1; + /* spp for checksum is always actual spp for contig, 1 for planar (first plane) */ + int eff_spp = (pc == PLANARCONFIG_SEPARATE) ? 1 : spp; + + uint64_t sum_u = 0; + int64_t sum_i = 0; + double sum_f = 0.0; + + if (is_tiled) { + uint32_t tw, th; + TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tw); + TIFFGetField(tif, TIFFTAG_TILELENGTH, &th); + tsize_t tile_size = TIFFTileSize(tif); + uint8_t *tile_buf = (uint8_t *)_TIFFmalloc(tile_size); + if (!tile_buf) { TIFFClose(tif); return -1; } + + /* Assemble tiles into a full image buffer, then checksum */ + int img_bytes; + switch (tc->mode) { + case SUM_U8_PACKED: + case SUM_U8: img_bytes = w * h * eff_spp; break; + case SUM_U16: + case SUM_U16_UNPACK: img_bytes = w * h * eff_spp * 2; break; + case SUM_U32: + case SUM_U32_UNPACK: + case SUM_F32: img_bytes = w * h * eff_spp * 4; break; + case SUM_U64: + case SUM_F64: img_bytes = w * h * eff_spp * 8; break; + case SUM_I8: img_bytes = w * h * eff_spp; break; + case SUM_I16: img_bytes = w * h * eff_spp * 2; break; + case SUM_I32: img_bytes = w * h * eff_spp * 4; break; + default: img_bytes = w * h * eff_spp; break; + } + uint8_t *img = (uint8_t *)calloc(1, img_bytes); + if (!img) { _TIFFfree(tile_buf); TIFFClose(tif); return -1; } + + int bytes_per_sample = bps / 8; + if (bytes_per_sample == 0) bytes_per_sample = 1; + int row_bytes = w * eff_spp * bytes_per_sample; + + for (uint32_t ty = 0; ty < h; ty += th) { + for (uint32_t tx = 0; tx < w; tx += tw) { + TIFFReadTile(tif, tile_buf, tx, ty, 0, 0); + /* Copy tile data into image buffer */ + for (uint32_t row = 0; row < th && (ty + row) < h; row++) { + int src_off = row * tw * eff_spp * bytes_per_sample; + int dst_off = ((ty + row) * w + tx) * eff_spp * bytes_per_sample; + int copy_w = tw; + if (tx + tw > w) copy_w = w - tx; + memcpy(img + dst_off, tile_buf + src_off, + copy_w * eff_spp * bytes_per_sample); + } + } + } + + /* Now checksum the assembled image */ + int npixels = w * h * eff_spp; + switch (tc->mode) { + case SUM_U8_PACKED: + case SUM_U8: + for (int j = 0; j < npixels; j++) sum_u += img[j]; + break; + case SUM_U16: + for (int j = 0; j < npixels; j++) sum_u += ((uint16_t *)img)[j]; + break; + case SUM_U32: + for (int j = 0; j < npixels; j++) sum_u += ((uint32_t *)img)[j]; + break; + case SUM_I8: + for (int j = 0; j < npixels; j++) sum_i += ((int8_t *)img)[j]; + break; + case SUM_F32: + for (int j = 0; j < npixels; j++) sum_f += ((float *)img)[j]; + break; + case SUM_F64: + for (int j = 0; j < npixels; j++) sum_f += ((double *)img)[j]; + break; + default: break; + } + + free(img); + _TIFFfree(tile_buf); + } else { + /* Stripped image */ + uint8_t *buf = (uint8_t *)_TIFFmalloc(scanline_size); + if (!buf) { TIFFClose(tif); return -1; } + + for (uint32_t y = 0; y < h; y++) { + if (TIFFReadScanline(tif, buf, y, 0) < 0) { + fprintf(stderr, " ERROR reading scanline %u of %s\n", y, tc->filename); + _TIFFfree(buf); TIFFClose(tif); return -1; + } + + switch (tc->mode) { + case SUM_U8_PACKED: + /* Sum raw packed bytes — matches image-tiff sub-byte output */ + for (tsize_t j = 0; j < scanline_size; j++) + sum_u += buf[j]; + break; + case SUM_U8: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_u += buf[x]; + break; + case SUM_U16: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_u += ((uint16_t *)buf)[x]; + break; + case SUM_U16_UNPACK: { + /* Unpack N-bit MSB-first packed samples to u16 */ + for (uint32_t x = 0; x < w * eff_spp; x++) { + uint32_t val = extract_bits_msb(buf, x * bps, bps); + sum_u += val; + } + break; + } + case SUM_U32: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_u += ((uint32_t *)buf)[x]; + break; + case SUM_U32_UNPACK: { + for (uint32_t x = 0; x < w * eff_spp; x++) { + uint32_t val = extract_bits_msb(buf, x * bps, bps); + sum_u += val; + } + break; + } + case SUM_U64: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_u += ((uint64_t *)buf)[x]; + break; + case SUM_I8: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_i += ((int8_t *)buf)[x]; + break; + case SUM_I16: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_i += ((int16_t *)buf)[x]; + break; + case SUM_I32: + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_i += ((int32_t *)buf)[x]; + break; + case SUM_F32: { + float *fp = (float *)buf; + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_f += fp[x]; + break; + } + case SUM_F64: { + double *dp = (double *)buf; + for (uint32_t x = 0; x < w * eff_spp; x++) + sum_f += dp[x]; + break; + } + } + } + _TIFFfree(buf); + } + + TIFFClose(tif); + + /* Compare checksums */ + int pass = 0; + char got_str[64], exp_str[64]; + + switch (tc->mode) { + case SUM_U8_PACKED: + case SUM_U8: + case SUM_U16: + case SUM_U16_UNPACK: + case SUM_U32: + case SUM_U32_UNPACK: + case SUM_U64: + snprintf(got_str, sizeof(got_str), "%llu", (unsigned long long)sum_u); + snprintf(exp_str, sizeof(exp_str), "%llu", (unsigned long long)tc->expected.u); + pass = (sum_u == tc->expected.u); + break; + case SUM_I8: + case SUM_I16: + case SUM_I32: + snprintf(got_str, sizeof(got_str), "%lld", (long long)sum_i); + snprintf(exp_str, sizeof(exp_str), "%lld", (long long)tc->expected.i); + pass = (sum_i == tc->expected.i); + break; + case SUM_F32: + snprintf(got_str, sizeof(got_str), "%.10g", sum_f); + snprintf(exp_str, sizeof(exp_str), "%.10g", tc->expected.f); + /* Allow small float rounding tolerance */ + pass = (fabs(sum_f - tc->expected.f) < 0.01); + break; + case SUM_F64: + snprintf(got_str, sizeof(got_str), "%.15g", sum_f); + snprintf(exp_str, sizeof(exp_str), "%.15g", tc->expected.f); + pass = (fabs(sum_f - tc->expected.f) < 1e-10); + break; + } + + if (pass) { + printf(" PASS %-45s sum=%s\n", tc->filename, got_str); + } else { + printf(" FAIL %-45s got=%s expected=%s\n", tc->filename, got_str, exp_str); + } + + return pass ? 0 : 1; +} + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + const char *dir = argv[1]; + + /* These expected values MUST match the Rust test expectations in decode_images.rs. + * If they don't, either the libtiff decoder or the image-tiff decoder has a bug. */ + TestCase cases[] = { + /* --- Sub-byte BlackIsZero (packed byte sums) --- */ + {"seq-1c-3b.tiff", SUM_U8_PACKED, 3, 1, {.u = 5792}}, + {"seq-1c-5b.tiff", SUM_U8_PACKED, 5, 1, {.u = 18944}}, + {"seq-1c-7b.tiff", SUM_U8_PACKED, 7, 1, {.u = 24992}}, + + /* --- Sub-byte WhiteIsZero (packed byte sums) --- + * libtiff returns RAW stored values (inverted), unlike image-tiff which + * inverts back to BlackIsZero. These sums verify libtiff round-trips + * the stored data correctly. image-tiff's inversion is verified separately + * by the Rust tests (which expect the same sums as BlackIsZero). */ + {"seq-1c-3b-miniswhite.tiff", SUM_U8_PACKED, 3, 1, {.u = 18688}}, + {"seq-1c-5b-miniswhite.tiff", SUM_U8_PACKED, 5, 1, {.u = 21856}}, + {"seq-1c-6b-miniswhite.tiff", SUM_U8_PACKED, 6, 1, {.u = 23232}}, + {"seq-1c-7b-miniswhite.tiff", SUM_U8_PACKED, 7, 1, {.u = 32128}}, + + /* --- Extended BlackIsZero (unpacked u16/u32 sums) --- */ + {"seq-1c-10b.tiff", SUM_U16_UNPACK, 10, 1, {.u = 32640}}, + {"seq-1c-12b.tiff", SUM_U16_UNPACK, 12, 1, {.u = 32640}}, + {"seq-1c-14b.tiff", SUM_U16_UNPACK, 14, 1, {.u = 32640}}, + {"seq-1c-24b.tiff", SUM_U32_UNPACK, 24, 1, {.u = 32640}}, + + /* --- Extended WhiteIsZero (unpacked sums, decoder inverts) --- */ + /* NOTE: libtiff does NOT invert WhiteIsZero — it returns raw stored values. + * image-tiff inverts them. So for WIZ, libtiff sum ≠ image-tiff sum. + * We verify these separately by checking the raw packed sum. */ + + /* --- Extended HPredict --- */ + {"seq-1c-10b-hpredict.tiff", SUM_U16_UNPACK, 10, 1, {.u = 32640}}, + {"seq-1c-12b-hpredict.tiff", SUM_U16_UNPACK, 12, 1, {.u = 32640}}, + {"seq-1c-14b-hpredict.tiff", SUM_U16_UNPACK, 14, 1, {.u = 32640}}, + {"seq-1c-24b-hpredict.tiff", SUM_U32_UNPACK, 24, 1, {.u = 32640}}, + + /* --- Extended RGB Contiguous (unpacked sums) --- */ + {"seq-3c-10b-contig.tiff", SUM_U16_UNPACK, 10, 3, {.u = 294528}}, + {"seq-3c-12b-contig.tiff", SUM_U16_UNPACK, 12, 3, {.u = 294528}}, + {"seq-3c-14b-contig.tiff", SUM_U16_UNPACK, 14, 3, {.u = 294528}}, + {"seq-3c-24b-contig.tiff", SUM_U32_UNPACK, 24, 3, {.u = 294528}}, + + /* --- Extended RGB Planar (first plane only, unpacked) --- */ + {"seq-3c-10b-planar.tiff", SUM_U16_UNPACK, 10, 3, {.u = 32640}}, + {"seq-3c-12b-planar.tiff", SUM_U16_UNPACK, 12, 3, {.u = 32640}}, + {"seq-3c-14b-planar.tiff", SUM_U16_UNPACK, 14, 3, {.u = 32640}}, + {"seq-3c-24b-planar.tiff", SUM_U32_UNPACK, 24, 3, {.u = 32640}}, + + /* --- Standard depths with compression --- */ + {"seq-1c-8b-lzw.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-8b-deflate.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-8b-packbits.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-16b-lzw.tiff", SUM_U16, 16, 1, {.u = 32640}}, + {"seq-1c-16b-deflate.tiff", SUM_U16, 16, 1, {.u = 32640}}, + {"seq-1c-8b-lzw-hpredict.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-3c-8b-lzw.tiff", SUM_U8, 8, 3, {.u = 97920}}, + + /* --- Tiled --- */ + {"seq-1c-8b-tiled.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-16b-tiled.tiff", SUM_U16, 16, 1, {.u = 32640}}, + {"seq-3c-8b-tiled.tiff", SUM_U8, 8, 3, {.u = 97920}}, + + /* --- BigTIFF --- */ + {"seq-1c-8b-bigtiff.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-3c-16b-bigtiff.tiff", SUM_U16, 16, 3, {.u = 294528}}, + + /* --- Float --- */ + {"seq-1c-32f.tiff", SUM_F32, 32, 1, {.f = 128.0}}, + {"seq-1c-64f.tiff", SUM_F64, 64, 1, {.f = 128.0}}, + {"seq-3c-32f.tiff", SUM_F32, 32, 3, {.f = 384.0}}, + + /* --- Signed integer --- */ + {"seq-1c-i8.tiff", SUM_I8, 8, 1, {.i = -128}}, + {"seq-1c-i16.tiff", SUM_I16, 16, 1, {.i = -128}}, + {"seq-1c-i32.tiff", SUM_I32, 32, 1, {.i = -128}}, + + /* --- CMYK --- */ + {"seq-4c-8b-cmyk.tiff", SUM_U8, 8, 4, {.u = 130560}}, + {"seq-4c-16b-cmyk.tiff", SUM_U16, 16, 4, {.u = 523776}}, + + /* --- Palette (raw packed indices) --- */ + {"seq-1c-8b-palette.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-4b-palette.tiff", SUM_U8_PACKED, 4, 1, {.u = 15360}}, + + /* --- RGBA --- */ + {"seq-4c-8b-rgba.tiff", SUM_U8, 8, 4, {.u = 130560}}, + {"seq-4c-16b-rgba.tiff", SUM_U16, 16, 4, {.u = 523776}}, + + /* --- Multi-page (first page only) --- */ + {"seq-1c-8b-multipage.tiff", SUM_U8, 8, 1, {.u = 32640}}, + + /* --- Big-endian --- */ + {"seq-1c-8b-bigendian.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-16b-bigendian.tiff", SUM_U16, 16, 1, {.u = 32640}}, + {"seq-3c-8b-bigendian.tiff", SUM_U8, 8, 3, {.u = 97920}}, + + /* --- Standard sub-byte (packed byte sums) --- */ + {"seq-1c-1b.tiff", SUM_U8_PACKED, 1, 1, {.u = 2720}}, + {"seq-1c-2b.tiff", SUM_U8_PACKED, 2, 1, {.u = 1728}}, + {"seq-1c-4b.tiff", SUM_U8_PACKED, 4, 1, {.u = 15360}}, + + /* --- Multi-strip --- */ + {"seq-1c-8b-multistrip.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-16b-multistrip.tiff", SUM_U16, 16, 1, {.u = 32640}}, + {"seq-3c-8b-multistrip.tiff", SUM_U8, 8, 3, {.u = 97920}}, + + /* --- Tiled + compressed --- */ + {"seq-1c-8b-tiled-lzw.tiff", SUM_U8, 8, 1, {.u = 32640}}, + {"seq-1c-8b-tiled-deflate.tiff", SUM_U8, 8, 1, {.u = 32640}}, + + /* --- Float + predictor --- */ + {"seq-1c-32f-deflate-fpredict.tiff", SUM_F32, 32, 1, {.f = 128.0}}, + {"seq-1c-64f-deflate-fpredict.tiff", SUM_F64, 64, 1, {.f = 128.0}}, + + /* --- Unassociated alpha --- */ + {"seq-4c-8b-rgba-unassoc.tiff", SUM_U8, 8, 4, {.u = 130560}}, + + /* --- Sub-byte RGB (packed byte sums) --- */ + {"seq-3c-5b-contig.tiff", SUM_U8_PACKED, 5, 3, {.u = 56832}}, + {"seq-3c-7b-contig.tiff", SUM_U8_PACKED, 7, 3, {.u = 74976}}, + + /* --- Signed RGB --- */ + {"seq-3c-i8.tiff", SUM_I8, 8, 3, {.i = -384}}, + {"seq-3c-i16.tiff", SUM_I16, 16, 3, {.i = -384}}, + + /* --- Float f64 RGB --- */ + {"seq-3c-64f.tiff", SUM_F64, 64, 3, {.f = 384.0}}, + + /* --- Tiled BigTIFF --- */ + {"seq-1c-8b-tiled-bigtiff.tiff", SUM_U8, 8, 1, {.u = 32640}}, + }; + + int ncases = sizeof(cases) / sizeof(cases[0]); + int failures = 0; + + printf("Verifying %d test images with libtiff decoder in %s/\n\n", ncases, dir); + + /* Suppress libtiff warnings for non-standard tag 317 (Predictor) */ + TIFFSetWarningHandler(NULL); + + for (int i = 0; i < ncases; i++) { + int ret = verify_stripped(dir, &cases[i]); + if (ret != 0) failures++; + } + + printf("\n%d/%d passed", ncases - failures, ncases); + if (failures > 0) + printf(", %d FAILED", failures); + printf("\n"); + + return failures > 0 ? 1 : 0; +}