Skip to content

Commit

Permalink
SVG path_flat_color renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
dzil123 committed Jul 29, 2021
1 parent 5f9ae3a commit 48ab099
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 1 deletion.
1 change: 1 addition & 0 deletions nannou/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ rusttype = { version = "0.8", features = ["gpu_cache"] }
serde = "1"
serde_derive = "1"
serde_json = "1"
svg = "0.10"
toml = "0.5"
walkdir = "2"
wgpu_upstream = { version = "0.9", package = "wgpu" }
Expand Down
3 changes: 2 additions & 1 deletion nannou/src/draw/renderer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod primitives;
pub mod svg;

pub use self::primitives::{PrimitiveRenderer, RenderContext, RenderPrimitive};
use crate::draw;
Expand Down Expand Up @@ -524,7 +525,7 @@ impl Renderer {

// Collect all draw commands to avoid borrow errors.
let draw_cmds: Vec<_> = draw.drain_commands().collect();
let draw_state = draw.state.borrow_mut();
let draw_state = draw.state.borrow();
let intermediary_state = draw_state.intermediary_state.borrow();
for cmd in draw_cmds {
match cmd {
Expand Down
140 changes: 140 additions & 0 deletions nannou/src/draw/renderer/svg/encode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use crate::draw::primitive::path::Options;
use crate::draw::properties::LinSrgba;
use crate::lyon::math::Point;
use lyon::lyon_tessellation::{LineCap, LineJoin};
use lyon::path::{Event, FillRule, PathEvent};
use nannou_core::color::{Alpha, Srgb};
use nannou_core::geom::Rect;
use svg::node::element::path::Position::Absolute;
use svg::node::element::path::{Command, Data, Parameters};
use svg::node::element::{Group, Path};
use svg::{Document, Node};

pub fn svg_document(dims: Rect, elements: Group, background: Option<LinSrgba>) -> Document {
let dims = dims.absolute();
let corner = dims.bottom_left(); // smallest x and y
let size = dims.wh();
let y_offset = dims.h().copysign(dims.y());

let mut document = Document::new()
.set("viewBox", (corner.x, corner.y, size.x, size.y))
.add(elements.set(
"transform",
format!("translate(0 {}) scale(1 -1)", y_offset), // in svg, +y is down; in nannou, +y is up
));

if let Some(color) = background {
document.assign(
"style",
format!("background-color: {};", convert_color(color)),
);
}

document
}

pub fn render_path<I>(svg: &mut Group, events: I, color: LinSrgba, options: Options)
where
I: Iterator<Item = PathEvent>,
{
let path_data = lyon_to_svg_path_data(events);
render_path_data(svg, path_data, color, options);
}

pub fn render_path_data(svg: &mut Group, path_data: Data, color: LinSrgba, options: Options) {
svg.append(path_options(color, options).set("d", path_data));
}

pub fn lyon_to_svg_path_data<I>(events: I) -> Data
where
I: Iterator<Item = PathEvent>,
{
let mut data = Vec::with_capacity(events.size_hint().0);

// follows lyon::FromPolyline convention that the previous Line.to == current Line.from
for event in events {
match event {
Event::Begin { at } => data.push(Command::Move(Absolute, param(at))),
Event::Line { to, .. } => data.push(Command::Line(Absolute, param(to))),
Event::Quadratic { ctrl, to, .. } => {
data.push(Command::QuadraticCurve(Absolute, params(&[ctrl, to])))
}
Event::Cubic {
ctrl1, ctrl2, to, ..
} => data.push(Command::CubicCurve(Absolute, params(&[ctrl1, ctrl2, to]))),
Event::End { first, close, .. } => {
if close {
data.push(Command::Line(Absolute, param(first)));
}
data.push(Command::Close);
}
}
}

data.into()
}

fn path_options(color: LinSrgba, options: Options) -> Path {
match options {
Options::Fill(options) => Path::new()
.set("stroke", "none")
.set("fill", convert_color(color))
.set(
"fill-rule",
match options.fill_rule {
FillRule::EvenOdd => "evenodd",
FillRule::NonZero => "nonzero",
},
),
Options::Stroke(options) => {
if options.start_cap != options.end_cap {
unimplemented!();
}
Path::new()
.set("fill", "none")
.set("stroke", convert_color(color))
.set(
"stroke-linecap",
match options.start_cap {
LineCap::Butt => "butt",
LineCap::Square => "round",
LineCap::Round => "square",
},
)
.set(
"stroke-line_join",
match options.line_join {
LineJoin::Miter => "miter",
LineJoin::MiterClip => "miter-clip",
LineJoin::Round => "round",
LineJoin::Bevel => "bevel",
},
)
.set("stroke-width", options.line_width)
.set("stroke-miterlimit", options.miter_limit)
}
}
}

fn convert_color(color: LinSrgba) -> String {
let color: Alpha<Srgb<u8>, f32> = color.into_encoding().into_format();

format!(
"rgba({}, {}, {}, {:.3})",
color.red, color.green, color.blue, color.alpha
)
}

fn param(pt: Point) -> Parameters {
let pt: (f32, f32) = pt.into();
pt.into()
}

fn params(points: &[Point]) -> Parameters {
let mut params = Vec::with_capacity(points.len() * 2);
for pt in points {
params.push(pt.x);
params.push(pt.y);
}
params.into()
}
132 changes: 132 additions & 0 deletions nannou/src/draw/renderer/svg/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
mod encode;

use crate::draw::mesh::vertex::TexCoords;
use crate::draw::primitive::path::Options;
use crate::draw::properties::LinSrgba;
use crate::draw::renderer::{PrimitiveRenderer, RenderContext, RenderPrimitive};
use crate::draw::{self, DrawCommand};
use crate::glam::{Mat4, Vec2};
use crate::Draw;
use nannou_core::geom::Rect;
use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
use svg::node::element::Group;
use svg::Document;

pub fn render_and_save(dims: Rect, draw: &Draw, path: impl AsRef<Path>) {
let document = render(dims, draw);
write_file(path, &document).unwrap();
}

fn write_file(path: impl AsRef<Path>, document: &Document) -> io::Result<()> {
let mut file = File::create(path)?;
file.write_all(&document.to_string().into_bytes())?;
file.sync_all()
}

pub fn render(dims: Rect, draw: &Draw) -> Document {
let draw_commands = draw.drain_commands();
let draw_state = draw.state.borrow();
let intermediary_state = draw_state.intermediary_state.borrow();

let mut svg = Group::new();
let mut curr_ctxt = Default::default();

for command in draw_commands {
match command {
DrawCommand::Context(new_context) => {
if new_context != Default::default() {
unimplemented!();
}
curr_ctxt = new_context;
}
DrawCommand::Primitive(primitive) => {
let ctxt = RenderContext {
path_event_buffer: &intermediary_state.path_event_buffer,
path_points_colored_buffer: &intermediary_state.path_points_colored_buffer,
path_points_textured_buffer: &intermediary_state.path_points_textured_buffer,
text_buffer: &intermediary_state.text_buffer,
theme: &draw_state.theme,
};

let renderer = SvgPrimitiveRenderer {
transform: &curr_ctxt.transform,
theme: &draw_state.theme,
svg: &mut svg,
};

let _ = primitive.render_primitive(ctxt, renderer);
}
}
}

encode::svg_document(dims, svg, draw_state.background_color)
}

struct SvgPrimitiveRenderer<'a> {
transform: &'a Mat4,
theme: &'a draw::Theme,
svg: &'a mut Group,
}

impl<'a> PrimitiveRenderer for SvgPrimitiveRenderer<'a> {
fn path_flat_color(
&mut self,
local_transform: Mat4,
events: impl Iterator<Item = lyon::path::PathEvent>,
color: Option<LinSrgba>,
theme_primitive: draw::theme::Primitive,
options: Options,
) {
let transform = *self.transform * local_transform;
if transform != Mat4::IDENTITY {
unimplemented!();
}

let color = self.theme.resolve_color(color, theme_primitive, &options);
encode::render_path(self.svg, events, color, options);
}

fn path_colored_points(
&mut self,
_local_transform: Mat4,
_points_colored: impl Iterator<Item = (Vec2, LinSrgba)>,
_close: bool,
_options: Options,
) {
unimplemented!();
}

fn path_textured_points(
&mut self,
_local_transform: Mat4,
_points_textured: impl Iterator<Item = (Vec2, TexCoords)>,
_close: bool,
_options: Options,
) {
unimplemented!();
}

fn mesh(
&mut self,
_local_transform: Mat4,
_vertex_range: std::ops::Range<usize>,
_index_range: std::ops::Range<usize>,
_fill_color: Option<LinSrgba>,
) {
unimplemented!();
}

fn text(
&mut self,
local_transform: Mat4,
_text: crate::text::Text,
_color: LinSrgba,
_glyph_colors: Vec<LinSrgba>,
) {
let _transform = *self.transform * local_transform;
// TODO: render each glyph as a path_flat_color
unimplemented!();
}
}

0 comments on commit 48ab099

Please sign in to comment.