opendefocus_shared/
cpu_image.rs1use glam::{UVec2, Vec2, Vec4};
2
3use image::{GenericImageView, ImageBuffer, Pixel, imageops::interpolate_bilinear};
4
5use crate::math::mix_vec;
6
7#[derive(PartialEq)]
8pub enum Interpolation {
9 Nearest,
10 Linear,
11}
12pub struct Sampler {
13 interpolation: Interpolation,
14}
15
16impl Sampler {
17 pub fn new(interpolation: Interpolation) -> Self {
18 Self { interpolation }
19 }
20}
21
22fn pixel_to_vec4<P: Pixel<Subpixel = f32>>(pixel: &P) -> Vec4 {
37 let pixel_channels = pixel.channels();
38
39 let max_channel = (P::CHANNEL_COUNT as usize).saturating_sub(1);
40
41 Vec4::new(
42 pixel_channels[0.min(max_channel)],
43 pixel_channels[1.min(max_channel)],
44 pixel_channels[2.min(max_channel)],
45 pixel_channels[3.min(max_channel)],
46 )
47}
48
49pub struct CPUImage<P>
50where
51 P: Pixel<Subpixel = f32>,
52{
53 images: Vec<ImageBuffer<P, Vec<f32>>>,
54}
55
56impl<P> CPUImage<P>
57where
58 P: Pixel<Subpixel = f32>,
59{
60 pub fn new(data: &[ImageBuffer<P, Vec<f32>>]) -> Self {
61 Self {
62 images: data.to_vec(),
63 }
64 }
65 fn bilinear(&self, coordinates: Vec2, mip: usize) -> Vec4 {
66 let image = &self.images[mip.clamp(0, self.images.len() - 1)];
67 let (width, height) = image.dimensions();
68 let resolution = UVec2::new(width, height);
69 if coordinates.x.ceil() as u32 >= resolution.x
70 || coordinates.y.ceil() as u32 >= resolution.y
71 {
72 return Vec4::default();
73 }
74
75 match interpolate_bilinear(image, coordinates.x, coordinates.y) {
76 Some(pixel) => pixel_to_vec4(&pixel),
77 None => Vec4::default(),
78 }
79 }
80
81 pub fn load_single_mip(&self, coordinates: Vec2, sampler: &Sampler, mip: usize) -> Vec4 {
82 let image = &self.images[mip.clamp(0, self.images.len() - 1)];
83 let (width, height) = image.dimensions();
84 let resolution = Vec2::new(width as f32, height as f32);
85 let converted_coordinates = (coordinates * resolution).clamp(Vec2::ZERO, resolution - 1.0);
86 if sampler.interpolation == Interpolation::Nearest {
87 let pixel = unsafe {
88 image.unsafe_get_pixel(
89 converted_coordinates.x as u32,
90 converted_coordinates.y as u32,
91 )
92 };
93 pixel_to_vec4(&pixel)
94 } else {
95 self.bilinear(converted_coordinates, mip)
96 }
97 }
98
99 pub fn load_texture(&self, coordinates: Vec2, sampler: &Sampler, mip: f32) -> Vec4 {
100 let multiple_images = mip.round() as usize == mip as usize;
101 if !multiple_images {
102 return self.load_single_mip(coordinates, sampler, mip as usize);
103 }
104
105 let bottom = mip.floor();
106 let top = mip.ceil();
107 let fract = mip - bottom;
108
109 let bottom_pixel = self.load_single_mip(coordinates, sampler, bottom as usize);
110 let top_pixel = self.load_single_mip(coordinates, sampler, top as usize);
111
112 mix_vec(bottom_pixel, top_pixel, fract)
113 }
114}