Imaging extension: add optional pure-Rust Skia backend #112

Closed
opened 2026-08-09 12:49:05 +00:00 by hugo · 1 comment
Owner

Objective

Add a complete optional pure-Rust Skia-compatible image backend behind a Cargo feature named rust-skia. Use skia-rs-codec from the current compatible 0.3 release line as the primary codec implementation. The backend must implement the same bounded ITextureCodec behavior and the same C# LibreMetaverse.Imaging.Skia.SkiaTextureCodec decode semantics as the existing native rust-skia/Google Skia backend; it must not be a stub, a subprocess, or a wrapper that silently links C/C++ libraries.

The existing C# source in LibreMetaverse.Imaging.Skia/SkiaTextureCodec.cs and the translated/golden Rust tests are the behavioral reference. Treat the C# as correct, but do not run or depend on .NET code.

Current architecture

  • crates/libremetaverse-imaging-skia has no default features.
  • Its existing skia feature enables optional skia-safe 0.99.0. skia-safe is a safe Rust API over skia-bindings and native Google Skia C++ binaries; retain this backend unchanged.
  • crates/libremetaverse-imaging-skia/src/skia_codec.rs owns bounded stream buffering, native skia_safe::codec::Codec adaptation, color/alpha mapping, premultiplied-alpha normalization, and conversion into ManagedImage.
  • SkiaTextureCodec is the mapped public C# type and currently denotes the existing native Google Skia backend.
  • The supported compatibility formats are BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP.
  • The workspace MSRV is Rust 1.96. skia-rs-codec 0.3.0 declares Rust 1.85 and is compatible with that floor; lock and audit the exact selected version.

Dependency caveat that must be handled

skia-rs-codec 0.3.0 calls itself pure Rust, but its default webp feature depends on webp 0.3, which in turn links libwebp-sys. Therefore:

  • Depend on skia-rs-codec with default-features = false and enable only audited pure-Rust codec features.
  • Do not enable skia-rs-codec/webp while it transitively uses libwebp-sys.
  • Preserve WebP compatibility through an audited pure-Rust implementation. Prefer image-webp from its current compatible 0.2 release line, or contribute/upgrade to an upstream skia-rs-codec release that has removed the native dependency before completing this issue.
  • The completed rust-skia dependency graph must contain no -sys codec crate, CMake/cc-driven native codec, system package lookup, downloaded binary, or C/C++ source build.

Feature and API design

  • Add optional skia-rs-codec and any required audited pure-Rust WebP dependency to libremetaverse-imaging-skia.
  • Add rust-skia = [...] to that crate. The feature must not enable skia, skia-safe, skia-bindings, Google Skia, or native libwebp.
  • Preserve the existing skia feature and SkiaTextureCodec behavior without a breaking rename or backend substitution.
  • Expose the Rust backend explicitly as RustSkiaTextureCodec (or a comparably clear public name), implementing ITextureCodec and the same bounded decode contract.
  • Move shared stream limits, decoded-dimension checks, typed error mapping, channel mapping, alpha normalization, and ManagedImage conversion into backend-neutral code. Do not fork two subtly different copies of the compatibility rules.
  • Enabling skia and rust-skia together must compile and expose both backends. --all-features must remain valid. Do not use feature ordering, mutually exclusive compile errors, or a conditional alias that silently changes what SkiaTextureCodec means.
  • The default feature set must remain native-dependency-free and retain its existing typed feature-disabled behavior.

Required decode behavior

  • Decode BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP from an arbitrary ReadWrite + Send stream.
  • Match native SkiaTextureCodec dimensions, first-frame behavior for animated inputs, logical canvas handling, component ordering, and alpha presence.
  • Match the C# channel rules for gray, alpha, RGB, RGBA, BGRA-compatible, packed/fallback, opaque, straight-alpha, and premultiplied-alpha inputs. Colors returned through ManagedImage must be straight/unpremultiplied.
  • Preserve row orientation and pixel ordering for top-down/bottom-up BMP and multi-entry ICO inputs.
  • Support baseline and progressive JPEG, interlaced and non-interlaced PNG, palette/transparency inputs, lossless and lossy WebP, and the BMP/GIF/ICO/WBMP variants supported by the existing backend.
  • Return existing typed Error variants for empty, malformed, truncated, unsupported, or oversized data. Never panic on decoder-controlled input.
  • If skia-rs-codec lacks a behavior needed for parity, implement a bounded adapter or contribute the missing behavior upstream. Do not accept and ignore a compatibility requirement.

Resource safety

  • Read at most DEFAULT_MAX_ENCODED_BYTES + 1 and reject oversized input before passing it to a decoder.
  • Inspect and validate dimensions before full decode wherever the upstream API permits. Enforce nonzero width/height, checked pixel multiplication, DEFAULT_MAX_PIXELS, row-byte bounds, output length, component count, and every integer conversion before allocation or indexing.
  • A malicious dimension header, decompression bomb, oversized animation canvas/frame, or crafted stride must fail before an unbounded allocation.
  • Keep all project crates under the workspace unsafe_code = "forbid" policy. Audit dependency unsafe code and fuzz-sensitive parsers rather than copying unchecked decoder logic into this repository.
  • The same Rust implementation must build on Linux, Windows, macOS, and supported WASM targets without platform-specific APIs. Gitea workflows must remain ubuntu-latest only because this Gitea installation has no Windows or macOS runners.

Tests

Refactor the codec contract tests so the same fixture/assertion table runs independently against native SkiaTextureCodec and RustSkiaTextureCodec, with backend-specific tests only where unavoidable. Add audited deterministic fixtures for at least:

  • PNG: gray, RGB, RGBA, palette transparency, interlacing, and alpha values that expose premultiplication errors.
  • JPEG: baseline and progressive, grayscale and RGB, with stable tolerance-based pixel assertions.
  • WebP: lossless exact RGB/RGBA, lossy tolerance checks, transparency, and first-frame animated behavior if supported by native Skia.
  • BMP: 16/24/32-bit layouts, row padding, and top-down/bottom-up orientation.
  • GIF: palette transparency, logical-screen/frame offsets, and deterministic first-frame composition.
  • ICO: multiple entries and alpha/mask behavior.
  • WBMP: one-bit samples and multi-byte dimensions.
  • Empty, unknown-format, truncated-at-several-boundaries, invalid-dimension, excessive-pixel, oversized-input, malformed palette/table, and decompression-bomb failures.

For lossless inputs, require identical ManagedImage dimensions, channel flags, and planes across the native and Rust backends. For lossy inputs, use explicit per-channel tolerances and invariant checks rather than byte-identical compressed or decoded output. Fixtures must be checked in with provenance and must not be generated at test time by C#, native Skia tools, ImageMagick, network services, or another subprocess.

Add concurrent decode/drop tests proving the adapter is stateless, deterministic, bounded, and does not retain streams or decoded buffers after return.

Validation gates

The implementation is complete only when all of the following pass:

cargo check --workspace --all-targets --locked -j 1
cargo test -p libremetaverse-imaging-skia --no-default-features
cargo test -p libremetaverse-imaging-skia --no-default-features --features rust-skia
cargo test -p libremetaverse-imaging-skia --no-default-features --features skia
cargo test -p libremetaverse-imaging-skia --no-default-features --features skia,rust-skia
cargo build --workspace --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
python3 tools/check_test_parity.py
python3 tools/audit_red_suite.py

Also record and verify:

cargo tree -p libremetaverse-imaging-skia --no-default-features --features rust-skia -e normal,build

The pure-Rust tree must contain neither skia-safe/skia-bindings nor libwebp-sys or any other native graphics/codec binding. Add an Ubuntu-only Gitea job for rust-skia that does not install Ninja, Clang, CMake, pkg-config, or native image libraries. Keep the existing native Skia job for the skia feature.

Documentation and audit

  • Update the main README optional codec table with rust-skia build/test commands and explain how it differs from the existing native skia feature.
  • Update crates/libremetaverse-imaging-skia/README.md, docs/imaging-meshing.md, and crate documentation with backend selection, supported formats, feature coexistence, MSRV, limits, and deployment implications.
  • Record exact versions, licenses, repositories, safety posture, native-dependency audit, and relevant transitive dependencies for skia-rs-codec, the pure-Rust WebP implementation, and their codec dependencies.
  • Clearly document any intentional format edge-case difference that cannot be eliminated, but do not weaken existing C#-derived tests to accommodate the new backend.

Definition of done

A user can build and test libremetaverse-imaging-skia with --no-default-features --features rust-skia, decode every format in the existing Skia adapter contract entirely in Rust, and obtain C#-compatible bounded ManagedImage results without downloading or linking Google Skia, libwebp, or any other native library. Existing default and native skia builds remain unchanged, the two backends coexist, and every gate above passes in isolation.

## Objective Add a complete optional pure-Rust Skia-compatible image backend behind a Cargo feature named `rust-skia`. Use `skia-rs-codec` from the current compatible 0.3 release line as the primary codec implementation. The backend must implement the same bounded `ITextureCodec` behavior and the same C# `LibreMetaverse.Imaging.Skia.SkiaTextureCodec` decode semantics as the existing native rust-skia/Google Skia backend; it must not be a stub, a subprocess, or a wrapper that silently links C/C++ libraries. The existing C# source in `LibreMetaverse.Imaging.Skia/SkiaTextureCodec.cs` and the translated/golden Rust tests are the behavioral reference. Treat the C# as correct, but do not run or depend on .NET code. ## Current architecture - `crates/libremetaverse-imaging-skia` has no default features. - Its existing `skia` feature enables optional `skia-safe` 0.99.0. `skia-safe` is a safe Rust API over `skia-bindings` and native Google Skia C++ binaries; retain this backend unchanged. - `crates/libremetaverse-imaging-skia/src/skia_codec.rs` owns bounded stream buffering, native `skia_safe::codec::Codec` adaptation, color/alpha mapping, premultiplied-alpha normalization, and conversion into `ManagedImage`. - `SkiaTextureCodec` is the mapped public C# type and currently denotes the existing native Google Skia backend. - The supported compatibility formats are BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP. - The workspace MSRV is Rust 1.96. `skia-rs-codec` 0.3.0 declares Rust 1.85 and is compatible with that floor; lock and audit the exact selected version. ## Dependency caveat that must be handled `skia-rs-codec` 0.3.0 calls itself pure Rust, but its default `webp` feature depends on `webp` 0.3, which in turn links `libwebp-sys`. Therefore: - Depend on `skia-rs-codec` with `default-features = false` and enable only audited pure-Rust codec features. - Do not enable `skia-rs-codec/webp` while it transitively uses `libwebp-sys`. - Preserve WebP compatibility through an audited pure-Rust implementation. Prefer `image-webp` from its current compatible 0.2 release line, or contribute/upgrade to an upstream `skia-rs-codec` release that has removed the native dependency before completing this issue. - The completed `rust-skia` dependency graph must contain no `-sys` codec crate, CMake/cc-driven native codec, system package lookup, downloaded binary, or C/C++ source build. ## Feature and API design - Add optional `skia-rs-codec` and any required audited pure-Rust WebP dependency to `libremetaverse-imaging-skia`. - Add `rust-skia = [...]` to that crate. The feature must not enable `skia`, `skia-safe`, `skia-bindings`, Google Skia, or native libwebp. - Preserve the existing `skia` feature and `SkiaTextureCodec` behavior without a breaking rename or backend substitution. - Expose the Rust backend explicitly as `RustSkiaTextureCodec` (or a comparably clear public name), implementing `ITextureCodec` and the same bounded decode contract. - Move shared stream limits, decoded-dimension checks, typed error mapping, channel mapping, alpha normalization, and `ManagedImage` conversion into backend-neutral code. Do not fork two subtly different copies of the compatibility rules. - Enabling `skia` and `rust-skia` together must compile and expose both backends. `--all-features` must remain valid. Do not use feature ordering, mutually exclusive compile errors, or a conditional alias that silently changes what `SkiaTextureCodec` means. - The default feature set must remain native-dependency-free and retain its existing typed feature-disabled behavior. ## Required decode behavior - Decode BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP from an arbitrary `ReadWrite + Send` stream. - Match native `SkiaTextureCodec` dimensions, first-frame behavior for animated inputs, logical canvas handling, component ordering, and alpha presence. - Match the C# channel rules for gray, alpha, RGB, RGBA, BGRA-compatible, packed/fallback, opaque, straight-alpha, and premultiplied-alpha inputs. Colors returned through `ManagedImage` must be straight/unpremultiplied. - Preserve row orientation and pixel ordering for top-down/bottom-up BMP and multi-entry ICO inputs. - Support baseline and progressive JPEG, interlaced and non-interlaced PNG, palette/transparency inputs, lossless and lossy WebP, and the BMP/GIF/ICO/WBMP variants supported by the existing backend. - Return existing typed `Error` variants for empty, malformed, truncated, unsupported, or oversized data. Never panic on decoder-controlled input. - If `skia-rs-codec` lacks a behavior needed for parity, implement a bounded adapter or contribute the missing behavior upstream. Do not accept and ignore a compatibility requirement. ## Resource safety - Read at most `DEFAULT_MAX_ENCODED_BYTES + 1` and reject oversized input before passing it to a decoder. - Inspect and validate dimensions before full decode wherever the upstream API permits. Enforce nonzero width/height, checked pixel multiplication, `DEFAULT_MAX_PIXELS`, row-byte bounds, output length, component count, and every integer conversion before allocation or indexing. - A malicious dimension header, decompression bomb, oversized animation canvas/frame, or crafted stride must fail before an unbounded allocation. - Keep all project crates under the workspace `unsafe_code = "forbid"` policy. Audit dependency unsafe code and fuzz-sensitive parsers rather than copying unchecked decoder logic into this repository. - The same Rust implementation must build on Linux, Windows, macOS, and supported WASM targets without platform-specific APIs. Gitea workflows must remain `ubuntu-latest` only because this Gitea installation has no Windows or macOS runners. ## Tests Refactor the codec contract tests so the same fixture/assertion table runs independently against native `SkiaTextureCodec` and `RustSkiaTextureCodec`, with backend-specific tests only where unavoidable. Add audited deterministic fixtures for at least: - PNG: gray, RGB, RGBA, palette transparency, interlacing, and alpha values that expose premultiplication errors. - JPEG: baseline and progressive, grayscale and RGB, with stable tolerance-based pixel assertions. - WebP: lossless exact RGB/RGBA, lossy tolerance checks, transparency, and first-frame animated behavior if supported by native Skia. - BMP: 16/24/32-bit layouts, row padding, and top-down/bottom-up orientation. - GIF: palette transparency, logical-screen/frame offsets, and deterministic first-frame composition. - ICO: multiple entries and alpha/mask behavior. - WBMP: one-bit samples and multi-byte dimensions. - Empty, unknown-format, truncated-at-several-boundaries, invalid-dimension, excessive-pixel, oversized-input, malformed palette/table, and decompression-bomb failures. For lossless inputs, require identical `ManagedImage` dimensions, channel flags, and planes across the native and Rust backends. For lossy inputs, use explicit per-channel tolerances and invariant checks rather than byte-identical compressed or decoded output. Fixtures must be checked in with provenance and must not be generated at test time by C#, native Skia tools, ImageMagick, network services, or another subprocess. Add concurrent decode/drop tests proving the adapter is stateless, deterministic, bounded, and does not retain streams or decoded buffers after return. ## Validation gates The implementation is complete only when all of the following pass: ```sh cargo check --workspace --all-targets --locked -j 1 cargo test -p libremetaverse-imaging-skia --no-default-features cargo test -p libremetaverse-imaging-skia --no-default-features --features rust-skia cargo test -p libremetaverse-imaging-skia --no-default-features --features skia cargo test -p libremetaverse-imaging-skia --no-default-features --features skia,rust-skia cargo build --workspace --all-features cargo clippy --workspace --all-targets --all-features -- -D warnings cargo fmt --all -- --check python3 tools/check_test_parity.py python3 tools/audit_red_suite.py ``` Also record and verify: ```sh cargo tree -p libremetaverse-imaging-skia --no-default-features --features rust-skia -e normal,build ``` The pure-Rust tree must contain neither `skia-safe`/`skia-bindings` nor `libwebp-sys` or any other native graphics/codec binding. Add an Ubuntu-only Gitea job for `rust-skia` that does not install Ninja, Clang, CMake, pkg-config, or native image libraries. Keep the existing native Skia job for the `skia` feature. ## Documentation and audit - Update the main README optional codec table with `rust-skia` build/test commands and explain how it differs from the existing native `skia` feature. - Update `crates/libremetaverse-imaging-skia/README.md`, `docs/imaging-meshing.md`, and crate documentation with backend selection, supported formats, feature coexistence, MSRV, limits, and deployment implications. - Record exact versions, licenses, repositories, safety posture, native-dependency audit, and relevant transitive dependencies for `skia-rs-codec`, the pure-Rust WebP implementation, and their codec dependencies. - Clearly document any intentional format edge-case difference that cannot be eliminated, but do not weaken existing C#-derived tests to accommodate the new backend. ## Definition of done A user can build and test `libremetaverse-imaging-skia` with `--no-default-features --features rust-skia`, decode every format in the existing Skia adapter contract entirely in Rust, and obtain C#-compatible bounded `ManagedImage` results without downloading or linking Google Skia, libwebp, or any other native library. Existing default and native `skia` builds remain unchanged, the two backends coexist, and every gate above passes in isolation.
hugo added this to the 13 - Extensions milestone 2026-08-09 12:49:05 +00:00
hugo added the enhancement label 2026-08-09 12:49:05 +00:00
Author
Owner

Implemented and verified the optional pure-Rust Skia-compatible backend.

Implementation:

  • Added the rust-skia feature and public RustSkiaTextureCodec, while preserving the native skia / SkiaTextureCodec API and allowing both features together.
  • Added bounded pure-Rust decoding for BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP with first-frame behavior, alpha normalization, orientation handling, typed failures, preflight dimension/pixel checks, and allocation/stream limits.
  • Added the shared native/Rust fixture contract, malformed and decompression-bomb coverage, concurrency/drop coverage, and audited fixture provenance.
  • Kept the pure-Rust dependency tree free of skia-safe, skia-bindings, libwebp-sys, and other native codec bindings.
  • Added the isolated Ubuntu pure-Rust CI job, WASM/cross-target checks, user documentation, dependency audits, and supply-chain policy.
  • Fixed the deterministic auto-reset regression test, the production seed-capability lost-notification race, the red-suite auditor, and the CI/policy failures exposed by the full required gate.

Commits:

  • 8c18b2a feature implementation and tests
  • e0e76be, 2ec65d0 refreshed generated audit/consumer evidence
  • 6f67568 deterministic auto-reset regression
  • 1ea44e5 capability race and CI audit fixes
  • e16ad04 audited license/dependency policy

Verification:

  • All issue-specified feature combinations, workspace check/build/clippy/fmt gates, parity/red-suite audits, relevant imaging compatibility cases, pure-Rust dependency-tree audit, and cross-target checks passed.
  • Final Gitea Actions run 409 is green in both rust-skia (Rust only) and required: https://git.rfc1437.de/hugo/MetaCrate/actions/runs/409
Implemented and verified the optional pure-Rust Skia-compatible backend. Implementation: - Added the `rust-skia` feature and public `RustSkiaTextureCodec`, while preserving the native `skia` / `SkiaTextureCodec` API and allowing both features together. - Added bounded pure-Rust decoding for BMP, GIF, ICO, JPEG, PNG, WBMP, and WebP with first-frame behavior, alpha normalization, orientation handling, typed failures, preflight dimension/pixel checks, and allocation/stream limits. - Added the shared native/Rust fixture contract, malformed and decompression-bomb coverage, concurrency/drop coverage, and audited fixture provenance. - Kept the pure-Rust dependency tree free of `skia-safe`, `skia-bindings`, `libwebp-sys`, and other native codec bindings. - Added the isolated Ubuntu pure-Rust CI job, WASM/cross-target checks, user documentation, dependency audits, and supply-chain policy. - Fixed the deterministic auto-reset regression test, the production seed-capability lost-notification race, the red-suite auditor, and the CI/policy failures exposed by the full required gate. Commits: - `8c18b2a` feature implementation and tests - `e0e76be`, `2ec65d0` refreshed generated audit/consumer evidence - `6f67568` deterministic auto-reset regression - `1ea44e5` capability race and CI audit fixes - `e16ad04` audited license/dependency policy Verification: - All issue-specified feature combinations, workspace check/build/clippy/fmt gates, parity/red-suite audits, relevant imaging compatibility cases, pure-Rust dependency-tree audit, and cross-target checks passed. - Final Gitea Actions run 409 is green in both `rust-skia (Rust only)` and `required`: https://git.rfc1437.de/hugo/MetaCrate/actions/runs/409
hugo closed this issue 2026-08-13 10:31:22 +00:00
Sign in to join this conversation.