| 1 | use std::collections::BTreeMap; |
| 2 | |
| 3 | use crate::{ |
| 4 | image::ImageStore, |
| 5 | paint::{GradientStop, MultiStopGradient}, |
| 6 | Color, ErrorKind, ImageFlags, ImageId, ImageInfo, ImageSource, Renderer, |
| 7 | }; |
| 8 | |
| 9 | /// `GradientStore` holds image ids for multi-stop gradients. The actual image/textures |
| 10 | /// are contained by the Canvas's `ImageStore`. |
| 11 | // |
| 12 | // If many gradients are used in a frame, we could combine them into a single texture |
| 13 | // and update the texture immediately prior to giving the renderer the command list. |
| 14 | pub struct GradientStore { |
| 15 | this_frame: BTreeMap<MultiStopGradient, ImageId>, |
| 16 | prev_frame: BTreeMap<MultiStopGradient, ImageId>, |
| 17 | } |
| 18 | impl GradientStore { |
| 19 | /// Create a new empty gradient store |
| 20 | pub fn new() -> Self { |
| 21 | Self { |
| 22 | this_frame: BTreeMap::new(), |
| 23 | prev_frame: BTreeMap::new(), |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | /// Lookup or add a multi-stop gradient in this gradient store. |
| 28 | pub fn lookup_or_add<R: Renderer>( |
| 29 | &mut self, |
| 30 | colors: &MultiStopGradient, |
| 31 | images: &mut ImageStore<R::Image>, |
| 32 | renderer: &mut R, |
| 33 | ) -> Result<ImageId, ErrorKind> { |
| 34 | if let Some(gradient_image_id) = self.prev_frame.remove(colors) { |
| 35 | // See if we already have this texture from the previous frame. If we find |
| 36 | // it then we migrate it to the current frame so we don't release it and |
| 37 | // return the texture id to the caller. |
| 38 | self.this_frame.insert(colors.clone(), gradient_image_id); |
| 39 | Ok(gradient_image_id) |
| 40 | } else if let Some(gradient_image_id) = self.this_frame.get(colors) { |
| 41 | // See if we already used this gradient in this frame, and return the texture |
| 42 | // id if we do. |
| 43 | Ok(*gradient_image_id) |
| 44 | } else { |
| 45 | // We need to allocate a texture and synthesize the gradient image. |
| 46 | let info = ImageInfo::new(ImageFlags::REPEAT_Y, 256, 1, crate::PixelFormat::Rgba8); |
| 47 | let gradient_image_id = images.alloc(renderer, info)?; |
| 48 | let image = linear_gradient_stops(colors); |
| 49 | images.update(renderer, gradient_image_id, ImageSource::Rgba(image.as_ref()), 0, 0)?; |
| 50 | |
| 51 | self.this_frame.insert(colors.clone(), gradient_image_id); |
| 52 | Ok(gradient_image_id) |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | /// Release the textures that were not used in the most recently rendered frame. This |
| 57 | /// method should be called when all the commands have been submitted. |
| 58 | pub fn release_old_gradients<R: Renderer>(&mut self, images: &mut ImageStore<R::Image>, renderer: &mut R) { |
| 59 | let mut prev_textures = BTreeMap::new(); |
| 60 | std::mem::swap(&mut prev_textures, &mut self.prev_frame); |
| 61 | for (_, gradient_image_id) in prev_textures { |
| 62 | images.remove(renderer, gradient_image_id); |
| 63 | } |
| 64 | // Move the "this_frame" textures to "prev_frame". "prev_frame" is already empty. |
| 65 | std::mem::swap(&mut self.this_frame, &mut self.prev_frame); |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | #[allow (clippy::many_single_char_names)] |
| 70 | // Gradient filling, adapted from https://github.com/lieff/lvg/blob/master/render/common.c#L147 |
| 71 | fn gradient_span(dest: &mut [rgb::RGBA8; 256], color0: Color, color1: Color, offset0: f32, offset1: f32) { |
| 72 | let s0o = offset0.clamp(0.0, 1.0); |
| 73 | let s1o = offset1.clamp(0.0, 1.0); |
| 74 | |
| 75 | if s1o < s0o { |
| 76 | return; |
| 77 | } |
| 78 | |
| 79 | let s = (s0o * 256.0) as usize; |
| 80 | let e = (s1o * 256.0) as usize; |
| 81 | |
| 82 | let mut r = color0.r; |
| 83 | let mut g = color0.g; |
| 84 | let mut b = color0.b; |
| 85 | let mut a = color0.a; |
| 86 | |
| 87 | let steps = (e - s) as f32; |
| 88 | |
| 89 | let dr = (color1.r - r) / steps; |
| 90 | let dg = (color1.g - g) / steps; |
| 91 | let db = (color1.b - b) / steps; |
| 92 | let da = (color1.a - a) / steps; |
| 93 | |
| 94 | #[allow (clippy::needless_range_loop)] |
| 95 | for i in s..e { |
| 96 | // The output must be premultiplied, but we don't premultiply until this point |
| 97 | // so that we can do gradients from transparent colors correctly -- for example |
| 98 | // if we have a stop that is fully transparent red and it transitions to opaque |
| 99 | // blue, we should see some red in the gradient. If we premultiply the stops |
| 100 | // then we won't see any red, because we will have already multiplied it to zero. |
| 101 | // This way we'll get the red contribution. |
| 102 | dest[i] = rgb::RGBA8::new( |
| 103 | (r * a * 255.0) as u8, |
| 104 | (g * a * 255.0) as u8, |
| 105 | (b * a * 255.0) as u8, |
| 106 | (a * 255.0) as u8, |
| 107 | ); |
| 108 | r += dr; |
| 109 | g += dg; |
| 110 | b += db; |
| 111 | a += da; |
| 112 | } |
| 113 | } |
| 114 | fn linear_gradient_stops(gradient: &MultiStopGradient) -> imgref::Img<Vec<rgb::RGBA8>> { |
| 115 | let mut dest = [rgb::RGBA8::new(0, 0, 0, 0); 256]; |
| 116 | |
| 117 | // Fill the gradient up to the first stop. |
| 118 | let first_stop = gradient.get(0); |
| 119 | if first_stop.0 > 0.0 { |
| 120 | let s0 = first_stop.0; |
| 121 | let color0 = first_stop.1; |
| 122 | gradient_span(&mut dest, color0, color0, 0.0, s0); |
| 123 | } |
| 124 | |
| 125 | // Iterate over the stops in overlapping pairs and fill out the rest of the |
| 126 | // gradient. If the stop position is > 1.0 then we have exhausted the stops |
| 127 | // and should break. As a special case, if the second stop is > 1.0 then we |
| 128 | // fill the current color to the end of the gradient. |
| 129 | for [GradientStop(s0, color0), GradientStop(s1, color1)] in gradient.pairs() { |
| 130 | // Catch the case where the last stop doesn't go all the way to 1.0 and |
| 131 | // pad it. |
| 132 | if s0 < 1.0 && s1 > 1.0 { |
| 133 | gradient_span(&mut dest, color0, color0, s0, 1.0); |
| 134 | } else { |
| 135 | gradient_span(&mut dest, color0, color1, s0, s1); |
| 136 | } |
| 137 | |
| 138 | // If the first stop is >1.0 then we're done. |
| 139 | if s0 > 1.0 { |
| 140 | break; |
| 141 | }; |
| 142 | } |
| 143 | imgref::Img::new(dest.to_vec(), 256, 1) |
| 144 | } |
| 145 | |