use std::sync::{Arc, OnceLock};
use glam::{Vec2, Vec4};
use crate::Rgb;
use crate::color::{linear_to_srgb, srgb_to_linear};
#[derive(Clone)]
pub struct Texture {
pub width: u32,
pub height: u32,
levels: Arc<Levels>,
#[cfg(not(target_arch = "wasm32"))]
_image: Option<Arc<usvgr::PreloadedImageData>>,
pub srgb: bool,
pub repeat: Vec2,
pub offset: Vec2,
}
pub(crate) struct Levels {
cpu: OnceLock<Vec<Level>>,
size: (u32, u32),
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
layer: Option<std::sync::Weak<fframes_media::wgpu_layer::WgpuLayer>>,
}
impl Levels {
fn cpu(levels: Vec<Level>) -> Arc<Self> {
Arc::new(Self {
size: (levels[0].width, levels[0].height),
cpu: OnceLock::from(levels),
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
layer: None,
})
}
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
pub(crate) fn layer(&self) -> Option<Arc<fframes_media::wgpu_layer::WgpuLayer>> {
self.layer.as_ref()?.upgrade()
}
}
#[derive(Clone)]
struct Level {
width: u32,
height: u32,
data: Vec<[u8; 4]>,
}
impl std::fmt::Debug for Texture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Texture")
.field("width", &self.width)
.field("height", &self.height)
.field("srgb", &self.srgb)
.field("cpu_levels", &self.levels.cpu.get().map(Vec::len))
.finish_non_exhaustive()
}
}
impl Texture {
pub fn from_rgba8(width: u32, height: u32, rgba: &[u8], srgb: bool) -> Self {
let (width, height) = (width.max(1), height.max(1));
let mut data = rgba.as_chunks::<4>().0.to_vec();
data.resize((width * height) as usize, [0; 4]);
Self::from_level(
Level {
width,
height,
data,
},
srgb,
)
}
pub fn decode(bytes: &[u8]) -> Result<Self, image::ImageError> {
let image = image::load_from_memory(bytes)?.to_rgba8();
Ok(Self::from_rgba8(
image.width(),
image.height(),
image.as_raw(),
true,
))
}
#[cfg(not(target_arch = "wasm32"))]
pub fn from_image(image: &fframes_media::ImageData<'_>) -> Self {
let source = &image.image;
let levels = images::levels(source, || {
#[cfg(feature = "gpu")]
if let Some(layer) = fframes_media::wgpu_layer::resolve_wgpu_layer(source) {
return Arc::new(Levels {
cpu: OnceLock::new(),
layer: Some(Arc::downgrade(&layer)),
size: (layer.texture().width(), layer.texture().height()),
});
}
Levels::cpu(match fframes_media::image_pixels(source) {
Some(pixels) => levels_of_premultiplied(&pixels, true),
None => transparent(),
})
});
Self {
_image: Some(Arc::clone(source)),
..Self::with_levels(levels, true)
}
}
pub fn decode_linear(bytes: &[u8]) -> Result<Self, image::ImageError> {
let image = image::load_from_memory(bytes)?.to_rgba8();
Ok(Self::from_rgba8(
image.width(),
image.height(),
image.as_raw(),
false,
))
}
pub fn from_fn(width: u32, height: u32, f: impl Fn(f32, f32) -> Rgb) -> Self {
Self::from_fn_data(width, height, true, |u, v| {
let [r, g, b] = f(u, v).to_srgb8();
[r, g, b, 255]
})
}
pub fn from_fn_data(
width: u32,
height: u32,
srgb: bool,
f: impl Fn(f32, f32) -> [u8; 4],
) -> Self {
let (width, height) = (width.max(1), height.max(1));
let mut data = Vec::with_capacity((width * height) as usize);
for y in 0..height {
let v = (y as f32 + 0.5) / height as f32;
for x in 0..width {
data.push(f((x as f32 + 0.5) / width as f32, v));
}
}
Self::from_level(
Level {
width,
height,
data,
},
srgb,
)
}
fn from_level(level: Level, srgb: bool) -> Self {
Self::with_levels(Levels::cpu(mip_chain(level, srgb)), srgb)
}
fn with_levels(levels: Arc<Levels>, srgb: bool) -> Self {
Self {
width: levels.size.0,
height: levels.size.1,
levels,
#[cfg(not(target_arch = "wasm32"))]
_image: None,
srgb,
repeat: Vec2::ONE,
offset: Vec2::ZERO,
}
}
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
pub(crate) fn shared_levels(&self) -> &Arc<Levels> {
&self.levels
}
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
pub(crate) fn levels(&self) -> impl Iterator<Item = (u32, u32, &[[u8; 4]])> {
self.cpu_levels()
.iter()
.map(|l| (l.width, l.height, l.data.as_slice()))
}
fn cpu_levels(&self) -> &[Level] {
self.levels.cpu.get_or_init(|| {
#[cfg(all(feature = "gpu", not(target_arch = "wasm32")))]
if let Some(pixels) = self.levels.layer().and_then(|l| l.pixels()) {
return levels_of_premultiplied(&pixels, self.srgb);
}
transparent()
})
}
pub fn repeat(mut self, u: f32, v: f32) -> Self {
self.repeat = Vec2::new(u, v);
self
}
pub fn sample(&self, uv: Vec2) -> Vec4 {
self.sample_lod(uv, 0.0)
}
pub(crate) fn sample_lod(&self, uv: Vec2, lod: f32) -> Vec4 {
let uv = uv * self.repeat + self.offset;
let levels = self.cpu_levels();
let max = (levels.len() - 1) as f32;
let lod = if lod.is_finite() {
lod.clamp(0.0, max)
} else {
0.0
};
let l0 = lod.floor() as usize;
let a = self.bilinear(&levels[l0], uv);
let f = lod - l0 as f32;
if f < 1e-3 || l0 + 1 >= levels.len() {
return a;
}
a.lerp(self.bilinear(&levels[l0 + 1], uv), f)
}
pub(crate) fn sample_grad(&self, uv: Vec2, dx: Vec2, dy: Vec2) -> Vec4 {
const MAX_ANISOTROPY: f32 = 8.0;
let texels = Vec2::new(self.width as f32, self.height as f32) * self.repeat;
let (x, y) = ((dx * texels).length(), (dy * texels).length());
let (major, minor, axis) = if x >= y { (x, y, dx) } else { (y, x, dy) };
let count = (major / minor.max(1e-6)).ceil().clamp(1.0, MAX_ANISOTROPY);
let lod = (major / count).max(1e-6).log2();
if count <= 1.0 || !count.is_finite() {
return self.sample_lod(uv, lod);
}
let sum = (0..count as usize).fold(Vec4::ZERO, |sum, i| {
let t = (i as f32 + 0.5) / count - 0.5;
sum + self.sample_lod(uv + axis * t, lod)
});
sum / count
}
fn bilinear(&self, level: &Level, uv: Vec2) -> Vec4 {
let x = uv.x * level.width as f32 - 0.5;
let y = uv.y * level.height as f32 - 0.5;
let (x0, y0) = (x.floor(), y.floor());
let (fx, fy) = (x - x0, y - y0);
let (w, h) = (level.width as i32, level.height as i32);
let xi = (x0 as i32).rem_euclid(w);
let yi = (y0 as i32).rem_euclid(h);
let xi1 = (xi + 1) % w;
let yi1 = (yi + 1) % h;
let lut = self.srgb.then(srgb_lut);
let texel = |x: i32, y: i32| decode(level.data[(y * w + x) as usize], lut);
let top = texel(xi, yi).lerp(texel(xi1, yi), fx);
let bottom = texel(xi, yi1).lerp(texel(xi1, yi1), fx);
top.lerp(bottom, fy)
}
}
fn transparent() -> Vec<Level> {
vec![Level {
width: 1,
height: 1,
data: vec![[0; 4]],
}]
}
fn mip_chain(level: Level, srgb: bool) -> Vec<Level> {
let mut levels = vec![level];
while let Some(last) = levels.last()
&& (last.width > 1 || last.height > 1)
{
let next = downsample(last, srgb);
levels.push(next);
}
levels
}
#[cfg(not(target_arch = "wasm32"))]
fn levels_of_premultiplied(image: &usvgr::PreloadedImageData, srgb: bool) -> Vec<Level> {
let data = image
.data
.as_chunks::<4>()
.0
.iter()
.map(|&[r, g, b, a]| match a {
0 | 255 => [r, g, b, a],
_ => {
let straight = |c: u8| (u32::from(c) * 255 / u32::from(a)).min(255) as u8;
[straight(r), straight(g), straight(b), a]
}
})
.collect();
mip_chain(
Level {
width: image.width,
height: image.height,
data,
},
srgb,
)
}
#[cfg(not(target_arch = "wasm32"))]
mod images {
use std::num::NonZeroUsize;
use std::sync::{Arc, Mutex, PoisonError, Weak};
use lru::LruCache;
use usvgr::PreloadedImageData;
use super::Levels;
const CAPACITY: NonZeroUsize = NonZeroUsize::new(32).unwrap();
type Entry = (Weak<PreloadedImageData>, Arc<Levels>);
static IMAGES: Mutex<Option<LruCache<usize, Entry>>> = Mutex::new(None);
pub(super) fn levels(
image: &Arc<PreloadedImageData>,
make: impl FnOnce() -> Arc<Levels>,
) -> Arc<Levels> {
let key = Arc::as_ptr(image) as usize;
let found = {
let mut images = IMAGES.lock().unwrap_or_else(PoisonError::into_inner);
let images = images.get_or_insert_with(|| LruCache::new(CAPACITY));
let dead: Vec<usize> = (images.iter())
.filter(|(_, (image, _))| image.strong_count() == 0)
.map(|(key, _)| *key)
.collect();
for key in dead {
images.pop(&key);
}
images.get(&key).map(|(_, levels)| Arc::clone(levels))
};
found.unwrap_or_else(|| {
let levels = make();
(IMAGES.lock().unwrap_or_else(PoisonError::into_inner))
.get_or_insert_with(|| LruCache::new(CAPACITY))
.put(key, (Arc::downgrade(image), Arc::clone(&levels)));
levels
})
}
}
#[inline]
fn decode(c: [u8; 4], lut: Option<&[f32; 256]>) -> Vec4 {
match lut {
Some(lut) => Vec4::new(
lut[c[0] as usize],
lut[c[1] as usize],
lut[c[2] as usize],
f32::from(c[3]) / 255.0,
),
None => Vec4::from_array(c.map(f32::from)) / 255.0,
}
}
fn downsample(level: &Level, srgb: bool) -> Level {
let width = (level.width / 2).max(1);
let height = (level.height / 2).max(1);
let lut = srgb.then(srgb_lut);
let encode = |v: f32, srgb: bool| {
let v = v.clamp(0.0, 1.0);
let v = if srgb { linear_to_srgb(v) } else { v };
(v * 255.0 + 0.5) as u8
};
let mut data = Vec::with_capacity((width * height) as usize);
for y in 0..height {
for x in 0..width {
let mut sum = Vec4::ZERO;
for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] {
let sx = (x * 2 + dx).min(level.width - 1);
let sy = (y * 2 + dy).min(level.height - 1);
sum += decode(level.data[(sy * level.width + sx) as usize], lut);
}
let mean = sum / 4.0;
data.push([
encode(mean.x, srgb),
encode(mean.y, srgb),
encode(mean.z, srgb),
encode(mean.w, false),
]);
}
}
Level {
width,
height,
data,
}
}
fn srgb_lut() -> &'static [f32; 256] {
static LUT: OnceLock<[f32; 256]> = OnceLock::new();
LUT.get_or_init(|| std::array::from_fn(|i| srgb_to_linear(i as f32 / 255.0)))
}