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.
This commit is contained in:
Lilith River committed 2026-03-12 00:52:03 -06:00
1 parent ba7a290ff7
commit 6c8f8abc71
5 files changed
+75

No files matched your search

+66
View File
@@ -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;
}
Binary file not shown.
Binary file not shown.
+9
View File
@@ -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]);