Skip to content

Commit

Permalink
refactor(painter): 💡 implement svg implement by paintcommand
Browse files Browse the repository at this point in the history
  • Loading branch information
wjian23 committed Sep 20, 2023
1 parent a74e72a commit 3983100
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 143 deletions.
64 changes: 8 additions & 56 deletions gpu/src/gpu_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,7 @@ where
if let Some((rect, mask_head)) = self.new_mask_layer(path) {
self.update_to_radial_gradient_indices();
let prim: RadialGradientPrimitive = RadialGradientPrimitive {
transform: radial_gradient
.transform
.then(&ts)
.inverse()
.unwrap()
.to_array(),
transform: ts.inverse().unwrap().to_array(),
stop_start: self.radial_gradient_stops.len() as u32,
stop_cnt: radial_gradient.stops.len() as u32,
start_center: radial_gradient.start_center.to_array(),
Expand Down Expand Up @@ -279,12 +274,7 @@ where
if let Some((rect, mask_head)) = self.new_mask_layer(path) {
self.update_to_linear_gradient_indices();
let prim: LinearGradientPrimitive = LinearGradientPrimitive {
transform: linear_gradient
.transform
.then(&ts)
.inverse()
.unwrap()
.to_array(),
transform: ts.inverse().unwrap().to_array(),
stop_start: self.linear_gradient_stops.len() as u32,
stop_cnt: linear_gradient.stops.len() as u32,
start_position: linear_gradient.start.to_array(),
Expand Down Expand Up @@ -541,10 +531,7 @@ mod tests {
use ribir_algo::ShareResource;
use ribir_dev_helper::*;
use ribir_geom::*;
use ribir_painter::{
color::{LinearGradient, RadialGradient},
Brush, Color, GradientStop, Painter, Path, PixelImage,
};
use ribir_painter::{Brush, Color, Painter, Path, PixelImage, Svg};

fn painter(bounds: Size) -> Painter { Painter::new(Rect::from_size(bounds)) }

Expand Down Expand Up @@ -682,48 +669,13 @@ mod tests {
painter
}

painter_backend_eq_image_test!(draw_radial_gradient);
fn draw_radial_gradient() -> Painter {
painter_backend_eq_image_test!(draw_svg_gradient);
fn draw_svg_gradient() -> Painter {
let mut painter = painter(Size::new(64., 64.));
let brush = Brush::RadialGradient(RadialGradient {
start_center: Point::new(16., 32.),
start_radius: 0.,
end_center: Point::new(32., 32.),
end_radius: 32.,
stops: vec![
GradientStop::new(Color::RED, 0.),
GradientStop::new(Color::BLUE, 1.),
],
transform: Transform::translation(16., 0.),
..Default::default()
});

painter
.set_brush(brush)
.rect(&Rect::from_size(Size::new(64., 64.)))
.fill();

painter
}

painter_backend_eq_image_test!(draw_linear_gradient);
fn draw_linear_gradient() -> Painter {
let mut painter = painter(Size::new(32., 32.));
let brush = Brush::LinearGradient(LinearGradient {
start: Point::new(0., 0.),
end: Point::new(16., 16.),
stops: vec![
GradientStop::new(Color::RED, 0.),
GradientStop::new(Color::BLUE, 1.),
],
..Default::default()
});

painter
.set_brush(brush)
.rect(&Rect::from_size(Size::new(64., 64.)))
.fill();
let svg =
Svg::parse_from_bytes(include_bytes!("../../tests/assets/fill_with_gradient.svg")).unwrap();

painter.draw_svg(&svg);
painter
}
}
4 changes: 2 additions & 2 deletions gpu/src/gpu_backend/textures_mgr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ where
// apply path transform matrix to view.
.then(path_ts)
}

let prefer_scale: f32 = transform.m11.abs().max(transform.m22.abs());
let Transform { m11, m12, m21, m22, .. } = *transform;
let prefer_scale: f32 = (m11.abs() + m12.abs()).max(m21.abs() + m22.abs());
let key = PathKey::from_path(path);

if let Some(h) = self
Expand Down
4 changes: 1 addition & 3 deletions painter/src/color.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use material_color_utilities_rs::htc;
use ribir_geom::{Point, Transform};
use ribir_geom::Point;
use serde::{Deserialize, Serialize};

use crate::SpreadMethod;
Expand Down Expand Up @@ -30,7 +30,6 @@ pub struct RadialGradient {
pub end_center: Point,
pub end_radius: f32,
pub stops: Vec<GradientStop>,
pub transform: Transform,
pub spread_method: SpreadMethod,
}

Expand All @@ -39,7 +38,6 @@ pub struct LinearGradient {
pub start: Point,
pub end: Point,
pub stops: Vec<GradientStop>,
pub transform: Transform,
pub spread_method: SpreadMethod,
}

Expand Down
34 changes: 24 additions & 10 deletions painter/src/painter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@ pub enum PaintCommand {
PopClip,
}

impl PaintCommand {
pub fn transform(mut self, transform: &Transform) -> Self {
match &mut self {
PaintCommand::ColorPath { path, .. }
| PaintCommand::ImgPath { path, .. }
| PaintCommand::RadialGradient { path, .. }
| PaintCommand::LinearGradient { path, .. }
| PaintCommand::Clip(path) => path.transform(transform),
PaintCommand::PopClip => {}
}
self
}
}

#[derive(Clone)]
struct PainterState {
/// The line width use to stroke path.
Expand Down Expand Up @@ -474,16 +488,11 @@ impl Painter {
}

pub fn draw_svg(&mut self, svg: &Svg) -> &mut Self {
self.scale(svg.view_scale.x, svg.view_scale.y);
svg.paths.iter().for_each(|c| {
self.set_brush(c.brush.clone());
match &c.style {
PathPaintStyle::Fill => self.fill_path(c.path.clone()),
PathPaintStyle::Stroke(options) => self
.set_strokes(options.clone())
.stroke_path(c.path.clone()),
};
});
let transform = *self.get_transform();
svg
.paint_commands
.iter()
.for_each(|c| self.commands.push(c.clone().transform(&transform)));
self
}

Expand Down Expand Up @@ -596,6 +605,11 @@ impl PaintPath {
self.transform = self.transform.then_scale(scale, scale);
self.paint_bounds = self.paint_bounds.scale(scale, scale);
}

pub fn transform(&mut self, transform: &Transform) {
self.transform = self.transform.then(&transform);
self.paint_bounds = self.transform.outer_transformed_rect(self.path.bounds());
}
}

#[cfg(test)]
Expand Down
122 changes: 51 additions & 71 deletions painter/src/svg.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
use crate::{
color::{LinearGradient, RadialGradient},
Brush, Color, GradientStop, LineCap, LineJoin, Path, PathPaintStyle, StrokeOptions,
Brush, Color, GradientStop, LineCap, LineJoin, PaintCommand, Path, StrokeOptions,
};
use ribir_geom::{Point, Size, Transform, Vector};
use ribir_geom::{Point, Rect, Size, Transform};
use serde::{Deserialize, Serialize};
use std::{error::Error, io::Read};
use usvg::{Options, Stop, Tree, TreeParsing};

#[derive(Serialize, Deserialize, Clone)]
pub struct Svg {
pub size: Size,
pub view_scale: Vector,
pub paths: Box<[SvgPath]>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct SvgPath {
pub path: Path,
pub brush: Brush,
pub style: PathPaintStyle,
pub paint_commands: Vec<PaintCommand>,
}

/// Fits size into a viewbox. copy from resvg
Expand All @@ -43,33 +35,28 @@ impl Svg {
let size = tree.size;
let fit_size = fit_view_box(size, &tree.view_box);

let view_scale = Vector::new(
size.width() / fit_size.width(),
size.height() / fit_size.height(),
)
.to_f32();
let t = Transform::translation(-view_rect.x(), -view_rect.y());

let mut t_stack = TransformStack::new(t);
let mut paths = vec![];

let bound_rect = Rect::from_size(Size::new(size.width(), size.height()));
let mut painter = crate::Painter::new(bound_rect);
painter.apply_transform(
&Transform::translation(-view_rect.x(), -view_rect.y()).then_scale(
size.width() / fit_size.width(),
size.height() / fit_size.height(),
),
);
tree.root.traverse().for_each(|edge| match edge {
rctree::NodeEdge::Start(node) => {
use usvg::NodeKind;

painter.save();
match &*node.borrow() {
NodeKind::Path(p) => {
t_stack.push(matrix_convert(p.transform));
painter.apply_transform(&matrix_convert(p.transform));
let path = usvg_path_to_path(p);
let path = path.transform(t_stack.current_transform());
if let Some(ref fill) = p.fill {
let brush = brush_from_usvg_paint(&fill.paint, fill.opacity, &size);

paths.push(SvgPath {
path: path.clone(),
brush,
style: PathPaintStyle::Fill,
});
let (brush, transform) = brush_from_usvg_paint(&fill.paint, fill.opacity, &size);
let mut painter = painter.save_guard();
painter.set_brush(brush.clone());
painter.apply_transform(&transform);
painter.fill_path(path.clone().transform(&transform.inverse().unwrap())); //&o_ts.then(&n_ts.inverse().unwrap())));
}

if let Some(ref stroke) = p.stroke {
Expand All @@ -90,20 +77,20 @@ impl Svg {
miter_limit: stroke.miterlimit.get(),
};

let brush = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size);
paths.push(SvgPath {
path,
brush,
style: PathPaintStyle::Stroke(options),
});
let (brush, transform) = brush_from_usvg_paint(&stroke.paint, stroke.opacity, &size);
let mut painter = painter.save_guard();
painter.set_brush(brush.clone());
painter.apply_transform(&transform);
painter.set_strokes(options);
painter.stroke_path(path.transform(&transform.inverse().unwrap()));
};
}
NodeKind::Image(_) => {
// todo;
log::warn!("[painter]: not support draw embed image in svg, ignored!");
}
NodeKind::Group(ref g) => {
t_stack.push(matrix_convert(g.transform));
painter.apply_transform(&matrix_convert(g.transform));
// todo;
if g.opacity.get() != 1. {
log::warn!("[painter]: not support `opacity` in svg, ignored!");
Expand All @@ -124,14 +111,13 @@ impl Svg {
}
}
rctree::NodeEdge::End(_) => {
t_stack.pop();
painter.restore();
}
});

Ok(Svg {
size: Size::new(size.width(), size.height()),
paths: paths.into_boxed_slice(),
view_scale,
paint_commands: painter.finish(),
})
}

Expand Down Expand Up @@ -182,11 +168,18 @@ fn matrix_convert(t: usvg::Transform) -> Transform {
Transform::new(sx, ky, kx, sy, tx, ty)
}

fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usvg::Size) -> Brush {
fn brush_from_usvg_paint(
paint: &usvg::Paint,
opacity: usvg::Opacity,
size: &usvg::Size,
) -> (Brush, Transform) {
match paint {
usvg::Paint::Color(usvg::Color { red, green, blue }) => Color::from_rgb(*red, *green, *blue)
.with_alpha(opacity.get())
.into(),
usvg::Paint::Color(usvg::Color { red, green, blue }) => (
Color::from_rgb(*red, *green, *blue)
.with_alpha(opacity.get())
.into(),
Transform::identity(),
),
usvg::Paint::LinearGradient(linear) => {
let stops = convert_to_gradient_stops(&linear.stops);
let size_scale = match linear.units {
Expand All @@ -195,12 +188,15 @@ fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usv
};
let gradient = LinearGradient {
start: Point::new(linear.x1 * size_scale.0, linear.y1 * size_scale.1),
end: Point::new(linear.y2 * size_scale.0, linear.y2 * size_scale.1),
end: Point::new(linear.x2 * size_scale.0, linear.y2 * size_scale.1),
stops,
transform: matrix_convert(linear.transform),
spread_method: linear.spread_method.into(),
};
Brush::LinearGradient(gradient)

(
Brush::LinearGradient(gradient),
matrix_convert(linear.transform),
)
}
usvg::Paint::RadialGradient(radial_gradient) => {
let stops = convert_to_gradient_stops(&radial_gradient.stops);
Expand All @@ -213,21 +209,24 @@ fn brush_from_usvg_paint(paint: &usvg::Paint, opacity: usvg::Opacity, size: &usv
radial_gradient.fx * size_scale.0,
radial_gradient.fy * size_scale.1,
),
start_radius: 0.,
start_radius: 0., // usvg not support fr
end_center: Point::new(
radial_gradient.cx * size_scale.0,
radial_gradient.cy * size_scale.1,
),
end_radius: radial_gradient.r.get() * size_scale.0,
stops,
transform: matrix_convert(radial_gradient.transform),
spread_method: radial_gradient.spread_method.into(),
};
Brush::RadialGradient(gradient)

(
Brush::RadialGradient(gradient),
matrix_convert(radial_gradient.transform),
)
}
paint => {
log::warn!("[painter]: not support `{paint:?}` in svg, use black instead!");
Color::BLACK.into()
(Color::BLACK.into(), Transform::identity())
}
}
}
Expand Down Expand Up @@ -260,22 +259,3 @@ fn convert_to_gradient_stops(stops: &Vec<Stop>) -> Vec<GradientStop> {
}
stops
}

struct TransformStack {
stack: Vec<Transform>,
}

impl TransformStack {
fn new(t: Transform) -> Self { TransformStack { stack: vec![t] } }

fn push(&mut self, mut t: Transform) {
if let Some(p) = self.stack.last() {
t = p.then(&t);
}
self.stack.push(t);
}

fn pop(&mut self) -> Option<Transform> { self.stack.pop() }

fn current_transform(&self) -> &Transform { self.stack.last().unwrap() }
}
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 3983100

Please sign in to comment.