use cgmath::Matrix4;
use std::path;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{
error::GameResult,
filesystem,
graphics::{BlendMode, DrawParam, Drawable, InstanceAttributes, Rect},
Context, GameError,
};
use miniquad::{Bindings, Buffer, BufferType, PassAction, Texture};
use crate::graphics::{apply_uniforms, Color};
pub use miniquad::graphics::FilterMode;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Image {
pub(crate) texture: Texture,
pub(crate) width: u16,
pub(crate) height: u16,
filter: FilterMode,
pub(crate) bindings: Bindings,
blend_mode: Option<BlendMode>,
dirty_filter: DirtyFlag,
pub(crate) bindings_clones_hack: Arc<()>,
pub(crate) texture_clones_hack: Arc<()>,
}
impl Image {
pub fn new<P: AsRef<path::Path>>(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
path: P,
) -> GameResult<Self> {
use std::io::Read;
let mut file = filesystem::open(ctx, path)?;
let mut bytes = vec![];
file.bytes.read_to_end(&mut bytes)?;
Self::from_png_bytes(ctx, quad_ctx, &bytes)
}
pub fn from_png_bytes(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
bytes: &[u8],
) -> GameResult<Self> {
match image::load_from_memory(bytes) {
Ok(img) => {
let rgba = img.to_rgba();
let width = rgba.width() as u16;
let height = rgba.height() as u16;
let bytes = rgba.into_raw();
Image::from_rgba8(ctx, quad_ctx, width, height, &bytes)
}
Err(e) => Err(GameError::ResourceLoadError(e.to_string())),
}
}
pub fn from_rgba8(
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
width: u16,
height: u16,
bytes: &[u8],
) -> GameResult<Image> {
let texture = Texture::from_rgba8(quad_ctx, width, height, bytes);
Self::from_texture(quad_ctx, texture, ctx.gfx_context.default_filter)
}
pub fn from_texture(
ctx: &mut miniquad::Context,
texture: Texture,
filter: FilterMode,
) -> GameResult<Image> {
#[rustfmt::skip]
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);
let attribute_buffer = Buffer::stream(
ctx,
BufferType::VertexBuffer,
std::mem::size_of::<InstanceAttributes>(), );
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let index_buffer = Buffer::immutable(ctx, BufferType::IndexBuffer, &indices);
let bindings = Bindings {
vertex_buffers: vec![vertex_buffer, attribute_buffer],
index_buffer,
images: vec![texture],
};
Ok(Image {
width: texture.width as u16,
height: texture.height as u16,
texture,
bindings,
blend_mode: None,
dirty_filter: DirtyFlag::new(false),
filter,
bindings_clones_hack: Arc::new(()),
texture_clones_hack: Arc::new(()),
})
}
pub fn solid(
context: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
size: u16,
color: Color,
) -> GameResult<Self> {
let (r, g, b, a) = color.into();
let pixel_array: [u8; 4] = [r, g, b, a];
let size_squared = usize::from(size) * usize::from(size);
let mut buffer = Vec::with_capacity(size_squared);
for _i in 0..size_squared {
buffer.extend(&pixel_array[..]);
}
Image::from_rgba8(context, quad_ctx, size, size, &buffer)
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.height
}
pub fn dimensions(&self) -> Rect {
Rect::new(0.0, 0.0, self.width() as f32, self.height() as f32)
}
pub fn set_filter(&mut self, filter: FilterMode) {
self.dirty_filter.store(true);
self.filter = filter;
}
pub fn filter(&self) -> FilterMode {
self.filter
}
pub(crate) fn draw_image_raw(
&self,
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: DrawParam,
) -> GameResult {
let instance = InstanceAttributes::from(¶m);
self.bindings.vertex_buffers[1].update(quad_ctx, &[instance]);
if self.dirty_filter.load() {
self.dirty_filter.store(false);
self.texture.set_filter(quad_ctx, self.filter);
}
let pass = ctx.framebuffer();
quad_ctx.begin_pass(pass, PassAction::Nothing);
quad_ctx.apply_bindings(&self.bindings);
let shader_id = *ctx.gfx_context.current_shader.borrow();
let current_shader = &mut ctx.gfx_context.shaders[shader_id];
quad_ctx.apply_pipeline(¤t_shader.pipeline);
apply_uniforms(ctx, quad_ctx, shader_id, None);
let mut custom_blend = false;
if let Some(blend_mode) = self.blend_mode() {
custom_blend = true;
crate::graphics::set_current_blend_mode(quad_ctx, blend_mode)
}
quad_ctx.draw(0, 6, 1);
if custom_blend {
crate::graphics::restore_blend_mode(ctx, quad_ctx);
}
quad_ctx.end_render_pass();
Ok(())
}
}
impl Drawable for Image {
fn draw(
&self,
ctx: &mut Context,
quad_ctx: &mut miniquad::graphics::GraphicsContext,
param: DrawParam,
) -> GameResult {
let src_width = param.src.w;
let src_height = param.src.h;
let scale_x = src_width * f32::from(self.width);
let scale_y = src_height * f32::from(self.height);
let new_param = match param.trans {
crate::graphics::Transform::Values { scale, .. } => param.scale(mint::Vector2 {
x: scale.x * scale_x,
y: scale.y * scale_y,
}),
crate::graphics::Transform::Matrix(m) => param.transform(
Matrix4::from(m) * Matrix4::from_nonuniform_scale(scale_x, scale_y, 1.0),
),
};
self.draw_image_raw(ctx, quad_ctx, new_param)
}
fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
self.blend_mode = mode;
}
fn blend_mode(&self) -> Option<BlendMode> {
self.blend_mode
}
fn dimensions(&self, _ctx: &mut Context) -> Option<Rect> {
Some(self.dimensions())
}
}
impl Drop for Image {
fn drop(&mut self) {
if Arc::strong_count(&self.bindings_clones_hack) == 1 {
crate::graphics::add_dropped_bindings(
self.bindings.clone(),
Arc::strong_count(&self.texture_clones_hack) == 1,
);
}
}
}
#[derive(Debug)]
struct DirtyFlag(AtomicBool);
impl DirtyFlag {
pub fn new(value: bool) -> Self {
Self(AtomicBool::new(value))
}
pub fn load(&self) -> bool {
self.0.load(Ordering::Acquire)
}
pub fn store(&self, value: bool) {
self.0.store(value, Ordering::Release)
}
}
impl Clone for DirtyFlag {
fn clone(&self) -> Self {
DirtyFlag(AtomicBool::new(self.0.load(Ordering::Acquire)))
}
}