1use cgmath::Matrix4;
2use std::path;
3use std::sync::atomic::{AtomicBool, Ordering};
4
5use crate::{
6 error::GameResult,
7 filesystem,
8 graphics::{BlendMode, DrawParam, Drawable, InstanceAttributes, Rect},
9 Context, GameError,
10};
11
12use miniquad::{Bindings, Buffer, BufferType, PassAction, Texture};
13
14use crate::graphics::{apply_uniforms, Color};
15pub use miniquad::graphics::FilterMode;
16use std::sync::Arc;
17
18#[derive(Clone, Debug)]
19pub struct Image {
20 pub(crate) texture: Texture,
21 pub(crate) width: u16,
22 pub(crate) height: u16,
23 filter: FilterMode,
24 pub(crate) bindings: Bindings,
25 blend_mode: Option<BlendMode>,
26 dirty_filter: DirtyFlag,
27 pub(crate) bindings_clones_hack: Arc<()>,
28 pub(crate) texture_clones_hack: Arc<()>,
29}
30
31impl Image {
32 pub fn new<P: AsRef<path::Path>>(
33 ctx: &mut Context,
34 quad_ctx: &mut miniquad::graphics::GraphicsContext,
35 path: P,
36 ) -> GameResult<Self> {
37 use std::io::Read;
38
39 let mut file = filesystem::open(ctx, path)?;
40
41 let mut bytes = vec![];
42 file.bytes.read_to_end(&mut bytes)?;
43
44 Self::from_png_bytes(ctx, quad_ctx, &bytes)
45 }
46
47 pub fn from_png_bytes(
48 ctx: &mut Context,
49 quad_ctx: &mut miniquad::graphics::GraphicsContext,
50 bytes: &[u8],
51 ) -> GameResult<Self> {
52 match image::load_from_memory(bytes) {
53 Ok(img) => {
54 let rgba = img.to_rgba();
55
56 let width = rgba.width() as u16;
57 let height = rgba.height() as u16;
58 let bytes = rgba.into_raw();
59
60 Image::from_rgba8(ctx, quad_ctx, width, height, &bytes)
61 }
62 Err(e) => Err(GameError::ResourceLoadError(e.to_string())),
63 }
64 }
65
66 pub fn from_rgba8(
67 ctx: &mut Context,
68 quad_ctx: &mut miniquad::graphics::GraphicsContext,
69 width: u16,
70 height: u16,
71 bytes: &[u8],
72 ) -> GameResult<Image> {
73 let texture = Texture::from_rgba8(quad_ctx, width, height, bytes);
74 Self::from_texture(quad_ctx, texture, ctx.gfx_context.default_filter)
75 }
76
77 pub fn from_texture(
78 ctx: &mut miniquad::Context,
79 texture: Texture,
80 filter: FilterMode,
81 ) -> GameResult<Image> {
82 #[rustfmt::skip]
84 let vertices: [f32; 32] = [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0]; let vertex_buffer = Buffer::immutable(ctx, BufferType::VertexBuffer, &vertices);
98 let attribute_buffer = Buffer::stream(
99 ctx,
100 BufferType::VertexBuffer,
101 std::mem::size_of::<InstanceAttributes>(), );
103
104 let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
105 let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
106
107 let bindings = Bindings {
108 vertex_buffers: vec![vertex_buffer, attribute_buffer],
109 index_buffer,
110 images: vec![texture],
111 };
112
113 Ok(Image {
114 width: texture.width as u16,
115 height: texture.height as u16,
116 texture,
117 bindings,
118 blend_mode: None,
119 dirty_filter: DirtyFlag::new(false),
120 filter,
121 bindings_clones_hack: Arc::new(()),
122 texture_clones_hack: Arc::new(()),
123 })
124 }
125
126 pub fn solid(
130 context: &mut Context,
131 quad_ctx: &mut miniquad::graphics::GraphicsContext,
132 size: u16,
133 color: Color,
134 ) -> GameResult<Self> {
135 let (r, g, b, a) = color.into();
136 let pixel_array: [u8; 4] = [r, g, b, a];
137 let size_squared = usize::from(size) * usize::from(size);
138 let mut buffer = Vec::with_capacity(size_squared);
139 for _i in 0..size_squared {
140 buffer.extend(&pixel_array[..]);
141 }
142 Image::from_rgba8(context, quad_ctx, size, size, &buffer)
143 }
144
145 pub fn width(&self) -> u16 {
146 self.width
147 }
148
149 pub fn height(&self) -> u16 {
150 self.height
151 }
152
153 pub fn dimensions(&self) -> Rect {
155 Rect::new(0.0, 0.0, self.width() as f32, self.height() as f32)
156 }
157
158 pub fn set_filter(&mut self, filter: FilterMode) {
159 self.dirty_filter.store(true);
160 self.filter = filter;
161 }
162
163 pub fn filter(&self) -> FilterMode {
164 self.filter
165 }
166
167 pub(crate) fn draw_image_raw(
169 &self,
170 ctx: &mut Context,
171 quad_ctx: &mut miniquad::graphics::GraphicsContext,
172 param: DrawParam,
173 ) -> GameResult {
174 let instance = InstanceAttributes::from(¶m);
175 self.bindings.vertex_buffers[1].update(quad_ctx, &[instance]);
176
177 if self.dirty_filter.load() {
178 self.dirty_filter.store(false);
179 self.texture.set_filter(quad_ctx, self.filter);
180 }
181
182 let pass = ctx.framebuffer();
183 quad_ctx.begin_pass(pass, PassAction::Nothing);
184 quad_ctx.apply_bindings(&self.bindings);
185
186 let shader_id = *ctx.gfx_context.current_shader.borrow();
187 let current_shader = &mut ctx.gfx_context.shaders[shader_id];
188 quad_ctx.apply_pipeline(¤t_shader.pipeline);
189
190 apply_uniforms(ctx, quad_ctx, shader_id, None);
191
192 let mut custom_blend = false;
193 if let Some(blend_mode) = self.blend_mode() {
194 custom_blend = true;
195 crate::graphics::set_current_blend_mode(quad_ctx, blend_mode)
196 }
197
198 quad_ctx.draw(0, 6, 1);
199
200 if custom_blend {
202 crate::graphics::restore_blend_mode(ctx, quad_ctx);
203 }
204
205 quad_ctx.end_render_pass();
206
207 Ok(())
208 }
209}
210
211impl Drawable for Image {
212 fn draw(
213 &self,
214 ctx: &mut Context,
215 quad_ctx: &mut miniquad::graphics::GraphicsContext,
216 param: DrawParam,
217 ) -> GameResult {
218 let src_width = param.src.w;
219 let src_height = param.src.h;
220 let scale_x = src_width * f32::from(self.width);
223 let scale_y = src_height * f32::from(self.height);
224
225 let new_param = match param.trans {
226 crate::graphics::Transform::Values { scale, .. } => param.scale(mint::Vector2 {
227 x: scale.x * scale_x,
228 y: scale.y * scale_y,
229 }),
230 crate::graphics::Transform::Matrix(m) => param.transform(
231 Matrix4::from(m) * Matrix4::from_nonuniform_scale(scale_x, scale_y, 1.0),
232 ),
233 };
234
235 self.draw_image_raw(ctx, quad_ctx, new_param)
236 }
237
238 fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
239 self.blend_mode = mode;
240 }
241
242 fn blend_mode(&self) -> Option<BlendMode> {
244 self.blend_mode
245 }
246
247 fn dimensions(&self, _ctx: &mut Context) -> Option<Rect> {
248 Some(self.dimensions())
249 }
250}
251
252impl Drop for Image {
253 fn drop(&mut self) {
254 if Arc::strong_count(&self.bindings_clones_hack) == 1 {
255 crate::graphics::add_dropped_bindings(
256 self.bindings.clone(),
257 Arc::strong_count(&self.texture_clones_hack) == 1,
258 );
259 }
260 }
261}
262
263#[derive(Debug)]
264struct DirtyFlag(AtomicBool);
265
266impl DirtyFlag {
267 pub fn new(value: bool) -> Self {
268 Self(AtomicBool::new(value))
269 }
270
271 pub fn load(&self) -> bool {
272 self.0.load(Ordering::Acquire)
273 }
274
275 pub fn store(&self, value: bool) {
276 self.0.store(value, Ordering::Release)
277 }
278}
279
280impl Clone for DirtyFlag {
281 fn clone(&self) -> Self {
282 DirtyFlag(AtomicBool::new(self.0.load(Ordering::Acquire)))
283 }
284}