feat(imaging): add pure-Rust Skia backend (#112)
Some checks failed
CI / rust-skia (Rust only) (push) Successful in 2m48s
CI / required (push) Failing after 3m25s

This commit is contained in:
2026-08-13 07:49:37 +00:00
parent e1744f44bb
commit 8c18b2ac8c
46 changed files with 4301 additions and 148 deletions

View File

@@ -16,6 +16,35 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
rust-skia:
name: rust-skia (Rust only)
runs-on: ubuntu-latest
timeout-minutes: 15
env:
CARGO_BUILD_JOBS: "1"
CARGO_INCREMENTAL: "0"
CARGO_PROFILE_DEV_DEBUG: "0"
CARGO_PROFILE_TEST_DEBUG: "0"
steps:
- uses: actions/checkout@v4
# Deliberately do not install CMake, Clang, pkg-config, Skia, WebP, or
# any other native codec prerequisite in this job.
- name: Install pinned Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.97.1
targets: wasm32-unknown-unknown
- name: Test the pure-Rust Skia-compatible extension
run: cargo test --locked -j 1 -p libremetaverse-imaging-skia --no-default-features --features rust-skia
- name: Audit the normal and build dependency graph
run: cargo tree --locked -p libremetaverse-imaging-skia --no-default-features --features rust-skia -e normal,build
- name: Compile the same Rust codec for WASM
run: cargo check --locked -j 1 -p libremetaverse-imaging-skia --no-default-features --features rust-skia --target wasm32-unknown-unknown
required: required:
name: required name: required
runs-on: ubuntu-latest runs-on: ubuntu-latest

569
Cargo.lock generated
View File

@@ -52,6 +52,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "alsa" name = "alsa"
version = "0.11.0" version = "0.11.0"
@@ -59,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [ dependencies = [
"alsa-sys", "alsa-sys",
"bitflags", "bitflags 2.13.1",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@@ -143,6 +149,15 @@ dependencies = [
"ogg_next_sys", "ogg_next_sys",
] ]
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "arrayvec" name = "arrayvec"
version = "0.7.8" version = "0.7.8"
@@ -254,7 +269,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"cexpr", "cexpr",
"clang-sys", "clang-sys",
"itertools", "itertools",
@@ -263,11 +278,26 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"regex", "regex",
"rustc-hash", "rustc-hash 2.1.3",
"shlex 1.3.0", "shlex 1.3.0",
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec 0.8.0",
]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "bit-vec" name = "bit-vec"
version = "0.9.1" version = "0.9.1"
@@ -277,6 +307,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.13.1" version = "2.13.1"
@@ -316,6 +352,38 @@ version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytemuck"
version = "1.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.3",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.12.1" version = "1.12.1"
@@ -479,6 +547,22 @@ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.5" version = "1.0.5"
@@ -529,7 +613,7 @@ version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"libc", "libc",
"objc2-audio-toolbox", "objc2-audio-toolbox",
"objc2-core-audio", "objc2-core-audio",
@@ -823,7 +907,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
] ]
@@ -844,6 +928,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "earcut"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88459a2a8e3a514b6e6de38cf3aaa9250a894cb098f74a932db77fcc8341b6d0"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "ecdsa" name = "ecdsa"
version = "0.16.9" version = "0.16.9"
@@ -907,6 +1000,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]] [[package]]
name = "fearless_simd" name = "fearless_simd"
version = "0.4.1" version = "0.4.1"
@@ -961,6 +1063,18 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float_next_after"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37007738a80ea34f969af54a3390dd72cacdef654974cfd449c9f6f72dbaac10"
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@@ -1047,6 +1161,51 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "geo"
version = "0.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30eb1fdc57c1e5cfd11826fe0caec4b9dc7901f3758263bb506228d88c8d9e9a"
dependencies = [
"earcut",
"float_next_after",
"geo-types",
"geographiclib-rs",
"i_overlay",
"log",
"num-traits",
"rand 0.10.2",
"rand_pcg",
"robust",
"rstar",
"sif-itree",
"spade",
]
[[package]]
name = "geo-types"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "777d18aa0f12f8b285331cd867133ee14422b3f023f6d388034c47d43e28786a"
dependencies = [
"approx",
"num-traits",
"rayon",
"rstar",
"serde",
"spade",
"thiserror 2.0.20",
]
[[package]]
name = "geographiclib-rs"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5a7f08910fd98737a6eda7568e7c5e645093e073328eeef49758cfe8b0489c7"
dependencies = [
"libm",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.17" version = "0.2.17"
@@ -1096,6 +1255,22 @@ dependencies = [
"polyval", "polyval",
] ]
[[package]]
name = "gif"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "glam"
version = "0.29.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.4" version = "0.3.4"
@@ -1124,18 +1299,54 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.17.1" version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32",
"stable_deref_trait",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hexf-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]] [[package]]
name = "hkdf" name = "hkdf"
version = "0.12.4" version = "0.12.4"
@@ -1266,6 +1477,52 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "i_float"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "813145bb0ad5b60f55cbbf3c74cdceda1c0a9d253b35c4cc36ae0df7887cb78f"
dependencies = [
"libm",
]
[[package]]
name = "i_key_sort"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d73d122b937fca067feb0ad74f62388920272b27c356d4df2d0cfdd59e044cf0"
dependencies = [
"rayon",
]
[[package]]
name = "i_overlay"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dd314b4668e2b3a12508f2e125558c82a6c0a8636fa5107a900f79ce414e450"
dependencies = [
"i_float",
"i_key_sort",
"i_shape",
"i_tree",
"rayon",
]
[[package]]
name = "i_shape"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa9eac533d7509a8ab87672b60ac610c17240f9ea4851d26227689fdfe349c8"
dependencies = [
"i_float",
]
[[package]]
name = "i_tree"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4804bdc1dc124eb7e1aa9e144ecc04096bcf787a10a15fa44af682b51f0f6cce"
[[package]] [[package]]
name = "icu_collections" name = "icu_collections"
version = "2.2.0" version = "2.2.0"
@@ -1369,6 +1626,16 @@ dependencies = [
"icu_properties", "icu_properties",
] ]
[[package]]
name = "image-webp"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.14.0" version = "2.14.0"
@@ -1376,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.17.1",
] ]
[[package]] [[package]]
@@ -1551,6 +1818,21 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "jpeg-decoder"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
dependencies = [
"rayon",
]
[[package]]
name = "jpeg-encoder"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b454d911ac55068f53495488d8ccd0646eaa540c033a28ee15b07838afafb01f"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.104" version = "0.3.104"
@@ -1660,8 +1942,10 @@ dependencies = [
name = "libremetaverse-imaging-skia" name = "libremetaverse-imaging-skia"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"image-webp",
"libremetaverse-imaging", "libremetaverse-imaging",
"libremetaverse-types", "libremetaverse-types",
"skia-rs-codec",
"skia-safe", "skia-safe",
] ]
@@ -1821,6 +2105,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.33" version = "0.4.33"
@@ -1954,13 +2247,34 @@ dependencies = [
"pxfm", "pxfm",
] ]
[[package]]
name = "naga"
version = "23.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.13.1",
"cfg_aliases 0.1.1",
"codespan-reporting",
"hexf-parse",
"indexmap",
"log",
"rustc-hash 1.1.0",
"spirv",
"termcolor",
"thiserror 1.0.69",
"unicode-xid",
]
[[package]] [[package]]
name = "ndk" name = "ndk"
version = "0.9.0" version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"jni-sys 0.3.1", "jni-sys 0.3.1",
"log", "log",
"ndk-sys", "ndk-sys",
@@ -1989,7 +2303,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"cfg-if", "cfg-if",
"cfg_aliases 0.1.1", "cfg_aliases 0.1.1",
"libc", "libc",
@@ -2002,7 +2316,7 @@ version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"cfg-if", "cfg-if",
"cfg_aliases 0.2.2", "cfg_aliases 0.2.2",
"libc", "libc",
@@ -2070,6 +2384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@@ -2109,7 +2424,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"libc", "libc",
"objc2", "objc2",
"objc2-core-audio", "objc2-core-audio",
@@ -2124,7 +2439,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
"objc2-foundation", "objc2-foundation",
] ]
@@ -2135,7 +2450,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
"objc2-foundation", "objc2-foundation",
] ]
@@ -2159,7 +2474,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
] ]
@@ -2179,7 +2494,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"block2", "block2",
"dispatch2", "dispatch2",
"libc", "libc",
@@ -2192,7 +2507,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"dispatch2", "dispatch2",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
@@ -2225,7 +2540,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-core-graphics", "objc2-core-graphics",
@@ -2243,7 +2558,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"block2", "block2",
"libc", "libc",
"objc2", "objc2",
@@ -2256,7 +2571,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
] ]
@@ -2267,7 +2582,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"objc2", "objc2",
"objc2-core-foundation", "objc2-core-foundation",
"objc2-foundation", "objc2-foundation",
@@ -2279,7 +2594,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"block2", "block2",
"objc2", "objc2",
"objc2-cloud-kit", "objc2-cloud-kit",
@@ -2385,6 +2700,29 @@ dependencies = [
"sha2 0.10.9", "sha2 0.10.9",
] ]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]] [[package]]
name = "pem-rfc7468" name = "pem-rfc7468"
version = "0.7.0" version = "0.7.0"
@@ -2428,6 +2766,19 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "poly1305" name = "poly1305"
version = "0.8.0" version = "0.8.0"
@@ -2518,6 +2869,12 @@ version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.11" version = "0.11.11"
@@ -2529,7 +2886,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash 2.1.3",
"rustls", "rustls",
"socket2", "socket2",
"thiserror 2.0.20", "thiserror 2.0.20",
@@ -2551,7 +2908,7 @@ dependencies = [
"rand 0.10.2", "rand 0.10.2",
"rand_pcg", "rand_pcg",
"ring", "ring",
"rustc-hash", "rustc-hash 2.1.3",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"slab", "slab",
@@ -2693,6 +3050,15 @@ dependencies = [
"yasna", "yasna",
] ]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.13.1",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.13.1" version = "1.13.1"
@@ -2784,6 +3150,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "robust"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e27ee8bb91ca0adcf0ecb116293afa12d393f9c2b9b9cd54d33e8078fe19839"
[[package]] [[package]]
name = "roxmltree" name = "roxmltree"
version = "0.21.1" version = "0.21.1"
@@ -2793,6 +3165,23 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "rstar"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
dependencies = [
"heapless",
"num-traits",
"smallvec",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.3" version = "2.1.3"
@@ -2823,7 +3212,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -2929,6 +3318,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "scroll" name = "scroll"
version = "0.13.0" version = "0.13.0"
@@ -2959,7 +3354,7 @@ dependencies = [
"crc", "crc",
"log", "log",
"rand 0.9.5", "rand 0.9.5",
"rustc-hash", "rustc-hash 2.1.3",
"slab", "slab",
"thiserror 2.0.20", "thiserror 2.0.20",
] ]
@@ -2984,7 +3379,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -3104,6 +3499,12 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "sif-itree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f45b8998ced5134fb1d75732c77842a3e888f19c1ff98481822e8fbfbf930b"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.8" version = "1.4.8"
@@ -3163,6 +3564,60 @@ dependencies = [
"toml", "toml",
] ]
[[package]]
name = "skia-rs-codec"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733be303d09c7e2bd0628dc1e592855d80e79a32c7f020f980ab0abe710d77a6"
dependencies = [
"gif",
"jpeg-decoder",
"jpeg-encoder",
"parking_lot",
"png",
"skia-rs-core",
"skia-rs-paint",
"thiserror 2.0.20",
]
[[package]]
name = "skia-rs-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88c10ce81ab452a6fa88e7228a5af713328e698b7100c2d021f58287a5370c49"
dependencies = [
"bitflags 2.13.1",
"bytemuck",
"getrandom 0.2.17",
"glam",
"smallvec",
"thiserror 2.0.20",
]
[[package]]
name = "skia-rs-paint"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4b7ea0fc7fa887d5875e49809eabc3cbd9e6a0247930a9f6456ad4c59cedfb1"
dependencies = [
"bitflags 2.13.1",
"naga",
"skia-rs-core",
"skia-rs-path",
"thiserror 2.0.20",
]
[[package]]
name = "skia-rs-path"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f928ca29bc3e26f46393048186bf64cf7eb7c39cc7176e8285a1828c601d7c0"
dependencies = [
"geo",
"skia-rs-core",
"smallvec",
]
[[package]] [[package]]
name = "skia-safe" name = "skia-safe"
version = "0.99.0" version = "0.99.0"
@@ -3170,7 +3625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc" checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags", "bitflags 2.13.1",
"percent-encoding", "percent-encoding",
"skia-bindings", "skia-bindings",
"skia-svg-macros", "skia-svg-macros",
@@ -3209,6 +3664,27 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "spade"
version = "2.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9699399fd9349b00b184f5635b074f9ec93afffef30c853f8c875b32c0f8c7fa"
dependencies = [
"hashbrown 0.16.1",
"num-traits",
"robust",
"smallvec",
]
[[package]]
name = "spirv"
version = "0.3.0+sdk-1.3.268.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844"
dependencies = [
"bitflags 2.13.1",
]
[[package]] [[package]]
name = "spki" name = "spki"
version = "0.7.3" version = "0.7.3"
@@ -3349,6 +3825,15 @@ dependencies = [
"xattr", "xattr",
] ]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "1.0.69"
@@ -3566,7 +4051,7 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.13.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@@ -3645,6 +4130,18 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
@@ -3699,9 +4196,19 @@ checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"js-sys", "js-sys",
"uuid-rng-internal",
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "uuid-rng-internal"
version = "1.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dff7387287837acf196f2596a6216f94c555d947d5331e1cc48393131020119"
dependencies = [
"getrandom 0.4.3",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@@ -3859,6 +4366,12 @@ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "1.2.1" version = "1.2.1"
@@ -4144,7 +4657,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [ dependencies = [
"bit-vec", "bit-vec 0.9.1",
"time", "time",
] ]

View File

@@ -62,7 +62,8 @@ The currently available opt-in codec backends are:
| --- | --- | --- | --- | | --- | --- | --- | --- |
| Raw J2K and JP2 decoding/encoding through OpenJPEG | `libremetaverse/jpeg2000` | `cargo build -p libremetaverse --features jpeg2000` | OpenJPEG 2.5.4 or newer | | Raw J2K and JP2 decoding/encoding through OpenJPEG | `libremetaverse/jpeg2000` | `cargo build -p libremetaverse --features jpeg2000` | OpenJPEG 2.5.4 or newer |
| Raw J2K and JP2 decoding/encoding in pure Rust | `libremetaverse/rust-j2k` | `cargo build -p libremetaverse --no-default-features --features rust-j2k` | none | | Raw J2K and JP2 decoding/encoding in pure Rust | `libremetaverse/rust-j2k` | `cargo build -p libremetaverse --no-default-features --features rust-j2k` | none |
| BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding through Skia | `libremetaverse-imaging-skia/skia` | `cargo build -p libremetaverse-imaging-skia --features skia` | A matching rust-skia binary cache, or the documented native source-build tools | | BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding in pure Rust | `libremetaverse-imaging-skia/rust-skia` | `cargo build -p libremetaverse-imaging-skia --features rust-skia` | None |
| BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP decoding through native Skia | `libremetaverse-imaging-skia/skia` | `cargo build -p libremetaverse-imaging-skia --features skia` | A matching rust-skia binary cache, or the documented native source-build tools |
| PCM to Ogg Vorbis encoding | `libremetaverse/vorbis` | `cargo build -p libremetaverse --features vorbis` | `vorbis_rs` 0.5.6 and its native encoder build prerequisites | | PCM to Ogg Vorbis encoding | `libremetaverse/vorbis` | `cargo build -p libremetaverse --features vorbis` | `vorbis_rs` 0.5.6 and its native encoder build prerequisites |
Run their focused test suites with: Run their focused test suites with:
@@ -71,6 +72,7 @@ Run their focused test suites with:
cargo test -p libremetaverse-imaging --features jpeg2000 cargo test -p libremetaverse-imaging --features jpeg2000
cargo test -p libremetaverse-imaging --no-default-features --features rust-j2k cargo test -p libremetaverse-imaging --no-default-features --features rust-j2k
cargo test -p libremetaverse-imaging-skia --features skia cargo test -p libremetaverse-imaging-skia --features skia
cargo test -p libremetaverse-imaging-skia --features rust-skia
cargo test -p libremetaverse --no-default-features --features vorbis cargo test -p libremetaverse --no-default-features --features vorbis
``` ```

View File

@@ -404,7 +404,7 @@ MSRV, features, and licenses at adoption time.
| `System.Net.Http` / WinHTTP | [`reqwest` 0.13.4](https://crates.io/crates/reqwest/0.13.4) | MSRV 1.85. Use rustls/default TLS deliberately, connection pooling, streaming bodies, and injected client/timeouts. | | `System.Net.Http` / WinHTTP | [`reqwest` 0.13.4](https://crates.io/crates/reqwest/0.13.4) | MSRV 1.85. Use rustls/default TLS deliberately, connection pooling, streaming bodies, and injected client/timeouts. |
| JSON and general data mapping | [`serde` 1.0.229](https://crates.io/crates/serde/1.0.229), [`serde_json` 1.0.151](https://crates.io/crates/serde_json/1.0.151), [`serde_bytes` 0.11.19](https://crates.io/crates/serde_bytes/0.11.19) | Use only where the C# implementation is schema-driven JSON. Hand-write LLSD encodings. | | JSON and general data mapping | [`serde` 1.0.229](https://crates.io/crates/serde/1.0.229), [`serde_json` 1.0.151](https://crates.io/crates/serde_json/1.0.151), [`serde_bytes` 0.11.19](https://crates.io/crates/serde_bytes/0.11.19) | Use only where the C# implementation is schema-driven JSON. Hand-write LLSD encodings. |
| MessagePack 3.1.8 | [`rmp-serde` 1.3.1](https://crates.io/crates/rmp-serde/1.3.1), [`rmpv` 1.3.1](https://crates.io/crates/rmpv/1.3.1) | Golden byte vectors must prove integer widths, maps, extensions, and field ordering. | | MessagePack 3.1.8 | [`rmp-serde` 1.3.1](https://crates.io/crates/rmp-serde/1.3.1), [`rmpv` 1.3.1](https://crates.io/crates/rmpv/1.3.1) | Golden byte vectors must prove integer widths, maps, extensions, and field ordering. |
| UUID | [`uuid` 1.24.0](https://crates.io/crates/uuid/1.24.0) | Wrap it; do not expose dependency-specific endian assumptions as protocol behavior. | | UUID | [`uuid` 1.24.0](https://crates.io/crates/uuid/1.24.0) | Wrap it; do not expose dependency-specific endian assumptions as protocol behavior. WASM selects UUID's `rng-getrandom` adapter and `getrandom`'s Web Crypto-backed `wasm_js` feature while desktop targets retain operating-system entropy. |
| byte buffers | [`bytes` 1.12.1](https://crates.io/crates/bytes/1.12.1), [`bytemuck` 1.25.2](https://crates.io/crates/bytemuck/1.25.2) | Prefer explicit endian reads. `bytemuck` is only for proven POD layouts, never untrusted variable packets. | | byte buffers | [`bytes` 1.12.1](https://crates.io/crates/bytes/1.12.1), [`bytemuck` 1.25.2](https://crates.io/crates/bytemuck/1.25.2) | Prefer explicit endian reads. `bytemuck` is only for proven POD layouts, never untrusted variable packets. |
| vector/matrix math | [`glam` 0.33.3](https://crates.io/crates/glam/0.33.3) | Keep compatibility newtypes to control component order, precision, equality, and serialization. | | vector/matrix math | [`glam` 0.33.3](https://crates.io/crates/glam/0.33.3) | Keep compatibility newtypes to control component order, precision, equality, and serialization. |
| flags | [`bitflags` 2.13.1](https://crates.io/crates/bitflags/2.13.1) | Retain unknown bits from newer grids. | | flags | [`bitflags` 2.13.1](https://crates.io/crates/bitflags/2.13.1) | Retain unknown bits from newer grids. |
@@ -417,6 +417,7 @@ MSRV, features, and licenses at adoption time.
| CoreJ2K 2.3.3.91 | system [OpenJPEG](https://www.openjpeg.org/) 2.5.4+ through the private `libremetaverse-openjpeg` adapter | Opt-in `jpeg2000` feature; checked-in minimal bindings avoid a libclang build dependency. Bounded memory streams and header validation isolate native code. Golden tests cover channel order, alpha, 16-bit precision, dimensions, discard levels, lossless/lossy modes, and malformed input. | | CoreJ2K 2.3.3.91 | system [OpenJPEG](https://www.openjpeg.org/) 2.5.4+ through the private `libremetaverse-openjpeg` adapter | Opt-in `jpeg2000` feature; checked-in minimal bindings avoid a libclang build dependency. Bounded memory streams and header validation isolate native code. Golden tests cover channel order, alpha, 16-bit precision, dimensions, discard levels, lossless/lossy modes, and malformed input. |
| CoreJ2K pure-Rust extension | [`j2k`](https://crates.io/crates/j2k) 0.8.1 | Independent opt-in `rust-j2k` feature with no native library. The safe adapter provides J2K/JP2 encode/decode, native component preservation, bounded streams and allocations, reduced resolution, progressive quality layers, strict/permissive truncation, and OpenJPEG interoperability. | | CoreJ2K pure-Rust extension | [`j2k`](https://crates.io/crates/j2k) 0.8.1 | Independent opt-in `rust-j2k` feature with no native library. The safe adapter provides J2K/JP2 encode/decode, native component preservation, bounded streams and allocations, reduced resolution, progressive quality layers, strict/permissive truncation, and OpenJPEG interoperability. |
| SkiaSharp 4.150.1 | [`skia-safe` 0.99.0](https://crates.io/crates/skia-safe/0.99.0) | Adopted behind the opt-in `skia` feature. Target-specific official binary-cache feature sets provide BMP/GIF/ICO/JPEG/PNG/WBMP/WebP decoding on macOS, Linux, and Windows; checked owned buffers keep Skia types out of the core image API. | | SkiaSharp 4.150.1 | [`skia-safe` 0.99.0](https://crates.io/crates/skia-safe/0.99.0) | Adopted behind the opt-in `skia` feature. Target-specific official binary-cache feature sets provide BMP/GIF/ICO/JPEG/PNG/WBMP/WebP decoding on macOS, Linux, and Windows; checked owned buffers keep Skia types out of the core image API. |
| SkiaSharp pure-Rust extension | [`skia-rs-codec` 0.3.0](https://crates.io/crates/skia-rs-codec/0.3.0) and [`image-webp` 0.2.4](https://crates.io/crates/image-webp/0.2.4) | Independent opt-in `rust-skia` feature with no native codec. Default features stay disabled: pure-Rust PNG/JPEG/GIF plus built-in BMP/ICO/WBMP come from `skia-rs-codec`, and pure-Rust WebP comes from `image-webp` instead of the native `libwebp-sys` path. |
| Pfim 0.11.4 | [`image` 0.25.10](https://crates.io/crates/image/0.25.10), [`ddsfile` 0.6.0](https://crates.io/crates/ddsfile/0.6.0) | `image` covers TGA and common DDS decoding; `ddsfile` exposes DDS container details. Golden files decide whether both are needed. | | Pfim 0.11.4 | [`image` 0.25.10](https://crates.io/crates/image/0.25.10), [`ddsfile` 0.6.0](https://crates.io/crates/ddsfile/0.6.0) | `image` covers TGA and common DDS decoding; `ddsfile` exposes DDS container details. Golden files decide whether both are needed. |
| OggVorbisEncoder 1.2.2 | [`vorbis_rs` 0.5.6](https://crates.io/crates/vorbis_rs/0.5.6) | BSD-3-Clause, MSRV 1.82, backed by C libraries. Feature-gate native audio encoding. | | OggVorbisEncoder 1.2.2 | [`vorbis_rs` 0.5.6](https://crates.io/crates/vorbis_rs/0.5.6) | BSD-3-Clause, MSRV 1.82, backed by C libraries. Feature-gate native audio encoding. |
| SIPSorcery 8.0.23 | [`str0m` 0.22.0](https://crates.io/crates/str0m/0.22.0), [`cpal` 0.18.1](https://crates.io/crates/cpal/0.18.1), system libopus 1.3+ through `libremetaverse-opus` | The private safe adapter avoids the unmaintained `audiopus_sys` binding. Validate SDP, ICE, data-channel framing, audio formats, device hotplug, and native libopus deployment separately. Do not claim parity from successful compilation. | | SIPSorcery 8.0.23 | [`str0m` 0.22.0](https://crates.io/crates/str0m/0.22.0), [`cpal` 0.18.1](https://crates.io/crates/cpal/0.18.1), system libopus 1.3+ through `libremetaverse-opus` | The private safe adapter avoids the unmaintained `audiopus_sys` binding. Validate SDP, ICE, data-channel framing, audio formats, device hotplug, and native libopus deployment separately. Do not claim parity from successful compilation. |
@@ -444,7 +445,7 @@ The current adopted set is:
| Async and HTTP | `tokio`, `futures-channel`, `futures-util`, `reqwest` | | Async and HTTP | `tokio`, `futures-channel`, `futures-util`, `reqwest` |
| Wire data and parsing | `base64`, `serde`, `serde_json`, `roxmltree`, `regex`, `unicode-general-category` | | Wire data and parsing | `base64`, `serde`, `serde_json`, `roxmltree`, `regex`, `unicode-general-category` |
| Identity, hashing, and platform facts | `uuid`, `getrandom`, `md-5`, `sha1`, `sha2`, `mac_address2`, `os_info` | | Identity, hashing, and platform facts | `uuid`, `getrandom`, `md-5`, `sha1`, `sha2`, `mac_address2`, `os_info` |
| Archives, images, and audio | `flate2`, `tar`, `bcdec_rs`, `skia-safe`, `hound`, `vorbis_rs`, `str0m`, `cpal` | | Archives, images, and audio | `flate2`, `tar`, `bcdec_rs`, `skia-safe`, `skia-rs-codec`, `image-webp`, `hound`, `vorbis_rs`, `str0m`, `cpal` |
| Code generation, program CLI, and artifact inspection | `syn`, `prettyplease`, `clap`, `goblin` | | Code generation, program CLI, and artifact inspection | `syn`, `prettyplease`, `clap`, `goblin` |
| Build and compatibility-test support | `pkg-config`, `vcpkg`, `stats_alloc` | | Build and compatibility-test support | `pkg-config`, `vcpkg`, `stats_alloc` |
@@ -518,10 +519,12 @@ and invalid-dimension tests match the original.
### 5.4 `libremetaverse-imaging-skia` ### 5.4 `libremetaverse-imaging-skia`
Implement `SkiaTextureCodec` as an optional adapter with `skia-safe`. Convert at Implement `SkiaTextureCodec` as an optional adapter with `skia-safe`, plus an
the boundary to/from `ManagedImage`; never expose `skia_safe` types in the core independently selectable `RustSkiaTextureCodec` using `skia-rs-codec` and
trait. CI must cover macOS, Linux, and Windows or explicitly document unsupported `image-webp`. Convert at the boundary to/from `ManagedImage`; never expose
targets. Validate native binary provenance and packaging. backend-specific types in the core trait. CI must cover macOS, Linux, Windows,
and the Rust-only WASM compile boundary. Validate native binary provenance and
the locked Rust dependency/license inventory.
### 5.5 `libremetaverse-prim-mesher` ### 5.5 `libremetaverse-prim-mesher`

View File

@@ -24,7 +24,7 @@ The compatibility source is pinned at [`2aa70bb68513b39795da5d13c88f31b86e85a3ba
| `libremetaverse-types` | 45 | 942 | 887 | none | `532cdfc175a60cb6ab109b2c43cfdda1d89cf0aadffccbcd7c9b18e1a3215bee` | | `libremetaverse-types` | 45 | 942 | 887 | none | `532cdfc175a60cb6ab109b2c43cfdda1d89cf0aadffccbcd7c9b18e1a3215bee` |
| `libremetaverse-structured-data` | 16 | 295 | 304 | none | `b1c2ca9a683efe92ea22293384189f9e7c9a1926860893ab78c41829e641a8c3` | | `libremetaverse-structured-data` | 16 | 295 | 304 | none | `b1c2ca9a683efe92ea22293384189f9e7c9a1926860893ab78c41829e641a8c3` |
| `libremetaverse-imaging` | 3 | 20 | 70 | default, jpeg2000, rust-j2k | `19423a5aec60e38d547b809ab947e7aff6366318f97f69cde824f57ae6194c0e` | | `libremetaverse-imaging` | 3 | 20 | 70 | default, jpeg2000, rust-j2k | `19423a5aec60e38d547b809ab947e7aff6366318f97f69cde824f57ae6194c0e` |
| `libremetaverse-imaging-skia` | 1 | 3 | 13 | default, skia | `a6a1445725410a036204496ad4953c84dcfea1e6a00390ce4e10d12d36f2876b` | | `libremetaverse-imaging-skia` | 1 | 3 | 17 | default, rust-skia, skia | `855a181b77413a1e5f0094b1581971fd5e0a4d93fadb5f83d54f82a7ba8ee71f` |
| `libremetaverse-prim-mesher` | 17 | 207 | 117 | none | `dde798e86c485948f1d67e093f8194a76082101416516df3f8dca4de04c2d415` | | `libremetaverse-prim-mesher` | 17 | 207 | 117 | none | `dde798e86c485948f1d67e093f8194a76082101416516df3f8dca4de04c2d415` |
| `libremetaverse-rendering-simple` | 1 | 6 | 12 | none | `4dc284ea0abb16de63494435758c8b7c5eeb57a80d2e32a141e488e5861f2fad` | | `libremetaverse-rendering-simple` | 1 | 6 | 12 | none | `4dc284ea0abb16de63494435758c8b7c5eeb57a80d2e32a141e488e5861f2fad` |
| `libremetaverse-rendering-mesh-foundry` | 1 | 14 | 25 | none | `3689f6b454061fdae699d5fb66a11482909fa25bda398e26c38327995a9e2896` | | `libremetaverse-rendering-mesh-foundry` | 1 | 14 | 25 | none | `3689f6b454061fdae699d5fb66a11482909fa25bda398e26c38327995a9e2896` |
@@ -46,7 +46,7 @@ The exact mapping, exported-marker, feature, and normalized Rust declaration has
- .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks. - .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.
- `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`. - `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.
- Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`. - Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.
- Optional native backends remain feature-gated (`jpeg2000`, `skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline. - Optional native and pure-Rust backends remain feature-gated (`jpeg2000`, `rust-j2k`, `skia`, `rust-skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline.
- Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface. - Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.
- External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations. - External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.

View File

@@ -20,7 +20,7 @@
"type_mapping_sha256": "21025889c62b0f8666a2dda46a03ff4f28931f51156927822a1c04e97b49f221", "type_mapping_sha256": "21025889c62b0f8666a2dda46a03ff4f28931f51156927822a1c04e97b49f221",
"member_mapping_sha256": "cd99420643c665ea393dd59418c33fb00f97f7030e71540c00b2b27470abb1d4", "member_mapping_sha256": "cd99420643c665ea393dd59418c33fb00f97f7030e71540c00b2b27470abb1d4",
"exported_mapping_sha256": "5b8a502ce229d26732744f2b44c029fc4a86b403966dc2f9b4a1c913a7b242b9", "exported_mapping_sha256": "5b8a502ce229d26732744f2b44c029fc4a86b403966dc2f9b4a1c913a7b242b9",
"rust_declaration_sha256": "687d3d29bb0e79651881ffdda962be21df8716a4d96417175611541d0fe27a8e", "rust_declaration_sha256": "b2ba1ccf9293ac94164e593546891a2a122b1e5997ee4ecc2de86ef5eb0d866e",
"crates": [ "crates": [
{ {
"name": "libremetaverse", "name": "libremetaverse",
@@ -87,14 +87,21 @@
"version": "0.0.1", "version": "0.0.1",
"features": { "features": {
"default": [], "default": [],
"rust-skia": [
"dep:image-webp",
"dep:skia-rs-codec",
"skia-rs-codec/gif",
"skia-rs-codec/jpeg",
"skia-rs-codec/png"
],
"skia": [ "skia": [
"dep:skia-safe" "dep:skia-safe"
] ]
}, },
"mapped_types": 1, "mapped_types": 1,
"mapped_members": 3, "mapped_members": 3,
"public_declarations": 13, "public_declarations": 17,
"declaration_sha256": "a6a1445725410a036204496ad4953c84dcfea1e6a00390ce4e10d12d36f2876b" "declaration_sha256": "855a181b77413a1e5f0094b1581971fd5e0a4d93fadb5f83d54f82a7ba8ee71f"
}, },
{ {
"name": "libremetaverse-prim-mesher", "name": "libremetaverse-prim-mesher",

View File

@@ -13,6 +13,7 @@
"minimal-features", "minimal-features",
"jpeg2000", "jpeg2000",
"rust-j2k", "rust-j2k",
"rust-skia",
"skia-binary", "skia-binary",
"program-offline-smoke", "program-offline-smoke",
"clippy", "clippy",
@@ -104,8 +105,8 @@
"workflow": "skia.yml", "workflow": "skia.yml",
"responsibility": "Default and Skia feature tests plus package listing", "responsibility": "Default and Skia feature tests plus package listing",
"destination": "required", "destination": "required",
"checks": ["skia-binary", "workspace-tests"], "checks": ["rust-skia", "skia-binary", "workspace-tests"],
"rationale": "The required gate proves the exact ARM64 GNU archive is installed and forbids source compilation." "rationale": "A dedicated dependency-free job tests the Rust-only codec graph, while the required gate proves the exact ARM64 GNU archive is installed and forbids source compilation."
}, },
{ {
"workflow": "supply-chain.yml", "workflow": "supply-chain.yml",

View File

@@ -2,7 +2,7 @@
"schema": 1, "schema": 1,
"msrv": "1.96.0", "msrv": "1.96.0",
"current": "stable", "current": "stable",
"reviewed_on": "2026-08-12", "reviewed_on": "2026-08-13",
"review_by": "2026-11-11", "review_by": "2026-11-11",
"direct": [ "direct": [
{ "name": "base64", "versions": ["0.22.1"], "purpose": "LLSD, login, asset, and protocol base64 encoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`base64`" }, { "name": "base64", "versions": ["0.22.1"], "purpose": "LLSD, login, asset, and protocol base64 encoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`base64`" },
@@ -15,6 +15,7 @@
{ "name": "getrandom", "versions": ["0.4.3"], "purpose": "Operating-system entropy for protocol identifiers", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`getrandom`" }, { "name": "getrandom", "versions": ["0.4.3"], "purpose": "Operating-system entropy for protocol identifiers", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`getrandom`" },
{ "name": "goblin", "versions": ["0.10.5"], "purpose": "Cross-platform ELF, PE, and Mach-O release artifact inspection", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`goblin`" }, { "name": "goblin", "versions": ["0.10.5"], "purpose": "Cross-platform ELF, PE, and Mach-O release artifact inspection", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`goblin`" },
{ "name": "hound", "versions": ["3.5.1"], "purpose": "Bounded PCM WAV parsing for WebRTC playback", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`hound`" }, { "name": "hound", "versions": ["3.5.1"], "purpose": "Bounded PCM WAV parsing for WebRTC playback", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`hound`" },
{ "name": "image-webp", "versions": ["0.2.4"], "purpose": "Opt-in pure-Rust bounded WebP image decoding", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`image-webp`" },
{ "name": "j2k", "versions": ["0.8.1"], "purpose": "Opt-in pure-Rust bounded JPEG 2000 and JP2 encoding and decoding", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`j2k`" }, { "name": "j2k", "versions": ["0.8.1"], "purpose": "Opt-in pure-Rust bounded JPEG 2000 and JP2 encoding and decoding", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`j2k`" },
{ "name": "mac_address2", "versions": ["2.0.2"], "purpose": "Cross-platform machine identity compatibility input", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`mac_address2`" }, { "name": "mac_address2", "versions": ["2.0.2"], "purpose": "Cross-platform machine identity compatibility input", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`mac_address2`" },
{ "name": "md-5", "versions": ["0.10.6"], "purpose": "Legacy protocol checksum compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`md-5`" }, { "name": "md-5", "versions": ["0.10.6"], "purpose": "Legacy protocol checksum compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`md-5`" },
@@ -29,6 +30,7 @@
{ "name": "sha1", "versions": ["0.10.7"], "purpose": "Legacy protocol hash compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha1`" }, { "name": "sha1", "versions": ["0.10.7"], "purpose": "Legacy protocol hash compatibility", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha1`" },
{ "name": "sha2", "versions": ["0.11.0"], "purpose": "Manifest integrity and protocol hashing", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha2`" }, { "name": "sha2", "versions": ["0.11.0"], "purpose": "Manifest integrity and protocol hashing", "maintenance": "active", "transitive_cost": "low", "native": false, "rewrite_anchor": "`sha2`" },
{ "name": "skia-safe", "versions": ["0.99.0"], "purpose": "Opt-in cross-platform Skia image decoding", "maintenance": "monitored-native", "transitive_cost": "high", "native": true, "rewrite_anchor": "`skia-safe`" }, { "name": "skia-safe", "versions": ["0.99.0"], "purpose": "Opt-in cross-platform Skia image decoding", "maintenance": "monitored-native", "transitive_cost": "high", "native": true, "rewrite_anchor": "`skia-safe`" },
{ "name": "skia-rs-codec", "versions": ["0.3.0"], "purpose": "Opt-in pure-Rust BMP, GIF, ICO, JPEG, PNG, and WBMP image decoding", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`skia-rs-codec`" },
{ "name": "stats_alloc", "versions": ["0.1.10"], "purpose": "Allocation-budget compatibility tests, concurrency leak audits, and reproducible performance evidence", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`stats_alloc`" }, { "name": "stats_alloc", "versions": ["0.1.10"], "purpose": "Allocation-budget compatibility tests, concurrency leak audits, and reproducible performance evidence", "maintenance": "stable", "transitive_cost": "low", "native": false, "rewrite_anchor": "`stats_alloc`" },
{ "name": "str0m", "versions": ["0.22.0"], "purpose": "Native Rust ICE, DTLS, SRTP, RTP, and SCTP WebRTC transport", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`str0m`" }, { "name": "str0m", "versions": ["0.22.0"], "purpose": "Native Rust ICE, DTLS, SRTP, RTP, and SCTP WebRTC transport", "maintenance": "active", "transitive_cost": "high", "native": false, "rewrite_anchor": "`str0m`" },
{ "name": "syn", "versions": ["2.0.119"], "purpose": "Syntax validation for generated Rust sources", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`syn`" }, { "name": "syn", "versions": ["2.0.119"], "purpose": "Syntax validation for generated Rust sources", "maintenance": "active", "transitive_cost": "medium", "native": false, "rewrite_anchor": "`syn`" },
@@ -40,6 +42,8 @@
{ "name": "vorbis_rs", "versions": ["0.5.6"], "purpose": "Opt-in Ogg Vorbis asset encoding", "maintenance": "monitored-native", "transitive_cost": "medium", "native": true, "rewrite_anchor": "`vorbis_rs`" } { "name": "vorbis_rs", "versions": ["0.5.6"], "purpose": "Opt-in Ogg Vorbis asset encoding", "maintenance": "monitored-native", "transitive_cost": "medium", "native": true, "rewrite_anchor": "`vorbis_rs`" }
], ],
"approved_duplicates": [ "approved_duplicates": [
{ "name": "bit-vec", "versions": ["0.8.0", "0.9.1"], "reason": "The pinned skia-rs shader parser and current WebRTC certificate stack require adjacent bit-vector API lines." },
{ "name": "bitflags", "versions": ["1.3.2", "2.13.1"], "reason": "The pinned pure-Rust PNG codec remains on bitflags 1 while the rest of the workspace uses bitflags 2." },
{ "name": "block-buffer", "versions": ["0.10.4", "0.12.1"], "reason": "RustCrypto digest 0.10 and 0.11 consumers have not unified their block-buffer major version." }, { "name": "block-buffer", "versions": ["0.10.4", "0.12.1"], "reason": "RustCrypto digest 0.10 and 0.11 consumers have not unified their block-buffer major version." },
{ "name": "cfg_aliases", "versions": ["0.1.1", "0.2.2"], "reason": "The target-specific native stacks use different nix-generation build helpers." }, { "name": "cfg_aliases", "versions": ["0.1.1", "0.2.2"], "reason": "The target-specific native stacks use different nix-generation build helpers." },
{ "name": "chacha20", "versions": ["0.9.1", "0.10.1"], "reason": "The WebRTC crypto and current random-number stacks require different RustCrypto generations." }, { "name": "chacha20", "versions": ["0.9.1", "0.10.1"], "reason": "The WebRTC crypto and current random-number stacks require different RustCrypto generations." },
@@ -48,12 +52,14 @@
{ "name": "crypto-common", "versions": ["0.1.7", "0.2.2"], "reason": "Legacy protocol hashes and current SHA-2 intentionally span RustCrypto digest generations." }, { "name": "crypto-common", "versions": ["0.1.7", "0.2.2"], "reason": "Legacy protocol hashes and current SHA-2 intentionally span RustCrypto digest generations." },
{ "name": "digest", "versions": ["0.10.7", "0.11.3"], "reason": "MD5, SHA-1, and WebRTC use digest 0.10 while manifest SHA-2 uses the audited 0.11 API." }, { "name": "digest", "versions": ["0.10.7", "0.11.3"], "reason": "MD5, SHA-1, and WebRTC use digest 0.10 while manifest SHA-2 uses the audited 0.11 API." },
{ "name": "getrandom", "versions": ["0.2.17", "0.3.4", "0.4.3"], "reason": "WebRTC crypto, transitive rand, and direct UUID entropy consumers span three compatible APIs." }, { "name": "getrandom", "versions": ["0.2.17", "0.3.4", "0.4.3"], "reason": "WebRTC crypto, transitive rand, and direct UUID entropy consumers span three compatible APIs." },
{ "name": "hashbrown", "versions": ["0.16.1", "0.17.1"], "reason": "The pinned skia-rs geometry and shader dependencies have not unified their hash table minor line." },
{ "name": "jni-sys", "versions": ["0.3.1", "0.4.1"], "reason": "CPAL Android NDK support and rustls Android certificate verification require different JNI bindings." }, { "name": "jni-sys", "versions": ["0.3.1", "0.4.1"], "reason": "CPAL Android NDK support and rustls Android certificate verification require different JNI bindings." },
{ "name": "nix", "versions": ["0.28.0", "0.31.3"], "reason": "Machine-address and operating-system reporting crates have not unified their Unix API generation." }, { "name": "nix", "versions": ["0.28.0", "0.31.3"], "reason": "Machine-address and operating-system reporting crates have not unified their Unix API generation." },
{ "name": "nom", "versions": ["7.1.3", "8.0.0"], "reason": "Skia bindgen tooling remains on nom 7 while the native WebRTC stack uses nom 8." }, { "name": "nom", "versions": ["7.1.3", "8.0.0"], "reason": "Skia bindgen tooling remains on nom 7 while the native WebRTC stack uses nom 8." },
{ "name": "r-efi", "versions": ["5.3.0", "6.0.0"], "reason": "The three resolved getrandom generations require two WASI EFI interface generations." }, { "name": "r-efi", "versions": ["5.3.0", "6.0.0"], "reason": "The three resolved getrandom generations require two WASI EFI interface generations." },
{ "name": "rand", "versions": ["0.9.5", "0.10.2"], "reason": "WebRTC protocol crates use rand 0.9 while target-specific dependencies have adopted rand 0.10." }, { "name": "rand", "versions": ["0.9.5", "0.10.2"], "reason": "WebRTC protocol crates use rand 0.9 while target-specific dependencies have adopted rand 0.10." },
{ "name": "rand_core", "versions": ["0.6.4", "0.9.5", "0.10.1"], "reason": "DTLS elliptic curves and the two resolved rand generations require distinct rand_core APIs." }, { "name": "rand_core", "versions": ["0.6.4", "0.9.5", "0.10.1"], "reason": "DTLS elliptic curves and the two resolved rand generations require distinct rand_core APIs." },
{ "name": "rustc-hash", "versions": ["1.1.0", "2.1.3"], "reason": "The pinned skia-rs shader parser remains on rustc-hash 1 while existing workspace parsers use version 2." },
{ "name": "sha2", "versions": ["0.10.9", "0.11.0"], "reason": "WebRTC crypto still requires SHA-2 0.10 while MetaCrate integrity code directly uses 0.11." }, { "name": "sha2", "versions": ["0.10.9", "0.11.0"], "reason": "WebRTC crypto still requires SHA-2 0.10 while MetaCrate integrity code directly uses 0.11." },
{ "name": "shlex", "versions": ["1.3.0", "2.0.1"], "reason": "Skia bindgen and current native C compilation helpers use different shell-token parsers." }, { "name": "shlex", "versions": ["1.3.0", "2.0.1"], "reason": "Skia bindgen and current native C compilation helpers use different shell-token parsers." },
{ "name": "syn", "versions": ["2.0.119", "3.0.3"], "reason": "MetaCrate code generation uses syn 2 while current derive macro dependencies use syn 3." }, { "name": "syn", "versions": ["2.0.119", "3.0.3"], "reason": "MetaCrate code generation uses syn 2 while current derive macro dependencies use syn 3." },

View File

@@ -42,12 +42,12 @@
"execution": "native", "execution": "native",
"toolchain": "1.96.0", "toolchain": "1.96.0",
"target": "x86_64-unknown-linux-gnu", "target": "x86_64-unknown-linux-gnu",
"feature_sets": ["no-default-features", "rust-j2k"], "feature_sets": ["no-default-features", "rust-j2k", "rust-skia"],
"prerequisites": ["libopus-1.3"], "prerequisites": ["libopus-1.3"],
"commands": [ "commands": [
{ {
"label": "Compile the portable public crates on the declared MSRV", "label": "Compile the portable public crates on the declared MSRV",
"args": ["check", "--locked", "-j", "1", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"] "args": ["check", "--locked", "-j", "1", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k,libremetaverse-imaging-skia/rust-skia", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"]
}, },
{ {
"label": "Compile the safe native Opus boundary and WebRTC voice on the declared MSRV", "label": "Compile the safe native Opus boundary and WebRTC voice on the declared MSRV",
@@ -106,7 +106,7 @@
"execution": "native", "execution": "native",
"toolchain": "stable", "toolchain": "stable",
"target": "x86_64-unknown-linux-gnu", "target": "x86_64-unknown-linux-gnu",
"feature_sets": ["all-features", "dds-bc67", "jpeg2000", "rust-j2k", "skia", "vorbis", "real-audio", "tests"], "feature_sets": ["all-features", "dds-bc67", "jpeg2000", "rust-j2k", "rust-skia", "skia", "vorbis", "real-audio", "tests"],
"prerequisites": ["openjpeg-2.5.4", "skia-safe-0.99.0", "libopus-1.3", "alsa-1.2", "vorbis-rs-0.5.6"], "prerequisites": ["openjpeg-2.5.4", "skia-safe-0.99.0", "libopus-1.3", "alsa-1.2", "vorbis-rs-0.5.6"],
"commands": [ "commands": [
{ {
@@ -125,10 +125,18 @@
"label": "Test OpenJPEG and pure-Rust JPEG 2000 interoperability", "label": "Test OpenJPEG and pure-Rust JPEG 2000 interoperability",
"args": ["test", "-p", "libremetaverse-imaging", "--no-default-features", "--features", "jpeg2000,rust-j2k", "--locked", "-j", "1"] "args": ["test", "-p", "libremetaverse-imaging", "--no-default-features", "--features", "jpeg2000,rust-j2k", "--locked", "-j", "1"]
}, },
{
"label": "Test the pure-Rust Skia-compatible feature independently",
"args": ["test", "-p", "libremetaverse-imaging-skia", "--no-default-features", "--features", "rust-skia", "--locked", "-j", "1"]
},
{ {
"label": "Test the Skia feature independently", "label": "Test the Skia feature independently",
"args": ["test", "-p", "libremetaverse-imaging-skia", "--no-default-features", "--features", "skia", "--locked", "-j", "1"] "args": ["test", "-p", "libremetaverse-imaging-skia", "--no-default-features", "--features", "skia", "--locked", "-j", "1"]
}, },
{
"label": "Test native Skia and pure-Rust codec interoperability",
"args": ["test", "-p", "libremetaverse-imaging-skia", "--no-default-features", "--features", "skia,rust-skia", "--locked", "-j", "1"]
},
{ {
"label": "Test the Vorbis feature independently", "label": "Test the Vorbis feature independently",
"args": ["test", "-p", "libremetaverse", "--no-default-features", "--features", "vorbis", "--lib", "--locked", "-j", "1", "--", "asset_pipeline_semantics"] "args": ["test", "-p", "libremetaverse", "--no-default-features", "--features", "vorbis", "--lib", "--locked", "-j", "1", "--", "asset_pipeline_semantics"]
@@ -170,12 +178,12 @@
"execution": "cross", "execution": "cross",
"toolchain": "stable", "toolchain": "stable",
"target": "x86_64-pc-windows-gnu", "target": "x86_64-pc-windows-gnu",
"feature_sets": ["no-default-features", "dds-bc67", "rust-j2k"], "feature_sets": ["no-default-features", "dds-bc67", "rust-j2k", "rust-skia"],
"prerequisites": [], "prerequisites": [],
"commands": [ "commands": [
{ {
"label": "Cross-check portable crates for Windows GNU", "label": "Cross-check portable crates for Windows GNU",
"args": ["check", "--locked", "-j", "1", "--target", "x86_64-pc-windows-gnu", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"] "args": ["check", "--locked", "-j", "1", "--target", "x86_64-pc-windows-gnu", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k,libremetaverse-imaging-skia/rust-skia", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"]
} }
] ]
}, },
@@ -186,12 +194,12 @@
"execution": "cross", "execution": "cross",
"toolchain": "stable", "toolchain": "stable",
"target": "x86_64-apple-darwin", "target": "x86_64-apple-darwin",
"feature_sets": ["no-default-features", "dds-bc67", "rust-j2k"], "feature_sets": ["no-default-features", "dds-bc67", "rust-j2k", "rust-skia"],
"prerequisites": [], "prerequisites": [],
"commands": [ "commands": [
{ {
"label": "Cross-check portable crates for macOS", "label": "Cross-check portable crates for macOS",
"args": ["check", "--locked", "-j", "1", "--target", "x86_64-apple-darwin", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"] "args": ["check", "--locked", "-j", "1", "--target", "x86_64-apple-darwin", "--all-targets", "--no-default-features", "--features", "libremetaverse-imaging/rust-j2k,libremetaverse-imaging-skia/rust-skia", "-p", "libremetaverse-types", "-p", "libremetaverse-structured-data", "-p", "libremetaverse-imaging", "-p", "libremetaverse-imaging-skia", "-p", "libremetaverse-prim-mesher", "-p", "libremetaverse-lsl-tools", "-p", "libremetaverse-rlv", "-p", "metacrate-ci-matrix"]
} }
] ]
} }

View File

@@ -10,10 +10,13 @@ description = "Optional Skia image decoder for the MetaCrate LibreMetaverse rewr
[features] [features]
default = [] default = []
skia = ["dep:skia-safe"] skia = ["dep:skia-safe"]
rust-skia = ["dep:image-webp", "dep:skia-rs-codec", "skia-rs-codec/gif", "skia-rs-codec/jpeg", "skia-rs-codec/png"]
[dependencies] [dependencies]
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" } libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" }
libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" } libremetaverse-types = { version = "0.0.1", path = "../libremetaverse-types" }
image-webp = { version = "=0.2.4", optional = true }
skia-rs-codec = { version = "=0.3.0", default-features = false, optional = true }
# rust-skia publishes WebP-capable binary caches with Vulkan on Linux/Windows # rust-skia publishes WebP-capable binary caches with Vulkan on Linux/Windows
# and without Vulkan on macOS. Matching those sets avoids an hours-long Skia # and without Vulkan on macOS. Matching those sets avoids an hours-long Skia

View File

@@ -6,10 +6,43 @@ exposes a `skia-safe` type and never discovers or links Skia.
## Features and formats ## Features and formats
The default feature set has no Skia dependency. `SkiaTextureCodec::decode` The default feature set has no Skia dependency. `SkiaTextureCodec::decode` and
returns a typed `InvalidOperation` error in that configuration, while conversion `RustSkiaTextureCodec::decode` return a typed `InvalidOperation` error when
from the checked project-owned `backend::SKBitmap` remains available for tests their respective feature is disabled, while conversion from the checked
and callers that already own decoded pixels. project-owned `backend::SKBitmap` remains available for tests and callers that
already own decoded pixels.
For a fully Rust implementation, enable:
```sh
cargo test -p libremetaverse-imaging-skia --no-default-features --features rust-skia
```
`rust-skia` pins [`skia-rs-codec` 0.3.0](https://github.com/rust-skia/skia-rs)
with default features disabled and only
its pure-Rust PNG, JPEG, and GIF codecs selected. Its `webp` feature is
deliberately disabled because that feature selects `libwebp-sys`; WebP is
instead decoded by the separately pinned, safe-Rust
[`image-webp` 0.2.4](https://github.com/image-rs/image-webp) crate.
BMP, ICO, and WBMP are built into `skia-rs-codec`. The resulting normal/build
graph has no codec `-sys` crate, C/C++ compilation, CMake, system-library
lookup, or binary download.
The locked transitive codec lines are `png` 0.17.16, `jpeg-decoder` 0.3.2,
`jpeg-encoder` 0.6.1 (the upstream JPEG feature couples decode and encode), and
`gif` 0.13.3. `skia-rs-codec` also brings its ordinary Rust core, paint, and
path crates; their build helpers only select Rust cfgs and compile no native
code. A source audit of the locked `skia-rs-*` crates found no unsafe blocks,
and `image-webp` declares `forbid(unsafe_code)`. MetaCrate itself continues to
compile with workspace `unsafe_code = "forbid"`.
`RustSkiaTextureCodec` implements the same `ITextureCodec` abstraction and
decodes BMP, GIF, ICO, baseline/progressive JPEG, interlaced PNG, WBMP, and
lossless/lossy/animated WebP. GIF and WebP return frame one composited on the
logical canvas; ICO chooses its largest valid entry. Both Skia features may be
enabled together, and each codec remains independently constructible.
The graph builds on the workspace Rust 1.96 MSRV; both selected direct codecs
declare older compatible Rust floors.
Enable native decoding with: Enable native decoding with:
@@ -17,7 +50,7 @@ Enable native decoding with:
cargo test -p libremetaverse-imaging-skia --features skia cargo test -p libremetaverse-imaging-skia --features skia
``` ```
The `skia` feature decodes the CPU codec formats supported by the pinned The native `skia` feature decodes the CPU codec formats supported by the pinned
rust-skia release: BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP. Input is buffered rust-skia release: BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP. Input is buffered
to at most 64 MiB. Dimensions are rejected before native pixel allocation when to at most 64 MiB. Dimensions are rejected before native pixel allocation when
they exceed the core 16,777,216-pixel limit, and decoded storage uses checked they exceed the core 16,777,216-pixel limit, and decoded storage uses checked
@@ -31,6 +64,25 @@ alpha at this boundary. RGB565, BGRA8888, RGBA8888, RGBA/BGRA1010102, Gray8,
Alpha8, row padding, and the reference byte-width fallback retain the pinned Alpha8, row padding, and the reference byte-width fallback retain the pinned
C# channel and rounding rules. C# channel and rounding rules.
Both paths accept any `ReadWrite + Send` stream and stop buffering after 64
MiB plus one sentinel byte. The pure-Rust path probes encoded dimensions before
codec pixel allocation and enforces the shared 16,777,216-pixel ceiling; WebP
also receives an explicit decoder memory limit. Checked dimensions, strides,
buffer sizes, and fallible project-owned allocations turn malformed,
truncated, oversized, and allocation failures into typed errors. Decoded rows
have a top-left origin and premultiplied native samples are normalized to
straight alpha at the shared `ManagedImage` boundary.
The pure-Rust graph has identical code on Linux, Windows, macOS, and WASM and
does not call platform APIs. Checked static fixtures cover top-down/bottom-up
BMP, palette/offset GIF, largest-entry ICO, baseline/progressive JPEG,
interlaced palette PNG, multibyte WBMP, lossless-alpha/lossy WebP, malformed and
oversized inputs, and concurrent decode/drop. Gitea runs the `rust-skia` suite
in a dedicated `ubuntu-latest` job that installs no native codec prerequisites.
`image-webp` 0.2's animated-canvas blend fast path rounds fully opaque nonzero
samples one low; the bounded adapter corrects that single upstream edge so the
checked first-frame planes remain byte-identical to native Skia.
## Binary cache and source builds ## Binary cache and source builds
`skia-safe` 0.99.0 downloads an official prebuilt Skia archive when the target `skia-safe` 0.99.0 downloads an official prebuilt Skia archive when the target
@@ -66,3 +118,8 @@ explicitly.
is BSD-3-Clause licensed. Official binary-cache archives contain compiled Skia. is BSD-3-Clause licensed. Official binary-cache archives contain compiled Skia.
Products that redistribute the resulting native artifacts must preserve the Products that redistribute the resulting native artifacts must preserve the
applicable MIT and BSD notices and audit the exact archive they ship. applicable MIT and BSD notices and audit the exact archive they ship.
`skia-rs-codec` is MIT OR Apache-2.0 and `image-webp` is MIT OR Apache-2.0.
They are ordinary Rust source dependencies recorded in `Cargo.lock`; generated
dependency notices and the distribution manifest bind their crates.io checksums
and license texts for source and binary redistribution.

View File

@@ -1,7 +1,7 @@
//! Optional Skia imaging adapter corresponding to `LibreMetaverse.Imaging.Skia`. //! Optional Skia imaging adapter corresponding to `LibreMetaverse.Imaging.Skia`.
//! //!
//! Enable `skia` for bounded `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and //! Enable `skia` or the fully native-Rust `rust-skia` alternative for bounded
//! `WebP` decoding. //! `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and `WebP` decoding.
//! The default build remains inert. See the crate README for native cache, //! The default build remains inert. See the crate README for native cache,
//! source-build, licensing, and cross-platform prerequisite details. //! source-build, licensing, and cross-platform prerequisite details.
@@ -15,6 +15,7 @@ mod skia_codec;
pub use generated::*; pub use generated::*;
pub use libremetaverse_imaging as imaging; pub use libremetaverse_imaging as imaging;
pub use libremetaverse_types::Error; pub use libremetaverse_types::Error;
pub use skia_codec::RustSkiaTextureCodec;
impl libremetaverse_imaging::ITextureCodec for SkiaTextureCodec { impl libremetaverse_imaging::ITextureCodec for SkiaTextureCodec {
fn decode( fn decode(
@@ -24,3 +25,12 @@ impl libremetaverse_imaging::ITextureCodec for SkiaTextureCodec {
Self::decode(self, stream) Self::decode(self, stream)
} }
} }
impl libremetaverse_imaging::ITextureCodec for RustSkiaTextureCodec {
fn decode(
&self,
stream: Box<dyn libremetaverse_types::compat::ReadWrite + Send>,
) -> Result<libremetaverse_imaging::ManagedImage, Error> {
Self::decode(self, stream)
}
}

View File

@@ -2,17 +2,24 @@
use crate::Error; use crate::Error;
use crate::backend::{SKAlphaType, SKBitmap, SKColorType}; use crate::backend::{SKAlphaType, SKBitmap, SKColorType};
#[cfg(feature = "skia")] #[cfg(any(feature = "rust-skia", feature = "skia"))]
use libremetaverse_imaging::{DEFAULT_MAX_ENCODED_BYTES, DEFAULT_MAX_PIXELS}; use libremetaverse_imaging::{DEFAULT_MAX_ENCODED_BYTES, DEFAULT_MAX_PIXELS};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels}; use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_types::compat::ReadWrite; use libremetaverse_types::compat::ReadWrite;
#[cfg(feature = "skia")] #[cfg(any(feature = "rust-skia", feature = "skia"))]
use std::io::Read; use std::io::Read;
/// Optional Skia-backed decoder and format-neutral bitmap converter. /// Optional Skia-backed decoder and format-neutral bitmap converter.
#[derive(Clone, Copy, Debug, Default)] #[derive(Clone, Copy, Debug, Default)]
pub struct SkiaTextureCodec; pub struct SkiaTextureCodec;
/// Pure-Rust alternative to [`SkiaTextureCodec`] with the same image boundary.
///
/// Enable the `rust-skia` feature to decode the common Skia codec formats
/// without a native library, build script, system package lookup, or download.
#[derive(Clone, Copy, Debug, Default)]
pub struct RustSkiaTextureCodec;
impl SkiaTextureCodec { impl SkiaTextureCodec {
/// Creates the stateless codec adapter. /// Creates the stateless codec adapter.
/// ///
@@ -33,17 +40,7 @@ impl SkiaTextureCodec {
pub fn decode(&self, mut stream: Box<dyn ReadWrite + Send>) -> Result<ManagedImage, Error> { pub fn decode(&self, mut stream: Box<dyn ReadWrite + Send>) -> Result<ManagedImage, Error> {
#[cfg(feature = "skia")] #[cfg(feature = "skia")]
{ {
let mut encoded = Vec::new(); let encoded = read_bounded(&mut stream)?;
Read::by_ref(&mut stream)
.take((DEFAULT_MAX_ENCODED_BYTES + 1) as u64)
.read_to_end(&mut encoded)
.map_err(|_| Error::InvalidOperation)?;
if encoded.is_empty() {
return Err(Error::InvalidOperation);
}
if encoded.len() > DEFAULT_MAX_ENCODED_BYTES {
return Err(Error::Argument);
}
decode_with_skia(&encoded) decode_with_skia(&encoded)
} }
#[cfg(not(feature = "skia"))] #[cfg(not(feature = "skia"))]
@@ -65,6 +62,55 @@ impl SkiaTextureCodec {
} }
} }
impl RustSkiaTextureCodec {
/// Creates the stateless pure-Rust codec adapter.
///
/// # Errors
///
/// This fixed compatibility constructor cannot fail.
pub const fn new() -> Result<Self, Error> {
Ok(Self)
}
/// Decodes a bounded stream with Rust-only BMP, GIF, ICO, JPEG, PNG,
/// WBMP, and WebP codecs.
///
/// # Errors
///
/// With `rust-skia`, returns [`Error::Argument`] for an input or decoded
/// canvas above the documented limits and [`Error::InvalidOperation`] for
/// malformed or unsupported images. Without the feature it returns
/// [`Error::InvalidOperation`].
pub fn decode(&self, mut stream: Box<dyn ReadWrite + Send>) -> Result<ManagedImage, Error> {
#[cfg(feature = "rust-skia")]
{
let encoded = read_bounded(&mut stream)?;
decode_with_rust_skia(&encoded)
}
#[cfg(not(feature = "rust-skia"))]
{
let _ = &mut stream;
Err(Error::InvalidOperation)
}
}
}
#[cfg(any(feature = "rust-skia", feature = "skia"))]
fn read_bounded(stream: &mut (dyn ReadWrite + Send)) -> Result<Vec<u8>, Error> {
let mut encoded = Vec::new();
stream
.take((DEFAULT_MAX_ENCODED_BYTES + 1) as u64)
.read_to_end(&mut encoded)
.map_err(|_| Error::InvalidOperation)?;
if encoded.is_empty() {
return Err(Error::InvalidOperation);
}
if encoded.len() > DEFAULT_MAX_ENCODED_BYTES {
return Err(Error::Argument);
}
Ok(encoded)
}
// Keeping the format branches together makes comparison with the pinned C# // Keeping the format branches together makes comparison with the pinned C#
// color switch auditable and prevents subtly different indexing paths. // color switch auditable and prevents subtly different indexing paths.
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
@@ -238,6 +284,404 @@ fn normalize_10bit_alpha(
(normalize(red), normalize(green), normalize(blue)) (normalize(red), normalize(green), normalize(blue))
} }
#[cfg(feature = "rust-skia")]
fn decode_with_rust_skia(encoded: &[u8]) -> Result<ManagedImage, Error> {
use skia_rs_codec::{ImageDecoder, ImageFormat};
let format = if wbmp_dimensions(encoded).is_ok() {
ImageFormat::Wbmp
} else {
ImageFormat::from_magic(encoded)
};
if format == ImageFormat::WebP {
return decode_webp_with_rust(encoded);
}
if !matches!(
format,
ImageFormat::Bmp
| ImageFormat::Gif
| ImageFormat::Ico
| ImageFormat::Jpeg
| ImageFormat::Png
| ImageFormat::Wbmp
) {
return Err(Error::InvalidOperation);
}
let (width, height) = match format {
ImageFormat::Gif => gif_dimensions(encoded)?,
ImageFormat::Ico => ico_dimensions(encoded)?,
ImageFormat::Wbmp => wbmp_dimensions(encoded)?,
_ => skia_rs_codec::get_image_dimensions(encoded).map_err(|_| Error::InvalidOperation)?,
};
validate_dimensions(width, height)?;
let decoded = if format == ImageFormat::Wbmp {
skia_rs_codec::WbmpDecoder::new().decode_bytes(encoded)
} else {
skia_rs_codec::decode_image(encoded)
}
.map_err(|_| Error::InvalidOperation)?;
validate_dimensions(decoded.width(), decoded.height())?;
if decoded.width() != width || decoded.height() != height {
return Err(Error::InvalidOperation);
}
let width_usize = usize::try_from(width).map_err(|_| Error::Argument)?;
if decoded.row_bytes() != width_usize.checked_mul(4).ok_or(Error::Argument)? {
return Err(Error::InvalidOperation);
}
let source_pixels = decoded.peek_pixels().ok_or(Error::InvalidOperation)?;
let grayscale = match format {
ImageFormat::Jpeg => jpeg_is_grayscale(encoded)?,
ImageFormat::Png => encoded.get(25) == Some(&0),
ImageFormat::Wbmp => true,
_ => false,
};
let (row_bytes, color_type, alpha_type, pixels) = if grayscale {
let pixel_count = width_usize
.checked_mul(usize::try_from(height).map_err(|_| Error::Argument)?)
.ok_or(Error::Argument)?;
let mut gray = Vec::new();
gray.try_reserve_exact(pixel_count)
.map_err(|_| Error::InvalidOperation)?;
gray.extend(source_pixels.chunks_exact(4).map(|rgba| rgba[0]));
if gray.len() != pixel_count {
return Err(Error::InvalidOperation);
}
(width_usize, SKColorType::Gray8, SKAlphaType::Opaque, gray)
} else {
let mut rgba = Vec::new();
rgba.try_reserve_exact(source_pixels.len())
.map_err(|_| Error::InvalidOperation)?;
rgba.extend_from_slice(source_pixels);
(
decoded.row_bytes(),
SKColorType::Rgba8888,
if decoded.is_opaque() {
SKAlphaType::Opaque
} else {
SKAlphaType::Unpremul
},
rgba,
)
};
let bitmap = SKBitmap::new(width, height, row_bytes, color_type, alpha_type, pixels)?;
bitmap_to_managed(&bitmap)
}
#[cfg(feature = "rust-skia")]
fn jpeg_is_grayscale(encoded: &[u8]) -> Result<bool, Error> {
let mut offset = 2;
while offset + 1 < encoded.len() {
if encoded[offset] != 0xff {
offset += 1;
continue;
}
let mut marker_offset = offset + 1;
while encoded.get(marker_offset) == Some(&0xff) {
marker_offset += 1;
}
let marker = *encoded.get(marker_offset).ok_or(Error::InvalidOperation)?;
if marker == 0 || marker == 1 || (0xd0..=0xd9).contains(&marker) {
offset = marker_offset + 1;
continue;
}
let length_bytes = encoded
.get(marker_offset + 1..marker_offset + 3)
.ok_or(Error::InvalidOperation)?;
let length = usize::from(u16::from_be_bytes(
length_bytes
.try_into()
.map_err(|_| Error::InvalidOperation)?,
));
if matches!(
marker,
0xc0..=0xc3 | 0xc5..=0xc7 | 0xc9..=0xcb | 0xcd..=0xcf
) {
let components = *encoded
.get(marker_offset + 8)
.ok_or(Error::InvalidOperation)?;
return match components {
1 => Ok(true),
3 => Ok(false),
_ => Err(Error::InvalidOperation),
};
}
if length < 2 {
return Err(Error::InvalidOperation);
}
offset = marker_offset
.checked_add(1)
.and_then(|offset| offset.checked_add(length))
.ok_or(Error::InvalidOperation)?;
}
Err(Error::InvalidOperation)
}
#[cfg(feature = "rust-skia")]
fn gif_dimensions(encoded: &[u8]) -> Result<(i32, i32), Error> {
if encoded.len() < 13 || !(encoded.starts_with(b"GIF87a") || encoded.starts_with(b"GIF89a")) {
return Err(Error::InvalidOperation);
}
let canvas = (
i32::from(u16::from_le_bytes([encoded[6], encoded[7]])),
i32::from(u16::from_le_bytes([encoded[8], encoded[9]])),
);
validate_dimensions(canvas.0, canvas.1)?;
let global_table_bytes = if encoded[10] & 0x80 == 0 {
0
} else {
3_usize
.checked_mul(1_usize << (usize::from(encoded[10] & 0x07) + 1))
.ok_or(Error::InvalidOperation)?
};
let mut offset = 13_usize
.checked_add(global_table_bytes)
.filter(|offset| *offset <= encoded.len())
.ok_or(Error::InvalidOperation)?;
loop {
let marker = *encoded.get(offset).ok_or(Error::InvalidOperation)?;
offset += 1;
match marker {
0x2c => {
let end = offset.checked_add(9).ok_or(Error::InvalidOperation)?;
let descriptor = encoded.get(offset..end).ok_or(Error::InvalidOperation)?;
let frame_width = i32::from(u16::from_le_bytes([descriptor[4], descriptor[5]]));
let frame_height = i32::from(u16::from_le_bytes([descriptor[6], descriptor[7]]));
validate_dimensions(frame_width, frame_height)?;
return Ok(canvas);
}
0x21 => {
offset = offset.checked_add(1).ok_or(Error::InvalidOperation)?;
loop {
let length = *encoded.get(offset).ok_or(Error::InvalidOperation)?;
offset += 1;
if length == 0 {
break;
}
offset = offset
.checked_add(usize::from(length))
.filter(|offset| *offset <= encoded.len())
.ok_or(Error::InvalidOperation)?;
}
}
_ => return Err(Error::InvalidOperation),
}
}
}
#[cfg(feature = "rust-skia")]
fn wbmp_dimensions(encoded: &[u8]) -> Result<(i32, i32), Error> {
if encoded.get(..2) != Some(&[0, 0]) {
return Err(Error::InvalidOperation);
}
let mut offset = 2;
let width =
i32::try_from(read_wbmp_integer(encoded, &mut offset)?).map_err(|_| Error::Argument)?;
let height =
i32::try_from(read_wbmp_integer(encoded, &mut offset)?).map_err(|_| Error::Argument)?;
validate_dimensions(width, height)?;
Ok((width, height))
}
#[cfg(feature = "rust-skia")]
fn read_wbmp_integer(encoded: &[u8], offset: &mut usize) -> Result<u32, Error> {
let mut value = 0_u32;
for _ in 0..5 {
let byte = *encoded.get(*offset).ok_or(Error::InvalidOperation)?;
*offset += 1;
value = value
.checked_shl(7)
.and_then(|value| value.checked_add(u32::from(byte & 0x7f)))
.ok_or(Error::InvalidOperation)?;
if byte & 0x80 == 0 {
return Ok(value);
}
}
Err(Error::InvalidOperation)
}
#[cfg(feature = "rust-skia")]
fn ico_dimensions(encoded: &[u8]) -> Result<(i32, i32), Error> {
if encoded.len() < 6 || encoded.get(..4) != Some(&[0, 0, 1, 0]) {
return Err(Error::InvalidOperation);
}
let count = usize::from(u16::from_le_bytes([encoded[4], encoded[5]]));
let mut best: Option<(u32, usize, usize)> = None;
for index in 0..count {
let offset = 6_usize
.checked_add(index.checked_mul(16).ok_or(Error::InvalidOperation)?)
.ok_or(Error::InvalidOperation)?;
let end = offset.checked_add(16).ok_or(Error::InvalidOperation)?;
let entry = encoded.get(offset..end).ok_or(Error::InvalidOperation)?;
let width = if entry[0] == 0 {
256
} else {
u32::from(entry[0])
};
let height = if entry[1] == 0 {
256
} else {
u32::from(entry[1])
};
let area = width.checked_mul(height).ok_or(Error::Argument)?;
let length = usize::try_from(u32::from_le_bytes(
entry[8..12]
.try_into()
.map_err(|_| Error::InvalidOperation)?,
))
.map_err(|_| Error::Argument)?;
let image_offset = usize::try_from(u32::from_le_bytes(
entry[12..16]
.try_into()
.map_err(|_| Error::InvalidOperation)?,
))
.map_err(|_| Error::Argument)?;
if best.is_none_or(|(best_area, _, _)| area > best_area) {
best = Some((area, image_offset, length));
}
}
let (_, offset, length) = best.ok_or(Error::InvalidOperation)?;
let end = offset.checked_add(length).ok_or(Error::Argument)?;
let image = encoded
.get(offset..end)
.filter(|image| !image.is_empty())
.ok_or(Error::InvalidOperation)?;
if image.starts_with(&[0x89, b'P', b'N', b'G']) {
return skia_rs_codec::get_image_dimensions(image).map_err(|_| Error::InvalidOperation);
}
if image.len() < 40 {
return Err(Error::InvalidOperation);
}
let header_size = u32::from_le_bytes(
image[0..4]
.try_into()
.map_err(|_| Error::InvalidOperation)?,
);
if header_size < 40 {
return Err(Error::InvalidOperation);
}
let width = i32::from_le_bytes(
image[4..8]
.try_into()
.map_err(|_| Error::InvalidOperation)?,
);
let stored_height = i32::from_le_bytes(
image[8..12]
.try_into()
.map_err(|_| Error::InvalidOperation)?,
);
if stored_height <= 0 || stored_height % 2 != 0 {
return Err(Error::InvalidOperation);
}
Ok((width, stored_height / 2))
}
#[cfg(feature = "rust-skia")]
fn validate_dimensions(width: i32, height: i32) -> Result<(), Error> {
let width = usize::try_from(width).map_err(|_| Error::Argument)?;
let height = usize::try_from(height).map_err(|_| Error::Argument)?;
width
.checked_mul(height)
.filter(|pixels| *pixels > 0 && *pixels <= DEFAULT_MAX_PIXELS)
.map(|_| ())
.ok_or(Error::Argument)
}
#[cfg(feature = "rust-skia")]
fn decode_webp_with_rust(encoded: &[u8]) -> Result<ManagedImage, Error> {
use std::io::{BufReader, Cursor};
let reader = BufReader::new(Cursor::new(encoded));
let mut decoder = image_webp::WebPDecoder::new(reader).map_err(|_| Error::InvalidOperation)?;
let (width, height) = decoder.dimensions();
let animated = decoder.is_animated();
let width_i32 = i32::try_from(width).map_err(|_| Error::Argument)?;
let height_i32 = i32::try_from(height).map_err(|_| Error::Argument)?;
validate_dimensions(width_i32, height_i32)?;
decoder.set_memory_limit(DEFAULT_MAX_PIXELS * 16);
let output_size = decoder.output_buffer_size().ok_or(Error::Argument)?;
if output_size > DEFAULT_MAX_PIXELS * 4 {
return Err(Error::Argument);
}
let mut packed_pixels = Vec::new();
packed_pixels
.try_reserve_exact(output_size)
.map_err(|_| Error::InvalidOperation)?;
packed_pixels.resize(output_size, 0);
decoder
.read_image(&mut packed_pixels)
.map_err(|_| Error::InvalidOperation)?;
let has_alpha = decoder.has_alpha();
// image-webp 0.2 uses a divide-by-256 blend fast path for animated
// canvases. Its fully opaque nonzero samples are consequently one below
// Skia/libwebp's divide-by-255 result. Correct that bounded first-frame
// canvas here; partially transparent samples already use the same rounding
// as the compatibility backend.
if animated {
if has_alpha {
for rgba in packed_pixels.chunks_exact_mut(4) {
if rgba[3] == u8::MAX {
for channel in &mut rgba[..3] {
if *channel != 0 {
*channel = channel.saturating_add(1);
}
}
}
}
} else {
for channel in &mut packed_pixels {
if *channel != 0 {
*channel = channel.saturating_add(1);
}
}
}
}
let pixels = if has_alpha {
packed_pixels
} else {
let pixel_count = usize::try_from(width)
.ok()
.and_then(|width| {
usize::try_from(height)
.ok()
.and_then(|height| width.checked_mul(height))
})
.ok_or(Error::Argument)?;
let rgba_size = pixel_count.checked_mul(4).ok_or(Error::Argument)?;
let mut rgba = Vec::new();
rgba.try_reserve_exact(rgba_size)
.map_err(|_| Error::InvalidOperation)?;
for rgb in packed_pixels.chunks_exact(3) {
rgba.extend_from_slice(&[rgb[0], rgb[1], rgb[2], u8::MAX]);
}
if rgba.len() != rgba_size {
return Err(Error::InvalidOperation);
}
rgba
};
let row_bytes = usize::try_from(width)
.map_err(|_| Error::Argument)?
.checked_mul(4)
.ok_or(Error::Argument)?;
let bitmap = SKBitmap::new(
width_i32,
height_i32,
row_bytes,
SKColorType::Rgba8888,
if has_alpha {
SKAlphaType::Unpremul
} else {
SKAlphaType::Opaque
},
pixels,
)?;
bitmap_to_managed(&bitmap)
}
#[cfg(feature = "skia")] #[cfg(feature = "skia")]
fn decode_with_skia(encoded: &[u8]) -> Result<ManagedImage, Error> { fn decode_with_skia(encoded: &[u8]) -> Result<ManagedImage, Error> {
use skia_safe::Data; use skia_safe::Data;
@@ -528,6 +972,394 @@ mod tests {
SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))), SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))),
Err(Error::InvalidOperation) Err(Error::InvalidOperation)
); );
#[cfg(not(feature = "rust-skia"))]
assert_eq!(
RustSkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))),
Err(Error::InvalidOperation)
);
}
#[cfg(any(feature = "rust-skia", feature = "skia"))]
fn hex_fixture(source: &str) -> Vec<u8> {
let digits: Vec<_> = source.bytes().filter(u8::is_ascii_hexdigit).collect();
assert_eq!(digits.len() % 2, 0, "fixture contains a partial byte");
digits
.chunks_exact(2)
.map(|pair| {
let high = (pair[0] as char).to_digit(16).expect("hex digit");
let low = (pair[1] as char).to_digit(16).expect("hex digit");
u8::try_from(high * 16 + low).expect("hex byte")
})
.collect()
}
#[cfg(feature = "rust-skia")]
fn rust_decode(encoded: Vec<u8>) -> Result<ManagedImage, Error> {
RustSkiaTextureCodec.decode(Box::new(std::io::Cursor::new(encoded)))
}
#[cfg(any(feature = "rust-skia", feature = "skia"))]
fn checked_format_matrix() -> [(&'static str, &'static str, (i32, i32)); 19] {
[
(
"BMP 16-bit RGB555",
include_str!("../tests/fixtures/rgb555.bmp.hex"),
(2, 1),
),
(
"BMP 24-bit bottom-up",
include_str!("../tests/fixtures/bottom-up.bmp.hex"),
(8, 8),
),
(
"BMP 32-bit BGRX",
include_str!("../tests/fixtures/bgrx32.bmp.hex"),
(2, 1),
),
(
"GIF palette and frame offset",
include_str!("../tests/fixtures/palette-offset.gif.hex"),
(4, 2),
),
(
"ICO largest entry",
include_str!("../tests/fixtures/multi.ico.hex"),
(2, 2),
),
(
"JPEG baseline RGB",
include_str!("../tests/fixtures/baseline.jpg.hex"),
(8, 8),
),
(
"JPEG progressive grayscale",
include_str!("../tests/fixtures/progressive.jpg.hex"),
(128, 128),
),
(
"PNG grayscale",
include_str!("../tests/fixtures/gray.png.hex"),
(32, 32),
),
(
"PNG grayscale alpha",
include_str!("../tests/fixtures/gray-alpha.png.hex"),
(32, 32),
),
(
"PNG RGB",
include_str!("../tests/fixtures/rgb.png.hex"),
(32, 32),
),
(
"PNG RGBA",
include_str!("../tests/fixtures/rgba.png.hex"),
(32, 32),
),
(
"PNG straight alpha",
include_str!("../tests/fixtures/straight-alpha.png.hex"),
(1, 1),
),
(
"PNG interlaced palette",
include_str!("../tests/fixtures/interlaced-palette.png.hex"),
(32, 32),
),
(
"WBMP multibyte width",
include_str!("../tests/fixtures/multibyte.wbmp.hex"),
(130, 1),
),
(
"WBMP minimum-length image",
include_str!("../tests/fixtures/small.wbmp.hex"),
(2, 2),
),
(
"WebP lossless RGB",
include_str!("../tests/fixtures/rgb-lossless.webp.hex"),
(2, 2),
),
(
"WebP lossless alpha",
include_str!("../tests/fixtures/alpha.webp.hex"),
(1, 1),
),
(
"WebP lossy",
include_str!("../tests/fixtures/lossy-red.webp.hex"),
(8, 8),
),
(
"WebP animated first frame",
include_str!("../tests/fixtures/animated.webp.hex"),
(11, 29),
),
]
}
#[cfg(any(feature = "rust-skia", feature = "skia"))]
fn assert_checked_format_matrix(codec: &dyn libremetaverse_imaging::ITextureCodec) {
for (name, fixture, dimensions) in checked_format_matrix() {
let image = codec
.decode(Box::new(std::io::Cursor::new(hex_fixture(fixture))))
.unwrap_or_else(|error| panic!("{name} failed to decode: {error:?}"));
assert_eq!((image.width, image.height), dimensions, "{name}");
assert_eq!(
image.channels,
if matches!(
name,
"JPEG progressive grayscale"
| "PNG grayscale"
| "WBMP multibyte width"
| "WBMP minimum-length image"
) {
ManagedImageImageChannels::GRAY
} else {
ManagedImageImageChannels::COLOR | ManagedImageImageChannels::ALPHA
},
"{name}"
);
}
}
#[cfg(feature = "rust-skia")]
#[test]
fn rust_feature_decodes_the_checked_format_matrix() {
assert_checked_format_matrix(&RustSkiaTextureCodec);
}
#[cfg(feature = "skia")]
#[test]
fn native_feature_decodes_the_checked_format_matrix() {
assert_checked_format_matrix(&SkiaTextureCodec);
}
#[cfg(all(feature = "rust-skia", feature = "skia"))]
#[test]
fn native_and_rust_backends_match_the_shared_contract_matrix() {
fn assert_plane(name: &str, plane: &str, native: &[u8], rust: &[u8], tolerance: u8) {
assert_eq!(native.len(), rust.len(), "{name} {plane} length");
for (index, (&native, &rust)) in native.iter().zip(rust).enumerate() {
assert!(
native.abs_diff(rust) <= tolerance,
"{name} {plane}[{index}]: native={native}, rust={rust}, tolerance={tolerance}"
);
}
}
for (name, fixture, _) in checked_format_matrix() {
let bytes = hex_fixture(fixture);
let native = SkiaTextureCodec
.decode(Box::new(std::io::Cursor::new(bytes.clone())))
.unwrap_or_else(|error| panic!("native {name}: {error:?}"));
let rust = RustSkiaTextureCodec
.decode(Box::new(std::io::Cursor::new(bytes)))
.unwrap_or_else(|error| panic!("Rust {name}: {error:?}"));
assert_eq!(native.width, rust.width, "{name} width");
assert_eq!(native.height, rust.height, "{name} height");
assert_eq!(native.channels, rust.channels, "{name} channels");
let tolerance = if name.starts_with("JPEG") {
8
} else if name == "WebP lossy" {
10
} else {
0
};
assert_plane(name, "red", &native.red, &rust.red, tolerance);
assert_plane(name, "green", &native.green, &rust.green, tolerance);
assert_plane(name, "blue", &native.blue, &rust.blue, tolerance);
assert_plane(name, "alpha", &native.alpha, &rust.alpha, tolerance);
}
}
#[cfg(feature = "rust-skia")]
#[test]
fn rust_feature_preserves_canvas_orientation_selection_and_alpha() {
for fixture in [
include_str!("../tests/fixtures/two-by-two-bottom-up.bmp.hex"),
include_str!("../tests/fixtures/two-by-two-top-down.bmp.hex"),
] {
let bitmap = rust_decode(hex_fixture(fixture)).expect("oriented BMP");
assert_eq!(bitmap.red, [255, 0, 0, 255]);
assert_eq!(bitmap.green, [0, 255, 0, 255]);
assert_eq!(bitmap.blue, [0, 0, 255, 255]);
}
let gif = rust_decode(hex_fixture(include_str!(
"../tests/fixtures/palette-offset.gif.hex"
)))
.expect("GIF logical canvas");
assert_eq!((gif.width, gif.height), (4, 2));
let icon = rust_decode(hex_fixture(include_str!("../tests/fixtures/multi.ico.hex")))
.expect("largest ICO entry");
assert_eq!(icon.red, [255, 0, 0, 255]);
assert_eq!(icon.green, [0, 255, 0, 255]);
assert_eq!(icon.blue, [0, 0, 255, 255]);
let alpha = rust_decode(hex_fixture(include_str!(
"../tests/fixtures/alpha.webp.hex"
)))
.expect("alpha WebP");
assert_eq!(
(alpha.red[0], alpha.green[0], alpha.blue[0]),
(255, 255, 255)
);
assert!((127..=128).contains(&alpha.alpha[0]));
let lossy = rust_decode(hex_fixture(include_str!(
"../tests/fixtures/lossy-red.webp.hex"
)))
.expect("lossy WebP");
assert!(lossy.red.iter().all(|red| *red >= 245));
assert!(lossy.green.iter().all(|green| *green <= 10));
assert!(lossy.blue.iter().all(|blue| *blue <= 10));
assert!(lossy.alpha.iter().all(|alpha| *alpha == u8::MAX));
}
#[cfg(feature = "rust-skia")]
#[test]
fn rust_feature_rejects_invalid_truncated_and_oversized_input_without_panicking() {
for malformed in [Vec::new(), b"not an image".to_vec(), b"GIF89a".to_vec()] {
let result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| rust_decode(malformed)));
assert!(result.is_ok(), "malformed input panicked");
assert_eq!(result.expect("checked above"), Err(Error::InvalidOperation));
}
let mut pixel_bomb =
hex_fixture(include_str!("../tests/fixtures/interlaced-palette.png.hex"));
pixel_bomb[16..20].copy_from_slice(&4097_u32.to_be_bytes());
pixel_bomb[20..24].copy_from_slice(&4097_u32.to_be_bytes());
assert_eq!(rust_decode(pixel_bomb), Err(Error::Argument));
let mut gif_frame_bomb =
hex_fixture(include_str!("../tests/fixtures/palette-offset.gif.hex"));
let descriptor = gif_frame_bomb[109..]
.iter()
.position(|byte| *byte == 0x2c)
.map(|offset| offset + 109)
.expect("first GIF image descriptor");
gif_frame_bomb[descriptor + 5..descriptor + 7].copy_from_slice(&u16::MAX.to_le_bytes());
gif_frame_bomb[descriptor + 7..descriptor + 9].copy_from_slice(&u16::MAX.to_le_bytes());
assert_eq!(rust_decode(gif_frame_bomb), Err(Error::Argument));
let mut malformed_gif_palette =
hex_fixture(include_str!("../tests/fixtures/palette-offset.gif.hex"));
// Advertise a 256-entry global palette while retaining the fixture's
// much smaller checked table. Preflight must reject the missing table
// before the decoder can allocate or index palette storage.
malformed_gif_palette[10] = (malformed_gif_palette[10] & 0xf8) | 0x07;
assert_eq!(
rust_decode(malformed_gif_palette),
Err(Error::InvalidOperation)
);
let mut icon_embedded_bomb = hex_fixture(include_str!("../tests/fixtures/multi.ico.hex"));
icon_embedded_bomb[86..90].copy_from_slice(&4097_i32.to_le_bytes());
icon_embedded_bomb[90..94].copy_from_slice(&8194_i32.to_le_bytes());
assert_eq!(rust_decode(icon_embedded_bomb), Err(Error::Argument));
let over_limit = vec![0_u8; DEFAULT_MAX_ENCODED_BYTES + 1];
assert_eq!(rust_decode(over_limit), Err(Error::Argument));
for fixture in [
include_str!("../tests/fixtures/bottom-up.bmp.hex"),
include_str!("../tests/fixtures/palette-offset.gif.hex"),
include_str!("../tests/fixtures/multi.ico.hex"),
include_str!("../tests/fixtures/interlaced-palette.png.hex"),
include_str!("../tests/fixtures/alpha.webp.hex"),
] {
let mut bytes = hex_fixture(fixture);
bytes.truncate(bytes.len() / 2);
let result =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| rust_decode(bytes)));
assert!(result.is_ok(), "truncated input panicked");
assert!(result.expect("checked above").is_err());
}
}
#[cfg(feature = "rust-skia")]
struct DropStream {
cursor: std::io::Cursor<Vec<u8>>,
drops: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
#[cfg(feature = "rust-skia")]
impl std::io::Read for DropStream {
fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
std::io::Read::read(&mut self.cursor, buffer)
}
}
#[cfg(feature = "rust-skia")]
impl std::io::Write for DropStream {
fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
std::io::Write::write(&mut self.cursor, buffer)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[cfg(feature = "rust-skia")]
impl std::io::Seek for DropStream {
fn seek(&mut self, position: std::io::SeekFrom) -> std::io::Result<u64> {
std::io::Seek::seek(&mut self.cursor, position)
}
}
#[cfg(feature = "rust-skia")]
impl Drop for DropStream {
fn drop(&mut self) {
self.drops.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
}
}
#[cfg(feature = "rust-skia")]
#[test]
fn rust_feature_is_thread_safe_across_repeated_decode_and_drop() {
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
fn assert_texture_codec<T: libremetaverse_imaging::ITextureCodec + Send + Sync>() {}
assert_texture_codec::<RustSkiaTextureCodec>();
let codec = Arc::new(RustSkiaTextureCodec);
let png = Arc::new(hex_fixture(include_str!(
"../tests/fixtures/interlaced-palette.png.hex"
)));
let mut workers = Vec::new();
for _ in 0..8 {
let codec = Arc::clone(&codec);
let png = Arc::clone(&png);
workers.push(std::thread::spawn(move || {
for _ in 0..16 {
let image = codec
.decode(Box::new(std::io::Cursor::new((*png).clone())))
.expect("concurrent PNG decode");
assert_eq!((image.width, image.height), (32, 32));
let repeated = codec
.decode(Box::new(std::io::Cursor::new((*png).clone())))
.expect("repeated concurrent PNG decode");
assert_eq!(image, repeated);
}
}));
}
for worker in workers {
worker.join().expect("decoder worker did not panic");
}
let drops = Arc::new(AtomicUsize::new(0));
let stream = DropStream {
cursor: std::io::Cursor::new((*png).clone()),
drops: Arc::clone(&drops),
};
codec.decode(Box::new(stream)).expect("drop-probe decode");
assert_eq!(drops.load(Ordering::SeqCst), 1);
} }
#[cfg(feature = "skia")] #[cfg(feature = "skia")]

View File

@@ -0,0 +1,29 @@
# Rust codec fixtures
The tests decode these hexadecimal files into their exact checked-in bytes; no
image is generated while tests run. Unless noted below, fixtures come from the
BSD-3-Clause Skia test resources vendored by `skia-bindings 0.99.0`.
| Fixture | SHA-256 |
| --- | --- |
| `alpha.webp` | `8b09993d8ffc9a9ec4e7f5f26a1c126cf265c37b0d63c0c5be2506e20dbc6fea` |
| `animated.webp` | `292753f066add623af1e30bbeeca58f15b3d6d1052e12b13e30f21f8d3c14505` |
| `baseline.jpg` | `4e45048dbe3078943eb364221a9757df9b9845b80e795ccdc944f1c18b5e221b` |
| `bottom-up.bmp` | `d9cc5fff9e8e351727ad4519236ae46eaf37f8007dad5cdb8619fcd67ec68903` |
| `gray-alpha.png` | `377a67510b4ccc74cdcce14847b02035e75ccab36e9d8e00586c4c24d1ae385a` |
| `gray.png` | `85b19fcda452d6cbf02f570c353cfa9e47c29237f2fc8f0114deb6cd0b23f97a` |
| `interlaced-palette.png` | `6283a61b1f7344c0cfa741076d6aed4a16a6bc5582e81db8dd7374a34230b0df` |
| `palette-offset.gif` | `32d3409a79b75614ef385354d54f148f08856f8e9ce0d55054582ff7f40f4461` |
| `progressive.jpg` | `d07a9815dc792543c16b796ec7729c19d6a29e7d9b4d0bbab50f4ef210be7767` |
| `rgb.png` | `ecea1ef4a001a999ce9b82d18abf47401eb92998708b306b6c9a8ac3a6a9a2c8` |
| `rgba.png` | `756a03364c02e3c9f85d6f4029eb3cef2488c081dc537d46e102bebcb9e02732` |
| `straight-alpha.png` | `5a647404fa5289da777b1bb9e1dcfe20027262f992a214f38efaecc3da0e08a4` |
`lossy-red.webp` was produced once with FFmpeg 5.1.8/libwebp from a solid
8x8 red source (`-c:v libwebp -lossless 0 -quality 80`) and has SHA-256
`7e3aaf1a442b6e69f7706ee5afe0aeacb2d096e384dbb63e792421fe2449cfe8`.
The remaining tiny BMP, ICO, WBMP, and lossless RGB WebP files are original
MetaCrate byte-level fixtures. Their reviewed bytes are intentionally readable
in the `.hex` files and exercise header fields, row layout, masks, multi-byte
integers, and multi-entry selection directly.

View File

@@ -0,0 +1,3 @@
524946461e000000574542505650384c110000002f0000001007d0fffef7
bf7f8188e87f0000

View File

@@ -0,0 +1,12 @@
524946464c01000057454250565038580a000000120000000a00001c0000
414e494d060000000000ffff0000414e4d465a0000000000000000000a00
001c0000e80300005650384c420000002f0a0007001720164ce6ff849cc6
388ee3fc078e0645b1ad54573b50821444f0db8728aec9f9562688e8bf80
a0c8013bd09c2c5e759505ce8f25232ee33615170c0d414e4d465a000000
010000050000060000100000f40100005650384c420000002f0600040017
201048525e647e814092f222330b049298079979fe037f15288a6ca749a3
de31819aee2fc70c32898988fe4f008092d5d79d5525f8079e709b3b6120
414e4d465c0000000100000100000600000f0000e80300005650384c4400
00002f06c0030017201048621e647e814092f222330b049298079979fe03
7f15286a1bc9b9defe4362d05ce5b7cfd5c23c1411fd4f093a8c46a30e09
ee4f7ff3c3c3c3cdd303

View File

@@ -0,0 +1,11 @@
ffd8ffe000104a46494600010101004800480000ffdb004300100b0c0e0c
0a100e0d0e1211101318281a181616183123251d283a333d3c3933383740
485c4e404457453738506d51575f626768673e4d71797064785c656763ff
db0043011112121815182f1a1a2f63423842636363636363636363636363
636363636363636363636363636363636363636363636363636363636363
6363636363636363ffc00011080008000803012200021101031101ffc400
1500010100000000000000000000000000000003ffc4001e100002010403
01000000000000000000000102000311213112224171ffc4001501010100
000000000000000000000000000304ffc4001a1101000203010000000000
00000000000001000203213141ffda000c03010002110311003f009d4e6a
8c15b354b2d455eda6b922c32367dd7db2222d6c9a2558c7c5393fffd9

View File

@@ -0,0 +1,2 @@
424d3e000000000000003600000028000000020000000100000001002000000000000800000000000000000000000000000000000000
0000ff0000ff0000

View File

@@ -0,0 +1,10 @@
424df6000000000000003600000028000000080000000800000001001800
00000000c0000000130b0000130b000000000000000000002272b0f8232e
d989f03857b32260a8fe403371fe95df716a63fea76b954ce022bce472b2
4a9f121345e342373d0a19bd98ac6f271d9411e45f83b2fb996ed8629116
8c1271b4ca39f93d27831ab4c312f867abcc184203a7b97d481082b44afe
5221c5d6cc78abb4dc7dd209f300ca475d60b26f44466e5742f73ba8028f
df09557e02c78aaa24966c888154628936400ea6bdb61dc088d62bd46293
b241c6decea5ba6e60f34b8f70a5bb5d5f39395ce266761927ba3cb0fcde
7438c19300fa

View File

@@ -0,0 +1,5 @@
89504e470d0a1a0a0000000d4948445200000020000000200804000000d9
73b27f0000000467414d41000186a031e8965f0000003549444154488963
fccfc0c001859c683431229c2cdf1928032c3f86be01a361301a06543160
341047c3802a068c06e2681850c30000df2a207da0bf7158000000004945
4e44ae426082

View File

@@ -0,0 +1,5 @@
89504e470d0a1a0a0000000d494844520000002000000020080000000056
1125280000000467414d41000186a031e8965f0000004149444154388d63
646024001408c8b30c05058c0f0829f8f71f3f6079301c1430ca11906764
a2795c0c06058c8ff0cafeffcff887e67131181430cae0956564040050e5
fe7171a90b240000000049454e44ae426082

View File

@@ -0,0 +1,5 @@
89504e470d0a1a0a0000000d49484452000000200000002001030000013e
b3d82100000006504c5445eeff222266ff6c1ad226000000294944415408
d7636080820f50b80a0a43d1b0b131c39933d849088348363f1040880f40
00212810030009ba422d1cbe5f9a0000000049454e44ae426082

View File

@@ -0,0 +1,4 @@
524946463c000000574542505650382030000000d001009d012a08000800
01402625a00274ba01f80003b000fef36997fe6c0ae6b9f7fbffe9707e97
07e9707fe9480000

View File

@@ -0,0 +1,8 @@
000001000200
01010000010020002c00000026000000
02020000010020003800000052000000
28000000010000000200000001002000000000000400000000000000000000000000000000000000
0000ffff
28000000020000000400000001002000000000001000000000000000000000000000000000000000
ff0000ffffffffff0000ffff00ff00ff

View File

@@ -0,0 +1,3 @@
0000810201
ffffffffffffffffffffffffffffffff80

View File

@@ -0,0 +1,7 @@
474946383961040002008400000000001000002000003000004000005000
00600000700000800000900000a00000b00000c00000d00000e00000f000
000000000000110000220000330000440000550000660000770000880000
990000aa0000bb0000cc0000dd0000ee0000ff21f904000a0000002c0000
00000400020000070908090a0b0c0d0e0f810021f9040114001a002c0100
0100030001000007041f1a1581003b

View File

@@ -0,0 +1,26 @@
ffd8ffe000104a46494600010101004800480000ffdb0043000806060706
05080707070909080a0c140d0c0b0b0c1912130f141d1a1f1e1d1a1c1c20
242e2720222c231c1c2837292c30313434341f27393d38323c2e333432ff
c2000b080080008001011100ffc400150001010000000000000000000000
0000000306ffda0008010100000001824444444444444448744444444444
444487444444444444444874444444444444448744444444444444487444
444444444444874444444444444448744444444444444487444444444444
444874444444444444448744444444444444487444444444444444874444
444444444448744444444444444487444444444444444874444444444444
44ffc40014100100000000000000000000000000000080ffda0008010100
010502007fffc40014100100000000000000000000000000000080ffda00
08010100063f02007fffc400151001010000000000000000000000000000
0100ffda0008010100013f21082082082082082082082082082082082082
082082082082082082082082082082082082082082082082082082082082
082082082082082082082082082082082082082082082082082082082082
082082082082082082082082082082082082082082082082082082082082
082082082082082082082082082082082082082082082082082082082082
082082082082082082082082082082082082082082082082082082082082
082082082082082082082082082082082082082082082082ffda00080101
000000100000000000000000000000000000000000000000000000000000
000000000000ffc40014100100000000000000000000000000000080ffda
0008010100013f10007fff00ff00ff00ff00ff00ff00ff00ff00ff00ff00
ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00
ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00
ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00
ff00ff00ff00ff00ff00ff00ff00ff00ff00ffd9

View File

@@ -0,0 +1,2 @@
524946461c000000574542505650384c0f0000002f014000000710f58ffe
0722a2ff0100

View File

@@ -0,0 +1,5 @@
89504e470d0a1a0a0000000d4948445200000020000000200802000000fc
18eda30000000467414d41000186a031e8965f00000048494441544889ed
d5c10900300c024085ec91fdb772133b442bf4a1f8cee12bb40d043b800a
14f81ca0ede47d4c784081020f4a871fc284071428f0a0743823a94081bb
7077a3c00182b1f95e3ed793980000000049454e44ae426082

View File

@@ -0,0 +1,2 @@
424d3a000000000000003600000028000000020000000100000001001000000000000400000000000000000000000000000000000000
007ce003

View File

@@ -0,0 +1,7 @@
89504e470d0a1a0a0000000d494844520000002000000020080600000073
7a7af40000000467414d41000186a031e8965f0000006f494441545885ed
d6310a80300c46e12764684fa1f73f55048f21c4ddc545781d52e85028fc
1f4d28d98a01305e7b7e9cffba33831d75054703ca06a8f90d58a0074e35
1e227d805c8254e31bb0420f5cdc2e0079208892ffe2a00136a07b400794
3c1004d900195036407f011bf00052201a9cd68fe9670000000049454e44
ae426082

View File

@@ -0,0 +1 @@
000002028040

View File

@@ -0,0 +1,6 @@
89504e470d0a1a0a0000000d49484452000000010000000108060000001f
15c489000000017352474200aece1ce900000006624b474400ff00ff00ff
a0bda793000000097048597300000b1300000b1301009a9c180000000774
494d4507de040313163ac721af9d0000001974455874436f6d6d656e7400
4372656174656420776974682047494d5057810e170000000d4944415408
d763f8ffff7f3d00097b037d178855c50000000049454e44ae426082

View File

@@ -0,0 +1,3 @@
424d46000000000000003600000028000000020000000200000001001800000000001000000000000000000000000000000000000000
ff0000ffffff00000000ff00ff000000

View File

@@ -0,0 +1,3 @@
424d4600000000000000360000002800000002000000feffffff01001800000000001000000000000000000000000000000000000000
0000ff00ff000000ff0000ffffff0000

View File

@@ -18,6 +18,12 @@ sha1 = "0.10"
sha2 = "0.11" sha2 = "0.11"
uuid = { version = "1.18", features = ["v4"] } uuid = { version = "1.18", features = ["v4"] }
# Browser and JavaScript WASM targets have no operating-system entropy syscall.
# Select getrandom's supported Web Crypto adapter only for that target family.
[target.'cfg(target_family = "wasm")'.dependencies]
getrandom = { version = "0.4", features = ["wasm_js"] }
uuid = { version = "1.18", features = ["v4", "rng-getrandom"] }
[dev-dependencies] [dev-dependencies]
stats_alloc = "0.1.10" stats_alloc = "0.1.10"

View File

@@ -19,7 +19,7 @@ they use those APIs directly.
| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust | | `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust |
| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust | | `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` | | `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` |
| `libremetaverse-imaging-skia` | Common raster formats through Skia | Optional `skia`; native Skia build/cache | | `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Optional `skia` native build/cache; optional `rust-skia` has no native prerequisites |
| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust | | `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust |
| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust | | `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust |
| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust | | `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust |

View File

@@ -39,6 +39,8 @@ allow = []
allow-workspace = true allow-workspace = true
deny = [] deny = []
skip = [ skip = [
{ crate = "bit-vec@0.8.0", reason = "Approved pinned skia-rs shader parser compatibility line" },
{ crate = "bitflags@1.3.2", reason = "Approved pinned pure-Rust PNG compatibility line" },
{ crate = "block-buffer@0.10.4", reason = "Approved RustCrypto digest 0.10 compatibility line" }, { crate = "block-buffer@0.10.4", reason = "Approved RustCrypto digest 0.10 compatibility line" },
{ crate = "cfg_aliases@0.1.1", reason = "Approved older nix build-helper line" }, { crate = "cfg_aliases@0.1.1", reason = "Approved older nix build-helper line" },
{ crate = "const-oid@0.9.6", reason = "Approved DTLS certificate compatibility line" }, { crate = "const-oid@0.9.6", reason = "Approved DTLS certificate compatibility line" },
@@ -47,10 +49,12 @@ skip = [
{ crate = "digest@0.10.7", reason = "Approved legacy hash and WebRTC compatibility line" }, { crate = "digest@0.10.7", reason = "Approved legacy hash and WebRTC compatibility line" },
{ crate = "getrandom@0.2.17", reason = "Approved elliptic-curve entropy compatibility line" }, { crate = "getrandom@0.2.17", reason = "Approved elliptic-curve entropy compatibility line" },
{ crate = "getrandom@0.3.4", reason = "Approved rand 0.9 entropy compatibility line" }, { crate = "getrandom@0.3.4", reason = "Approved rand 0.9 entropy compatibility line" },
{ crate = "hashbrown@0.16.1", reason = "Approved pinned skia-rs geometry compatibility line" },
{ crate = "nix@0.28.0", reason = "Approved machine-address Unix compatibility line" }, { crate = "nix@0.28.0", reason = "Approved machine-address Unix compatibility line" },
{ crate = "nom@7.1.3", reason = "Approved Skia bindgen parser compatibility line" }, { crate = "nom@7.1.3", reason = "Approved Skia bindgen parser compatibility line" },
{ crate = "rand_core@0.6.4", reason = "Approved elliptic-curve random compatibility line" }, { crate = "rand_core@0.6.4", reason = "Approved elliptic-curve random compatibility line" },
{ crate = "rand_core@0.9.5", reason = "Approved rand 0.9 compatibility line" }, { crate = "rand_core@0.9.5", reason = "Approved rand 0.9 compatibility line" },
{ crate = "rustc-hash@1.1.0", reason = "Approved pinned skia-rs shader parser compatibility line" },
{ crate = "sha2@0.10.9", reason = "Approved WebRTC RustCrypto compatibility line" }, { crate = "sha2@0.10.9", reason = "Approved WebRTC RustCrypto compatibility line" },
{ crate = "shlex@1.3.0", reason = "Approved Skia bindgen shell parser compatibility line" }, { crate = "shlex@1.3.0", reason = "Approved Skia bindgen shell parser compatibility line" },
{ crate = "syn@2.0.119", reason = "Approved MetaCrate code generator syntax line" }, { crate = "syn@2.0.119", reason = "Approved MetaCrate code generator syntax line" },

View File

@@ -18,7 +18,8 @@ and [`MeshFoundry` guide](../crates/libremetaverse-rendering-mesh-foundry/README
| Managed images and TGA | enabled | n/a | none | | Managed images and TGA | enabled | n/a | none |
| JPEG 2000 | disabled | `libremetaverse-imaging/jpeg2000` | OpenJPEG 2.5.x | | JPEG 2000 | disabled | `libremetaverse-imaging/jpeg2000` | OpenJPEG 2.5.x |
| JPEG 2000 (pure Rust) | disabled | `libremetaverse-imaging/rust-j2k` | none (`j2k` 0.8.1) | | JPEG 2000 (pure Rust) | disabled | `libremetaverse-imaging/rust-j2k` | none (`j2k` 0.8.1) |
| BMP/GIF/ICO/JPEG/PNG/WBMP/WebP | disabled | `libremetaverse-imaging-skia/skia` | Skia | | BMP/GIF/ICO/JPEG/PNG/WBMP/WebP (pure Rust) | disabled | `libremetaverse-imaging-skia/rust-skia` | none (`skia-rs-codec` 0.3.0 plus `image-webp` 0.2.4) |
| BMP/GIF/ICO/JPEG/PNG/WBMP/WebP (native) | disabled | `libremetaverse-imaging-skia/skia` | Skia |
| Prim and sculpt meshing, OBJ ingestion | enabled | n/a | none | | Prim and sculpt meshing, OBJ ingestion | enabled | n/a | none |
OpenJPEG is a non-default workspace member so release tooling can package the OpenJPEG is a non-default workspace member so release tooling can package the
@@ -26,10 +27,11 @@ adapter explicitly, and imaging reaches it only through the optional
dependency. The independent `rust-j2k` feature reaches only the audited dependency. The independent `rust-j2k` feature reaches only the audited
pure-Rust `j2k` graph; enabling both features keeps both explicit codec types pure-Rust `j2k` graph; enabling both features keeps both explicit codec types
available for interoperability testing. Skia is likewise an optional, available for interoperability testing. Skia is likewise an optional,
target-specific dependency. The target-specific dependency. The consolidated required Gitea gate exercises the
consolidated required Gitea gate exercises both optional features on native feature, while a dedicated `ubuntu-latest` job tests `rust-skia`, audits
`ubuntu-latest` while its minimal-feature check proves that no native codec is its normal/build graph without installing native codec tools, and compiles the
needed for the owned Rust and translated compatibility tests. same code for WASM. Minimal-feature checks still prove no codec is needed for
the owned Rust and translated compatibility tests.
## Bounds and rendering contract ## Bounds and rendering contract

View File

@@ -24,6 +24,7 @@ Optional boundaries are explicit:
| --- | --- | --- | | --- | --- | --- |
| `jpeg2000` | J2K/JP2 through the safe OpenJPEG adapter | OpenJPEG 2.5.4 or newer | | `jpeg2000` | J2K/JP2 through the safe OpenJPEG adapter | OpenJPEG 2.5.4 or newer |
| `rust-j2k` | J2K/JP2 through `RustJ2kCodec` | none; exact pure-Rust `j2k` 0.8.1 | | `rust-j2k` | J2K/JP2 through `RustJ2kCodec` | none; exact pure-Rust `j2k` 0.8.1 |
| `rust-skia` | BMP/GIF/ICO/JPEG/PNG/WBMP/WebP through `RustSkiaTextureCodec` | none; exact pure-Rust `skia-rs-codec` 0.3.0 and `image-webp` 0.2.4 |
| `skia` | PNG/JPEG/WebP and other Skia formats | Skia 0.99 target cache or documented source tools | | `skia` | PNG/JPEG/WebP and other Skia formats | Skia 0.99 target cache or documented source tools |
| `vorbis` | PCM to Ogg Vorbis | the locked `vorbis_rs` native build stack | | `vorbis` | PCM to Ogg Vorbis | the locked `vorbis_rs` native build stack |
| default WebRTC | native ICE/DTLS/SRTP/RTP/SCTP and Opus | system libopus 1.3 or newer | | default WebRTC | native ICE/DTLS/SRTP/RTP/SCTP and Opus | system libopus 1.3 or newer |

View File

@@ -19,7 +19,7 @@ they use those APIs directly.
| `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust | | `libremetaverse-types` | UUIDs, vectors, matrices, colors, cancellation, compatibility collections, and boundary types | Pure Rust |
| `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust | | `libremetaverse-structured-data` | LLSD/OSD XML, JSON, binary, and notation | Pure Rust |
| `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` | | `libremetaverse-imaging` | Managed images, TGA/DDS, codec traits | Optional `jpeg2000` or pure-Rust `rust-j2k` |
| `libremetaverse-imaging-skia` | Common raster formats through Skia | Optional `skia`; native Skia build/cache | | `libremetaverse-imaging-skia` | Common raster formats through native Skia or pure Rust | Optional `skia` native build/cache; optional `rust-skia` has no native prerequisites |
| `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust | | `libremetaverse-prim-mesher` | Legacy prim and sculpt geometry | Pure Rust |
| `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust | | `libremetaverse-rendering-simple` | Deterministic reference geometry | Pure Rust |
| `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust | | `libremetaverse-rendering-mesh-foundry` | Prim, terrain, sculpt, and mesh-asset rendering | Pure Rust |

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"schema": 1, "schema": 1,
"cargo_lock_sha256": "68988caa2c014bf2c67563df3dd0a8348d67eeb06559649798463fd8211fc0cb", "cargo_lock_sha256": "a547e07459a810d61571eca6775cebea871fd2874508a494350918fddccf8d6d",
"provenance_policy_sha256": "f95475a2284ab282e9432865d4a7ed103498a56ed5ec2393017bc312c77da890", "provenance_policy_sha256": "f95475a2284ab282e9432865d4a7ed103498a56ed5ec2393017bc312c77da890",
"upstream_repository": "https://github.com/cinderblocks/libremetaverse", "upstream_repository": "https://github.com/cinderblocks/libremetaverse",
"upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba", "upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba",
@@ -17,8 +17,8 @@
}, },
{ {
"path": ".gitea/workflows/ci.yml", "path": ".gitea/workflows/ci.yml",
"bytes": 6440, "bytes": 7567,
"sha256": "6241a445155b0691896af98310b18858e0c38537dca70c80c5c0bd9cf8f1c0ca" "sha256": "ae8f50aaf0ab6bd07cfff49d75ba7cd1f051bbd6c7599dc8f1d5f239db5b8561"
}, },
{ {
"path": ".gitea/workflows/release.yml", "path": ".gitea/workflows/release.yml",
@@ -42,8 +42,8 @@
}, },
{ {
"path": "Cargo.lock", "path": "Cargo.lock",
"bytes": 103842, "bytes": 117042,
"sha256": "68988caa2c014bf2c67563df3dd0a8348d67eeb06559649798463fd8211fc0cb" "sha256": "a547e07459a810d61571eca6775cebea871fd2874508a494350918fddccf8d6d"
}, },
{ {
"path": "Cargo.toml", "path": "Cargo.toml",
@@ -62,13 +62,13 @@
}, },
{ {
"path": "README.md", "path": "README.md",
"bytes": 35953, "bytes": 36207,
"sha256": "dea4a52f12e5885e2c53356c56b122c9bbf00382cbff21ff179e05b1d8714f05" "sha256": "c3f0ff673ad886783314122cf49162e59ccfa4d82a928023d930d60bf521c6b3"
}, },
{ {
"path": "RUSTREWRITE.md", "path": "RUSTREWRITE.md",
"bytes": 54472, "bytes": 55217,
"sha256": "b3bc471cb82e76cca0c3d251090634de3ed29e24ef91b01d3cc89723e31854be" "sha256": "487dce943fbe823ab5967bbedff506e1229b70508cb73d33fe6726ee11a2fc68"
}, },
{ {
"path": "api/API-COVERAGE.md", "path": "api/API-COVERAGE.md",
@@ -102,13 +102,13 @@
}, },
{ {
"path": "api/SEMVER-AUDIT.md", "path": "api/SEMVER-AUDIT.md",
"bytes": 5558, "bytes": 5608,
"sha256": "3c44b4b0ea579d975492fd5f9395a29fc597e43b126f8cbae4a0c14792232dd0" "sha256": "91c8dfc22d6f6d411fddc0c52ea02f43245c303eb0fe6d8bac2d46046d7a4ddd"
}, },
{ {
"path": "api/SEMVER-BASELINE.json", "path": "api/SEMVER-BASELINE.json",
"bytes": 5901, "bytes": 6087,
"sha256": "cd422369af58c6f60dbdda202ae4bb331f68ca3aacb5171839512100c027432e" "sha256": "41d77e564f53f7757c8ce7be56abc310d92edf094a0b9ee516944ad2389c0025"
}, },
{ {
"path": "api/SHIM-COVERAGE.md", "path": "api/SHIM-COVERAGE.md",
@@ -212,8 +212,8 @@
}, },
{ {
"path": "ci/ci-coverage.json", "path": "ci/ci-coverage.json",
"bytes": 5593, "bytes": 5694,
"sha256": "241e7bc687b1c2395885e4cd2a95c446e5d4847e3bf9a34d8cee3d76bcbc2903" "sha256": "40eed10def683f978c65deba5d2c09f463330fe1cd6357efafc875d86ed1afa3"
}, },
{ {
"path": "ci/concurrency-thresholds.json", "path": "ci/concurrency-thresholds.json",
@@ -222,8 +222,8 @@
}, },
{ {
"path": "ci/dependency-policy.json", "path": "ci/dependency-policy.json",
"bytes": 10851, "bytes": 12019,
"sha256": "781d497a7a0ddd53fb9158e74e1c5f967eaf3b97535596b42bf34fa38e59b482" "sha256": "5d35e2940c22da4c7a488a4ace7dc55ab7293a8ea713b6d71a62d1d0af2ac9cb"
}, },
{ {
"path": "ci/evidence/api-audit.json", "path": "ci/evidence/api-audit.json",
@@ -267,8 +267,8 @@
}, },
{ {
"path": "ci/release-matrix.json", "path": "ci/release-matrix.json",
"bytes": 8560, "bytes": 9206,
"sha256": "f6a641f66c023cd00d5d8b8692b5d6f4a8273fee8e8b5411cab4481de1b0f602" "sha256": "c87d86bd754b0c6d98971e0bae83b89bee33be1068f080b02ce897adbd22414e"
}, },
{ {
"path": "codegen/README.md", "path": "codegen/README.md",
@@ -332,13 +332,13 @@
}, },
{ {
"path": "crates/libremetaverse-imaging-skia/Cargo.toml", "path": "crates/libremetaverse-imaging-skia/Cargo.toml",
"bytes": 1440, "bytes": 1691,
"sha256": "0fb038f36280490487eb3f9e24176abd0f002504918b7bdac83ca319d92298e2" "sha256": "013ba0f82c12f654d3b7d90aea4088eb5af0f888fc810736821b9dc9eef20d44"
}, },
{ {
"path": "crates/libremetaverse-imaging-skia/README.md", "path": "crates/libremetaverse-imaging-skia/README.md",
"bytes": 3196, "bytes": 6542,
"sha256": "8e1475598b33502d8b7de9528cf925919232dd57ad5c84cce9a382a9b5a22dc2" "sha256": "9812fa5c5221497a9a3200564aa9d29425ec7f79ff0d2e3e86455c04cb1ff460"
}, },
{ {
"path": "crates/libremetaverse-imaging-skia/src/backend.rs", "path": "crates/libremetaverse-imaging-skia/src/backend.rs",
@@ -352,13 +352,123 @@
}, },
{ {
"path": "crates/libremetaverse-imaging-skia/src/lib.rs", "path": "crates/libremetaverse-imaging-skia/src/lib.rs",
"bytes": 811, "bytes": 1182,
"sha256": "a180686de4aa47d167e61d1198676dd0fa817490669090b177fc9f662069803d" "sha256": "8a8a49b2802b0012e0f4731c9a40bfa3c12784716ed2ddb17dad07ef8cd7a4f3"
}, },
{ {
"path": "crates/libremetaverse-imaging-skia/src/skia_codec.rs", "path": "crates/libremetaverse-imaging-skia/src/skia_codec.rs",
"bytes": 21674, "bytes": 52736,
"sha256": "59215a746a0842609ba8a4c86dca0c09c38043a0ea832416159a2d555c612827" "sha256": "373cb9c033dc1d2ea40cee19fc1212860882ac5f679b1952fd5b2ffd72b46809"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/README.md",
"bytes": 1852,
"sha256": "d84610151dceba3012549a2b001d49cc4679e0160713dbcafc11f748f7b19edb"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/alpha.webp.hex",
"bytes": 79,
"sha256": "f7e268437d69a569a27ee15fb3c1c21785afe27c4ad1f99fe63e362cf85af718"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/animated.webp.hex",
"bytes": 692,
"sha256": "90accfdcb7c2548162d641990f9c7dc5276b81164137467b274b9ab65e12ec5c"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/baseline.jpg.hex",
"bytes": 669,
"sha256": "c614e00c9ab1247ac4957dde699e62a82f61f1ea25bbd02a8f86dac27fc0f2c8"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/bgrx32.bmp.hex",
"bytes": 126,
"sha256": "9cdb2db245a0d4ea5289c36e8ef3c55e8a1d9933b2854760dfc2b3474fbcd299"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/bottom-up.bmp.hex",
"bytes": 502,
"sha256": "dc73f5816020b36c312b48d86afe4fac7bbe250ca4255f7a84b654cb610d7bf6"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/gray-alpha.png.hex",
"bytes": 257,
"sha256": "ba2bd4b3ec1286aecfd33141474a2855d697ee88888881396e517a9fbfc1781e"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/gray.png.hex",
"bytes": 281,
"sha256": "0122a5fb4c9ec3398056b74cf503492df06dea9b174abde61b05853eaf5d781f"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/interlaced-palette.png.hex",
"bytes": 237,
"sha256": "d3e76f043e8da40cce95cfb75ac1d9a34a5dc6633b87258f62c77830c9c3d311"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/lossy-red.webp.hex",
"bytes": 140,
"sha256": "5a31db73c00a7543b1831a8488c8c393c91042c35d10ee874ab2b91060524491"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/multi.ico.hex",
"bytes": 284,
"sha256": "75f085b4a18c72fbf2404bd16c5d3c6ffce16e6e8823c7d5dd12f4aa54e4f179"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/multibyte.wbmp.hex",
"bytes": 47,
"sha256": "fd1e97b193d36d1c4d5650b41081487d4ecacbfe0277d29e61a3c6dc9d9ce5e6"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/palette-offset.gif.hex",
"bytes": 337,
"sha256": "b53f9756afa1a8409ba800fadc51113664210eda9d16afbbf4465a509c230cbd"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/progressive.jpg.hex",
"bytes": 1566,
"sha256": "61244f0a07cdb9bf6908c8ec8f896e0a9a6385f65cffc5fb99407c772e3a4a5a"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/rgb-lossless.webp.hex",
"bytes": 74,
"sha256": "e8ab53c5c310c356bccaf7f2baa40190ff39add1c68307fa816e769cfb044f61"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/rgb.png.hex",
"bytes": 295,
"sha256": "1e5ec2ef16d17aa895651ae49a05e173fb631a8addf2400fa0376788f23e8656"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/rgb555.bmp.hex",
"bytes": 118,
"sha256": "3e3fd2851ae755c990d9a3d0b2b48d47e67e76a812782cc1107feebb9a2bfca5"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/rgba.png.hex",
"bytes": 375,
"sha256": "2f6af08ab8092a3705a7776727b2791be4a041ceacde64651993f4bb7e96eb04"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/small.wbmp.hex",
"bytes": 13,
"sha256": "00625cd5781673ec81928d2c08a73516ba820fb46cfe155779c321335068ecab"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/straight-alpha.png.hex",
"bytes": 362,
"sha256": "123f0a325427e2810c0544d3dc24e5943757a4f86f0e2892f10e7a974cc01156"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/two-by-two-bottom-up.bmp.hex",
"bytes": 143,
"sha256": "be53e0cd06441f5795b11d1b517d1542459952d0aa464ec5c7f9d443a42111df"
},
{
"path": "crates/libremetaverse-imaging-skia/tests/fixtures/two-by-two-top-down.bmp.hex",
"bytes": 143,
"sha256": "83bbf5c1c66e93c7aa413428d760230bca205eb4a1c4f3fc60f9fc63560c285f"
}, },
{ {
"path": "crates/libremetaverse-imaging/Cargo.toml", "path": "crates/libremetaverse-imaging/Cargo.toml",
@@ -702,8 +812,8 @@
}, },
{ {
"path": "crates/libremetaverse-types/Cargo.toml", "path": "crates/libremetaverse-types/Cargo.toml",
"bytes": 505, "bytes": 837,
"sha256": "674f227613ac9776f7ea09fb59a6504850830bd8bfc6e62e96ee691226635ae4" "sha256": "5751090243c10be962db766afa5bcede1074d6d63d3c715528f922bdc440e3e4"
}, },
{ {
"path": "crates/libremetaverse-types/benches/geometry.rs", "path": "crates/libremetaverse-types/benches/geometry.rs",
@@ -892,8 +1002,8 @@
}, },
{ {
"path": "crates/libremetaverse/README.md", "path": "crates/libremetaverse/README.md",
"bytes": 13059, "bytes": 13123,
"sha256": "871b0b94073aafa46235bba7d150c13d4c4900da5e4e2d82201604365d2c5c40" "sha256": "d0fa677f09dcc7e85f1b63e92cce6316e57103583916e166727d1e999173105a"
}, },
{ {
"path": "crates/libremetaverse/assets/avatar_lad.xml", "path": "crates/libremetaverse/assets/avatar_lad.xml",
@@ -1522,8 +1632,8 @@
}, },
{ {
"path": "deny.toml", "path": "deny.toml",
"bytes": 2738, "bytes": 3140,
"sha256": "2186fbadd60f11b672c47a916b89dcfc350c3ed922595d9ecf48346a0767f364" "sha256": "aa3d63fd17c6647be6c4b2d6467ca6282de588a8253a9d6a938a432287a8f2eb"
}, },
{ {
"path": "docs/agent-manager.md", "path": "docs/agent-manager.md",
@@ -1582,8 +1692,8 @@
}, },
{ {
"path": "docs/imaging-meshing.md", "path": "docs/imaging-meshing.md",
"bytes": 3678, "bytes": 3967,
"sha256": "c63e434d5181dc903c83f00c7649ee2040028c6d4bda698f57da8c1c3b1416c6" "sha256": "a220d1d907a0359d6a82168dee121721895950bd86d2b0ea25f6e998bf5744f3"
}, },
{ {
"path": "docs/inventory.md", "path": "docs/inventory.md",
@@ -1612,8 +1722,8 @@
}, },
{ {
"path": "docs/release-candidate.md", "path": "docs/release-candidate.md",
"bytes": 4327, "bytes": 4476,
"sha256": "20aa8c39de842ca1dee15808c44f1e71ee33441796b17d03feea8e14ef99165f" "sha256": "25e5a581235539b9145a9030b9f3af9d78f6a22cfc7587b1090f8c19b8653e26"
}, },
{ {
"path": "docs/release-ci-matrix.md", "path": "docs/release-ci-matrix.md",
@@ -1627,8 +1737,8 @@
}, },
{ {
"path": "docs/rust-api-guide.md", "path": "docs/rust-api-guide.md",
"bytes": 12058, "bytes": 12122,
"sha256": "db4d0ee7986eebe9434e5c51ca65a31ffc3b32323240d9dc62cbd931b21b21b1" "sha256": "25d2b333be7106c68bb9d6e1a8149d8abd333566c6705af7a9fb7e97ac7163e4"
}, },
{ {
"path": "docs/social.md", "path": "docs/social.md",
@@ -2517,8 +2627,8 @@
}, },
{ {
"path": "tools/ci-matrix/src/api_surface.rs", "path": "tools/ci-matrix/src/api_surface.rs",
"bytes": 48586, "bytes": 48993,
"sha256": "a82ac15da446f2f393ba267f8c7537d43cb4595a6be3e08f27d2f718e2aad4e8" "sha256": "2c4248ca2cb16c9ae84e25af823f5a45a3f7840519baa8f08d85c67212df8b4e"
}, },
{ {
"path": "tools/ci-matrix/src/artifact.rs", "path": "tools/ci-matrix/src/artifact.rs",
@@ -2542,8 +2652,8 @@
}, },
{ {
"path": "tools/ci-matrix/src/lib.rs", "path": "tools/ci-matrix/src/lib.rs",
"bytes": 21271, "bytes": 21338,
"sha256": "53f4286d600c78d7a3ea593e7d663854e722787573dc5bbf3e48d41e28ac8267" "sha256": "cd2d54c1ee8182843af43c271ff1592040c32de254d91bd146aeb822ae3a9a4e"
}, },
{ {
"path": "tools/ci-matrix/src/main.rs", "path": "tools/ci-matrix/src/main.rs",
@@ -2664,13 +2774,13 @@
"generated_release_files": [ "generated_release_files": [
{ {
"path": "release/DEPENDENCY-LICENSES.json", "path": "release/DEPENDENCY-LICENSES.json",
"bytes": 230084, "bytes": 259721,
"sha256": "64ee30790d4b3b643514098184bf15b2e9901fe177333dbd7950b4311a47292e" "sha256": "5f80b45ab18a45365673b55b52d68318bc31f602ce2f6559f573027cc446c58e"
}, },
{ {
"path": "release/THIRD-PARTY-NOTICES.md", "path": "release/THIRD-PARTY-NOTICES.md",
"bytes": 897381, "bytes": 980767,
"sha256": "47d10a98d94bfe09124cbbfcf57f91a34231286aaec71a3265372ee9006d3eab" "sha256": "c4d381944a9b57277963d2a5ee4e23a9a064c2ba93a676a49e023931b53a71d6"
}, },
{ {
"path": "release/NATIVE-LICENSES.md", "path": "release/NATIVE-LICENSES.md",

File diff suppressed because it is too large Load Diff

View File

@@ -932,6 +932,16 @@ fn validate_features(name: &str, actual: &BTreeMap<String, Vec<String>>) -> Resu
]), ]),
"libremetaverse-imaging-skia" => BTreeMap::from([ "libremetaverse-imaging-skia" => BTreeMap::from([
("default".to_owned(), Vec::new()), ("default".to_owned(), Vec::new()),
(
"rust-skia".to_owned(),
vec![
"dep:image-webp".to_owned(),
"dep:skia-rs-codec".to_owned(),
"skia-rs-codec/gif".to_owned(),
"skia-rs-codec/jpeg".to_owned(),
"skia-rs-codec/png".to_owned(),
],
),
("skia".to_owned(), vec!["dep:skia-safe".to_owned()]), ("skia".to_owned(), vec!["dep:skia-safe".to_owned()]),
]), ]),
"libremetaverse-voice-webrtc" => BTreeMap::from([ "libremetaverse-voice-webrtc" => BTreeMap::from([
@@ -1232,7 +1242,7 @@ The exact mapping, exported-marker, feature, and normalized Rust declaration has
- .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.\n\ - .NET events become `subscribe_*` calls returning an owned `Subscription`; callbacks are `Send + Sync` and dispatch outside internal locks.\n\
- `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.\n\ - `IDisposable` becomes explicit idempotent shutdown plus `Drop`; cancellation remains a typed `Error::Cancelled`.\n\
- Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.\n\ - Closed C# enums retain fixed discriminants and are SemVer-breaking to extend; flags retain unknown bits, while public Rust error enums are `non_exhaustive`.\n\
- Optional native backends remain feature-gated (`jpeg2000`, `skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline.\n\ - Optional native and pure-Rust backends remain feature-gated (`jpeg2000`, `rust-j2k`, `skia`, `rust-skia`, `vorbis`, `real-audio`); default and empty-default sets are part of the baseline.\n\
- Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.\n\ - Capability HTTP injection uses project-owned `HttpMessageHandler`, `CapsHttpLimits`, and `with_native_transport`; the internal `reqwest` client is not part of the public surface.\n\
- External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.\n\n\ - External C# signature types map to core/std, adopted cross-platform crates, or project-owned boundary types; no third-party crate path leaks through mapped signatures or public Rust declarations.\n\n\
## Compile consumers\n\n\ ## Compile consumers\n\n\

View File

@@ -38,7 +38,7 @@ const REQUIRED_PROFILES: [&str; 7] = [
"windows-stable-portable", "windows-stable-portable",
"macos-stable-portable", "macos-stable-portable",
]; ];
const REQUIRED_FEATURE_SETS: [&str; 12] = [ const REQUIRED_FEATURE_SETS: [&str; 13] = [
"all-features", "all-features",
"dds-bc67", "dds-bc67",
"default", "default",
@@ -48,11 +48,12 @@ const REQUIRED_FEATURE_SETS: [&str; 12] = [
"no-default-features", "no-default-features",
"real-audio", "real-audio",
"rust-j2k", "rust-j2k",
"rust-skia",
"skia", "skia",
"tests", "tests",
"vorbis", "vorbis",
]; ];
const REQUIRED_FEATURES: [(&str, &str); 8] = [ const REQUIRED_FEATURES: [(&str, &str); 9] = [
("libremetaverse", "dds-bc67"), ("libremetaverse", "dds-bc67"),
("libremetaverse", "jpeg2000"), ("libremetaverse", "jpeg2000"),
("libremetaverse", "rust-j2k"), ("libremetaverse", "rust-j2k"),
@@ -60,6 +61,7 @@ const REQUIRED_FEATURES: [(&str, &str); 8] = [
("libremetaverse-imaging", "jpeg2000"), ("libremetaverse-imaging", "jpeg2000"),
("libremetaverse-imaging", "rust-j2k"), ("libremetaverse-imaging", "rust-j2k"),
("libremetaverse-imaging-skia", "skia"), ("libremetaverse-imaging-skia", "skia"),
("libremetaverse-imaging-skia", "rust-skia"),
("libremetaverse-voice-webrtc", "real-audio"), ("libremetaverse-voice-webrtc", "real-audio"),
]; ];