Add the ability to merge scenes together, and expose it to the canvas API.

Closes #268.
This commit is contained in:
Patrick Walton 2020-04-02 13:49:30 -07:00
parent 5d43eedf20
commit ba7fe4be39
10 changed files with 481 additions and 407 deletions

View File

@ -13,8 +13,8 @@
use font_kit::handle::Handle;
use foreign_types::ForeignTypeRef;
use gl;
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, LineJoin, Path2D};
use pathfinder_canvas::{TextAlign, TextMetrics};
use pathfinder_canvas::{Canvas, CanvasFontContext, CanvasRenderingContext2D, FillStyle, LineJoin};
use pathfinder_canvas::{Path2D, TextAlign, TextMetrics};
use pathfinder_color::{ColorF, ColorU};
use pathfinder_content::fill::FillRule;
use pathfinder_content::outline::ArcDirection;
@ -196,8 +196,7 @@ pub type PFRenderTransformRef = *mut RenderTransform;
pub unsafe extern "C" fn PFCanvasCreate(font_context: PFCanvasFontContextRef,
size: *const PFVector2F)
-> PFCanvasRef {
Box::into_raw(Box::new(CanvasRenderingContext2D::new((*font_context).clone(),
(*size).to_rust())))
Box::into_raw(Box::new(Canvas::new((*size).to_rust()).get_context_2d((*font_context).clone())))
}
#[no_mangle]
@ -235,7 +234,7 @@ pub unsafe extern "C" fn PFCanvasFontContextRelease(font_context: PFCanvasFontCo
/// the scene is destroyed.
#[no_mangle]
pub unsafe extern "C" fn PFCanvasCreateScene(canvas: PFCanvasRef) -> PFSceneRef {
Box::into_raw(Box::new(Box::from_raw(canvas).into_scene()))
Box::into_raw(Box::new(Box::from_raw(canvas).into_canvas().into_scene()))
}
// Drawing rectangles

View File

@ -16,7 +16,7 @@ use pathfinder_content::effects::{BlendMode, BlurDirection, Effects, Filter};
use pathfinder_content::fill::FillRule;
use pathfinder_content::gradient::Gradient;
use pathfinder_content::outline::{ArcDirection, Contour, Outline};
use pathfinder_content::pattern::{Pattern, PatternFlags};
use pathfinder_content::pattern::{Pattern, PatternFlags, PatternSource};
use pathfinder_content::render_target::RenderTargetId;
use pathfinder_content::stroke::{LineCap, LineJoin as StrokeLineJoin};
use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle};
@ -43,7 +43,6 @@ use crate::text::FontCollection;
#[cfg(feature = "pf-text")]
pub use text::TextMetrics;
const HAIRLINE_STROKE_WIDTH: f32 = 0.0333;
const DEFAULT_FONT_SIZE: f32 = 10.0;
@ -68,8 +67,44 @@ mod text {
#[cfg(test)]
mod tests;
pub struct CanvasRenderingContext2D {
pub struct Canvas {
scene: Scene,
}
impl Canvas {
#[inline]
pub fn new(size: Vector2F) -> Canvas {
let mut scene = Scene::new();
scene.set_view_box(RectF::new(Vector2F::zero(), size));
Canvas::from_scene(scene)
}
#[inline]
pub fn from_scene(scene: Scene) -> Canvas {
Canvas { scene }
}
#[inline]
pub fn into_scene(self) -> Scene {
self.scene
}
pub fn get_context_2d(self, font_context: CanvasFontContext) -> CanvasRenderingContext2D {
#[cfg(feature = "pf-text")]
let default_font_collection = font_context.default_font_collection.clone();
#[cfg(not(feature = "pf-text"))]
let default_font_collection = Arc::new(FontCollection);
CanvasRenderingContext2D {
canvas: self,
current_state: State::default(default_font_collection),
saved_states: vec![],
font_context,
}
}
}
pub struct CanvasRenderingContext2D {
canvas: Canvas,
current_state: State,
saved_states: Vec<State>,
#[allow(dead_code)]
@ -77,29 +112,11 @@ pub struct CanvasRenderingContext2D {
}
impl CanvasRenderingContext2D {
#[inline]
pub fn new(font_context: CanvasFontContext, size: Vector2F) -> CanvasRenderingContext2D {
let mut scene = Scene::new();
scene.set_view_box(RectF::new(Vector2F::zero(), size));
CanvasRenderingContext2D::from_scene(font_context, scene)
}
pub fn from_scene(font_context: CanvasFontContext, scene: Scene) -> CanvasRenderingContext2D {
#[cfg(feature = "pf-text")]
let default_font_collection = font_context.default_font_collection.clone();
#[cfg(not(feature = "pf-text"))]
let default_font_collection = Arc::new(FontCollection);
CanvasRenderingContext2D {
scene,
current_state: State::default(default_font_collection),
saved_states: vec![],
font_context,
}
}
// Finalization
#[inline]
pub fn into_scene(self) -> Scene {
self.scene
pub fn into_canvas(self) -> Canvas {
self.canvas
}
// Drawing rectangles
@ -124,14 +141,14 @@ impl CanvasRenderingContext2D {
let paint = Paint::transparent_black();
let paint = self.current_state.resolve_paint(&paint);
let paint_id = self.scene.push_paint(&paint);
let paint_id = self.canvas.scene.push_paint(&paint);
let mut outline = path.into_outline();
outline.transform(&self.current_state.transform);
let mut path = DrawPath::new(outline, paint_id);
path.set_blend_mode(BlendMode::Clear);
self.scene.push_path(path);
self.canvas.scene.push_path(path);
}
// Line styles
@ -225,14 +242,14 @@ impl CanvasRenderingContext2D {
#[inline]
pub fn fill_path(&mut self, path: Path2D, fill_rule: FillRule) {
let paint = self.current_state.resolve_paint(&self.current_state.fill_paint);
let paint_id = self.scene.push_paint(&paint);
let paint_id = self.canvas.scene.push_paint(&paint);
self.push_path(path.into_outline(), paint_id, fill_rule);
}
#[inline]
pub fn stroke_path(&mut self, path: Path2D) {
let paint = self.current_state.resolve_paint(&self.current_state.stroke_paint);
let paint_id = self.scene.push_paint(&paint);
let paint_id = self.canvas.scene.push_paint(&paint);
let mut stroke_style = self.current_state.resolve_stroke_style();
@ -267,7 +284,7 @@ impl CanvasRenderingContext2D {
let mut clip_path = ClipPath::new(outline);
clip_path.set_fill_rule(fill_rule);
let clip_path_id = self.scene.push_clip_path(clip_path);
let clip_path_id = self.canvas.scene.push_clip_path(clip_path);
self.current_state.clip_path = Some(clip_path_id);
}
@ -282,7 +299,7 @@ impl CanvasRenderingContext2D {
let shadow_blur_render_target_ids = self.push_shadow_blur_render_targets_if_needed();
let paint = self.current_state.resolve_paint(&self.current_state.shadow_paint);
let paint_id = self.scene.push_paint(&paint);
let paint_id = self.canvas.scene.push_paint(&paint);
let mut outline = outline.clone();
outline.transform(&(Transform2F::from_translation(self.current_state.shadow_offset) *
@ -293,7 +310,7 @@ impl CanvasRenderingContext2D {
path.set_fill_rule(fill_rule);
path.set_blend_mode(blend_mode);
path.set_opacity(opacity);
self.scene.push_path(path);
self.canvas.scene.push_path(path);
self.composite_shadow_blur_render_targets_if_needed(shadow_blur_render_target_ids);
}
@ -305,7 +322,7 @@ impl CanvasRenderingContext2D {
path.set_fill_rule(fill_rule);
path.set_blend_mode(blend_mode);
path.set_opacity(opacity);
self.scene.push_path(path);
self.canvas.scene.push_path(path);
}
fn push_shadow_blur_render_targets_if_needed(&mut self) -> Option<[RenderTargetId; 2]> {
@ -313,11 +330,11 @@ impl CanvasRenderingContext2D {
return None;
}
let render_target_size = self.scene.view_box().size().ceil().to_i32();
let render_target_id_a =
self.scene.push_render_target(RenderTarget::new(render_target_size, String::new()));
let render_target_id_b =
self.scene.push_render_target(RenderTarget::new(render_target_size, String::new()));
let render_target_size = self.canvas.scene.view_box().size().ceil().to_i32();
let render_target_a = RenderTarget::new(render_target_size, String::new());
let render_target_id_a = self.canvas.scene.push_render_target(render_target_a);
let render_target_b = RenderTarget::new(render_target_size, String::new());
let render_target_id_b = self.canvas.scene.push_render_target(render_target_b);
Some([render_target_id_a, render_target_id_b])
}
@ -330,13 +347,13 @@ impl CanvasRenderingContext2D {
};
let sigma = self.current_state.shadow_blur * 0.5;
self.scene.pop_render_target();
self.scene.draw_render_target(render_target_ids[1], Effects::new(Filter::Blur {
self.canvas.scene.pop_render_target();
self.canvas.scene.draw_render_target(render_target_ids[1], Effects::new(Filter::Blur {
direction: BlurDirection::X,
sigma,
}));
self.scene.pop_render_target();
self.scene.draw_render_target(render_target_ids[0], Effects::new(Filter::Blur {
self.canvas.scene.pop_render_target();
self.canvas.scene.draw_render_target(render_target_ids[0], Effects::new(Filter::Blur {
direction: BlurDirection::Y,
sigma,
}));
@ -431,6 +448,19 @@ impl CanvasRenderingContext2D {
self.current_state = state;
}
}
// Extensions
pub fn create_pattern_from_canvas(&mut self, canvas: Canvas) -> Pattern {
let subscene = canvas.into_scene();
let subscene_size = subscene.view_box().size().ceil().to_i32();
let render_target = RenderTarget::new(subscene_size, String::new());
let render_target_id = self.canvas.scene.push_render_target(render_target);
self.canvas.scene.append_scene(subscene);
self.canvas.scene.pop_render_target();
let pattern_source = PatternSource::RenderTarget(render_target_id);
Pattern::new(pattern_source, Transform2F::default(), PatternFlags::empty())
}
}
#[derive(Clone)]

View File

@ -26,12 +26,12 @@ use std::sync::Arc;
impl CanvasRenderingContext2D {
pub fn fill_text(&mut self, string: &str, position: Vector2F) {
let paint_id = self.scene.push_paint(&self.current_state.fill_paint);
let paint_id = self.canvas.scene.push_paint(&self.current_state.fill_paint);
self.fill_or_stroke_text(string, position, paint_id, TextRenderMode::Fill);
}
pub fn stroke_text(&mut self, string: &str, position: Vector2F) {
let paint_id = self.scene.push_paint(&self.current_state.stroke_paint);
let paint_id = self.canvas.scene.push_paint(&self.current_state.stroke_paint);
let render_mode = TextRenderMode::Stroke(self.current_state.resolve_stroke_style());
self.fill_or_stroke_text(string, position, paint_id, render_mode);
}
@ -41,8 +41,8 @@ impl CanvasRenderingContext2D {
}
pub fn fill_layout(&mut self, layout: &Layout, transform: Transform2F) {
let paint_id = self.scene.push_paint(&self.current_state.fill_paint);
drop(self.scene.push_layout(&layout,
let paint_id = self.canvas.scene.push_paint(&self.current_state.fill_paint);
drop(self.canvas.scene.push_layout(&layout,
&TextStyle { size: self.current_state.font_size },
&(transform * self.current_state.transform),
TextRenderMode::Fill,
@ -77,7 +77,7 @@ impl CanvasRenderingContext2D {
let transform = self.current_state.transform * Transform2F::from_translation(position);
// TODO(pcwalton): Report errors.
drop(self.scene.push_layout(&layout,
drop(self.canvas.scene.push_layout(&layout,
&TextStyle { size: self.current_state.font_size },
&transform,
render_mode,

View File

@ -15,7 +15,7 @@ use glutin::{ContextBuilder, GlProfile, GlRequest};
use glutin::event_loop::{ControlFlow, EventLoop};
use glutin::event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use glutin::window::WindowBuilder;
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, Path2D};
use pathfinder_canvas::{Canvas, CanvasFontContext, Path2D};
use pathfinder_color::ColorF;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::{vec2f, vec2i};
@ -54,8 +54,8 @@ fn main() {
RendererOptions { background_color: Some(ColorF::white()) });
// Make a canvas. We're going to draw a house.
let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(),
window_size.to_f32());
let font_context = CanvasFontContext::from_system_source();
let mut canvas = Canvas::new(window_size.to_f32()).get_context_2d(font_context);
// Set line width.
canvas.set_line_width(10.0);
@ -75,7 +75,7 @@ fn main() {
canvas.stroke_path(path);
// Render the canvas to screen.
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
let scene = SceneProxy::from_scene(canvas.into_canvas().into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, BuildOptions::default());
gl_context.swap_buffers().unwrap();

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, Path2D};
use pathfinder_canvas::{Canvas, CanvasFontContext, Path2D};
use pathfinder_color::ColorF;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::{vec2f, vec2i};
@ -52,8 +52,8 @@ fn main() {
RendererOptions { background_color: Some(ColorF::white()) });
// Make a canvas. We're going to draw a house.
let mut canvas = CanvasRenderingContext2D::new(CanvasFontContext::from_system_source(),
window_size.to_f32());
let font_context = CanvasFontContext::from_system_source();
let mut canvas = Canvas::new(window_size.to_f32()).get_context_2d(font_context);
// Set line width.
canvas.set_line_width(10.0);
@ -73,7 +73,7 @@ fn main() {
canvas.stroke_path(path);
// Render the canvas to screen.
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
let scene = SceneProxy::from_scene(canvas.into_canvas().into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, BuildOptions::default());
window.gl_swap_window();

View File

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, FillStyle, Path2D};
use pathfinder_canvas::{Canvas, CanvasFontContext, CanvasRenderingContext2D, FillStyle, Path2D};
use pathfinder_color::{ColorF, ColorU};
use pathfinder_geometry::vector::{Vector2F, Vector2I, vec2f, vec2i};
use pathfinder_gl::{GLDevice, GLVersion};
@ -127,8 +127,8 @@ impl MoireRenderer {
self.renderer.set_options(RendererOptions { background_color: Some(background_color) });
// Make a canvas.
let mut canvas = CanvasRenderingContext2D::new(self.font_context.clone(),
self.drawable_size.to_f32());
let mut canvas =
Canvas::new(self.drawable_size.to_f32()).get_context_2d(self.font_context.clone());
canvas.set_line_width(CIRCLE_THICKNESS * self.device_pixel_ratio);
canvas.set_stroke_style(FillStyle::Color(foreground_color.to_u8()));
canvas.set_global_alpha(0.75);
@ -138,7 +138,7 @@ impl MoireRenderer {
self.draw_circles(&mut canvas, inner_center);
// Build and render scene.
self.scene.replace_scene(canvas.into_scene());
self.scene.replace_scene(canvas.into_canvas().into_scene());
self.scene.build_and_render(&mut self.renderer, BuildOptions::default());
self.frame += 1;

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@
// except according to those terms.
use font_kit::handle::Handle;
use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, TextAlign};
use pathfinder_canvas::{Canvas, CanvasFontContext, TextAlign};
use pathfinder_color::ColorF;
use pathfinder_geometry::vector::{vec2f, vec2i};
use pathfinder_gl::{GLDevice, GLVersion};
@ -61,7 +61,7 @@ fn main() {
let font_context = CanvasFontContext::from_fonts(iter::once(font));
// Make a canvas.
let mut canvas = CanvasRenderingContext2D::new(font_context, window_size.to_f32());
let mut canvas = Canvas::new(window_size.to_f32()).get_context_2d(font_context);
// Draw the text.
canvas.set_font("Overpass-Regular");
@ -71,7 +71,7 @@ fn main() {
canvas.stroke_text("Goodbye Pathfinder!", vec2f(608.0, 464.0));
// Render the canvas to screen.
let scene = SceneProxy::from_scene(canvas.into_scene(), RayonExecutor);
let scene = SceneProxy::from_scene(canvas.into_canvas().into_scene(), RayonExecutor);
scene.build_and_render(&mut renderer, BuildOptions::default());
window.gl_swap_window();

View File

@ -50,7 +50,7 @@ pub enum Paint {
Pattern(Pattern),
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct PaintId(pub u16);
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]

View File

@ -22,6 +22,7 @@ use pathfinder_content::render_target::RenderTargetId;
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::transform2d::Transform2F;
use pathfinder_geometry::vector::{Vector2I, vec2f};
use std::collections::HashMap;
#[derive(Clone)]
pub struct Scene {
@ -85,6 +86,51 @@ impl Scene {
self.display_list.push(DisplayItem::DrawRenderTarget { render_target, effects });
}
pub fn append_scene(&mut self, scene: Scene) {
// Merge render targets.
let mut render_target_mapping = HashMap::new();
for (old_render_target_index, render_target) in scene.palette
.render_targets
.into_iter()
.enumerate() {
let old_render_target_id = RenderTargetId(old_render_target_index as u32);
let new_render_target_id = self.palette.push_render_target(render_target);
render_target_mapping.insert(old_render_target_id, new_render_target_id);
}
// Merge paints.
let mut paint_mapping = HashMap::new();
for (old_paint_index, paint) in scene.palette.paints.iter().enumerate() {
let old_paint_id = PaintId(old_paint_index as u16);
let new_paint_id = self.palette.push_paint(&paint);
paint_mapping.insert(old_paint_id, new_paint_id);
}
// Merge clip paths.
let mut clip_path_mapping = Vec::with_capacity(scene.clip_paths.len());
for clip_path in scene.clip_paths {
clip_path_mapping.push(self.clip_paths.len());
self.clip_paths.push(clip_path);
}
// Merge draw paths.
let mut draw_path_mapping = Vec::with_capacity(scene.paths.len());
for draw_path in scene.paths {
draw_path_mapping.push(self.paths.len());
self.paths.push(DrawPath {
outline: draw_path.outline,
paint: paint_mapping[&draw_path.paint],
clip_path: draw_path.clip_path.map(|clip_path_id| {
ClipPathId(clip_path_mapping[clip_path_id.0 as usize] as u32)
}),
fill_rule: draw_path.fill_rule,
blend_mode: draw_path.blend_mode,
opacity: draw_path.opacity,
name: draw_path.name,
});
}
}
#[inline]
pub fn build_paint_info(&self, render_transform: Transform2F) -> PaintInfo {
self.palette.build_paint_info(render_transform)