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
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:
name: required
runs-on: ubuntu-latest

569
Cargo.lock generated
View File

@@ -52,6 +52,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "alsa"
version = "0.11.0"
@@ -59,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d"
dependencies = [
"alsa-sys",
"bitflags",
"bitflags 2.13.1",
"cfg-if",
"libc",
]
@@ -143,6 +149,15 @@ dependencies = [
"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]]
name = "arrayvec"
version = "0.7.8"
@@ -254,7 +269,7 @@ version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"cexpr",
"clang-sys",
"itertools",
@@ -263,11 +278,26 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"rustc-hash 2.1.3",
"shlex 1.3.0",
"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]]
name = "bit-vec"
version = "0.9.1"
@@ -277,6 +307,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.13.1"
@@ -316,6 +352,38 @@ version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "bytes"
version = "1.12.1"
@@ -479,6 +547,22 @@ dependencies = [
"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]]
name = "colorchoice"
version = "1.0.5"
@@ -529,7 +613,7 @@ version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"libc",
"objc2-audio-toolbox",
"objc2-core-audio",
@@ -823,7 +907,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
]
@@ -844,6 +928,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ecdsa"
version = "0.16.9"
@@ -907,6 +1000,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "fearless_simd"
version = "0.4.1"
@@ -961,6 +1063,18 @@ dependencies = [
"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]]
name = "form_urlencoded"
version = "1.2.2"
@@ -1047,6 +1161,51 @@ dependencies = [
"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]]
name = "getrandom"
version = "0.2.17"
@@ -1096,6 +1255,22 @@ dependencies = [
"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]]
name = "glob"
version = "0.3.4"
@@ -1124,18 +1299,54 @@ dependencies = [
"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]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hexf-parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "hkdf"
version = "0.12.4"
@@ -1266,6 +1477,52 @@ dependencies = [
"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]]
name = "icu_collections"
version = "2.2.0"
@@ -1369,6 +1626,16 @@ dependencies = [
"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]]
name = "indexmap"
version = "2.14.0"
@@ -1376,7 +1643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.17.1",
]
[[package]]
@@ -1551,6 +1818,21 @@ dependencies = [
"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]]
name = "js-sys"
version = "0.3.104"
@@ -1660,8 +1942,10 @@ dependencies = [
name = "libremetaverse-imaging-skia"
version = "0.0.1"
dependencies = [
"image-webp",
"libremetaverse-imaging",
"libremetaverse-types",
"skia-rs-codec",
"skia-safe",
]
@@ -1821,6 +2105,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "log"
version = "0.4.33"
@@ -1954,13 +2247,34 @@ dependencies = [
"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]]
name = "ndk"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"jni-sys 0.3.1",
"log",
"ndk-sys",
@@ -1989,7 +2303,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"cfg-if",
"cfg_aliases 0.1.1",
"libc",
@@ -2002,7 +2316,7 @@ version = "0.31.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"cfg-if",
"cfg_aliases 0.2.2",
"libc",
@@ -2070,6 +2384,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@@ -2109,7 +2424,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"libc",
"objc2",
"objc2-core-audio",
@@ -2124,7 +2439,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
"objc2-foundation",
]
@@ -2135,7 +2450,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
"objc2-foundation",
]
@@ -2159,7 +2474,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
]
@@ -2179,7 +2494,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"block2",
"dispatch2",
"libc",
@@ -2192,7 +2507,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"dispatch2",
"objc2",
"objc2-core-foundation",
@@ -2225,7 +2540,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
"objc2-core-foundation",
"objc2-core-graphics",
@@ -2243,7 +2558,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"block2",
"libc",
"objc2",
@@ -2256,7 +2571,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
"objc2-core-foundation",
]
@@ -2267,7 +2582,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"objc2",
"objc2-core-foundation",
"objc2-foundation",
@@ -2279,7 +2594,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"block2",
"objc2",
"objc2-cloud-kit",
@@ -2385,6 +2700,29 @@ dependencies = [
"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]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -2428,6 +2766,19 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "poly1305"
version = "0.8.0"
@@ -2518,6 +2869,12 @@ version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quinn"
version = "0.11.11"
@@ -2529,7 +2886,7 @@ dependencies = [
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustc-hash 2.1.3",
"rustls",
"socket2",
"thiserror 2.0.20",
@@ -2551,7 +2908,7 @@ dependencies = [
"rand 0.10.2",
"rand_pcg",
"ring",
"rustc-hash",
"rustc-hash 2.1.3",
"rustls",
"rustls-pki-types",
"slab",
@@ -2693,6 +3050,15 @@ dependencies = [
"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]]
name = "regex"
version = "1.13.1"
@@ -2784,6 +3150,12 @@ dependencies = [
"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]]
name = "roxmltree"
version = "0.21.1"
@@ -2793,6 +3165,23 @@ dependencies = [
"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]]
name = "rustc-hash"
version = "2.1.3"
@@ -2823,7 +3212,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"errno",
"libc",
"linux-raw-sys",
@@ -2929,6 +3318,12 @@ dependencies = [
"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]]
name = "scroll"
version = "0.13.0"
@@ -2959,7 +3354,7 @@ dependencies = [
"crc",
"log",
"rand 0.9.5",
"rustc-hash",
"rustc-hash 2.1.3",
"slab",
"thiserror 2.0.20",
]
@@ -2984,7 +3379,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -3104,6 +3499,12 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "sif-itree"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7f45b8998ced5134fb1d75732c77842a3e888f19c1ff98481822e8fbfbf930b"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
@@ -3163,6 +3564,60 @@ dependencies = [
"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]]
name = "skia-safe"
version = "0.99.0"
@@ -3170,7 +3625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f512ac418a64194842dd05566320805dad1c957c521039db2486fd6368865bc"
dependencies = [
"base64",
"bitflags",
"bitflags 2.13.1",
"percent-encoding",
"skia-bindings",
"skia-svg-macros",
@@ -3209,6 +3664,27 @@ dependencies = [
"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]]
name = "spki"
version = "0.7.3"
@@ -3349,6 +3825,15 @@ dependencies = [
"xattr",
]
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.69"
@@ -3566,7 +4051,7 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags",
"bitflags 2.13.1",
"bytes",
"futures-util",
"http",
@@ -3645,6 +4130,18 @@ version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "universal-hash"
version = "0.5.1"
@@ -3699,9 +4196,19 @@ checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239"
dependencies = [
"getrandom 0.4.3",
"js-sys",
"uuid-rng-internal",
"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]]
name = "vcpkg"
version = "0.2.15"
@@ -3859,6 +4366,12 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "widestring"
version = "1.2.1"
@@ -4144,7 +4657,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282"
dependencies = [
"bit-vec",
"bit-vec 0.9.1",
"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 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 |
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 --no-default-features --features rust-j2k
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
```

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. |
| 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. |
| 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. |
| 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. |
@@ -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 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 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. |
| 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. |
@@ -444,7 +445,7 @@ The current adopted set is:
| Async and HTTP | `tokio`, `futures-channel`, `futures-util`, `reqwest` |
| 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` |
| 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` |
| 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`
Implement `SkiaTextureCodec` as an optional adapter with `skia-safe`. Convert at
the boundary to/from `ManagedImage`; never expose `skia_safe` types in the core
trait. CI must cover macOS, Linux, and Windows or explicitly document unsupported
targets. Validate native binary provenance and packaging.
Implement `SkiaTextureCodec` as an optional adapter with `skia-safe`, plus an
independently selectable `RustSkiaTextureCodec` using `skia-rs-codec` and
`image-webp`. Convert at the boundary to/from `ManagedImage`; never expose
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`

View File

@@ -24,7 +24,7 @@ The compatibility source is pinned at [`2aa70bb68513b39795da5d13c88f31b86e85a3ba
| `libremetaverse-types` | 45 | 942 | 887 | none | `532cdfc175a60cb6ab109b2c43cfdda1d89cf0aadffccbcd7c9b18e1a3215bee` |
| `libremetaverse-structured-data` | 16 | 295 | 304 | none | `b1c2ca9a683efe92ea22293384189f9e7c9a1926860893ab78c41829e641a8c3` |
| `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-rendering-simple` | 1 | 6 | 12 | none | `4dc284ea0abb16de63494435758c8b7c5eeb57a80d2e32a141e488e5861f2fad` |
| `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.
- `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`.
- 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.
- 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",
"member_mapping_sha256": "cd99420643c665ea393dd59418c33fb00f97f7030e71540c00b2b27470abb1d4",
"exported_mapping_sha256": "5b8a502ce229d26732744f2b44c029fc4a86b403966dc2f9b4a1c913a7b242b9",
"rust_declaration_sha256": "687d3d29bb0e79651881ffdda962be21df8716a4d96417175611541d0fe27a8e",
"rust_declaration_sha256": "b2ba1ccf9293ac94164e593546891a2a122b1e5997ee4ecc2de86ef5eb0d866e",
"crates": [
{
"name": "libremetaverse",
@@ -87,14 +87,21 @@
"version": "0.0.1",
"features": {
"default": [],
"rust-skia": [
"dep:image-webp",
"dep:skia-rs-codec",
"skia-rs-codec/gif",
"skia-rs-codec/jpeg",
"skia-rs-codec/png"
],
"skia": [
"dep:skia-safe"
]
},
"mapped_types": 1,
"mapped_members": 3,
"public_declarations": 13,
"declaration_sha256": "a6a1445725410a036204496ad4953c84dcfea1e6a00390ce4e10d12d36f2876b"
"public_declarations": 17,
"declaration_sha256": "855a181b77413a1e5f0094b1581971fd5e0a4d93fadb5f83d54f82a7ba8ee71f"
},
{
"name": "libremetaverse-prim-mesher",

View File

@@ -13,6 +13,7 @@
"minimal-features",
"jpeg2000",
"rust-j2k",
"rust-skia",
"skia-binary",
"program-offline-smoke",
"clippy",
@@ -104,8 +105,8 @@
"workflow": "skia.yml",
"responsibility": "Default and Skia feature tests plus package listing",
"destination": "required",
"checks": ["skia-binary", "workspace-tests"],
"rationale": "The required gate proves the exact ARM64 GNU archive is installed and forbids source compilation."
"checks": ["rust-skia", "skia-binary", "workspace-tests"],
"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",

View File

@@ -2,7 +2,7 @@
"schema": 1,
"msrv": "1.96.0",
"current": "stable",
"reviewed_on": "2026-08-12",
"reviewed_on": "2026-08-13",
"review_by": "2026-11-11",
"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`" },
@@ -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": "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": "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": "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`" },
@@ -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": "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-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": "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`" },
@@ -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`" }
],
"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": "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." },
@@ -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": "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": "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": "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": "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_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": "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." },

View File

@@ -42,12 +42,12 @@
"execution": "native",
"toolchain": "1.96.0",
"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"],
"commands": [
{
"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",
@@ -106,7 +106,7 @@
"execution": "native",
"toolchain": "stable",
"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"],
"commands": [
{
@@ -125,10 +125,18 @@
"label": "Test OpenJPEG and pure-Rust JPEG 2000 interoperability",
"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",
"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",
"args": ["test", "-p", "libremetaverse", "--no-default-features", "--features", "vorbis", "--lib", "--locked", "-j", "1", "--", "asset_pipeline_semantics"]
@@ -170,12 +178,12 @@
"execution": "cross",
"toolchain": "stable",
"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": [],
"commands": [
{
"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",
"toolchain": "stable",
"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": [],
"commands": [
{
"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]
default = []
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]
libremetaverse-imaging = { version = "0.0.1", path = "../libremetaverse-imaging" }
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
# 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
The default feature set has no Skia dependency. `SkiaTextureCodec::decode`
returns a typed `InvalidOperation` error in that configuration, while conversion
from the checked project-owned `backend::SKBitmap` remains available for tests
and callers that already own decoded pixels.
The default feature set has no Skia dependency. `SkiaTextureCodec::decode` and
`RustSkiaTextureCodec::decode` return a typed `InvalidOperation` error when
their respective feature is disabled, while conversion from the checked
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:
@@ -17,7 +50,7 @@ Enable native decoding with:
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
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
@@ -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
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
`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.
Products that redistribute the resulting native artifacts must preserve the
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`.
//!
//! Enable `skia` for bounded `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and
//! `WebP` decoding.
//! Enable `skia` or the fully native-Rust `rust-skia` alternative for bounded
//! `BMP`, `GIF`, `ICO`, `JPEG`, `PNG`, `WBMP`, and `WebP` decoding.
//! The default build remains inert. See the crate README for native cache,
//! source-build, licensing, and cross-platform prerequisite details.
@@ -15,6 +15,7 @@ mod skia_codec;
pub use generated::*;
pub use libremetaverse_imaging as imaging;
pub use libremetaverse_types::Error;
pub use skia_codec::RustSkiaTextureCodec;
impl libremetaverse_imaging::ITextureCodec for SkiaTextureCodec {
fn decode(
@@ -24,3 +25,12 @@ impl libremetaverse_imaging::ITextureCodec for SkiaTextureCodec {
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::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::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_types::compat::ReadWrite;
#[cfg(feature = "skia")]
#[cfg(any(feature = "rust-skia", feature = "skia"))]
use std::io::Read;
/// Optional Skia-backed decoder and format-neutral bitmap converter.
#[derive(Clone, Copy, Debug, Default)]
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 {
/// Creates the stateless codec adapter.
///
@@ -33,17 +40,7 @@ impl SkiaTextureCodec {
pub fn decode(&self, mut stream: Box<dyn ReadWrite + Send>) -> Result<ManagedImage, Error> {
#[cfg(feature = "skia")]
{
let mut encoded = Vec::new();
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);
}
let encoded = read_bounded(&mut stream)?;
decode_with_skia(&encoded)
}
#[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#
// color switch auditable and prevents subtly different indexing paths.
#[allow(clippy::too_many_lines)]
@@ -238,6 +284,404 @@ fn normalize_10bit_alpha(
(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")]
fn decode_with_skia(encoded: &[u8]) -> Result<ManagedImage, Error> {
use skia_safe::Data;
@@ -528,6 +972,394 @@ mod tests {
SkiaTextureCodec.decode(Box::new(std::io::Cursor::new(Vec::new()))),
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")]

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"
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]
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-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-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-rendering-simple` | Deterministic reference geometry | 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
deny = []
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 = "cfg_aliases@0.1.1", reason = "Approved older nix build-helper 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 = "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 = "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 = "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.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 = "shlex@1.3.0", reason = "Approved Skia bindgen shell parser compatibility 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 |
| JPEG 2000 | disabled | `libremetaverse-imaging/jpeg2000` | OpenJPEG 2.5.x |
| 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 |
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
pure-Rust `j2k` graph; enabling both features keeps both explicit codec types
available for interoperability testing. Skia is likewise an optional,
target-specific dependency. The
consolidated required Gitea gate exercises both optional features on
`ubuntu-latest` while its minimal-feature check proves that no native codec is
needed for the owned Rust and translated compatibility tests.
target-specific dependency. The consolidated required Gitea gate exercises the
native feature, while a dedicated `ubuntu-latest` job tests `rust-skia`, audits
its normal/build graph without installing native codec tools, and compiles the
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

View File

@@ -24,6 +24,7 @@ Optional boundaries are explicit:
| --- | --- | --- |
| `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-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 |
| `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 |

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-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-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-rendering-simple` | Deterministic reference geometry | 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,
"cargo_lock_sha256": "68988caa2c014bf2c67563df3dd0a8348d67eeb06559649798463fd8211fc0cb",
"cargo_lock_sha256": "a547e07459a810d61571eca6775cebea871fd2874508a494350918fddccf8d6d",
"provenance_policy_sha256": "f95475a2284ab282e9432865d4a7ed103498a56ed5ec2393017bc312c77da890",
"upstream_repository": "https://github.com/cinderblocks/libremetaverse",
"upstream_commit": "2aa70bb68513b39795da5d13c88f31b86e85a3ba",
@@ -17,8 +17,8 @@
},
{
"path": ".gitea/workflows/ci.yml",
"bytes": 6440,
"sha256": "6241a445155b0691896af98310b18858e0c38537dca70c80c5c0bd9cf8f1c0ca"
"bytes": 7567,
"sha256": "ae8f50aaf0ab6bd07cfff49d75ba7cd1f051bbd6c7599dc8f1d5f239db5b8561"
},
{
"path": ".gitea/workflows/release.yml",
@@ -42,8 +42,8 @@
},
{
"path": "Cargo.lock",
"bytes": 103842,
"sha256": "68988caa2c014bf2c67563df3dd0a8348d67eeb06559649798463fd8211fc0cb"
"bytes": 117042,
"sha256": "a547e07459a810d61571eca6775cebea871fd2874508a494350918fddccf8d6d"
},
{
"path": "Cargo.toml",
@@ -62,13 +62,13 @@
},
{
"path": "README.md",
"bytes": 35953,
"sha256": "dea4a52f12e5885e2c53356c56b122c9bbf00382cbff21ff179e05b1d8714f05"
"bytes": 36207,
"sha256": "c3f0ff673ad886783314122cf49162e59ccfa4d82a928023d930d60bf521c6b3"
},
{
"path": "RUSTREWRITE.md",
"bytes": 54472,
"sha256": "b3bc471cb82e76cca0c3d251090634de3ed29e24ef91b01d3cc89723e31854be"
"bytes": 55217,
"sha256": "487dce943fbe823ab5967bbedff506e1229b70508cb73d33fe6726ee11a2fc68"
},
{
"path": "api/API-COVERAGE.md",
@@ -102,13 +102,13 @@
},
{
"path": "api/SEMVER-AUDIT.md",
"bytes": 5558,
"sha256": "3c44b4b0ea579d975492fd5f9395a29fc597e43b126f8cbae4a0c14792232dd0"
"bytes": 5608,
"sha256": "91c8dfc22d6f6d411fddc0c52ea02f43245c303eb0fe6d8bac2d46046d7a4ddd"
},
{
"path": "api/SEMVER-BASELINE.json",
"bytes": 5901,
"sha256": "cd422369af58c6f60dbdda202ae4bb331f68ca3aacb5171839512100c027432e"
"bytes": 6087,
"sha256": "41d77e564f53f7757c8ce7be56abc310d92edf094a0b9ee516944ad2389c0025"
},
{
"path": "api/SHIM-COVERAGE.md",
@@ -212,8 +212,8 @@
},
{
"path": "ci/ci-coverage.json",
"bytes": 5593,
"sha256": "241e7bc687b1c2395885e4cd2a95c446e5d4847e3bf9a34d8cee3d76bcbc2903"
"bytes": 5694,
"sha256": "40eed10def683f978c65deba5d2c09f463330fe1cd6357efafc875d86ed1afa3"
},
{
"path": "ci/concurrency-thresholds.json",
@@ -222,8 +222,8 @@
},
{
"path": "ci/dependency-policy.json",
"bytes": 10851,
"sha256": "781d497a7a0ddd53fb9158e74e1c5f967eaf3b97535596b42bf34fa38e59b482"
"bytes": 12019,
"sha256": "5d35e2940c22da4c7a488a4ace7dc55ab7293a8ea713b6d71a62d1d0af2ac9cb"
},
{
"path": "ci/evidence/api-audit.json",
@@ -267,8 +267,8 @@
},
{
"path": "ci/release-matrix.json",
"bytes": 8560,
"sha256": "f6a641f66c023cd00d5d8b8692b5d6f4a8273fee8e8b5411cab4481de1b0f602"
"bytes": 9206,
"sha256": "c87d86bd754b0c6d98971e0bae83b89bee33be1068f080b02ce897adbd22414e"
},
{
"path": "codegen/README.md",
@@ -332,13 +332,13 @@
},
{
"path": "crates/libremetaverse-imaging-skia/Cargo.toml",
"bytes": 1440,
"sha256": "0fb038f36280490487eb3f9e24176abd0f002504918b7bdac83ca319d92298e2"
"bytes": 1691,
"sha256": "013ba0f82c12f654d3b7d90aea4088eb5af0f888fc810736821b9dc9eef20d44"
},
{
"path": "crates/libremetaverse-imaging-skia/README.md",
"bytes": 3196,
"sha256": "8e1475598b33502d8b7de9528cf925919232dd57ad5c84cce9a382a9b5a22dc2"
"bytes": 6542,
"sha256": "9812fa5c5221497a9a3200564aa9d29425ec7f79ff0d2e3e86455c04cb1ff460"
},
{
"path": "crates/libremetaverse-imaging-skia/src/backend.rs",
@@ -352,13 +352,123 @@
},
{
"path": "crates/libremetaverse-imaging-skia/src/lib.rs",
"bytes": 811,
"sha256": "a180686de4aa47d167e61d1198676dd0fa817490669090b177fc9f662069803d"
"bytes": 1182,
"sha256": "8a8a49b2802b0012e0f4731c9a40bfa3c12784716ed2ddb17dad07ef8cd7a4f3"
},
{
"path": "crates/libremetaverse-imaging-skia/src/skia_codec.rs",
"bytes": 21674,
"sha256": "59215a746a0842609ba8a4c86dca0c09c38043a0ea832416159a2d555c612827"
"bytes": 52736,
"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",
@@ -702,8 +812,8 @@
},
{
"path": "crates/libremetaverse-types/Cargo.toml",
"bytes": 505,
"sha256": "674f227613ac9776f7ea09fb59a6504850830bd8bfc6e62e96ee691226635ae4"
"bytes": 837,
"sha256": "5751090243c10be962db766afa5bcede1074d6d63d3c715528f922bdc440e3e4"
},
{
"path": "crates/libremetaverse-types/benches/geometry.rs",
@@ -892,8 +1002,8 @@
},
{
"path": "crates/libremetaverse/README.md",
"bytes": 13059,
"sha256": "871b0b94073aafa46235bba7d150c13d4c4900da5e4e2d82201604365d2c5c40"
"bytes": 13123,
"sha256": "d0fa677f09dcc7e85f1b63e92cce6316e57103583916e166727d1e999173105a"
},
{
"path": "crates/libremetaverse/assets/avatar_lad.xml",
@@ -1522,8 +1632,8 @@
},
{
"path": "deny.toml",
"bytes": 2738,
"sha256": "2186fbadd60f11b672c47a916b89dcfc350c3ed922595d9ecf48346a0767f364"
"bytes": 3140,
"sha256": "aa3d63fd17c6647be6c4b2d6467ca6282de588a8253a9d6a938a432287a8f2eb"
},
{
"path": "docs/agent-manager.md",
@@ -1582,8 +1692,8 @@
},
{
"path": "docs/imaging-meshing.md",
"bytes": 3678,
"sha256": "c63e434d5181dc903c83f00c7649ee2040028c6d4bda698f57da8c1c3b1416c6"
"bytes": 3967,
"sha256": "a220d1d907a0359d6a82168dee121721895950bd86d2b0ea25f6e998bf5744f3"
},
{
"path": "docs/inventory.md",
@@ -1612,8 +1722,8 @@
},
{
"path": "docs/release-candidate.md",
"bytes": 4327,
"sha256": "20aa8c39de842ca1dee15808c44f1e71ee33441796b17d03feea8e14ef99165f"
"bytes": 4476,
"sha256": "25e5a581235539b9145a9030b9f3af9d78f6a22cfc7587b1090f8c19b8653e26"
},
{
"path": "docs/release-ci-matrix.md",
@@ -1627,8 +1737,8 @@
},
{
"path": "docs/rust-api-guide.md",
"bytes": 12058,
"sha256": "db4d0ee7986eebe9434e5c51ca65a31ffc3b32323240d9dc62cbd931b21b21b1"
"bytes": 12122,
"sha256": "25d2b333be7106c68bb9d6e1a8149d8abd333566c6705af7a9fb7e97ac7163e4"
},
{
"path": "docs/social.md",
@@ -2517,8 +2627,8 @@
},
{
"path": "tools/ci-matrix/src/api_surface.rs",
"bytes": 48586,
"sha256": "a82ac15da446f2f393ba267f8c7537d43cb4595a6be3e08f27d2f718e2aad4e8"
"bytes": 48993,
"sha256": "2c4248ca2cb16c9ae84e25af823f5a45a3f7840519baa8f08d85c67212df8b4e"
},
{
"path": "tools/ci-matrix/src/artifact.rs",
@@ -2542,8 +2652,8 @@
},
{
"path": "tools/ci-matrix/src/lib.rs",
"bytes": 21271,
"sha256": "53f4286d600c78d7a3ea593e7d663854e722787573dc5bbf3e48d41e28ac8267"
"bytes": 21338,
"sha256": "cd2d54c1ee8182843af43c271ff1592040c32de254d91bd146aeb822ae3a9a4e"
},
{
"path": "tools/ci-matrix/src/main.rs",
@@ -2664,13 +2774,13 @@
"generated_release_files": [
{
"path": "release/DEPENDENCY-LICENSES.json",
"bytes": 230084,
"sha256": "64ee30790d4b3b643514098184bf15b2e9901fe177333dbd7950b4311a47292e"
"bytes": 259721,
"sha256": "5f80b45ab18a45365673b55b52d68318bc31f602ce2f6559f573027cc446c58e"
},
{
"path": "release/THIRD-PARTY-NOTICES.md",
"bytes": 897381,
"sha256": "47d10a98d94bfe09124cbbfcf57f91a34231286aaec71a3265372ee9006d3eab"
"bytes": 980767,
"sha256": "c4d381944a9b57277963d2a5ee4e23a9a064c2ba93a676a49e023931b53a71d6"
},
{
"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([
("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()]),
]),
"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\
- `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\
- 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\
- 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\

View File

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