1use std::sync::{Arc, Weak};
4
5use super::frames::{FramePool, HalfPixel};
6use super::half;
7use crate::{RenderError, RenderSize};
8
9#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
11pub(crate) struct PixelBox {
12 pub(crate) x: u32,
13 pub(crate) y: u32,
14 pub(crate) width: u32,
15 pub(crate) height: u32,
16}
17
18impl PixelBox {
19 pub(crate) const EMPTY: Self = Self {
20 x: 0,
21 y: 0,
22 width: 0,
23 height: 0,
24 };
25
26 pub(crate) const fn whole(size: RenderSize) -> Self {
27 Self {
28 x: 0,
29 y: 0,
30 width: size.width,
31 height: size.height,
32 }
33 }
34
35 pub(crate) const fn is_empty(self) -> bool {
36 self.width == 0 || self.height == 0
37 }
38
39 pub(crate) const fn right(self) -> u32 {
40 self.x + self.width
41 }
42
43 pub(crate) const fn bottom(self) -> u32 {
44 self.y + self.height
45 }
46
47 pub(crate) fn pixels(self) -> usize {
48 self.width as usize * self.height as usize
49 }
50
51 pub(crate) const fn contains(self, x: u32, y: u32) -> bool {
52 x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
53 }
54
55 pub(crate) fn intersect(self, other: Self) -> Self {
57 let x = self.x.max(other.x);
58 let y = self.y.max(other.y);
59 let right = self.right().min(other.right());
60 let bottom = self.bottom().min(other.bottom());
61 if right <= x || bottom <= y {
62 return Self::EMPTY;
63 }
64 Self {
65 x,
66 y,
67 width: right - x,
68 height: bottom - y,
69 }
70 }
71
72 pub(crate) fn union(self, other: Self) -> Self {
74 if self.is_empty() {
75 return other;
76 }
77 if other.is_empty() {
78 return self;
79 }
80 let x = self.x.min(other.x);
81 let y = self.y.min(other.y);
82 Self {
83 x,
84 y,
85 width: self.right().max(other.right()) - x,
86 height: self.bottom().max(other.bottom()) - y,
87 }
88 }
89
90 pub(crate) fn expand(self, margin: u32, size: RenderSize) -> Self {
92 if self.is_empty() {
93 return self;
94 }
95 let x = self.x.saturating_sub(margin);
96 let y = self.y.saturating_sub(margin);
97 let right = self.right().saturating_add(margin).min(size.width);
98 let bottom = self.bottom().saturating_add(margin).min(size.height);
99 Self {
100 x,
101 y,
102 width: right - x,
103 height: bottom - y,
104 }
105 }
106}
107
108#[derive(Clone, Debug)]
110pub(crate) enum Pixels {
111 Half(Vec<[u16; 4]>),
113 Unorm8(Vec<[u8; 4]>),
115}
116
117#[derive(Clone, Debug)]
122pub struct CpuImage {
123 size: RenderSize,
124 bounds: PixelBox,
125 pixels: Pixels,
126 pool: Weak<FramePool>,
128}
129
130impl CpuImage {
131 pub(crate) fn new(size: RenderSize, bounds: PixelBox, pixels: Pixels) -> Self {
132 let bounds = bounds.intersect(PixelBox::whole(size));
133 debug_assert_eq!(
134 match &pixels {
135 Pixels::Half(pixels) => pixels.len(),
136 Pixels::Unorm8(pixels) => pixels.len(),
137 },
138 bounds.pixels()
139 );
140 Self {
141 size,
142 bounds,
143 pixels,
144 pool: Weak::new(),
145 }
146 }
147
148 pub(crate) fn pooled(
150 size: RenderSize,
151 bounds: PixelBox,
152 pixels: Vec<HalfPixel>,
153 pool: &Arc<FramePool>,
154 ) -> Self {
155 let mut image = Self::new(size, bounds, Pixels::Half(pixels));
156 image.pool = Arc::downgrade(pool);
157 image
158 }
159
160 pub(crate) fn transparent(size: RenderSize) -> Self {
162 Self {
163 size,
164 bounds: PixelBox::EMPTY,
165 pixels: Pixels::Half(Vec::new()),
166 pool: Weak::new(),
167 }
168 }
169
170 pub fn from_premultiplied_rgba8(size: RenderSize, rgba8: &[u8]) -> Result<Self, RenderError> {
176 let expected = size.width as usize * size.height as usize * 4;
177 if rgba8.len() != expected {
178 return Err(RenderError::InvalidImageData);
179 }
180 let pixels = rgba8.as_chunks::<4>().0.to_vec();
181 Ok(Self::new(
182 size,
183 PixelBox::whole(size),
184 Pixels::Unorm8(pixels),
185 ))
186 }
187
188 #[must_use]
190 pub const fn size(&self) -> RenderSize {
191 self.size
192 }
193
194 pub(crate) const fn bounds(&self) -> PixelBox {
195 self.bounds
196 }
197
198 pub(crate) const fn pixels(&self) -> &Pixels {
199 &self.pixels
200 }
201
202 #[must_use]
204 pub fn retained_bytes(&self) -> u64 {
205 let pixels = self.bounds.pixels() as u64;
206 match self.pixels {
207 Pixels::Half(_) => pixels * 8,
208 Pixels::Unorm8(_) => pixels * 4,
209 }
210 }
211
212 #[must_use]
214 pub fn read_rgba8(&self) -> Vec<u8> {
215 let width = self.size.width as usize;
216 let mut bytes = vec![0_u8; width * self.size.height as usize * 4];
217 let bounds = self.bounds;
218 for row in 0..bounds.height as usize {
219 let start = ((bounds.y as usize + row) * width + bounds.x as usize) * 4;
220 let out = &mut bytes[start..start + bounds.width as usize * 4];
221 match &self.pixels {
222 Pixels::Half(pixels) => {
223 let line = &pixels[row * bounds.width as usize..][..bounds.width as usize];
224 for (pixel, out) in line.iter().zip(out.as_chunks_mut::<4>().0) {
225 *out = half::load4(*pixel).map(half::unorm8);
226 }
227 }
228 Pixels::Unorm8(pixels) => {
229 let line = &pixels[row * bounds.width as usize..][..bounds.width as usize];
230 out.copy_from_slice(line.as_flattened());
231 }
232 }
233 }
234 bytes
235 }
236
237 #[inline]
239 pub(crate) fn texel(&self, x: u32, y: u32) -> [f32; 4] {
240 let bounds = self.bounds;
241 if !bounds.contains(x, y) {
242 return [0.0; 4];
243 }
244 let index = (y - bounds.y) as usize * bounds.width as usize + (x - bounds.x) as usize;
245 match &self.pixels {
246 Pixels::Half(pixels) => half::load4(pixels[index]),
247 Pixels::Unorm8(pixels) => pixels[index].map(half::unorm8_value),
248 }
249 }
250
251 pub(crate) fn decode_row(&self, y: u32, x0: u32, out: &mut [[f32; 4]]) {
253 let bounds = self.bounds;
254 if y < bounds.y || y >= bounds.bottom() {
255 out.fill([0.0; 4]);
256 return;
257 }
258 let row = (y - bounds.y) as usize * bounds.width as usize;
259 let start = x0.max(bounds.x);
260 #[allow(clippy::cast_possible_truncation)]
261 let end = (x0 + out.len() as u32).min(bounds.right());
262 out.fill([0.0; 4]);
263 if start >= end {
264 return;
265 }
266 let target = &mut out[(start - x0) as usize..(end - x0) as usize];
267 let source = row + (start - bounds.x) as usize..row + (end - bounds.x) as usize;
268 match &self.pixels {
269 Pixels::Half(pixels) => {
270 let table = half::table();
271 for (pixel, bits) in target.iter_mut().zip(&pixels[source]) {
272 *pixel = bits.map(|bits| table[usize::from(bits)]);
273 }
274 }
275 Pixels::Unorm8(pixels) => {
276 for (pixel, codes) in target.iter_mut().zip(&pixels[source]) {
277 *pixel = codes.map(half::unorm8_value);
278 }
279 }
280 }
281 }
282
283 #[inline]
285 pub(crate) fn texel_clamped(&self, x: i64, y: i64) -> [f32; 4] {
286 let last_x = i64::from(self.size.width) - 1;
287 let last_y = i64::from(self.size.height) - 1;
288 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
289 self.texel(x.clamp(0, last_x) as u32, y.clamp(0, last_y) as u32)
290 }
291
292 #[inline]
295 pub(crate) fn sample(&self, u: f32, v: f32) -> [f32; 4] {
296 #[allow(clippy::cast_precision_loss)]
297 let x = u * self.size.width as f32 - 0.5;
298 #[allow(clippy::cast_precision_loss)]
299 let y = v * self.size.height as f32 - 0.5;
300 let x0 = x.floor();
301 let y0 = y.floor();
302 let fx = x - x0;
303 let fy = y - y0;
304 #[allow(clippy::cast_possible_truncation)]
305 let (ix, iy) = (x0 as i64, y0 as i64);
306 let t00 = self.texel_clamped(ix, iy);
307 let t10 = self.texel_clamped(ix + 1, iy);
308 let t01 = self.texel_clamped(ix, iy + 1);
309 let t11 = self.texel_clamped(ix + 1, iy + 1);
310 let mut out = [0.0; 4];
311 for channel in 0..4 {
312 let top = t00[channel] + (t10[channel] - t00[channel]) * fx;
313 let bottom = t01[channel] + (t11[channel] - t01[channel]) * fx;
314 out[channel] = top + (bottom - top) * fy;
315 }
316 out
317 }
318}
319
320impl Drop for CpuImage {
321 fn drop(&mut self) {
322 if let Pixels::Half(pixels) = &mut self.pixels
323 && let Some(pool) = self.pool.upgrade()
324 {
325 pool.give_back(std::mem::take(pixels));
326 }
327 }
328}