nx_dedither.nx
buildroot/runtime/nx_dedither.nx
about
nx_dedither.nx -- RECOVER A 1990s DITHERED IMAGE WITHOUT TOUCHING THE ORIGINAL BYTES.
WHY THIS EXISTS (measured 2026-08-07, page3.com/13Nov96.gif): a 280x403 GIF weighing 81,953 B is a
PHOTOGRAPH crushed into a 256-entry palette. The encoder of 1996 traded colour depth for file size by
scattering alternating palette entries across smooth regions -- so a plain studio backdrop is stored as
high-frequency speckle. The archive serves those bytes FAITHFULLY, and faithful is exactly why the
backdrop reads as white fuzz. The fuzz is not our defect and not decode damage: header valid, trailer
0x3B present, extension preserved 1:1 from the 1996 HTML, no re-encode anywhere in the pipeline.
*THE ARCHIVE WAS RIGHT AND THE PICTURE STILL LOOKED WRONG. Those are different problems and only the
second one is fixable. Rule 25 says build intelligence, never strip features -- so this NEVER rewrites
the stored asset. It emits a SEPARATE enhanced render beside it. The original remains the record.
HOW: a sigma filter (Lee 1983) -- average only over neighbours whose colour is already CLOSE to the
centre. Dither speckle is by construction a set of near-neighbours straddling the true value, so it
averages away; a real edge has far neighbours, which are EXCLUDED from the mean and therefore survive.
A plain box blur would also lower the noise metric -- and would destroy the picture. That is precisely
why the selftest below refuses to pass on noise reduction alone.
RULER: mean absolute local deviation (MALD) vs a 3x3 mean, x1000, integer. Reported BEFORE and AFTER
so the claim is a measurement and not an adjective.
usage: nx_dedither <in-image> <out.png> [radius=2] [threshold=40]
nx_dedither --kat (selftest: 4 teeth, 2 of them anti-vacuity)
argv[1]=input (any format nx_img_bytes_to_rgb decodes), argv[2]=output PNG.
exit 0 ok | 2 decode/write failure | 3 usage | 1 selftest RED
license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
dependencies 4 imports · 0 importers
imports: nx_syscalls.nxnx_img_to_rgb.nxnx_png_write.nxnx_img_resample.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 41 | const DD_MAXR: i64 = 8 |
| 42 | const DD_MAXSCALE: i64 = 4 |
| 55 | const DD_DITHER_MIN: i64 = 450 |
| 56 | const DD_MAXTHR: i64 = 255 |
| 484 | const DD_DIRBUF: i64 = 262144 |
| 485 | const DD_RECLEN_OFF: i64 = 16 |
| 486 | const DD_NAME_OFF: i64 = 19 |
functions
| 58 | func dp(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 } |
| 59 | func de(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(2,s,n); return 0 } |
| 60 | func dnum(v: i64) -> i64 |
| 73 | func dd_atoi(s: *u8) -> i64 called by 1: main |
| 90 | func dd_mald(rgb: *u8, w: i64, h: i64) -> i64 |
| 126 | func dd_localmean(rgb: *u8, w: i64, x: i64, y: i64, c: i64) -> i64 |
| 149 | func dd_meangrad(rgb: *u8, w: i64, h: i64) -> i64 |
| 189 | func dd_altrate(rgb: *u8, w: i64, h: i64) -> i64 |
| 228 | func dd_sigma(src: *u8, dst: *u8, w: i64, h: i64, r: i64, thr: i64) -> i64 |
| 286 | func dd_coldiff(rgb: *u8, w: i64, h: i64, xa: i64, xb: i64) -> i64 called by 1: dd_kat |
| 305 | func dd_kat() -> i64 |
| 444 | func dd_classify(path: *u8) -> i64 |
| 488 | func dd_cat(dst: *u8, off: i64, src: *u8) -> i64 { var o: i64=off; var j: i64=0; while src[j]!=(0 as u8) { dst[o]=src[j]; o=o+1; j=j+1 } return o } |
| 490 | func dd_census(dir: *u8, maxn: i64) -> i64 |
| 565 | func dd_is_palette(path: *u8) -> i64 |
| 589 | func dd_endswith(nm: *u8, sfx: *u8) -> i64 called by 1: dd_sweep |
| 610 | func dd_sweep(dir: *u8, maxn: i64, r: i64, thr: i64, scale: i64) -> i64 |
| 724 | func main(argc: i64, argv: *i64) -> i64 |