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