A small, dependency-free C library for CCITT Group 3 / Group 4 fax compression — the bitonal codec used by fax machines, TIFF, and PDF.
The encode/decode core is the CCITT fax implementation carved out of libtiff (Sam Leffler / Silicon Graphics), lifted out of the TIFF container machinery and wrapped behind a tiny buffer-in / buffer-out API. It compresses and decompresses raw 1-bit-per-pixel raster data; it does not read or write TIFF files.
| Variant | Standard | Description |
|---|---|---|
FAXTIFF_VARIANT_G3_1D |
ITU-T T.4 1D | Group 3, one-dimensional |
FAXTIFF_VARIANT_G3_2D |
ITU-T T.4 2D | Group 3, two-dimensional (K factor) |
FAXTIFF_VARIANT_G4 |
ITU-T T.6 | Group 4, MMR (best ratio) |
meson setup build
meson compile -C buildThis builds the library plus two command-line tools in utils/:
cfc— encode a monochrome BMP into a self-describing.cfaxstream.cfd— decode a.cfaxstream back to PBM (P4) or raw 1bpp.
They double as worked examples; the snippets below are distilled from their inner loops.
Both directions speak the same raster layout — there is no TIFF, no palette, no header:
- 1 bit per pixel, packed MSB-first within each byte.
- A set bit (
1) is black; a clear bit (0) is white. - Each row is
ceil(columns / 8)bytes; the final byte of a row is padded to a byte boundary, and rows follow one another with no padding between them.
So a full raster is rows * ceil(columns / 8) bytes.
The public surface is one header — <libfaxtiff/libfaxtiff.h> — and three
functions, all prefixed faxtiff_*.
struct faxtiff_params_t {
faxtiff_variant_t variant; /* G3_1D | G3_2D | G4 (required) */
int columns; /* image width in pixels (required) */
int rows; /* image height in rows (required) */
faxtiff_fill_t fill; /* MSB2LSB (default) | LSB2MSB */
int maxk; /* G3_2D K factor: 2 or 4; else ignored */
int end_of_line; /* emit/expect EOL per row (G3) */
int end_of_block; /* emit/expect RTC (G3) / EOFB (G4) */
};fill selects the bit order of the compressed stream (FAXTIFF_FILL_MSB2LSB
is the usual default). maxk is consulted only for FAXTIFF_VARIANT_G3_2D
(use 2 for ≤200 lpi, 4 for higher); pass 2 otherwise. The same parameters
must be supplied to decode that were used to encode — the compressed stream does
not carry its own geometry, so you must transmit it out of band (that is
exactly what the .cfax container header in utils/ does).
struct faxtiff_buffer_t {
char *buf; /* heap-allocated data */
size_t cap; /* allocated capacity of buf */
size_t len; /* meaningful bytes written into buf */
};Both calls return a freshly allocated faxtiff_buffer_t *, or NULL on
failure. The caller owns it and frees it in two steps:
free(out->buf);
free(out);const char *faxtiff_version(void);
struct faxtiff_buffer_t *
faxtiff_encode(const char *src, const struct faxtiff_params_t *params);
struct faxtiff_buffer_t *
faxtiff_decode(const char *src, size_t srclen,
const struct faxtiff_params_t *params);For faxtiff_encode, out->len is the compressed byte count. For
faxtiff_decode, out->len is rows * ceil(columns / 8) of unpacked raster.
Given width × height pixels already in the 1bpp MSB-first layout described
above (here as const char *pixels):
#include <libfaxtiff/libfaxtiff.h>
struct faxtiff_params_t p = {
.variant = FAXTIFF_VARIANT_G4, /* MMR — best compression */
.columns = width,
.rows = height,
.fill = FAXTIFF_FILL_MSB2LSB,
.maxk = 2, /* only consulted for G3_2D */
.end_of_line = 0,
.end_of_block = 0,
};
struct faxtiff_buffer_t *enc = faxtiff_encode(pixels, &p);
if (!enc) {
/* bad parameters or out of memory */
return -1;
}
/* enc->len bytes of compressed fax data live in enc->buf. */
fwrite(enc->buf, 1, enc->len, stdout);
free(enc->buf);
free(enc);Given a compressed stream in enc / enclen, plus the geometry it was encoded
with (carried alongside the data — see the .cfax header cfd reads):
#include <libfaxtiff/libfaxtiff.h>
struct faxtiff_params_t p = {
.variant = FAXTIFF_VARIANT_G4,
.columns = width, /* must match the encoder */
.rows = height,
.fill = FAXTIFF_FILL_MSB2LSB,
.maxk = 2,
.end_of_line = 0,
.end_of_block = 0,
};
struct faxtiff_buffer_t *raw = faxtiff_decode(enc, enclen, &p);
if (!raw) {
/* corrupt stream, geometry mismatch, or out of memory */
return -1;
}
/* raw->len == rows * ceil(columns / 8), same 1bpp MSB-first packing. */
fwrite(raw->buf, 1, raw->len, stdout);
free(raw->buf);
free(raw);The CCITT codec sources (fax3.c, fax3.h, fax3sm.c, t4.h) retain their
original libtiff copyright and permission notices (Sam Leffler / Silicon
Graphics). libfaxtiff is distributed under the same libtiff license; see
LICENSE.md.