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