diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 3b2e455efb..9f88994bbc 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -24,7 +24,7 @@ jobs: -p iced_highlighter \ -p iced_renderer \ -p iced_runtime \ - -p iced_tiny_skia \ + -p iced_vello_cpu \ -p iced_wgpu \ -p iced_widget \ -p iced_winit \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4452842972..d07b5052f3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: runs-on: ${{ matrix.os }} env: RUSTFLAGS: --deny warnings - ICED_TEST_BACKEND: tiny-skia + ICED_TEST_BACKEND: vello_cpu strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] diff --git a/Cargo.lock b/Cargo.lock index 56372e1a1c..d62fdd2e5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -870,6 +870,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" +dependencies = [ + "bytemuck", +] + [[package]] name = "color_palette" version = "0.1.0" @@ -1104,6 +1113,15 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -1533,6 +1551,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "fearless_simd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76258897e51fd156ee03b6246ea53f3e0eb395d0b327e9961c4fc4c8b2fa151a" + [[package]] name = "ferris" version = "0.1.0" @@ -1998,6 +2022,15 @@ dependencies = [ "svg_fmt", ] +[[package]] +name = "guillotiere" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" +dependencies = [ + "euclid", +] + [[package]] name = "h2" version = "0.4.13" @@ -2392,7 +2425,7 @@ name = "iced_renderer" version = "0.15.0-dev" dependencies = [ "iced_graphics", - "iced_tiny_skia", + "iced_vello_cpu", "iced_wgpu", "log", "thiserror 2.0.18", @@ -2444,19 +2477,18 @@ dependencies = [ ] [[package]] -name = "iced_tiny_skia" +name = "iced_vello_cpu" version = "0.15.0-dev" dependencies = [ "bytemuck", - "cosmic-text", "iced_debug", "iced_graphics", - "kurbo 0.10.4", "log", "resvg", "rustc-hash 2.1.1", "softbuffer", - "tiny-skia", + "vello_common", + "vello_cpu", ] [[package]] @@ -2468,7 +2500,7 @@ dependencies = [ "cryoglyph", "futures", "glam", - "guillotiere", + "guillotiere 0.6.2", "iced_debug", "iced_graphics", "log", @@ -2644,7 +2676,7 @@ dependencies = [ "rgb", "tiff", "zune-core 0.5.1", - "zune-jpeg 0.5.14", + "zune-jpeg 0.5.15", ] [[package]] @@ -2949,19 +2981,20 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kurbo" -version = "0.10.4" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" +checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ "arrayvec", + "euclid", "smallvec", ] [[package]] name = "kurbo" -version = "0.11.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" +checksum = "7564e90fe3c0d5771e1f0bc95322b21baaeaa0d9213fa6a0b61c99f8b17b3bfb" dependencies = [ "arrayvec", "euclid", @@ -4008,11 +4041,20 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-channel" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95be4d57809897b5a7539fc15a7dfe0e84141bc3dfaa2e9b1b27caa90acf61ab" +dependencies = [ + "crossbeam-channel", +] + [[package]] name = "ordered-float" -version = "5.1.0" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +checksum = "0218004a4aae742209bee9c3cef05672f6b2708be36a50add8eb613b1f2a4008" dependencies = [ "num-traits", ] @@ -4148,6 +4190,19 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "peniko" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2b6aadb221872732e87d465213e9be5af2849b0e8cc5300a8ba98fffa2e00a" +dependencies = [ + "bytemuck", + "color", + "kurbo 0.13.0", + "linebender_resource_handle", + "smallvec", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -5288,9 +5343,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "simd_cesu8" @@ -5820,7 +5875,7 @@ dependencies = [ "half", "quick-error", "weezl", - "zune-jpeg 0.5.14", + "zune-jpeg 0.5.15", ] [[package]] @@ -6313,9 +6368,9 @@ checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-segmentation" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da36089a805484bcccfffe0739803392c8298778a2d2f09febf76fac5ad9025b" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-vo" @@ -6414,9 +6469,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6455,6 +6510,49 @@ dependencies = [ "iced", ] +[[package]] +name = "vello_api" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5088cd0113bc5332c753f24503825e3bc93e26c7883c9dc3ad9637bb62c4634" +dependencies = [ + "bytemuck", + "peniko", +] + +[[package]] +name = "vello_common" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986dc49a501a683477614bf07b8e7b6c79ae4828efce3bf22e51850f4a0a8a4c" +dependencies = [ + "bytemuck", + "fearless_simd", + "guillotiere 0.7.0", + "hashbrown 0.16.1", + "log", + "peniko", + "skrifa 0.40.0", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "vello_cpu" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a678f91c7524a3a9ac9a19df9f83552866ec70b2ca26441b916a6b219b6aa2de" +dependencies = [ + "bytemuck", + "crossbeam-channel", + "hashbrown 0.16.1", + "ordered-channel", + "rayon", + "thread_local", + "vello_api", + "vello_common", +] + [[package]] name = "version_check" version = "0.9.5" @@ -7920,9 +8018,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" dependencies = [ "zune-core 0.5.1", ] diff --git a/Cargo.toml b/Cargo.toml index 5ccd4ca10c..d9b423cce6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,13 +22,15 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "crisp", "hinting", "web-colors", "thread-pool", "linux-theme-detection", "x11", "wayland"] +default = ["wgpu", "vello-cpu", "crisp", "hinting", "web-colors", "thread-pool", "linux-theme-detection", "x11", "wayland"] # Enables the `wgpu` GPU-accelerated renderer with all its default features (Vulkan, Metal, DX12, OpenGL, and WebGPU) wgpu = ["wgpu-bare", "iced_renderer/wgpu"] # Enables the `wgpu` GPU-accelerated renderer with the minimum required features (no backends!) wgpu-bare = ["iced_renderer/wgpu-bare", "iced_widget/wgpu"] -# Enables the `tiny-skia` software renderer -tiny-skia = ["iced_renderer/tiny-skia"] +# Enables the `vello-cpu` software renderer +vello-cpu = ["iced_renderer/vello-cpu"] +# Enables multithreading in the `vello-cpu` software renderer +vello-cpu-multithreading = ["iced_renderer/vello-cpu-multithreading"] # Enables the `image` widget and image clipboard support image = ["image-without-codecs", "image/default", "iced_winit/image"] # Enables the `image` widget, without any built-in codecs of the `image` crate @@ -155,7 +157,7 @@ members = [ "selector", "test", "tester", - "tiny_skia", + "vello_cpu", "wgpu", "widget", "winit", @@ -188,7 +190,7 @@ iced_runtime = { version = "0.15.0-dev", path = "runtime" } iced_selector = { version = "0.15.0-dev", path = "selector" } iced_test = { version = "0.15.0-dev", path = "test" } iced_tester = { version = "0.15.0-dev", path = "tester" } -iced_tiny_skia = { version = "0.15.0-dev", path = "tiny_skia", default-features = false } +iced_vello_cpu = { version = "0.15.0-dev", path = "vello_cpu", default-features = false } iced_wgpu = { version = "0.15.0-dev", path = "wgpu", default-features = false } iced_widget = { version = "0.15.0-dev", path = "widget" } iced_winit = { version = "0.15.0-dev", path = "winit", default-features = false } @@ -232,12 +234,13 @@ smol_str = "0.2" softbuffer = { version = "0.4", default-features = false } sysinfo = "0.33" thiserror = "2" -tiny-skia = { version = "0.11", default-features = false, features = ["std", "simd"] } tokio = "1.0" tracing = "0.1" two-face = { version = "0.4", default-features = false, features = ["syntect-default-fancy"] } unicode-segmentation = "1.0" url = "2.5" +vello_cpu = { version = "0.0.7", default-features = false, features = ["std", "u8_pipeline"] } +vello_common = { version = "0.0.7", default-features = false } wasm-bindgen-futures = "0.4" wasmtimer = "0.4.2" web-sys = "=0.3.85" diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 87f8c798d5..2f204aa1a1 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -5,7 +5,6 @@ use iced::widget::canvas::{Cache, Geometry, LineCap, Path, Stroke, stroke}; use iced::widget::{canvas, container, text}; use iced::{ Degrees, Element, Fill, Font, Point, Radians, Rectangle, Renderer, Size, Subscription, Theme, - Vector, }; pub fn main() -> iced::Result { @@ -85,7 +84,6 @@ impl canvas::Program for Clock { frame.fill(&background, palette.secondary.strong.color); let short_hand = Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); - let long_hand = Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); let width = radius / 100.0; @@ -108,7 +106,7 @@ impl canvas::Program for Clock { } }; - frame.translate(Vector::new(center.x, center.y)); + frame.translate(center - Point::ORIGIN); let minutes_portion = Radians::from(hand_rotation(self.now.minute(), 60)) / 12.0; let hour_hand_angle = Radians::from(hand_rotation(self.now.hour(), 12)) + minutes_portion; diff --git "a/examples/styling/snapshots/catppuccin_frapp\303\251-tiny-skia.sha256" "b/examples/styling/snapshots/catppuccin_frapp\303\251-tiny-skia.sha256" deleted file mode 100644 index 65a0ff4ce7..0000000000 --- "a/examples/styling/snapshots/catppuccin_frapp\303\251-tiny-skia.sha256" +++ /dev/null @@ -1 +0,0 @@ -4a602409066a7506b35b7564187d9b91ad2a278c034a2a27047245d3603e106e \ No newline at end of file diff --git "a/examples/styling/snapshots/catppuccin_frapp\303\251-vello_cpu.sha256" "b/examples/styling/snapshots/catppuccin_frapp\303\251-vello_cpu.sha256" new file mode 100644 index 0000000000..17049c69ad --- /dev/null +++ "b/examples/styling/snapshots/catppuccin_frapp\303\251-vello_cpu.sha256" @@ -0,0 +1 @@ +9af92d4906e8fa4abe11155dd9b2a934b6075d3cd8d9f07f6cb2ec6e0be887be \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_latte-tiny-skia.sha256 b/examples/styling/snapshots/catppuccin_latte-tiny-skia.sha256 deleted file mode 100644 index 39eaa0f343..0000000000 --- a/examples/styling/snapshots/catppuccin_latte-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -05ff440c3ca4cfe27bd4540b68d3579f34082da2dc9b65195a2f3cb5c481f66d \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_latte-vello_cpu.sha256 b/examples/styling/snapshots/catppuccin_latte-vello_cpu.sha256 new file mode 100644 index 0000000000..168e630430 --- /dev/null +++ b/examples/styling/snapshots/catppuccin_latte-vello_cpu.sha256 @@ -0,0 +1 @@ +208deb1d2783d5b8aabd1708e971247dca5198d26e6f9b77734d382e5a311945 \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_macchiato-tiny-skia.sha256 b/examples/styling/snapshots/catppuccin_macchiato-tiny-skia.sha256 deleted file mode 100644 index 37654f4a00..0000000000 --- a/examples/styling/snapshots/catppuccin_macchiato-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -a887c61a62121e95d118ee11e5263e3ea6054a469d04e4e040b468dda2985eee \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_macchiato-vello_cpu.sha256 b/examples/styling/snapshots/catppuccin_macchiato-vello_cpu.sha256 new file mode 100644 index 0000000000..fcaf52a989 --- /dev/null +++ b/examples/styling/snapshots/catppuccin_macchiato-vello_cpu.sha256 @@ -0,0 +1 @@ +5871ceb7153cb9501e207532e7a37d975c52889259a2b49f5ad3eed9c54c334a \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_mocha-tiny-skia.sha256 b/examples/styling/snapshots/catppuccin_mocha-tiny-skia.sha256 deleted file mode 100644 index f03fde7064..0000000000 --- a/examples/styling/snapshots/catppuccin_mocha-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -883a505d5c855d8c5400e2dc5231580cd320c8801600333478270a8d8ea42bbd \ No newline at end of file diff --git a/examples/styling/snapshots/catppuccin_mocha-vello_cpu.sha256 b/examples/styling/snapshots/catppuccin_mocha-vello_cpu.sha256 new file mode 100644 index 0000000000..acd108ae9e --- /dev/null +++ b/examples/styling/snapshots/catppuccin_mocha-vello_cpu.sha256 @@ -0,0 +1 @@ +79ace1a552fb941a43126030ba8eccb0b1d772b8612c6091efa0634925adaaa0 \ No newline at end of file diff --git a/examples/styling/snapshots/dark-tiny-skia.sha256 b/examples/styling/snapshots/dark-tiny-skia.sha256 deleted file mode 100644 index bab6381fbd..0000000000 --- a/examples/styling/snapshots/dark-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -e67c164f725d92837c0f67ec49ce676156d46e1302d382bc3dc67255087fcd1d \ No newline at end of file diff --git a/examples/styling/snapshots/dark-vello_cpu.sha256 b/examples/styling/snapshots/dark-vello_cpu.sha256 new file mode 100644 index 0000000000..a60ede2920 --- /dev/null +++ b/examples/styling/snapshots/dark-vello_cpu.sha256 @@ -0,0 +1 @@ +143bed189a4c1b88fb39af9e884331424e7549d4e544572dbb02b8af791ef097 \ No newline at end of file diff --git a/examples/styling/snapshots/dracula-tiny-skia.sha256 b/examples/styling/snapshots/dracula-tiny-skia.sha256 deleted file mode 100644 index 69bcdff45e..0000000000 --- a/examples/styling/snapshots/dracula-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -54bfddd255c34f469e3c98afec573cce6aca4bbe11df19111eb0b1055cb2f0d4 \ No newline at end of file diff --git a/examples/styling/snapshots/dracula-vello_cpu.sha256 b/examples/styling/snapshots/dracula-vello_cpu.sha256 new file mode 100644 index 0000000000..f94bf15973 --- /dev/null +++ b/examples/styling/snapshots/dracula-vello_cpu.sha256 @@ -0,0 +1 @@ +571c0e768303c54da00ce7b92baefe581c7009072d5b46c9322e7c938c9cec7a \ No newline at end of file diff --git a/examples/styling/snapshots/ferra-tiny-skia.sha256 b/examples/styling/snapshots/ferra-tiny-skia.sha256 deleted file mode 100644 index 4d839e456e..0000000000 --- a/examples/styling/snapshots/ferra-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -2159a7041d66a76d0eff150f2d3a50c6393d294859e67351b0858dd21fac6887 \ No newline at end of file diff --git a/examples/styling/snapshots/ferra-vello_cpu.sha256 b/examples/styling/snapshots/ferra-vello_cpu.sha256 new file mode 100644 index 0000000000..a6a1d56e98 --- /dev/null +++ b/examples/styling/snapshots/ferra-vello_cpu.sha256 @@ -0,0 +1 @@ +8a555f0086480dac3a5487075667f88add800d1a088ece1d60a4933144c10f84 \ No newline at end of file diff --git a/examples/styling/snapshots/gruvbox_dark-tiny-skia.sha256 b/examples/styling/snapshots/gruvbox_dark-tiny-skia.sha256 deleted file mode 100644 index 07c49c31b9..0000000000 --- a/examples/styling/snapshots/gruvbox_dark-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -88938aaeaed5f1ba0bd3dfc290764a93819b2321ac339e33dc58fdaf780bbea1 \ No newline at end of file diff --git a/examples/styling/snapshots/gruvbox_dark-vello_cpu.sha256 b/examples/styling/snapshots/gruvbox_dark-vello_cpu.sha256 new file mode 100644 index 0000000000..5c4b510e44 --- /dev/null +++ b/examples/styling/snapshots/gruvbox_dark-vello_cpu.sha256 @@ -0,0 +1 @@ +24b42d89f55f29f33a0964c48a72021adb46e006bf99e3816e8d69553ff3eecb \ No newline at end of file diff --git a/examples/styling/snapshots/gruvbox_light-tiny-skia.sha256 b/examples/styling/snapshots/gruvbox_light-tiny-skia.sha256 deleted file mode 100644 index c9ebdcae50..0000000000 --- a/examples/styling/snapshots/gruvbox_light-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -94f6071d7fb4a6d93a351f219194a48e93ddc9494a735ef2eaf89dbb7e719563 \ No newline at end of file diff --git a/examples/styling/snapshots/gruvbox_light-vello_cpu.sha256 b/examples/styling/snapshots/gruvbox_light-vello_cpu.sha256 new file mode 100644 index 0000000000..257e26391b --- /dev/null +++ b/examples/styling/snapshots/gruvbox_light-vello_cpu.sha256 @@ -0,0 +1 @@ +56d75783dbd975cd4bd6b2ded0bed2a426c2151f94854177609aa52561bc2b2e \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_dragon-tiny-skia.sha256 b/examples/styling/snapshots/kanagawa_dragon-tiny-skia.sha256 deleted file mode 100644 index c83961445e..0000000000 --- a/examples/styling/snapshots/kanagawa_dragon-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -42fd4986288be2e3173499e5631018c112d1f139e92632531e21272c3f179274 \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_dragon-vello_cpu.sha256 b/examples/styling/snapshots/kanagawa_dragon-vello_cpu.sha256 new file mode 100644 index 0000000000..fb008fda9e --- /dev/null +++ b/examples/styling/snapshots/kanagawa_dragon-vello_cpu.sha256 @@ -0,0 +1 @@ +37e28f50bae588ee2a9d356790cf1c3f387b073f9d8dff9cbe0b4a988c46dfe2 \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_lotus-tiny-skia.sha256 b/examples/styling/snapshots/kanagawa_lotus-tiny-skia.sha256 deleted file mode 100644 index b5b99517f1..0000000000 --- a/examples/styling/snapshots/kanagawa_lotus-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -de9aa60d77e94e586b9a8e4f411166cfa4377d17bcea491b60a0fd75cbfe887c \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_lotus-vello_cpu.sha256 b/examples/styling/snapshots/kanagawa_lotus-vello_cpu.sha256 new file mode 100644 index 0000000000..733ab6182f --- /dev/null +++ b/examples/styling/snapshots/kanagawa_lotus-vello_cpu.sha256 @@ -0,0 +1 @@ +6a34cda0c07e7e07736186e0aa66d3f4dd90f457ede7d947430a80ce97a1d2d0 \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_wave-tiny-skia.sha256 b/examples/styling/snapshots/kanagawa_wave-tiny-skia.sha256 deleted file mode 100644 index e8906ee1d4..0000000000 --- a/examples/styling/snapshots/kanagawa_wave-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -9e85b76b1ab91c0dc0e9cb21ad0ce28445fff2a17924f8e1221fa25f24a646c5 \ No newline at end of file diff --git a/examples/styling/snapshots/kanagawa_wave-vello_cpu.sha256 b/examples/styling/snapshots/kanagawa_wave-vello_cpu.sha256 new file mode 100644 index 0000000000..b5ad9214a1 --- /dev/null +++ b/examples/styling/snapshots/kanagawa_wave-vello_cpu.sha256 @@ -0,0 +1 @@ +c0ad23f33f5ed1710cd248b01567cc36629dba1887cc0e6ef8b1958e34c84d99 \ No newline at end of file diff --git a/examples/styling/snapshots/light-tiny-skia.sha256 b/examples/styling/snapshots/light-tiny-skia.sha256 deleted file mode 100644 index 180f802efe..0000000000 --- a/examples/styling/snapshots/light-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -3aa01b2e64fc23b85e2489c1f32a3295123a3dc8fa62f12652d7bd1524af7eec \ No newline at end of file diff --git a/examples/styling/snapshots/light-vello_cpu.sha256 b/examples/styling/snapshots/light-vello_cpu.sha256 new file mode 100644 index 0000000000..0dd3f3a6fa --- /dev/null +++ b/examples/styling/snapshots/light-vello_cpu.sha256 @@ -0,0 +1 @@ +d1d5b8e016f293c5721a0da8337f042eef4f36a0eb025d3de175cd90a81c85b7 \ No newline at end of file diff --git a/examples/styling/snapshots/moonfly-tiny-skia.sha256 b/examples/styling/snapshots/moonfly-tiny-skia.sha256 deleted file mode 100644 index dcb3c98bea..0000000000 --- a/examples/styling/snapshots/moonfly-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -57e5e9f1eb3b1f2950e79a66a0e581f2e8dea74a35ec0c590fb93057d7de50eb \ No newline at end of file diff --git a/examples/styling/snapshots/moonfly-vello_cpu.sha256 b/examples/styling/snapshots/moonfly-vello_cpu.sha256 new file mode 100644 index 0000000000..257beb20b3 --- /dev/null +++ b/examples/styling/snapshots/moonfly-vello_cpu.sha256 @@ -0,0 +1 @@ +092b22f92ec1a541f32d347165852b2f92255093430f7366bc0eb559527ef42b \ No newline at end of file diff --git a/examples/styling/snapshots/nightfly-tiny-skia.sha256 b/examples/styling/snapshots/nightfly-tiny-skia.sha256 deleted file mode 100644 index fb8002d1d6..0000000000 --- a/examples/styling/snapshots/nightfly-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -1cac75136e18c821921d06b8ce349f15bb0886ea46fca85598c18e63890a447e \ No newline at end of file diff --git a/examples/styling/snapshots/nightfly-vello_cpu.sha256 b/examples/styling/snapshots/nightfly-vello_cpu.sha256 new file mode 100644 index 0000000000..5923be5705 --- /dev/null +++ b/examples/styling/snapshots/nightfly-vello_cpu.sha256 @@ -0,0 +1 @@ +1ef3a3de86181e2c438eca9377bc1a0f4ec16d013124c3de2db1010569d8fbbc \ No newline at end of file diff --git a/examples/styling/snapshots/nord-tiny-skia.sha256 b/examples/styling/snapshots/nord-tiny-skia.sha256 deleted file mode 100644 index 99b996cbe3..0000000000 --- a/examples/styling/snapshots/nord-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -b00759f66ce6c7e04efe48ad6480a2f374777ddf3b52291f0ba45734813f339d \ No newline at end of file diff --git a/examples/styling/snapshots/nord-vello_cpu.sha256 b/examples/styling/snapshots/nord-vello_cpu.sha256 new file mode 100644 index 0000000000..71db943623 --- /dev/null +++ b/examples/styling/snapshots/nord-vello_cpu.sha256 @@ -0,0 +1 @@ +f089327038080afc069258f905b142fdb0aeb49771bfb085741ac713d42d8762 \ No newline at end of file diff --git a/examples/styling/snapshots/oxocarbon-tiny-skia.sha256 b/examples/styling/snapshots/oxocarbon-tiny-skia.sha256 deleted file mode 100644 index fb128fcd70..0000000000 --- a/examples/styling/snapshots/oxocarbon-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -70d7e219c88ca56c1f77e6aa427cc0c8a7d20219a76f6c5738d38f1cd78c7fae \ No newline at end of file diff --git a/examples/styling/snapshots/oxocarbon-vello_cpu.sha256 b/examples/styling/snapshots/oxocarbon-vello_cpu.sha256 new file mode 100644 index 0000000000..79a50d205c --- /dev/null +++ b/examples/styling/snapshots/oxocarbon-vello_cpu.sha256 @@ -0,0 +1 @@ +e7944ffc21cc82e349cc582fefeb137c0b6b10aeb90d53d916e460f3db615b53 \ No newline at end of file diff --git a/examples/styling/snapshots/solarized_dark-tiny-skia.sha256 b/examples/styling/snapshots/solarized_dark-tiny-skia.sha256 deleted file mode 100644 index 9737eb5088..0000000000 --- a/examples/styling/snapshots/solarized_dark-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -06a9e730406f07f2619f980ac5d77db61075e54899ec0d3c82eaa7c9829c643d \ No newline at end of file diff --git a/examples/styling/snapshots/solarized_dark-vello_cpu.sha256 b/examples/styling/snapshots/solarized_dark-vello_cpu.sha256 new file mode 100644 index 0000000000..f066019e01 --- /dev/null +++ b/examples/styling/snapshots/solarized_dark-vello_cpu.sha256 @@ -0,0 +1 @@ +403ed9cf8989c9af01740e1784ae592f48ca3beab13cc18ca0d85f114bb6952c \ No newline at end of file diff --git a/examples/styling/snapshots/solarized_light-tiny-skia.sha256 b/examples/styling/snapshots/solarized_light-tiny-skia.sha256 deleted file mode 100644 index d7b7933ae9..0000000000 --- a/examples/styling/snapshots/solarized_light-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -e134aaba0aedddec5784f83950a32384071cc1341e6b7dfa703451dc5030a38e \ No newline at end of file diff --git a/examples/styling/snapshots/solarized_light-vello_cpu.sha256 b/examples/styling/snapshots/solarized_light-vello_cpu.sha256 new file mode 100644 index 0000000000..419a93709d --- /dev/null +++ b/examples/styling/snapshots/solarized_light-vello_cpu.sha256 @@ -0,0 +1 @@ +5bac7f5f4a3f6ce3c464a1385f1d932e55d2b81688ddc3261e2173d25f0a5f56 \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night-tiny-skia.sha256 b/examples/styling/snapshots/tokyo_night-tiny-skia.sha256 deleted file mode 100644 index ae4c892864..0000000000 --- a/examples/styling/snapshots/tokyo_night-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -7d35f80b930fb45dd4de2884c5994d21024eaadf20b049d9ef9a3bb9b1c12e4b \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night-vello_cpu.sha256 b/examples/styling/snapshots/tokyo_night-vello_cpu.sha256 new file mode 100644 index 0000000000..0879c018b1 --- /dev/null +++ b/examples/styling/snapshots/tokyo_night-vello_cpu.sha256 @@ -0,0 +1 @@ +ee2db61a54111b72466a9fe74c0a7541b954070edc52e5f972e0d5bab94e715e \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night_light-tiny-skia.sha256 b/examples/styling/snapshots/tokyo_night_light-tiny-skia.sha256 deleted file mode 100644 index 70f077005c..0000000000 --- a/examples/styling/snapshots/tokyo_night_light-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -dc240086ba66e2fb97877c0b84e62df924b851996c2659c565cbdda7f23e17aa \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night_light-vello_cpu.sha256 b/examples/styling/snapshots/tokyo_night_light-vello_cpu.sha256 new file mode 100644 index 0000000000..ced9cc4798 --- /dev/null +++ b/examples/styling/snapshots/tokyo_night_light-vello_cpu.sha256 @@ -0,0 +1 @@ +58262cf537ce197d25090de62ee4dde951368d047fc651e45d559f0a45cd428d \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night_storm-tiny-skia.sha256 b/examples/styling/snapshots/tokyo_night_storm-tiny-skia.sha256 deleted file mode 100644 index 63d646215d..0000000000 --- a/examples/styling/snapshots/tokyo_night_storm-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -bdca14fd8383405faf2f88ecd2cf9a248864294de68745be9b7547b2dcd25c51 \ No newline at end of file diff --git a/examples/styling/snapshots/tokyo_night_storm-vello_cpu.sha256 b/examples/styling/snapshots/tokyo_night_storm-vello_cpu.sha256 new file mode 100644 index 0000000000..065367ccdf --- /dev/null +++ b/examples/styling/snapshots/tokyo_night_storm-vello_cpu.sha256 @@ -0,0 +1 @@ +8fed075b41749cc2cff65f40a021d08da330057b65db7e317c9a611e9f252197 \ No newline at end of file diff --git a/examples/todos/snapshots/creates_a_new_task-tiny-skia.sha256 b/examples/todos/snapshots/creates_a_new_task-tiny-skia.sha256 deleted file mode 100644 index f56ef74408..0000000000 --- a/examples/todos/snapshots/creates_a_new_task-tiny-skia.sha256 +++ /dev/null @@ -1 +0,0 @@ -0acb67235c6a11014a2d2b825e0a70069bca0c67bee0cdb38a0144fc72b25220 \ No newline at end of file diff --git a/examples/todos/snapshots/creates_a_new_task-vello_cpu.sha256 b/examples/todos/snapshots/creates_a_new_task-vello_cpu.sha256 new file mode 100644 index 0000000000..89016b674f --- /dev/null +++ b/examples/todos/snapshots/creates_a_new_task-vello_cpu.sha256 @@ -0,0 +1 @@ +97571a13ee03cfb7a386781136fef5bbecdb8d216a13414f46b6f99222b1e79c \ No newline at end of file diff --git a/renderer/Cargo.toml b/renderer/Cargo.toml index f025927a54..d93f6df89e 100644 --- a/renderer/Cargo.toml +++ b/renderer/Cargo.toml @@ -16,22 +16,23 @@ workspace = true [features] wgpu = ["iced_wgpu/default"] wgpu-bare = ["iced_wgpu"] -tiny-skia = ["iced_tiny_skia"] -image = ["iced_tiny_skia?/image", "iced_wgpu?/image"] -svg = ["iced_tiny_skia?/svg", "iced_wgpu?/svg"] -geometry = ["iced_graphics/geometry", "iced_tiny_skia?/geometry", "iced_wgpu?/geometry"] +vello-cpu = ["iced_vello_cpu"] +vello-cpu-multithreading = ["iced_vello_cpu?/multithreading"] +image = ["iced_vello_cpu?/image", "iced_wgpu?/image"] +svg = ["iced_vello_cpu?/svg", "iced_wgpu?/svg"] +geometry = ["iced_graphics/geometry", "iced_vello_cpu?/geometry", "iced_wgpu?/geometry"] web-colors = ["iced_wgpu?/web-colors"] webgl = ["iced_wgpu?/webgl"] fira-sans = ["iced_graphics/fira-sans"] strict-assertions = ["iced_wgpu?/strict-assertions"] -x11 = ["iced_tiny_skia?/x11"] -wayland = ["iced_tiny_skia?/wayland"] +x11 = ["iced_vello_cpu?/x11"] +wayland = ["iced_vello_cpu?/wayland"] [dependencies] iced_graphics.workspace = true -iced_tiny_skia.workspace = true -iced_tiny_skia.optional = true +iced_vello_cpu.workspace = true +iced_vello_cpu.optional = true iced_wgpu.workspace = true iced_wgpu.optional = true diff --git a/renderer/src/lib.rs b/renderer/src/lib.rs index d430ccb73b..ae78e80831 100644 --- a/renderer/src/lib.rs +++ b/renderer/src/lib.rs @@ -22,35 +22,33 @@ pub type Renderer = renderer::Renderer; /// [`iced`]: https://github.com/iced-rs/iced pub type Compositor = renderer::Compositor; -#[cfg(all(feature = "wgpu-bare", feature = "tiny-skia"))] +#[cfg(all(feature = "wgpu-bare", feature = "vello-cpu"))] mod renderer { - pub type Renderer = crate::fallback::Renderer; + pub type Renderer = crate::fallback::Renderer; - pub type Compositor = crate::fallback::Compositor< - iced_wgpu::window::Compositor, - iced_tiny_skia::window::Compositor, - >; + pub type Compositor = + crate::fallback::Compositor; } -#[cfg(all(feature = "wgpu-bare", not(feature = "tiny-skia")))] +#[cfg(all(feature = "wgpu-bare", not(feature = "vello-cpu")))] mod renderer { pub type Renderer = iced_wgpu::Renderer; pub type Compositor = iced_wgpu::window::Compositor; } -#[cfg(all(not(feature = "wgpu-bare"), feature = "tiny-skia"))] +#[cfg(all(not(feature = "wgpu-bare"), feature = "vello-cpu"))] mod renderer { - pub type Renderer = iced_tiny_skia::Renderer; - pub type Compositor = iced_tiny_skia::window::Compositor; + pub type Renderer = iced_vello_cpu::Renderer; + pub type Compositor = iced_vello_cpu::Compositor; } -#[cfg(not(any(feature = "wgpu-bare", feature = "tiny-skia")))] +#[cfg(not(any(feature = "wgpu-bare", feature = "vello-cpu")))] mod renderer { #[cfg(not(debug_assertions))] compile_error!( "Cannot compile `iced_renderer` in release mode \ without a renderer feature enabled. \ - Enable either the `wgpu` or `tiny-skia` feature, or both." + Enable either the `wgpu` or `vello-cpu` feature, or both." ); pub type Renderer = (); diff --git a/tiny_skia/src/engine.rs b/tiny_skia/src/engine.rs deleted file mode 100644 index 0f78372903..0000000000 --- a/tiny_skia/src/engine.rs +++ /dev/null @@ -1,790 +0,0 @@ -use crate::Primitive; -use crate::core::renderer::Quad; -use crate::core::{Background, Color, Gradient, Rectangle, Size, Transformation, Vector}; -use crate::graphics::{Image, Text}; -use crate::text; - -#[derive(Debug)] -pub struct Engine { - text_pipeline: text::Pipeline, - - #[cfg(feature = "image")] - pub(crate) raster_pipeline: crate::raster::Pipeline, - #[cfg(feature = "svg")] - pub(crate) vector_pipeline: crate::vector::Pipeline, -} - -impl Engine { - pub fn new() -> Self { - Self { - text_pipeline: text::Pipeline::new(), - #[cfg(feature = "image")] - raster_pipeline: crate::raster::Pipeline::new(), - #[cfg(feature = "svg")] - vector_pipeline: crate::vector::Pipeline::new(), - } - } - - pub fn draw_quad( - &mut self, - quad: &Quad, - background: &Background, - transformation: Transformation, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, - ) { - let physical_bounds = quad.bounds * transformation; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let transform = into_transform(transformation); - - // Make sure the border radius is not larger than the bounds - let border_width = quad - .border - .width - .min(quad.bounds.width / 2.0) - .min(quad.bounds.height / 2.0); - - let mut fill_border_radius = <[f32; 4]>::from(quad.border.radius); - - for radius in &mut fill_border_radius { - *radius = (*radius) - .min(quad.bounds.width / 2.0) - .min(quad.bounds.height / 2.0); - } - - let path = rounded_rectangle(quad.bounds, fill_border_radius); - - let shadow = quad.shadow; - - if shadow.color.a > 0.0 { - let shadow_bounds = Rectangle { - x: quad.bounds.x + shadow.offset.x - shadow.blur_radius, - y: quad.bounds.y + shadow.offset.y - shadow.blur_radius, - width: quad.bounds.width + shadow.blur_radius * 2.0, - height: quad.bounds.height + shadow.blur_radius * 2.0, - } * transformation; - - let radii = fill_border_radius - .into_iter() - .map(|radius| radius * transformation.scale_factor()) - .collect::>(); - let (x, y, width, height) = ( - shadow_bounds.x as u32, - shadow_bounds.y as u32, - shadow_bounds.width as u32, - shadow_bounds.height as u32, - ); - let half_width = physical_bounds.width / 2.0; - let half_height = physical_bounds.height / 2.0; - - let colors = (y..y + height) - .flat_map(|y| (x..x + width).map(move |x| (x as f32, y as f32))) - .filter_map(|(x, y)| { - tiny_skia::Size::from_wh(half_width, half_height).map(|size| { - let shadow_distance = rounded_box_sdf( - Vector::new( - x - physical_bounds.position().x - - (shadow.offset.x * transformation.scale_factor()) - - half_width, - y - physical_bounds.position().y - - (shadow.offset.y * transformation.scale_factor()) - - half_height, - ), - size, - &radii, - ) - .max(0.0); - let shadow_alpha = 1.0 - - smoothstep( - -shadow.blur_radius * transformation.scale_factor(), - shadow.blur_radius * transformation.scale_factor(), - shadow_distance, - ); - - let mut color = into_color(shadow.color); - color.apply_opacity(shadow_alpha); - - color.to_color_u8().premultiply() - }) - }) - .collect(); - - if let Some(pixmap) = tiny_skia::IntSize::from_wh(width, height) - .and_then(|size| tiny_skia::Pixmap::from_vec(bytemuck::cast_vec(colors), size)) - { - pixels.draw_pixmap( - x as i32, - y as i32, - pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - tiny_skia::Transform::default(), - Some(clip_mask), - ); - } - } - - let clip_mask = (!physical_bounds.is_within(&clip_bounds)).then_some(clip_mask as &_); - - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: match background { - Background::Color(color) => tiny_skia::Shader::SolidColor(into_color(*color)), - Background::Gradient(Gradient::Linear(linear)) => { - let (start, end) = linear.angle.to_distance(&quad.bounds); - - let stops: Vec = linear - .stops - .into_iter() - .flatten() - .map(|stop| { - tiny_skia::GradientStop::new( - stop.offset, - tiny_skia::Color::from_rgba( - stop.color.b, - stop.color.g, - stop.color.r, - stop.color.a, - ) - .expect("Create color"), - ) - }) - .collect(); - - tiny_skia::LinearGradient::new( - tiny_skia::Point { - x: start.x, - y: start.y, - }, - tiny_skia::Point { x: end.x, y: end.y }, - if stops.is_empty() { - vec![tiny_skia::GradientStop::new(0.0, tiny_skia::Color::BLACK)] - } else { - stops - }, - tiny_skia::SpreadMode::Pad, - tiny_skia::Transform::identity(), - ) - .expect("Create linear gradient") - } - }, - anti_alias: true, - ..tiny_skia::Paint::default() - }, - tiny_skia::FillRule::EvenOdd, - transform, - clip_mask, - ); - - if border_width > 0.0 { - // Border path is offset by half the border width - let border_bounds = Rectangle { - x: quad.bounds.x + border_width / 2.0, - y: quad.bounds.y + border_width / 2.0, - width: quad.bounds.width - border_width, - height: quad.bounds.height - border_width, - }; - - // Make sure the border radius is correct - let mut border_radius = <[f32; 4]>::from(quad.border.radius); - let mut is_simple_border = true; - - for radius in &mut border_radius { - *radius = if *radius == 0.0 { - // Path should handle this fine - 0.0 - } else if *radius > border_width / 2.0 { - *radius - border_width / 2.0 - } else { - is_simple_border = false; - 0.0 - } - .min(border_bounds.width / 2.0) - .min(border_bounds.height / 2.0); - } - - // Stroking a path works well in this case - if is_simple_border { - let border_path = rounded_rectangle(border_bounds, border_radius); - - pixels.stroke_path( - &border_path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color(quad.border.color)), - anti_alias: true, - ..tiny_skia::Paint::default() - }, - &tiny_skia::Stroke { - width: border_width, - ..tiny_skia::Stroke::default() - }, - transform, - clip_mask, - ); - } else { - // Draw corners that have too small border radii as having no border radius, - // but mask them with the rounded rectangle with the correct border radius. - let mut temp_pixmap = - tiny_skia::Pixmap::new(quad.bounds.width as u32, quad.bounds.height as u32) - .unwrap(); - - let mut quad_mask = - tiny_skia::Mask::new(quad.bounds.width as u32, quad.bounds.height as u32) - .unwrap(); - - let zero_bounds = Rectangle { - x: 0.0, - y: 0.0, - width: quad.bounds.width, - height: quad.bounds.height, - }; - let path = rounded_rectangle(zero_bounds, fill_border_radius); - - quad_mask.fill_path(&path, tiny_skia::FillRule::EvenOdd, true, transform); - let path_bounds = Rectangle { - x: border_width / 2.0, - y: border_width / 2.0, - width: quad.bounds.width - border_width, - height: quad.bounds.height - border_width, - }; - - let border_radius_path = rounded_rectangle(path_bounds, border_radius); - - temp_pixmap.stroke_path( - &border_radius_path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(into_color(quad.border.color)), - anti_alias: true, - ..tiny_skia::Paint::default() - }, - &tiny_skia::Stroke { - width: border_width, - ..tiny_skia::Stroke::default() - }, - transform, - Some(&quad_mask), - ); - - pixels.draw_pixmap( - quad.bounds.x as i32, - quad.bounds.y as i32, - temp_pixmap.as_ref(), - &tiny_skia::PixmapPaint::default(), - transform, - clip_mask, - ); - } - } - } - - pub fn draw_text( - &mut self, - text: &Text, - transformation: Transformation, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, - ) { - match text { - Text::Paragraph { - paragraph, - position, - color, - clip_bounds: local_clip_bounds, - transformation: local_transformation, - } => { - let transformation = transformation * *local_transformation; - let Some(clip_bounds) = - clip_bounds.intersection(&(*local_clip_bounds * transformation)) - else { - return; - }; - - let physical_bounds = - Rectangle::new(*position, paragraph.min_bounds) * transformation; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = match physical_bounds.is_within(&clip_bounds) { - true => None, - false => { - adjust_clip_mask(clip_mask, clip_bounds); - Some(clip_mask as &_) - } - }; - - self.text_pipeline.draw_paragraph( - paragraph, - *position, - *color, - pixels, - clip_mask, - transformation, - ); - } - Text::Editor { - editor, - position, - color, - clip_bounds: local_clip_bounds, - transformation: local_transformation, - } => { - let transformation = transformation * *local_transformation; - - let Some(clip_bounds) = - clip_bounds.intersection(&(*local_clip_bounds * transformation)) - else { - return; - }; - - adjust_clip_mask(clip_mask, clip_bounds); - - self.text_pipeline.draw_editor( - editor, - *position, - *color, - pixels, - Some(clip_mask), - transformation, - ); - } - Text::Cached { - content, - bounds, - color, - size, - line_height, - font, - align_x, - align_y, - shaping, - wrapping, - ellipsis, - clip_bounds: local_clip_bounds, - } => { - let physical_bounds = *local_clip_bounds * transformation; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = match physical_bounds.is_within(&clip_bounds) { - true => None, - false => { - adjust_clip_mask(clip_mask, clip_bounds); - Some(clip_mask as &_) - } - }; - - self.text_pipeline.draw_cached( - content, - *bounds, - *color, - *size, - *line_height, - *font, - *align_x, - *align_y, - *shaping, - *wrapping, - *ellipsis, - pixels, - clip_mask, - transformation, - ); - } - Text::Raw { - raw, - transformation: local_transformation, - } => { - let Some(buffer) = raw.buffer.upgrade() else { - return; - }; - - let transformation = transformation * *local_transformation; - let (width, height) = buffer.size(); - - let physical_bounds = Rectangle::new( - raw.position, - Size::new( - width.unwrap_or(clip_bounds.width), - height.unwrap_or(clip_bounds.height), - ), - ) * transformation; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = - (!physical_bounds.is_within(&clip_bounds)).then_some(clip_mask as &_); - - self.text_pipeline.draw_raw( - &buffer, - raw.position, - raw.color, - pixels, - clip_mask, - transformation, - ); - } - } - } - - pub fn draw_primitive( - &mut self, - primitive: &Primitive, - transformation: Transformation, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - clip_bounds: Rectangle, - ) { - match primitive { - Primitive::Fill { path, paint, rule } => { - let physical_bounds = { - let bounds = path.bounds(); - - Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } * transformation - }; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = - (!physical_bounds.is_within(&clip_bounds)).then_some(clip_mask as &_); - - pixels.fill_path( - path, - paint, - *rule, - into_transform(transformation), - clip_mask, - ); - } - Primitive::Stroke { - path, - paint, - stroke, - } => { - let physical_bounds = { - let bounds = path.bounds(); - - Rectangle { - x: bounds.x() - stroke.width / 2.0, - y: bounds.y() - stroke.width / 2.0, - width: bounds.width() + stroke.width, - height: bounds.height() + stroke.width, - } * transformation - }; - - if !clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = - (!physical_bounds.is_within(&clip_bounds)).then_some(clip_mask as &_); - - pixels.stroke_path( - path, - paint, - stroke, - into_transform(transformation), - clip_mask, - ); - } - } - } - - pub fn draw_image( - &mut self, - image: &Image, - _transformation: Transformation, - _pixels: &mut tiny_skia::PixmapMut<'_>, - _clip_mask: &mut tiny_skia::Mask, - _clip_bounds: Rectangle, - ) { - match image { - #[cfg(feature = "image")] - Image::Raster { - image, - bounds, - clip_bounds: local_clip_bounds, - } => { - let physical_bounds = *local_clip_bounds * _transformation; - - let Some(clip_bounds) = physical_bounds.intersection(&_clip_bounds) else { - return; - }; - - // TODO: Border radius - adjust_clip_mask(_clip_mask, clip_bounds); - - let center = physical_bounds.center(); - let radians = f32::from(image.rotation); - - let transform = into_transform(_transformation).post_rotate_at( - radians.to_degrees(), - center.x, - center.y, - ); - - self.raster_pipeline.draw( - &image.handle, - image.filter_method, - *bounds, - image.opacity, - _pixels, - transform, - Some(_clip_mask), - ); - } - #[cfg(feature = "svg")] - Image::Vector { svg, bounds, .. } => { - let physical_bounds = *bounds * _transformation; - - if !_clip_bounds.intersects(&physical_bounds) { - return; - } - - let clip_mask = - (!physical_bounds.is_within(&_clip_bounds)).then_some(_clip_mask as &_); - - let center = physical_bounds.center(); - let radians = f32::from(svg.rotation); - - let transform = into_transform(_transformation).post_rotate_at( - radians.to_degrees(), - center.x, - center.y, - ); - - self.vector_pipeline.draw( - &svg.handle, - svg.color, - *bounds, - svg.opacity, - _pixels, - transform, - clip_mask, - ); - } - #[cfg(not(feature = "image"))] - Image::Raster { .. } => { - log::warn!("Unsupported primitive in `iced_tiny_skia`: {image:?}",); - } - #[cfg(not(feature = "svg"))] - Image::Vector { .. } => { - log::warn!("Unsupported primitive in `iced_tiny_skia`: {image:?}",); - } - } - } - - pub fn trim(&mut self) { - self.text_pipeline.trim_cache(); - - #[cfg(feature = "image")] - self.raster_pipeline.trim_cache(); - - #[cfg(feature = "svg")] - self.vector_pipeline.trim_cache(); - } -} - -pub fn into_color(color: Color) -> tiny_skia::Color { - tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) - .expect("Convert color from iced to tiny_skia") -} - -fn into_transform(transformation: Transformation) -> tiny_skia::Transform { - let translation = transformation.translation(); - - tiny_skia::Transform { - sx: transformation.scale_factor(), - kx: 0.0, - ky: 0.0, - sy: transformation.scale_factor(), - tx: translation.x, - ty: translation.y, - } -} - -fn rounded_rectangle(bounds: Rectangle, border_radius: [f32; 4]) -> tiny_skia::Path { - let [top_left, top_right, bottom_right, bottom_left] = border_radius; - - if top_left == 0.0 && top_right == 0.0 && bottom_right == 0.0 && bottom_left == 0.0 { - return tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh(bounds.x, bounds.y, bounds.width, bounds.height) - .expect("Build quad rectangle"), - ); - } - - if top_left == top_right - && top_left == bottom_right - && top_left == bottom_left - && top_left == bounds.width / 2.0 - && top_left == bounds.height / 2.0 - { - return tiny_skia::PathBuilder::from_circle( - bounds.x + bounds.width / 2.0, - bounds.y + bounds.height / 2.0, - top_left, - ) - .expect("Build circle path"); - } - - let mut builder = tiny_skia::PathBuilder::new(); - - builder.move_to(bounds.x + top_left, bounds.y); - builder.line_to(bounds.x + bounds.width - top_right, bounds.y); - - if top_right > 0.0 { - arc_to( - &mut builder, - bounds.x + bounds.width - top_right, - bounds.y, - bounds.x + bounds.width, - bounds.y + top_right, - top_right, - ); - } - - maybe_line_to( - &mut builder, - bounds.x + bounds.width, - bounds.y + bounds.height - bottom_right, - ); - - if bottom_right > 0.0 { - arc_to( - &mut builder, - bounds.x + bounds.width, - bounds.y + bounds.height - bottom_right, - bounds.x + bounds.width - bottom_right, - bounds.y + bounds.height, - bottom_right, - ); - } - - maybe_line_to( - &mut builder, - bounds.x + bottom_left, - bounds.y + bounds.height, - ); - - if bottom_left > 0.0 { - arc_to( - &mut builder, - bounds.x + bottom_left, - bounds.y + bounds.height, - bounds.x, - bounds.y + bounds.height - bottom_left, - bottom_left, - ); - } - - maybe_line_to(&mut builder, bounds.x, bounds.y + top_left); - - if top_left > 0.0 { - arc_to( - &mut builder, - bounds.x, - bounds.y + top_left, - bounds.x + top_left, - bounds.y, - top_left, - ); - } - - builder.finish().expect("Build rounded rectangle path") -} - -fn maybe_line_to(path: &mut tiny_skia::PathBuilder, x: f32, y: f32) { - if path.last_point() != Some(tiny_skia::Point { x, y }) { - path.line_to(x, y); - } -} - -fn arc_to( - path: &mut tiny_skia::PathBuilder, - x_from: f32, - y_from: f32, - x_to: f32, - y_to: f32, - radius: f32, -) { - let svg_arc = kurbo::SvgArc { - from: kurbo::Point::new(f64::from(x_from), f64::from(y_from)), - to: kurbo::Point::new(f64::from(x_to), f64::from(y_to)), - radii: kurbo::Vec2::new(f64::from(radius), f64::from(radius)), - x_rotation: 0.0, - large_arc: false, - sweep: true, - }; - - match kurbo::Arc::from_svg_arc(&svg_arc) { - Some(arc) => { - arc.to_cubic_beziers(0.1, |p1, p2, p| { - path.cubic_to( - p1.x as f32, - p1.y as f32, - p2.x as f32, - p2.y as f32, - p.x as f32, - p.y as f32, - ); - }); - } - None => { - path.line_to(x_to, y_to); - } - } -} - -fn smoothstep(a: f32, b: f32, x: f32) -> f32 { - let x = ((x - a) / (b - a)).clamp(0.0, 1.0); - - x * x * (3.0 - 2.0 * x) -} - -fn rounded_box_sdf(to_center: Vector, size: tiny_skia::Size, radii: &[f32]) -> f32 { - let radius = match (to_center.x > 0.0, to_center.y > 0.0) { - (true, true) => radii[2], - (true, false) => radii[1], - (false, true) => radii[3], - (false, false) => radii[0], - }; - - let x = (to_center.x.abs() - size.width() + radius).max(0.0); - let y = (to_center.y.abs() - size.height() + radius).max(0.0); - - (x.powf(2.0) + y.powf(2.0)).sqrt() - radius -} - -pub fn adjust_clip_mask(clip_mask: &mut tiny_skia::Mask, bounds: Rectangle) { - clip_mask.clear(); - - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh(bounds.x, bounds.y, bounds.width, bounds.height) - .expect("Create clip rectangle"), - ); - - clip_mask.fill_path( - &path, - tiny_skia::FillRule::EvenOdd, - false, - tiny_skia::Transform::default(), - ); -} diff --git a/tiny_skia/src/geometry.rs b/tiny_skia/src/geometry.rs deleted file mode 100644 index cc9913ef1e..0000000000 --- a/tiny_skia/src/geometry.rs +++ /dev/null @@ -1,479 +0,0 @@ -use crate::Primitive; -use crate::core::text::LineHeight; -use crate::core::{self, Pixels, Point, Radians, Rectangle, Size, Svg, Vector}; -use crate::graphics::cache::{self, Cached}; -use crate::graphics::geometry::fill::{self, Fill}; -use crate::graphics::geometry::stroke::{self, Stroke}; -use crate::graphics::geometry::{self, Path, Style}; -use crate::graphics::{self, Gradient, Image, Text}; - -use std::sync::Arc; - -#[derive(Debug)] -pub enum Geometry { - Live { - text: Vec, - images: Vec, - primitives: Vec, - clip_bounds: Rectangle, - }, - Cache(Cache), -} - -#[derive(Debug, Clone)] -pub struct Cache { - pub text: Arc<[Text]>, - pub images: Arc<[graphics::Image]>, - pub primitives: Arc<[Primitive]>, - pub clip_bounds: Rectangle, -} - -impl Cached for Geometry { - type Cache = Cache; - - fn load(cache: &Cache) -> Self { - Self::Cache(cache.clone()) - } - - fn cache(self, _group: cache::Group, _previous: Option) -> Cache { - match self { - Self::Live { - primitives, - images, - text, - clip_bounds, - } => Cache { - primitives: Arc::from(primitives), - images: Arc::from(images), - text: Arc::from(text), - clip_bounds, - }, - Self::Cache(cache) => cache, - } - } -} - -#[derive(Debug)] -pub struct Frame { - clip_bounds: Rectangle, - transform: tiny_skia::Transform, - stack: Vec, - primitives: Vec, - images: Vec, - text: Vec, -} - -impl Frame { - pub fn new(bounds: Rectangle) -> Self { - Self { - clip_bounds: bounds, - stack: Vec::new(), - primitives: Vec::new(), - images: Vec::new(), - text: Vec::new(), - transform: tiny_skia::Transform::identity(), - } - } -} - -impl geometry::frame::Backend for Frame { - type Geometry = Geometry; - - fn width(&self) -> f32 { - self.clip_bounds.width - } - - fn height(&self) -> f32 { - self.clip_bounds.height - } - - fn size(&self) -> Size { - self.clip_bounds.size() - } - - fn center(&self) -> Point { - Point::new(self.clip_bounds.width / 2.0, self.clip_bounds.height / 2.0) - } - - fn fill(&mut self, path: &Path, fill: impl Into) { - let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { - return; - }; - - let fill = fill.into(); - - let mut paint = into_paint(fill.style); - paint.shader.transform(self.transform); - - self.primitives.push(Primitive::Fill { - path, - paint, - rule: into_fill_rule(fill.rule), - }); - } - - fn fill_rectangle(&mut self, top_left: Point, size: Size, fill: impl Into) { - let Some(path) = convert_path(&Path::rectangle(top_left, size)) - .and_then(|path| path.transform(self.transform)) - else { - return; - }; - - let fill = fill.into(); - - let mut paint = tiny_skia::Paint { - anti_alias: false, - ..into_paint(fill.style) - }; - paint.shader.transform(self.transform); - - self.primitives.push(Primitive::Fill { - path, - paint, - rule: into_fill_rule(fill.rule), - }); - } - - fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { - let Some(path) = convert_path(path).and_then(|path| path.transform(self.transform)) else { - return; - }; - - let stroke = stroke.into(); - let skia_stroke = into_stroke(&stroke); - - let mut paint = into_paint(stroke.style); - paint.shader.transform(self.transform); - - self.primitives.push(Primitive::Stroke { - path, - paint, - stroke: skia_stroke, - }); - } - - fn stroke_rectangle<'a>(&mut self, top_left: Point, size: Size, stroke: impl Into>) { - self.stroke(&Path::rectangle(top_left, size), stroke); - } - - fn fill_text(&mut self, text: impl Into) { - let text = text.into(); - - let (scale_x, scale_y) = self.transform.get_scale(); - - if !self.transform.has_skew() && scale_x == scale_y && scale_x > 0.0 && scale_y > 0.0 { - let (bounds, size, line_height) = if self.transform.is_identity() { - ( - Rectangle::new(text.position, Size::new(text.max_width, f32::INFINITY)), - text.size, - text.line_height, - ) - } else { - let mut position = [tiny_skia::Point { - x: text.position.x, - y: text.position.y, - }]; - - self.transform.map_points(&mut position); - - let size = text.size.0 * scale_y; - - let line_height = match text.line_height { - LineHeight::Absolute(size) => LineHeight::Absolute(Pixels(size.0 * scale_y)), - LineHeight::Relative(factor) => LineHeight::Relative(factor), - }; - - ( - Rectangle { - x: position[0].x, - y: position[0].y, - width: text.max_width * scale_x, - height: f32::INFINITY, - }, - size.into(), - line_height, - ) - }; - - // TODO: Honor layering! - self.text.push(Text::Cached { - content: text.content, - bounds, - color: text.color, - size, - line_height: line_height.to_absolute(size), - font: text.font, - align_x: text.align_x, - align_y: text.align_y, - shaping: text.shaping, - wrapping: text.wrapping, - ellipsis: text.ellipsis, - clip_bounds: Rectangle::with_size(Size::INFINITE), - }); - } else { - text.draw_with(|path, color| self.fill(&path, color)); - } - } - - fn stroke_text<'a>(&mut self, text: impl Into, stroke: impl Into>) { - let text = text.into(); - let stroke = stroke.into(); - - text.draw_with(|path, _color| self.stroke(&path, stroke)); - } - - fn push_transform(&mut self) { - self.stack.push(self.transform); - } - - fn pop_transform(&mut self) { - self.transform = self.stack.pop().expect("Pop transform"); - } - - fn draft(&mut self, clip_bounds: Rectangle) -> Self { - Self::new(clip_bounds) - } - - fn paste(&mut self, frame: Self) { - self.primitives.extend(frame.primitives); - self.text.extend(frame.text); - self.images.extend(frame.images); - } - - fn translate(&mut self, translation: Vector) { - self.transform = self.transform.pre_translate(translation.x, translation.y); - } - - fn rotate(&mut self, angle: impl Into) { - self.transform = self.transform.pre_concat(tiny_skia::Transform::from_rotate( - angle.into().0.to_degrees(), - )); - } - - fn scale(&mut self, scale: impl Into) { - let scale = scale.into(); - - self.scale_nonuniform(Vector { x: scale, y: scale }); - } - - fn scale_nonuniform(&mut self, scale: impl Into) { - let scale = scale.into(); - - self.transform = self.transform.pre_scale(scale.x, scale.y); - } - - fn into_geometry(self) -> Geometry { - Geometry::Live { - primitives: self.primitives, - images: self.images, - text: self.text, - clip_bounds: self.clip_bounds, - } - } - - fn draw_image(&mut self, bounds: Rectangle, image: impl Into) { - let mut image = image.into(); - - let (bounds, external_rotation) = transform_rectangle(bounds, self.transform); - - image.rotation += external_rotation; - - self.images.push(graphics::Image::Raster { - image, - bounds, - clip_bounds: self.clip_bounds, - }); - } - - fn draw_svg(&mut self, bounds: Rectangle, svg: impl Into) { - let mut svg = svg.into(); - - let (bounds, external_rotation) = transform_rectangle(bounds, self.transform); - - svg.rotation += external_rotation; - - self.images.push(Image::Vector { - svg, - bounds, - clip_bounds: self.clip_bounds, - }); - } -} - -fn transform_rectangle( - rectangle: Rectangle, - transform: tiny_skia::Transform, -) -> (Rectangle, Radians) { - let mut top_left = tiny_skia::Point { - x: rectangle.x, - y: rectangle.y, - }; - - let mut top_right = tiny_skia::Point { - x: rectangle.x + rectangle.width, - y: rectangle.y, - }; - - let mut bottom_left = tiny_skia::Point { - x: rectangle.x, - y: rectangle.y + rectangle.height, - }; - - transform.map_point(&mut top_left); - transform.map_point(&mut top_right); - transform.map_point(&mut bottom_left); - - Rectangle::with_vertices( - Point::new(top_left.x, top_left.y), - Point::new(top_right.x, top_right.y), - Point::new(bottom_left.x, bottom_left.y), - ) -} - -fn convert_path(path: &Path) -> Option { - use iced_graphics::geometry::path::lyon_path; - - let mut builder = tiny_skia::PathBuilder::new(); - let mut last_point = lyon_path::math::Point::default(); - - for event in path.raw() { - match event { - lyon_path::Event::Begin { at } => { - builder.move_to(at.x, at.y); - - last_point = at; - } - lyon_path::Event::Line { from, to } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder.line_to(to.x, to.y); - - last_point = to; - } - lyon_path::Event::Quadratic { from, ctrl, to } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder.quad_to(ctrl.x, ctrl.y, to.x, to.y); - - last_point = to; - } - lyon_path::Event::Cubic { - from, - ctrl1, - ctrl2, - to, - } => { - if last_point != from { - builder.move_to(from.x, from.y); - } - - builder.cubic_to(ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y); - - last_point = to; - } - lyon_path::Event::End { close, .. } => { - if close { - builder.close(); - } - } - } - } - - let result = builder.finish(); - - #[cfg(debug_assertions)] - if result.is_none() { - log::warn!("Invalid path: {:?}", path.raw()); - } - - result -} - -pub fn into_paint(style: Style) -> tiny_skia::Paint<'static> { - tiny_skia::Paint { - shader: match style { - Style::Solid(color) => tiny_skia::Shader::SolidColor( - tiny_skia::Color::from_rgba(color.b, color.g, color.r, color.a) - .expect("Create color"), - ), - Style::Gradient(gradient) => match gradient { - Gradient::Linear(linear) => { - let stops: Vec = linear - .stops - .into_iter() - .flatten() - .map(|stop| { - tiny_skia::GradientStop::new( - stop.offset, - tiny_skia::Color::from_rgba( - stop.color.b, - stop.color.g, - stop.color.r, - stop.color.a, - ) - .expect("Create color"), - ) - }) - .collect(); - - tiny_skia::LinearGradient::new( - tiny_skia::Point { - x: linear.start.x, - y: linear.start.y, - }, - tiny_skia::Point { - x: linear.end.x, - y: linear.end.y, - }, - if stops.is_empty() { - vec![tiny_skia::GradientStop::new(0.0, tiny_skia::Color::BLACK)] - } else { - stops - }, - tiny_skia::SpreadMode::Pad, - tiny_skia::Transform::identity(), - ) - .expect("Create linear gradient") - } - }, - }, - anti_alias: true, - ..Default::default() - } -} - -pub fn into_fill_rule(rule: fill::Rule) -> tiny_skia::FillRule { - match rule { - fill::Rule::EvenOdd => tiny_skia::FillRule::EvenOdd, - fill::Rule::NonZero => tiny_skia::FillRule::Winding, - } -} - -pub fn into_stroke(stroke: &Stroke<'_>) -> tiny_skia::Stroke { - tiny_skia::Stroke { - width: stroke.width, - line_cap: match stroke.line_cap { - stroke::LineCap::Butt => tiny_skia::LineCap::Butt, - stroke::LineCap::Square => tiny_skia::LineCap::Square, - stroke::LineCap::Round => tiny_skia::LineCap::Round, - }, - line_join: match stroke.line_join { - stroke::LineJoin::Miter => tiny_skia::LineJoin::Miter, - stroke::LineJoin::Round => tiny_skia::LineJoin::Round, - stroke::LineJoin::Bevel => tiny_skia::LineJoin::Bevel, - }, - dash: if stroke.line_dash.segments.is_empty() { - None - } else { - tiny_skia::StrokeDash::new( - stroke.line_dash.segments.into(), - stroke.line_dash.offset as f32, - ) - }, - ..Default::default() - } -} diff --git a/tiny_skia/src/lib.rs b/tiny_skia/src/lib.rs deleted file mode 100644 index e72702c83a..0000000000 --- a/tiny_skia/src/lib.rs +++ /dev/null @@ -1,414 +0,0 @@ -#![allow(missing_docs)] -#![cfg_attr(docsrs, feature(doc_cfg))] -pub mod window; - -mod engine; -mod layer; -mod primitive; -mod text; - -#[cfg(feature = "image")] -mod raster; - -#[cfg(feature = "svg")] -mod vector; - -#[cfg(feature = "geometry")] -pub mod geometry; - -use iced_debug as debug; -pub use iced_graphics as graphics; -pub use iced_graphics::core; - -pub use layer::Layer; -pub use primitive::Primitive; - -#[cfg(feature = "geometry")] -pub use geometry::Geometry; - -use crate::core::renderer; -use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation}; -use crate::engine::Engine; -use crate::graphics::Viewport; -use crate::graphics::compositor; -use crate::graphics::text::{Editor, Paragraph}; - -/// A [`tiny-skia`] graphics renderer for [`iced`]. -/// -/// [`tiny-skia`]: https://github.com/RazrFalcon/tiny-skia -/// [`iced`]: https://github.com/iced-rs/iced -#[derive(Debug)] -pub struct Renderer { - settings: renderer::Settings, - layers: layer::Stack, - engine: Engine, // TODO: Shared engine -} - -impl Renderer { - pub fn new(settings: renderer::Settings) -> Self { - Self { - settings, - layers: layer::Stack::new(), - engine: Engine::new(), - } - } - - pub fn layers(&mut self) -> &[Layer] { - self.layers.flush(); - self.layers.as_slice() - } - - pub fn draw( - &mut self, - pixels: &mut tiny_skia::PixmapMut<'_>, - clip_mask: &mut tiny_skia::Mask, - viewport: &Viewport, - damage: &[Rectangle], - background_color: Color, - ) { - let scale_factor = viewport.scale_factor(); - self.layers.flush(); - - for &damage_bounds in damage { - let damage_bounds = damage_bounds * scale_factor; - - let path = tiny_skia::PathBuilder::from_rect( - tiny_skia::Rect::from_xywh( - damage_bounds.x, - damage_bounds.y, - damage_bounds.width, - damage_bounds.height, - ) - .expect("Create damage rectangle"), - ); - - pixels.fill_path( - &path, - &tiny_skia::Paint { - shader: tiny_skia::Shader::SolidColor(engine::into_color(background_color)), - anti_alias: false, - blend_mode: tiny_skia::BlendMode::Source, - ..Default::default() - }, - tiny_skia::FillRule::default(), - tiny_skia::Transform::identity(), - None, - ); - - for layer in self.layers.iter() { - let Some(layer_bounds) = damage_bounds.intersection(&(layer.bounds * scale_factor)) - else { - continue; - }; - - engine::adjust_clip_mask(clip_mask, layer_bounds); - - if !layer.quads.is_empty() { - let render_span = debug::render(debug::Primitive::Quad); - for (quad, background) in &layer.quads { - self.engine.draw_quad( - quad, - background, - Transformation::scale(scale_factor), - pixels, - clip_mask, - layer_bounds, - ); - } - render_span.finish(); - } - - if !layer.primitives.is_empty() { - let render_span = debug::render(debug::Primitive::Triangle); - - for group in &layer.primitives { - let Some(group_bounds) = - (group.clip_bounds() * scale_factor).intersection(&layer_bounds) - else { - continue; - }; - - engine::adjust_clip_mask(clip_mask, group_bounds); - - for primitive in group.as_slice() { - self.engine.draw_primitive( - primitive, - Transformation::scale(scale_factor) * group.transformation(), - pixels, - clip_mask, - group_bounds, - ); - } - - engine::adjust_clip_mask(clip_mask, layer_bounds); - } - - render_span.finish(); - } - - if !layer.images.is_empty() { - let render_span = debug::render(debug::Primitive::Image); - - for image in &layer.images { - self.engine.draw_image( - image, - Transformation::scale(scale_factor), - pixels, - clip_mask, - layer_bounds, - ); - } - - render_span.finish(); - } - - if !layer.text.is_empty() { - let render_span = debug::render(debug::Primitive::Image); - - for group in &layer.text { - for text in group.as_slice() { - self.engine.draw_text( - text, - Transformation::scale(scale_factor) * group.transformation(), - pixels, - clip_mask, - layer_bounds, - ); - } - } - - render_span.finish(); - } - } - } - - self.engine.trim(); - } -} - -impl core::Renderer for Renderer { - fn start_layer(&mut self, bounds: Rectangle) { - self.layers.push_clip(bounds); - } - - fn end_layer(&mut self) { - self.layers.pop_clip(); - } - - fn start_transformation(&mut self, transformation: Transformation) { - self.layers.push_transformation(transformation); - } - - fn end_transformation(&mut self) { - self.layers.pop_transformation(); - } - - fn fill_quad(&mut self, quad: renderer::Quad, background: impl Into) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_quad(quad, background.into(), transformation); - } - - fn allocate_image( - &mut self, - _handle: &core::image::Handle, - callback: impl FnOnce(Result) + Send + 'static, - ) { - #[cfg(feature = "image")] - #[allow(unsafe_code)] - // TODO: Concurrency - callback(self.engine.raster_pipeline.load(_handle)); - - #[cfg(not(feature = "image"))] - callback(Err(core::image::Error::Unsupported)); - } - - fn hint(&mut self, _scale_factor: f32) { - // TODO: No hinting supported - // We'll replace `tiny-skia` with `vello_cpu` soon - } - - fn scale_factor(&self) -> Option { - None - } - - fn reset(&mut self, new_bounds: Rectangle) { - self.layers.reset(new_bounds); - } -} - -impl core::text::Renderer for Renderer { - type Font = Font; - type Paragraph = Paragraph; - type Editor = Editor; - - const ICON_FONT: Font = Font::new("Iced-Icons"); - const CHECKMARK_ICON: char = '\u{f00c}'; - const ARROW_DOWN_ICON: char = '\u{e800}'; - const ICED_LOGO: char = '\u{e801}'; - const SCROLL_UP_ICON: char = '\u{e802}'; - const SCROLL_DOWN_ICON: char = '\u{e803}'; - const SCROLL_LEFT_ICON: char = '\u{e804}'; - const SCROLL_RIGHT_ICON: char = '\u{e805}'; - - fn default_font(&self) -> Self::Font { - self.settings.default_font - } - - fn default_size(&self) -> Pixels { - self.settings.default_text_size - } - - fn fill_paragraph( - &mut self, - text: &Self::Paragraph, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - - layer.draw_paragraph(text, position, color, clip_bounds, transformation); - } - - fn fill_editor( - &mut self, - editor: &Self::Editor, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_editor(editor, position, color, clip_bounds, transformation); - } - - fn fill_text( - &mut self, - text: core::Text, - position: Point, - color: Color, - clip_bounds: Rectangle, - ) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_text(text, position, color, clip_bounds, transformation); - } -} - -impl graphics::text::Renderer for Renderer { - fn fill_raw(&mut self, raw: graphics::text::Raw) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_text_raw(raw, transformation); - } -} - -#[cfg(feature = "geometry")] -impl graphics::geometry::Renderer for Renderer { - type Geometry = Geometry; - type Frame = geometry::Frame; - - fn new_frame(&self, bounds: Rectangle) -> Self::Frame { - geometry::Frame::new(bounds) - } - - fn draw_geometry(&mut self, geometry: Self::Geometry) { - let (layer, transformation) = self.layers.current_mut(); - - match geometry { - Geometry::Live { - primitives, - images, - text, - clip_bounds, - } => { - layer.draw_primitive_group(primitives, clip_bounds, transformation); - - for image in images { - layer.draw_image(image, transformation); - } - - layer.draw_text_group(text, clip_bounds, transformation); - } - Geometry::Cache(cache) => { - layer.draw_primitive_cache(cache.primitives, cache.clip_bounds, transformation); - - for image in cache.images.iter() { - layer.draw_image(image.clone(), transformation); - } - - layer.draw_text_cache(cache.text, cache.clip_bounds, transformation); - } - } - } -} - -impl graphics::mesh::Renderer for Renderer { - fn draw_mesh(&mut self, _mesh: graphics::Mesh) { - log::warn!("iced_tiny_skia does not support drawing meshes"); - } - - fn draw_mesh_cache(&mut self, _cache: iced_graphics::mesh::Cache) { - log::warn!("iced_tiny_skia does not support drawing meshes"); - } -} - -#[cfg(feature = "image")] -impl core::image::Renderer for Renderer { - type Handle = core::image::Handle; - - fn load_image( - &self, - handle: &Self::Handle, - ) -> Result { - self.engine.raster_pipeline.load(handle) - } - - fn measure_image(&self, handle: &Self::Handle) -> Option> { - self.engine.raster_pipeline.dimensions(handle) - } - - fn draw_image(&mut self, image: core::Image, bounds: Rectangle, clip_bounds: Rectangle) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_raster(image, bounds, clip_bounds, transformation); - } -} - -#[cfg(feature = "svg")] -impl core::svg::Renderer for Renderer { - fn measure_svg(&self, handle: &core::svg::Handle) -> crate::core::Size { - self.engine.vector_pipeline.viewport_dimensions(handle) - } - - fn draw_svg(&mut self, svg: core::Svg, bounds: Rectangle, clip_bounds: Rectangle) { - let (layer, transformation) = self.layers.current_mut(); - layer.draw_svg(svg, bounds, clip_bounds, transformation); - } -} - -impl compositor::Default for Renderer { - type Compositor = window::Compositor; -} - -impl renderer::Headless for Renderer { - async fn new(settings: renderer::Settings, backend: Option<&str>) -> Option { - if backend.is_some_and(|backend| !["tiny-skia", "tiny_skia", "software"].contains(&backend)) - { - return None; - } - - Some(Self::new(settings)) - } - - fn name(&self) -> String { - "tiny-skia".to_owned() - } - - fn screenshot( - &mut self, - size: Size, - scale_factor: f32, - background_color: Color, - ) -> Vec { - let viewport = Viewport::with_physical_size(size, scale_factor); - - window::compositor::screenshot(self, &viewport, background_color) - } -} diff --git a/tiny_skia/src/primitive.rs b/tiny_skia/src/primitive.rs deleted file mode 100644 index 5de51047e2..0000000000 --- a/tiny_skia/src/primitive.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::core::Rectangle; - -#[derive(Debug, Clone, PartialEq)] -pub enum Primitive { - /// A path filled with some paint. - Fill { - /// The path to fill. - path: tiny_skia::Path, - /// The paint to use. - paint: tiny_skia::Paint<'static>, - /// The fill rule to follow. - rule: tiny_skia::FillRule, - }, - /// A path stroked with some paint. - Stroke { - /// The path to stroke. - path: tiny_skia::Path, - /// The paint to use. - paint: tiny_skia::Paint<'static>, - /// The stroke settings. - stroke: tiny_skia::Stroke, - }, -} - -impl Primitive { - /// Returns the visible bounds of the [`Primitive`]. - pub fn visible_bounds(&self) -> Rectangle { - let bounds = match self { - Primitive::Fill { path, .. } => path.bounds(), - Primitive::Stroke { path, .. } => path.bounds(), - }; - - Rectangle { - x: bounds.x(), - y: bounds.y(), - width: bounds.width(), - height: bounds.height(), - } - } -} diff --git a/tiny_skia/src/raster.rs b/tiny_skia/src/raster.rs deleted file mode 100644 index bc3323d6d3..0000000000 --- a/tiny_skia/src/raster.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::core::image as raster; -use crate::core::{Rectangle, Size}; -use crate::graphics; - -use rustc_hash::{FxHashMap, FxHashSet}; -use std::cell::RefCell; -use std::collections::hash_map; - -#[derive(Debug)] -pub struct Pipeline { - cache: RefCell, -} - -impl Pipeline { - pub fn new() -> Self { - Self { - cache: RefCell::new(Cache::default()), - } - } - - pub fn load(&self, handle: &raster::Handle) -> Result { - let mut cache = self.cache.borrow_mut(); - let image = cache.allocate(handle)?; - - #[allow(unsafe_code)] - Ok(unsafe { raster::allocate(handle, Size::new(image.width(), image.height())) }) - } - - pub fn dimensions(&self, handle: &raster::Handle) -> Option> { - let mut cache = self.cache.borrow_mut(); - let image = cache.allocate(handle).ok()?; - - Some(Size::new(image.width(), image.height())) - } - - pub fn draw( - &mut self, - handle: &raster::Handle, - filter_method: raster::FilterMethod, - bounds: Rectangle, - opacity: f32, - pixels: &mut tiny_skia::PixmapMut<'_>, - transform: tiny_skia::Transform, - clip_mask: Option<&tiny_skia::Mask>, - ) { - let mut cache = self.cache.borrow_mut(); - - let Ok(image) = cache.allocate(handle) else { - return; - }; - - let width_scale = bounds.width / image.width() as f32; - let height_scale = bounds.height / image.height() as f32; - - let transform = transform.pre_scale(width_scale, height_scale); - - let quality = match filter_method { - raster::FilterMethod::Linear => tiny_skia::FilterQuality::Bilinear, - raster::FilterMethod::Nearest => tiny_skia::FilterQuality::Nearest, - }; - - pixels.draw_pixmap( - (bounds.x / width_scale) as i32, - (bounds.y / height_scale) as i32, - image, - &tiny_skia::PixmapPaint { - quality, - opacity, - ..Default::default() - }, - transform, - clip_mask, - ); - } - - pub fn trim_cache(&mut self) { - self.cache.borrow_mut().trim(); - } -} - -#[derive(Debug, Default)] -struct Cache { - entries: FxHashMap>, - hits: FxHashSet, -} - -impl Cache { - pub fn allocate( - &mut self, - handle: &raster::Handle, - ) -> Result, raster::Error> { - let id = handle.id(); - - if let hash_map::Entry::Vacant(entry) = self.entries.entry(id) { - let image = match graphics::image::load(handle) { - Ok(image) => image, - Err(error) => { - let _ = entry.insert(None); - - return Err(error); - } - }; - - if image.width() == 0 || image.height() == 0 { - return Err(raster::Error::Empty); - } - - let mut buffer = vec![0u32; image.width() as usize * image.height() as usize]; - - for (i, pixel) in image.pixels().enumerate() { - let [r, g, b, a] = pixel.0; - - buffer[i] = bytemuck::cast(tiny_skia::ColorU8::from_rgba(b, g, r, a).premultiply()); - } - - let _ = entry.insert(Some(Entry { - width: image.width(), - height: image.height(), - pixels: buffer, - })); - } - - let _ = self.hits.insert(id); - - Ok(self - .entries - .get(&id) - .unwrap() - .as_ref() - .map(|entry| { - tiny_skia::PixmapRef::from_bytes( - bytemuck::cast_slice(&entry.pixels), - entry.width, - entry.height, - ) - .expect("Build pixmap from image bytes") - }) - .expect("Image should be allocated")) - } - - fn trim(&mut self) { - self.entries.retain(|key, _| self.hits.contains(key)); - self.hits.clear(); - } -} - -#[derive(Debug)] -struct Entry { - width: u32, - height: u32, - pixels: Vec, -} diff --git a/tiny_skia/src/window.rs b/tiny_skia/src/window.rs deleted file mode 100644 index d8d9378e16..0000000000 --- a/tiny_skia/src/window.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod compositor; - -pub use compositor::{Compositor, Surface}; diff --git a/tiny_skia/src/window/compositor.rs b/tiny_skia/src/window/compositor.rs deleted file mode 100644 index 223e230a12..0000000000 --- a/tiny_skia/src/window/compositor.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::core::renderer; -use crate::core::{Color, Rectangle, Size}; -use crate::graphics::compositor::{self, Information}; -use crate::graphics::damage; -use crate::graphics::error::{self, Error}; -use crate::graphics::{Shell, Viewport}; -use crate::{Layer, Renderer}; - -use std::collections::VecDeque; -use std::num::NonZeroU32; - -pub struct Compositor { - context: softbuffer::Context>, -} - -pub struct Surface { - window: softbuffer::Surface, Box>, - clip_mask: tiny_skia::Mask, - frames: VecDeque