depthbake-cli
v1.1.0
Published
Local-first Depthbake package baker for depth-aware creative web assets.
Readme
depthbake-cli
A local-first batch baker for Depthbake packages.
It turns photos into portable, depth-aware assets for creative web runtimes: AVIF/WebP/JPEG photo variants plus depth.png / meta.json and optional mask.png / normal.png. It runs monocular depth estimation (Depth Anything V2) on Node (CPU) and writes output readable by depthbake-runtime.
Install
npm install -g depthbake-cliOr run it without installing via npx depthbake-cli.
Requires Node 20+. Because it includes the native binaries of sharp and onnxruntime-node, it runs only on supported platforms (macOS/Linux/Windows, x64 and arm64).
Usage
depthbake bake ./photos --out ./out<patterns...>: one or more image file paths, glob patterns, or directories (passing a directory targets thejpg/jpeg/png/webp/avif/tifffiles directly inside it)--out <dir>: output directory (defaultout). One package directory is written toout/<name>/per file--config <path>: path todepthbake.config.json(defaults are used when omitted)--mask: bundlemask.png(sky + edge masks); overridesmaps.maskin the config--normal: bundlenormal.png(world-space normals); overridesmaps.normalin the config--boundary: resolve boundaries — remove the intermediate depth values at discontinuities and store the coverage inmask.png's B channel; overridesboundary.enabledin the config. Implies--mask, since that is where the coverage lives
mask.png/normal.pngare baked purely from depth + camera meta, so consumers can derive the same data at runtime instead —depthbake-runtimeexportscomputeSkyMask/computeEdgeMask/computeNormals, andGLSL_SNIPPETS.screenSpaceNormalcovers the in-shader case. Skipping the maps shrinks the package, and under amaps.maxBytescap the freed budget raises the depth resolution. Bundle them only when you want to skip the runtime derivation cost.
The SHA-256 hash of the photo bytes + config is recorded in meta.json as sourceHash, so re-running on identical input skips the bake.
config.json
{
"version": 1,
"camera": { "fovDeg": 55, "farRange": 12 },
"sky": { "threshold": 0.03 },
"depth": { "maxSize": 1024 },
"model": { "dtype": "fp32" },
"maps": { "maxBytes": 1500000, "pngCompressionLevel": 9, "mask": false, "normal": false },
"boundary": { "enabled": false, "bandWidthPx": 0, "gradientThreshold": 0.05, "backgroundExtendPx": 0 },
"photo": {
"maxSize": 2048,
"formats": ["avif", "webp", "jpeg"],
"avifQuality": 50,
"webpQuality": 75,
"jpegQuality": 82
}
}| Field | Description |
| --- | --- |
| camera.fovDeg | Vertical FOV (degrees) of the virtual camera used by the viewer |
| camera.farRange | Depth range for the disparity → depth conversion |
| sky.threshold | Disparity below this value is treated as sky; recorded in meta.json and baked into the R channel of mask.png when bundled |
| depth.maxSize | Long-edge pixel size of the output maps (snapped to the source photo resolution with a guided filter) |
| model.dtype | Weight precision for the Node (CPU) depth inference: fp32 / fp16 / q8 / int8 / uint8 / q4 / q4f16 / bnb4. fp32 (default) is the quality baseline; quantized dtypes such as q8 trade a slight quality difference for 2–4× faster inference |
| maps.maxBytes | Maximum combined bytes for the bundled maps; 0 disables the limit. All bundled maps are rebaked at a smaller shared resolution when exceeded |
| maps.pngCompressionLevel | PNG compression level for the map PNGs (0–9). Maps are always encoded as non-palette (truecolor) PNGs — palette quantization would corrupt the RG16 depth packing |
| maps.mask / maps.normal | Bundle mask.png / normal.png (default false); their presence is declared in meta.json |
| boundary.enabled | Resolve boundaries (default false). Writes meta.boundary and meta.safeExcursion, and forces mask.png to be bundled |
| boundary.bandWidthPx | Half-width of the uncertain band around a discontinuity. 0 (default) derives it from the map long edge |
| boundary.gradientThreshold | Per-pixel depth gradient, relative to the depth dynamic range, above which a pixel is a discontinuity candidate. Raise it if a flat wall picks up cuts |
| boundary.backgroundExtendPx | How far a cut gap may open before it counts as exposed; drives meta.safeExcursion. 0 (default) uses bandWidthPx |
| photo.maxSize | Long-edge pixel size of each encoded photo variant |
| photo.formats | Ordered photo variants to emit: avif, webp, jpeg. Must include jpeg — photo.jpg is the package's mandatory final fallback |
| photo.*Quality | Per-format encoding quality (0–100) |
Fields may be omitted; defaults are merged per section.
measure / compare
Both read a baked package and never load a model.
depthbake measure ./out/hero --json
depthbake compare ./out/raw/hero ./out/hero --out ab.png --translate 0.02measure <packages...>: boundary offset (median / p95 pixel distance from each depth discontinuity to the nearest photo edge), stretch ratio, hole ratio, band pixel ratio, and package size.--translate <units>sets the camera displacement to measure at; it defaults to the package's declaredsafeExcursion.maxTranslatecompare <packages...> --out <file>: writes the packages side by side under one camera translation. Each panel is rendered the way it is actually drawn — plain displacement for a raw package, the geometry cut for a resolved one
A large unmatched count in measure means few depth discontinuities had a photo edge within the search radius; the median is not meaningful for that run.
Output format
See docs/package-format.md for the full spec of the package written by the CLI.
Building from source
pnpm install
pnpm --filter depthbake-cli build
node packages/cli/dist/index.js bake ./photosLicense
MIT
