good_web_game/graphics/
image.rs

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        // if we wanted to we could optimize this a bit by creating this buffer only once and then just cloning the handle
83        #[rustfmt::skip]
84        let vertices: [f32; 32] = [0.0, 0.0, // first pos
85                                  0.0, 0.0, // first texcoord
86                                  1.0, 1.0, 1.0, 1.0, // first color
87                                  1.0, 0.0, // second pos
88                                  1.0, 0.0, // second texcoord
89                                  1.0, 1.0, 1.0, 1.0, // second color
90                                  1.0, 1.0, // third pos
91                                  1.0, 1.0, // third texcoord
92                                  1.0, 1.0, 1.0, 1.0, // third color
93                                  0.0, 1.0, // fourth pos
94                                  0.0, 1.0, // fourth texcoord
95                                  1.0, 1.0, 1.0, 1.0]; // fourth color
96
97        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>(), // start out with space for one instance
102        );
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    /// A little helper function that creates a new `Image` that is just
127    /// a solid square of the given size and color.  Mainly useful for
128    /// debugging.
129    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    /// Returns the dimensions of the image.
154    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    /// Draws without adapting the scaling.
168    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(&param);
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(&current_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        // restore default blend mode
201        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        // We have to mess with the scale to make everything
221        // be its-unit-size-in-pixels.
222        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    /// Gets the blend mode to be used when drawing this drawable.
243    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}