ltk/render/
image.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! Image draw + SHM serialisation for [`SoftwareCanvas`].
//!
//! `draw_image_data` premultiplies the incoming straight-alpha RGBA
//! into a thread-local scratch buffer, wraps the result in a
//! short-lived tiny-skia pixmap, and composites it into `self.pixmap`
//! honouring the active clip mask + global alpha + `opacity`.
//!
//! `write_to_wayland_buf` is the serialisation path to the `wl_shm`
//! pool used by the software draw path. Either memcpys straight
//! (Abgr8888 matches tiny-skia's byte order) or swaps R/B in blocks
//! of four pixels (Argb8888 fallback).

use tiny_skia::{ Pixmap, PixmapPaint, Transform };

use crate::types::Rect;

use super::SoftwareCanvas;

impl SoftwareCanvas
{
	pub fn draw_image_data( &mut self, rgba_data: &[u8], img_w: u32, img_h: u32, dest: Rect, opacity: f32 )
	{
		let expected = ( img_w as usize ).saturating_mul( img_h as usize ).saturating_mul( 4 );
		if img_w == 0 || img_h == 0 || rgba_data.len() != expected
		{
			eprintln!(
				"[ltk] SoftwareCanvas::draw_image_data: refusing draw — {}×{} declared, {} bytes provided, expected {}",
				img_w, img_h, rgba_data.len(), expected,
			);
			return;
		}

		let Some( int_size ) = tiny_skia::IntSize::from_wh( img_w, img_h ) else { return };

		thread_local! {
			static PREMUL_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new( Vec::new() );
		}

		PREMUL_BUF.with( |cell|
		{
			let mut premul = cell.borrow_mut();
			let needed = rgba_data.len();
			premul.resize( needed, 0 );
			for ( dst, src ) in premul.chunks_exact_mut( 4 ).zip( rgba_data.chunks_exact( 4 ) )
			{
				let a = (src[3] as f32 / 255.0) * opacity * self.global_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;
			}

			if let Some( src_pixmap ) = Pixmap::from_vec( std::mem::take( &mut *premul ), int_size )
			{
				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() );
				*premul = src_pixmap.take();
			}
		} );
	}

	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 chunks = len / 16;
		let remainder = len % 16;
		let mut i = 0;
		for _ in 0..chunks
		{
			buf[i]      = src[i + 2];
			buf[i + 1]  = src[i + 1];
			buf[i + 2]  = src[i];
			buf[i + 3]  = src[i + 3];
			buf[i + 4]  = src[i + 6];
			buf[i + 5]  = src[i + 5];
			buf[i + 6]  = src[i + 4];
			buf[i + 7]  = src[i + 7];
			buf[i + 8]  = src[i + 10];
			buf[i + 9]  = src[i + 9];
			buf[i + 10] = src[i + 8];
			buf[i + 11] = src[i + 11];
			buf[i + 12] = src[i + 14];
			buf[i + 13] = src[i + 13];
			buf[i + 14] = src[i + 12];
			buf[i + 15] = src[i + 15];
			i += 16;
		}
		for _ in 0..(remainder / 4)
		{
			buf[i]     = src[i + 2];
			buf[i + 1] = src[i + 1];
			buf[i + 2] = src[i];
			buf[i + 3] = src[i + 3];
			i += 4;
		}
	}
}