1use crate::color::srgb8_to_linear;
14
15#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
17pub enum Error {
18 #[error("invalid image: {0}")]
20 Invalid(String),
21}
22
23pub type Result<T> = std::result::Result<T, Error>;
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum Address {
29 Wrap,
31 Clamp,
33 Mirror,
35 MirrorOnce,
37 Border,
39}
40
41impl Address {
42 fn index(self, i: i64, size: u32) -> Option<usize> {
44 let n = i64::from(size.max(1));
45 let (last, period) = (n.saturating_sub(1), n.saturating_mul(2));
46 let at = match self {
47 Address::Wrap => i.rem_euclid(n),
48 Address::Clamp => i.clamp(0, last),
49 Address::Mirror => {
51 let m = i.rem_euclid(period);
52 if m < n {
53 m
54 } else {
55 period.saturating_sub(1).saturating_sub(m)
56 }
57 }
58 Address::MirrorOnce => (if i < 0 { (-1i64).saturating_sub(i) } else { i }).clamp(0, last),
60 Address::Border if (0..n).contains(&i) => i,
61 Address::Border => return None,
62 };
63 Some(usize::try_from(at).unwrap_or_default())
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
72pub struct Addressing<const N: usize> {
73 pub axes: [Address; N],
75 pub border: Option<[f32; 4]>,
78}
79
80impl<const N: usize> From<[Address; N]> for Addressing<N> {
81 fn from(axes: [Address; N]) -> Addressing<N> {
82 Addressing { axes, border: None }
83 }
84}
85
86impl<const N: usize> Addressing<N> {
87 pub fn bordered(axes: [Address; N], border: [f32; 4]) -> Addressing<N> {
89 Addressing {
90 axes,
91 border: Some(border),
92 }
93 }
94
95 fn border_colour(&self) -> Result<[f32; 4]> {
98 match self.border {
99 Some(c) if c.iter().any(|v| !v.is_finite()) => Err(Error::Invalid(format!(
100 "sampled texture: border colour {c:?} is not finite"
101 ))),
102 Some(c) => Ok(c),
103 None if self.axes.contains(&Address::Border) => Err(Error::Invalid(
104 "sampled texture: border addressing with no border colour".into(),
105 )),
106 None => Ok([0.0; 4]),
107 }
108 }
109
110 fn indices(&self, at: [i64; N], size: [u32; N]) -> Option<[usize; N]> {
113 let mut out = [0usize; N];
114 for (o, ((a, i), n)) in out.iter_mut().zip(self.axes.iter().zip(at).zip(size)) {
115 *o = a.index(i, n)?;
116 }
117 Some(out)
118 }
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub enum Transfer {
124 Stored,
126 SrgbColour,
128}
129
130impl Transfer {
131 pub fn channel(self, c: usize, byte: u8) -> f64 {
133 match self {
134 Transfer::SrgbColour if c < 3 => srgb8_to_linear(byte),
135 _ => f64::from(byte) / 255.0,
136 }
137 }
138}
139
140#[derive(Clone, Debug)]
143pub struct Levels {
144 pub width: u32,
146 pub height: u32,
148 pub levels: Vec<Vec<u8>>,
151}
152
153impl Levels {
154 pub fn one(width: u32, height: u32, rgba: Vec<u8>) -> Result<Levels> {
156 let count = (width as usize)
157 .checked_mul(height as usize)
158 .filter(|&n| n > 0)
159 .ok_or_else(|| Error::Invalid("sampled texture: invalid dimensions".into()))?;
160 if count.checked_mul(4) != Some(rgba.len()) {
161 return Err(Error::Invalid("sampled texture: RGBA length mismatch".into()));
162 }
163 Ok(Levels {
164 width,
165 height,
166 levels: vec![rgba],
167 })
168 }
169
170 pub fn rows_reversed(mut self) -> Result<Levels> {
173 for i in 0..self.levels.len() {
174 let row = self.level(i)?.row_bytes;
175 let Some(level) = self.levels.get_mut(i) else {
176 continue;
177 };
178 *level = level.chunks_exact(row).rev().flatten().copied().collect();
179 }
180 Ok(self)
181 }
182
183 pub fn size(&self, level: usize) -> (u32, u32) {
185 let s = u32::try_from(level).unwrap_or(31);
186 (
187 (self.width >> s.min(31)).max(1),
188 (self.height >> s.min(31)).max(1),
189 )
190 }
191
192 fn level(&self, level: usize) -> Result<Level<'_>> {
194 let (w, h) = self.size(level);
195 let bytes = self.levels.get(level).ok_or_else(|| {
196 Error::Invalid(format!(
197 "sampled texture: level {level} requested, expected one of the {} stored levels",
198 self.levels.len()
199 ))
200 })?;
201 let row_bytes = (w as usize).checked_mul(4);
202 let want = row_bytes.and_then(|r| r.checked_mul(h as usize));
203 match (row_bytes, want) {
204 (Some(row_bytes), Some(want)) if want == bytes.len() && want > 0 => Ok(Level {
205 bytes,
206 row_bytes,
207 width: w,
208 height: h,
209 }),
210 _ => Err(Error::Invalid(format!(
211 "sampled texture: level {level} holds {} bytes, expected {h} rows of {w} RGBA8 texels",
212 bytes.len()
213 ))),
214 }
215 }
216
217 pub fn texel(&self, level: usize, x: i64, y: i64, address: [Address; 2]) -> Result<[u8; 4]> {
220 self.level(level)?
221 .texel(x, y, &Addressing::from(address))?
222 .ok_or_else(|| {
223 Error::Invalid(format!(
224 "sampled texture: texel ({x}, {y}) is outside a border axis"
225 ))
226 })
227 }
228
229 pub fn bilinear(
233 &self,
234 level: usize,
235 uv: [f64; 2],
236 address: impl Into<Addressing<2>>,
237 transfer: Transfer,
238 ) -> Result<[f64; 4]> {
239 let address: Addressing<2> = address.into();
240 let border = address.border_colour()?;
241 let (w, h) = self.size(level);
242 let level = self.level(level)?;
243 let (x, y) = texel_coordinate([uv[0] * f64::from(w) - 0.5, uv[1] * f64::from(h) - 0.5])?;
244 let (fx, fy) = (x.floor(), y.floor());
245 let (tx, ty) = (x - fx, y - fy);
246 let (ix, iy) = (floor_f64(fx), floor_f64(fy));
247 let mut out = [0.0; 4];
248 for (dx, dy, wt) in [
249 (0, 0, (1.0 - tx) * (1.0 - ty)),
250 (1, 0, tx * (1.0 - ty)),
251 (0, 1, (1.0 - tx) * ty),
252 (1, 1, tx * ty),
253 ] {
254 let t = level.texel(ix.saturating_add(dx), iy.saturating_add(dy), &address)?;
255 for (c, o) in out.iter_mut().enumerate() {
256 *o += wt * texel_channel(t, c, transfer, border);
257 }
258 }
259 Ok(out)
260 }
261
262 pub fn point(
266 &self,
267 level: usize,
268 uv: [f64; 2],
269 address: impl Into<Addressing<2>>,
270 transfer: Transfer,
271 ) -> Result<[f64; 4]> {
272 let address: Addressing<2> = address.into();
273 let border = address.border_colour()?;
274 let (w, h) = self.size(level);
275 let level = self.level(level)?;
276 let (x, y) = texel_coordinate([uv[0] * f64::from(w), uv[1] * f64::from(h)])?;
277 let t = level.texel(floor_f64(x), floor_f64(y), &address)?;
278 Ok(std::array::from_fn(|c| texel_channel(t, c, transfer, border)))
279 }
280
281 pub fn decoded(&self, level: usize, transfer: Transfer) -> Result<Decoded> {
284 let l = self.level(level)?;
285 let texels = l
286 .bytes
287 .as_chunks::<4>()
288 .0
289 .iter()
290 .map(|p| {
291 let mut texel = [0f32; 4];
292 for (c, (out, &byte)) in texel.iter_mut().zip(p).enumerate() {
293 *out = match transfer {
294 Transfer::SrgbColour if c < 3 => to_f32(srgb8_to_linear(byte)),
295 _ => f32::from(byte) / 255.0,
296 };
297 }
298 texel
299 })
300 .collect();
301 Ok(Decoded {
302 width: l.width,
303 height: l.height,
304 depth: 1,
305 texels,
306 })
307 }
308}
309
310struct Level<'a> {
312 bytes: &'a [u8],
313 row_bytes: usize,
314 width: u32,
315 height: u32,
316}
317
318fn texel_channel(texel: Option<[u8; 4]>, c: usize, transfer: Transfer, border: [f32; 4]) -> f64 {
320 match texel {
321 Some(t) => transfer.channel(c, t.get(c).copied().unwrap_or_default()),
322 None => f64::from(border.get(c).copied().unwrap_or_default()),
323 }
324}
325
326impl Level<'_> {
327 fn texel(&self, x: i64, y: i64, address: &Addressing<2>) -> Result<Option<[u8; 4]>> {
329 let Some([x, y]) = address.indices([x, y], [self.width, self.height]) else {
330 return Ok(None);
331 };
332 let i = y
333 .checked_mul(self.row_bytes)
334 .and_then(|row| row.checked_add(x.checked_mul(4)?));
335 i.and_then(|i| self.bytes.get(i..)?.first_chunk::<4>())
336 .copied()
337 .map(Some)
338 .ok_or_else(|| {
339 Error::Invalid(format!(
340 "sampled texture: texel ({x}, {y}) outside the level's {} bytes",
341 self.bytes.len()
342 ))
343 })
344 }
345}
346
347fn texel_coordinate([x, y]: [f64; 2]) -> Result<(f64, f64)> {
349 if !x.is_finite() || !y.is_finite() || x.abs() >= 2147483520.0 || y.abs() >= 2147483520.0 {
350 return Err(Error::Invalid(
351 "sampled texture: nonfinite or excessive texel coordinate".into(),
352 ));
353 }
354 Ok((x, y))
355}
356
357pub fn footprint_lod(width: u32, height: u32, gradients: [[f64; 2]; 2], bias: f64) -> f64 {
361 let texels = |d: [f64; 2]| (d[0] * f64::from(width)).hypot(d[1] * f64::from(height));
362 texels(gradients[0]).max(texels(gradients[1])).log2() + bias
363}
364
365impl Levels {
366 pub fn trilinear(
370 &self,
371 uv: [f64; 2],
372 lod: f64,
373 address: impl Into<Addressing<2>>,
374 transfer: Transfer,
375 ) -> Result<[f64; 4]> {
376 let address: Addressing<2> = address.into();
377 if lod.is_nan() {
378 return Err(Error::Invalid(
379 "sampled texture: a level of detail that isn't a number".into(),
380 ));
381 }
382 let last = self.levels.len().saturating_sub(1);
383 let lod = lod.clamp(0.0, last as f64);
384 let below = lod.floor();
385 let (l0, f) = (level_number(below), lod - below);
386 let a = self.bilinear(l0, uv, address, transfer)?;
387 if f == 0.0 {
388 return Ok(a);
389 }
390 let b = self.bilinear(l0.saturating_add(1).min(last), uv, address, transfer)?;
391 let mut mixed = [0.0; 4];
392 for (m, (a, b)) in mixed.iter_mut().zip(a.into_iter().zip(b)) {
393 *m = a + (b - a) * f;
394 }
395 Ok(mixed)
396 }
397
398 pub fn nearest_level(&self, lod: f64) -> Result<usize> {
401 if lod.is_nan() {
402 return Err(Error::Invalid(
403 "sampled texture: a level of detail that isn't a number".into(),
404 ));
405 }
406 let last = self.levels.len().saturating_sub(1);
407 Ok(level_number((lod + 0.5).floor().clamp(0.0, last as f64)))
408 }
409}
410
411#[derive(Clone, Copy, Debug, PartialEq, Eq)]
413pub enum Subtexel {
414 Exact,
416 D3d11,
422}
423
424impl Subtexel {
425 fn resolve(self, x: f32) -> f32 {
427 match self {
428 Subtexel::Exact => x,
429 Subtexel::D3d11 => (x * 256.).floor() / 256.,
430 }
431 }
432}
433
434#[derive(Clone, Debug)]
437pub struct Decoded {
438 width: u32,
439 height: u32,
440 depth: u32,
441 texels: Vec<[f32; 4]>,
442}
443
444fn texel_coordinate_f32(x: f32) -> Result<f32> {
446 if !x.is_finite() || x.abs() >= 2147483520.0 {
447 return Err(Error::Invalid(
448 "sampled texture: nonfinite or excessive texel coordinate".into(),
449 ));
450 }
451 Ok(x)
452}
453
454impl Decoded {
455 pub fn from_texels(width: u32, height: u32, texels: Vec<[f32; 4]>) -> Result<Decoded> {
458 Decoded::volume(width, height, 1, texels)
459 }
460
461 pub fn volume(width: u32, height: u32, depth: u32, texels: Vec<[f32; 4]>) -> Result<Decoded> {
464 let count = (width as usize)
465 .checked_mul(height as usize)
466 .and_then(|n| n.checked_mul(depth as usize))
467 .filter(|&n| n > 0)
468 .ok_or_else(|| Error::Invalid("sampled texture: invalid dimensions".into()))?;
469 if texels.len() != count {
470 return Err(Error::Invalid(format!(
471 "sampled texture: {} texels for {width} x {height} x {depth}",
472 texels.len()
473 )));
474 }
475 Ok(Decoded {
476 width,
477 height,
478 depth,
479 texels,
480 })
481 }
482
483 pub fn dimensions(&self) -> [usize; 2] {
485 [self.width as usize, self.height as usize]
486 }
487 pub fn depth(&self) -> usize {
489 self.depth as usize
490 }
491 pub fn decoded_bytes(&self) -> usize {
493 self.texels.len().saturating_mul(16)
494 }
495
496 fn get(&self, x: usize, y: usize, z: usize) -> Option<[f32; 4]> {
498 let index = z
499 .checked_mul(self.height as usize)?
500 .checked_add(y)?
501 .checked_mul(self.width as usize)?
502 .checked_add(x)?;
503 self.texels.get(index).copied()
504 }
505
506 pub fn texel(&self, x: i64, y: i64, z: i64) -> Option<[f32; 4]> {
508 let inside = |i: i64, n: u32| usize::try_from(i).ok().filter(|&i| i < n as usize);
509 self.get(
510 inside(x, self.width)?,
511 inside(y, self.height)?,
512 inside(z, self.depth)?,
513 )
514 }
515
516 fn at(&self, x: usize, y: usize, z: usize) -> Result<[f32; 4]> {
519 self.get(x, y, z).ok_or_else(|| {
520 Error::Invalid(format!(
521 "sampled texture: texel ({x}, {y}, {z}) outside the {} texels of a {} x {} x {} level",
522 self.texels.len(),
523 self.width,
524 self.height,
525 self.depth
526 ))
527 })
528 }
529
530 fn plane(&self) -> Result<()> {
532 if self.depth == 1 {
533 Ok(())
534 } else {
535 Err(Error::Invalid(format!(
536 "sampled texture: a 2D sample of a volume of {} slices",
537 self.depth
538 )))
539 }
540 }
541
542 pub fn bilinear(&self, uv: [f32; 2], address: impl Into<Addressing<2>>) -> Result<[f32; 4]> {
546 self.bilinear_at(uv, address, Subtexel::Exact)
547 }
548 pub fn bilinear_at(
550 &self,
551 uv: [f32; 2],
552 address: impl Into<Addressing<2>>,
553 subtexel: Subtexel,
554 ) -> Result<[f32; 4]> {
555 let address: Addressing<2> = address.into();
556 let border = address.border_colour()?;
557 self.plane()?;
558 let [u, v] = uv;
559 let x = texel_coordinate_f32(subtexel.resolve(u * self.width as f32 - 0.5))?;
560 let y = texel_coordinate_f32(subtexel.resolve(v * self.height as f32 - 0.5))?;
561 let (ix, iy) = (floor_f32(x), floor_f32(y));
562 let (tx, ty) = (x - ix as f32, y - iy as f32);
563 let mut result = [0.; 4];
564 for dy in 0..2 {
565 for dx in 0..2 {
566 let weight = (if dx == 1 { tx } else { 1. - tx }) * (if dy == 1 { ty } else { 1. - ty });
567 let texel = match address.indices(
568 [ix.saturating_add(dx), iy.saturating_add(dy)],
569 [self.width, self.height],
570 ) {
571 Some([xx, yy]) => self.at(xx, yy, 0)?,
572 None => border,
573 };
574 for (out, t) in result.iter_mut().zip(texel) {
575 *out += t * weight;
576 }
577 }
578 }
579 Ok(result)
580 }
581
582 pub fn point(&self, uv: [f32; 2], address: impl Into<Addressing<2>>) -> Result<[f32; 4]> {
586 let address: Addressing<2> = address.into();
587 let border = address.border_colour()?;
588 self.plane()?;
589 let [u, v] = uv;
590 let x = texel_coordinate_f32(u * self.width as f32)?;
591 let y = texel_coordinate_f32(v * self.height as f32)?;
592 match address.indices([floor_f32(x), floor_f32(y)], [self.width, self.height]) {
593 Some([xx, yy]) => self.at(xx, yy, 0),
594 None => Ok(border),
595 }
596 }
597
598 pub fn point_3d(&self, uvw: [f32; 3], address: impl Into<Addressing<3>>) -> Result<[f32; 4]> {
601 let address: Addressing<3> = address.into();
602 let border = address.border_colour()?;
603 let [u, v, w] = uvw;
604 let axis = |c: f32, n: u32| -> Result<i64> { Ok(floor_f32(texel_coordinate_f32(c * n as f32)?)) };
605 let at = [axis(u, self.width)?, axis(v, self.height)?, axis(w, self.depth)?];
606 match address.indices(at, [self.width, self.height, self.depth]) {
607 Some([x, y, z]) => self.at(x, y, z),
608 None => Ok(border),
609 }
610 }
611
612 pub fn linear_3d(
617 &self,
618 uvw: [f32; 3],
619 address: impl Into<Addressing<3>>,
620 subtexel: Subtexel,
621 ) -> Result<[f32; 4]> {
622 let address: Addressing<3> = address.into();
623 let border = address.border_colour()?;
624 let [u, v, w] = uvw;
625 let (sw, sh, sd) = (self.width, self.height, self.depth);
626 let axis = |c: f32, n: u32| -> Result<(i64, f32)> {
627 let t = texel_coordinate_f32(subtexel.resolve(c * n as f32 - 0.5))?;
628 let base = floor_f32(t);
629 Ok((base, t - base as f32))
630 };
631 let ((bx, tx), (by, ty), (bz, tz)) = (axis(u, sw)?, axis(v, sh)?, axis(w, sd)?);
632 let weight = |d: i64, t: f32| if d == 1 { t } else { 1. - t };
633 let mut result = [0.; 4];
634 for dz in 0..2 {
635 for dy in 0..2 {
636 for dx in 0..2 {
637 let texel = match address.indices(
638 [
639 bx.saturating_add(dx),
640 by.saturating_add(dy),
641 bz.saturating_add(dz),
642 ],
643 [sw, sh, sd],
644 ) {
645 Some([x, y, z]) => self.at(x, y, z)?,
646 None => border,
647 };
648 let wt = weight(dx, tx) * weight(dy, ty) * weight(dz, tz);
649 for (out, t) in result.iter_mut().zip(texel) {
650 *out += t * wt;
651 }
652 }
653 }
654 }
655 Ok(result)
656 }
657}
658
659#[allow(
661 clippy::cast_possible_truncation,
662 reason = "the caller checked the coordinate is finite and within an i32's range"
663)]
664fn floor_f32(x: f32) -> i64 {
665 x.floor() as i64
666}
667
668#[allow(
670 clippy::cast_possible_truncation,
671 reason = "the caller checked the coordinate is finite and within an i32's range"
672)]
673fn floor_f64(x: f64) -> i64 {
674 x.floor() as i64
675}
676
677#[allow(
679 clippy::cast_possible_truncation,
680 reason = "rounding the f64 curve value to the f32 texel is the documented decode"
681)]
682fn to_f32(x: f64) -> f32 {
683 x as f32
684}
685
686#[allow(
688 clippy::cast_possible_truncation,
689 reason = "the caller clamped the value to a stored level number"
690)]
691fn level_number(lod: f64) -> usize {
692 lod as usize
693}
694
695#[cfg(test)]
696#[allow(clippy::unwrap_used, reason = "test fixtures")]
697mod tests {
698 use super::*;
699
700 #[test]
703 fn addressing_out_of_range() {
704 let read = |a: Address, from: i64, to: i64| -> Vec<usize> {
705 (from..=to).map(|i| a.index(i, 4).unwrap()).collect()
706 };
707 assert_eq!(read(Address::Wrap, -5, 5), [3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1]);
708 assert_eq!(
709 read(Address::Mirror, -5, 8),
710 [3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 2, 1, 0, 0]
711 );
712 assert_eq!(read(Address::Clamp, -2, 5), [0, 0, 0, 1, 2, 3, 3, 3]);
713 assert_eq!(
714 read(Address::MirrorOnce, -5, 5),
715 [3, 3, 2, 1, 0, 0, 1, 2, 3, 3, 3]
716 );
717 let border: Vec<Option<usize>> = (-2..=5).map(|i| Address::Border.index(i, 4)).collect();
719 assert_eq!(
720 border,
721 [None, None, Some(0), Some(1), Some(2), Some(3), None, None]
722 );
723 }
724
725 #[test]
730 fn border_addressing_reads_the_stated_colour() {
731 let l = Levels::one(2, 1, vec![0, 0, 0, 255, 255, 255, 255, 255]).unwrap();
732 let axes = [Address::Border, Address::Clamp];
733 let (zero, one) = (
734 Addressing::bordered(axes, [0.0; 4]),
735 Addressing::bordered(axes, [1.0; 4]),
736 );
737 assert_eq!(
739 l.point(0, [-0.25, 0.5], zero, Transfer::Stored).unwrap(),
740 [0.0; 4]
741 );
742 assert_eq!(l.point(0, [-0.25, 0.5], one, Transfer::Stored).unwrap(), [1.0; 4]);
743 assert_eq!(
744 l.point(0, [0.25, 0.5], one, Transfer::Stored).unwrap(),
745 [0.0, 0.0, 0.0, 1.0]
746 );
747 assert_eq!(
749 l.bilinear(0, [0.0, 0.5], one, Transfer::Stored).unwrap(),
750 [0.5, 0.5, 0.5, 1.0]
751 );
752 assert_eq!(
753 l.bilinear(0, [0.0, 0.5], zero, Transfer::Stored).unwrap(),
754 [0.0, 0.0, 0.0, 0.5]
755 );
756 let grey = Addressing::bordered(axes, [0.5; 4]);
758 assert_eq!(
759 l.point(0, [-0.25, 0.5], grey, Transfer::SrgbColour).unwrap(),
760 [0.5; 4]
761 );
762 assert_eq!(
763 l.trilinear([-1.0, 0.5], 0.0, one, Transfer::Stored).unwrap(),
764 [1.0; 4]
765 );
766 let d = l.decoded(0, Transfer::Stored).unwrap();
768 assert_eq!(d.point([1.5, 0.5], one).unwrap(), [1.0; 4]);
769 assert_eq!(d.bilinear([0.0, 0.5], one).unwrap(), [0.5, 0.5, 0.5, 1.0]);
770 assert_eq!(d.bilinear([0.0, 0.5], zero).unwrap(), [0.0, 0.0, 0.0, 0.5]);
771 let v = Decoded::volume(1, 1, 2, vec![[0.25; 4], [0.75; 4]]).unwrap();
772 let volume = Addressing::bordered([Address::Clamp, Address::Clamp, Address::Border], [1.0; 4]);
773 assert_eq!(v.point_3d([0.5, 0.5, 1.5], volume).unwrap(), [1.0; 4]);
774 assert_eq!(v.point_3d([0.5, 0.5, 0.25], volume).unwrap(), [0.25; 4]);
775 assert_eq!(
777 v.linear_3d([0.5, 0.5, 1.0], volume, Subtexel::Exact).unwrap(),
778 [0.875; 4]
779 );
780 assert!(l.point(0, [0.25, 0.5], axes, Transfer::Stored).is_err());
782 assert!(d.bilinear([0.5, 0.5], axes).is_err());
783 let nan = Addressing::bordered(axes, [f32::NAN, 0.0, 0.0, 0.0]);
784 assert!(l.bilinear(0, [0.5, 0.5], nan, Transfer::Stored).is_err());
785 assert!(l.texel(0, -1, 0, axes).is_err());
786 assert_eq!(l.texel(0, 1, 0, axes).unwrap(), [255; 4]);
787 assert_eq!(
789 Addressing::from([Address::Wrap; 2]),
790 Addressing {
791 axes: [Address::Wrap; 2],
792 border: None
793 }
794 );
795 }
796
797 #[test]
801 fn d3d11_subtexel_precision_truncates_to_eight_bits() {
802 let d = Levels::one(2, 1, vec![0, 0, 0, 255, 255, 255, 255, 255])
803 .unwrap()
804 .decoded(0, Transfer::Stored)
805 .unwrap();
806 let clamp = [Address::Clamp; 2];
807 let u = 0.4f32;
809 let exact = d.bilinear_at([u, 0.5], clamp, Subtexel::Exact).unwrap()[0];
810 let d3d = d.bilinear_at([u, 0.5], clamp, Subtexel::D3d11).unwrap()[0];
811 assert!((exact - 0.3).abs() < 1e-6, "{exact}");
812 assert_eq!(d3d, 76. / 256.);
813 let on_step = (64. / 256. + 0.5) / 2.;
814 assert_eq!(
815 d.bilinear_at([on_step, 0.5], clamp, Subtexel::D3d11).unwrap(),
816 d.bilinear_at([on_step, 0.5], clamp, Subtexel::Exact).unwrap()
817 );
818 }
819
820 #[test]
824 fn bilinear_between_texel_centres() {
825 let l = Levels::one(2, 1, vec![0, 0, 0, 0, 255, 255, 255, 255]).unwrap();
826 let wrap = [Address::Wrap; 2];
827 let clamp = [Address::Clamp; 2];
828 assert_eq!(
829 l.bilinear(0, [0.5, 0.5], wrap, Transfer::Stored).unwrap(),
830 [0.5; 4]
831 );
832 assert_eq!(
833 l.bilinear(0, [0.25, 0.5], wrap, Transfer::Stored).unwrap(),
834 [0.0; 4]
835 );
836 assert_eq!(
837 l.bilinear(0, [0.0, 0.5], wrap, Transfer::Stored).unwrap(),
838 [0.5; 4]
839 );
840 assert_eq!(
841 l.bilinear(0, [0.0, 0.5], clamp, Transfer::Stored).unwrap(),
842 [0.0; 4]
843 );
844 let d = l.decoded(0, Transfer::SrgbColour).unwrap();
845 let half = d.bilinear([0.5, 0.5], wrap).unwrap();
846 assert!(half.iter().all(|c| (c - 0.5).abs() < 1e-6), "{half:?}");
847 let g = Levels::one(1, 1, vec![128, 128, 128, 128]).unwrap();
849 let c = g.bilinear(0, [0.5, 0.5], wrap, Transfer::SrgbColour).unwrap();
850 assert!(
851 (c[0] - crate::color::srgb_to_linear(128.0 / 255.0)).abs() < 1e-15
852 && (c[3] - 128.0 / 255.0).abs() < 1e-15
853 );
854 }
855
856 #[test]
860 fn trilinear_at_a_footprint() {
861 assert_eq!(
862 footprint_lod(8, 4, [[1.0 / 8.0, 0.0], [0.0, 1.0 / 4.0]], 0.0),
863 0.0
864 );
865 assert_eq!(footprint_lod(8, 4, [[0.0, 0.0], [0.0, 1.0]], 0.0), 2.0);
866 assert_eq!(footprint_lod(8, 4, [[0.0; 2]; 2], 3.0), f64::NEG_INFINITY);
867 let l = Levels {
868 width: 2,
869 height: 2,
870 levels: vec![vec![0; 16], vec![255; 4]],
871 };
872 let wrap = [Address::Wrap; 2];
873 let at = |lod| l.trilinear([0.5, 0.5], lod, wrap, Transfer::Stored).unwrap()[0];
874 assert_eq!((at(-1.0), at(0.25), at(1.0), at(7.0)), (0.0, 0.25, 1.0, 1.0));
875 assert!(l.trilinear([0.5, 0.5], f64::NAN, wrap, Transfer::Stored).is_err());
876 }
877
878 #[test]
881 fn point_filtering_nearest_levels_and_reversed_rows() {
882 let l = Levels {
884 width: 2,
885 height: 2,
886 levels: vec![[[0; 4], [255; 4], [128; 4], [255; 4]].concat(), vec![64; 4]],
887 };
888 let (wrap, clamp) = ([Address::Wrap; 2], [Address::Clamp; 2]);
889 let at = |l: &Levels, uv: [f64; 2], a| l.point(0, uv, a, Transfer::Stored).unwrap()[0];
890 assert_eq!(at(&l, [0.25, 0.25], wrap), 0.0);
891 assert_eq!(at(&l, [0.5, 0.25], wrap), 1.0);
892 assert_eq!(at(&l, [0.25, 0.75], wrap), 128.0 / 255.0);
893 assert_eq!(
895 (at(&l, [-0.25, 0.25], wrap), at(&l, [-0.25, 0.25], clamp)),
896 (1.0, 0.0)
897 );
898 let c = l.point(0, [0.25, 0.75], wrap, Transfer::SrgbColour).unwrap();
899 assert_eq!(c[0], crate::color::srgb8_to_linear(128));
900 assert!(l.point(0, [f64::NAN, 0.0], wrap, Transfer::Stored).is_err());
901 assert_eq!(
902 [-3.0, 0.49, 0.5, 1.2, 9.0].map(|lod| l.nearest_level(lod).unwrap()),
903 [0, 0, 1, 1, 1]
904 );
905 assert!(l.nearest_level(f64::NAN).is_err());
906 let up = l.clone().rows_reversed().unwrap();
907 assert_eq!(at(&up, [0.25, 0.25], wrap), 128.0 / 255.0);
908 assert_eq!(at(&up, [0.25, 0.75], wrap), 0.0);
909 assert_eq!(up.levels[1], vec![64; 4]);
910 let short = Levels {
911 width: 2,
912 height: 2,
913 levels: vec![vec![0; 12]],
914 };
915 assert!(short.rows_reversed().is_err());
916 }
917
918 #[test]
922 fn float_texels_point_and_bilinear() {
923 let red_green = vec![
924 [0.0, 0.0, 0.0, 1.0],
925 [1.0, 0.0, 0.0, 1.0],
926 [0.0, 1.0, 0.0, 1.0],
927 [1.0, 1.0, 0.0, 1.0],
928 ];
929 let d = Decoded::from_texels(2, 2, red_green.clone()).unwrap();
930 let (wrap, clamp) = ([Address::Wrap; 2], [Address::Clamp; 2]);
931 assert_eq!(d.point([0.25, 0.25], clamp).unwrap(), [0.0, 0.0, 0.0, 1.0]);
932 assert_eq!(d.point([0.75, 0.25], clamp).unwrap(), [1.0, 0.0, 0.0, 1.0]);
933 assert_eq!(d.point([0.25, 0.75], clamp).unwrap(), [0.0, 1.0, 0.0, 1.0]);
934 assert_eq!(d.point([1.75, -3.0], clamp).unwrap(), [1.0, 0.0, 0.0, 1.0]);
936 assert_eq!(d.point([1.25, 0.25], wrap).unwrap(), [0.0, 0.0, 0.0, 1.0]);
937 assert_eq!(d.point([-0.25, 0.25], wrap).unwrap(), [1.0, 0.0, 0.0, 1.0]);
938 assert_eq!(d.bilinear([0.5, 0.5], clamp).unwrap(), [0.5, 0.5, 0.0, 1.0]);
939 assert_eq!(d.bilinear([0.375, 0.25], clamp).unwrap(), [0.25, 0.0, 0.0, 1.0]);
940 let hdr = Decoded::from_texels(2, 1, vec![[-2.0; 4], [6.0; 4]]).unwrap();
942 assert_eq!(hdr.bilinear([0.5, 0.5], clamp).unwrap(), [2.0; 4]);
943 assert!(Decoded::from_texels(2, 2, red_green[..3].to_vec()).is_err());
944 assert!(Decoded::from_texels(0, 2, Vec::new()).is_err());
945 assert!(d.point([f32::NAN, 0.5], clamp).is_err());
946 let volume = Decoded::volume(1, 1, 2, vec![[0.0; 4], [1.0; 4]]).unwrap();
947 assert!(volume.point([0.5, 0.5], clamp).is_err());
948 assert!(volume.bilinear([0.5, 0.5], clamp).is_err());
949 }
950
951 #[test]
955 fn volumes_point_linear_and_texels() {
956 let texels: Vec<[f32; 4]> = (0..8)
957 .map(|i| [(i % 2) as f32, (i / 2 % 2) as f32, (i / 4) as f32, 1.0])
958 .collect();
959 let v = Decoded::volume(2, 2, 2, texels).unwrap();
960 assert_eq!(v.depth(), 2);
961 let (wrap, clamp) = ([Address::Wrap; 3], [Address::Clamp; 3]);
962 assert_eq!(
963 v.point_3d([0.75, 0.25, 0.75], clamp).unwrap(),
964 [1.0, 0.0, 1.0, 1.0]
965 );
966 assert_eq!(
967 v.point_3d([0.25, 1.25, -0.25], wrap).unwrap(),
968 [0.0, 0.0, 1.0, 1.0]
969 );
970 let exact = Subtexel::Exact;
972 assert_eq!(v.linear_3d([0.5; 3], clamp, exact).unwrap(), [0.5, 0.5, 0.5, 1.0]);
973 assert_eq!(
974 v.linear_3d([0.375, 0.25, 0.25], clamp, exact).unwrap(),
975 [0.25, 0.0, 0.0, 1.0]
976 );
977 assert_eq!(
978 v.linear_3d([0.25, 0.25, 0.75], clamp, exact).unwrap(),
979 [0.0, 0.0, 1.0, 1.0]
980 );
981 assert_eq!(v.linear_3d([0.0, 0.25, 0.25], clamp, exact).unwrap()[0], 0.0);
983 assert_eq!(v.linear_3d([0.0, 0.25, 0.25], wrap, exact).unwrap()[0], 0.5);
984 assert!(v.linear_3d([0.5, f32::INFINITY, 0.5], clamp, exact).is_err());
985 assert_eq!(v.texel(1, 0, 1), Some([1.0, 0.0, 1.0, 1.0]));
986 assert_eq!(v.texel(2, 0, 0), None);
987 assert_eq!(v.texel(0, -1, 0), None);
988 assert_eq!(v.texel(0, 0, 2), None);
989 let d = Decoded::from_texels(1, 1, vec![[3.0; 4]]).unwrap();
991 assert_eq!(d.point_3d([0.5; 3], clamp).unwrap(), [3.0; 4]);
992 assert_eq!(d.texel(0, 0, 0), Some([3.0; 4]));
993 assert_eq!(d.texel(0, 0, 1), None);
994 }
995
996 #[test]
999 fn nonfinite_or_excessive_coordinates_are_refused() {
1000 let l = Levels::one(2, 2, vec![10; 16]).unwrap();
1001 let wrap = [Address::Wrap; 2];
1002 for uv in [[f64::NAN, 0.5], [0.5, f64::INFINITY], [1e300, 0.5], [0.5, -1e12]] {
1003 assert!(l.bilinear(0, uv, wrap, Transfer::Stored).is_err(), "{uv:?}");
1004 assert!(l.trilinear(uv, 0.0, wrap, Transfer::Stored).is_err(), "{uv:?}");
1005 assert!(
1006 l.decoded(0, Transfer::Stored)
1007 .unwrap()
1008 .bilinear(uv.map(|v| v as f32), wrap)
1009 .is_err(),
1010 "{uv:?}"
1011 );
1012 }
1013 assert!(l.trilinear([1e8, -1e8], 0.0, wrap, Transfer::Stored).is_ok());
1014 }
1015}