open-standards/wai/wai-web/src/neural_int.rs
Transaction Science 4d7b9e28fd
All checks were successful
wai-quantum WebGPU member / member · DX12 and Vulkan (cbio-windows) (pull_request) Successful in 12m4s
wai-ops differential (nightly) / differential · debug (pull_request) Successful in 2m26s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 5s
wai-ops differential (nightly) / differential · release (pull_request) Successful in 3m4s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Successful in 2m54s
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Successful in 4m32s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 5s
standards workspaces build + test / macOS energy backends (pull_request) Has been skipped
wai cross-architecture byte-equality / does this change touch it (pull_request) Successful in 4s
WAI conformance / does this change touch it (pull_request) Successful in 4s
WAI energy receipts / does this change touch it (pull_request) Successful in 5s
wai formal verification (integer core) / does this change touch it (pull_request) Successful in 5s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 4s
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Successful in 23s
WAI JPEG-AI whole decode (pinned inputs, required) / does this change touch it (pull_request) Successful in 4s
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Successful in 29s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 50s
WAI neural sink gates / does this change touch it (pull_request) Successful in 5s
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 5s
wai-ops integer operators / does this change touch it (pull_request) Successful in 6s
wai-quantum WebGPU member / does this change touch it (pull_request) Successful in 4s
WAI mbt2018_mean integer decode (regenerated vectors) / does this change touch it (pull_request) Successful in 4s
wai-web wasm32 / does this change touch it (pull_request) Successful in 5s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Successful in 29s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 17s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m10s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 2m55s
wai formal verification (integer core) / every harness in one shard (pull_request) Successful in 11s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 9m31s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 23s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 10m41s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 52s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 12s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 11m24s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 1m42s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 27s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 58s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 11m47s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m11s
wai formal verification (integer core) / kani (arith) (pull_request) Successful in 10m15s
wai-quantum WebGPU member / member · discrete GPU, Vulkan (jetson-hub) (pull_request) Successful in 20m20s
wai formal verification (integer core) / kani (plan) (pull_request) Successful in 7m49s
wai formal verification (integer core) / kani (rans64-t2) (pull_request) Successful in 14m52s
wai-pack / wai-pack · macos (pull_request) Successful in 1m20s
wai-ops integer operators / macos-arm64 (pull_request) Successful in 1m7s
wai-quantum WebGPU member / member · Metal (macos) (pull_request) Successful in 5m13s
wai formal verification (integer core) / kani (sound) (pull_request) Successful in 11m45s
wai-quantum WebGPU member / member · browser WebGPU (macos) (pull_request) Successful in 1m50s
wai formal verification (integer core) / kani (stage) (pull_request) Successful in 3m13s
wai formal verification (integer core) / kani (isqrt512) (pull_request) Successful in 49m3s
wai formal verification (integer core) / kani (stage-long) (pull_request) Successful in 21m19s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 46m48s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 47m39s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 46m52s
WAI JPEG-AI whole decode (pinned inputs, required) / fetch pinned JPEG-AI inputs → whole decode + pixel hash, required (pull_request) Successful in 1m28s
WAI neural sink gates / model-free refusals of the neural sink (pull_request) Successful in 1m20s
wai-pack / wai-pack · linux (pull_request) Successful in 1m19s
wai-ops integer operators / linux-x86_64 (pull_request) Successful in 52s
wai formal verification (integer core) / kani (integer-core) (pull_request) Successful in 6m8s
wai-ops integer operators / wasm32 under Node (pull_request) Successful in 22s
wai-ops integer operators / wasm32-wasip2 (wasmtime) (pull_request) Successful in 42s
wai-ops integer operators / independent checker (Python) (pull_request) Successful in 17s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 6m1s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 4s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 6m51s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 6m18s
wai-quantum WebGPU member / member · software Vulkan (linux) (pull_request) Successful in 2m7s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m11s
wai-quantum WebGPU member / shader checks · browser build · wasip2 inertness · MSRV (pull_request) Successful in 3m6s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m20s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 2m49s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 23s
wai-ops integer operators / linux-aarch64 (pull_request) Successful in 1m8s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 1m17s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m17s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 4m6s
WAI mbt2018_mean integer decode (regenerated vectors) / regenerate mbt2018_mean integer vectors → Rust decode, required (pull_request) Successful in 5m35s
wai: the store stays out of the sources the byte-equality crate includes
byte-exact-conformance #[path]-includes int_synth.rs and int_hyper_synth.rs, and
has no int_set_cache, codecs or pin module, so gate.sh's byte-exact step failed to
compile. int_synth::decode_from_spec is now three steps (check_payload,
load_spec, decode_loaded) and loads its set every time, as before the store;
int_payload's wai.neural.int_synth decode takes the middle step from the store.
wai-web's decodeIntSynth decodes through decode_int, as the other payload entry
points do (a refusal's message now starts with its code). The Resident impl for
int_hyper_synth::ops::Nets moves into int_set_cache.rs.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-15 11:51:01 -04:00

304 lines
16 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! In-browser **integer-exact** neural decode — zero JS ML runtime.
//!
//! The rest of this crate is parse-and-dispatch: it names a capability and
//! hands the payload to whatever backend the deployer registered (ONNX,
//! WebGPU, transformers.js). This module is the exception the integer-entropy
//! contract earns. `int_synth` (integer synthesis) and `int_entropy` (the
//! deterministic scale→bucket step) are pure i64 — no `ort`, no float — so the
//! SAME Rust that decodes natively compiles straight to wasm32 and runs in the
//! browser with **no ML runtime at all**. The bytes it produces are identical
//! to the native decode on every machine: a learned-codec reconstruction you
//! can verify in-page against a receipt.
//!
//! `decodeIntSynth(payload, specJson)` → RGB. The host supplies the model spec
//! bytes (the `int_synth.json` weights); WAI carries the payload. The
//! `int_entropy` primitives are exposed too, so a future conditional decode can
//! run its bucketing in-browser deterministically.
use wasm_bindgen::prelude::*;
/// An integer-exact decoded image: RGB row-major, `width * height * 3` bytes.
#[wasm_bindgen]
pub struct DecodedImage {
rgb: Vec<u8>,
width: u32,
height: u32,
}
#[wasm_bindgen]
impl DecodedImage {
/// RGB bytes, row-major, length `width * height * 3`. Copy into an
/// `ImageData` (after widening to RGBA) to paint a canvas.
#[wasm_bindgen(getter)]
pub fn rgb(&self) -> Box<[u8]> {
self.rgb.clone().into_boxed_slice()
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> u32 {
self.width
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> u32 {
self.height
}
}
/// Decode a `wai.neural.int_synth` payload to RGB in the browser, integer-exact.
/// `payload` is the envelope's payload bytes; `spec_json` is the model spec (the
/// `int_synth.json` weights) the host has installed. No float, no ONNX — the
/// output is byte-identical to the native reference decode. It runs
/// `wai::int_payload::decode_int`, as the other payload entry points do, so a set it
/// has loaded is kept for the next decode (`loadedSetStats`), and a refusal's message
/// starts with its code (`payload_malformed`).
#[wasm_bindgen(js_name = decodeIntSynth)]
pub fn decode_int_synth(payload: &[u8], spec_json: &[u8]) -> Result<DecodedImage, JsError> {
match crate::int_payload::decode("wai.neural.int_synth", payload, &[("int_synth.json", spec_json)]).map_err(|e| JsError::new(&e))? {
wai::int_payload::IntDecoded::Image { rgb, width, height } => Ok(DecodedImage { rgb, width, height }),
_ => Err(JsError::new("unsupported_capability: not an image")),
}
}
/// Decode a `WIH1` payload (`wai.neural.int_hyper`, `extensions/integer-payloads.md`
/// §4) → RGB in the browser, byte-identical to the native reference, with the
/// conditional codec's set: `g_s.whm` and `h_s.whm` (the compact `WHM1` networks) and
/// `entropy.json` (the tables). A refusal's message starts with its code
/// (`bad_magic` for a JSON form). No float, no ONNX.
#[wasm_bindgen(js_name = decodeWih1)]
pub fn decode_wih1(payload: &[u8], g_s: &[u8], h_s: &[u8], entropy: &[u8]) -> Result<DecodedImage, JsError> {
let set = crate::int_payload::int_hyper_set(g_s, h_s, entropy);
match crate::int_payload::decode("wai.neural.int_hyper", payload, &set).map_err(|e| JsError::new(&e))? {
wai::int_payload::IntDecoded::Image { rgb, width, height } => Ok(DecodedImage { rgb, width, height }),
_ => Err(JsError::new("unsupported_capability: not an image")),
}
}
/// Decode the tool's JSON conditional bitstream → RGB in the browser. For debugging
/// and conversion only: the payload `wai.neural.int_hyper` carries is `WIH1`
/// ([`decode_wih1`]). The rest of this note describes the decode both share:
/// integer-exact (`wai.neural.int_hyper`). The host installs the model
/// (`int_synth_sota_model.json` = g_s, `int_hs_model.json` = h_s,
/// `cond_entropy.json` = the entropy tables); WAI carries the bitstream
/// (`cond_bitstream.json`). The WHOLE decode — factorized z, integer h_s,
/// integer-σ entropy, integer g_s — is pure i64, no float, no ONNX, so the RGB
/// is byte-identical to the native reference on every machine: a pretrained
/// learned codec decoding deterministically in-page, zero JS ML runtime.
#[wasm_bindgen(js_name = decodeIntHyper)]
pub fn decode_int_hyper(
g_s_json: &[u8], h_s_json: &[u8], entropy_json: &[u8], bitstream_json: &[u8],
) -> Result<DecodedImage, JsError> {
use wai::int_hyper_synth::IntModel;
let parse = |b: &[u8]| -> Result<serde_json::Value, JsError> {
serde_json::from_slice(b).map_err(|e| JsError::new(&e.to_string()))
};
let net = |b: &[u8]| IntModel::try_from_json(&parse(b)?).map_err(|k| JsError::new(&format!("model_mismatch: a model's `{k}`")));
cond_decode(net(g_s_json)?, net(h_s_json)?, entropy_json, bitstream_json)
}
/// Same decode from the **compact binary** model (`*_model.bin`, int12 weights as
/// `i16`), of the tool's JSON bitstream. For debugging and conversion only: the
/// payload is `WIH1` ([`decode_wih1`]). Byte-identical to the JSON model and to the
/// native reference.
#[wasm_bindgen(js_name = decodeIntHyperBin)]
pub fn decode_int_hyper_bin(
g_s_bin: &[u8], h_s_bin: &[u8], entropy_json: &[u8], bitstream_json: &[u8],
) -> Result<DecodedImage, JsError> {
use wai::int_hyper_synth::IntModel;
let g_s = IntModel::from_bytes(g_s_bin).map_err(JsError::new)?;
let h_s = IntModel::from_bytes(h_s_bin).map_err(JsError::new)?;
cond_decode(g_s, h_s, entropy_json, bitstream_json)
}
fn cond_decode(
g_s: wai::int_hyper_synth::IntModel, h_s: wai::int_hyper_synth::IntModel,
entropy_json: &[u8], bitstream_json: &[u8],
) -> Result<DecodedImage, JsError> {
let (rgb, height, width) = cond_decode_checked(&g_s, &h_s, entropy_json, bitstream_json).map_err(|e| JsError::new(&e))?;
Ok(DecodedImage { rgb, width: width as u32, height: height as u32 })
}
/// The debug JSON decode, through the checked readers (`CondEntropy::try_from_value`,
/// `CondBitstream::try_from_value`) and `decode_conditional_checked`, so a JSON the
/// export tool did not write is refused, never a panic: the pure-Rust half the native
/// tests run.
pub(crate) fn cond_decode_checked(
g_s: &wai::int_hyper_synth::IntModel, h_s: &wai::int_hyper_synth::IntModel, entropy_json: &[u8], bitstream_json: &[u8],
) -> Result<(Vec<u8>, usize, usize), String> {
use wai::int_hyper_synth::{decode_conditional_checked, CondBitstream, CondEntropy};
let parse = |b: &[u8]| serde_json::from_slice::<serde_json::Value>(b).map_err(|e| e.to_string());
let ent = CondEntropy::try_from_value(&parse(entropy_json)?).map_err(|e| format!("model_mismatch: {e}"))?;
let bs = CondBitstream::try_from_value(&parse(bitstream_json)?).map_err(|k| format!("field_range: `{k}`"))?;
decode_conditional_checked(g_s, h_s, &ent, &bs.stream()).map_err(|r| r.to_string())
}
#[cfg(test)]
mod tests {
use serde_json::json;
use wai::int_hyper_synth::IntModel;
fn read(rel: &str) -> Vec<u8> {
let p = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../wai-rs/tests/vectors").join(rel);
std::fs::read(&p).unwrap_or_else(|e| panic!("{}: {e}", p.display()))
}
/// The debug JSON decode refuses, rather than panics on, a JSON the export tool
/// did not write: an empty model or bitstream, a hyper-latent of 2^61 elements,
/// more hyper-latent channels than tables. The committed tiny codec decodes.
#[test]
fn the_debug_json_decode_refuses_instead_of_panicking() {
assert!(IntModel::try_from_json(&json!({})).is_err());
let (g, h) = (IntModel::from_bytes(&read("int_hyper/tiny_g_s.bin")).unwrap(), IntModel::from_bytes(&read("int_hyper/tiny_h_s.bin")).unwrap());
let ent = read("int_hyper/tiny_entropy.json");
let got = super::cond_decode_checked(&g, &h, &ent, &read("int_hyper/tiny_bitstream.json")).unwrap();
assert_eq!((got.1, got.2), (16, 16));
// The JSON models read through the checked reader decode alike.
let net = |rel: &str| IntModel::try_from_json(&serde_json::from_slice(&read(rel)).unwrap()).unwrap();
let from_json = super::cond_decode_checked(&net("int_hyper/tiny_g_s.json"), &net("int_hyper/tiny_h_s.json"), &ent, &read("int_hyper/tiny_bitstream.json")).unwrap();
assert_eq!(from_json, got);
let bs = |v: serde_json::Value| serde_json::to_vec(&v).unwrap();
let zeros = vec![0u8; 8];
for (what, b) in [
("{}", bs(json!({}))),
("n_z·zh·zw = 2^61", bs(json!({"z_rans": zeros, "rans": zeros, "n_z": 1u64 << 20, "zh": 1u64 << 20, "zw": 1u64 << 21, "m": 1, "sh": 1, "sw": 1}))),
("more z channels than tables", bs(json!({"z_rans": zeros, "rans": zeros, "n_z": 4096, "zh": 1, "zw": 1, "m": 1, "sh": 1, "sw": 1}))),
] {
let r = std::panic::catch_unwind(|| super::cond_decode_checked(&g, &h, &ent, &b));
assert!(matches!(r, Ok(Err(_))), "{what}: {r:?}");
}
}
}
// ---- int_entropy: the deterministic scale→bucket primitive, in the browser ----
/// Precompute a CompressAI-style ascending `scale_table` as fixed-point integer
/// bucket edges (Q`frac`). Returns a `BigInt64Array` to JS.
#[wasm_bindgen(js_name = fixedScaleEdges)]
pub fn fixed_scale_edges(scale_table: &[f32], frac: u32) -> Vec<i64> {
wai::int_entropy::fixed_scale_edges(scale_table, frac)
}
/// Snap a float σ to the fixed-point grid (Q`frac`), deterministic floor.
#[wasm_bindgen(js_name = quantizeScale)]
pub fn quantize_scale(sigma: f32, frac: u32) -> i64 {
wai::int_entropy::quantize_scale(sigma, frac)
}
/// Integer `build_index`: bucket a fixed-point σ to a scale-table index — pure
/// integer, identical on every machine given the same `sigma_q`. `edges_q` from
/// [`fixed_scale_edges`].
#[wasm_bindgen(js_name = detBuildIndex)]
pub fn det_build_index(sigma_q: i64, edges_q: &[i64]) -> usize {
wai::int_entropy::det_build_index(sigma_q, edges_q)
}
/// Lowercase-hex BLAKE3 of `bytes` — the in-page receipt check. Hash the RGB of
/// an integer-exact decode and compare it to the digest a publisher pinned: the
/// reconstruction either matches on this machine or it does not, verified
/// client-side with no server round-trip. This is the whole point of an
/// integer-exact decode — the hash is portable.
#[wasm_bindgen(js_name = blake3Hex)]
pub fn blake3_hex(bytes: &[u8]) -> String {
blake3::hash(bytes).to_hex().to_string()
}
// ---- JPEG-AI byte-exact mANS: the ISO entropy decode, in the browser ----
/// Decode the ISO/IEC 6048 JPEG-AI **mANS** entropy stream in the browser, byte-exact —
/// the same pure-i64 decoder native sinks run (`wai::jpegai_mans`), compiled to wasm32.
///
/// A **reproduction vector**: given the reference stream `mem`, the integer PMF rows
/// (`pmf_flat`, `n_rows × 256` row-major), the per-distribution `bounds`, and the
/// per-position `sigma` distribution indices + coded-position `masks` (0/1), it returns
/// the decoded residual symbols. The decode is entirely integer, so the symbols — and
/// their hash — are identical on every machine. The page hashes them and compares to the
/// digest the native reference produced; a byte-identical match across host + browser is
/// the whole point of the integer-entropy contract. This is the same stream and
/// symbols the cross-architecture byte-equality matrix checks natively.
#[wasm_bindgen(js_name = decodeJpegAiMans)]
pub fn decode_jpegai_mans(
mem: &[u8],
pmf_flat: &[i64],
bounds: &[u8],
sigma: &[u8],
masks: &[u8],
) -> Vec<i16> {
use wai::jpegai_mans::{build_decode_transitions, cdf_from_pmf, MansDecoder};
const WIDTH: usize = 256;
let rows = pmf_flat.len() / WIDTH;
let pmf: Vec<Vec<i64>> =
(0..rows).map(|r| pmf_flat[r * WIDTH..(r + 1) * WIDTH].to_vec()).collect();
let flat: Vec<u32> = build_decode_transitions(&pmf, &cdf_from_pmf(&pmf))
.iter()
.flatten()
.copied()
.collect();
let mut dec = MansDecoder::new(mem, &[mem.len() as u32]);
dec.set_sgm_transitions(&flat, bounds);
let masks_b: Vec<bool> = masks.iter().map(|&m| m != 0).collect();
let mut out = vec![0i16; sigma.len()];
dec.decode_sgm(sigma, &mut out, &masks_b);
out
}
// ---- JPEG-AI full decode → RGB, in the browser (PTQ value/synthesis path) ----
/// Decode a full ISO/IEC 6048 JPEG-AI codestream to RGB in the browser
/// (`wai.image.jpegai`). The same exact decoder a native sink runs
/// (`wai::jpegai_decode::decode`), compiled to wasm32: an integer entropy decode and
/// integer PTQ networks, then dequantisation, post-filters and colour conversion in
/// IEEE-754 float with a fixed evaluation order. It pins one reconstruction, which
/// reproduces across machines under the conditions in
/// `extensions/determinism-tiers.md` §5; that is a property of this decoder, not of
/// the capability, which is registered at entropy-consistency and does not back
/// `intent = replicate`. It decodes one configuration and refuses any other
/// codestream with the reason (`wai::jpegai_decode::check_scope`). The 13 byte
/// inputs are the model weight bundle (`tools/wai_jpegai_build_e2e_inputs.py`):
/// per-plane hyper-scale / hyper-decoder / synthesis, the four luma MCM stages, the
/// eICCI net, and the normative tables (`norm`).
#[wasm_bindgen(js_name = decodeJpegAiFull)]
#[allow(clippy::too_many_arguments)]
pub fn decode_jpegai_full(
codestream: &[u8],
hs_y: &[u8], hyp_y: &[u8], synth_y: &[u8],
hs_uv: &[u8], hyp_uv: &[u8], synth_uv: &[u8],
mcm0: &[u8], mcm1: &[u8], mcm2: &[u8], mcm3: &[u8],
icci: &[u8], norm: &[u8],
) -> Result<DecodedImage, JsError> {
use wai::jpegai_decode::{decode, DecoderWeights, NormTables, PlaneWeights};
use wai::jpegai_icci::IcciNet;
use wai::jpegai_synthesis::{IntNet, Weights};
let parse = |b: &[u8]| -> Result<serde_json::Value, JsError> {
serde_json::from_slice(b).map_err(|e| JsError::new(&e.to_string()))
};
let w = |b: &[u8]| -> Result<Weights, JsError> { Ok(Weights::from_json(&parse(b)?)) };
let net = |b: &[u8]| -> Result<IntNet, JsError> { Ok(IntNet::from_json(&parse(b)?)) };
let weights = DecoderWeights {
luma: PlaneWeights { hyper_scale: net(hs_y)?, hyper_decoder: w(hyp_y)?, synthesis: w(synth_y)? },
chroma: PlaneWeights { hyper_scale: net(hs_uv)?, hyper_decoder: w(hyp_uv)?, synthesis: w(synth_uv)? },
mcm_y: [w(mcm0)?, w(mcm1)?, w(mcm2)?, w(mcm3)?],
icci: IcciNet::from_json(&parse(icci)?),
};
let nv = parse(norm)?;
let i64v = |k: &str| nv[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect::<Vec<i64>>();
let u8v = |k: &str| nv[k].as_array().unwrap().iter().map(|x| x.as_u64().unwrap() as u8).collect::<Vec<u8>>();
let i32v = |k: &str| nv[k].as_array().unwrap().iter().map(|x| x.as_i64().unwrap() as i32).collect::<Vec<i32>>();
let i3 = |k: &str| { let a = nv[k].as_array().unwrap(); [a[0].as_i64().unwrap(), a[1].as_i64().unwrap(), a[2].as_i64().unwrap()] };
let f3 = |k: &str| { let a = nv[k].as_array().unwrap(); [a[0].as_f64().unwrap() as f32, a[1].as_f64().unwrap() as f32, a[2].as_f64().unwrap() as f32] };
let norm_t = NormTables {
pmf: nv["pmf"].as_array().ok_or_else(|| JsError::new("norm.pmf"))?.iter()
.map(|r| r.as_array().unwrap().iter().map(|x| x.as_i64().unwrap()).collect()).collect(),
bounds: u8v("bounds"), per_channel: nv["per_channel"].as_u64().unwrap() as usize,
cdfs_y: u8v("cdfs_y"), cdfs_uv: u8v("cdfs_uv"), gain_y: i64v("gain_y"), gain_uv: i64v("gain_uv"),
lsbs_t0: i32v("lsbs_t0"), lsbs_t1: i32v("lsbs_t1"), lef_thr: i3("lef_thr0"), lef_mag: f3("lef_mag0"),
};
if let Err(why) = wai::jpegai_decode::check_scope(codestream) {
return Err(JsError::new(&format!("wai.image.jpegai: codestream refused: {why}")));
}
let img = decode(codestream, &weights, &norm_t).ok_or_else(|| {
JsError::new("wai.image.jpegai: decode refused (the weight bundle or tables do not fit the codestream)")
})?;
Ok(DecodedImage { rgb: img.data, width: img.w as u32, height: img.h as u32 })
}