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
78pub struct Texture {
84 id: GLuint,
85 width: u32,
86 height: u32,
87}
88
89impl Texture {
90 #[inline(always)]
92 pub fn new(id: GLuint, width: u32, height: u32) -> Self {
93 Texture { id, width, height }
94 }
95
96 #[inline(always)]
98 pub fn get_id(&self) -> GLuint {
99 self.id
100 }
101
102 pub fn empty(settings: &TextureSettings) -> Result<Self, String> {
104 CreateTexture::create(&mut (), Format::Rgba8, &[0u8; 4], [1, 1], settings)
105 }
106
107 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 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 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 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 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}