diff --git a/c/src/lib.rs b/c/src/lib.rs index 90d2eac5..e6195ee8 100644 --- a/c/src/lib.rs +++ b/c/src/lib.rs @@ -358,13 +358,15 @@ pub unsafe extern "C" fn PFCanvasSetTextAlign(canvas: PFCanvasRef, new_text_alig #[no_mangle] pub unsafe extern "C" fn PFCanvasSetFillStyle(canvas: PFCanvasRef, fill_style: PFFillStyleRef) { - (*canvas).set_fill_style(*fill_style) + // FIXME(pcwalton): Avoid the copy? + (*canvas).set_fill_style((*fill_style).clone()) } #[no_mangle] pub unsafe extern "C" fn PFCanvasSetStrokeStyle(canvas: PFCanvasRef, stroke_style: PFFillStyleRef) { - (*canvas).set_stroke_style(*stroke_style) + // FIXME(pcwalton): Avoid the copy? + (*canvas).set_stroke_style((*stroke_style).clone()) } /// This function automatically destroys the path. If you wish to use the path again, clone it diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index 3d8dbff6..c8f27677 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -12,6 +12,7 @@ use pathfinder_color::ColorU; use pathfinder_content::dash::OutlineDash; +use pathfinder_content::gradient::Gradient; use pathfinder_content::outline::{ArcDirection, Contour, Outline}; use pathfinder_content::stroke::{LineCap, LineJoin as StrokeLineJoin}; use pathfinder_content::stroke::{OutlineStrokeToFill, StrokeStyle}; @@ -21,6 +22,7 @@ use pathfinder_geometry::rect::RectF; use pathfinder_geometry::transform2d::Transform2F; use pathfinder_renderer::paint::{Paint, PaintId}; use pathfinder_renderer::scene::{PathObject, Scene}; +use std::borrow::Cow; use std::default::Default; use std::f32::consts::PI; use std::mem; @@ -129,19 +131,19 @@ impl CanvasRenderingContext2D { #[inline] pub fn set_fill_style(&mut self, new_fill_style: FillStyle) { - self.current_state.fill_paint = new_fill_style.to_paint(); + self.current_state.fill_paint = new_fill_style.into_paint(); } #[inline] pub fn set_stroke_style(&mut self, new_stroke_style: FillStyle) { - self.current_state.stroke_paint = new_stroke_style.to_paint(); + self.current_state.stroke_paint = new_stroke_style.into_paint(); } // Shadows #[inline] pub fn set_shadow_color(&mut self, new_shadow_color: ColorU) { - self.current_state.shadow_paint = Paint { color: new_shadow_color }; + self.current_state.shadow_paint = Paint::Color(new_shadow_color); } #[inline] @@ -156,7 +158,7 @@ impl CanvasRenderingContext2D { let mut outline = path.into_outline(); outline.transform(&self.current_state.transform); - let paint = self.current_state.resolve_paint(self.current_state.fill_paint); + let paint = self.current_state.resolve_paint(&self.current_state.fill_paint); let paint_id = self.scene.push_paint(&paint); self.push_path(outline, paint_id); @@ -164,7 +166,7 @@ impl CanvasRenderingContext2D { #[inline] pub fn stroke_path(&mut self, path: Path2D) { - let paint = self.current_state.resolve_paint(self.current_state.stroke_paint); + let paint = self.current_state.resolve_paint(&self.current_state.stroke_paint); let paint_id = self.scene.push_paint(&paint); let mut stroke_style = self.current_state.resolve_stroke_style(); @@ -195,7 +197,7 @@ impl CanvasRenderingContext2D { fn push_path(&mut self, outline: Outline, paint_id: PaintId) { if !self.current_state.shadow_paint.is_fully_transparent() { - let paint = self.current_state.resolve_paint(self.current_state.shadow_paint); + let paint = self.current_state.resolve_paint(&self.current_state.shadow_paint); let paint_id = self.scene.push_paint(&paint); let mut outline = outline.clone(); @@ -281,18 +283,23 @@ impl State { miter_limit: 10.0, line_dash: vec![], line_dash_offset: 0.0, - fill_paint: Paint { color: ColorU::black() }, - stroke_paint: Paint { color: ColorU::black() }, - shadow_paint: Paint { color: ColorU::transparent_black() }, + fill_paint: Paint::black(), + stroke_paint: Paint::black(), + shadow_paint: Paint::transparent_black(), shadow_offset: Vector2F::default(), text_align: TextAlign::Left, global_alpha: 1.0, } } - fn resolve_paint(&self, mut paint: Paint) -> Paint { - paint.color.a = (paint.color.a as f32 * self.global_alpha).round() as u8; - paint + fn resolve_paint<'a>(&self, paint: &'a Paint) -> Cow<'a, Paint> { + if self.global_alpha == 1.0 { + return Cow::Borrowed(paint); + } + + let mut paint = (*paint).clone(); + paint.set_opacity(self.global_alpha); + Cow::Owned(paint) } fn resolve_stroke_style(&self) -> StrokeStyle { @@ -415,16 +422,18 @@ impl Path2D { } } -// TODO(pcwalton): Gradients. -#[derive(Clone, Copy)] +#[derive(Clone)] pub enum FillStyle { Color(ColorU), + Gradient(Gradient), } impl FillStyle { - #[inline] - fn to_paint(&self) -> Paint { - match *self { FillStyle::Color(color) => Paint { color } } + fn into_paint(self) -> Paint { + match self { + FillStyle::Color(color) => Paint::Color(color), + FillStyle::Gradient(gradient) => Paint::Gradient(gradient), + } } } diff --git a/color/src/lib.rs b/color/src/lib.rs index f918b173..77d91e18 100644 --- a/color/src/lib.rs +++ b/color/src/lib.rs @@ -52,6 +52,11 @@ impl ColorU { ColorF(color * F32x4::splat(1.0 / 255.0)) } + #[inline] + pub fn is_opaque(&self) -> bool { + self.a == !0 + } + #[inline] pub fn is_fully_transparent(&self) -> bool { self.a == 0 diff --git a/content/src/gradient.rs b/content/src/gradient.rs index 3020ed48..1c08bf1f 100644 --- a/content/src/gradient.rs +++ b/content/src/gradient.rs @@ -10,27 +10,97 @@ use crate::sorted_vector::SortedVector; use pathfinder_color::ColorU; -use std::cmp::PartialOrd; +use pathfinder_geometry::line_segment::LineSegment2F; +use pathfinder_simd::default::F32x4; +use std::cmp::{self, Ordering, PartialOrd}; +use std::convert; +use std::hash::{Hash, Hasher}; +use std::mem; -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct Gradient { + line: LineSegment2F, stops: SortedVector, } #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] pub struct ColorStop { - pub offset: f32, pub color: ColorU, + pub offset: f32, +} + +impl Eq for Gradient {} + +impl Hash for Gradient { + fn hash(&self, state: &mut H) where H: Hasher { + unsafe { + let data: [u32; 4] = mem::transmute::(self.line.0); + data.hash(state); + self.stops.hash(state); + } + } +} + +impl Eq for ColorStop {} + +impl Hash for ColorStop { + fn hash(&self, state: &mut H) where H: Hasher { + unsafe { + self.color.hash(state); + let offset = mem::transmute::(self.offset); + offset.hash(state); + } + } } impl Gradient { + #[inline] + pub fn new(line: LineSegment2F) -> Gradient { + Gradient { line, stops: SortedVector::new() } + } + #[inline] pub fn add_color_stop(&mut self, stop: ColorStop) { self.stops.push(stop); } + #[inline] + pub fn line(&self) -> LineSegment2F { + self.line + } + #[inline] pub fn stops(&self) -> &[ColorStop] { &self.stops.array } + + pub fn sample(&self, t: f32) -> ColorU { + if self.stops.is_empty() { + return ColorU::transparent_black(); + } + + let lower_index = self.stops.binary_search_by(|stop| { + stop.offset.partial_cmp(&t).unwrap_or(Ordering::Less) + }).unwrap_or_else(convert::identity); + let upper_index = cmp::min(lower_index + 1, self.stops.len() - 1); + + let lower_stop = &self.stops.array[lower_index]; + let upper_stop = &self.stops.array[upper_index]; + + let denom = upper_stop.offset - lower_stop.offset; + if denom == 0.0 { + return lower_stop.color; + } + + lower_stop.color + .to_f32() + .lerp(upper_stop.color.to_f32(), (t - lower_stop.offset) / denom) + .to_u8() + } + + pub fn set_opacity(&mut self, alpha: f32) { + for stop in &mut self.stops.array { + stop.color.a = (stop.color.a as f32 * alpha).round() as u8; + } + } } diff --git a/content/src/sorted_vector.rs b/content/src/sorted_vector.rs index 46ade1c6..21c584f5 100644 --- a/content/src/sorted_vector.rs +++ b/content/src/sorted_vector.rs @@ -13,7 +13,7 @@ use std::cmp::Ordering; use std::convert; -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct SortedVector where T: PartialOrd, @@ -32,7 +32,7 @@ where #[inline] pub fn push(&mut self, value: T) { - let index = self.array.binary_search_by(|other| { + let index = self.binary_search_by(|other| { other.partial_cmp(&value).unwrap_or(Ordering::Less) }).unwrap_or_else(convert::identity); self.array.insert(index, value); @@ -58,6 +58,17 @@ where pub fn is_empty(&self) -> bool { self.array.is_empty() } + + #[inline] + pub fn len(&self) -> usize { + self.array.len() + } + + #[inline] + pub fn binary_search_by<'a, F>(&'a self, f: F) -> Result + where F: FnMut(&'a T) -> Ordering { + self.array.binary_search_by(f) + } } #[cfg(test)] diff --git a/export/src/lib.rs b/export/src/lib.rs index f1b71b75..d37df9c0 100644 --- a/export/src/lib.rs +++ b/export/src/lib.rs @@ -9,10 +9,11 @@ // except according to those terms. use pathfinder_content::segment::SegmentKind; +use pathfinder_renderer::paint::Paint; use pathfinder_renderer::scene::Scene; use pathfinder_geometry::vector::Vector2F; -use std::io::{self, Write}; use std::fmt; +use std::io::{self, Write}; mod pdf; use pdf::Pdf; @@ -57,11 +58,7 @@ fn export_svg(scene: &Scene, writer: &mut W) -> io::Result<()> { if !name.is_empty() { write!(writer, " id=\"{}\"", name)?; } - writeln!( - writer, - " fill=\"{:?}\" d=\"{:?}\" />", - paint.color, outline - )?; + writeln!(writer, " fill=\"{:?}\" d=\"{:?}\" />", paint, outline)?; } writeln!(writer, "")?; Ok(()) @@ -79,7 +76,12 @@ fn export_pdf(scene: &Scene, writer: &mut W) -> io::Result<()> { }; for (paint, outline, _) in scene.paths() { - pdf.set_fill_color(paint.color); + match paint { + Paint::Color(color) => pdf.set_fill_color(*color), + Paint::Gradient(_) => { + // TODO(pcwalton): Gradients. + } + } for contour in outline.contours() { for (segment_index, segment) in contour.iter().enumerate() { @@ -140,7 +142,7 @@ fn export_ps(scene: &Scene, writer: &mut W) -> io::Result<()> { } else { writeln!(writer, "newpath")?; } - let color = paint.color.to_f32(); + for contour in outline.contours() { for (segment_index, segment) in contour.iter().enumerate() { if segment_index == 0 { @@ -174,7 +176,16 @@ fn export_ps(scene: &Scene, writer: &mut W) -> io::Result<()> { writeln!(writer, "closepath")?; } } - writeln!(writer, "{} {} {} setrgbcolor", color.r(), color.g(), color.b())?; + + match paint { + Paint::Color(color) => { + writeln!(writer, "{} {} {} setrgbcolor", color.r, color.g, color.b)?; + } + Paint::Gradient(_) => { + // TODO(pcwalton): Gradients. + } + } + writeln!(writer, "fill")?; } writeln!(writer, "showpage")?; diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 165a67eb..2633f4b4 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -10,12 +10,14 @@ use crate::allocator::{TextureAllocator, TextureLocation}; use crate::gpu_data::PaintData; -use crate::scene::Scene; +use hashbrown::HashMap; use pathfinder_color::ColorU; +use pathfinder_content::gradient::Gradient; use pathfinder_geometry::rect::RectI; use pathfinder_geometry::transform2d::{Matrix2x2I, Transform2I}; use pathfinder_geometry::vector::Vector2I; use pathfinder_simd::default::I32x4; +use std::fmt::{self, Debug, Formatter}; const PAINT_TEXTURE_LENGTH: u32 = 1024; const PAINT_TEXTURE_SCALE: u32 = 65536 / PAINT_TEXTURE_LENGTH; @@ -23,23 +25,81 @@ const PAINT_TEXTURE_SCALE: u32 = 65536 / PAINT_TEXTURE_LENGTH; const SOLID_COLOR_TILE_LENGTH: u32 = 16; const MAX_SOLID_COLORS_PER_TILE: u32 = SOLID_COLOR_TILE_LENGTH * SOLID_COLOR_TILE_LENGTH; -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Paint { - pub color: ColorU, +#[derive(Clone)] +pub struct Palette { + pub(crate) paints: Vec, + cache: HashMap, +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum Paint { + Color(ColorU), + Gradient(Gradient), } #[derive(Clone, Copy, PartialEq, Debug)] pub struct PaintId(pub u16); +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct GradientId(pub u32); + +impl Debug for Paint { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match *self { + Paint::Color(color) => color.fmt(formatter), + Paint::Gradient(_) => { + // TODO(pcwalton) + write!(formatter, "(gradient)") + } + } + } +} + +impl Palette { + #[inline] + pub fn new() -> Palette { + Palette { paints: vec![], cache: HashMap::new() } + } +} + impl Paint { #[inline] - pub fn is_opaque(&self) -> bool { - self.color.a == 255 + pub fn black() -> Paint { + Paint::Color(ColorU::black()) } #[inline] + pub fn transparent_black() -> Paint { + Paint::Color(ColorU::transparent_black()) + } + + pub fn is_opaque(&self) -> bool { + match *self { + Paint::Color(color) => color.is_opaque(), + Paint::Gradient(ref gradient) => { + gradient.stops().iter().all(|stop| stop.color.is_opaque()) + } + } + } + pub fn is_fully_transparent(&self) -> bool { - self.color.is_fully_transparent() + match *self { + Paint::Color(color) => color.is_opaque(), + Paint::Gradient(ref gradient) => { + gradient.stops().iter().all(|stop| stop.color.is_fully_transparent()) + } + } + } + + pub fn set_opacity(&mut self, alpha: f32) { + if alpha == 1.0 { + return; + } + + match *self { + Paint::Color(ref mut color) => color.a = (color.a as f32 * alpha).round() as u8, + Paint::Gradient(ref mut gradient) => gradient.set_opacity(alpha), + } } } @@ -60,7 +120,19 @@ pub struct PaintMetadata { pub is_opaque: bool, } -impl Scene { +impl Palette { + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn push_paint(&mut self, paint: &Paint) -> PaintId { + if let Some(paint_id) = self.cache.get(paint) { + return *paint_id; + } + + let paint_id = PaintId(self.paints.len() as u16); + self.cache.insert((*paint).clone(), paint_id); + self.paints.push((*paint).clone()); + paint_id + } + pub fn build_paint_info(&self) -> PaintInfo { let mut allocator = TextureAllocator::new(PAINT_TEXTURE_LENGTH); let area = PAINT_TEXTURE_LENGTH as usize * PAINT_TEXTURE_LENGTH as usize; @@ -68,14 +140,21 @@ impl Scene { let mut solid_color_tile_builder = SolidColorTileBuilder::new(); for paint in &self.paints { - // TODO(pcwalton): Handle other paint types. - let texture_location = solid_color_tile_builder.allocate(&mut allocator); - put_pixel(&mut texels, texture_location.rect.origin(), paint.color); - let tex_transform = Transform2I { - matrix: Matrix2x2I(I32x4::default()), - vector: texture_location.rect.origin().scale(PAINT_TEXTURE_SCALE as i32) + - Vector2I::splat(PAINT_TEXTURE_SCALE as i32 / 2), - }; + let tex_transform; + match paint { + Paint::Color(color) => { + // TODO(pcwalton): Handle other paint types. + let texture_location = solid_color_tile_builder.allocate(&mut allocator); + put_pixel(&mut texels, texture_location.rect.origin(), *color); + tex_transform = Transform2I { + matrix: Matrix2x2I(I32x4::default()), + vector: texture_location.rect.origin().scale(PAINT_TEXTURE_SCALE as i32) + + Vector2I::splat(PAINT_TEXTURE_SCALE as i32 / 2), + }; + } + Paint::Gradient(_) => unimplemented!(), + } + metadata.push(PaintMetadata { tex_transform, is_opaque: paint.is_opaque() }); } diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 79e64485..9892cfa6 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -14,8 +14,7 @@ use crate::builder::SceneBuilder; use crate::concurrent::executor::Executor; use crate::options::{BuildOptions, PreparedBuildOptions}; use crate::options::{PreparedRenderTransform, RenderCommandListener}; -use crate::paint::{Paint, PaintId}; -use hashbrown::HashMap; +use crate::paint::{Paint, PaintId, PaintInfo, Palette}; use pathfinder_color::ColorU; use pathfinder_geometry::vector::Vector2F; use pathfinder_geometry::rect::RectF; @@ -25,8 +24,7 @@ use pathfinder_content::outline::Outline; #[derive(Clone)] pub struct Scene { pub(crate) paths: Vec, - pub(crate) paints: Vec, - paint_cache: HashMap, + palette: Palette, bounds: RectF, view_box: RectF, } @@ -36,8 +34,7 @@ impl Scene { pub fn new() -> Scene { Scene { paths: vec![], - paints: vec![], - paint_cache: HashMap::new(), + palette: Palette::new(), bounds: RectF::default(), view_box: RectF::default(), } @@ -48,16 +45,14 @@ impl Scene { self.paths.push(path); } + #[inline] + pub fn build_paint_info(&self) -> PaintInfo { + self.palette.build_paint_info() + } + #[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 + self.palette.push_paint(paint) } #[inline] @@ -150,7 +145,11 @@ impl Scene { .any(|path_object| path_object.paint != first_paint_id) { return None; } - Some(self.paints[first_paint_id.0 as usize].color) + + match self.palette.paints[first_paint_id.0 as usize] { + Paint::Color(color) => Some(color), + Paint::Gradient(_) => None, + } } #[inline] @@ -190,7 +189,7 @@ impl<'a> Iterator for PathIter<'a> { fn next(&mut self) -> Option { let item = self.scene.paths.get(self.pos).map(|path_object| { ( - self.scene.paints.get(path_object.paint.0 as usize).unwrap(), + self.scene.palette.paints.get(path_object.paint.0 as usize).unwrap(), &path_object.outline, &*path_object.name ) diff --git a/svg/src/lib.rs b/svg/src/lib.rs index cab2520b..72219bda 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -246,16 +246,15 @@ impl PaintExt for Paint { #[inline] fn from_svg_paint(svg_paint: &UsvgPaint, opacity: Opacity, result_flags: &mut BuildResultFlags) -> Paint { - Paint { - color: match *svg_paint { - UsvgPaint::Color(color) => ColorU::from_svg_color(color, opacity), - UsvgPaint::Link(_) => { - // TODO(pcwalton) - result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT); - ColorU::black() - } - }, - } + // TODO(pcwalton): Support gradients. + Paint::Color(match *svg_paint { + UsvgPaint::Color(color) => ColorU::from_svg_color(color, opacity), + UsvgPaint::Link(_) => { + // TODO(pcwalton) + result_flags.insert(BuildResultFlags::UNSUPPORTED_LINK_PAINT); + ColorU::black() + } + }) } } diff --git a/swf/src/shapes.rs b/swf/src/shapes.rs index 835d8521..2b3d3c46 100644 --- a/swf/src/shapes.rs +++ b/swf/src/shapes.rs @@ -13,8 +13,8 @@ use crate::{Twips, Point2}; use pathfinder_color::ColorU; use pathfinder_content::stroke::{LineJoin, LineCap}; use pathfinder_renderer::paint::Paint; -use std::mem; use std::cmp::Ordering; +use std::mem; use swf_tree::tags::DefineShape; use swf_tree::{CapStyle, FillStyle, JoinStyle, LineStyle, ShapeRecord, StraightSRgba8, Vector2D}; use swf_tree::{fill_styles, join_styles, shape_records}; @@ -149,10 +149,10 @@ impl StyleLayer { } } - pub(crate) fn fill(&self) -> Paint { + pub(crate) fn fill(&self) -> &Paint { match &self.fill { - PaintOrLine::Paint(paint) => *paint, - PaintOrLine::Line(line) => line.color, + PaintOrLine::Paint(ref paint) => paint, + PaintOrLine::Line(line) => &line.color, } } @@ -253,16 +253,7 @@ fn get_new_styles<'a>( a } } - ) => { - Some(PaintOrLine::Paint(Paint { - color: ColorU { - r: *r, - g: *g, - b: *b, - a: *a - } - })) - }, + ) => Some(PaintOrLine::Paint(Paint::Color(ColorU { r: *r, g: *g, b: *b, a: *a }))), _ => unimplemented!("Unimplemented fill style") } }).chain( @@ -295,7 +286,7 @@ fn get_new_styles<'a>( // assert_eq!(start_cap, end_cap); Some(PaintOrLine::Line(SwfLineStyle { width: Twips(*width as i32), - color: Paint { color: ColorU { r: *r, g: *g, b: *b, a: *a } }, + color: Paint::Color(ColorU { r: *r, g: *g, b: *b, a: *a }), join: match join { JoinStyle::Bevel => LineJoin::Bevel, JoinStyle::Round => LineJoin::Round,