blob: e0717cb9dc3c40d9112b1586a9082200a7b8afa4 [file]
// 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.
mod gradient;
mod image;
use crate::fine::gradient::GradientFiller;
use crate::fine::image::ImageFiller;
use crate::util::scalar::div_255;
use alloc::vec;
use alloc::vec::Vec;
use core::iter;
use vello_common::encode::{EncodedKind, EncodedPaint};
use vello_common::paint::Paint;
use vello_common::{
coarse::{Cmd, WideTile},
tile::Tile,
};
pub(crate) const COLOR_COMPONENTS: usize = 4;
pub(crate) const TILE_HEIGHT_COMPONENTS: usize = Tile::HEIGHT as usize * COLOR_COMPONENTS;
#[doc(hidden)]
pub const SCRATCH_BUF_SIZE: usize =
WideTile::WIDTH as usize * Tile::HEIGHT as usize * COLOR_COMPONENTS;
#[doc(hidden)]
pub type ScratchBuf = [u8; SCRATCH_BUF_SIZE];
#[derive(Debug)]
#[doc(hidden)]
/// This is an internal struct, do not access directly.
pub struct Fine {
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) wide_coords: (u16, u16),
pub(crate) blend_buf: Vec<ScratchBuf>,
pub(crate) color_buf: ScratchBuf,
}
impl Fine {
/// Create a new fine rasterizer.
pub fn new(width: u16, height: u16) -> Self {
let blend_buf = [0; SCRATCH_BUF_SIZE];
let color_buf = [0; SCRATCH_BUF_SIZE];
Self {
width,
height,
wide_coords: (0, 0),
blend_buf: vec![blend_buf],
color_buf,
}
}
/// Set the coordinates of the current wide tile that is being processed (in tile units).
pub fn set_coords(&mut self, x: u16, y: u16) {
self.wide_coords = (x, y);
}
pub fn clear(&mut self, premul_color: [u8; 4]) {
let blend_buf = self.blend_buf.last_mut().unwrap();
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.
blend_buf.fill(premul_color[0]);
} else {
for z in blend_buf.chunks_exact_mut(COLOR_COMPONENTS) {
z.copy_from_slice(&premul_color);
}
}
}
#[doc(hidden)]
pub fn pack(&mut self, out_buf: &mut [u8]) {
let blend_buf = self.blend_buf.last_mut().unwrap();
pack(
out_buf,
blend_buf,
self.width.into(),
self.height.into(),
self.wide_coords.0.into(),
self.wide_coords.1.into(),
);
}
pub(crate) fn run_cmd(&mut self, cmd: &Cmd, alphas: &[u8], paints: &[EncodedPaint]) {
match cmd {
Cmd::Fill(f) => {
self.fill(f.x as usize, f.width as usize, &f.paint, paints);
}
Cmd::AlphaFill(s) => {
let a_slice = &alphas[s.alpha_idx..];
self.strip(s.x as usize, s.width as usize, a_slice, &s.paint, paints);
}
Cmd::PushClip => {
self.blend_buf.push([0; SCRATCH_BUF_SIZE]);
}
Cmd::PopClip => {
self.blend_buf.pop();
}
Cmd::ClipFill(cf) => {
self.clip_fill(cf.x as usize, cf.width as usize);
}
Cmd::ClipStrip(cs) => {
let aslice = &alphas[cs.alpha_idx..];
self.clip_strip(cs.x as usize, cs.width as usize, aslice);
}
}
}
/// Fill at a given x and with a width using the given paint.
pub fn fill(&mut self, x: usize, width: usize, fill: &Paint, encoded_paints: &[EncodedPaint]) {
let blend_buf = &mut self.blend_buf.last_mut().unwrap()[x * TILE_HEIGHT_COMPONENTS..]
[..TILE_HEIGHT_COMPONENTS * width];
let color_buf =
&mut self.color_buf[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let start_x = self.wide_coords.0 * WideTile::WIDTH + x as u16;
let start_y = self.wide_coords.1 * Tile::HEIGHT;
fn fill_complex_paint(
color_buf: &mut [u8],
blend_buf: &mut [u8],
has_opacities: bool,
filler: impl Painter,
) {
if has_opacities {
filler.paint(color_buf);
fill::src_over(
blend_buf,
color_buf.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
);
} else {
// Similarly to solid colors we can just override the previous values
// if all colors in the gradient are fully opaque.
filler.paint(blend_buf);
}
}
match fill {
Paint::Solid(color) => {
let color = &color.to_u8_array();
// If color is completely opaque we can just memcopy the colors.
if color[3] == 255 {
for t in blend_buf.chunks_exact_mut(COLOR_COMPONENTS) {
t.copy_from_slice(color);
}
return;
}
fill::src_over(blend_buf, iter::repeat(*color));
}
Paint::Indexed(paint) => {
let encoded_paint = &encoded_paints[paint.index()];
match encoded_paint {
EncodedPaint::Gradient(g) => match &g.kind {
EncodedKind::Linear(l) => {
let filler = GradientFiller::new(g, l, start_x, start_y);
fill_complex_paint(color_buf, blend_buf, g.has_opacities, filler);
}
EncodedKind::Radial(r) => {
let filler = GradientFiller::new(g, r, start_x, start_y);
fill_complex_paint(color_buf, blend_buf, g.has_opacities, filler);
}
EncodedKind::Sweep(s) => {
let filler = GradientFiller::new(g, s, start_x, start_y);
fill_complex_paint(color_buf, blend_buf, g.has_opacities, filler);
}
},
EncodedPaint::Image(i) => {
let filler = ImageFiller::new(i, start_x, start_y);
fill_complex_paint(color_buf, blend_buf, i.has_opacities, filler);
}
}
}
}
}
/// Strip at a given x and with a width using the given paint and alpha values.
pub fn strip(
&mut self,
x: usize,
width: usize,
alphas: &[u8],
fill: &Paint,
paints: &[EncodedPaint],
) {
debug_assert!(
alphas.len() >= width,
"alpha buffer doesn't contain sufficient elements"
);
let blend_buf = &mut self.blend_buf.last_mut().unwrap()[x * TILE_HEIGHT_COMPONENTS..]
[..TILE_HEIGHT_COMPONENTS * width];
let color_buf =
&mut self.color_buf[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let start_x = self.wide_coords.0 * WideTile::WIDTH + x as u16;
let start_y = self.wide_coords.1 * Tile::HEIGHT;
fn strip_complex_paint(
color_buf: &mut [u8],
blend_buf: &mut [u8],
filler: impl Painter,
alphas: &[u8],
) {
filler.paint(color_buf);
strip::src_over(
blend_buf,
color_buf.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
alphas,
);
}
match fill {
Paint::Solid(color) => {
strip::src_over(blend_buf, iter::repeat(color.to_u8_array()), alphas);
}
Paint::Indexed(paint) => {
let encoded_paint = &paints[paint.index()];
match encoded_paint {
EncodedPaint::Gradient(g) => match &g.kind {
EncodedKind::Linear(l) => {
let filler = GradientFiller::new(g, l, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, filler, alphas);
}
EncodedKind::Radial(r) => {
let filler = GradientFiller::new(g, r, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, filler, alphas);
}
EncodedKind::Sweep(s) => {
let filler = GradientFiller::new(g, s, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, filler, alphas);
}
},
EncodedPaint::Image(i) => {
let filler = ImageFiller::new(i, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, filler, alphas);
}
}
}
}
}
fn clip_fill(&mut self, x: usize, width: usize) {
let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
let target_buffer = rest.last_mut().unwrap();
for col_idx in 0..width {
for row_idx in 0..usize::from(Tile::HEIGHT) {
let px_offset = (x + col_idx) * TILE_HEIGHT_COMPONENTS + row_idx * COLOR_COMPONENTS;
let source_alpha = source_buffer[px_offset + 3] as u16;
let inverse_alpha = 255 - source_alpha;
for channel_idx in 0..COLOR_COMPONENTS {
let dest = target_buffer[px_offset + channel_idx] as u16;
let src = source_buffer[px_offset + channel_idx] as u16;
target_buffer[px_offset + channel_idx] =
(src + div_255(dest * inverse_alpha)) as u8;
}
}
}
}
fn clip_strip(&mut self, x: usize, width: usize, alphas: &[u8]) {
let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
let target_buffer = rest.last_mut().unwrap();
for (col_idx, column_alphas) in alphas
.chunks_exact(usize::from(Tile::HEIGHT))
.take(width)
.enumerate()
{
for (row_idx, &alpha) in column_alphas.iter().enumerate() {
let px_offset = (x + col_idx) * TILE_HEIGHT_COMPONENTS + row_idx * COLOR_COMPONENTS;
let mask_alpha = alpha as u16;
let source_alpha = source_buffer[px_offset + 3] as u16;
let inverse_alpha = 255 - div_255(mask_alpha * source_alpha);
for channel_idx in 0..COLOR_COMPONENTS {
let dest = target_buffer[px_offset + channel_idx] as u16;
let source = source_buffer[px_offset + channel_idx] as u16;
target_buffer[px_offset + channel_idx] =
div_255(dest * inverse_alpha + mask_alpha * source) as u8;
}
}
}
}
}
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<T: Iterator<Item = [u8; COLOR_COMPONENTS]>>(
target: &mut [u8],
mut color_iter: T,
) {
for strip in target.chunks_exact_mut(TILE_HEIGHT_COMPONENTS) {
for bg_c in strip.chunks_exact_mut(COLOR_COMPONENTS) {
let src_c = color_iter.next().unwrap();
for i in 0..COLOR_COMPONENTS {
bg_c[i] = src_c[i] + div_255(bg_c[i] as u16 * (255 - src_c[3] as u16)) 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<T: Iterator<Item = [u8; COLOR_COMPONENTS]>>(
target: &mut [u8],
mut color_iter: T,
alphas: &[u8],
) {
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 src_c = color_iter.next().unwrap();
let mask_a = u16::from(masks[j]);
let inv_src_a_mask_a = 255 - div_255(mask_a * src_c[3] as u16);
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;
}
}
}
}
}
trait Painter {
fn paint(self, target: &mut [u8]);
}