Implement basic radial gradients.
This doesn't handle the case where the circles are not concentric yet.
This commit is contained in:
parent
5a21557a6d
commit
aad467e716
|
@ -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);
|
||||
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]
|
||||
|
|
|
@ -247,7 +247,6 @@ impl LineSegment2F {
|
|||
self.sample(0.5)
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
pub fn offset(self, distance: f32) -> LineSegment2F {
|
||||
if self.is_zero_length() {
|
||||
|
|
|
@ -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,7 +174,34 @@ impl Palette {
|
|||
Transform2F::from_scale(Vector2F::splat(GRADIENT_TILE_SCALE) /
|
||||
view_box_size.to_f32());
|
||||
|
||||
let gradient_line = tex_transform * gradient.line();
|
||||
self.build_paint_info_for_gradient(gradient,
|
||||
texture_location,
|
||||
&tex_transform,
|
||||
&mut texels);
|
||||
}
|
||||
}
|
||||
|
||||
metadata.push(PaintMetadata {
|
||||
tex_rect: texture_location.rect,
|
||||
tex_transform,
|
||||
is_opaque: paint.is_opaque(),
|
||||
});
|
||||
}
|
||||
|
||||
let size = Vector2I::splat(PAINT_TEXTURE_LENGTH as i32);
|
||||
return PaintInfo { data: PaintData { size, texels }, metadata };
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// TODO(pcwalton): Optimize this:
|
||||
// 1. Calculate ∇t up front and use differencing in the inner loop.
|
||||
|
@ -188,21 +215,46 @@ impl Palette {
|
|||
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));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metadata.push(PaintMetadata {
|
||||
tex_rect: texture_location.rect,
|
||||
tex_transform,
|
||||
is_opaque: paint.is_opaque(),
|
||||
});
|
||||
impl PaintMetadata {
|
||||
// TODO(pcwalton): Apply clamp/repeat to tile rect.
|
||||
pub(crate) fn calculate_tex_coords(&self, tile_position: Vector2I) -> Vector2F {
|
||||
let tile_size = Vector2I::new(TILE_WIDTH as i32, TILE_HEIGHT as i32);
|
||||
let tex_coords = self.tex_transform * tile_position.scale_xy(tile_size).to_f32();
|
||||
tex_coords
|
||||
}
|
||||
}
|
||||
|
||||
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 +
|
||||
|
@ -220,17 +272,6 @@ impl Palette {
|
|||
fn rect_to_inset_uv(rect: RectI) -> RectF {
|
||||
rect_to_uv(rect).contract(Vector2F::splat(0.5 / PAINT_TEXTURE_LENGTH as f32))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaintMetadata {
|
||||
// TODO(pcwalton): Apply clamp/repeat to tile rect.
|
||||
pub(crate) fn calculate_tex_coords(&self, tile_position: Vector2I) -> Vector2F {
|
||||
let tile_size = Vector2I::new(TILE_WIDTH as i32, TILE_HEIGHT as i32);
|
||||
let tex_coords = self.tex_transform * tile_position.scale_xy(tile_size).to_f32();
|
||||
tex_coords
|
||||
}
|
||||
}
|
||||
|
||||
// Solid color allocation
|
||||
|
||||
|
|
Loading…
Reference in New Issue