1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 22:42:48 +03:00

fonts: harfbuzz raster: add linear and radial gradients

fixup colors and excessive clipping.

This now looks decent. Still don't support sweep gradients;
those look complex and I'm not sure that I have a font to
test that against.
This commit is contained in:
Wez Furlong 2023-08-18 23:16:11 -07:00
parent f1c82e8894
commit 6b7ba71f32
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
2 changed files with 206 additions and 38 deletions

View File

@ -325,7 +325,6 @@ impl Font {
}
pub fn set_font_scale(&self, x_scale: c_int, y_scale: c_int) {
log::info!("setting x_scale={x_scale}, y_scale={y_scale}");
unsafe {
hb_font_set_scale(self.font, x_scale, y_scale);
}

View File

@ -1,11 +1,15 @@
use crate::hbwrap::{
hb_color, hb_color_get_alpha, hb_color_get_blue, hb_color_get_green, hb_color_get_red,
hb_color_t, hb_paint_composite_mode_t, hb_tag_to_string, DrawOp, Face, Font, PaintOp, IS_PNG,
hb_color_t, hb_paint_composite_mode_t, hb_paint_extend_t, hb_tag_to_string, ColorLine, DrawOp,
Face, Font, PaintOp, IS_PNG,
};
use crate::rasterizer::FAKE_ITALIC_SKEW;
use crate::units::PixelLength;
use crate::{FontRasterizer, ParsedFont, RasterizedGlyph};
use cairo::{Content, Context, Format, ImageSurface, Matrix, Operator, RecordingSurface};
use cairo::{
Content, Context, Extend, Format, ImageSurface, LinearGradient, Matrix, Operator,
RadialGradient, RecordingSurface,
};
use image::DynamicImage::{ImageLuma8, ImageLumaA8};
use image::GenericImageView;
@ -43,7 +47,6 @@ impl FontRasterizer for HarfbuzzRasterizer {
let scale = pixel_size as i32 * 64;
let ppem = pixel_size;
log::info!("computed scale={scale}, ppem={ppem}, upem={upem}");
self.font.set_ppem(ppem, ppem);
self.font.set_ptem(size as f32);
@ -60,6 +63,7 @@ impl FontRasterizer for HarfbuzzRasterizer {
let (surface, has_color) = record_to_cairo_surface(ops)?;
let (left, top, width, height) = surface.ink_extents();
log::trace!("extents: left={left} top={top} width={width} height={height}");
if width as usize == 0 || height as usize == 0 {
return Ok(RasterizedGlyph {
@ -73,7 +77,8 @@ impl FontRasterizer for HarfbuzzRasterizer {
}
let mut bounds_adjust = Matrix::identity();
bounds_adjust.translate(left.min(0.) * -1., top.min(0.) * -1.);
bounds_adjust.translate(left * -1., top * -1.);
log::trace!("dims: {width}x{height} {bounds_adjust:?}");
let target = ImageSurface::create(Format::ARgb32, width as i32, height as i32)?;
{
@ -103,6 +108,7 @@ fn record_to_cairo_surface(paint_ops: Vec<PaintOp>) -> anyhow::Result<(Recording
let surface = RecordingSurface::create(Content::ColorAlpha, None)?;
let context = Context::new(&surface)?;
context.scale(1. / 64., -1. / 64.);
context.set_antialias(cairo::Antialias::Best);
for pop in paint_ops {
match pop {
@ -168,7 +174,6 @@ fn record_to_cairo_surface(paint_ops: Vec<PaintOp>) -> anyhow::Result<(Recording
has_color = true;
}
let (r, g, b, a) = hb_color_to_rgba(color);
log::info!("PaintSolid {color:x} -> rgba {r} {g} {b} {a}");
context.set_source_rgba(r, g, b, a);
context.paint()?;
}
@ -182,9 +187,16 @@ fn record_to_cairo_surface(paint_ops: Vec<PaintOp>) -> anyhow::Result<(Recording
color_line,
} => {
has_color = true;
context.set_source_rgba(1.0, 1., 1., 1.0);
context.paint()?;
//anyhow::bail!("NOT IMPL: PaintLinearGradient");
paint_linear_gradient(
&context,
x0.into(),
y0.into(),
x1.into(),
y1.into(),
x2.into(),
y2.into(),
color_line,
)?;
}
PaintOp::PaintRadialGradient {
x0,
@ -196,7 +208,16 @@ fn record_to_cairo_surface(paint_ops: Vec<PaintOp>) -> anyhow::Result<(Recording
color_line,
} => {
has_color = true;
anyhow::bail!("NOT IMPL: PaintRadialGradient");
paint_radial_gradient(
&context,
x0.into(),
y0.into(),
r0.into(),
x1.into(),
y1.into(),
r1.into(),
color_line,
)?;
}
PaintOp::PaintSweepGradient {
x0,
@ -309,11 +330,10 @@ fn premultiply(data: &mut [u8]) {
}
fn rgba_to_argb_and_multiply(data: &mut [u8]) {
let data_32 = unsafe {
std::slice::from_raw_parts_mut(data.as_ptr() as *mut u8 as *mut u32, data.len() / 4)
};
for mut_pixel in data_32 {
let [mut r, mut g, mut b, a] = mut_pixel.to_ne_bytes();
for pixel in data.chunks_exact_mut(4) {
let [mut r, mut g, mut b, a] = *pixel else {
unreachable!()
};
if a != 0xff {
r = multiply_alpha(a, r);
@ -321,33 +341,28 @@ fn rgba_to_argb_and_multiply(data: &mut [u8]) {
b = multiply_alpha(a, b);
}
*mut_pixel = u32::from_ne_bytes([a, r, g, b]);
#[cfg(target_endian = "big")]
let result = [a, r, g, b];
#[cfg(target_endian = "little")]
let result = [b, g, r, a];
pixel.copy_from_slice(&result);
}
}
fn argb_to_rgba(data: &mut [u8]) {
let data_32 = unsafe {
std::slice::from_raw_parts_mut(data.as_ptr() as *mut u8 as *mut u32, data.len() / 4)
};
for mut_pixel in data_32 {
let [a, r, g, b] = mut_pixel.to_ne_bytes();
*mut_pixel = u32::from_ne_bytes([r, g, b, a]);
}
}
fn argb_to_rgba_and_demultiply(data: &mut [u8]) {
let data_32 = unsafe {
std::slice::from_raw_parts_mut(data.as_ptr() as *mut u8 as *mut u32, data.len() / 4)
};
for mut_pixel in data_32 {
let [a, mut r, mut g, mut b] = mut_pixel.to_ne_bytes();
if a < 0xff {
r = demultiply_alpha(a, r);
g = demultiply_alpha(a, g);
b = demultiply_alpha(a, b);
}
*mut_pixel = u32::from_ne_bytes([r, g, b, a]);
for pixel in data.chunks_exact_mut(4) {
#[cfg(target_endian = "little")]
let [b, g, r, a] = *pixel
else {
unreachable!()
};
#[cfg(target_endian = "big")]
let [a, r, g, b] = *pixel
else {
unreachable!()
};
pixel.copy_from_slice(&[r, g, b, a]);
}
}
@ -460,3 +475,157 @@ fn hb_color_to_rgba(color: hb_color_t) -> (f64, f64, f64, f64) {
let alpha = unsafe { hb_color_get_alpha(color) } as f64;
(red / 255., green / 255., blue / 255., alpha / 255.)
}
fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
let mut smallest = color_line.color_stops[0].offset;
let mut largest = smallest;
for stop in &color_line.color_stops[1..] {
smallest = smallest.min(stop.offset);
largest = largest.max(stop.offset);
}
if smallest != largest {
for stop in &mut color_line.color_stops {
stop.offset = (stop.offset - smallest) / (largest - smallest);
}
}
// 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)
}
struct ReduceAnchorsIn {
x0: f64,
y0: f64,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
}
struct ReduceAnchorsOut {
xx0: f64,
yy0: f64,
xx1: f64,
yy1: f64,
}
fn reduce_anchors(
ReduceAnchorsIn {
x0,
y0,
x1,
y1,
x2,
y2,
}: ReduceAnchorsIn,
) -> ReduceAnchorsOut {
let q2x = x2 - x0;
let q2y = y2 - y0;
let q1x = x1 - x0;
let q1y = y1 - y0;
let s = q2x * q2x + q2y * q2y;
if s < 0.000001 {
return ReduceAnchorsOut {
xx0: x0,
yy0: y0,
xx1: x1,
yy1: y1,
};
}
let k = (q2x * q1x + q2y * q1y) / s;
ReduceAnchorsOut {
xx0: x0,
yy0: y0,
xx1: x1 - k * q2x,
yy1: y1 - k * q2y,
}
}
fn paint_linear_gradient(
context: &Context,
x0: f64,
y0: f64,
x1: f64,
y1: f64,
x2: f64,
y2: f64,
mut color_line: ColorLine,
) -> anyhow::Result<()> {
let (min_stop, max_stop) = normalize_color_line(&mut color_line);
let anchors = reduce_anchors(ReduceAnchorsIn {
x0,
y0,
x1,
y1,
x2,
y2,
});
let xxx0 = anchors.xx0 + min_stop * (anchors.xx1 - anchors.xx0);
let yyy0 = anchors.yy0 + min_stop * (anchors.yy1 - anchors.yy0);
let xxx1 = anchors.xx0 + max_stop * (anchors.xx1 - anchors.xx0);
let yyy1 = anchors.yy0 + max_stop * (anchors.yy1 - anchors.yy0);
let pattern = LinearGradient::new(xxx0, yyy0, xxx1, yyy1);
pattern.set_extend(hb_extend_to_cairo(color_line.extend));
for stop in &color_line.color_stops {
let (r, g, b, a) = hb_color_to_rgba(stop.color);
pattern.add_color_stop_rgba(stop.offset.into(), r, g, b, a);
}
context.set_source(pattern)?;
context.paint()?;
Ok(())
}
fn paint_radial_gradient(
context: &Context,
x0: f64,
y0: f64,
r0: f64,
x1: f64,
y1: f64,
r1: f64,
mut color_line: ColorLine,
) -> anyhow::Result<()> {
let (min_stop, max_stop) = normalize_color_line(&mut color_line);
let xx0 = x0 + min_stop * (x1 - x0);
let yy0 = y0 + min_stop * (y1 - y0);
let xx1 = x0 + max_stop * (x1 - x0);
let yy1 = y0 + max_stop * (y1 - y0);
let rr0 = r0 + min_stop * (r1 - r0);
let rr1 = r0 + max_stop * (r1 - r0);
let pattern = RadialGradient::new(xx0, yy0, rr0, xx1, yy1, rr1);
pattern.set_extend(hb_extend_to_cairo(color_line.extend));
for stop in &color_line.color_stops {
let (r, g, b, a) = hb_color_to_rgba(stop.color);
pattern.add_color_stop_rgba(stop.offset.into(), r, g, b, a);
}
context.set_source(pattern)?;
context.paint()?;
Ok(())
}
fn hb_extend_to_cairo(extend: hb_paint_extend_t) -> Extend {
match extend {
hb_paint_extend_t::HB_PAINT_EXTEND_PAD => Extend::Pad,
hb_paint_extend_t::HB_PAINT_EXTEND_REPEAT => Extend::Repeat,
hb_paint_extend_t::HB_PAINT_EXTEND_REFLECT => Extend::Reflect,
}
}