use alloc::vec::Vec;
use arrayvec::ArrayVec;
use tiny_skia_path::NormalizedF32;
use crate::{Color, PremultipliedColor, PremultipliedColorU8, SpreadMode};
use crate::{PixmapRef, Transform};
pub use blitter::RasterPipelineBlitter;
use crate::geom::ScreenIntRect;
use crate::pixmap::SubPixmapMut;
use crate::wide::u32x8;
mod blitter;
#[rustfmt::skip] mod highp;
#[rustfmt::skip] mod lowp;
const MAX_STAGES: usize = 32; #[allow(dead_code)]
#[derive(Copy, Clone, Debug)]
pub enum Stage {
MoveSourceToDestination = 0,
MoveDestinationToSource,
Clamp0,
ClampA,
Premultiply,
UniformColor,
SeedShader,
LoadDestination,
Store,
LoadDestinationU8,
StoreU8,
Gather,
LoadMaskU8,
MaskU8,
ScaleU8,
LerpU8,
Scale1Float,
Lerp1Float,
DestinationAtop,
DestinationIn,
DestinationOut,
DestinationOver,
SourceAtop,
SourceIn,
SourceOut,
SourceOver,
Clear,
Modulate,
Multiply,
Plus,
Screen,
Xor,
ColorBurn,
ColorDodge,
Darken,
Difference,
Exclusion,
HardLight,
Lighten,
Overlay,
SoftLight,
Hue,
Saturation,
Color,
Luminosity,
SourceOverRgba,
Transform,
Reflect,
Repeat,
Bilinear,
Bicubic,
PadX1,
ReflectX1,
RepeatX1,
Gradient,
EvenlySpaced2StopGradient,
XYToRadius,
XYTo2PtConicalFocalOnCircle,
XYTo2PtConicalWellBehaved,
XYTo2PtConicalGreater,
Mask2PtConicalDegenerates,
ApplyVectorMask,
}
pub const STAGES_COUNT: usize = Stage::ApplyVectorMask as usize + 1;
impl<'a> PixmapRef<'a> {
#[inline(always)]
pub(crate) fn gather(&self, index: u32x8) -> [PremultipliedColorU8; highp::STAGE_WIDTH] {
let index: [u32; 8] = bytemuck::cast(index);
let pixels = self.pixels();
[
pixels[index[0] as usize],
pixels[index[1] as usize],
pixels[index[2] as usize],
pixels[index[3] as usize],
pixels[index[4] as usize],
pixels[index[5] as usize],
pixels[index[6] as usize],
pixels[index[7] as usize],
]
}
}
impl<'a> SubPixmapMut<'a> {
#[inline(always)]
pub(crate) fn offset(&self, dx: usize, dy: usize) -> usize {
self.real_width * dy + dx
}
#[inline(always)]
pub(crate) fn slice_at_xy(&mut self, dx: usize, dy: usize) -> &mut [PremultipliedColorU8] {
let offset = self.offset(dx, dy);
&mut self.pixels_mut()[offset..]
}
#[inline(always)]
pub(crate) fn slice_mask_at_xy(&mut self, dx: usize, dy: usize) -> &mut [u8] {
let offset = self.offset(dx, dy);
&mut self.data[offset..]
}
#[inline(always)]
pub(crate) fn slice4_at_xy(
&mut self,
dx: usize,
dy: usize,
) -> &mut [PremultipliedColorU8; highp::STAGE_WIDTH] {
arrayref::array_mut_ref!(self.pixels_mut(), self.offset(dx, dy), highp::STAGE_WIDTH)
}
#[inline(always)]
pub(crate) fn slice16_at_xy(
&mut self,
dx: usize,
dy: usize,
) -> &mut [PremultipliedColorU8; lowp::STAGE_WIDTH] {
arrayref::array_mut_ref!(self.pixels_mut(), self.offset(dx, dy), lowp::STAGE_WIDTH)
}
#[inline(always)]
pub(crate) fn slice16_mask_at_xy(
&mut self,
dx: usize,
dy: usize,
) -> &mut [u8; lowp::STAGE_WIDTH] {
arrayref::array_mut_ref!(self.data, self.offset(dx, dy), lowp::STAGE_WIDTH)
}
}
#[derive(Default, Debug)]
pub struct AAMaskCtx {
pub pixels: [u8; 2],
pub stride: u32, pub shift: usize, }
impl AAMaskCtx {
#[inline(always)]
pub fn copy_at_xy(&self, dx: usize, dy: usize, tail: usize) -> [u8; 2] {
let offset = (self.stride as usize * dy + dx) - self.shift;
match (offset, tail) {
(0, 1) => [self.pixels[0], 0],
(0, 2) => [self.pixels[0], self.pixels[1]],
(1, 1) => [self.pixels[1], 0],
_ => [0, 0], }
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct MaskCtx<'a> {
pub data: &'a [u8],
pub real_width: u32,
}
impl MaskCtx<'_> {
#[inline(always)]
fn offset(&self, dx: usize, dy: usize) -> usize {
self.real_width as usize * dy + dx
}
}
#[derive(Default)]
pub struct Context {
pub current_coverage: f32,
pub sampler: SamplerCtx,
pub uniform_color: UniformColorCtx,
pub evenly_spaced_2_stop_gradient: EvenlySpaced2StopGradientCtx,
pub gradient: GradientCtx,
pub two_point_conical_gradient: TwoPointConicalGradientCtx,
pub limit_x: TileCtx,
pub limit_y: TileCtx,
pub transform: Transform,
}
#[derive(Copy, Clone, Default, Debug)]
pub struct SamplerCtx {
pub spread_mode: SpreadMode,
pub inv_width: f32,
pub inv_height: f32,
}
#[derive(Copy, Clone, Default, Debug)]
pub struct UniformColorCtx {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
pub rgba: [u16; 4], }
#[derive(Copy, Clone, Default, Debug)]
pub struct GradientColor {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl GradientColor {
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self {
GradientColor { r, g, b, a }
}
}
impl From<Color> for GradientColor {
fn from(c: Color) -> Self {
GradientColor {
r: c.red(),
g: c.green(),
b: c.blue(),
a: c.alpha(),
}
}
}
#[derive(Copy, Clone, Default, Debug)]
pub struct EvenlySpaced2StopGradientCtx {
pub factor: GradientColor,
pub bias: GradientColor,
}
#[derive(Clone, Default, Debug)]
pub struct GradientCtx {
pub len: usize,
pub factors: Vec<GradientColor>,
pub biases: Vec<GradientColor>,
pub t_values: Vec<NormalizedF32>,
}
impl GradientCtx {
pub fn push_const_color(&mut self, color: GradientColor) {
self.factors.push(GradientColor::new(0.0, 0.0, 0.0, 0.0));
self.biases.push(color);
}
}
#[derive(Copy, Clone, Default, Debug)]
pub struct TwoPointConicalGradientCtx {
pub mask: u32x8,
pub p0: f32,
}
#[derive(Copy, Clone, Default, Debug)]
pub struct TileCtx {
pub scale: f32,
pub inv_scale: f32, }
pub struct RasterPipelineBuilder {
stages: ArrayVec<Stage, MAX_STAGES>,
force_hq_pipeline: bool,
pub ctx: Context,
}
impl RasterPipelineBuilder {
pub fn new() -> Self {
RasterPipelineBuilder {
stages: ArrayVec::new(),
force_hq_pipeline: false,
ctx: Context::default(),
}
}
pub fn set_force_hq_pipeline(&mut self, hq: bool) {
self.force_hq_pipeline = hq;
}
pub fn push(&mut self, stage: Stage) {
self.stages.push(stage);
}
pub fn push_transform(&mut self, ts: Transform) {
if ts.is_finite() && !ts.is_identity() {
self.stages.push(Stage::Transform);
self.ctx.transform = ts;
}
}
pub fn push_uniform_color(&mut self, c: PremultipliedColor) {
let r = c.red();
let g = c.green();
let b = c.blue();
let a = c.alpha();
let rgba = [
(r * 255.0 + 0.5) as u16,
(g * 255.0 + 0.5) as u16,
(b * 255.0 + 0.5) as u16,
(a * 255.0 + 0.5) as u16,
];
let ctx = UniformColorCtx { r, g, b, a, rgba };
self.stages.push(Stage::UniformColor);
self.ctx.uniform_color = ctx;
}
pub fn compile(self) -> RasterPipeline {
if self.stages.is_empty() {
return RasterPipeline {
kind: RasterPipelineKind::High {
functions: ArrayVec::new(),
tail_functions: ArrayVec::new(),
},
ctx: Context::default(),
};
}
let is_lowp_compatible = self
.stages
.iter()
.all(|stage| !lowp::fn_ptr_eq(lowp::STAGES[*stage as usize], lowp::null_fn));
if self.force_hq_pipeline || !is_lowp_compatible {
let mut functions: ArrayVec<_, MAX_STAGES> = self
.stages
.iter()
.map(|stage| highp::STAGES[*stage as usize] as highp::StageFn)
.collect();
functions.push(highp::just_return as highp::StageFn);
let mut tail_functions = functions.clone();
for fun in &mut tail_functions {
if highp::fn_ptr(*fun) == highp::fn_ptr(highp::load_dst) {
*fun = highp::load_dst_tail as highp::StageFn;
} else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::store) {
*fun = highp::store_tail as highp::StageFn;
} else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::load_dst_u8) {
*fun = highp::load_dst_u8_tail as highp::StageFn;
} else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::store_u8) {
*fun = highp::store_u8_tail as highp::StageFn;
} else if highp::fn_ptr(*fun) == highp::fn_ptr(highp::source_over_rgba) {
*fun = highp::source_over_rgba_tail as highp::StageFn;
}
}
RasterPipeline {
kind: RasterPipelineKind::High {
functions,
tail_functions,
},
ctx: self.ctx,
}
} else {
let mut functions: ArrayVec<_, MAX_STAGES> = self
.stages
.iter()
.map(|stage| lowp::STAGES[*stage as usize] as lowp::StageFn)
.collect();
functions.push(lowp::just_return as lowp::StageFn);
let mut tail_functions = functions.clone();
for fun in &mut tail_functions {
if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::load_dst) {
*fun = lowp::load_dst_tail as lowp::StageFn;
} else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::store) {
*fun = lowp::store_tail as lowp::StageFn;
} else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::load_dst_u8) {
*fun = lowp::load_dst_u8_tail as lowp::StageFn;
} else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::store_u8) {
*fun = lowp::store_u8_tail as lowp::StageFn;
} else if lowp::fn_ptr(*fun) == lowp::fn_ptr(lowp::source_over_rgba) {
*fun = lowp::source_over_rgba_tail as lowp::StageFn;
}
}
RasterPipeline {
kind: RasterPipelineKind::Low {
functions,
tail_functions,
},
ctx: self.ctx,
}
}
}
}
pub enum RasterPipelineKind {
High {
functions: ArrayVec<highp::StageFn, MAX_STAGES>,
tail_functions: ArrayVec<highp::StageFn, MAX_STAGES>,
},
Low {
functions: ArrayVec<lowp::StageFn, MAX_STAGES>,
tail_functions: ArrayVec<lowp::StageFn, MAX_STAGES>,
},
}
pub struct RasterPipeline {
kind: RasterPipelineKind,
pub ctx: Context,
}
impl RasterPipeline {
pub fn run(
&mut self,
rect: &ScreenIntRect,
aa_mask_ctx: AAMaskCtx,
mask_ctx: MaskCtx,
pixmap_src: PixmapRef,
pixmap_dst: &mut SubPixmapMut,
) {
match self.kind {
RasterPipelineKind::High {
ref functions,
ref tail_functions,
} => {
highp::start(
functions.as_slice(),
tail_functions.as_slice(),
rect,
aa_mask_ctx,
mask_ctx,
&mut self.ctx,
pixmap_src,
pixmap_dst,
);
}
RasterPipelineKind::Low {
ref functions,
ref tail_functions,
} => {
lowp::start(
functions.as_slice(),
tail_functions.as_slice(),
rect,
aa_mask_ctx,
mask_ctx,
&mut self.ctx,
pixmap_dst,
);
}
}
}
}
#[rustfmt::skip]
#[cfg(test)]
mod blend_tests {
use super::*;
use crate::{BlendMode, Color, Pixmap, PremultipliedColorU8};
use crate::geom::IntSizeExt;
macro_rules! test_blend {
($name:ident, $mode:expr, $is_highp:expr, $r:expr, $g:expr, $b:expr, $a:expr) => {
#[test]
fn $name() {
let mut pixmap = Pixmap::new(1, 1).unwrap();
pixmap.fill(Color::from_rgba8(50, 127, 150, 200));
let pixmap_src = PixmapRef::from_bytes(&[0, 0, 0, 0], 1, 1).unwrap();
let mut p = RasterPipelineBuilder::new();
p.set_force_hq_pipeline($is_highp);
p.push_uniform_color(Color::from_rgba8(220, 140, 75, 180).premultiply());
p.push(Stage::LoadDestination);
p.push($mode.to_stage().unwrap());
p.push(Stage::Store);
let mut p = p.compile();
let rect = pixmap.size().to_screen_int_rect(0, 0);
p.run(&rect, AAMaskCtx::default(), MaskCtx::default(), pixmap_src,
&mut pixmap.as_mut().as_subpixmap());
assert_eq!(
pixmap.as_ref().pixel(0, 0).unwrap(),
PremultipliedColorU8::from_rgba($r, $g, $b, $a).unwrap()
);
}
};
}
macro_rules! test_blend_lowp {
($name:ident, $mode:expr, $r:expr, $g:expr, $b:expr, $a:expr) => (
test_blend!{$name, $mode, false, $r, $g, $b, $a}
)
}
macro_rules! test_blend_highp {
($name:ident, $mode:expr, $r:expr, $g:expr, $b:expr, $a:expr) => (
test_blend!{$name, $mode, true, $r, $g, $b, $a}
)
}
test_blend_lowp!(clear_lowp, BlendMode::Clear, 0, 0, 0, 0);
test_blend_lowp!(destination_lowp, BlendMode::Destination, 39, 100, 118, 200);
test_blend_lowp!(source_over_lowp, BlendMode::SourceOver, 167, 129, 88, 239);
test_blend_lowp!(destination_over_lowp, BlendMode::DestinationOver, 73, 122, 130, 239);
test_blend_lowp!(source_in_lowp, BlendMode::SourceIn, 122, 78, 42, 141);
test_blend_lowp!(destination_in_lowp, BlendMode::DestinationIn, 28, 71, 83, 141);
test_blend_lowp!(source_out_lowp, BlendMode::SourceOut, 34, 22, 12, 39);
test_blend_lowp!(destination_out_lowp, BlendMode::DestinationOut, 12, 30, 35, 59);
test_blend_lowp!(source_atop_lowp, BlendMode::SourceAtop, 133, 107, 76, 200);
test_blend_lowp!(destination_atop_lowp, BlendMode::DestinationAtop, 61, 92, 95, 180);
test_blend_lowp!(xor_lowp, BlendMode::Xor, 45, 51, 46, 98);
test_blend_lowp!(plus_lowp, BlendMode::Plus, 194, 199, 171, 255);
test_blend_lowp!(modulate_lowp, BlendMode::Modulate, 24, 39, 25, 141);
test_blend_lowp!(screen_lowp, BlendMode::Screen, 170, 160, 146, 239);
test_blend_lowp!(overlay_lowp, BlendMode::Overlay, 92, 128, 106, 239);
test_blend_lowp!(darken_lowp, BlendMode::Darken, 72, 121, 88, 239);
test_blend_lowp!(lighten_lowp, BlendMode::Lighten, 166, 128, 129, 239);
test_blend_lowp!(hard_light_lowp, BlendMode::HardLight, 154, 128, 95, 239);
test_blend_lowp!(difference_lowp, BlendMode::Difference, 138, 57, 87, 239);
test_blend_lowp!(exclusion_lowp, BlendMode::Exclusion, 146, 121, 121, 239);
test_blend_lowp!(multiply_lowp, BlendMode::Multiply, 69, 90, 71, 238);
test_blend_highp!(clear_highp, BlendMode::Clear, 0, 0, 0, 0);
test_blend_highp!(destination_highp, BlendMode::Destination, 39, 100, 118, 200);
test_blend_highp!(source_over_highp, BlendMode::SourceOver, 167, 128, 88, 239);
test_blend_highp!(destination_over_highp, BlendMode::DestinationOver, 72, 121, 129, 239);
test_blend_highp!(source_in_highp, BlendMode::SourceIn, 122, 78, 42, 141);
test_blend_highp!(destination_in_highp, BlendMode::DestinationIn, 28, 71, 83, 141);
test_blend_highp!(source_out_highp, BlendMode::SourceOut, 33, 21, 11, 39);
test_blend_highp!(destination_out_highp, BlendMode::DestinationOut, 11, 29, 35, 59);
test_blend_highp!(source_atop_highp, BlendMode::SourceAtop, 133, 107, 76, 200);
test_blend_highp!(destination_atop_highp, BlendMode::DestinationAtop, 61, 92, 95, 180);
test_blend_highp!(xor_highp, BlendMode::Xor, 45, 51, 46, 98);
test_blend_highp!(plus_highp, BlendMode::Plus, 194, 199, 171, 255);
test_blend_highp!(modulate_highp, BlendMode::Modulate, 24, 39, 24, 141);
test_blend_highp!(screen_highp, BlendMode::Screen, 171, 160, 146, 239);
test_blend_highp!(overlay_highp, BlendMode::Overlay, 92, 128, 106, 239);
test_blend_highp!(darken_highp, BlendMode::Darken, 72, 121, 88, 239);
test_blend_highp!(lighten_highp, BlendMode::Lighten, 167, 128, 129, 239);
test_blend_highp!(color_dodge_highp, BlendMode::ColorDodge, 186, 192, 164, 239);
test_blend_highp!(color_burn_highp, BlendMode::ColorBurn, 54, 63, 46, 239);
test_blend_highp!(hard_light_highp, BlendMode::HardLight, 155, 128, 95, 239);
test_blend_highp!(soft_light_highp, BlendMode::SoftLight, 98, 124, 115, 239);
test_blend_highp!(difference_highp, BlendMode::Difference, 139, 58, 88, 239);
test_blend_highp!(exclusion_highp, BlendMode::Exclusion, 147, 121, 122, 239);
test_blend_highp!(multiply_highp, BlendMode::Multiply, 69, 89, 71, 239);
test_blend_highp!(hue_highp, BlendMode::Hue, 128, 103, 74, 239);
test_blend_highp!(saturation_highp, BlendMode::Saturation, 59, 126, 140, 239);
test_blend_highp!(color_highp, BlendMode::Color, 139, 100, 60, 239);
test_blend_highp!(luminosity_highp, BlendMode::Luminosity, 100, 149, 157, 239);
}