Add an API for gradients to the canvas frontend, not implemented yet

This commit is contained in:
Patrick Walton 2020-02-05 19:59:40 -08:00
parent 9a2e4f0b6d
commit 706b6dbd1d
10 changed files with 266 additions and 90 deletions

View File

@ -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

View File

@ -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),
}
}
}

View File

@ -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

View File

@ -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<ColorStop>,
}
#[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<H>(&self, state: &mut H) where H: Hasher {
unsafe {
let data: [u32; 4] = mem::transmute::<F32x4, [u32; 4]>(self.line.0);
data.hash(state);
self.stops.hash(state);
}
}
}
impl Eq for ColorStop {}
impl Hash for ColorStop {
fn hash<H>(&self, state: &mut H) where H: Hasher {
unsafe {
self.color.hash(state);
let offset = mem::transmute::<f32, u32>(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;
}
}
}

View File

@ -13,7 +13,7 @@
use std::cmp::Ordering;
use std::convert;
#[derive(Clone, Debug)]
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct SortedVector<T>
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<usize, usize>
where F: FnMut(&'a T) -> Ordering {
self.array.binary_search_by(f)
}
}
#[cfg(test)]

View File

@ -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<W: Write>(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, "</svg>")?;
Ok(())
@ -79,7 +76,12 @@ fn export_pdf<W: Write>(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<W: Write>(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<W: Write>(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")?;

View File

@ -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<Paint>,
cache: HashMap<Paint, PaintId>,
}
#[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() });
}

View File

@ -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<PathObject>,
pub(crate) paints: Vec<Paint>,
paint_cache: HashMap<Paint, PaintId>,
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<Self::Item> {
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
)

View File

@ -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()
}
})
}
}

View File

@ -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,