1use wgpu::*;
4use std::path::Path;
5use std::fs;
6
7pub struct Texture {
8 pub texture: wgpu::Texture,
9 pub view: TextureView,
10 pub sampler: Sampler,
11 pub width: u32,
12 pub height: u32,
13}
14
15impl Texture {
16 pub fn from_jxl(
17 device: &Device,
18 queue: &Queue,
19 path: &Path,
20 ) -> Result<Self, Box<dyn std::error::Error>> {
21 let data = fs::read(path)?;
23
24 use jxl_oxide::JxlImage;
26
27 let mut image = JxlImage::builder()
29 .read(&data[..])
30 .map_err(|e| format!("Failed to read JXL header: {}", e))?;
31
32 let header = image.image_header();
34 let width = header.size.width as u32;
35 let height = header.size.height as u32;
36
37 let render = image.render_frame(0)
39 .map_err(|e| format!("Failed to render frame: {}", e))?;
40
41 let frame_buffer = render.image_all_channels();
43 let f32_pixels = frame_buffer.buf();
44
45 let pixels_per_channel = (width * height) as usize;
48 let num_channels = f32_pixels.len() / pixels_per_channel;
49
50 let mut rgba8_data = Vec::with_capacity(pixels_per_channel * 4);
52
53 if num_channels == 3 {
54 for i in 0..pixels_per_channel {
56 rgba8_data.push((f32_pixels[i * 3].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push((f32_pixels[i * 3 + 1].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push((f32_pixels[i * 3 + 2].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push(255); }
61 } else if num_channels >= 4 {
62 for i in 0..pixels_per_channel {
64 rgba8_data.push((f32_pixels[i * num_channels].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push((f32_pixels[i * num_channels + 1].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push((f32_pixels[i * num_channels + 2].clamp(0.0, 1.0) * 255.0) as u8); rgba8_data.push((f32_pixels[i * num_channels + 3].clamp(0.0, 1.0) * 255.0) as u8); }
69 } else {
70 return Err(format!("Unsupported channel count: {} (expected 3 or 4)", num_channels).into());
71 }
72
73 let pixels = rgba8_data.as_slice();
74
75 let texture_size = Extent3d {
77 width,
78 height,
79 depth_or_array_layers: 1,
80 };
81
82 let texture = device.create_texture(&TextureDescriptor {
83 label: Some("JXL Texture"),
84 size: texture_size,
85 mip_level_count: 1,
86 sample_count: 1,
87 dimension: TextureDimension::D2,
88 format: TextureFormat::Rgba8UnormSrgb,
89 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
90 view_formats: &[],
91 });
92
93 queue.write_texture(
94 ImageCopyTexture {
95 texture: &texture,
96 mip_level: 0,
97 origin: Origin3d::ZERO,
98 aspect: TextureAspect::All,
99 },
100 pixels,
101 ImageDataLayout {
102 offset: 0,
103 bytes_per_row: Some(4 * width),
104 rows_per_image: Some(height),
105 },
106 texture_size,
107 );
108
109 let view = texture.create_view(&TextureViewDescriptor::default());
110 let sampler = device.create_sampler(&SamplerDescriptor {
111 label: Some("Texture Sampler"),
112 address_mode_u: AddressMode::ClampToEdge,
113 address_mode_v: AddressMode::ClampToEdge,
114 address_mode_w: AddressMode::ClampToEdge,
115 mag_filter: FilterMode::Linear,
116 min_filter: FilterMode::Linear,
117 mipmap_filter: FilterMode::Nearest,
118 ..Default::default()
119 });
120
121 Ok(Self {
122 texture,
123 view,
124 sampler,
125 width,
126 height,
127 })
128 }
129}