mirror of
https://github.com/wez/wezterm.git
synced 2024-12-28 07:55:03 +03:00
background: cache computed gradient images
Avoid recomputing on each config change/reload.
This commit is contained in:
parent
167127c14b
commit
2b083cc131
@ -290,7 +290,7 @@ impl Default for BackgroundOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
|
||||
pub enum Interpolation {
|
||||
Linear,
|
||||
Basis,
|
||||
@ -303,7 +303,7 @@ impl Default for Interpolation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
|
||||
pub enum BlendMode {
|
||||
Rgb,
|
||||
LinearRgb,
|
||||
@ -317,7 +317,7 @@ impl Default for BlendMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
|
||||
pub enum GradientOrientation {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
@ -337,7 +337,7 @@ impl Default for GradientOrientation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic)]
|
||||
#[derive(Debug, Copy, Clone, FromDynamic, ToDynamic, PartialEq)]
|
||||
pub enum GradientPreset {
|
||||
Blues,
|
||||
BrBg,
|
||||
@ -424,7 +424,7 @@ impl GradientPreset {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic)]
|
||||
#[derive(Debug, Clone, FromDynamic, ToDynamic, PartialEq)]
|
||||
pub struct Gradient {
|
||||
#[dynamic(default)]
|
||||
pub orientation: GradientOrientation,
|
||||
|
@ -5,7 +5,7 @@ use crate::Dimensions;
|
||||
use anyhow::Context;
|
||||
use config::{
|
||||
BackgroundHorizontalAlignment, BackgroundLayer, BackgroundRepeat, BackgroundSize,
|
||||
BackgroundSource, BackgroundVerticalAlignment, ConfigHandle, DimensionContext,
|
||||
BackgroundSource, BackgroundVerticalAlignment, ConfigHandle, DimensionContext, Gradient,
|
||||
GradientOrientation,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
@ -16,6 +16,158 @@ use wezterm_term::StableRowIndex;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref IMAGE_CACHE: Mutex<HashMap<String, CachedImage>> = Mutex::new(HashMap::new());
|
||||
static ref GRADIENT_CACHE: Mutex<Vec<CachedGradient>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
struct CachedGradient {
|
||||
g: Gradient,
|
||||
width: u32,
|
||||
height: u32,
|
||||
image: Arc<ImageData>,
|
||||
marked: bool,
|
||||
}
|
||||
|
||||
impl CachedGradient {
|
||||
fn compute(g: &Gradient, width: u32, height: u32) -> anyhow::Result<Arc<ImageData>> {
|
||||
let grad = g
|
||||
.build()
|
||||
.with_context(|| format!("building gradient {:?}", g))?;
|
||||
|
||||
let mut imgbuf = image::RgbaImage::new(width, height);
|
||||
let fw = width as f64;
|
||||
let fh = height as f64;
|
||||
|
||||
fn to_pixel(c: colorgrad::Color) -> image::Rgba<u8> {
|
||||
let (r, g, b, a) = c.rgba_u8();
|
||||
image::Rgba([r, g, b, a])
|
||||
}
|
||||
|
||||
// Map t which is in range [a, b] to range [c, d]
|
||||
fn remap(t: f64, a: f64, b: f64, c: f64, d: f64) -> f64 {
|
||||
(t - a) * ((d - c) / (b - a)) + c
|
||||
}
|
||||
|
||||
let (dmin, dmax) = grad.domain();
|
||||
|
||||
let rng = fastrand::Rng::new();
|
||||
|
||||
// We add some randomness to the position that we use to
|
||||
// index into the color gradient, so that we can avoid
|
||||
// visible color banding. The default 64 was selected
|
||||
// because it it was the smallest value on my mac where
|
||||
// the banding wasn't obvious.
|
||||
let noise_amount = g.noise.unwrap_or_else(|| {
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
16
|
||||
} else {
|
||||
64
|
||||
}
|
||||
});
|
||||
|
||||
fn noise(rng: &fastrand::Rng, noise_amount: usize) -> f64 {
|
||||
if noise_amount == 0 {
|
||||
0.
|
||||
} else {
|
||||
rng.usize(0..noise_amount) as f64 * -1.
|
||||
}
|
||||
}
|
||||
|
||||
match g.orientation {
|
||||
GradientOrientation::Horizontal => {
|
||||
for (x, _, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
x as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fw,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Vertical => {
|
||||
for (_, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
y as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fh,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Linear { angle } => {
|
||||
let angle = angle.unwrap_or(0.0).to_radians();
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let (x, y) = (x as f64, y as f64);
|
||||
let (x, y) = (x - fw / 2., y - fh / 2.);
|
||||
let t = x * f64::cos(angle) - y * f64::sin(angle);
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
t + noise(&rng, noise_amount),
|
||||
-fw / 2.,
|
||||
fw / 2.,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Radial { radius, cx, cy } => {
|
||||
let radius = fw * radius.unwrap_or(0.5);
|
||||
let cx = fw * cx.unwrap_or(0.5);
|
||||
let cy = fh * cy.unwrap_or(0.5);
|
||||
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let nx = noise(&rng, noise_amount);
|
||||
let ny = noise(&rng, noise_amount);
|
||||
|
||||
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2)).sqrt()
|
||||
/ radius;
|
||||
*pixel = to_pixel(grad.at(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = imgbuf.into_vec();
|
||||
let image = Arc::new(ImageData::with_data(ImageDataType::new_single_frame(
|
||||
width, height, data,
|
||||
)));
|
||||
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn load(g: &Gradient, width: u32, height: u32) -> anyhow::Result<Arc<ImageData>> {
|
||||
let mut cache = GRADIENT_CACHE.lock().unwrap();
|
||||
|
||||
if let Some(entry) = cache
|
||||
.iter_mut()
|
||||
.find(|entry| entry.g == *g && entry.width == width && entry.height == height)
|
||||
{
|
||||
entry.marked = false;
|
||||
return Ok(Arc::clone(&entry.image));
|
||||
}
|
||||
|
||||
let image = Self::compute(g, width, height)?;
|
||||
|
||||
cache.push(Self {
|
||||
g: g.clone(),
|
||||
width,
|
||||
height,
|
||||
image: Arc::clone(&image),
|
||||
marked: false,
|
||||
});
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn mark() {
|
||||
let mut cache = GRADIENT_CACHE.lock().unwrap();
|
||||
for entry in cache.iter_mut() {
|
||||
entry.marked = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn sweep() {
|
||||
let mut cache = GRADIENT_CACHE.lock().unwrap();
|
||||
cache.retain(|entry| !entry.marked);
|
||||
}
|
||||
}
|
||||
|
||||
struct CachedImage {
|
||||
@ -99,10 +251,6 @@ fn load_background_layer(
|
||||
|
||||
let data = match &layer.source {
|
||||
BackgroundSource::Gradient(g) => {
|
||||
let grad = g
|
||||
.build()
|
||||
.with_context(|| format!("building gradient {:?}", g))?;
|
||||
|
||||
let mut width = match layer.width {
|
||||
BackgroundSize::Dimension(d) => d.evaluate_as_pixels(h_context),
|
||||
unsup => anyhow::bail!("{:?} not yet implemented", unsup),
|
||||
@ -120,104 +268,7 @@ fn load_background_layer(
|
||||
height = height.min(width);
|
||||
}
|
||||
|
||||
let mut imgbuf = image::RgbaImage::new(width, height);
|
||||
let fw = width as f64;
|
||||
let fh = height as f64;
|
||||
|
||||
fn to_pixel(c: colorgrad::Color) -> image::Rgba<u8> {
|
||||
let (r, g, b, a) = c.rgba_u8();
|
||||
image::Rgba([r, g, b, a])
|
||||
}
|
||||
|
||||
// Map t which is in range [a, b] to range [c, d]
|
||||
fn remap(t: f64, a: f64, b: f64, c: f64, d: f64) -> f64 {
|
||||
(t - a) * ((d - c) / (b - a)) + c
|
||||
}
|
||||
|
||||
let (dmin, dmax) = grad.domain();
|
||||
|
||||
let rng = fastrand::Rng::new();
|
||||
|
||||
// We add some randomness to the position that we use to
|
||||
// index into the color gradient, so that we can avoid
|
||||
// visible color banding. The default 64 was selected
|
||||
// because it it was the smallest value on my mac where
|
||||
// the banding wasn't obvious.
|
||||
let noise_amount = g.noise.unwrap_or_else(|| {
|
||||
if matches!(g.orientation, GradientOrientation::Radial { .. }) {
|
||||
16
|
||||
} else {
|
||||
64
|
||||
}
|
||||
});
|
||||
|
||||
fn noise(rng: &fastrand::Rng, noise_amount: usize) -> f64 {
|
||||
if noise_amount == 0 {
|
||||
0.
|
||||
} else {
|
||||
rng.usize(0..noise_amount) as f64 * -1.
|
||||
}
|
||||
}
|
||||
|
||||
match g.orientation {
|
||||
GradientOrientation::Horizontal => {
|
||||
for (x, _, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
x as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fw,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Vertical => {
|
||||
for (_, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
y as f64 + noise(&rng, noise_amount),
|
||||
0.0,
|
||||
fh,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Linear { angle } => {
|
||||
let angle = angle.unwrap_or(0.0).to_radians();
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let (x, y) = (x as f64, y as f64);
|
||||
let (x, y) = (x - fw / 2., y - fh / 2.);
|
||||
let t = x * f64::cos(angle) - y * f64::sin(angle);
|
||||
*pixel = to_pixel(grad.at(remap(
|
||||
t + noise(&rng, noise_amount),
|
||||
-fw / 2.,
|
||||
fw / 2.,
|
||||
dmin,
|
||||
dmax,
|
||||
)));
|
||||
}
|
||||
}
|
||||
GradientOrientation::Radial { radius, cx, cy } => {
|
||||
let radius = fw * radius.unwrap_or(0.5);
|
||||
let cx = fw * cx.unwrap_or(0.5);
|
||||
let cy = fh * cy.unwrap_or(0.5);
|
||||
|
||||
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
|
||||
let nx = noise(&rng, noise_amount);
|
||||
let ny = noise(&rng, noise_amount);
|
||||
|
||||
let t = (nx + (x as f64 - cx).powi(2) + (ny + y as f64 - cy).powi(2))
|
||||
.sqrt()
|
||||
/ radius;
|
||||
*pixel = to_pixel(grad.at(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = imgbuf.into_vec();
|
||||
Arc::new(ImageData::with_data(ImageDataType::new_single_frame(
|
||||
width, height, data,
|
||||
)))
|
||||
CachedGradient::load(g, width, height)?
|
||||
}
|
||||
BackgroundSource::Color(color) => {
|
||||
// In theory we could just make a 1x1 texture and allow
|
||||
@ -295,6 +346,7 @@ pub fn reload_background_image(
|
||||
.collect();
|
||||
|
||||
CachedImage::mark();
|
||||
CachedGradient::mark();
|
||||
|
||||
let result = load_background_image(config, dimensions, render_metrics)
|
||||
.into_iter()
|
||||
@ -310,6 +362,7 @@ pub fn reload_background_image(
|
||||
.collect();
|
||||
|
||||
CachedImage::sweep();
|
||||
CachedGradient::sweep();
|
||||
|
||||
result
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user