use cranpose_render_common::{
graph_scene::RenderDiagnostics,
rgb565::{GlyphPixel, Rgb565, Rgb565Be, Rgb565Le},
text_canvas::TextPixelRows,
};
use cranpose_ui_graphics::BlendMode;
use crate::draw::{blend_pixel, unit_byte};
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum GradientColors {
Dithered,
Plain,
}
macro_rules! blend_runs_of_one_pixel {
($pixels:expr, |$pixel:ident| $blend:expr) => {
if let Some((first, rest)) = $pixels.split_first_mut() {
let mut under = *first;
{
let $pixel = &mut *first;
$blend;
}
let mut over = *first;
for $pixel in rest {
if *$pixel == under {
*$pixel = over;
} else {
under = *$pixel;
$blend;
over = *$pixel;
}
}
}
};
}
pub(crate) trait Pixel: GlyphPixel {
const GRADIENT: GradientColors;
type Paint: Copy;
fn opaque(bytes: [u8; 4]) -> Self;
fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint;
fn blend_paint(&mut self, paint: Self::Paint, coverage: f32, diagnostics: &RenderDiagnostics);
fn blend_run(
pixels: &mut [Self],
paint: Self::Paint,
table: &mut Option<BlendTable>,
diagnostics: &RenderDiagnostics,
);
fn fill(pixels: &mut [Self], value: Self);
fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]);
fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_>;
}
impl Pixel for [u8; 4] {
const GRADIENT: GradientColors = GradientColors::Dithered;
type Paint = ([f32; 4], BlendMode);
fn opaque(bytes: [u8; 4]) -> Self {
bytes
}
fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint {
(color, blend_mode)
}
fn blend_paint(
&mut self,
(mut color, blend_mode): Self::Paint,
coverage: f32,
diagnostics: &RenderDiagnostics,
) {
color[3] *= coverage;
blend_pixel(self, color, blend_mode, diagnostics);
}
#[inline(never)]
fn blend_run(
pixels: &mut [Self],
(color, blend_mode): Self::Paint,
table: &mut Option<BlendTable>,
diagnostics: &RenderDiagnostics,
) {
let table = table.get_or_insert_with(BlendTable::default);
blend_runs_of_one_pixel!(pixels, |pixel| table.blend(
pixel,
color,
blend_mode,
diagnostics
));
}
fn fill(pixels: &mut [Self], value: Self) {
match bytemuck::try_cast_slice_mut::<[u8; 4], u32>(pixels) {
Ok(words) => fill_words(words, u32::from_ne_bytes(value)),
Err(_) => pixels.fill(value),
}
}
fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]) {
match bytemuck::try_cast_slice_mut::<[u8; 4], u32>(pixels) {
Ok(words) => repeat(words, pattern.map(u32::from_ne_bytes)),
Err(_) => repeat(pixels, pattern),
}
}
fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_> {
TextPixelRows::Rgba(pixels)
}
}
#[derive(Clone, Copy)]
pub(crate) enum Paint565 {
Over { bytes: [u8; 3], alpha: f32 },
Unit([f32; 4], BlendMode),
}
fn paint_565(color: [f32; 4], blend_mode: BlendMode) -> Paint565 {
match blend_mode {
BlendMode::SrcOver => Paint565::Over {
bytes: [
unit_byte(color[0]),
unit_byte(color[1]),
unit_byte(color[2]),
],
alpha: color[3],
},
_ => Paint565::Unit(color, blend_mode),
}
}
fn blend_paint_565<P: Rgb565 + GlyphPixel>(
pixel: &mut P,
paint: Paint565,
coverage: f32,
diagnostics: &RenderDiagnostics,
) {
match paint {
Paint565::Over {
bytes: [red, green, blue],
alpha,
} => pixel.blend_bytes([red, green, blue, unit_byte(alpha * coverage)]),
Paint565::Unit(mut color, blend_mode) => {
color[3] *= coverage;
let mut rgba = pixel.to_rgba();
blend_pixel(&mut rgba, color, blend_mode, diagnostics);
*pixel = P::from_rgb(rgba);
}
}
}
fn fill_565(pixels: &mut [u16], value: u16) {
let (head, words, tail) = bytemuck::pod_align_to_mut::<u16, u32>(pixels);
head.fill(value);
fill_words(words, u32::from(value) * 0x0001_0001);
tail.fill(value);
}
fn fill_pattern_565(pixels: &mut [u16], pattern: [u16; 4]) {
let (head, words, tail) = bytemuck::pod_align_to_mut::<u16, u32>(pixels);
repeat(head, pattern);
let start = head.len();
let after = start + 2 * words.len();
let word =
|first: usize| -> u32 { bytemuck::cast([pattern[first & 3], pattern[(first + 1) & 3]]) };
let pair = [word(start), word(start + 2)];
let (pairs, rest) = words.as_chunks_mut::<2>();
for chunk in pairs {
*chunk = pair;
}
rest.fill(pair[0]);
for (offset, pixel) in tail.iter_mut().enumerate() {
*pixel = pattern[(after + offset) & 3];
}
}
trait Pixel565: Rgb565 + GlyphPixel + bytemuck::Pod + PartialEq {
fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_>;
}
impl Pixel565 for Rgb565Le {
fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_> {
TextPixelRows::Rgb565Le(pixels)
}
}
impl Pixel565 for Rgb565Be {
fn text_rows_565(pixels: &mut [Self]) -> TextPixelRows<'_> {
TextPixelRows::Rgb565Be(pixels)
}
}
impl<P: Pixel565> Pixel for P {
const GRADIENT: GradientColors = GradientColors::Plain;
type Paint = Paint565;
fn opaque(bytes: [u8; 4]) -> Self {
Self::from_rgb(bytes)
}
fn paint(color: [f32; 4], blend_mode: BlendMode) -> Self::Paint {
paint_565(color, blend_mode)
}
fn blend_paint(&mut self, paint: Self::Paint, coverage: f32, diagnostics: &RenderDiagnostics) {
blend_paint_565(self, paint, coverage, diagnostics);
}
#[inline(never)]
fn blend_run(
pixels: &mut [Self],
paint: Self::Paint,
table: &mut Option<BlendTable>,
diagnostics: &RenderDiagnostics,
) {
let Paint565::Over {
bytes: [red, green, blue],
alpha,
} = paint
else {
for pixel in pixels {
blend_paint_565(pixel, paint, 1.0, diagnostics);
}
return;
};
let src = [red, green, blue, unit_byte(alpha)];
let table = table.get_or_insert_with(BlendTable::default);
blend_runs_of_one_pixel!(pixels, |pixel| table.blend_565(pixel, src));
}
fn fill(pixels: &mut [Self], value: Self) {
fill_565(bytemuck::cast_slice_mut(pixels), bytemuck::cast(value));
}
fn fill_pattern(pixels: &mut [Self], pattern: [Self; 4]) {
fill_pattern_565(
bytemuck::cast_slice_mut(pixels),
pattern.map(bytemuck::cast),
);
}
fn text_rows(pixels: &mut [Self]) -> TextPixelRows<'_> {
Self::text_rows_565(pixels)
}
}
pub(crate) struct BlendTable {
bytes: [[u8; 256]; 4],
known: [[u64; 4]; 4],
}
impl Default for BlendTable {
fn default() -> Self {
Self {
bytes: [[0; 256]; 4],
known: [[0; 4]; 4],
}
}
}
impl BlendTable {
fn blend(
&mut self,
pixel: &mut [u8; 4],
color: [f32; 4],
blend_mode: BlendMode,
diagnostics: &RenderDiagnostics,
) {
let under = *pixel;
if let Some(over) = self.look_up(under) {
*pixel = over;
return;
}
blend_pixel(pixel, color, blend_mode, diagnostics);
self.keep::<4>(under, *pixel);
}
fn blend_565<P: Rgb565 + GlyphPixel>(&mut self, pixel: &mut P, src: [u8; 4]) {
let under = pixel.to_rgba();
if let Some(over) = self.look_up_565(under) {
*pixel = P::from_rgb(over);
return;
}
pixel.blend_bytes(src);
self.keep::<3>(under, pixel.to_rgba());
}
fn knows(&self, channel: usize, byte: u8) -> bool {
let byte = usize::from(byte);
self.known[channel][byte / 64] & (1 << (byte % 64)) != 0
}
fn look_up(&self, under: [u8; 4]) -> Option<[u8; 4]> {
(0..4)
.all(|channel| self.knows(channel, under[channel]))
.then(|| [0, 1, 2, 3].map(|channel| self.bytes[channel][usize::from(under[channel])]))
}
fn look_up_565(&self, under: [u8; 4]) -> Option<[u8; 4]> {
let mut over = under;
for channel in 0..3 {
if !self.knows(channel, under[channel]) {
return None;
}
over[channel] = self.bytes[channel][usize::from(under[channel])];
}
Some(over)
}
fn keep<const CHANNELS: usize>(&mut self, under: [u8; 4], over: [u8; 4]) {
for channel in 0..CHANNELS {
let byte = usize::from(under[channel]);
self.bytes[channel][byte] = over[channel];
self.known[channel][byte / 64] |= 1 << (byte % 64);
}
}
}
fn fill_words(words: &mut [u32], word: u32) {
let (quads, rest) = words.as_chunks_mut::<4>();
for quad in quads {
*quad = [word; 4];
}
rest.fill(word);
}
fn repeat<T: Copy>(items: &mut [T], pattern: [T; 4]) {
let (quads, rest) = items.as_chunks_mut::<4>();
for quad in quads {
*quad = pattern;
}
for (item, &value) in rest.iter_mut().zip(&pattern) {
*item = value;
}
}