diff --git a/content/src/gradient.rs b/content/src/gradient.rs index 54bc2382..e7a6098a 100644 --- a/content/src/gradient.rs +++ b/content/src/gradient.rs @@ -19,10 +19,20 @@ use std::mem; #[derive(Clone, PartialEq, Debug)] pub struct Gradient { - line: LineSegment2F, + geometry: GradientGeometry, stops: SortedVector, } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum GradientGeometry { + Linear(LineSegment2F), + Radial { + line: LineSegment2F, + start_radius: f32, + end_radius: f32, + } +} + #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] pub struct ColorStop { pub offset: f32, @@ -33,10 +43,32 @@ 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); + match self.geometry { + GradientGeometry::Linear(line) => { + (0).hash(state); + hash_line_segment(line, state); + } + GradientGeometry::Radial { line, start_radius, end_radius } => { + (1).hash(state); + hash_line_segment(line, state); + hash_f32(start_radius, state); + hash_f32(end_radius, state); + } + } + self.stops.hash(state); + + fn hash_line_segment(line_segment: LineSegment2F, state: &mut H) where H: Hasher { + unsafe { + let data: [u32; 4] = mem::transmute::(line_segment.0); + data.hash(state); + } + } + + fn hash_f32(value: f32, state: &mut H) where H: Hasher { + unsafe { + let data: u32 = mem::transmute::(value); + data.hash(state); + } } } } @@ -55,8 +87,18 @@ impl Hash for ColorStop { impl Gradient { #[inline] - pub fn new(line: LineSegment2F) -> Gradient { - Gradient { line, stops: SortedVector::new() } + pub fn new(geometry: GradientGeometry) -> Gradient { + Gradient { geometry, stops: SortedVector::new() } + } + + #[inline] + pub fn linear(line: LineSegment2F) -> Gradient { + Gradient::new(GradientGeometry::Linear(line)) + } + + #[inline] + pub fn radial(line: LineSegment2F, start_radius: f32, end_radius: f32) -> Gradient { + Gradient::new(GradientGeometry::Radial { line, start_radius, end_radius }) } #[inline] @@ -65,8 +107,8 @@ impl Gradient { } #[inline] - pub fn line(&self) -> LineSegment2F { - self.line + pub fn geometry(&self) -> &GradientGeometry { + &self.geometry } #[inline] diff --git a/geometry/src/line_segment.rs b/geometry/src/line_segment.rs index 4a15e471..77640274 100644 --- a/geometry/src/line_segment.rs +++ b/geometry/src/line_segment.rs @@ -247,7 +247,6 @@ impl LineSegment2F { self.sample(0.5) } - #[inline] pub fn offset(self, distance: f32) -> LineSegment2F { if self.is_zero_length() { diff --git a/renderer/src/paint.rs b/renderer/src/paint.rs index 80c2a2b4..6ca98e9b 100644 --- a/renderer/src/paint.rs +++ b/renderer/src/paint.rs @@ -13,7 +13,7 @@ use crate::gpu_data::PaintData; use crate::tiles::{TILE_HEIGHT, TILE_WIDTH}; use hashbrown::HashMap; use pathfinder_color::ColorU; -use pathfinder_content::gradient::Gradient; +use pathfinder_content::gradient::{Gradient, GradientGeometry}; use pathfinder_geometry::rect::{RectF, RectI}; use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F}; use pathfinder_geometry::util; @@ -174,23 +174,10 @@ impl Palette { Transform2F::from_scale(Vector2F::splat(GRADIENT_TILE_SCALE) / view_box_size.to_f32()); - let gradient_line = tex_transform * gradient.line(); - - // TODO(pcwalton): Optimize this: - // 1. Calculate ∇t up front and use differencing in the inner loop. - // 2. Go four pixels at a time with SIMD. - for y in 0..(GRADIENT_TILE_LENGTH as i32) { - for x in 0..(GRADIENT_TILE_LENGTH as i32) { - let point = texture_location.rect.origin() + Vector2I::new(x, y); - let vector = point.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32) - - gradient_line.from(); - - let mut t = gradient_line.vector().projection_coefficient(vector); - t = util::clamp(t, 0.0, 1.0); - - put_pixel(&mut texels, point, gradient.sample(t)); - } - } + self.build_paint_info_for_gradient(gradient, + texture_location, + &tex_transform, + &mut texels); } } @@ -204,21 +191,58 @@ impl Palette { let size = Vector2I::splat(PAINT_TEXTURE_LENGTH as i32); return PaintInfo { data: PaintData { size, texels }, metadata }; - fn put_pixel(texels: &mut [u8], position: Vector2I, color: ColorU) { - let index = (position.y() as usize * PAINT_TEXTURE_LENGTH as usize + - position.x() as usize) * 4; - texels[index + 0] = color.r; - texels[index + 1] = color.g; - texels[index + 2] = color.b; - texels[index + 3] = color.a; - } + } - fn rect_to_uv(rect: RectI) -> RectF { - rect.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32) - } + fn build_paint_info_for_gradient(&self, + gradient: &Gradient, + texture_location: TextureLocation, + tex_transform: &Transform2F, + texels: &mut [u8]) { + match *gradient.geometry() { + GradientGeometry::Linear(gradient_line) => { + // FIXME(pcwalton): Paint transparent if gradient line has zero size, per spec. + let gradient_line = *tex_transform * gradient_line; - fn rect_to_inset_uv(rect: RectI) -> RectF { - rect_to_uv(rect).contract(Vector2F::splat(0.5 / PAINT_TEXTURE_LENGTH as f32)) + // TODO(pcwalton): Optimize this: + // 1. Calculate ∇t up front and use differencing in the inner loop. + // 2. Go four pixels at a time with SIMD. + for y in 0..(GRADIENT_TILE_LENGTH as i32) { + for x in 0..(GRADIENT_TILE_LENGTH as i32) { + let point = texture_location.rect.origin() + Vector2I::new(x, y); + let vector = point.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32) - + gradient_line.from(); + + let mut t = gradient_line.vector().projection_coefficient(vector); + t = util::clamp(t, 0.0, 1.0); + + put_pixel(texels, point, gradient.sample(t)); + } + } + } + GradientGeometry::Radial { line: gradient_line, start_radius, end_radius } => { + // FIXME(pcwalton): Paint transparent if line has zero size and radii are equal, + // per spec. + let tex_transform_inv = tex_transform.inverse(); + + // FIXME(pcwalton): This is not correct. Follow the spec. + let center = gradient_line.midpoint(); + + // TODO(pcwalton): Optimize this: + // 1. Calculate ∇t up front and use differencing in the inner loop, if possible. + // 2. Go four pixels at a time with SIMD. + for y in 0..(GRADIENT_TILE_LENGTH as i32) { + for x in 0..(GRADIENT_TILE_LENGTH as i32) { + let point = texture_location.rect.origin() + Vector2I::new(x, y); + let vector = tex_transform_inv * + point.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32); + + let t = util::clamp((vector - center).length(), start_radius, end_radius) / + (end_radius - start_radius); + + put_pixel(texels, point, gradient.sample(t)); + } + } + } } } } @@ -232,6 +256,23 @@ impl PaintMetadata { } } +fn put_pixel(texels: &mut [u8], position: Vector2I, color: ColorU) { + let index = (position.y() as usize * PAINT_TEXTURE_LENGTH as usize + + position.x() as usize) * 4; + texels[index + 0] = color.r; + texels[index + 1] = color.g; + texels[index + 2] = color.b; + texels[index + 3] = color.a; +} + +fn rect_to_uv(rect: RectI) -> RectF { + rect.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32) +} + +fn rect_to_inset_uv(rect: RectI) -> RectF { + rect_to_uv(rect).contract(Vector2F::splat(0.5 / PAINT_TEXTURE_LENGTH as f32)) +} + // Solid color allocation struct SolidColorTileBuilder(Option);