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:
parent
c41ae92404
commit
7af61159a8
@ -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)
|
||||
|
@ -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),
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user