use std::cell::RefCell;
use tiny_skia::{ IntSize, Pixmap, PixmapPaint, Transform };
use crate::types::Rect;
use super::helpers::{ fingerprint_rgba, ImageId };
use super::lru::Lru;
use super::SoftwareCanvas;
const PREMUL_CACHE_MAX_BYTES: usize = 32 * 1024 * 1024;
type PremulKey = ( u32, u32, ImageId, u32 );
struct Premul
{
pixmap: Pixmap,
opaque: bool,
}
thread_local! {
static PREMUL_CACHE: RefCell<Lru<PremulKey, Premul>> = RefCell::new( Lru::new( PREMUL_CACHE_MAX_BYTES ) );
}
fn premultiply( rgba: &[u8], size: IntSize, alpha: f32 ) -> Option<Premul>
{
let mut out = vec![ 0u8; rgba.len() ];
let mut opaque = alpha >= 1.0;
for ( dst, src ) in out.chunks_exact_mut( 4 ).zip( rgba.chunks_exact( 4 ) )
{
opaque &= src[3] == 255;
let a = ( src[3] as f32 / 255.0 ) * alpha;
dst[0] = ( src[0] as f32 * a ) as u8;
dst[1] = ( src[1] as f32 * a ) as u8;
dst[2] = ( src[2] as f32 * a ) as u8;
dst[3] = ( a * 255.0 ) as u8;
}
Pixmap::from_vec( out, size ).map( |pixmap| Premul { pixmap, opaque } )
}
fn copy_rows( dst: &mut Pixmap, src: &Pixmap, x: i32, y: i32 )
{
let ( dw, dh ) = ( dst.width() as i32, dst.height() as i32 );
let ( sw, sh ) = ( src.width() as i32, src.height() as i32 );
let x0 = x.max( 0 );
let x1 = ( x + sw ).min( dw );
let y0 = y.max( 0 );
let y1 = ( y + sh ).min( dh );
if x0 >= x1 || y0 >= y1 { return; }
let row_bytes = ( ( x1 - x0 ) * 4 ) as usize;
let src_data = src.data();
let dst_data = dst.data_mut();
for row in y0..y1
{
let s = ( ( ( row - y ) * sw + ( x0 - x ) ) * 4 ) as usize;
let d = ( ( row * dw + x0 ) * 4 ) as usize;
dst_data[ d..d + row_bytes ].copy_from_slice( &src_data[ s..s + row_bytes ] );
}
}
impl SoftwareCanvas
{
pub fn draw_image_data( &mut self, rgba_data: &[u8], img_w: u32, img_h: u32, dest: Rect, opacity: f32 )
{
self.draw_image( ImageId::Content( fingerprint_rgba( rgba_data ) ), rgba_data, img_w, img_h, dest, opacity );
}
pub fn draw_image_keyed( &mut self, key: u64, rgba_data: &[u8], img_w: u32, img_h: u32, dest: Rect, opacity: f32 )
{
self.draw_image( ImageId::Keyed( key ), rgba_data, img_w, img_h, dest, opacity );
}
fn draw_image( &mut self, id: ImageId, rgba_data: &[u8], img_w: u32, img_h: u32, dest: Rect, opacity: f32 )
{
if !super::helpers::validate_rgba_dims( "SoftwareCanvas", rgba_data, img_w, img_h )
{
return;
}
let dest = Rect
{
x: dest.x.round(),
y: dest.y.round(),
width: dest.width.round(),
height: dest.height.round(),
};
let Some( int_size ) = tiny_skia::IntSize::from_wh( img_w, img_h ) else { return };
let alpha = opacity * self.global_alpha;
let key = ( img_w, img_h, id, alpha.to_bits() );
PREMUL_CACHE.with( |cell|
{
let mut cache = cell.borrow_mut();
if cache.get( &key ).is_none()
{
let Some( premul ) = premultiply( rgba_data, int_size, alpha ) else { return };
let bytes = premul.pixmap.data().len();
cache.insert( key, premul, bytes );
}
let Some( src ) = cache.get( &key ) else { return };
let unscaled = dest.width as u32 == img_w && dest.height as u32 == img_h;
if src.opaque && unscaled && self.clip_mask.is_none()
{
copy_rows( &mut self.pixmap, &src.pixmap, dest.x as i32, dest.y as i32 );
return;
}
let sx = dest.width / img_w as f32;
let sy = dest.height / img_h as f32;
let t = Transform::from_scale( sx, sy ).post_translate( dest.x, dest.y );
let paint = PixmapPaint
{
quality: tiny_skia::FilterQuality::Bilinear,
..PixmapPaint::default()
};
self.pixmap.draw_pixmap( 0, 0, src.pixmap.as_ref(), &paint, t, self.clip_mask.as_ref() );
} );
}
pub fn write_to_wayland_buf( &self, buf: &mut [u8], swap_rb: bool )
{
let src = self.pixmap.data();
let len = src.len().min( buf.len() );
if !swap_rb
{
buf[..len].copy_from_slice( &src[..len] );
return;
}
let whole = len - len % 4;
for ( dst, px ) in buf[..whole].chunks_exact_mut( 4 ).zip( src[..whole].chunks_exact( 4 ) )
{
let v = u32::from_le_bytes( [ px[0], px[1], px[2], px[3] ] );
let swapped = ( v & 0xff00_ff00 ) | ( ( v >> 16 ) & 0xff ) | ( ( v & 0xff ) << 16 );
dst.copy_from_slice( &swapped.to_le_bytes() );
}
}
}