From daebb5a033ee1a71d1a0aa65d8b25ffc9243f3bb Mon Sep 17 00:00:00 2001 From: sagudev <16504129+sagudev@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:14:25 +0200 Subject: [PATCH] canvas: Move peniko/kurbo conversions in separate file (#38279) This will allow reusing those conversions in vello_cpu backend. Testing: Just refactor Signed-off-by: sagudev <16504129+sagudev@users.noreply.github.com> --- Cargo.lock | 1 + Cargo.toml | 1 + components/canvas/Cargo.toml | 3 +- components/canvas/backend.rs | 7 + components/canvas/lib.rs | 2 + components/canvas/peniko_conversions.rs | 209 +++++++++++++++++++++++ components/canvas/vello_backend.rs | 211 +----------------------- 7 files changed, 224 insertions(+), 210 deletions(-) create mode 100644 components/canvas/peniko_conversions.rs diff --git a/Cargo.lock b/Cargo.lock index 258afc80086..8c290eb9066 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1076,6 +1076,7 @@ dependencies = [ "kurbo", "log", "net_traits", + "peniko", "pixels", "pollster", "range", diff --git a/Cargo.toml b/Cargo.toml index 6391d648670..546024a9e4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ num-traits = "0.2" num_cpus = "1.17.0" openxr = "0.19" parking_lot = "0.12" +peniko = "0.4" percent-encoding = "2.3" proc-macro2 = "1" profile_traits = { path = "components/shared/profile" } diff --git a/components/canvas/Cargo.toml b/components/canvas/Cargo.toml index 97b36e10f4b..4b8fd31ccb6 100644 --- a/components/canvas/Cargo.toml +++ b/components/canvas/Cargo.toml @@ -12,7 +12,7 @@ name = "canvas" path = "lib.rs" [features] -vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive"] +vello = ["dep:vello", "dep:pollster", "dep:futures-intrusive", "dep:peniko"] [dependencies] app_units = { workspace = true } @@ -27,6 +27,7 @@ ipc-channel = { workspace = true } kurbo = { workspace = true } log = { workspace = true } net_traits = { workspace = true } +peniko = { workspace = true, optional = true } pixels = { path = "../pixels" } range = { path = "../range" } raqote = "0.8.5" diff --git a/components/canvas/backend.rs b/components/canvas/backend.rs index 5808be49a5b..5b1e50b02f3 100644 --- a/components/canvas/backend.rs +++ b/components/canvas/backend.rs @@ -92,3 +92,10 @@ pub(crate) trait GenericDrawTarget { ) -> (ImageDescriptor, SerializableImageData); fn snapshot(&mut self) -> Snapshot; } + +#[allow(dead_code)] // used by gated backends +/// A version of the `Into` trait from the standard library that can be used +/// to convert between two types that are not defined in the canvas crate. +pub(crate) trait Convert { + fn convert(self) -> T; +} diff --git a/components/canvas/lib.rs b/components/canvas/lib.rs index a0c47557474..00f0b1d5a8e 100644 --- a/components/canvas/lib.rs +++ b/components/canvas/lib.rs @@ -5,6 +5,8 @@ #![deny(unsafe_code)] mod backend; +#[cfg(feature = "vello")] +mod peniko_conversions; mod raqote_backend; #[cfg(feature = "vello")] mod vello_backend; diff --git a/components/canvas/peniko_conversions.rs b/components/canvas/peniko_conversions.rs new file mode 100644 index 00000000000..429f0be9e5a --- /dev/null +++ b/components/canvas/peniko_conversions.rs @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use canvas_traits::canvas::*; +use pixels::{SnapshotAlphaMode, SnapshotPixelFormat}; +use style::color::AbsoluteColor; + +use crate::backend::Convert; +use crate::canvas_data::Filter; + +impl Convert for LineJoinStyle { + fn convert(self) -> kurbo::Join { + match self { + LineJoinStyle::Round => kurbo::Join::Round, + LineJoinStyle::Bevel => kurbo::Join::Bevel, + LineJoinStyle::Miter => kurbo::Join::Miter, + } + } +} + +impl Convert for LineCapStyle { + fn convert(self) -> kurbo::Cap { + match self { + LineCapStyle::Butt => kurbo::Cap::Butt, + LineCapStyle::Round => kurbo::Cap::Round, + LineCapStyle::Square => kurbo::Cap::Square, + } + } +} + +impl Convert for AbsoluteColor { + fn convert(self) -> peniko::Color { + let srgb = self.into_srgb_legacy(); + peniko::Color::new([ + srgb.components.0, + srgb.components.1, + srgb.components.2, + srgb.alpha, + ]) + } +} + +impl Convert for CompositionOrBlending { + fn convert(self) -> peniko::BlendMode { + match self { + CompositionOrBlending::Composition(composition_style) => { + composition_style.convert().into() + }, + CompositionOrBlending::Blending(blending_style) => blending_style.convert().into(), + } + } +} + +impl Convert for CompositionStyle { + fn convert(self) -> peniko::Compose { + match self { + CompositionStyle::SourceIn => peniko::Compose::SrcIn, + CompositionStyle::SourceOut => peniko::Compose::SrcOut, + CompositionStyle::SourceOver => peniko::Compose::SrcOver, + CompositionStyle::SourceAtop => peniko::Compose::SrcAtop, + CompositionStyle::DestinationIn => peniko::Compose::DestIn, + CompositionStyle::DestinationOut => peniko::Compose::DestOut, + CompositionStyle::DestinationOver => peniko::Compose::DestOver, + CompositionStyle::DestinationAtop => peniko::Compose::DestAtop, + CompositionStyle::Copy => peniko::Compose::Copy, + CompositionStyle::Lighter => peniko::Compose::Plus, + CompositionStyle::Xor => peniko::Compose::Xor, + CompositionStyle::Clear => peniko::Compose::Clear, + } + } +} + +impl Convert for BlendingStyle { + fn convert(self) -> peniko::Mix { + match self { + BlendingStyle::Multiply => peniko::Mix::Multiply, + BlendingStyle::Screen => peniko::Mix::Screen, + BlendingStyle::Overlay => peniko::Mix::Overlay, + BlendingStyle::Darken => peniko::Mix::Darken, + BlendingStyle::Lighten => peniko::Mix::Lighten, + BlendingStyle::ColorDodge => peniko::Mix::ColorDodge, + BlendingStyle::ColorBurn => peniko::Mix::ColorBurn, + BlendingStyle::HardLight => peniko::Mix::HardLight, + BlendingStyle::SoftLight => peniko::Mix::SoftLight, + BlendingStyle::Difference => peniko::Mix::Difference, + BlendingStyle::Exclusion => peniko::Mix::Exclusion, + BlendingStyle::Hue => peniko::Mix::Hue, + BlendingStyle::Saturation => peniko::Mix::Saturation, + BlendingStyle::Color => peniko::Mix::Color, + BlendingStyle::Luminosity => peniko::Mix::Luminosity, + } + } +} + +impl Convert for LineOptions { + fn convert(self) -> kurbo::Stroke { + let LineOptions { + width, + cap_style, + join_style, + miter_limit, + dash, + dash_offset, + } = self; + kurbo::Stroke { + width, + join: join_style.convert(), + miter_limit, + start_cap: cap_style.convert(), + end_cap: cap_style.convert(), + dash_pattern: dash.iter().map(|x| *x as f64).collect(), + dash_offset, + } + } +} + +impl Convert for FillOrStrokeStyle { + fn convert(self) -> peniko::Brush { + use canvas_traits::canvas::FillOrStrokeStyle::*; + match self { + Color(absolute_color) => peniko::Brush::Solid(absolute_color.convert()), + LinearGradient(style) => { + let start = kurbo::Point::new(style.x0, style.y0); + let end = kurbo::Point::new(style.x1, style.y1); + let mut gradient = peniko::Gradient::new_linear(start, end); + gradient.stops = style.stops.convert(); + peniko::Brush::Gradient(gradient) + }, + RadialGradient(style) => { + let center1 = kurbo::Point::new(style.x0, style.y0); + let center2 = kurbo::Point::new(style.x1, style.y1); + let mut gradient = peniko::Gradient::new_two_point_radial( + center1, + style.r0 as f32, + center2, + style.r1 as f32, + ); + gradient.stops = style.stops.convert(); + peniko::Brush::Gradient(gradient) + }, + Surface(surface_style) => { + let data = surface_style + .surface_data + .to_owned() + .to_vec( + Some(SnapshotAlphaMode::Transparent { + premultiplied: false, + }), + Some(SnapshotPixelFormat::RGBA), + ) + .0; + peniko::Brush::Image(peniko::Image { + data: peniko::Blob::from(data), + format: peniko::ImageFormat::Rgba8, + width: surface_style.surface_size.width, + height: surface_style.surface_size.height, + x_extend: if surface_style.repeat_x { + peniko::Extend::Repeat + } else { + peniko::Extend::Pad + }, + y_extend: if surface_style.repeat_y { + peniko::Extend::Repeat + } else { + peniko::Extend::Pad + }, + quality: peniko::ImageQuality::Low, + alpha: 1.0, + }) + }, + } + } +} + +impl Convert for AbsoluteColor { + fn convert(self) -> peniko::color::DynamicColor { + peniko::color::DynamicColor::from_alpha_color(self.convert()) + } +} + +impl Convert for CanvasGradientStop { + fn convert(self) -> peniko::ColorStop { + peniko::ColorStop { + offset: self.offset as f32, + color: self.color.convert(), + } + } +} + +impl Convert for Vec { + fn convert(self) -> peniko::ColorStops { + let mut stops = peniko::ColorStops(self.into_iter().map(|item| item.convert()).collect()); + // https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap + stops + .0 + .sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap()); + stops + } +} + +impl Convert for Filter { + fn convert(self) -> peniko::ImageQuality { + match self { + Filter::Bilinear => peniko::ImageQuality::Medium, + Filter::Nearest => peniko::ImageQuality::Low, + } + } +} diff --git a/components/canvas/vello_backend.rs b/components/canvas/vello_backend.rs index e78697b798c..ebbf25fea32 100644 --- a/components/canvas/vello_backend.rs +++ b/components/canvas/vello_backend.rs @@ -17,8 +17,7 @@ use std::num::NonZeroUsize; use std::rc::Rc; use canvas_traits::canvas::{ - BlendingStyle, CanvasGradientStop, CompositionOptions, CompositionOrBlending, CompositionStyle, - FillOrStrokeStyle, LineCapStyle, LineJoinStyle, LineOptions, Path, ShadowOptions, + CompositionOptions, FillOrStrokeStyle, LineOptions, Path, ShadowOptions, }; use compositing_traits::SerializableImageData; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; @@ -26,7 +25,6 @@ use fonts::{ByteIndex, FontIdentifier, FontTemplateRefMethods as _}; use ipc_channel::ipc::IpcSharedMemory; use pixels::{Snapshot, SnapshotAlphaMode, SnapshotPixelFormat}; use range::Range; -use style::color::AbsoluteColor; use vello::wgpu::{ BackendOptions, Backends, BufferDescriptor, BufferUsages, CommandEncoderDescriptor, Device, Extent3d, Instance, InstanceDescriptor, InstanceFlags, MapMode, Queue, TexelCopyBufferInfo, @@ -36,7 +34,7 @@ use vello::wgpu::{ use vello::{kurbo, peniko}; use webrender_api::{ImageDescriptor, ImageDescriptorFlags}; -use crate::backend::GenericDrawTarget; +use crate::backend::{Convert as _, GenericDrawTarget}; use crate::canvas_data::{Filter, TextRun}; thread_local! { @@ -545,208 +543,3 @@ impl GenericDrawTarget for VelloDrawTarget { Some(data) } } - -/// A version of the `Into` trait from the standard library that can be used -/// to convert between two types that are not defined in the canvas crate. -pub(crate) trait Convert { - fn convert(self) -> T; -} - -impl Convert for LineJoinStyle { - fn convert(self) -> kurbo::Join { - match self { - LineJoinStyle::Round => kurbo::Join::Round, - LineJoinStyle::Bevel => kurbo::Join::Bevel, - LineJoinStyle::Miter => kurbo::Join::Miter, - } - } -} - -impl Convert for LineCapStyle { - fn convert(self) -> kurbo::Cap { - match self { - LineCapStyle::Butt => kurbo::Cap::Butt, - LineCapStyle::Round => kurbo::Cap::Round, - LineCapStyle::Square => kurbo::Cap::Square, - } - } -} - -impl Convert for AbsoluteColor { - fn convert(self) -> peniko::Color { - let srgb = self.into_srgb_legacy(); - peniko::Color::new([ - srgb.components.0, - srgb.components.1, - srgb.components.2, - srgb.alpha, - ]) - } -} - -impl Convert for CompositionOrBlending { - fn convert(self) -> peniko::BlendMode { - match self { - CompositionOrBlending::Composition(composition_style) => { - composition_style.convert().into() - }, - CompositionOrBlending::Blending(blending_style) => blending_style.convert().into(), - } - } -} - -impl Convert for CompositionStyle { - fn convert(self) -> peniko::Compose { - match self { - CompositionStyle::SourceIn => peniko::Compose::SrcIn, - CompositionStyle::SourceOut => peniko::Compose::SrcOut, - CompositionStyle::SourceOver => peniko::Compose::SrcOver, - CompositionStyle::SourceAtop => peniko::Compose::SrcAtop, - CompositionStyle::DestinationIn => peniko::Compose::DestIn, - CompositionStyle::DestinationOut => peniko::Compose::DestOut, - CompositionStyle::DestinationOver => peniko::Compose::DestOver, - CompositionStyle::DestinationAtop => peniko::Compose::DestAtop, - CompositionStyle::Copy => peniko::Compose::Copy, - CompositionStyle::Lighter => peniko::Compose::Plus, - CompositionStyle::Xor => peniko::Compose::Xor, - CompositionStyle::Clear => peniko::Compose::Clear, - } - } -} - -impl Convert for BlendingStyle { - fn convert(self) -> peniko::Mix { - match self { - BlendingStyle::Multiply => peniko::Mix::Multiply, - BlendingStyle::Screen => peniko::Mix::Screen, - BlendingStyle::Overlay => peniko::Mix::Overlay, - BlendingStyle::Darken => peniko::Mix::Darken, - BlendingStyle::Lighten => peniko::Mix::Lighten, - BlendingStyle::ColorDodge => peniko::Mix::ColorDodge, - BlendingStyle::ColorBurn => peniko::Mix::ColorBurn, - BlendingStyle::HardLight => peniko::Mix::HardLight, - BlendingStyle::SoftLight => peniko::Mix::SoftLight, - BlendingStyle::Difference => peniko::Mix::Difference, - BlendingStyle::Exclusion => peniko::Mix::Exclusion, - BlendingStyle::Hue => peniko::Mix::Hue, - BlendingStyle::Saturation => peniko::Mix::Saturation, - BlendingStyle::Color => peniko::Mix::Color, - BlendingStyle::Luminosity => peniko::Mix::Luminosity, - } - } -} - -impl Convert for LineOptions { - fn convert(self) -> kurbo::Stroke { - let LineOptions { - width, - cap_style, - join_style, - miter_limit, - dash, - dash_offset, - } = self; - kurbo::Stroke { - width, - join: join_style.convert(), - miter_limit, - start_cap: cap_style.convert(), - end_cap: cap_style.convert(), - dash_pattern: dash.iter().map(|x| *x as f64).collect(), - dash_offset, - } - } -} - -impl Convert for FillOrStrokeStyle { - fn convert(self) -> peniko::Brush { - use canvas_traits::canvas::FillOrStrokeStyle::*; - match self { - Color(absolute_color) => peniko::Brush::Solid(absolute_color.convert()), - LinearGradient(style) => { - let start = kurbo::Point::new(style.x0, style.y0); - let end = kurbo::Point::new(style.x1, style.y1); - let mut gradient = peniko::Gradient::new_linear(start, end); - gradient.stops = style.stops.convert(); - peniko::Brush::Gradient(gradient) - }, - RadialGradient(style) => { - let center1 = kurbo::Point::new(style.x0, style.y0); - let center2 = kurbo::Point::new(style.x1, style.y1); - let mut gradient = peniko::Gradient::new_two_point_radial( - center1, - style.r0 as f32, - center2, - style.r1 as f32, - ); - gradient.stops = style.stops.convert(); - peniko::Brush::Gradient(gradient) - }, - Surface(surface_style) => { - let data = surface_style - .surface_data - .to_owned() - .to_vec( - Some(SnapshotAlphaMode::Transparent { - premultiplied: false, - }), - Some(SnapshotPixelFormat::RGBA), - ) - .0; - peniko::Brush::Image(peniko::Image { - data: peniko::Blob::from(data), - format: peniko::ImageFormat::Rgba8, - width: surface_style.surface_size.width, - height: surface_style.surface_size.height, - x_extend: if surface_style.repeat_x { - peniko::Extend::Repeat - } else { - peniko::Extend::Pad - }, - y_extend: if surface_style.repeat_y { - peniko::Extend::Repeat - } else { - peniko::Extend::Pad - }, - quality: peniko::ImageQuality::Low, - alpha: 1.0, - }) - }, - } - } -} - -impl Convert for AbsoluteColor { - fn convert(self) -> peniko::color::DynamicColor { - peniko::color::DynamicColor::from_alpha_color(self.convert()) - } -} - -impl Convert for CanvasGradientStop { - fn convert(self) -> peniko::ColorStop { - peniko::ColorStop { - offset: self.offset as f32, - color: self.color.convert(), - } - } -} - -impl Convert for Vec { - fn convert(self) -> peniko::ColorStops { - let mut stops = peniko::ColorStops(self.into_iter().map(|item| item.convert()).collect()); - // https://www.w3.org/html/test/results/2dcontext/annotated-spec/canvas.html#testrefs.2d.gradient.interpolate.overlap - stops - .0 - .sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap()); - stops - } -} - -impl Convert for Filter { - fn convert(self) -> peniko::ImageQuality { - match self { - Filter::Bilinear => peniko::ImageQuality::Medium, - Filter::Nearest => peniko::ImageQuality::Low, - } - } -}