| // Copyright 2025 the Vello Authors |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| //! Fine rasterization runs the commands in each wide tile to determine the final RGBA value |
| //! of each pixel and pack it into the pixmap. |
| |
| use crate::util::ColorExt; |
| use vello_common::{ |
| coarse::{Cmd, WideTile}, |
| paint::Paint, |
| tile::Tile, |
| }; |
| |
| pub(crate) const COLOR_COMPONENTS: usize = 4; |
| pub(crate) const TILE_HEIGHT_COMPONENTS: usize = Tile::HEIGHT as usize * COLOR_COMPONENTS; |
| pub(crate) const SCRATCH_BUF_SIZE: usize = |
| WideTile::WIDTH as usize * Tile::HEIGHT as usize * COLOR_COMPONENTS; |
| |
| pub(crate) type ScratchBuf = [u8; SCRATCH_BUF_SIZE]; |
| |
| pub(crate) struct Fine<'a> { |
| pub(crate) width: u16, |
| pub(crate) height: u16, |
| pub(crate) out_buf: &'a mut [u8], |
| pub(crate) scratch: ScratchBuf, |
| } |
| |
| impl<'a> Fine<'a> { |
| pub(crate) fn new(width: u16, height: u16, out_buf: &'a mut [u8]) -> Self { |
| let scratch = [0; SCRATCH_BUF_SIZE]; |
| |
| Self { |
| width, |
| height, |
| out_buf, |
| scratch, |
| } |
| } |
| |
| pub(crate) fn clear(&mut self, premul_color: [u8; 4]) { |
| if premul_color[0] == premul_color[1] |
| && premul_color[1] == premul_color[2] |
| && premul_color[2] == premul_color[3] |
| { |
| // All components are the same, so we can use memset instead. |
| self.scratch.fill(premul_color[0]); |
| } else { |
| for z in self.scratch.chunks_exact_mut(COLOR_COMPONENTS) { |
| z.copy_from_slice(&premul_color); |
| } |
| } |
| } |
| |
| pub(crate) fn pack(&mut self, x: u16, y: u16) { |
| pack( |
| self.out_buf, |
| &self.scratch, |
| self.width.into(), |
| self.height.into(), |
| x.into(), |
| y.into(), |
| ); |
| } |
| |
| pub(crate) fn run_cmd(&mut self, cmd: &Cmd, alphas: &[u8]) { |
| match cmd { |
| Cmd::Fill(f) => { |
| self.fill(f.x as usize, f.width as usize, &f.paint); |
| } |
| Cmd::AlphaFill(s) => { |
| let a_slice = &alphas[s.alpha_ix..]; |
| self.strip(s.x as usize, s.width as usize, a_slice, &s.paint); |
| } |
| } |
| } |
| |
| pub(crate) fn fill(&mut self, x: usize, width: usize, paint: &Paint) { |
| match paint { |
| Paint::Solid(c) => { |
| let color = c.premultiply().to_rgba8_fast(); |
| |
| let target = &mut self.scratch[x * TILE_HEIGHT_COMPONENTS..] |
| [..TILE_HEIGHT_COMPONENTS * width]; |
| |
| // If color is completely opaque we can just memcopy the colors. |
| if color[3] == 255 { |
| for t in target.chunks_exact_mut(COLOR_COMPONENTS) { |
| t.copy_from_slice(&color); |
| } |
| |
| return; |
| } |
| |
| fill::src_over(target, &color); |
| } |
| _ => unimplemented!(), |
| } |
| } |
| |
| pub(crate) fn strip(&mut self, x: usize, width: usize, alphas: &[u8], paint: &Paint) { |
| debug_assert!( |
| alphas.len() >= width, |
| "alpha buffer doesn't contain sufficient elements" |
| ); |
| |
| match paint { |
| Paint::Solid(s) => { |
| let color = s.premultiply().to_rgba8_fast(); |
| |
| let target = &mut self.scratch[x * TILE_HEIGHT_COMPONENTS..] |
| [..TILE_HEIGHT_COMPONENTS * width]; |
| |
| strip::src_over(target, &color, alphas); |
| } |
| _ => unimplemented!(), |
| } |
| } |
| } |
| |
| fn pack(out_buf: &mut [u8], scratch: &ScratchBuf, width: usize, height: usize, x: usize, y: usize) { |
| let base_ix = (y * usize::from(Tile::HEIGHT) * width + x * usize::from(WideTile::WIDTH)) |
| * COLOR_COMPONENTS; |
| |
| // Make sure we don't process rows outside the range of the pixmap. |
| let max_height = (height - y * usize::from(Tile::HEIGHT)).min(usize::from(Tile::HEIGHT)); |
| |
| for j in 0..max_height { |
| let line_ix = base_ix + j * width * COLOR_COMPONENTS; |
| |
| // Make sure we don't process columns outside the range of the pixmap. |
| let max_width = |
| (width - x * usize::from(WideTile::WIDTH)).min(usize::from(WideTile::WIDTH)); |
| let target_len = max_width * COLOR_COMPONENTS; |
| // This helps the compiler to understand that any access to `dest` cannot |
| // be out of bounds, and thus saves corresponding checks in the for loop. |
| let dest = &mut out_buf[line_ix..][..target_len]; |
| |
| for i in 0..max_width { |
| let src = &scratch[(i * usize::from(Tile::HEIGHT) + j) * COLOR_COMPONENTS..] |
| [..COLOR_COMPONENTS]; |
| dest[i * COLOR_COMPONENTS..][..COLOR_COMPONENTS] |
| .copy_from_slice(&src[..COLOR_COMPONENTS]); |
| } |
| } |
| } |
| |
| pub(crate) mod fill { |
| // See https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators for the |
| // formulas. |
| |
| use crate::fine::{COLOR_COMPONENTS, TILE_HEIGHT_COMPONENTS}; |
| use crate::util::scalar::div_255; |
| |
| pub(crate) fn src_over(target: &mut [u8], src_c: &[u8; COLOR_COMPONENTS]) { |
| let src_a = src_c[3] as u16; |
| |
| for strip in target.chunks_exact_mut(TILE_HEIGHT_COMPONENTS) { |
| for bg_c in strip.chunks_exact_mut(COLOR_COMPONENTS) { |
| for i in 0..COLOR_COMPONENTS { |
| bg_c[i] = src_c[i] + div_255(bg_c[i] as u16 * (255 - src_a)) as u8; |
| } |
| } |
| } |
| } |
| } |
| |
| pub(crate) mod strip { |
| use crate::fine::{COLOR_COMPONENTS, TILE_HEIGHT_COMPONENTS}; |
| use crate::util::scalar::div_255; |
| use vello_common::tile::Tile; |
| |
| pub(crate) fn src_over(target: &mut [u8], src_c: &[u8; COLOR_COMPONENTS], alphas: &[u8]) { |
| let src_a = src_c[3] as u16; |
| |
| for (bg_c, masks) in target |
| .chunks_exact_mut(TILE_HEIGHT_COMPONENTS) |
| .zip(alphas.chunks_exact(usize::from(Tile::HEIGHT))) |
| { |
| for j in 0..usize::from(Tile::HEIGHT) { |
| let mask_a = u16::from(masks[j]); |
| let inv_src_a_mask_a = 255 - div_255(mask_a * src_a); |
| |
| for i in 0..COLOR_COMPONENTS { |
| let im1 = bg_c[j * COLOR_COMPONENTS + i] as u16 * inv_src_a_mask_a; |
| let im2 = src_c[i] as u16 * mask_a; |
| let im3 = div_255(im1 + im2); |
| bg_c[j * COLOR_COMPONENTS + i] = im3 as u8; |
| } |
| } |
| } |
| } |
| } |