use taffy::Point;
use tiny_skia::PixmapMut;
use super::paint_source::{MaskCompositeColor, apply_mask_color_mode, sample_paint_source};
use super::{
MaskSamplingOptions, MaskView, PaintSource, SamplingFootprint, compute_overlay_bounds_for_canvas,
};
use crate::layout::style::{Affine, BlendMode};
use crate::rendering::{
Placement,
blend::{blend_premultiplied_pixel, composite_premultiplied_over, scale_premultiplied_pixel},
};
#[derive(Clone, Copy)]
pub(super) struct Options<'a> {
pub placement: Placement,
pub sampling: MaskSamplingOptions,
pub color_mode: MaskCompositeColor,
pub mode: BlendMode,
pub combined_mask: Option<MaskView<'a>>,
}
pub(super) fn constant(
pixmap: &mut PixmapMut<'_>,
mask: &[u8],
placement: Placement,
color: [u8; 4],
mode: BlendMode,
combined_mask: Option<MaskView<'_>>,
) {
if color[3] == 0 {
return;
}
let canvas_width = pixmap.width();
let canvas_height = pixmap.height();
let Some((offset_x, offset_y, dest_x_min, dest_x_max, dest_y_min, dest_y_max)) =
compute_overlay_bounds_for_canvas(
canvas_width,
canvas_height,
Point {
x: placement.left as f32,
y: placement.top as f32,
},
placement.width,
placement.height,
)
else {
return;
};
let pixels: &mut [[u8; 4]] = bytemuck::cast_slice_mut(pixmap.pixels_mut());
if mode == BlendMode::Normal && combined_mask.is_none() {
constant_normal(
pixels,
canvas_width as usize,
mask,
placement.width as usize,
color,
(dest_x_min, dest_x_max, dest_y_min, dest_y_max),
(offset_x, offset_y),
);
return;
}
constant_general(
pixels,
canvas_width as usize,
mask,
placement.width as usize,
color,
mode,
combined_mask,
(dest_x_min, dest_x_max, dest_y_min, dest_y_max),
(offset_x, offset_y),
);
}
pub(super) fn source(
pixmap: &mut PixmapMut<'_>,
mask: &[u8],
source: PaintSource<'_>,
options: Options<'_>,
) {
if mask.is_empty() {
return;
}
if let Some(color) = source.premultiplied_constant() {
constant(
pixmap,
mask,
options.placement,
apply_mask_color_mode(color, options.color_mode),
options.mode,
options.combined_mask,
);
return;
}
let canvas_width = pixmap.width();
let canvas_height = pixmap.height();
let Some(bounds) = compute_overlay_bounds_for_canvas(
canvas_width,
canvas_height,
Point {
x: options.placement.left as f32,
y: options.placement.top as f32,
},
options.placement.width,
options.placement.height,
) else {
return;
};
let (offset_x, offset_y, dest_x_min, dest_x_max, dest_y_min, dest_y_max) = bounds;
if options.color_mode == MaskCompositeColor::SourceOnly
&& options.mode == BlendMode::Normal
&& try_translation_blit(
pixmap,
mask,
source,
&options,
(dest_x_min, dest_x_max, dest_y_min, dest_y_max),
(offset_x, offset_y),
)
{
return;
}
source_general(
pixmap,
mask,
source,
&options,
(dest_x_min, dest_x_max, dest_y_min, dest_y_max),
(offset_x, offset_y),
canvas_width as usize,
);
}
#[inline]
fn constant_normal(
pixels: &mut [[u8; 4]],
canvas_width: usize,
mask: &[u8],
mask_stride: usize,
color: [u8; 4],
bounds: (i32, i32, i32, i32),
offset: (i32, i32),
) {
let (dest_x_min, dest_x_max, dest_y_min, dest_y_max) = bounds;
let (offset_x, offset_y) = offset;
let span = (dest_x_max - dest_x_min) as usize;
for dest_y in dest_y_min..dest_y_max {
let mask_y = (dest_y - offset_y) as usize;
let mask_x = (dest_x_min - offset_x) as usize;
let dst_row_start = dest_y as usize * canvas_width + dest_x_min as usize;
let mask_row_start = mask_y * mask_stride + mask_x;
let dst = &mut pixels[dst_row_start..dst_row_start + span];
let mask_row = &mask[mask_row_start..mask_row_start + span];
for (dst_px, &alpha) in dst.iter_mut().zip(mask_row) {
if alpha == 0 {
continue;
}
let src = scale_premultiplied_pixel(color, alpha);
if src[3] != 0 {
composite_premultiplied_over(dst_px, src);
}
}
}
}
#[allow(clippy::too_many_arguments)]
#[inline]
fn constant_general(
pixels: &mut [[u8; 4]],
canvas_width: usize,
mask: &[u8],
mask_stride: usize,
color: [u8; 4],
mode: BlendMode,
combined_mask: Option<MaskView<'_>>,
bounds: (i32, i32, i32, i32),
offset: (i32, i32),
) {
let (dest_x_min, dest_x_max, dest_y_min, dest_y_max) = bounds;
let (offset_x, offset_y) = offset;
for dest_y in dest_y_min..dest_y_max {
let mask_y = (dest_y - offset_y) as usize;
let dst_row = dest_y as usize * canvas_width;
let mask_row = mask_y * mask_stride;
for dest_x in dest_x_min..dest_x_max {
let alpha = mask[mask_row + (dest_x - offset_x) as usize];
if alpha == 0 {
continue;
}
let mut src = scale_premultiplied_pixel(color, alpha);
if src[3] == 0 {
continue;
}
if let Some(view) = combined_mask {
let extra = view.alpha_at(dest_x as u32, dest_y as u32);
if extra == 0 {
continue;
}
src = scale_premultiplied_pixel(src, extra);
if src[3] == 0 {
continue;
}
}
blend_premultiplied_pixel(&mut pixels[dst_row + dest_x as usize], src, mode);
}
}
}
fn try_translation_blit(
pixmap: &mut PixmapMut<'_>,
mask: &[u8],
source: PaintSource<'_>,
options: &Options<'_>,
bounds: (i32, i32, i32, i32),
offset: (i32, i32),
) -> bool {
let transform = options.sampling.canvas_to_source;
if !transform.only_translation() || transform.x.fract() != 0.0 || transform.y.fract() != 0.0 {
return false;
}
let Some(source_pixmap) = source.as_pixmap_ref() else {
return false;
};
let (dest_x_min, dest_x_max, dest_y_min, dest_y_max) = bounds;
let (offset_x, offset_y) = offset;
let canvas_width = pixmap.width() as usize;
let mask_stride = options.placement.width as usize;
let source_width = source_pixmap.width() as i32;
let source_height = source_pixmap.height() as i32;
let source_pixels = source_pixmap.pixels();
let pixels: &mut [[u8; 4]] = bytemuck::cast_slice_mut(pixmap.pixels_mut());
let sample_dx = transform.x as i32;
let sample_dy = transform.y as i32;
let span = (dest_x_max - dest_x_min) as usize;
for dest_y in dest_y_min..dest_y_max {
let src_y = dest_y + sample_dy;
if src_y < 0 || src_y >= source_height {
continue;
}
let mask_y = (dest_y - offset_y) as usize;
let mask_x_start = (dest_x_min - offset_x) as usize;
let mask_row =
&mask[mask_y * mask_stride + mask_x_start..mask_y * mask_stride + mask_x_start + span];
let combined_row = options
.combined_mask
.as_ref()
.map(|view| view.row(dest_y, dest_x_min));
let src_row_offset = src_y as usize * source_width as usize;
let dst_row_offset = dest_y as usize * canvas_width;
let dst_row = &mut pixels
[dst_row_offset + dest_x_min as usize..dst_row_offset + dest_x_min as usize + span];
let src_x_base = dest_x_min + sample_dx;
for (i, (dst, &mask_alpha)) in dst_row.iter_mut().zip(mask_row).enumerate() {
if mask_alpha == 0 {
continue;
}
let alpha = match combined_row.as_ref() {
Some(row) => {
let extra = row.alpha_at_offset(i);
if extra == 0 {
continue;
}
if extra == u8::MAX {
mask_alpha
} else if mask_alpha == u8::MAX {
extra
} else {
crate::rendering::fast_div_255(mask_alpha as u32 * extra as u32)
}
}
None => mask_alpha,
};
if alpha == 0 {
continue;
}
let src_x = src_x_base + i as i32;
if src_x < 0 || src_x >= source_width {
continue;
}
let src_pixel = source_pixels[src_row_offset + src_x as usize];
let src_a = src_pixel.alpha();
if src_a == 0 {
continue;
}
let src_rgba = [src_pixel.red(), src_pixel.green(), src_pixel.blue(), src_a];
if alpha == u8::MAX && src_a == u8::MAX {
*dst = src_rgba;
continue;
}
let src = scale_premultiplied_pixel(src_rgba, alpha);
if src[3] == 0 {
continue;
}
composite_premultiplied_over(dst, src);
}
}
true
}
#[allow(clippy::too_many_arguments)]
fn source_general(
pixmap: &mut PixmapMut<'_>,
mask: &[u8],
source: PaintSource<'_>,
options: &Options<'_>,
bounds: (i32, i32, i32, i32),
offset: (i32, i32),
canvas_width: usize,
) {
let (dest_x_min, dest_x_max, dest_y_min, dest_y_max) = bounds;
let (offset_x, offset_y) = offset;
let mask_stride = options.placement.width as usize;
let footprint = sampling_footprint(options.sampling.canvas_to_source);
let pixels: &mut [[u8; 4]] = bytemuck::cast_slice_mut(pixmap.pixels_mut());
for dest_y in dest_y_min..dest_y_max {
let mask_y = (dest_y - offset_y) as usize;
let dst_row = dest_y as usize * canvas_width;
let mask_row = mask_y * mask_stride;
let mut sample = options.sampling.canvas_to_source.transform_point(Point {
x: dest_x_min as f32 + options.sampling.sample_bias.x,
y: dest_y as f32 + options.sampling.sample_bias.y,
});
for dest_x in dest_x_min..dest_x_max {
let mask_alpha = mask[mask_row + (dest_x - offset_x) as usize];
let sampled = if mask_alpha == 0 {
None
} else {
sample_paint_source(
source,
options.sampling.algorithm,
sample.x,
sample.y,
footprint,
)
};
sample.x += options.sampling.canvas_to_source.a;
sample.y += options.sampling.canvas_to_source.b;
let Some(mut src) = sampled else {
continue;
};
src = apply_mask_color_mode(src, options.color_mode);
src = scale_premultiplied_pixel(src, mask_alpha);
if src[3] == 0 {
continue;
}
if let Some(view) = options.combined_mask {
let extra = view.alpha_at(dest_x as u32, dest_y as u32);
if extra == 0 {
continue;
}
src = scale_premultiplied_pixel(src, extra);
if src[3] == 0 {
continue;
}
}
blend_premultiplied_pixel(&mut pixels[dst_row + dest_x as usize], src, options.mode);
}
}
}
#[inline(always)]
pub(super) fn sampling_footprint(transform: Affine) -> SamplingFootprint {
SamplingFootprint::new(
transform.a.hypot(transform.b),
transform.c.hypot(transform.d),
)
}