gltf_viewer_lib/render/
texture.rs

1use std::os::raw::c_void;
2use std::path::Path;
3use std::{fs, io};
4
5use base64;
6use gl;
7use gltf;
8use gltf::json::texture::MinFilter;
9use gltf::image::Source;
10
11use image;
12use image::ImageFormat::{JPEG, PNG};
13use image::DynamicImage::*;
14use image::GenericImageView;
15use image::FilterType;
16
17use crate::importdata::ImportData;
18
19pub struct Texture {
20    pub index: usize, // glTF index
21    pub name: Option<String>,
22
23    pub id: u32, // OpenGL id
24    pub tex_coord: u32, // the tex coord set to use
25}
26
27impl Texture {
28    pub fn from_gltf(g_texture: &gltf::Texture<'_>, tex_coord: u32, imp: &ImportData, base_path: &Path) -> Texture {
29        let buffers = &imp.buffers;
30        let mut texture_id = 0;
31        unsafe {
32            gl::GenTextures(1, &mut texture_id);
33            gl::BindTexture(gl::TEXTURE_2D, texture_id);
34        }
35        let (needs_power_of_two, generate_mip_maps) =
36            unsafe { Self::set_sampler_params(&g_texture.sampler()) };
37
38        // TODO!: share images via Rc? detect if occurs?
39        // TODO!!: better I/O abstraction...
40        let g_img = g_texture.source();
41        let img = match g_img.source() {
42            Source::View { view, mime_type } => {
43                let parent_buffer_data = &buffers[view.buffer().index()].0;
44                let begin = view.offset();
45                let end = begin + view.length();
46                let data = &parent_buffer_data[begin..end];
47                match mime_type {
48                    "image/jpeg" => image::load_from_memory_with_format(data, JPEG),
49                    "image/png" => image::load_from_memory_with_format(data, PNG),
50                    _ => panic!(format!("unsupported image type (image: {}, mime_type: {})",
51                        g_img.index(), mime_type)),
52                }
53            },
54            Source::Uri { uri, mime_type } => {
55                if uri.starts_with("data:") {
56                    let encoded = uri.split(',').nth(1).unwrap();
57                    let data = base64::decode(&encoded).unwrap();
58                    let mime_type = if let Some(ty) = mime_type {
59                        ty
60                    } else {
61                        uri.split(',')
62                            .nth(0).unwrap()
63                            .split(':')
64                            .nth(1).unwrap()
65                            .split(';')
66                            .nth(0).unwrap()
67                    };
68
69                    match mime_type {
70                        "image/jpeg" => image::load_from_memory_with_format(&data, JPEG),
71                        "image/png" => image::load_from_memory_with_format(&data, PNG),
72                        _ => panic!(format!("unsupported image type (image: {}, mime_type: {})",
73                            g_img.index(), mime_type)),
74                    }
75                }
76                else if let Some(mime_type) = mime_type {
77                    let path = base_path.parent().unwrap_or_else(|| Path::new("./")).join(uri);
78                    let file = fs::File::open(path).unwrap();
79                    let reader = io::BufReader::new(file);
80                    match mime_type {
81                        "image/jpeg" => image::load(reader, JPEG),
82                        "image/png" => image::load(reader, PNG),
83                        _ => panic!(format!("unsupported image type (image: {}, mime_type: {})",
84                            g_img.index(), mime_type)),
85                    }
86                }
87                else {
88                    let path = base_path.parent().unwrap_or_else(||Path::new("./")).join(uri);
89                    image::open(path)
90                }
91            }
92        };
93
94        // TODO: handle I/O problems
95        let dyn_img = img.expect("Image loading failed.");
96
97        let format = match dyn_img {
98            ImageLuma8(_) => gl::RED,
99            ImageLumaA8(_) => gl::RG,
100            ImageRgb8(_) => gl::RGB,
101            ImageRgba8(_) => gl::RGBA,
102            ImageBgr8(_) => gl::BGR,
103            ImageBgra8(_) => gl::BGRA,
104        };
105
106        // **Non-Power-Of-Two Texture Implementation Note**: glTF does not guarantee that a texture's
107        // dimensions are a power-of-two.  At runtime, if a texture's width or height is not a
108        // power-of-two, the texture needs to be resized so its dimensions are powers-of-two if the
109        // `sampler` the texture references
110        // * Has a wrapping mode (either `wrapS` or `wrapT`) equal to `REPEAT` or `MIRRORED_REPEAT`, or
111        // * Has a minification filter (`minFilter`) that uses mipmapping (`NEAREST_MIPMAP_NEAREST`, \\
112        //   `NEAREST_MIPMAP_LINEAR`, `LINEAR_MIPMAP_NEAREST`, or `LINEAR_MIPMAP_LINEAR`).
113        let (width, height) = dyn_img.dimensions();
114        let (data, width, height) =
115            if needs_power_of_two && (!width.is_power_of_two() || !height.is_power_of_two()) {
116                let nwidth = width.next_power_of_two();
117                let nheight = height.next_power_of_two();
118                let resized = dyn_img.resize(nwidth, nheight, FilterType::Lanczos3);
119                (resized.raw_pixels(), resized.width(), resized.height())
120            }
121            else {
122                (dyn_img.raw_pixels(), dyn_img.width(), dyn_img.height())
123            };
124
125        unsafe {
126            gl::TexImage2D(gl::TEXTURE_2D, 0, format as i32, width as i32, height as i32,
127                0, format, gl::UNSIGNED_BYTE, &data[0] as *const u8 as *const c_void);
128
129            if generate_mip_maps {
130                gl::GenerateMipmap(gl::TEXTURE_2D);
131            }
132        }
133        Texture {
134            index: g_texture.index(),
135            name: g_texture.name().map(|s| s.into()),
136            id: texture_id,
137            tex_coord,
138        }
139    }
140
141    // Returns whether image needs to be Power-Of-Two-sized and whether mip maps should be generated
142    // TODO: refactor return type into enum?
143    unsafe fn set_sampler_params(sampler: &gltf::texture::Sampler<'_>) -> (bool, bool) {
144        // **Mipmapping Implementation Note**: When a sampler's minification filter (`minFilter`)
145        // uses mipmapping (`NEAREST_MIPMAP_NEAREST`, `NEAREST_MIPMAP_LINEAR`, `LINEAR_MIPMAP_NEAREST`,
146        // or `LINEAR_MIPMAP_LINEAR`), any texture referencing the sampler needs to have mipmaps,
147        // e.g., by calling GL's `generateMipmap()` function.
148        let mip_maps = match sampler.min_filter() {
149            Some(MinFilter::NearestMipmapNearest) |
150            Some(MinFilter::LinearMipmapNearest) |
151            Some(MinFilter::NearestMipmapLinear) |
152            Some(MinFilter::LinearMipmapLinear) |
153            None => true, // see below
154            _ => false
155        };
156
157        // **Default Filtering Implementation Note:** When filtering options are defined,
158        // runtime must use them. Otherwise, it is free to adapt filtering to performance or quality goals.
159        if let Some(min_filter) = sampler.min_filter() {
160            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, min_filter.as_gl_enum() as i32);
161        }
162        else {
163            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR_MIPMAP_LINEAR as i32);
164        }
165        if let Some(mag_filter) = sampler.mag_filter() {
166            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, mag_filter.as_gl_enum() as i32);
167        }
168        else {
169            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
170        }
171
172        let wrap_s = sampler.wrap_s().as_gl_enum();
173        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, wrap_s as i32);
174        let wrap_t = sampler.wrap_t().as_gl_enum();
175        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, wrap_t as i32);
176
177        let needs_power_of_two =
178            wrap_s != gl::CLAMP_TO_EDGE ||
179            wrap_t != gl::CLAMP_TO_EDGE ||
180            mip_maps;
181        (needs_power_of_two, mip_maps)
182    }
183}