Skip to main content

zenthra_render/
texture.rs

1fn load_image_or_svg(bytes: &[u8], max_dim: Option<u32>) -> Result<(Vec<u8>, u32, u32), image::ImageError> {
2    match image::load_from_memory(bytes) {
3        Ok(img) => {
4            let resized = if let Some(max) = max_dim {
5                img.thumbnail(max, max)
6            } else {
7                img
8            };
9            let rgba = resized.to_rgba8();
10            let dim = rgba.dimensions();
11            Ok((rgba.into_raw(), dim.0, dim.1))
12        }
13        Err(e) => {
14            // Try SVG rendering
15            let opt = resvg::usvg::Options::default();
16            match resvg::usvg::Tree::from_data(bytes, &opt) {
17                Ok(rtree) => {
18                    let size = rtree.size();
19                    let mut w = size.width();
20                    let mut h = size.height();
21                    
22                    let target_max = max_dim.unwrap_or(256) as f32;
23                    let current_max = w.max(h);
24                    if current_max > 0.0 {
25                        let scale = target_max / current_max;
26                        w *= scale;
27                        h *= scale;
28                    }
29                    
30                    let w_u32 = w.round() as u32;
31                    let h_u32 = h.round() as u32;
32                    
33                    if w_u32 > 0 && h_u32 > 0 {
34                        if let Some(mut pixmap) = resvg::tiny_skia::Pixmap::new(w_u32, h_u32) {
35                            let transform = resvg::tiny_skia::Transform::from_scale(
36                                w / size.width(),
37                                h / size.height(),
38                            );
39                            resvg::render(&rtree, transform, &mut pixmap.as_mut());
40                            return Ok((pixmap.data().to_vec(), w_u32, h_u32));
41                        }
42                    }
43                    Err(e)
44                }
45                Err(_) => Err(e),
46            }
47        }
48    }
49}
50
51pub fn create_texture_bind_group(
52    device: &wgpu::Device,
53    queue: &wgpu::Queue,
54    bgl: &wgpu::BindGroupLayout,
55    bytes: &[u8],
56) -> Result<(wgpu::BindGroup, u32, u32), image::ImageError> {
57    let (rgba, w, h) = load_image_or_svg(bytes, None)?;
58    let size = wgpu::Extent3d {
59        width: w,
60        height: h,
61        depth_or_array_layers: 1,
62    };
63
64    let texture = device.create_texture(&wgpu::TextureDescriptor {
65        label: Some("Image Texture"),
66        size,
67        mip_level_count: 1,
68        sample_count: 1,
69        dimension: wgpu::TextureDimension::D2,
70        format: wgpu::TextureFormat::Rgba8UnormSrgb,
71        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
72        view_formats: &[],
73    });
74
75    queue.write_texture(
76        wgpu::TexelCopyTextureInfo {
77            texture: &texture,
78            mip_level: 0,
79            origin: wgpu::Origin3d::ZERO,
80            aspect: wgpu::TextureAspect::All,
81        },
82        &rgba,
83        wgpu::TexelCopyBufferLayout {
84            offset: 0,
85            bytes_per_row: Some(4 * w),
86            rows_per_image: Some(h),
87        },
88        size,
89    );
90
91    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
92    let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
93        address_mode_u: wgpu::AddressMode::ClampToEdge,
94        address_mode_v: wgpu::AddressMode::ClampToEdge,
95        address_mode_w: wgpu::AddressMode::ClampToEdge,
96        mag_filter: wgpu::FilterMode::Linear,
97        min_filter: wgpu::FilterMode::Linear,
98        mipmap_filter: wgpu::MipmapFilterMode::Linear,
99        ..Default::default()
100    });
101
102    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
103        layout: bgl,
104        entries: &[
105            wgpu::BindGroupEntry {
106                binding: 0,
107                resource: wgpu::BindingResource::TextureView(&view),
108            },
109            wgpu::BindGroupEntry {
110                binding: 1,
111                resource: wgpu::BindingResource::Sampler(&sampler),
112            },
113        ],
114        label: Some("Image Bind Group"),
115    });
116
117    Ok((bind_group, w, h))
118}
119
120pub fn create_texture_bind_group_thumbnail(
121    device: &wgpu::Device,
122    queue: &wgpu::Queue,
123    bgl: &wgpu::BindGroupLayout,
124    bytes: &[u8],
125    max_dim: u32,
126) -> Result<(wgpu::BindGroup, u32, u32), image::ImageError> {
127    let (rgba, w, h) = load_image_or_svg(bytes, Some(max_dim))?;
128    let size = wgpu::Extent3d {
129        width: w,
130        height: h,
131        depth_or_array_layers: 1,
132    };
133
134    let texture = device.create_texture(&wgpu::TextureDescriptor {
135        label: Some("Thumbnail Texture"),
136        size,
137        mip_level_count: 1,
138        sample_count: 1,
139        dimension: wgpu::TextureDimension::D2,
140        format: wgpu::TextureFormat::Rgba8UnormSrgb,
141        usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
142        view_formats: &[],
143    });
144
145    queue.write_texture(
146        wgpu::TexelCopyTextureInfo {
147            texture: &texture,
148            mip_level: 0,
149            origin: wgpu::Origin3d::ZERO,
150            aspect: wgpu::TextureAspect::All,
151        },
152        &rgba,
153        wgpu::TexelCopyBufferLayout {
154            offset: 0,
155            bytes_per_row: Some(4 * w),
156            rows_per_image: Some(h),
157        },
158        size,
159    );
160
161    let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
162    let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
163        address_mode_u: wgpu::AddressMode::ClampToEdge,
164        address_mode_v: wgpu::AddressMode::ClampToEdge,
165        address_mode_w: wgpu::AddressMode::ClampToEdge,
166        mag_filter: wgpu::FilterMode::Linear,
167        min_filter: wgpu::FilterMode::Linear,
168        mipmap_filter: wgpu::MipmapFilterMode::Linear,
169        ..Default::default()
170    });
171
172    let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
173        layout: bgl,
174        entries: &[
175            wgpu::BindGroupEntry {
176                binding: 0,
177                resource: wgpu::BindingResource::TextureView(&view),
178            },
179            wgpu::BindGroupEntry {
180                binding: 1,
181                resource: wgpu::BindingResource::Sampler(&sampler),
182            },
183        ],
184        label: Some("Thumbnail Bind Group"),
185    });
186
187    Ok((bind_group, w, h))
188}