crapify 0.4.0

Deep-fry your images, and other crimes against pixels.
# Changelog

All notable changes to `crapify` are recorded here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## v0.4.0

_Released 2026-05-17._

### Added

- **`webcam-1999`** — fourth crapifier. Simulates the 1999 USB-webcam look: nearest-neighbor downsample to a low internal resolution, recolor with a CCD-style saturation/tint pair, oversharpen the small buffer, then nearest-neighbor upsample back to the original dimensions so the sharpen halos blow up into block-scale stripes around the chunky pixel boundaries. Finishes with occasional replicate-previous-row scanline tears. Three presets: `quickcam-vc` (640×480, slight warm cast), `aim-buddy` (320×240, cool cyan cast — the default), `parents-bought-the-cheap-one` (160×120, washed colors, visible tears).
- **Seven webcam-1999 knobs** — `--downsample-w`, `--downsample-h`, `--preserve-aspect` (escape hatch from the default aspect-mangle), `--sharpen-radius` (same parameter as deep-fry's), `--saturation`, `--tint <#rrggbb>`, `--scanline-drop-rate`. All overridable on top of `--preset`; run `crapify webcam-1999 --help` for units and ranges.
- **Pipeline composability** — webcam-1999 emits RGB output, chains cleanly with `deep-fry`, `palettecrush`, and `xerox`.

## v0.3.0

_Released 2026-05-17._

### Added

- **`xerox`** — third crapifier. Simulates a photocopy of a photocopy of a photocopy: per-pass blur, compounding contrast, toner-speckle noise, optional skew, optional jitter, optional 1-bit threshold. Three presets: `decent-copy` (1 pass, light), `staff-meeting-handout` (3 passes, jittery — the default), `from-a-fax-from-1994` (8 passes, binarized, tilted).
- **Eight xerox knobs** — `--passes`, `--contrast`, `--blur`, `--noise`, `--skew-deg`, `--jitter`, `--threshold`, `--threshold-timing` (`each-pass`/`end-of-chain`, only meaningful with `--threshold`). All overridable on top of `--preset`; run `crapify xerox --help` for units and ranges.
- **Pipeline composability** — xerox emits RGB output, chains cleanly with `deep-fry` and `palettecrush`.

### Changed

- `deep-fry --noise` help text now states the unit (raw `0..=255` pixel stddev, not a `0..=1` fraction) and drops a stale reference to a `--seed` flag that doesn't actually exist.
- `palettecrush --help` footer gained the override-and-default reminder (`Knobs override preset values when both are supplied: ...` + `With no flags, defaults resolve to the 'geocities' preset.`) that `deep-fry --help` already had — was ending abruptly after the DITHERS table.

## v0.2.0

_Released 2026-05-17._

### Added

- **`palettecrush`** — second crapifier. Snap every pixel to a tiny palette for the "saved-from-a-1997-GeoCities-page" look. Three named presets: `geocities` (16-color CSS palette + 8×8 Bayer crosshatch, the default), `gameboy-pocket` (4 swamp greens, no dither), `cga-eyestrain` (CGA cyan/magenta, no dither).
- **Twelve built-in palettes** behind `--palette <slug>` — `web-safe-16`, `web-safe-216`, `vga-16`, `ega-16`, `cga0-lo`, `cga1-lo`, `cga1-hi`, `gameboy-pocket`, `mac-classic-16`, `nes-54`, `c64-16`, `atari2600`. `web-safe-16` (CSS-1 named colors) and `vga-16` (IRGB IBM palette) are intentionally distinct — about half their colors overlap; the rest don't.
- **Adaptive quantization** via `--colors N` (2..=256) using NeuQuant — same algorithm `image` uses for GIF encoding. Mutually exclusive with `--palette`.
- **Five dither options** behind `--dither <slug>` — `none`, `bayer-2`, `bayer-4`, `bayer-8`, `floyd-steinberg`. Each preset has a sensible default that `--dither` overrides.
- **Determinism guarantee** — `palettecrush`'s entire pipeline is reproducible from inputs alone (NeuQuant has fixed initial state, Bayer is positional, Floyd-Steinberg is scan-order). No `--seed` debt accrues.

### Performance

- **5-bit RGB lookup table for nearest-color quantization** — palettes of 8+ colors build a 32 KB (5-bit-per-channel) RGB → palette-index table at construction time so the per-pixel hot path becomes one L1-resident array fetch instead of an O(palette_size) distance scan. Benched on a 44 MP nebula image: `web-safe-216` quantization drops from 5.56 s to 387 ms (14.4× faster), `atari2600` from 3.44 s to 327 ms (10.5×), `nes-54` from 1.58 s to 324 ms (4.9×). The same speedup applies under all dither modes. Bucket size of 8 sRGB units per channel introduces at most ~3.6 ΔE error in pathological cases (two palette entries colliding in one bucket, e.g., NES's two dark greens at `[0,73,0]` and `[0,79,0]`), well below the palette's own quantization noise.

## v0.1.0

_Released 2026-05-17._

First public release. `crapify` is a CLI that does the opposite of every other image tool: it takes a fine image and makes it look like dogshit on purpose. v0.1.0 ships one crapifier — `deep-fry` — plus the pipeline runner that future crapifiers will plug into.

### Highlights

- **`deep-fry`** — the mid-2010s "this meme has been forwarded so many times" aesthetic: low-quality JPEG passes, boosted saturation/contrast, sharpened edges, sprinkled grain.
- **Five doneness presets** — `raw`, `pan-seared`, `chicken-fried`, `deep-fried` (default), `forwarded-by-your-dad`. Override any individual knob alongside `--preset` to mix-and-match.
- **Pipeline grammar** — stages separated by `+` (`crapify deep-fry + xerox in.png out.png`). Only `deep-fry` ships, but the runner is multi-stage-ready.
- **Single static Rust binary** — `cargo install crapify` or grab a prebuilt archive from the GitHub Release.

Full docs: `crapify --help` / `crapify deep-fry --help`.