From 6c8f8abc7124677abaa4633bfa464285d657f4a2 Mon Sep 17 00:00:00 2001 From: Lilith River Date: Thu, 12 Mar 2026 00:52:03 -0600 Subject: [PATCH] test: add CCITT Group 3 (Fax3) test images with libtiff cross-validation Three 16x16 bilevel fax3 test images covering the key Group 3 variants: - seq-1c-1b-fax3: basic G3, no fill bits, MSB-first fill order - seq-1c-1b-fax3-fillbits: G3 with T4Options fill bits (byte-aligned EOLs) - seq-1c-1b-fax3-lsb: G3 with FillOrder=2 (LSB-first) All use MinIsWhite photometric with inverted sequential pattern, producing the same decoded CRC32 hash (71f6a21a) as the uncompressed 1-bit images. Cross-validated: libtiff writes the images, both libtiff and image-tiff read them back and agree on the decoded data. --- tests/gen_seq_tiff.c | 66 ++++++++++++++++++ tests/images/seq/seq-1c-1b-fax3-71f6a21a.tiff | Bin 0 -> 354 bytes .../seq/seq-1c-1b-fax3-fillbits-71f6a21a.tiff | Bin 0 -> 362 bytes .../seq/seq-1c-1b-fax3-lsb-71f6a21a.tiff | Bin 0 -> 354 bytes tests/verify_seq_tiff.c | 9 +++ 5 files changed, 75 insertions(+) create mode 100644 tests/images/seq/seq-1c-1b-fax3-71f6a21a.tiff create mode 100644 tests/images/seq/seq-1c-1b-fax3-fillbits-71f6a21a.tiff create mode 100644 tests/images/seq/seq-1c-1b-fax3-lsb-71f6a21a.tiff diff --git a/tests/gen_seq_tiff.c b/tests/gen_seq_tiff.c index 89f5661..061fa2b 100644 --- a/tests/gen_seq_tiff.c +++ b/tests/gen_seq_tiff.c @@ -948,6 +948,52 @@ static int write_multipage(const char *path, int bits, const char *mode) return 0; } +/* Write a 1-bit bilevel TIFF with CCITT Group 3 (Fax3) compression. + * Sequential pattern: pixel = (y*W+x) % 2, inverted for MinIsWhite. */ +static int write_bilevel_fax3(const char *path, uint32_t t4options, uint16_t fill_order) +{ + 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, 1); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISWHITE); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_CCITTFAX3); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, H); + TIFFSetField(tif, TIFFTAG_GROUP3OPTIONS, t4options); + TIFFSetField(tif, TIFFTAG_FILLORDER, fill_order); + + 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); + int bit_offset = 0; + for (int x = 0; x < W; x++) { + uint32_t val = (y * W + x) % 2; + /* MinIsWhite: store inverted so decoder inverts back to match + * the canonical sequential pattern (same as seq-1c-1b.tiff). */ + val = 1 - val; + pack_value(buf, &bit_offset, val, 1); + } + 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; +} + int main(int argc, char **argv) { if (argc < 2) { @@ -1346,6 +1392,26 @@ int main(int argc, char **argv) printf(" OK seq-1c-8b-tiled-bigtiff.tiff\n"); else { printf(" FAIL seq-1c-8b-tiled-bigtiff.tiff\n"); ok = 0; } + printf("\n--- CCITT Group 3 (Fax3) ---\n"); + + /* Fax3: basic (no fill bits, MSB-first) */ + snprintf(path, sizeof(path), "%s/seq-1c-1b-fax3.tiff", dir); + if (write_bilevel_fax3(path, 0, FILLORDER_MSB2LSB) == 0) + printf(" OK seq-1c-1b-fax3.tiff\n"); + else { printf(" FAIL seq-1c-1b-fax3.tiff\n"); ok = 0; } + + /* Fax3: with fill bits (T4Options bit 2 = 4) */ + snprintf(path, sizeof(path), "%s/seq-1c-1b-fax3-fillbits.tiff", dir); + if (write_bilevel_fax3(path, 4, FILLORDER_MSB2LSB) == 0) + printf(" OK seq-1c-1b-fax3-fillbits.tiff\n"); + else { printf(" FAIL seq-1c-1b-fax3-fillbits.tiff\n"); ok = 0; } + + /* Fax3: LSB-first fill order */ + snprintf(path, sizeof(path), "%s/seq-1c-1b-fax3-lsb.tiff", dir); + if (write_bilevel_fax3(path, 0, FILLORDER_LSB2MSB) == 0) + printf(" OK seq-1c-1b-fax3-lsb.tiff\n"); + else { printf(" FAIL seq-1c-1b-fax3-lsb.tiff\n"); ok = 0; } + printf("\nDone. %s\n", ok ? "All succeeded." : "Some failed!"); return ok ? 0 : 1; } diff --git a/tests/images/seq/seq-1c-1b-fax3-71f6a21a.tiff b/tests/images/seq/seq-1c-1b-fax3-71f6a21a.tiff new file mode 100644 index 0000000000000000000000000000000000000000..bd5b5496fd0fdb7e5859021572870ed50b996518 GIT binary patch literal 354 zcmebD)M7Zm00hDzRQRC}1=NQ6(V$pcm$4U|?isU<7(V0Eiip*i2A1NRAoG zW(Km^pll$MfeT5EAR`Od91ftkD3Z7slnpXp9I9pqkS&9xMg=Mk6lMrU5(n`CU|dK_ literal 0 HcmV?d00001 diff --git a/tests/images/seq/seq-1c-1b-fax3-fillbits-71f6a21a.tiff b/tests/images/seq/seq-1c-1b-fax3-fillbits-71f6a21a.tiff new file mode 100644 index 0000000000000000000000000000000000000000..c4a859178293609d4138d13567ad5059fb111d0d GIT binary patch literal 362 zcmebD)M7Zn00fMt&I>QRD6x{|YafbiUZ4+{85n^c5de7xiOmFMgXEZ@Y-S*v4ax>G t8Mu(t2r{yO&EWuwiz11OLD?Yl#i42r0NFA~YE+=&EI{#KByk{@0RT>jM)m*z literal 0 HcmV?d00001 diff --git a/tests/images/seq/seq-1c-1b-fax3-lsb-71f6a21a.tiff b/tests/images/seq/seq-1c-1b-fax3-lsb-71f6a21a.tiff new file mode 100644 index 0000000000000000000000000000000000000000..353bc37a67eeb4dca930b6e9a7d2fea879ea5397 GIT binary patch literal 354 zcmebD)M7Zm00bwrVs;cfJk-i9&eyO8MCpO}gI<`Ifq{{kff48d0U%~XVlzS6AUS3z zn;FPvgR+541}-R@3CI>?WC5GQ0c49J=@o;rLFS7?)$9PWWsuaUK*fQ=48cg^AU*)P CaZ_^u literal 0 HcmV?d00001 diff --git a/tests/verify_seq_tiff.c b/tests/verify_seq_tiff.c index 2f177ba..df863e4 100644 --- a/tests/verify_seq_tiff.c +++ b/tests/verify_seq_tiff.c @@ -436,6 +436,15 @@ int main(int argc, char **argv) /* --- Tiled BigTIFF --- */ {"seq-1c-8b-tiled-bigtiff.tiff", SUM_U8, 8, 1, {.u = 32640}}, + + /* --- CCITT Group 3 (Fax3) --- + * libtiff returns raw scanline data in MinIsWhite convention (0=white, 1=black). + * The stored pattern is inverted sequential, so libtiff sum differs from + * image-tiff's BlackIsZero sum. For 16x16 1-bit with inverted sequential pattern, + * the raw packed byte sum is computed by verify run below. */ + {"seq-1c-1b-fax3.tiff", SUM_U8_PACKED, 1, 1, {.u = 5440}}, + {"seq-1c-1b-fax3-fillbits.tiff", SUM_U8_PACKED, 1, 1, {.u = 5440}}, + {"seq-1c-1b-fax3-lsb.tiff", SUM_U8_PACKED, 1, 1, {.u = 5440}}, }; int ncases = sizeof(cases) / sizeof(cases[0]);