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>
This commit is contained in:
sagudev 2025-07-26 15:14:25 +02:00 committed by GitHub
parent 4d899ecba5
commit daebb5a033
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 224 additions and 210 deletions

1
Cargo.lock generated
View file

@ -1076,6 +1076,7 @@ dependencies = [
"kurbo",
"log",
"net_traits",
"peniko",
"pixels",
"pollster",
"range",

View file

@ -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" }

View file

@ -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"

View file

@ -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<T>` 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<T> {
fn convert(self) -> T;
}

View file

@ -5,6 +5,8 @@
#![deny(unsafe_code)]
mod backend;
#[cfg(feature = "vello")]
mod peniko_conversions;
mod raqote_backend;
#[cfg(feature = "vello")]
mod vello_backend;

View file

@ -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<kurbo::Join> 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<kurbo::Cap> 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<peniko::Color> 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<peniko::BlendMode> 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<peniko::Compose> 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<peniko::Mix> 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<kurbo::Stroke> 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<peniko::Brush> 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<peniko::color::DynamicColor> for AbsoluteColor {
fn convert(self) -> peniko::color::DynamicColor {
peniko::color::DynamicColor::from_alpha_color(self.convert())
}
}
impl Convert<peniko::ColorStop> for CanvasGradientStop {
fn convert(self) -> peniko::ColorStop {
peniko::ColorStop {
offset: self.offset as f32,
color: self.color.convert(),
}
}
}
impl Convert<peniko::ColorStops> for Vec<CanvasGradientStop> {
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<peniko::ImageQuality> for Filter {
fn convert(self) -> peniko::ImageQuality {
match self {
Filter::Bilinear => peniko::ImageQuality::Medium,
Filter::Nearest => peniko::ImageQuality::Low,
}
}
}

View file

@ -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<T>` 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<T> {
fn convert(self) -> T;
}
impl Convert<kurbo::Join> 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<kurbo::Cap> 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<peniko::Color> 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<peniko::BlendMode> 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<peniko::Compose> 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<peniko::Mix> 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<kurbo::Stroke> 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<peniko::Brush> 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<peniko::color::DynamicColor> for AbsoluteColor {
fn convert(self) -> peniko::color::DynamicColor {
peniko::color::DynamicColor::from_alpha_color(self.convert())
}
}
impl Convert<peniko::ColorStop> for CanvasGradientStop {
fn convert(self) -> peniko::ColorStop {
peniko::ColorStop {
offset: self.offset as f32,
color: self.color.convert(),
}
}
}
impl Convert<peniko::ColorStops> for Vec<CanvasGradientStop> {
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<peniko::ImageQuality> for Filter {
fn convert(self) -> peniko::ImageQuality {
match self {
Filter::Bilinear => peniko::ImageQuality::Medium,
Filter::Nearest => peniko::ImageQuality::Low,
}
}
}