| // Copyright 2025 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| //! Rendering linear gradients. |
| |
| use crate::fine::{COLOR_COMPONENTS, FineType, Painter, TILE_HEIGHT_COMPONENTS}; |
| use vello_common::encode::{EncodedGradient, GradientLike, GradientRange}; |
| use vello_common::kurbo::Point; |
| |
| #[derive(Debug)] |
| pub(crate) struct GradientFiller<'a, T: GradientLike> { |
| /// The current position that should be processed. |
| cur_pos: Point, |
| /// The index of the current range. |
| range_idx: usize, |
| /// The underlying gradient. |
| gradient: &'a EncodedGradient, |
| /// The underlying gradient kind. |
| kind: &'a T, |
| /// The current gradient range (pointed to by `range_idx`). |
| cur_range: &'a GradientRange, |
| } |
| |
| impl<'a, T: GradientLike> GradientFiller<'a, T> { |
| pub(crate) fn new( |
| gradient: &'a EncodedGradient, |
| kind: &'a T, |
| start_x: u16, |
| start_y: u16, |
| ) -> Self { |
| Self { |
| cur_pos: gradient.transform * Point::new(f64::from(start_x), f64::from(start_y)), |
| range_idx: 0, |
| cur_range: &gradient.ranges[0], |
| gradient, |
| kind, |
| } |
| } |
| |
| fn advance(&mut self, target_pos: f32) { |
| while target_pos > self.cur_range.x1 || target_pos < self.cur_range.x0 { |
| if self.range_idx == 0 { |
| self.range_idx = self.gradient.ranges.len() - 1; |
| } else { |
| self.range_idx -= 1; |
| } |
| |
| self.cur_range = &self.gradient.ranges[self.range_idx]; |
| } |
| } |
| |
| pub(super) fn run<F: FineType>(mut self, target: &mut [F]) { |
| let original_pos = self.cur_pos; |
| |
| target |
| .chunks_exact_mut(TILE_HEIGHT_COMPONENTS) |
| .for_each(|column| { |
| self.run_column(column); |
| self.cur_pos += self.gradient.x_advance; |
| }); |
| |
| // Radial gradients can have positions that are undefined and thus shouldn't be |
| // painted at all. Checking for this inside of the main filling logic would be |
| // an unnecessary overhead for the general case, while this is really just an edge |
| // case. Because of this, in the first run we will fill it using a dummy color, and |
| // in case the gradient might have undefined locations, we do another run over |
| // the buffer and override the positions with a transparent fill. This way, we need |
| // 2x as long to handle such gradients, but for the common case we don't incur any |
| // additional overhead. |
| if self.kind.has_undefined() { |
| self.cur_pos = original_pos; |
| self.run_undefined(target); |
| } |
| } |
| |
| fn run_column<F: FineType>(&mut self, col: &mut [F]) { |
| let pad = self.gradient.pad; |
| let extend = |val| extend(val, pad, self.gradient.clamp_range); |
| let mut pos = self.cur_pos; |
| |
| for pixel in col.chunks_exact_mut(COLOR_COMPONENTS) { |
| let dist = extend(self.kind.cur_pos(pos)); |
| self.advance(dist); |
| let range = self.cur_range; |
| let c0 = range.c0.as_premul_f32().components; |
| |
| for (comp_idx, comp) in pixel.iter_mut().enumerate() { |
| let factor = range.factors_f32[comp_idx] * (dist - range.x0); |
| |
| *comp = F::from_normalized_f32(c0[comp_idx] + factor); |
| } |
| |
| pos += self.gradient.y_advance; |
| } |
| } |
| |
| fn run_undefined<F: FineType>(mut self, target: &mut [F]) { |
| target |
| .chunks_exact_mut(TILE_HEIGHT_COMPONENTS) |
| .for_each(|column| { |
| let mut pos = self.cur_pos; |
| |
| for pixel in column.chunks_exact_mut(COLOR_COMPONENTS) { |
| let actual_pos = pos; |
| |
| if !self.kind.is_defined(actual_pos) { |
| pixel.copy_from_slice(&[F::ZERO, F::ZERO, F::ZERO, F::ZERO]); |
| } |
| |
| pos += self.gradient.y_advance; |
| } |
| |
| self.cur_pos += self.gradient.x_advance; |
| }); |
| } |
| } |
| |
| impl<T: GradientLike> Painter for GradientFiller<'_, T> { |
| fn paint<F: FineType>(self, target: &mut [F]) { |
| self.run(target); |
| } |
| } |
| |
| pub(crate) fn extend(mut val: f32, pad: bool, clamp_range: (f32, f32)) -> f32 { |
| let start = clamp_range.0; |
| let end = clamp_range.1; |
| |
| if pad { |
| val |
| } else { |
| // Avoid using modulo here because it's slower. |
| |
| while val < start { |
| val += end - start; |
| } |
| |
| while val > end { |
| val -= end - start; |
| } |
| |
| val |
| } |
| } |