opengl_graphics/
texture.rs

1use gl::types::GLuint;
2use image::{self, DynamicImage, RgbaImage};
3
4use std::path::Path;
5
6use crate::{
7    ops, CreateTexture, Filter, Format, ImageSize, TextureOp, TextureSettings, UpdateTexture, Wrap,
8};
9
10trait GlSettings {
11    fn get_gl_mag(&self) -> gl::types::GLenum;
12    fn get_gl_min(&self) -> gl::types::GLenum;
13    #[allow(dead_code)]
14    fn get_gl_mipmap(&self) -> gl::types::GLenum;
15    fn get_gl_wrap_u(&self) -> gl::types::GLenum;
16    fn get_gl_wrap_v(&self) -> gl::types::GLenum;
17}
18
19impl GlSettings for TextureSettings {
20    fn get_gl_mag(&self) -> gl::types::GLenum {
21        match self.get_mag() {
22            Filter::Linear => gl::LINEAR,
23            Filter::Nearest => gl::NEAREST,
24        }
25    }
26
27    fn get_gl_min(&self) -> gl::types::GLenum {
28        match self.get_min() {
29            Filter::Linear => {
30                if self.get_generate_mipmap() {
31                    match self.get_mipmap() {
32                        Filter::Linear => gl::LINEAR_MIPMAP_LINEAR,
33                        Filter::Nearest => gl::LINEAR_MIPMAP_NEAREST,
34                    }
35                } else {
36                    gl::LINEAR
37                }
38            }
39            Filter::Nearest => {
40                if self.get_generate_mipmap() {
41                    match self.get_mipmap() {
42                        Filter::Linear => gl::NEAREST_MIPMAP_LINEAR,
43                        Filter::Nearest => gl::NEAREST_MIPMAP_NEAREST,
44                    }
45                } else {
46                    gl::NEAREST
47                }
48            }
49        }
50    }
51
52    fn get_gl_mipmap(&self) -> gl::types::GLenum {
53        match self.get_mipmap() {
54            Filter::Linear => gl::LINEAR,
55            Filter::Nearest => gl::NEAREST,
56        }
57    }
58
59    fn get_gl_wrap_u(&self) -> gl::types::GLenum {
60        match self.get_wrap_u() {
61            Wrap::Repeat => gl::REPEAT,
62            Wrap::MirroredRepeat => gl::MIRRORED_REPEAT,
63            Wrap::ClampToEdge => gl::CLAMP_TO_EDGE,
64            Wrap::ClampToBorder => gl::CLAMP_TO_BORDER,
65        }
66    }
67
68    fn get_gl_wrap_v(&self) -> gl::types::GLenum {
69        match self.get_wrap_v() {
70            Wrap::Repeat => gl::REPEAT,
71            Wrap::MirroredRepeat => gl::MIRRORED_REPEAT,
72            Wrap::ClampToEdge => gl::CLAMP_TO_EDGE,
73            Wrap::ClampToBorder => gl::CLAMP_TO_BORDER,
74        }
75    }
76}
77
78/// Wraps OpenGL texture data.
79/// The texture gets deleted when running out of scope.
80///
81/// In order to create a texture the function `GenTextures` must be loaded.
82/// This is done automatically by the window back-ends in Piston.
83pub struct Texture {
84    id: GLuint,
85    width: u32,
86    height: u32,
87}
88
89impl Texture {
90    /// Creates a new texture.
91    #[inline(always)]
92    pub fn new(id: GLuint, width: u32, height: u32) -> Self {
93        Texture { id, width, height }
94    }
95
96    /// Gets the OpenGL id of the texture.
97    #[inline(always)]
98    pub fn get_id(&self) -> GLuint {
99        self.id
100    }
101
102    /// Returns empty texture.
103    pub fn empty(settings: &TextureSettings) -> Result<Self, String> {
104        CreateTexture::create(&mut (), Format::Rgba8, &[0u8; 4], [1, 1], settings)
105    }
106
107    /// Loads image from memory, the format is 8-bit greyscale.
108    pub fn from_memory_alpha(
109        buf: &[u8],
110        width: u32,
111        height: u32,
112        settings: &TextureSettings,
113    ) -> Result<Self, String> {
114        let size = [width, height];
115        let buffer = ops::alpha_to_rgba8(buf, size);
116        CreateTexture::create(&mut (), Format::Rgba8, &buffer, size, settings)
117    }
118
119    /// Loads image by relative file name to the asset root.
120    pub fn from_path<P>(path: P, settings: &TextureSettings) -> Result<Self, String>
121    where
122        P: AsRef<Path>,
123    {
124        let path = path.as_ref();
125
126        let img = match image::open(path) {
127            Ok(img) => img,
128            Err(e) => {
129                return Err(format!(
130                    "Could not load '{:?}': {:?}",
131                    path.file_name().unwrap(),
132                    e
133                ))
134            }
135        };
136
137        let img = match img {
138            DynamicImage::ImageRgba8(img) => img,
139            x => x.to_rgba8(),
140        };
141
142        Ok(Texture::from_image(&img, settings))
143    }
144
145    /// Load image from bytes.
146    pub fn from_bytes(bytes: &[u8], settings: &TextureSettings) -> Result<Self, String> {
147        let img = match image::load_from_memory(bytes) {
148            Ok(img) => img,
149            Err(e) => return Err(format!("Could not load image from bytes. {:?}", e)),
150        };
151
152        let img = match img {
153            DynamicImage::ImageRgba8(img) => img,
154            x => x.to_rgba8(),
155        };
156
157        Ok(Texture::from_image(&img, settings))
158    }
159
160    /// Creates a texture from image.
161    pub fn from_image(img: &RgbaImage, settings: &TextureSettings) -> Self {
162        let (width, height) = img.dimensions();
163        CreateTexture::create(&mut (), Format::Rgba8, img, [width, height], settings).unwrap()
164    }
165
166    /// Updates image with a new one.
167    pub fn update(&mut self, img: &RgbaImage) {
168        let (width, height) = img.dimensions();
169
170        UpdateTexture::update(self, &mut (), Format::Rgba8, img, [0, 0], [width, height]).unwrap();
171    }
172}
173
174impl Drop for Texture {
175    fn drop(&mut self) {
176        unsafe {
177            let ids = [self.id];
178            gl::DeleteTextures(1, ids.as_ptr());
179        }
180    }
181}
182
183impl ImageSize for Texture {
184    fn get_size(&self) -> (u32, u32) {
185        (self.width, self.height)
186    }
187}
188
189impl TextureOp<()> for Texture {
190    type Error = String;
191}
192
193impl CreateTexture<()> for Texture {
194    fn create<S: Into<[u32; 2]>>(
195        _factory: &mut (),
196        _format: Format,
197        memory: &[u8],
198        size: S,
199        settings: &TextureSettings,
200    ) -> Result<Self, Self::Error> {
201        let size = size.into();
202        let mut id: GLuint = 0;
203        let internal_format = if settings.get_convert_gamma() {
204            gl::RGBA
205        } else {
206            gl::SRGB_ALPHA
207        };
208        unsafe {
209            gl::GenTextures(1, &mut id);
210            gl::BindTexture(gl::TEXTURE_2D, id);
211            gl::TexParameteri(
212                gl::TEXTURE_2D,
213                gl::TEXTURE_MIN_FILTER,
214                settings.get_gl_min() as i32,
215            );
216            gl::TexParameteri(
217                gl::TEXTURE_2D,
218                gl::TEXTURE_MAG_FILTER,
219                settings.get_gl_mag() as i32,
220            );
221            gl::TexParameteri(
222                gl::TEXTURE_2D,
223                gl::TEXTURE_WRAP_S,
224                settings.get_gl_wrap_u() as i32,
225            );
226            gl::TexParameteri(
227                gl::TEXTURE_2D,
228                gl::TEXTURE_WRAP_T,
229                settings.get_gl_wrap_v() as i32,
230            );
231            if settings.get_wrap_u() == Wrap::ClampToBorder
232                || settings.get_wrap_v() == Wrap::ClampToBorder
233            {
234                gl::TexParameterfv(
235                    gl::TEXTURE_2D,
236                    gl::TEXTURE_BORDER_COLOR,
237                    settings.get_border_color().as_ptr(),
238                );
239            }
240            if settings.get_generate_mipmap() {
241                gl::GenerateMipmap(gl::TEXTURE_2D);
242            }
243            gl::TexImage2D(
244                gl::TEXTURE_2D,
245                0,
246                internal_format as i32,
247                size[0] as i32,
248                size[1] as i32,
249                0,
250                gl::RGBA,
251                gl::UNSIGNED_BYTE,
252                memory.as_ptr() as *const _,
253            );
254        }
255
256        Ok(Texture::new(id, size[0], size[1]))
257    }
258}
259
260impl UpdateTexture<()> for Texture {
261    fn update<O: Into<[u32; 2]>, S: Into<[u32; 2]>>(
262        &mut self,
263        _factory: &mut (),
264        _format: Format,
265        memory: &[u8],
266        offset: O,
267        size: S,
268    ) -> Result<(), Self::Error> {
269        let offset = offset.into();
270        let size = size.into();
271        unsafe {
272            gl::BindTexture(gl::TEXTURE_2D, self.id);
273            gl::TexSubImage2D(
274                gl::TEXTURE_2D,
275                0,
276                offset[0] as i32,
277                offset[1] as i32,
278                size[0] as i32,
279                size[1] as i32,
280                gl::RGBA,
281                gl::UNSIGNED_BYTE,
282                memory.as_ptr() as *const _,
283            );
284        }
285
286        Ok(())
287    }
288}