1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +03:00

font: port over radial gradient fills

This commit is contained in:
Wez Furlong 2023-08-22 06:43:58 -07:00
parent c41ae92404
commit 7af61159a8
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
3 changed files with 389 additions and 14 deletions

View File

@ -221,6 +221,38 @@ impl SrgbaPixel {
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
pub struct SrgbaTuple(pub f32, pub f32, pub f32, pub f32);
impl SrgbaTuple {
pub fn premultiply(self) -> Self {
let SrgbaTuple(r, g, b, a) = self;
Self(r * a, g * a, b * a, a)
}
pub fn demultiply(self) -> Self {
let SrgbaTuple(r, g, b, a) = self;
if a != 0. {
Self(r / a, g / a, b / a, a)
} else {
self
}
}
pub fn interpolate(self, other: Self, k: f64) -> Self {
let k = k as f32;
let SrgbaTuple(r0, g0, b0, a0) = self.premultiply();
let SrgbaTuple(r1, g1, b1, a1) = other.premultiply();
let r = SrgbaTuple(
r0 + k * (r1 - r0),
g0 + k * (g1 - g0),
b0 + k * (b1 - b0),
a0 + k * (a1 - a0),
);
r.demultiply()
}
}
impl ToDynamic for SrgbaTuple {
fn to_dynamic(&self) -> Value {
self.to_string().to_dynamic()
@ -237,6 +269,13 @@ impl FromDynamic for SrgbaTuple {
}
}
impl From<SrgbaPixel> for SrgbaTuple {
fn from(pixel: SrgbaPixel) -> SrgbaTuple {
let (r, g, b, a) = pixel.as_srgba_tuple();
SrgbaTuple(r, g, b, a)
}
}
impl From<(f32, f32, f32, f32)> for SrgbaTuple {
fn from((r, g, b, a): (f32, f32, f32, f32)) -> SrgbaTuple {
SrgbaTuple(r, g, b, a)

View File

@ -864,8 +864,12 @@ impl ColorLine {
color_stops: color_stops
.into_iter()
.map(|stop| ColorStop {
offset: stop.offset,
color: hb_color_to_srgba_pixel(stop.color),
offset: stop.offset.into(),
color: if stop.is_foreground != 0 {
SrgbaPixel::rgba(0xff, 0xff, 0xff, 0xff)
} else {
hb_color_to_srgba_pixel(stop.color)
},
})
.collect(),
extend: hb_extend_to_cairo(extend),

View File

@ -1,9 +1,17 @@
use cairo::{Context, Extend, LinearGradient, Matrix, Operator, RadialGradient};
use wezterm_color_types::SrgbaPixel;
use cairo::{Context, Extend, LinearGradient, Matrix, Mesh, MeshCorner, Operator, RadialGradient};
use wezterm_color_types::{SrgbaPixel, SrgbaTuple};
/* The gradient related routines in this file were ported from HarfBuzz, which
* were in turn ported from BlackRenderer by Black Foundry.
* Used by permission to relicense to HarfBuzz license,
* which is in turn compatible with wezterm's license.
*
* https://github.com/BlackFoundryCom/black-renderer
*/
#[derive(Clone, Debug)]
pub struct ColorStop {
pub offset: f32,
pub offset: f64,
pub color: SrgbaPixel,
}
@ -148,16 +156,346 @@ pub fn paint_radial_gradient(
Ok(())
}
#[derive(Copy, Clone, Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn dot(&self, other: Self) -> f64 {
(self.x * other.x) + (self.y * other.y)
}
fn normalize(self) -> Self {
let len = self.dot(self).sqrt();
Self {
x: self.x / len,
y: self.y / len,
}
}
pub fn sum(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
pub fn difference(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
}
}
pub fn scale(self, factor: f64) -> Self {
Self {
x: self.x * factor,
y: self.y * factor,
}
}
/// Compute a vector from the supplied angle
pub fn from_angle(angle: f64) -> Self {
let (y, x) = angle.sin_cos();
Self { x, y }
}
}
fn interpolate(f0: f64, f1: f64, f: f64) -> f64 {
f0 + f * (f1 - f0)
}
#[derive(Debug)]
struct Patch {
p0: Point,
c0: Point,
c1: Point,
p1: Point,
color0: SrgbaTuple,
color1: SrgbaTuple,
}
impl Patch {
fn add_to_mesh(&self, center: Point, mesh: &Mesh) {
mesh.begin_patch();
mesh.move_to(center.x, center.y);
mesh.line_to(self.p0.x, self.p0.y);
mesh.curve_to(
self.c0.x, self.c0.y, self.c1.x, self.c1.y, self.p1.x, self.p1.y,
);
mesh.line_to(center.x, center.y);
fn set_corner_color(mesh: &Mesh, corner: MeshCorner, color: SrgbaTuple) {
let SrgbaTuple(r, g, b, a) = color;
mesh.set_corner_color_rgba(corner, r.into(), g.into(), b.into(), a.into());
}
set_corner_color(mesh, MeshCorner::MeshCorner0, self.color0);
set_corner_color(mesh, MeshCorner::MeshCorner1, self.color0);
set_corner_color(mesh, MeshCorner::MeshCorner2, self.color1);
set_corner_color(mesh, MeshCorner::MeshCorner3, self.color1);
mesh.end_patch();
}
}
fn add_sweep_gradient_patches(
mesh: &Mesh,
center: Point,
radius: f64,
a0: f64,
c0: SrgbaTuple,
a1: f64,
c1: SrgbaTuple,
) {
const MAX_ANGLE: f64 = std::f64::consts::PI / 8.;
let num_splits = ((a1 - a0).abs() / MAX_ANGLE).ceil() as usize;
let mut p0 = Point::from_angle(a0);
let mut color0 = c0;
for idx in 0..num_splits {
let k = (idx as f64 + 1.) / num_splits as f64;
let angle1 = interpolate(a0, a1, k);
let color1 = c0.interpolate(c1, k);
let p1 = Point::from_angle(angle1);
let a = p0.sum(p1).normalize();
let u = Point { x: -a.y, y: a.x };
fn compute_control(a: Point, u: Point, p: Point, center: Point, radius: f64) -> Point {
let c = a.sum(u.scale(p.difference(a).dot(p) / u.dot(p)));
c.difference(p)
.scale(0.33333)
.sum(c)
.scale(radius)
.sum(center)
}
let patch = Patch {
color0,
color1,
p0: center.sum(p0.scale(radius)),
p1: center.sum(p1.scale(radius)),
c0: compute_control(a, u, p0, center, radius),
c1: compute_control(a, u, p1, center, radius),
};
patch.add_to_mesh(center, mesh);
p0 = p1;
color0 = color1;
}
}
const PI_TIMES_2: f64 = std::f64::consts::PI * 2.;
fn apply_sweep_gradient_patches(
mesh: &Mesh,
mut color_line: ColorLine,
center: Point,
radius: f64,
mut start_angle: f64,
mut end_angle: f64,
) {
if start_angle == end_angle {
if color_line.extend == Extend::Pad {
if start_angle > 0. {
let c = color_line.color_stops[0].color.into();
add_sweep_gradient_patches(mesh, center, radius, 0., c, start_angle, c);
}
if end_angle < PI_TIMES_2 {
let c = color_line.color_stops.last().unwrap().color.into();
add_sweep_gradient_patches(mesh, center, radius, end_angle, c, PI_TIMES_2, c);
}
}
return;
}
if end_angle < start_angle {
std::mem::swap(&mut start_angle, &mut end_angle);
color_line.color_stops.reverse();
for stop in &mut color_line.color_stops {
stop.offset = 1.0 - stop.offset;
}
}
let angles: Vec<f64> = color_line
.color_stops
.iter()
.map(|stop| start_angle + stop.offset * (end_angle - start_angle))
.collect();
let colors: Vec<SrgbaTuple> = color_line
.color_stops
.iter()
.map(|stop| stop.color.into())
.collect();
let n_stops = angles.len();
if color_line.extend == Extend::Pad {
let mut color0 = colors[0];
let mut pos = 0;
while pos < n_stops {
if angles[pos] >= 0. {
if pos > 0 {
let k = (0. - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
color0 = colors[pos - 1].interpolate(colors[pos], k);
}
break;
}
pos += 1;
}
if pos == n_stops {
/* everything is below 0 */
color0 = colors[n_stops - 1];
add_sweep_gradient_patches(mesh, center, radius, 0., color0, PI_TIMES_2, color0);
return;
}
add_sweep_gradient_patches(mesh, center, radius, 0., color0, angles[pos], colors[pos]);
pos += 1;
while pos < n_stops {
if angles[pos] <= PI_TIMES_2 {
add_sweep_gradient_patches(
mesh,
center,
radius,
angles[pos - 1],
colors[pos - 1],
angles[pos],
colors[pos],
);
} else {
let k = (PI_TIMES_2 - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
let color1 = colors[pos - 1].interpolate(colors[pos], k);
add_sweep_gradient_patches(
mesh,
center,
radius,
angles[pos - 1],
colors[pos - 1],
PI_TIMES_2,
color1,
);
break;
}
pos += 1;
}
if pos == n_stops {
/* everything is below 2*M_PI */
color0 = colors[n_stops - 1];
add_sweep_gradient_patches(
mesh,
center,
radius,
angles[n_stops - 1],
color0,
PI_TIMES_2,
color0,
);
return;
}
} else {
let span = angles[n_stops - 1] - angles[0];
let mut k = 0isize;
if angles[0] >= 0. {
let mut ss = angles[0];
while ss > 0. {
if span > 0. {
ss -= span;
k -= 1;
} else {
ss += span;
k += 1;
}
}
} else if angles[0] < 0. {
let mut ee = angles[n_stops - 1];
while ee < 0. {
if span > 0. {
ee += span;
k += 1;
} else {
ee -= span;
k -= 1;
}
}
}
debug_assert!(
angles[0] + (k as f64) * span <= 0. && 0. < angles[n_stops - 1] + (k as f64) * span
);
let span = span.abs();
for l in k..k.min(1000) {
for i in 1..n_stops {
let (a0, a1, c0, c1);
if l % 2 != 0 && color_line.extend == Extend::Reflect {
a0 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - (i - 1)]
+ (l as f64) * span;
a1 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - i]
+ (l as f64) * span;
c0 = colors[n_stops - 1 - (i - 1)];
c1 = colors[n_stops - 1 - i];
} else {
a0 = angles[i - 1] + (l as f64) * span;
a1 = angles[i] + (l as f64) * span;
c0 = colors[i - 1];
c1 = colors[i];
}
if a1 < 0. {
continue;
}
if a0 < 0. {
let f = (0. - a0) / (a1 - a0);
let color = c0.interpolate(c1, f);
add_sweep_gradient_patches(mesh, center, radius, 0., color, a1, c1);
} else if a1 >= PI_TIMES_2 {
let f = (PI_TIMES_2 - a0) / (a1 - a0);
let color = c0.interpolate(c1, f);
add_sweep_gradient_patches(mesh, center, radius, a0, c0, PI_TIMES_2, color);
return;
} else {
add_sweep_gradient_patches(mesh, center, radius, a0, c0, a1, c1);
}
}
}
}
}
pub fn paint_sweep_gradient(
context: &Context,
x0: f64,
y0: f64,
start_angle: f64,
end_angle: f64,
mut color_line: ColorLine,
color_line: ColorLine,
) -> anyhow::Result<()> {
let (min_stop, max_stop) = normalize_color_line(&mut color_line);
anyhow::bail!("NOT IMPL: SweepGradient");
let (x1, y1, x2, y2) = context.clip_extents()?;
let max_x = ((x1 - x0) * (x1 - x0)).max((x2 - x0) * (x2 - x0));
let max_y = ((y1 - y0) * (y1 - y0)).max((y2 - y0) * (y2 - y0));
let radius = (max_x + max_y).sqrt();
let mesh = Mesh::new();
let center = Point { x: x0, y: y0 };
apply_sweep_gradient_patches(&mesh, color_line, center, radius, start_angle, end_angle);
context.set_source(mesh)?;
context.paint()?;
Ok(())
}
fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
@ -179,12 +517,6 @@ fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
}
}
// NOTE: hb-cairo-utils will call back out to some other state
// to fill in the color when is_foreground is true, defaulting
// to black with alpha varying by the alpha channel of the
// color value in the stop. Do we need to do something like
// that here?
(smallest as f64, largest as f64)
}