Skip to main content

gust_render/
texture.rs

1//! This module is for texture handling
2//! Importing, Loading, Pushing into OpenGl
3//! I'm using image crate that is really useful
4
5use color::Color;
6use gl;
7use gl::types::*;
8use image;
9use image::{DynamicImage, ImageBuffer};
10use std::error::Error;
11use std::os::raw::c_void;
12use std::path::Path;
13use Vector;
14
15/// # Texture structure
16/// A texture is an id inside openGL that can contain a array of byte
17/// this array can be spreaded to drawable object
18/// ```no_run
19/// use gust::window::Window;
20/// use gust::sprite::Sprite;
21/// use gust::texture::Texture;
22/// use std::rc::Rc;
23///
24/// let window = Window::new(1080, 1920, "Test");
25/// let leave = Rc::new(Texture::new("path/to/test"));
26///	let sprite = Sprite::from(&leave);
27/// ```
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Texture {
30    pub id: u32,
31    width: u32,
32    height: u32,
33    rgb_mode: RgbMode,
34}
35
36impl Texture {
37    //--------------------CONSTRUCTOR---------------------//
38
39    /// Create an empty texture
40    pub fn new() -> Texture {
41        let mut id = 0;
42        unsafe {
43            gl::GenTextures(1, &mut id);
44        };
45        Texture {
46            id,
47            width: 0,
48            height: 0,
49            rgb_mode: RgbMode::RGBA,
50        }
51    }
52
53    /// Create a texture from a raw data pointer needed for Font handling unsafe version of
54    /// from slice
55    pub unsafe fn from_data(data: *mut c_void, mode: RgbMode, width: u32, height: u32) -> Texture {
56        Texture {
57            id: Self::create(data, mode.as_gl(), width as i32, height as i32),
58            width: width as u32,
59            height: height as u32,
60            rgb_mode: mode,
61        }
62    }
63
64    /// Create a texture from a slice
65    pub fn from_slice(data: &mut [u8], mode: RgbMode, width: u32, height: u32) -> Texture {
66        Texture {
67            id: Self::create(
68                data.as_mut_ptr() as *mut c_void,
69                mode.as_gl(),
70                width as i32,
71                height as i32,
72            ),
73            width: width as u32,
74            height: height as u32,
75            rgb_mode: mode,
76        }
77    }
78
79    pub fn from_color(color: Color, sizes: Vector<u32>) -> Texture {
80        let length: usize = (sizes.x * sizes.y * 4) as usize;
81        let mut data: Vec<u8> = Vec::with_capacity(length);
82        let mut i: usize = 0;
83        let color_u8: (u8, u8, u8, u8) = color.into();
84
85        while i < length {
86            data.push(color_u8.0);
87            data.push(color_u8.1);
88            data.push(color_u8.2);
89            data.push(color_u8.3);
90            i += 4;
91        }
92        Self::from_slice(data.as_mut_slice(), RgbMode::RGBA, sizes.x, sizes.y)
93    }
94
95    /// Create an empty texture with a size
96    pub fn from_size(sizes: Vector<u32>) -> Texture {
97        let mut ve: Vec<u8> = vec![255; sizes.x as usize * sizes.y as usize * 4];
98        Self::from_slice(ve.as_mut_slice(), RgbMode::RGBA, sizes.x, sizes.y)
99    }
100
101    /// Create a texture from an image
102    pub fn from_image(img: DynamicImage) -> Result<Texture, TextureError> {
103        let id;
104        let mut size = (0, 0);
105        let mode;
106
107        match img {
108            DynamicImage::ImageRgba8(data) => {
109                size.0 = data.width();
110                size.1 = data.height();
111                id = Self::create(
112                    &data.into_raw()[0] as *const u8 as *mut std::ffi::c_void,
113                    gl::RGBA,
114                    size.0 as i32,
115                    size.1 as i32,
116                );
117                mode = RgbMode::RGBA;
118            }
119            DynamicImage::ImageRgb8(data) => {
120                size.0 = data.width();
121                size.1 = data.height();
122                id = Self::create(
123                    &data.into_raw()[0] as *const u8 as *mut std::ffi::c_void,
124                    gl::RGB,
125                    size.0 as i32,
126                    size.1 as i32,
127                );
128                mode = RgbMode::RGB;
129            }
130            _ => {
131                return Err(TextureError::ImageLoading);
132            }
133        }
134
135        Ok(Texture {
136            id,
137            width: size.0,
138            height: size.1,
139            rgb_mode: mode,
140        })
141    }
142
143    /// Create new texture from file path
144    pub fn from_path<P: AsRef<Path>>(path_to_file: P) -> Result<Texture, TextureError> {
145        if let Ok(img) = image::open(path_to_file) {
146            Texture::from_image(img)
147        } else {
148            Err(TextureError::FileError)
149        }
150    }
151
152    pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Box<Error>> {
153        let data = self.get_data();
154
155        match self.rgb_mode {
156            RgbMode::RGBA => {
157                let image: Option<image::RgbaImage> =
158                    ImageBuffer::from_vec(self.width, self.height, data);
159
160                if let Some(img) = image {
161                    img.save(path)?;
162                } else {
163                    return Err(Box::new(TextureError::WriteFile));
164                }
165            }
166            RgbMode::RGB => {
167                let image: Option<image::RgbImage> =
168                    ImageBuffer::from_vec(self.width, self.height, data);
169
170                if let Some(img) = image {
171                    img.save(path)?;
172                } else {
173                    return Err(Box::new(TextureError::WriteFile));
174                }
175            }
176            _ => unimplemented!(),
177        }
178        Ok(())
179    }
180
181    /// Create a texture with a
182    fn create(data: *mut c_void, rgb_mode: GLenum, width: i32, height: i32) -> u32 {
183        let mut id = 0;
184        unsafe {
185            gl::GenTextures(1, &mut id);
186            gl::BindTexture(gl::TEXTURE_2D, id);
187            gl::TexStorage2D(
188                // Create the storage
189                gl::TEXTURE_2D,
190                1,
191                if rgb_mode == gl::RGBA {
192                    gl::RGBA8
193                } else {
194                    gl::RGB8
195                },
196                width,
197                height,
198            );
199            gl::TexSubImage2D(
200                // Put pixel inside the storage
201                gl::TEXTURE_2D,
202                0,
203                0,
204                0,
205                width,
206                height,
207                rgb_mode,
208                gl::UNSIGNED_BYTE,
209                data,
210            );
211            Texture::default_param();
212            gl::GenerateMipmap(gl::TEXTURE_2D);
213            gl::BindTexture(gl::TEXTURE_2D, 0);
214        }
215        id
216    }
217
218    /// Update a block of a texture with an offset and a size
219    /// return TextureError if the sizes are not correct.
220    pub fn update_block<T, U>(
221        &mut self,
222        data: &[u8],
223        sizes: Vector<u32>,
224        pos: T,
225        rgb_mode: U,
226    ) -> Result<(), TextureError>
227    where
228        T: Into<Option<Vector<u32>>>,
229        U: Into<Option<RgbMode>>,
230    {
231        let pos = pos.into().unwrap_or_else(|| Vector::new(0, 0));
232        let rgb_mode = rgb_mode.into().unwrap_or(self.rgb_mode);
233        // If sizes are fucked up return an error
234        if data.is_empty() {
235            Ok(())
236        } else if pos.x + sizes.x > self.width || pos.y + sizes.y > self.height {
237            Err(TextureError::UpdateSize(
238                self.width,
239                pos.x + sizes.x,
240                self.height,
241                pos.y + sizes.y,
242            ))
243        } else {
244            // Bind the texture then give it to opengl
245            unsafe {
246                gl::BindTexture(gl::TEXTURE_2D, self.id);
247                gl::TexSubImage2D(
248                    gl::TEXTURE_2D,
249                    0,
250                    pos.x as i32,
251                    pos.y as i32,
252                    sizes.x as i32,
253                    sizes.y as i32,
254                    rgb_mode.as_gl(),
255                    gl::UNSIGNED_BYTE,
256                    data as *const _ as *const c_void, //mem::transmute(data.as_ptr())
257                );
258                gl::BindTexture(gl::TEXTURE_2D, 0);
259                gl::Flush();
260            }
261            Ok(())
262        }
263    }
264
265    pub fn get_rawsize(&self) -> usize {
266        match self.rgb_mode {
267            RgbMode::RGBA => (self.height * self.width * 4) as usize,
268            RgbMode::RGB => (self.height * self.width * 3) as usize,
269            RgbMode::RED => (self.height * self.width) as usize,
270        }
271    }
272
273    /// Get a Vec<u8> representing pixels of the texture
274    pub fn get_data(&self) -> Vec<u8> {
275        let size = self.get_rawsize();
276        let mut data: Vec<u8> = Vec::with_capacity(size);
277
278        if size == 0 || self.id == 0 {
279            Vec::new()
280        } else {
281            unsafe {
282                data.set_len(size);
283                gl::BindTexture(gl::TEXTURE_2D, self.id);
284                gl::GetTexImage(
285                    gl::TEXTURE_2D,
286                    0,
287                    self.rgb_mode.as_gl(),
288                    gl::UNSIGNED_BYTE,
289                    data.as_mut_ptr() as *mut c_void,
290                );
291                gl::BindTexture(gl::TEXTURE_2D, 0);
292            };
293            data
294        }
295    }
296
297    /// Nicely working efficiently legacy fb deleted
298    pub fn update_from_texture(
299        &mut self,
300        texture: &Texture,
301        pos: Vector<u32>,
302    ) -> Result<(), TextureError> {
303        let size = texture.get_rawsize();
304        let mut data: Vec<u8> = Vec::with_capacity(size);
305
306        if self.rgb_mode != texture.rgb_mode {
307            return Err(TextureError::UpdateMode(self.rgb_mode, texture.rgb_mode));
308        }
309        unsafe {
310            data.set_len(size);
311            gl::BindTexture(gl::TEXTURE_2D, texture.id);
312            gl::GetTexImage(
313                gl::TEXTURE_2D,
314                0,
315                self.rgb_mode.as_gl(),
316                gl::UNSIGNED_BYTE,
317                data.as_mut_ptr() as *mut c_void,
318            );
319            gl::BindTexture(gl::TEXTURE_2D, 0);
320        }
321        let h = texture.height;
322        let w = texture.width;
323        let mode = texture.rgb_mode;
324        // Then update block
325        self.update_block(data.as_slice(), Vector::new(w, h), pos, mode)
326    }
327
328    /// Update the data of the texture
329    pub fn update<T>(&mut self, data: &[u8], mode: T) -> Result<(), TextureError>
330    where
331        T: Into<Option<RgbMode>>,
332    {
333        let mode = mode.into().unwrap_or(self.rgb_mode);
334        let w = self.width;
335        let h = self.height;
336
337        self.update_block(data, Vector::new(w, h), Vector::new(0, 0), mode)?;
338        Ok(())
339    }
340
341    //--------------------------UTILS----------------------------//
342
343    /// Repeat mode texture wrap
344    pub fn repeat_mode(&self) {
345        self.active(0);
346        unsafe {
347            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
348
349            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
350        }
351        self.unbind();
352    }
353
354    /// Linear mode for filter
355    pub fn linear_mode(&self) {
356        self.active(0);
357        unsafe {
358            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
359            gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
360        }
361        self.unbind();
362    }
363
364    #[inline]
365    /// Unbind the texture
366    pub fn unbind(&self) {
367        unsafe {
368            gl::BindTexture(gl::TEXTURE_2D, 0);
369        }
370    }
371
372    #[inline]
373    /// Active texture num
374    pub fn active(&self, num: i32) {
375        unsafe {
376            gl::ActiveTexture(gl::TEXTURE0 + num as u32);
377            gl::BindTexture(gl::TEXTURE_2D, self.id);
378        }
379    }
380
381    unsafe fn default_param() {
382        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
383        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
384        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
385        gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
386    }
387
388    //-------------------------GETTER-----------------------//
389
390    /// Getter for color mode
391    pub fn rgb_mode(&self) -> &RgbMode {
392        &self.rgb_mode
393    }
394
395    /// Simple getter for width
396    pub fn width(&self) -> u32 {
397        self.width
398    }
399
400    /// Simple getter for height
401    pub fn height(&self) -> u32 {
402        self.height
403    }
404}
405
406impl Default for Texture {
407    /// Create a 1 white pixel texture
408    fn default() -> Texture {
409        let mut id = 0;
410        unsafe {
411            let data = vec![255, 255, 255, 255];
412            gl::GenTextures(1, &mut id);
413            gl::BindTexture(gl::TEXTURE_2D, id);
414            Texture::default_param();
415            gl::TexImage2D(
416                gl::TEXTURE_2D,
417                0,
418                gl::RGBA as i32,
419                1,
420                1,
421                0,
422                gl::RGBA,
423                gl::UNSIGNED_BYTE,
424                data.as_ptr() as *const c_void,
425            );
426        };
427
428        Texture {
429            id,
430            width: 1,
431            height: 1,
432            rgb_mode: RgbMode::RGBA,
433        }
434    }
435}
436
437/// Enum to wrap gl RGB modes
438#[derive(PartialEq, Clone, Copy, Debug, Eq)]
439pub enum RgbMode {
440    RGBA,
441    RGB,
442    RED,
443}
444
445impl RgbMode {
446    pub fn as_gl(self) -> GLenum {
447        match self {
448            RgbMode::RGBA => gl::RGBA,
449            RgbMode::RGB => gl::RGB,
450            RgbMode::RED => gl::RED,
451        }
452    }
453}
454
455#[derive(Debug)]
456pub enum TextureError {
457    UpdateSize(u32, u32, u32, u32),
458    UpdateMode(RgbMode, RgbMode),
459    FileError,
460    ImageLoading,
461    WriteFile,
462}
463
464use std::fmt;
465
466impl fmt::Display for TextureError {
467    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
468        match self {
469            TextureError::UpdateSize(x, new_x, y, new_y) => write!(
470                f,
471                "
472Error while updating texture: Sizes are not
473okay with this texture. x: {}
474< new_x: {}  | y: {} new_y: {}",
475                x, new_x, y, new_y
476            ),
477            TextureError::FileError => write!(f, "Error while openning given file."),
478            TextureError::ImageLoading => write!(f, "Error While loading image."),
479            TextureError::WriteFile => write!(f, "Error while writing texture to file."),
480            TextureError::UpdateMode(a, b) => write!(
481                f,
482                "You were trying to update a {:?} with a {:?} Texture",
483                a, b
484            ),
485        }
486    }
487}
488
489impl Error for TextureError {
490    fn cause(&self) -> Option<&Error> {
491        None
492    }
493}
494
495impl Drop for Texture {
496    fn drop(&mut self) {
497        unsafe {
498            println!("Texture {} deleted", self.id);
499            gl::DeleteTextures(1, &[self.id] as *const _);
500        }
501    }
502}
503
504#[cfg(test)]
505mod test {
506    extern crate test;
507
508    use self::test::Bencher;
509    use super::Vector;
510    use color::Color;
511    use texture::RgbMode;
512    use texture::Texture;
513    use window::Window;
514
515    #[bench]
516    fn from_color(b: &mut Bencher) {
517        let _ = Window::new(200, 200, "Loader");
518
519        b.iter(|| {
520            Texture::from_color(Color::new(1.0, 1.0, 1.0), Vector::new(100, 100));
521        });
522    }
523
524    #[bench]
525    fn from_slice(b: &mut Bencher) {
526        let _ = Window::new(200, 200, "Loader");
527
528        b.iter(|| {
529            let mut slice = vec![255; 10000];
530
531            Texture::from_slice(slice.as_mut_slice(), RgbMode::RGBA, 100, 100);
532        });
533    }
534
535    #[bench]
536    fn update_block(b: &mut Bencher) {
537        let _ = Window::new(200, 200, "Loader");
538
539        let mut text_host = Texture::from_color(Color::new(0.0, 1.0, 0.0), Vector::new(100, 100));
540        let text_guest = Texture::from_color(Color::new(0.0, 0.0, 1.0), Vector::new(10, 10));
541
542        b.iter(|| {
543            text_host
544                .update_block(
545                    text_guest.get_data().as_slice(),
546                    Vector::new(10, 10),
547                    Vector::new(10, 10),
548                    None,
549                )
550                .unwrap();
551        });
552    }
553}