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)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Gradient {
|
pub struct Gradient {
|
||||||
line: LineSegment2F,
|
geometry: GradientGeometry,
|
||||||
stops: SortedVector<ColorStop>,
|
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)]
|
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
|
||||||
pub struct ColorStop {
|
pub struct ColorStop {
|
||||||
pub offset: f32,
|
pub offset: f32,
|
||||||
|
@ -33,10 +43,32 @@ impl Eq for Gradient {}
|
||||||
|
|
||||||
impl Hash for Gradient {
|
impl Hash for Gradient {
|
||||||
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
fn hash<H>(&self, state: &mut H) where H: Hasher {
|
||||||
unsafe {
|
match self.geometry {
|
||||||
let data: [u32; 4] = mem::transmute::<F32x4, [u32; 4]>(self.line.0);
|
GradientGeometry::Linear(line) => {
|
||||||
data.hash(state);
|
(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);
|
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 {
|
impl Gradient {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(line: LineSegment2F) -> Gradient {
|
pub fn new(geometry: GradientGeometry) -> Gradient {
|
||||||
Gradient { line, stops: SortedVector::new() }
|
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]
|
#[inline]
|
||||||
|
@ -65,8 +107,8 @@ impl Gradient {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn line(&self) -> LineSegment2F {
|
pub fn geometry(&self) -> &GradientGeometry {
|
||||||
self.line
|
&self.geometry
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -247,7 +247,6 @@ impl LineSegment2F {
|
||||||
self.sample(0.5)
|
self.sample(0.5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn offset(self, distance: f32) -> LineSegment2F {
|
pub fn offset(self, distance: f32) -> LineSegment2F {
|
||||||
if self.is_zero_length() {
|
if self.is_zero_length() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::gpu_data::PaintData;
|
||||||
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
|
use crate::tiles::{TILE_HEIGHT, TILE_WIDTH};
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use pathfinder_color::ColorU;
|
use pathfinder_color::ColorU;
|
||||||
use pathfinder_content::gradient::Gradient;
|
use pathfinder_content::gradient::{Gradient, GradientGeometry};
|
||||||
use pathfinder_geometry::rect::{RectF, RectI};
|
use pathfinder_geometry::rect::{RectF, RectI};
|
||||||
use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F};
|
use pathfinder_geometry::transform2d::{Matrix2x2F, Transform2F};
|
||||||
use pathfinder_geometry::util;
|
use pathfinder_geometry::util;
|
||||||
|
@ -174,23 +174,10 @@ impl Palette {
|
||||||
Transform2F::from_scale(Vector2F::splat(GRADIENT_TILE_SCALE) /
|
Transform2F::from_scale(Vector2F::splat(GRADIENT_TILE_SCALE) /
|
||||||
view_box_size.to_f32());
|
view_box_size.to_f32());
|
||||||
|
|
||||||
let gradient_line = tex_transform * gradient.line();
|
self.build_paint_info_for_gradient(gradient,
|
||||||
|
texture_location,
|
||||||
// TODO(pcwalton): Optimize this:
|
&tex_transform,
|
||||||
// 1. Calculate ∇t up front and use differencing in the inner loop.
|
&mut texels);
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,21 +191,58 @@ impl Palette {
|
||||||
let size = Vector2I::splat(PAINT_TEXTURE_LENGTH as i32);
|
let size = Vector2I::splat(PAINT_TEXTURE_LENGTH as i32);
|
||||||
return PaintInfo { data: PaintData { size, texels }, metadata };
|
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 {
|
fn build_paint_info_for_gradient(&self,
|
||||||
rect.to_f32().scale(1.0 / PAINT_TEXTURE_LENGTH as f32)
|
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 {
|
// TODO(pcwalton): Optimize this:
|
||||||
rect_to_uv(rect).contract(Vector2F::splat(0.5 / PAINT_TEXTURE_LENGTH as f32))
|
// 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
|
// Solid color allocation
|
||||||
|
|
||||||
struct SolidColorTileBuilder(Option<SolidColorTileBuilderData>);
|
struct SolidColorTileBuilder(Option<SolidColorTileBuilderData>);
|
||||||
|
|
Loading…
Reference in New Issue