use std::collections::BTreeMap;
use crate::{
image::ImageStore, paint::MultiStopGradient, Color, ErrorKind, ImageFlags, ImageId, ImageInfo, ImageSource,
Renderer,
};
pub(crate) struct GradientStore {
this_frame: BTreeMap<MultiStopGradient, ImageId>,
prev_frame: BTreeMap<MultiStopGradient, ImageId>,
}
impl GradientStore {
pub fn new() -> GradientStore {
GradientStore {
this_frame: BTreeMap::new(),
prev_frame: BTreeMap::new(),
}
}
pub fn lookup_or_add<R: Renderer>(
&mut self,
colors: MultiStopGradient,
images: &mut ImageStore<R::Image>,
renderer: &mut R,
) -> Result<ImageId, ErrorKind> {
if let Some(gradient_image_id) = self.prev_frame.remove(&colors) {
self.this_frame.insert(colors, gradient_image_id);
Ok(gradient_image_id)
} else if let Some(gradient_image_id) = self.this_frame.get(&colors) {
Ok(*gradient_image_id)
} else {
let info = ImageInfo::new(ImageFlags::REPEAT_Y, 256, 1, crate::PixelFormat::Rgba8);
let gradient_image_id = images.alloc(renderer, info)?;
let image = linear_gradient_stops(&colors);
images.update(renderer, gradient_image_id, ImageSource::Rgba(image.as_ref()), 0, 0)?;
self.this_frame.insert(colors, gradient_image_id);
Ok(gradient_image_id)
}
}
pub fn release_old_gradients<R: Renderer>(&mut self, images: &mut ImageStore<R::Image>, renderer: &mut R) {
let mut prev_textures = BTreeMap::new();
std::mem::swap(&mut prev_textures, &mut self.prev_frame);
for (_, gradient_image_id) in prev_textures {
images.remove(renderer, gradient_image_id);
}
std::mem::swap(&mut self.this_frame, &mut self.prev_frame);
}
}
#[allow(clippy::many_single_char_names)]
fn gradient_span(dest: &mut [rgb::RGBA8; 256], color0: Color, color1: Color, offset0: f32, offset1: f32) {
let s0o = offset0.max(0.0).min(1.0);
let s1o = offset1.max(0.0).min(1.0);
if s1o < s0o {
return;
}
let s = (s0o * 256.0) as usize;
let e = (s1o * 256.0) as usize;
let mut r = color0.r;
let mut g = color0.g;
let mut b = color0.b;
let mut a = color0.a;
let steps = (e - s) as f32;
let dr = (color1.r - r) / steps;
let dg = (color1.g - g) / steps;
let db = (color1.b - b) / steps;
let da = (color1.a - a) / steps;
#[allow(clippy::needless_range_loop)]
for i in s..e {
dest[i] = rgb::RGBA8::new(
(r * a * 255.0) as u8,
(g * a * 255.0) as u8,
(b * a * 255.0) as u8,
(a * 255.0) as u8,
);
r += dr;
g += dg;
b += db;
a += da;
}
}
fn linear_gradient_stops(gradient: &MultiStopGradient) -> imgref::Img<Vec<rgb::RGBA8>> {
let mut dest = [rgb::RGBA8::new(0, 0, 0, 0); 256];
if gradient[0].0 > 0.0 {
let s0 = gradient[0].0;
let color0 = gradient[0].1;
gradient_span(&mut dest, color0, color0, 0.0, s0);
}
for stop in gradient.windows(2) {
let s0 = stop[0].0;
let s1 = stop[1].0;
let color0 = stop[0].1;
let color1 = stop[1].1;
if s0 < 1.0 && s1 > 1.0 {
gradient_span(&mut dest, color0, color0, s0, 1.0);
} else {
gradient_span(&mut dest, color0, color1, s0, s1);
}
if s0 > 1.0 {
break;
};
}
imgref::Img::new(dest.to_vec(), 256, 1)
}