Skip to main content

fae/
image.rs

1use crate::error::ImageCreationError;
2#[cfg(feature = "png")]
3use crate::error::PngLoadingError;
4use crate::gl;
5use crate::gl::types::*;
6#[cfg(feature = "png")]
7use png;
8
9/// Contains the raw pixel color data of an image.
10#[derive(Clone, Debug)]
11pub struct Image {
12    /// The pixels of the image.
13    pub pixels: Vec<u8>,
14    /// The width of the image.
15    pub width: i32,
16    /// The height of the image.
17    pub height: i32,
18    /// The OpenGL format of the image.
19    pub format: GLuint,
20    /// The OpenGL type of the pixels of the image.
21    pub pixel_type: GLuint,
22    /// Whether the image represents a null pointer for
23    /// glTexImage2D. If true, the memory for the texture of width x
24    /// height will be allocated on the GPU, but will probably be
25    /// garbage.
26    pub null_data: bool,
27}
28
29impl Image {
30    /// Parses a PNG image and makes an `Image` out of it.
31    ///
32    /// This function assumes that the image is in SRGB space, so the
33    /// image `format` defaults to `SRGB` or `SRGB_ALPHA` if the image
34    /// contains the RGB or RGBA components.
35    ///
36    /// # Color type note
37    ///
38    /// If your image has a
39    /// [`ColorType`](https://docs.rs/png/0.15.2/png/enum.ColorType.html)
40    /// of Grayscale, Indexed or GrayscaleAlpha, it will not display
41    /// as you would imagine with the default shaders. These images
42    /// will use `GL_RED`, `GL_RED`, and `GL_RG` as their format when
43    /// uploading the texture, so you need to take that into account
44    /// in your shaders (eg. when using GrayscaleAlpha, you'd use the
45    /// `color.g` value as your alpha, and `color.r` as your grayscale
46    /// value).
47    ///
48    /// # Errors
49    ///
50    /// A [`PngError`](enum.PngLoadingError.html#variant.PngError)
51    /// will be returned if the data couldn't be read for some reason
52    /// by the `png` crate (most probably, `bytes` doesn't describe a
53    /// valid PNG image). An
54    /// [`UnsupportedBitDepth`](enum.PngLoadingError.html#variant.UnsupportedBitDepth)
55    /// error will be returned if the PNG bit depth isn't 8 or 16 bits
56    /// per channel.
57    ///
58    /// # Example
59    /// ```no_run
60    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
61    /// let sprite = fae::Image::with_png(&std::fs::read("sprite.png")?)?;
62    /// # Ok(()) }
63    /// ```
64    #[cfg(feature = "png")]
65    pub fn with_png(bytes: &[u8]) -> Result<Image, PngLoadingError> {
66        use png::{BitDepth, ColorType, Decoder};
67        let decoder = Decoder::new(bytes);
68        let (info, mut reader) = decoder.read_info()?;
69        let format = match info.color_type {
70            ColorType::RGB => gl::SRGB,
71            ColorType::RGBA => gl::SRGB_ALPHA,
72            ColorType::Grayscale | ColorType::Indexed => gl::RED,
73            ColorType::GrayscaleAlpha => gl::RG,
74        };
75        let pixel_type = match info.bit_depth {
76            BitDepth::Eight => gl::UNSIGNED_BYTE,
77            BitDepth::Sixteen => gl::UNSIGNED_SHORT,
78            bitdepth => return Err(PngLoadingError::UnsupportedBitDepth(bitdepth)),
79        };
80        let mut pixels = vec![0; info.buffer_size()];
81        reader.next_frame(&mut pixels)?;
82        Ok(Image {
83            pixels,
84            width: info.width as i32,
85            height: info.height as i32,
86            format,
87            pixel_type,
88            null_data: false,
89        })
90    }
91
92    /// Creates a solid color image.
93    ///
94    /// The color can be 1-4 items long, and will be interpreted in
95    /// the following order: red, green, blue, alpha.
96    ///
97    /// The color is interpreted as SRGB when 3 or 4 color components
98    /// are provided, to be consistent with loading images from the
99    /// disk, which are assumed to be in SRGB space by default.
100    ///
101    /// Based on the length of the `color` slice, the format of the
102    /// resulting image will be `gl::RED`, `gl::RG`, `gl::SRGB`, or
103    /// `gl::SRGB_ALPHA`. This can be changed with
104    /// [`format()`](struct.Image.html#method.format).
105    ///
106    /// # Example
107    /// ```
108    /// use fae::Image;
109    /// let image = Image::with_color(128, 128, &[0xB4, 0x6E, 0xC8, 0xFF]);
110    /// // image now represents a 128px by 128px image that consists of fully opaque violet pixels.
111    /// ```
112    pub fn with_color(width: i32, height: i32, color: &[u8]) -> Result<Image, ImageCreationError> {
113        let format = match color.len() {
114            4 => gl::SRGB_ALPHA,
115            3 => gl::SRGB,
116            2 => gl::RG,
117            1 => gl::RED,
118            n => return Err(ImageCreationError::InvalidColorComponentCount(n)),
119        };
120        let mut pixels = vec![0; (width * height) as usize * color.len()];
121        for i in 0..pixels.len() {
122            pixels[i] = color[i % color.len()];
123        }
124        Ok(Image {
125            pixels,
126            width,
127            height,
128            format,
129            pixel_type: gl::UNSIGNED_BYTE,
130            null_data: false,
131        })
132    }
133
134    /// Creates an image with a specified width, height and a format,
135    /// and signals to OpenGL that the texture will be filled in
136    /// later. The memory for the texture will be allocated on the
137    /// GPU, but no pixel data needs to be sent from the CPU to the
138    /// GPU during initialization.
139    ///
140    /// See also:
141    /// [`Spritesheet::upload_texture_region`](struct.Spritesheet.html#method.upload_texture_region).
142    pub fn with_null_texture(width: i32, height: i32, format: GLuint) -> Image {
143        Image {
144            pixels: Vec::new(),
145            width,
146            height,
147            format,
148            pixel_type: gl::UNSIGNED_BYTE,
149            null_data: true,
150        }
151    }
152
153    /// Sets the format of the pixels.
154    ///
155    /// Generally: `gl::RED` for grayscale pixels, `gl::RGB` for
156    /// linear non-transparent pixels, and `gl::RGBA` for linear and
157    /// transparent pixels.
158    ///
159    /// # Example
160    /// ```
161    /// use fae::{gl, Image};
162    /// # fn main() -> Result<(), fae::Error> {
163    /// let image = Image::with_color(128, 128, &[0x88])?.format(gl::RED);
164    /// // image now represents a 128px by 128px image that consists of half-red pixels taking up only one byte per pixel.
165    /// # Ok(()) }
166    /// ```
167    pub fn format(&mut self, format: GLuint) -> &mut Self {
168        self.format = format;
169        self
170    }
171}