# NYX Graphics — icosahedra.com
> Normal maps, BCn/DDS texture compression, signed distance fields and
> SDF/MSDF font atlases. Everything runs locally — the pages in a tab, the same
> tools in a headless browser or a shell. Nothing is uploaded.
## Agents: do not automate the tool pages
/normals/, /dxt/, /sdf/ and /font/ only give results back as browser downloads.
Use one of the two routes below; both run the pages' own modules and the same
wasm, so the bytes are identical to what the download buttons produce.
**A browser, nothing installed.** loads the CLI
as `window.nyx`:
```js
await page.goto('https://icosahedra.com/cli/');
const out = await page.evaluate(`nyx.sdf('${pngBase64}', { chan: 3, size: 256 })`);
```
From your own page or worker, any origin:
`const { sdf, run } = await import('https://icosahedra.com/api/nyx.js')`
**A shell**, with JavaScriptCore (ships with macOS) or Node 22.7+:
```sh
curl -fsSL https://icosahedra.com/cli/install.sh -o install.sh
sh install.sh ./nyx # 37 files, ~1 MB, no build step
./nyx/nyx.sh sdf logo.png --chan 3 --size 256
```
## API
```
nyx.(input, options) -> { ok, file, files, json, stdout, stderr, exitCode }
input base64 | data: URL | Uint8Array | ArrayBuffer
{ 'logo.png': bytes } when the name matters — nyx dxt names its DDS after it
nyx.dxt takes source slots: { a: bytes, b: bytes, c: bytes, d: bytes }
options one key per flag listed below, without the dashes and camelCased:
--atlas-max 4096 -> atlasMax: 4096 · --psnr -> psnr: true · --no-simd -> simd: false
an array is whatever the flag takes: eq: [3,2.5,2,1.5,1] is one value,
map: ['R=A.LUMA', 'A=ONE'] is --map twice
file the output as base64, when the run wrote exactly one file; else null
files { name: base64 }, always · pass binary: true for Uint8Array values
json stdout parsed, when the run printed JSON (--json; --json-summary for font)
exitCode 0 ok · 1 usage error · 2 runtime failure · ok === (exitCode === 0)
stderr progress and errors, as the shell would have printed them
nyx.spec('sdf') every option of one tool, with its default, range and help,
keyed the way you would write it — ask rather than guess
nyx.run('sdf in.png --chan 3', { 'in.png': bytes }) any flag, same result object
nyx.preload() fetch the ~900 KB of wasm early
An option a tool does not have comes back as exitCode 1 and a stderr naming
every option it does have, so a wrong guess costs one call, not a lookup.
shell: nyx [options] — same flags, same defaults, same exit codes,
--help per tool, files written to disk instead of returned
```
Input is PNG, any bit depth or colour type, non-interlaced; fonts are TTF, OTF
or TTC. There is no JPEG or WebP decoder off the browser. In a page the work
runs on the calling thread, so use a worker if the caller has a UI to keep
alive. Defaults below are the web page's controls, so a default run reproduces
the page.
## nyx normals · nyx.normals(input, options)
Normal, height and cavity-AO maps from a height or depth image.
```
nyx normals [options]
depth.png height or depth image; a 16-bit PNG keeps full precision, anything else is 8-bit luminance
--out-dir . where the default names land
--normal — write the 8-bit normal map here
--normal16 — write the 16-bit normal map here (recomputed from the float normals, like the page)
--height — write the 8-bit processed height view here
--height16 — write the input depth as a 16-bit grey PNG (needs 16-bit input)
--ao — write the cavity AO map here
--all off write normal, height and AO (and the 16-bit height when the input has it) under the page's names
--black 0 black point, % (0..99)
--white 100 white point, % (1..100)
--gamma 1 height gamma (0.25..4)
--sharpen 0 unsharp amount (0..2)
--smooth 1 gaussian smoothing sigma, px (0..8)
--[no-]invert off dark pixels become high
--power 1 overall height, as the page's ×gain (0..100)
--eq 2,2,2,2,2 five band gains, finest to low (×2 is the page's reference) (0..100)
--convention gl OpenGL +Y or DirectX −Y (gl|dx)
--[no-]dither on 4×4 Bayer dither on the normal map
--ao-intensity 1 cavity AO strength (0..4)
--[no-]simd on use heightfield-simd.wasm where the engine runs it
--json off print the summary as JSON on stdout
--quiet off no progress on stderr
```
## nyx dxt · nyx.dxt(input, options)
Pack up to four sources into RGBA, compress to BC1/3/4/5/7 and write a DDS.
```
nyx dxt -a [-b/-c/-d ...] [options]
-a — source slot A
-b — source slot B
-c — source slot C
-d — source slot D
--preset custom channel layout, format and colour space in one (custom|ue-orm|unity-mask|bc5-normal|passthrough)
--map — one output channel, e.g. R=A.LUMA or G=B.R,invert,black=12,white=240,gamma=1.7 or A=ONE. Repeatable; overrides the preset
--format bc7 block format (bc1|bc3|bc4|bc5|bc7)
--quality high encoder tier (fast|high)
--[no-]srgb off tag the DXGI format sRGB and filter mips in linear light (BC1, BC3 and BC7 only)
--[no-]mips on write the whole mip chain
--width — output width; the first slot in use sets it by default (1..16384)
--height — output height (1..16384)
--dds — output DDS (default -.dds in --out-dir)
--png — also write the packed RGBA as a PNG
--out-dir . where the default names land
--psnr off decode the base level back and report PSNR over the channels the format stores
--list-presets off print the preset table and exit
--[no-]simd on use bcn-simd.wasm where the engine runs it
--json off print the summary as JSON on stdout
--quiet off no progress on stderr
```
## nyx sdf · nyx.sdf(input, options)
Signed distance field from a shape image: 1-channel SDF, 2-channel convex-corner field or 3-channel MSDF.
```
nyx sdf [options]
input.png the shape: alpha when it has any, otherwise luminance
--out — output PNG (default sdf-ch-x.png in --out-dir)
--out-dir . where the default name lands
--chan 1 1 standard, 2 convex corners (Green 2007), 3 MSDF (1..3)
--size 64 output size on the long edge, in texels (8..4096)
--spread 16 distance range in output pixels (1..256)
--pad 0 padding in output pixels; negative crops in
--threshold 128 alpha or luminance cutoff (1..254)
--sign inside which side is positive (inside|outside)
--json off print the summary as JSON on stdout
--quiet off no progress on stderr
```
## nyx font · nyx.font(input, options)
SDF, MSDF or MTSDF glyph atlas with metrics and kerning, straight from a TTF, OTF or TTC.
```
nyx font [options]
font.ttf TrueType, OpenType or a TrueType collection (WOFF is not supported)
--out-dir . where the default names land
--png — atlas PNG (default --px.png)
--json — layout JSON (default --px.json)
--font — venus .font bytes (SDF mode only)
--all off also write the .font when the mode is sdf
--mode msdf 1, 3 or 4 channels (sdf|msdf|mtsdf)
--em 48 glyph size, pixels per em (4..512)
--px-range 8 distance range in texels (the shader's pxRange) (1..64)
--padding 1 texels between glyph boxes (0..16)
--atlas-max 2048 largest atlas edge (64..16384)
--[no-]pot off power-of-two atlas height (otherwise a multiple of 32)
--outline curves evaluate the outline exactly, or rasterize it first (curves|raster)
--raster-factor 8 raster upscale, and the curve path's fallback (4|8|12|16)
--y-origin bottom atlas bounds origin (bottom|top)
--charset latin comma-separated presets; ASCII is always included
--chars extra characters, literally
--ranges extra code points, e.g. "U+2190-21FF,20AC"
--face 0 face index in a collection (0..255)
--info off print the face, its metrics and the charset coverage, and stop
--list-charsets off print the charset presets and exit
--json-summary off print the run summary as JSON on stdout
--quiet off no progress on stderr
```
## Pages
- — normal, height and cavity-AO maps, five-band EQ, 16-bit, optional on-device AI depth (browser only)
- — channel packing, BC1/3/4/5/7, DDS with mips, decode and PSNR
- — 1-, 2- or 3-channel distance fields
- — SDF/MSDF/MTSDF atlases, msdf-atlas-gen JSON, venus .font
- — the above for people, and the page that carries `window.nyx`
- — layout, the three places CLI output differs from the page, how it is tested
- — third-party notices and licenses
## Notes
- Nothing is uploaded by any route: every request is for this site's own files.
- The compression, distance-field and font code is compiled Rust (the venus
engine) shipped as WebAssembly, used unchanged by the pages, the browser
route and the CLI.