Implement basic radial gradients.

This doesn't handle the case where the circles are not concentric yet.
This commit is contained in:
Patrick Walton 2020-02-10 22:15:50 -08:00
parent 5a21557a6d
commit aad467e716
3 changed files with 123 additions and 41 deletions

View File

@ -19,10 +19,20 @@ use std::mem;
#[derive(Clone, PartialEq, Debug)]
pub struct Gradient {
line: LineSegment2F,
geometry: GradientGeometry,
stops: SortedVector<ColorStop>,
}
#[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<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);
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<H>(line_segment: LineSegment2F, state: &mut H) where H: Hasher {
unsafe {
let data: [u32; 4] = mem::transmute::<F32x4, [u32; 4]>(line_segment.0);
data.hash(state);
}
}
fn hash_f32<H>(value: f32, state: &mut H) where H: Hasher {
unsafe {
let data: u32 = mem::transmute::<f32, u32>(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]

View File

@ -247,7 +247,6 @@ impl LineSegment2F {
self.sample(0.5)
}
#[inline]
pub fn offset(self, distance: f32) -> LineSegment2F {
if self.is_zero_length() {

View File

@ -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<SolidColorTileBuilderData>);