Skip to main content

hotline_rs/
image.rs

1use stb_image_rust;
2use stb_image_write_rust::ImageWriter::ImageWriter;
3
4use std::fs;
5use std::io::Read;
6
7use ddsfile::Caps2;
8use ddsfile::D3DFormat;
9use ddsfile::DxgiFormat;
10use ddsfile::Dds as DDS;
11
12use crate::gfx;
13use gfx::{TextureInfo, TextureType, Device};
14
15/// Minimal header to describe image data, with a `TextureInfo` and a `Vec<u8>` of actual image data.
16pub struct ImageData {
17    /// gfx::TextureInfo describing the texture
18    pub info: TextureInfo,
19    /// Vector of linear image data tightly packed
20    pub data: Vec<u8>,
21}
22
23/// Loads an image from file returning information in the ImageData struct
24/// supported formats are (png, tga, bmp, jpg, gif, dds)
25pub fn load_from_file(filename: &str) -> Result<ImageData, super::Error> {
26    // read file
27    let path = std::path::Path::new(filename);
28    println!("hotline_rs::image:: loading: {}", path.display());
29    let mut f = fs::File::open(path).expect("hotline_rs::image:: File not found");
30    // dds file
31    if filename.ends_with(".dds") {
32        let dds = DDS::read(f)?;        
33        Ok(ImageData {
34            info: TextureInfo {
35                tex_type: to_gfx_texture_type(&dds),
36                format: to_gfx_format(&dds),
37                width: dds.get_width() as u64,
38                height: dds.get_height() as u64,
39                depth: dds.get_depth(),
40                array_layers: dds.get_num_array_layers(),
41                mip_levels: dds.get_num_mipmap_levels(),
42                samples: 1,
43                usage: gfx::TextureUsage::SHADER_RESOURCE,
44                initial_state: gfx::ResourceState::ShaderResource
45            },
46            data: dds.data.to_vec(),
47        })
48    }
49    else {
50        // stb image
51        let mut contents = vec![];
52        f.read_to_end(&mut contents)?;
53
54        let mut x = 0;
55        let mut y = 0;
56        let mut comp = 0;
57        let mut data_out: Vec<u8> = Vec::new();
58
59        unsafe {
60            // load image
61            let img = stb_image_rust::stbi_load_from_memory(
62                contents.as_mut_ptr(),
63                contents.len() as i32,
64                &mut x,
65                &mut y,
66                &mut comp,
67                stb_image_rust::STBI_rgb_alpha,
68            );
69
70            if !img.is_null() {
71                // copy data
72                let data_size_bytes = x * y * 4;
73                data_out.resize(data_size_bytes as usize, 0);
74                std::ptr::copy_nonoverlapping(img, data_out.as_mut_ptr(), data_size_bytes as usize);
75        
76                // cleanup
77                stb_image_rust::c_runtime::free(img);
78
79                Ok(ImageData {
80                    info: TextureInfo {
81                        format: gfx::Format::RGBA8n,
82                        width: x as u64,
83                        height: y as u64,
84                        ..Default::default()
85                    },
86                    data: data_out,
87                })
88            }
89            else {
90                Err(super::Error {
91                    msg: format!("hotline_rs::image:: failed to load image via stb_image: {}", filename)
92                })
93            }
94        }
95    }
96}
97
98/// Loads an image from file and creates a shader resource on the specified heap, or on the device heap if `heap.is_none()`
99#[cfg(target_os = "windows")]
100pub fn load_texture_from_file(
101    device: &mut crate::gfx_platform::Device,
102    file: &str,
103    heap: Option<&mut crate::gfx_platform::Heap>) -> Result<crate::gfx_platform::Texture, super::Error> {
104    let image = load_from_file(file)?;
105    device.create_texture_with_heaps(
106        &image.info, 
107        gfx::TextureHeapInfo {
108            shader: heap,
109            ..Default::default()
110        },
111    crate::data![image.data.as_slice()])
112}
113
114/// Convert ddsfile to gfx::TextureType
115fn to_gfx_texture_type(dds: &DDS) -> TextureType {
116    if dds.header.caps.contains(ddsfile::Caps::COMPLEX) {
117        let all_faces = Caps2::CUBEMAP_POSITIVEX | Caps2::CUBEMAP_NEGATIVEX | Caps2::CUBEMAP_POSITIVEY |
118                        Caps2::CUBEMAP_NEGATIVEY | Caps2::CUBEMAP_POSITIVEZ | Caps2::CUBEMAP_NEGATIVEZ;
119        if dds.header.caps2.contains(all_faces) {
120            if dds.get_num_array_layers() > 6 {
121                TextureType::TextureCubeArray
122            }
123            else {
124                TextureType::TextureCube
125            }
126        }
127        else if dds.get_depth() > 1 {
128            TextureType::Texture3D
129        }
130        else if dds.get_height() == 1 {
131            if dds.get_num_array_layers() > 1 {
132                TextureType::Texture1DArray
133            }
134            else {
135                TextureType::Texture1D
136            }
137        }
138        else if dds.get_num_array_layers() > 1 {
139            TextureType::Texture2DArray
140        }
141        else {
142            TextureType::Texture2D
143        }
144    }
145    else if dds.get_height() == 1 {
146        if dds.get_num_array_layers() > 1 {
147            TextureType::Texture1DArray
148        }
149        else {
150            TextureType::Texture1D
151        }
152    }
153    else if dds.get_num_array_layers() > 1 {
154        TextureType::Texture2DArray
155    }
156    else {
157        TextureType::Texture2D
158    }
159}
160
161/// Writes a buffer of image data to a file. The type of image format written is determined by filename ext
162/// supported image formats are (png, bmp, tga and jpg).
163pub fn write_to_file(filename: &str, width: u64, height: u64, components: u32, image_data: &[u8]) -> Result<(), super::Error> {
164    let path = std::path::Path::new(&filename);
165    let mut writer = ImageWriter::new(filename);
166    match path.extension() {
167        Some(os_str) => match os_str.to_str() {
168            Some("png") => {
169                writer.write_png(
170                    width as i32,
171                    height as i32,
172                    components as i32,
173                    image_data.as_ptr(),
174                );
175                Ok(())
176            }
177            Some("bmp") => {
178                writer.write_bmp(
179                    width as i32,
180                    height as i32,
181                    components as i32,
182                    image_data.as_ptr(),
183                );
184                Ok(())
185            }
186            Some("tga") => {
187                writer.write_tga(
188                    width as i32,
189                    height as i32,
190                    components as i32,
191                    image_data.as_ptr(),
192                );
193                Ok(())
194            }
195            Some("jpg") => {
196                writer.write_jpg(
197                    width as i32,
198                    height as i32,
199                    components as i32,
200                    image_data.as_ptr(),
201                    90,
202                );
203                Ok(())
204            }
205            _ => {
206                if os_str.to_str().is_some() {
207                    Err(super::Error {
208                            msg: format!("hotline_rs::image: Image format '{}' is not supported", os_str.to_str().unwrap())
209                    })
210                } else {
211                    Err(super::Error {
212                        msg: format!("hotline_rs::image: Filename '{}' did not specify image format extension!",filename)
213                    })
214                }
215            }
216        },
217        _ => Err(super::Error {
218                msg: format!("hotline_rs::image: Filename '{}' has no extension!", filename)
219             }),
220    }
221}
222
223/// Writes an image from file which is formed of data read back from the GPU. This will account for alignment and padding
224pub fn write_to_file_from_gpu(filename: &str, data: &gfx::ReadBackData) -> Result<(), super::Error> {
225    let fmt = if data.format == gfx::Format::Unknown {
226        gfx::Format::RGBA8n
227    }
228    else {
229        data.format
230    };
231    let w = data.row_pitch / gfx::block_size_for_format(fmt) as usize;
232    let h = data.slice_pitch / data.row_pitch;
233    let c = gfx::components_for_format(fmt);
234    write_to_file(filename, w as u64, h as u64, c, data.data)
235}
236
237/// Convert ddsfile format D3D or DXGI to gfx::Format.. gfx does not expose all formats. this may grow over time.
238fn to_gfx_format(dds: &DDS) -> gfx::Format {
239    if let Some(fmt) = dds.get_d3d_format() {
240        match fmt {
241            D3DFormat::A8B8G8R8 => gfx::Format::RGBA8n,
242            D3DFormat::G16R16 => panic!(),
243            D3DFormat::A2B10G10R10 => panic!(),
244            D3DFormat::A1R5G5B5 => panic!(),
245            D3DFormat::R5G6B5 => panic!(),
246            D3DFormat::A8 => panic!(),
247            D3DFormat::A8R8G8B8 => panic!(),
248            D3DFormat::X8R8G8B8 => panic!(),
249            D3DFormat::X8B8G8R8 => panic!(),
250            D3DFormat::A2R10G10B10 => panic!(),
251            D3DFormat::R8G8B8 => panic!(),
252            D3DFormat::X1R5G5B5 => panic!(),
253            D3DFormat::A4R4G4B4 => panic!(),
254            D3DFormat::X4R4G4B4 => panic!(),
255            D3DFormat::A8R3G3B2 => panic!(),
256            D3DFormat::A8L8 => panic!(),
257            D3DFormat::L16 => panic!(),
258            D3DFormat::L8 => panic!(),
259            D3DFormat::A4L4 => panic!(),
260            D3DFormat::DXT1 => panic!(),
261            D3DFormat::DXT3 => panic!(),
262            D3DFormat::DXT5 => panic!(),
263            D3DFormat::R8G8_B8G8 => panic!(),
264            D3DFormat::G8R8_G8B8 => panic!(),
265            D3DFormat::A16B16G16R16 => panic!(),
266            D3DFormat::Q16W16V16U16 => panic!(),
267            D3DFormat::R16F => gfx::Format::R16f,
268            D3DFormat::G16R16F => gfx::Format::RG16f,
269            D3DFormat::A16B16G16R16F => gfx::Format::RGBA16f,
270            D3DFormat::R32F => gfx::Format::R32f,
271            D3DFormat::G32R32F => gfx::Format::RG32f,
272            D3DFormat::A32B32G32R32F => gfx::Format::RGBA32f,
273            D3DFormat::DXT2 => panic!(),
274            D3DFormat::DXT4 => panic!(),
275            D3DFormat::UYVY => panic!(),
276            D3DFormat::YUY2 => panic!(),
277            D3DFormat::CXV8U8 => panic!(),
278        }
279    }
280    else if let Some(fmt) = dds.get_dxgi_format() {
281        match fmt {
282            DxgiFormat::Unknown => gfx::Format::Unknown,
283            DxgiFormat::R32G32B32A32_Typeless => panic!(),
284            DxgiFormat::R32G32B32A32_Float => gfx::Format::RGBA32f,
285            DxgiFormat::R32G32B32A32_UInt => gfx::Format::RGBA32u,
286            DxgiFormat::R32G32B32A32_SInt => gfx::Format::RGBA32i,
287            DxgiFormat::R32G32B32_Typeless => panic!(),
288            DxgiFormat::R32G32B32_Float => gfx::Format::RGB32f,
289            DxgiFormat::R32G32B32_UInt => gfx::Format::RGB32u,
290            DxgiFormat::R32G32B32_SInt => gfx::Format::RGB32i,
291            DxgiFormat::R16G16B16A16_Typeless => panic!(),
292            DxgiFormat::R16G16B16A16_Float => gfx::Format::RGBA16f,
293            DxgiFormat::R16G16B16A16_UNorm => panic!(),
294            DxgiFormat::R16G16B16A16_UInt => gfx::Format::RGBA16u,
295            DxgiFormat::R16G16B16A16_SNorm => panic!(),
296            DxgiFormat::R16G16B16A16_SInt => gfx::Format::RGBA16i,
297            DxgiFormat::R32G32_Typeless => panic!(),
298            DxgiFormat::R32G32_Float => gfx::Format::RG32f,
299            DxgiFormat::R32G32_UInt => gfx::Format::RG32u,
300            DxgiFormat::R32G32_SInt => gfx::Format::RG32i,
301            DxgiFormat::R32G8X24_Typeless => panic!(),
302            DxgiFormat::D32_Float_S8X24_UInt => panic!(),
303            DxgiFormat::R32_Float_X8X24_Typeless => panic!(),
304            DxgiFormat::X32_Typeless_G8X24_UInt => panic!(),
305            DxgiFormat::R10G10B10A2_Typeless => panic!(),
306            DxgiFormat::R10G10B10A2_UNorm => panic!(),
307            DxgiFormat::R10G10B10A2_UInt => panic!(),
308            DxgiFormat::R11G11B10_Float => panic!(),
309            DxgiFormat::R8G8B8A8_Typeless => panic!(),
310            DxgiFormat::R8G8B8A8_UNorm => gfx::Format::RGBA8n,
311            DxgiFormat::R8G8B8A8_UNorm_sRGB => gfx::Format::RGBA8nSRGB,
312            DxgiFormat::R8G8B8A8_UInt => gfx::Format::RGBA8u,
313            DxgiFormat::R8G8B8A8_SNorm => panic!(),
314            DxgiFormat::R8G8B8A8_SInt => gfx::Format::RGBA8i,
315            DxgiFormat::R16G16_Typeless => panic!(),
316            DxgiFormat::R16G16_Float => gfx::Format::RG16f,
317            DxgiFormat::R16G16_UNorm => panic!(),
318            DxgiFormat::R16G16_UInt => gfx::Format::RG16u,
319            DxgiFormat::R16G16_SNorm => panic!(),
320            DxgiFormat::R16G16_SInt => gfx::Format::RG16i,
321            DxgiFormat::R32_Typeless => panic!(),
322            DxgiFormat::D32_Float => gfx::Format::D32f,
323            DxgiFormat::R32_Float => gfx::Format::R32f,
324            DxgiFormat::R32_UInt => gfx::Format::R32u,
325            DxgiFormat::R32_SInt => gfx::Format::R32i,
326            DxgiFormat::R24G8_Typeless => panic!(),
327            DxgiFormat::D24_UNorm_S8_UInt => gfx::Format::D24nS8u,
328            DxgiFormat::R24_UNorm_X8_Typeless => panic!(),
329            DxgiFormat::X24_Typeless_G8_UInt => panic!(),
330            DxgiFormat::R8G8_Typeless => panic!(),
331            DxgiFormat::R8G8_UNorm => panic!(),
332            DxgiFormat::R8G8_UInt => panic!(),
333            DxgiFormat::R8G8_SNorm => panic!(),
334            DxgiFormat::R8G8_SInt => panic!(),
335            DxgiFormat::R16_Typeless => panic!(),
336            DxgiFormat::R16_Float => gfx::Format::R16f,
337            DxgiFormat::D16_UNorm => gfx::Format::D16n,
338            DxgiFormat::R16_UNorm => gfx::Format::R16n,
339            DxgiFormat::R16_UInt => gfx::Format::R16u,
340            DxgiFormat::R16_SNorm => panic!(),
341            DxgiFormat::R16_SInt => gfx::Format::R16i,
342            DxgiFormat::R8_Typeless => panic!(),
343            DxgiFormat::R8_UNorm => panic!(),
344            DxgiFormat::R8_UInt => panic!(),
345            DxgiFormat::R8_SNorm => panic!(),
346            DxgiFormat::R8_SInt => panic!(),
347            DxgiFormat::A8_UNorm => panic!(),
348            DxgiFormat::R1_UNorm => panic!(),
349            DxgiFormat::R9G9B9E5_SharedExp => panic!(),
350            DxgiFormat::R8G8_B8G8_UNorm => panic!(),
351            DxgiFormat::G8R8_G8B8_UNorm => panic!(),
352            DxgiFormat::BC1_Typeless => panic!(),
353            DxgiFormat::BC1_UNorm => gfx::Format::BC1n,
354            DxgiFormat::BC1_UNorm_sRGB => gfx::Format::BC1nSRGB,
355            DxgiFormat::BC2_Typeless => panic!(),
356            DxgiFormat::BC2_UNorm => gfx::Format::BC2n,
357            DxgiFormat::BC2_UNorm_sRGB => gfx::Format::BC2nSRGB,
358            DxgiFormat::BC3_Typeless => panic!(),
359            DxgiFormat::BC3_UNorm => gfx::Format::BC3n,
360            DxgiFormat::BC3_UNorm_sRGB => gfx::Format::BC3nSRGB,
361            DxgiFormat::BC4_Typeless => panic!(),
362            DxgiFormat::BC4_UNorm => gfx::Format::BC4n,
363            DxgiFormat::BC4_SNorm => panic!(),
364            DxgiFormat::BC5_Typeless => panic!(),
365            DxgiFormat::BC5_UNorm => gfx::Format::BC5n,
366            DxgiFormat::BC5_SNorm => panic!(),
367            DxgiFormat::B5G6R5_UNorm => panic!(),
368            DxgiFormat::B5G5R5A1_UNorm => panic!(),
369            DxgiFormat::B8G8R8A8_UNorm => gfx::Format::BGRA8n,
370            DxgiFormat::B8G8R8X8_UNorm => gfx::Format::BGRX8n,
371            DxgiFormat::R10G10B10_XR_Bias_A2_UNorm => panic!(),
372            DxgiFormat::B8G8R8A8_Typeless => panic!(),
373            DxgiFormat::B8G8R8A8_UNorm_sRGB => gfx::Format::BGRA8nSRGB,
374            DxgiFormat::B8G8R8X8_Typeless => panic!(),
375            DxgiFormat::B8G8R8X8_UNorm_sRGB => gfx::Format::BGRX8nSRGB,
376            DxgiFormat::BC6H_Typeless => panic!(),
377            DxgiFormat::BC6H_UF16 => panic!(),
378            DxgiFormat::BC6H_SF16 => panic!(),
379            DxgiFormat::BC7_Typeless => panic!(),
380            DxgiFormat::BC7_UNorm => panic!(),
381            DxgiFormat::BC7_UNorm_sRGB => panic!(),
382            DxgiFormat::AYUV => panic!(),
383            DxgiFormat::Y410 => panic!(),
384            DxgiFormat::Y416 => panic!(),
385            DxgiFormat::NV12 => panic!(),
386            DxgiFormat::P010 => panic!(),
387            DxgiFormat::P016 => panic!(),
388            DxgiFormat::Format_420_Opaque => panic!(),
389            DxgiFormat::YUY2 => panic!(),
390            DxgiFormat::Y210 => panic!(),
391            DxgiFormat::Y216 => panic!(),
392            DxgiFormat::NV11 => panic!(),
393            DxgiFormat::AI44 => panic!(),
394            DxgiFormat::IA44 => panic!(), 
395            DxgiFormat::P8 => panic!(),
396            DxgiFormat::A8P8 => panic!(),
397            DxgiFormat::B4G4R4A4_UNorm => panic!(),
398            DxgiFormat::P208 => panic!(),
399            DxgiFormat::V208 => panic!(),
400            DxgiFormat::V408 => panic!(),
401            DxgiFormat::Force_UInt => panic!(),
402        }
403    }
404    else {
405        panic!("hotline_rs::image:: unsupported dds format is neither d3d or dxgi!");
406    }
407}