2019-01-14 17:20:36 -05:00
|
|
|
// pathfinder/renderer/src/scene.rs
|
|
|
|
//
|
|
|
|
// Copyright © 2019 The Pathfinder Project Developers.
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
|
|
|
//! A set of paths to be rendered.
|
|
|
|
|
2019-04-30 22:13:28 -04:00
|
|
|
use crate::builder::SceneBuilder;
|
|
|
|
use crate::concurrent::executor::Executor;
|
2019-05-03 15:35:19 -04:00
|
|
|
use crate::options::{PreparedRenderOptions, PreparedRenderTransform};
|
|
|
|
use crate::options::{RenderCommandListener, RenderOptions};
|
2019-01-14 17:20:36 -05:00
|
|
|
use hashbrown::HashMap;
|
2019-04-30 22:13:28 -04:00
|
|
|
use pathfinder_geometry::basic::point::Point2DF32;
|
2019-04-11 22:38:31 -04:00
|
|
|
use pathfinder_geometry::basic::rect::RectF32;
|
2019-03-25 19:13:56 -04:00
|
|
|
use pathfinder_geometry::basic::transform2d::Transform2DF32;
|
2019-03-06 22:35:57 -05:00
|
|
|
use pathfinder_geometry::color::ColorU;
|
2019-01-14 17:20:36 -05:00
|
|
|
use pathfinder_geometry::outline::Outline;
|
2019-05-07 17:37:46 -04:00
|
|
|
use std::io::{self, Write};
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2019-01-29 17:50:15 -05:00
|
|
|
#[derive(Clone)]
|
2019-01-14 17:20:36 -05:00
|
|
|
pub struct Scene {
|
2019-04-30 22:13:28 -04:00
|
|
|
pub(crate) objects: Vec<PathObject>,
|
|
|
|
paints: Vec<Paint>,
|
|
|
|
paint_cache: HashMap<Paint, PaintId>,
|
|
|
|
bounds: RectF32,
|
|
|
|
view_box: RectF32,
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Scene {
|
|
|
|
#[inline]
|
|
|
|
pub fn new() -> Scene {
|
|
|
|
Scene {
|
|
|
|
objects: vec![],
|
|
|
|
paints: vec![],
|
|
|
|
paint_cache: HashMap::new(),
|
2019-02-05 13:03:20 -05:00
|
|
|
bounds: RectF32::default(),
|
|
|
|
view_box: RectF32::default(),
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-30 22:13:28 -04:00
|
|
|
pub fn push_object(&mut self, object: PathObject) {
|
2019-05-03 15:35:19 -04:00
|
|
|
self.bounds = self.bounds.union_rect(object.outline.bounds());
|
2019-04-30 22:13:28 -04:00
|
|
|
self.objects.push(object);
|
|
|
|
}
|
|
|
|
|
2019-01-14 17:20:36 -05:00
|
|
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
|
|
|
pub fn push_paint(&mut self, paint: &Paint) -> PaintId {
|
|
|
|
if let Some(paint_id) = self.paint_cache.get(paint) {
|
|
|
|
return *paint_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
let paint_id = PaintId(self.paints.len() as u16);
|
|
|
|
self.paint_cache.insert(*paint, paint_id);
|
|
|
|
self.paints.push(*paint);
|
|
|
|
paint_id
|
|
|
|
}
|
|
|
|
|
2019-04-30 22:13:28 -04:00
|
|
|
#[inline]
|
|
|
|
pub fn object_count(&self) -> usize {
|
|
|
|
self.objects.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn bounds(&self) -> RectF32 {
|
|
|
|
self.bounds
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_bounds(&mut self, new_bounds: RectF32) {
|
|
|
|
self.bounds = new_bounds;
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn view_box(&self) -> RectF32 {
|
|
|
|
self.view_box
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn set_view_box(&mut self, new_view_box: RectF32) {
|
|
|
|
self.view_box = new_view_box;
|
2019-04-18 18:09:37 -04:00
|
|
|
}
|
|
|
|
|
2019-04-30 22:13:28 -04:00
|
|
|
pub fn build_shaders(&self) -> Vec<ObjectShader> {
|
2019-04-29 19:45:29 -04:00
|
|
|
self.objects
|
|
|
|
.iter()
|
|
|
|
.map(|object| {
|
|
|
|
let paint = &self.paints[object.paint.0 as usize];
|
|
|
|
ObjectShader {
|
|
|
|
fill_color: paint.color,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect()
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
2019-04-29 19:45:29 -04:00
|
|
|
pub(crate) fn apply_render_options(
|
|
|
|
&self,
|
|
|
|
original_outline: &Outline,
|
|
|
|
options: &PreparedRenderOptions,
|
|
|
|
) -> Outline {
|
2019-03-25 19:13:56 -04:00
|
|
|
let effective_view_box = self.effective_view_box(options);
|
|
|
|
|
2019-02-09 22:24:30 -05:00
|
|
|
let mut outline;
|
2019-02-06 21:09:37 -05:00
|
|
|
match options.transform {
|
2019-04-29 19:45:29 -04:00
|
|
|
PreparedRenderTransform::Perspective {
|
|
|
|
ref perspective,
|
|
|
|
ref clip_polygon,
|
|
|
|
..
|
|
|
|
} => {
|
2019-02-09 22:24:30 -05:00
|
|
|
if original_outline.is_outside_polygon(clip_polygon) {
|
|
|
|
outline = Outline::new();
|
|
|
|
} else {
|
|
|
|
outline = (*original_outline).clone();
|
|
|
|
outline.clip_against_polygon(clip_polygon);
|
|
|
|
outline.apply_perspective(perspective);
|
2019-03-20 16:40:48 -04:00
|
|
|
|
2019-03-25 19:13:56 -04:00
|
|
|
// TODO(pcwalton): Support subpixel AA in 3D.
|
2019-02-09 22:24:30 -05:00
|
|
|
}
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
2019-03-25 19:13:56 -04:00
|
|
|
_ => {
|
2019-02-09 22:24:30 -05:00
|
|
|
// TODO(pcwalton): Short circuit.
|
|
|
|
outline = (*original_outline).clone();
|
2019-03-25 19:13:56 -04:00
|
|
|
if options.transform.is_2d() || options.subpixel_aa_enabled {
|
|
|
|
let mut transform = match options.transform {
|
|
|
|
PreparedRenderTransform::Transform2D(transform) => transform,
|
|
|
|
PreparedRenderTransform::None => Transform2DF32::default(),
|
|
|
|
PreparedRenderTransform::Perspective { .. } => unreachable!(),
|
|
|
|
};
|
|
|
|
if options.subpixel_aa_enabled {
|
2019-04-29 19:45:29 -04:00
|
|
|
transform = transform
|
|
|
|
.post_mul(&Transform2DF32::from_scale(&Point2DF32::new(3.0, 1.0)))
|
2019-03-25 19:13:56 -04:00
|
|
|
}
|
|
|
|
outline.transform(&transform);
|
|
|
|
}
|
|
|
|
outline.clip_against_rect(effective_view_box);
|
2019-02-02 14:36:42 -05:00
|
|
|
}
|
2019-01-30 17:42:06 -05:00
|
|
|
}
|
2019-02-06 21:09:37 -05:00
|
|
|
|
|
|
|
if !options.dilation.is_zero() {
|
|
|
|
outline.dilate(options.dilation);
|
|
|
|
}
|
|
|
|
|
2019-02-12 11:57:49 -05:00
|
|
|
// TODO(pcwalton): Fold this into previous passes to avoid unnecessary clones during
|
|
|
|
// monotonic conversion.
|
2019-03-25 19:13:56 -04:00
|
|
|
outline.prepare_for_tiling(self.effective_view_box(options));
|
2019-02-02 14:36:42 -05:00
|
|
|
outline
|
2019-01-16 19:53:10 -05:00
|
|
|
}
|
2019-03-06 22:35:57 -05:00
|
|
|
|
2019-03-22 17:28:18 -04:00
|
|
|
pub fn monochrome_color(&self) -> Option<ColorU> {
|
2019-03-06 22:35:57 -05:00
|
|
|
if self.objects.is_empty() {
|
2019-03-22 17:28:18 -04:00
|
|
|
return None;
|
2019-03-06 22:35:57 -05:00
|
|
|
}
|
|
|
|
let first_paint_id = self.objects[0].paint;
|
2019-04-29 19:45:29 -04:00
|
|
|
if self
|
|
|
|
.objects
|
|
|
|
.iter()
|
|
|
|
.skip(1)
|
|
|
|
.any(|object| object.paint != first_paint_id)
|
|
|
|
{
|
2019-03-22 17:28:18 -04:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(self.paints[first_paint_id.0 as usize].color)
|
2019-03-06 22:35:57 -05:00
|
|
|
}
|
2019-03-25 19:13:56 -04:00
|
|
|
|
|
|
|
#[inline]
|
2019-05-03 15:35:19 -04:00
|
|
|
pub(crate) fn effective_view_box(&self, render_options: &PreparedRenderOptions) -> RectF32 {
|
2019-03-25 19:13:56 -04:00
|
|
|
if render_options.subpixel_aa_enabled {
|
|
|
|
self.view_box.scale_xy(Point2DF32::new(3.0, 1.0))
|
|
|
|
} else {
|
|
|
|
self.view_box
|
|
|
|
}
|
|
|
|
}
|
2019-04-30 22:13:28 -04:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn build<E>(&self,
|
2019-05-03 15:35:19 -04:00
|
|
|
options: RenderOptions,
|
2019-04-30 22:13:28 -04:00
|
|
|
listener: Box<dyn RenderCommandListener>,
|
|
|
|
executor: &E)
|
|
|
|
where E: Executor {
|
2019-05-03 15:35:19 -04:00
|
|
|
let prepared_options = options.prepare(self.bounds);
|
|
|
|
SceneBuilder::new(self, &prepared_options, listener).build(executor)
|
2019-04-30 22:13:28 -04:00
|
|
|
}
|
2019-01-14 17:20:36 -05:00
|
|
|
|
2019-05-07 17:37:46 -04:00
|
|
|
pub fn write_svg<W>(&self, writer: &mut W) -> io::Result<()> where W: Write {
|
2019-04-29 19:45:29 -04:00
|
|
|
writeln!(
|
2019-05-07 17:37:46 -04:00
|
|
|
writer,
|
2019-04-29 19:45:29 -04:00
|
|
|
"<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"{} {} {} {}\">",
|
|
|
|
self.view_box.origin().x(),
|
|
|
|
self.view_box.origin().y(),
|
|
|
|
self.view_box.size().x(),
|
|
|
|
self.view_box.size().y()
|
|
|
|
)?;
|
2019-01-29 17:50:15 -05:00
|
|
|
for object in &self.objects {
|
|
|
|
let paint = &self.paints[object.paint.0 as usize];
|
2019-05-07 17:37:46 -04:00
|
|
|
write!(writer, " <path")?;
|
2019-01-29 17:50:15 -05:00
|
|
|
if !object.name.is_empty() {
|
2019-05-07 17:37:46 -04:00
|
|
|
write!(writer, " id=\"{}\"", object.name)?;
|
2019-01-29 17:50:15 -05:00
|
|
|
}
|
2019-04-29 19:45:29 -04:00
|
|
|
writeln!(
|
2019-05-07 17:37:46 -04:00
|
|
|
writer,
|
2019-04-29 19:45:29 -04:00
|
|
|
" fill=\"{:?}\" d=\"{:?}\" />",
|
|
|
|
paint.color, object.outline
|
|
|
|
)?;
|
2019-01-29 17:50:15 -05:00
|
|
|
}
|
2019-05-07 17:37:46 -04:00
|
|
|
writeln!(writer, "</svg>")?;
|
2019-01-29 17:50:15 -05:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-15 14:42:25 -05:00
|
|
|
#[derive(Clone, Debug)]
|
2019-01-14 17:20:36 -05:00
|
|
|
pub struct PathObject {
|
|
|
|
outline: Outline,
|
|
|
|
paint: PaintId,
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PathObject {
|
|
|
|
#[inline]
|
2019-05-03 15:35:19 -04:00
|
|
|
pub fn new(outline: Outline, paint: PaintId, name: String) -> PathObject {
|
|
|
|
PathObject { outline, paint, name }
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
2019-01-29 17:50:15 -05:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn outline(&self) -> &Outline {
|
|
|
|
&self.outline
|
|
|
|
}
|
2019-01-14 17:20:36 -05:00
|
|
|
}
|
|
|
|
|
2019-03-06 22:35:57 -05:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
|
|
pub struct Paint {
|
|
|
|
pub color: ColorU,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
|
|
|
pub struct PaintId(pub u16);
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Default)]
|
|
|
|
pub struct ObjectShader {
|
|
|
|
pub fill_color: ColorU,
|
|
|
|
}
|