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}