diff --git a/Cargo.lock b/Cargo.lock index 28c9d3ace..661dedb71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,6 +120,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.5.2" @@ -3654,6 +3660,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -4301,6 +4316,20 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "tiny-skia" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf81f2900d2e235220e6f31ec9f63ade6a7f59090c556d74fe949bb3b15e9fe" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if 1.0.0", + "png", + "safe_arch", +] + [[package]] name = "tinyvec" version = "1.2.0" @@ -4915,6 +4944,7 @@ dependencies = [ "termwiz", "textwrap 0.14.0", "thiserror", + "tiny-skia", "umask", "unicode-normalization", "unicode-segmentation", @@ -4931,7 +4961,6 @@ dependencies = [ "winapi 0.3.9", "window", "windows", - "zeno", ] [[package]] @@ -5421,12 +5450,6 @@ dependencies = [ "syn", ] -[[package]] -name = "zeno" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea708573d4a67f939793b0d3f923d105bfdf06bbd6acb3f9cd02ce1bd905f6c8" - [[package]] name = "zstd" version = "0.6.1+zstd.1.4.9" diff --git a/wezterm-gui/Cargo.toml b/wezterm-gui/Cargo.toml index 46ff44df1..429a7bcb1 100644 --- a/wezterm-gui/Cargo.toml +++ b/wezterm-gui/Cargo.toml @@ -57,6 +57,7 @@ terminfo = "0.7" termwiz = { path = "../termwiz" } textwrap = "0.14" thiserror = "1.0" +tiny-skia = "0.5" umask = { path = "../umask" } unicode-normalization = "0.1" unicode-segmentation = "1.7" @@ -71,7 +72,6 @@ wezterm-ssh = { path = "../wezterm-ssh" } wezterm-term = { path = "../term", features=["use_serde"] } wezterm-toast-notification = { path = "../wezterm-toast-notification" } window = { path = "../window" } -zeno = "0.2" [target."cfg(windows)".dependencies] shared_library = "0.1" diff --git a/wezterm-gui/src/glyphcache.rs b/wezterm-gui/src/glyphcache.rs index 5503d4bf5..afde3775c 100644 --- a/wezterm-gui/src/glyphcache.rs +++ b/wezterm-gui/src/glyphcache.rs @@ -18,10 +18,10 @@ use std::rc::Rc; use std::sync::Arc; use std::time::{Duration, Instant}; use termwiz::image::ImageData; +use tiny_skia::{FillRule, Paint, Path, PathBuilder, PixmapMut, Stroke, Transform}; use wezterm_font::units::*; use wezterm_font::{FontConfiguration, GlyphInfo}; use wezterm_term::Underline; -use zeno::{Command, Fill, Format, Join, Mask, PathBuilder, Stroke, Style, Vector}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct GlyphKey { @@ -262,27 +262,26 @@ pub enum PolyCommand { } impl PolyCommand { - fn to_zeno( - &self, - width: usize, - height: usize, - underline_height: f32, - sink: &mut impl PathBuilder, - ) { - let coord = |x: &BlockCoord, y: &BlockCoord| { - Vector::new( + fn to_skia(&self, width: usize, height: usize, underline_height: f32, pb: &mut PathBuilder) { + match self { + Self::MoveTo(x, y) => pb.move_to( x.to_pixel(width, underline_height), y.to_pixel(height, underline_height), - ) - }; - - let point = |(x, y): &BlockPoint| coord(x, y); - - match self { - Self::MoveTo(x, y) => sink.move_to(coord(x, y)), - Self::LineTo(x, y) => sink.line_to(coord(x, y)), - Self::QuadTo { control, to } => sink.quad_to(point(control), point(to)), - Self::Close => sink.close(), + ), + Self::LineTo(x, y) => pb.line_to( + x.to_pixel(width, underline_height), + y.to_pixel(height, underline_height), + ), + Self::QuadTo { + control: (x1, y1), + to: (x, y), + } => pb.quad_to( + x1.to_pixel(width, underline_height), + y1.to_pixel(height, underline_height), + x.to_pixel(width, underline_height), + y.to_pixel(height, underline_height), + ), + Self::Close => pb.close(), }; } } @@ -297,11 +296,20 @@ pub enum PolyStyle { } impl PolyStyle { - fn to_zeno(self, width: f32) -> Style<'static> { + fn apply(self, width: f32, paint: &Paint, path: &Path, pixmap: &mut PixmapMut) { match self { - Self::Fill => Style::default(), - Self::Outline => Style::Stroke(*Stroke::new(width).join(Join::Miter)), - Self::OutlineHeavy => Style::Stroke(*Stroke::new(2. * width).join(Join::Miter)), + PolyStyle::Fill => { + pixmap.fill_path(path, paint, FillRule::Winding, Transform::identity(), None); + } + + PolyStyle::Outline | PolyStyle::OutlineHeavy => { + let mut stroke = Stroke::default(); + stroke.width = width; + if self == PolyStyle::OutlineHeavy { + stroke.width *= 2.0; + } + pixmap.stroke_path(path, paint, &stroke, Transform::identity(), None); + } } } } @@ -2611,23 +2619,28 @@ impl GlyphCache { // Fill a rectangular region described by the x and y ranges let fill_rect = |buffer: &mut Image, x: Range, y: Range| { let (width, height) = buffer.image_dimensions(); + let mut pixmap = + PixmapMut::from_bytes(buffer.pixel_data_slice_mut(), width as u32, height as u32) + .expect("make pixmap from existing bitmap"); + let x = x.start as f32..x.end as f32; let y = y.start as f32..y.end as f32; - let mut path: Vec = vec![]; - path.add_rect([x.start, y.start], x.end - x.start, y.end - y.start); - let (alpha, _placement) = Mask::new(&path) - .format(Format::Alpha) - .size(width as u32, height as u32) - .style(Fill::NonZero) - .render(); + let path = PathBuilder::from_rect( + tiny_skia::Rect::from_xywh(x.start, y.start, x.end - x.start, y.end - y.start) + .expect("valid rect"), + ); - for (alpha, dest) in alpha.into_iter().zip(buffer.pixels_mut()) { - let alpha = alpha as u32; - // If existing pixel was blank, we want to replace it. - // If alpha is blank then we don't want to replace existing non-blank. - *dest |= alpha << 24 | alpha << 16 | alpha << 8 | alpha; - } + let mut paint = Paint::default(); + paint.set_color(tiny_skia::Color::WHITE); + + pixmap.fill_path( + &path, + &paint, + FillRule::Winding, + Transform::identity(), + None, + ); }; match block { @@ -2716,35 +2729,34 @@ impl GlyphCache { } BlockKey::Poly(polys) => { let (width, height) = buffer.image_dimensions(); + let mut pixmap = PixmapMut::from_bytes( + buffer.pixel_data_slice_mut(), + width as u32, + height as u32, + ) + .expect("make pixmap from existing bitmap"); + for Poly { path, intensity, style, } in polys { - let intensity = intensity.to_scale(); - let mut cmd = vec![]; + let intensity = (intensity.to_scale() * 255.) as u8; + let mut paint = Paint::default(); + paint.set_color_rgba8(intensity, intensity, intensity, intensity); + paint.anti_alias = false; // explicitly do not want AA for small sizes + let mut pb = PathBuilder::new(); for item in path.iter() { - item.to_zeno( - width, - height, - self.metrics.underline_height as f32, - &mut cmd, - ); - } - - let (alpha, _placement) = Mask::new(&cmd) - .format(Format::Alpha) - .style(style.to_zeno(self.metrics.underline_height as f32)) - .size(width as u32, height as u32) - .render(); - - for (alpha, dest) in alpha.into_iter().zip(buffer.pixels_mut()) { - let alpha = (intensity * (alpha as f32)) as u32; - // If existing pixel was blank, we want to replace it. - // If alpha is blank then we don't want to replace existing non-blank. - *dest |= alpha << 24 | alpha << 16 | alpha << 8 | alpha; + item.to_skia(width, height, self.metrics.underline_height as f32, &mut pb); } + let path = pb.finish().expect("poly path to be valid"); + style.apply( + self.metrics.underline_height as f32, + &paint, + &path, + &mut pixmap, + ); } } } diff --git a/window/src/bitmaps/mod.rs b/window/src/bitmaps/mod.rs index 3743f8af8..66e054e99 100644 --- a/window/src/bitmaps/mod.rs +++ b/window/src/bitmaps/mod.rs @@ -95,6 +95,14 @@ pub trait BitmapImage { /// Return the pair (width, height) of the image, measured in pixels fn image_dimensions(&self) -> (usize, usize); + fn pixel_data_slice_mut(&mut self) -> &mut [u8] { + let (width, height) = self.image_dimensions(); + unsafe { + let first = self.pixel_data_mut(); + std::slice::from_raw_parts_mut(first, width * height * 4) + } + } + #[inline] fn pixels(&self) -> &[u32] { let (width, height) = self.image_dimensions();