{"id":50425479,"url":"https://github.com/pmarreck/tiffz","last_synced_at":"2026-05-31T10:03:50.507Z","repository":{"id":355720484,"uuid":"1229303939","full_name":"pmarreck/tiffz","owner":"pmarreck","description":"Cleanroom spec-complete TIFF reader (and eventually writer) in pure Zig","archived":false,"fork":false,"pushed_at":"2026-05-21T18:32:40.000Z","size":6751,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"yolo","last_synced_at":"2026-05-22T03:10:47.698Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pmarreck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"audit/AUDIT_SUMMARY.md","citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-04T22:59:02.000Z","updated_at":"2026-05-21T18:32:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pmarreck/tiffz","commit_stats":null,"previous_names":["pmarreck/tiffz"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pmarreck/tiffz","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Ftiffz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Ftiffz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Ftiffz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Ftiffz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pmarreck","download_url":"https://codeload.github.com/pmarreck/tiffz/tar.gz/refs/heads/yolo","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pmarreck%2Ftiffz/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33726722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-31T10:03:49.713Z","updated_at":"2026-05-31T10:03:50.491Z","avatar_url":"https://github.com/pmarreck.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tiffz\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Garnix](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fgarnix.io%2Fapi%2Fbadges%2Fpmarreck%2Ftiffz)](https://garnix.io/repo/pmarreck/tiffz)\n\nA cleanroom, spec-complete TIFF reader in pure Zig. Targets full\nconformance with the TIFF 6.0 specification and the major modern\nextensions (BigTIFF, DNG, ZSTD-in-TIFF) with both streaming and\nwhole-buffer decode modes.\n\nSibling project to [`validate`](../validate) (the primary downstream\nconsumer), [`jpegz`](../jpegz) (provides the JPEG-in-TIFF codec\nunder the hood), and [`zstdz`](../zstdz) (provides the ZSTD codec).\nBuilt because the existing TIFF support in `zigimg` is incremental-\nby-design — useful as a generalist library but not a path to\nbyte-complete spec coverage. Pro photographers, GIS / mapping\narchives, and pathology slide stacks all need byte-complete TIFF\nverification.\n\n## Status\n\n✅ **Shipped — usable today.** All SPEC §9 milestones M1–M10 are\ncomplete, plus ZSTD-in-TIFF, the streaming\n`Source.fromBufferedReader`, and eager IFD value caching. See\n[`docs/possible_future_directions.md`](docs/possible_future_directions.md)\nfor the honest inventory of remaining gaps (mostly niche format\nvariants and adjacent improvements).\n\n## What's supported\n\n### Compression schemes\n\n| Compression | Code | Status |\n|---|---|---|\n| None | 1 | ✅ |\n| CCITT 1D / T.4 (Group 3) | 3 | ✅ |\n| CCITT T.6 (Group 4) | 4 | ✅ marquee target validated against 11059×15671 fax scan |\n| LZW (TIFF 6.0 + Sun/Adobe legacy LSB-first) | 5 | ✅ |\n| JPEG-in-TIFF (TN2 Mode 1 + Mode 2) | 7 | ✅ via `jpegz.wrapperDecode`; RGB + YCbCr photometric |\n| Deflate / AdobeDeflate | 8 / 32946 | ✅ wraps system zlib |\n| PackBits | 32773 | ✅ |\n| ZSTD-in-TIFF (GDAL/libtiff extension) | 50000 | ✅ via `zstdz` |\n\n### Photometric interpretations\n\n| Photometric | Code | Status |\n|---|---|---|\n| MinIsWhite (fax) | 0 | ✅ 1-bit, 8-bit, 16-bit |\n| MinIsBlack (grayscale) | 1 | ✅ 1-bit, 8-bit, 16-bit |\n| RGB | 2 | ✅ 8-bit, 16-bit |\n| Palette | 3 | ✅ 8-bit (canonical `(u16*255+32767)/65535` ColorMap downscale matches ImageMagick's ScaleQuantumToChar) |\n| Separated / CMYK | 5 | ✅ 8-bit, 16-bit (no ICC profile — device-dependent subtractive recipe) |\n| YCbCr | 6 | ✅ BT.601 inverse with Q16 fixed-point coefficients; matches libtiff `tiff2rgba` byte-exact |\n| CIE L\\*a\\*b\\* | 8 | ✅ 8-bit; Lab→XYZ(D50) → Bradford D50→D65 → sRGB linear → sRGB gamma (all integer Q24 fixed-point at runtime, LUTs precomputed at comptime) |\n| CFA (DNG raw mosaic) | 32803 | ✅ v1 emits each sample as gray RGBA; full Bayer/X-Trans demosaic deferred |\n\n### Layout / structural\n\n- **Strip layout** + **Tile layout** (M6 — shared codec dispatch\n  via `ChunkExtent`)\n- **Chunky** + **separate-planar** photometric expansion\n  (separate-planar via the `interleavePlanesToChunky` helper)\n- **BigTIFF** (M7 — runtime `OffsetWidth` enum threaded through\n  the IFD parser; classic and big inline-fit caps abstracted)\n- **Multi-IFD chains** (lazy materialization on `Decoder.ifd(N)`)\n- **DNG metadata** (M8 — CFA pattern, opcode list — parsed not\n  executed; see `src/dng.zig`)\n- **Eager IFD value caching** — all out-of-line tag values are\n  copied into the Ifd at parse time (sorted by value-offset for\n  forward-only Source reads), so per-strip decodes don't\n  round-trip the Source\n\n### Predictors (tag 317)\n\n- **None** (predictor=1)\n- **Horizontal** (predictor=2) — 8-bit + 16-bit endian-aware\n- **Floating-point** (predictor=3, TIFF Tech Note 3) — FP16, FP24,\n  FP32, FP64 with endian-aware byte-plane de-interleave\n\n### Sources\n\n- `Source.fromBuffer(handle)` — wrap an immutable byte slice\n  (no allocation)\n- `Source.fromBufferedReader(handle)` — sequential reader +\n  caller-supplied sliding cache, single-threaded only. See the\n  factory's doc-comment for cache-sizing guidance (IFD-at-start\n  files work with a tiny cache; IFD-at-end files need a cache\n  spanning the strip region)\n\n### Limits / defenses\n\nConfigurable via `Decoder.openWithLimits(allocator, source, limits)`:\n\n- Per-IFD tag count cap\n- Per-tag value byte cap\n- Strip / tile count cap\n- Decompressed strip byte cap\n- Codec scratch cap\n- DNG opcode count cap (default 1,000,000)\n\n## Quick start (Zig)\n\n```zig\nconst std = @import(\"std\");\nconst tiffz = @import(\"tiffz\");\n\npub fn main() !void {\n    var gpa = std.heap.GeneralPurposeAllocator(.{}){};\n    defer _ = gpa.deinit();\n    const allocator = gpa.allocator();\n\n    // Load a TIFF into a buffer.\n    const bytes = try std.fs.cwd().readFileAlloc(allocator, \"image.tif\", 64 \u003c\u003c 20);\n    defer allocator.free(bytes);\n\n    // Wrap as a Source.\n    var handle = tiffz.source.BufferHandle.init(bytes);\n    const src = tiffz.Source.fromBuffer(\u0026handle);\n\n    // Header + IFD0 parse + eager out-of-line value caching.\n    var dec = try tiffz.Decoder.open(allocator, src);\n    defer dec.deinit();\n\n    const dir = try dec.ifd(0);\n    // dir.get(tag_id) -\u003e ?*const Entry,\n    // dir.cachedValueBytes(tag_id) -\u003e ?[]const u8,\n    // dir.arrayElementU64(tag_id, index, endian, source) — cache-first\n    // for strip/tile offset and byte-count arrays.\n\n    // Decode each strip into a caller-owned buffer; expand to RGBA via\n    // photometrics.expandRowsToRgba.\n    var ws = tiffz.Workspace.init(allocator);\n    defer ws.deinit();\n}\n```\n\nThe fixture-test helpers in `tests/fixture_test.zig` show end-to-end\nstrip / tile decode and RGBA expansion against ImageMagick / libtiff\noracles — that's the most accurate reference for the full call\nsequence including planar=separate handling, multi-IFD walks, and\nthe JPEG-in-TIFF YCbCr photometric override.\n\n## Validate integration\n\ntiffz is designed to be `validate`'s TIFF deep-verification engine.\nThe mapping from tiffz errors and informational findings to\nvalidate's `RoutedFinding` taxonomy lives in\n[`docs/tiffz_findings_mapping.md`](docs/tiffz_findings_mapping.md) —\ncopy the snippet at the bottom into validate's\n`src/core/tiffz_shim.zig` to wire it up.\n\n`build.zig.zon` dep stanza:\n\n```zig\n.tiffz = .{\n    .url = \"git+https://github.com/pmarreck/tiffz#\u003ccommit\u003e\",\n    .hash = \"\",  // run `zig build --fetch=all` to fill in\n},\n```\n\n## Architecture\n\n```\nAny consumer (validate / image tools / GUI) ──► C FFI ──► tiffz Zig core (pure, no I/O)\n```\n\n- **Zig core** (`src/`) — pure decoder logic. Operates on byte\n  slices and `Source` (the I/O-shape abstraction). No I/O performed\n  by the core itself.\n- **C FFI** (`src/ffi.zig` + `include/tiffz.h`) — the real public\n  API for non-Zig consumers. Currently a scaffold\n  (`tiffz_version` exported); the Zig-module path\n  (`b.dependency(\"tiffz\").module(\"tiffz\")`) is what validate\n  consumes today.\n- **C CLI** (`cli/main.c` → `tiffz` executable) — dogfoods the\n  FFI. All I/O happens here.\n\n### Dependencies\n\n- [`pmarreck/jpegz`](https://github.com/pmarreck/jpegz) — JPEG\n  codec (currently wraps libjpeg-turbo + openjpeg; cleanroom in\n  progress)\n- [`pmarreck/zstdz`](https://github.com/pmarreck/zstdz) — ZSTD\n  codec (wraps vendored facebook/zstd C library)\n- System zlib (via `flake.nix` `buildInputs`)\n\n### Algorithmic discipline\n\nAll hot-path math is integer / fixed-point. The YCbCr inverse uses\nQ16 coefficients matching libtiff's `TIFFYCbCrToRGBInit` byte-exact;\nthe CIELAB pipeline uses Q24 fixed-point matrix multiplications\nwith comptime-generated LUTs for the L_byte→Y_d50 and sRGB-gamma\nnonlinearities. The only floating-point in the codebase is inside\n`comptime` initializers (LUT generation) — runtime is 100% integer.\n\n## Testing\n\n```bash\n./test    # runs the entire sandboxed Nix-derivation test suite\n          # (unit tests + integration fixtures + CLI tests)\n./build   # builds via `nix build`\n```\n\nReal-fixture oracle tests live in `tests/fixtures/` with byte-exact\nRGBA oracles. Each oracle is generated by either ImageMagick or\nlibtiff `tiff2rgba` — whichever is the spec-canonical reference for\nthe codec / photometric in question, documented per-test. (For\nYCbCr-derived photometrics tiffz matches `tiff2rgba` byte-exact;\nImageMagick's Q16-internal chroma upsampling drifts ±1 LSB and\nisn't the canonical reference.)\n\n## License\n\nMIT. See [`LICENSE`](LICENSE).\n\n### Cleanroom legal hygiene\n\nImplementation derived strictly from published specs. libtiff and\nother GPL / LGPL code may be used as a black-box oracle for\nverification but may not be copied or transcribed. See\n[`CLEANROOM_LEGAL_PACKET.md`](CLEANROOM_LEGAL_PACKET.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmarreck%2Ftiffz","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpmarreck%2Ftiffz","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpmarreck%2Ftiffz/lists"}