use crate::{
color::Color,
file::{load_file, FileError},
get_context, get_quad_context,
math::Rect,
};
use crate::quad_gl::{DrawMode, Vertex};
use glam::{vec2, Vec2};
pub use crate::quad_gl::FilterMode;
#[derive(Clone)]
pub struct Image {
pub bytes: Vec<u8>,
pub width: u16,
pub height: u16,
}
impl std::fmt::Debug for Image {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Image")
.field("width", &self.width)
.field("height", &self.height)
.field("bytes.len()", &self.bytes.len())
.finish()
}
}
impl Image {
pub fn empty() -> Image {
Image {
width: 0,
height: 0,
bytes: vec![],
}
}
pub fn from_file_with_format(bytes: &[u8], format: Option<image::ImageFormat>) -> Image {
let img = if let Some(fmt) = format {
image::load_from_memory_with_format(bytes, fmt)
.unwrap_or_else(|e| panic!("{}", e))
.to_rgba8()
} else {
image::load_from_memory(bytes)
.unwrap_or_else(|e| panic!("{}", e))
.to_rgba8()
};
let width = img.width() as u16;
let height = img.height() as u16;
let bytes = img.into_raw();
Image {
width,
height,
bytes,
}
}
pub fn gen_image_color(width: u16, height: u16, color: Color) -> Image {
let mut bytes = vec![0; width as usize * height as usize * 4];
for i in 0..width as usize * height as usize {
bytes[i * 4 + 0] = (color.r * 255.) as u8;
bytes[i * 4 + 1] = (color.g * 255.) as u8;
bytes[i * 4 + 2] = (color.b * 255.) as u8;
bytes[i * 4 + 3] = (color.a * 255.) as u8;
}
Image {
width,
height,
bytes,
}
}
pub fn update(&mut self, colors: &[Color]) {
assert!(self.width as usize * self.height as usize == colors.len());
for i in 0..colors.len() {
self.bytes[i * 4] = (colors[i].r * 255.) as u8;
self.bytes[i * 4 + 1] = (colors[i].g * 255.) as u8;
self.bytes[i * 4 + 2] = (colors[i].b * 255.) as u8;
self.bytes[i * 4 + 3] = (colors[i].a * 255.) as u8;
}
}
pub fn width(&self) -> usize {
self.width as usize
}
pub fn height(&self) -> usize {
self.height as usize
}
pub fn get_image_data(&self) -> &[[u8; 4]] {
use std::slice;
unsafe {
slice::from_raw_parts(
self.bytes.as_ptr() as *const [u8; 4],
self.width as usize * self.height as usize,
)
}
}
pub fn get_image_data_mut(&mut self) -> &mut [[u8; 4]] {
use std::slice;
unsafe {
slice::from_raw_parts_mut(
self.bytes.as_mut_ptr() as *mut [u8; 4],
self.width as usize * self.height as usize,
)
}
}
pub fn set_pixel(&mut self, x: u32, y: u32, color: Color) {
let width = self.width;
self.get_image_data_mut()[(y * width as u32 + x) as usize] = color.into();
}
pub fn get_pixel(&self, x: u32, y: u32) -> Color {
self.get_image_data()[(y * self.width as u32 + x) as usize].into()
}
pub fn sub_image(&self, rect: Rect) -> Image {
let width = rect.w as usize;
let height = rect.h as usize;
let mut bytes = vec![0; width * height * 4];
let x = rect.x as usize;
let y = rect.y as usize;
let mut n = 0;
for y in y..y + height {
for x in x..x + width {
bytes[n] = self.bytes[y * self.width as usize * 4 + x * 4 + 0];
bytes[n + 1] = self.bytes[y * self.width as usize * 4 + x * 4 + 1];
bytes[n + 2] = self.bytes[y * self.width as usize * 4 + x * 4 + 2];
bytes[n + 3] = self.bytes[y * self.width as usize * 4 + x * 4 + 3];
n += 4;
}
}
Image {
width: width as u16,
height: height as u16,
bytes,
}
}
pub fn export_png(&self, path: &str) {
let mut bytes = vec![0; self.width as usize * self.height as usize * 4];
for y in 0..self.height as usize {
for x in 0..self.width as usize * 4 {
bytes[y * self.width as usize * 4 + x] =
self.bytes[(self.height as usize - y - 1) * self.width as usize * 4 + x];
}
}
image::save_buffer(
path,
&bytes[..],
self.width as _,
self.height as _,
image::ColorType::Rgba8,
)
.unwrap();
}
}
pub async fn load_image(path: &str) -> Result<Image, FileError> {
let bytes = load_file(path).await?;
Ok(Image::from_file_with_format(&bytes, None))
}
pub async fn load_texture(path: &str) -> Result<Texture2D, FileError> {
let bytes = load_file(path).await?;
Ok(Texture2D::from_file_with_format(&bytes[..], None))
}
#[derive(Clone, Copy, Debug)]
pub struct RenderTarget {
pub texture: Texture2D,
pub render_pass: miniquad::RenderPass,
}
impl RenderTarget {
pub fn delete(&self) {
self.texture.delete();
let context = get_quad_context();
self.render_pass.delete(context);
}
}
pub fn render_target(width: u32, height: u32) -> RenderTarget {
let context = get_quad_context();
let texture = miniquad::Texture::new_render_texture(
context,
miniquad::TextureParams {
width,
height,
..Default::default()
},
);
let render_pass = miniquad::RenderPass::new(context, texture, None);
let texture = Texture2D::from_miniquad_texture(texture);
RenderTarget {
texture,
render_pass,
}
}
#[derive(Debug, Clone)]
pub struct DrawTextureParams {
pub dest_size: Option<Vec2>,
pub source: Option<Rect>,
pub rotation: f32,
pub flip_x: bool,
pub flip_y: bool,
pub pivot: Option<Vec2>,
}
impl Default for DrawTextureParams {
fn default() -> DrawTextureParams {
DrawTextureParams {
dest_size: None,
source: None,
rotation: 0.,
pivot: None,
flip_x: false,
flip_y: false,
}
}
}
pub fn draw_texture(texture: Texture2D, x: f32, y: f32, color: Color) {
draw_texture_ex(texture, x, y, color, Default::default());
}
pub fn draw_texture_ex(
texture: Texture2D,
x: f32,
y: f32,
color: Color,
params: DrawTextureParams,
) {
let context = get_context();
let Rect {
x: mut sx,
y: mut sy,
w: mut sw,
h: mut sh,
} = params.source.unwrap_or(Rect {
x: 0.,
y: 0.,
w: texture.width(),
h: texture.height(),
});
let mut texture = texture;
if let Some((batched_texture, uv)) = context.texture_batcher.get(texture) {
sx = ((sx / texture.width()) * uv.w + uv.x) * batched_texture.width();
sy = ((sy / texture.height()) * uv.h + uv.y) * batched_texture.height();
sw = (sw / texture.width()) * uv.w * batched_texture.width();
sh = (sh / texture.height()) * uv.h * batched_texture.height();
texture = batched_texture;
}
let (mut w, mut h) = match params.dest_size {
Some(dst) => (dst.x, dst.y),
_ => (sw, sh),
};
let mut x = x;
let mut y = y;
if params.flip_x {
x = x + w;
w = -w;
}
if params.flip_y {
y = y + h;
h = -h;
}
let pivot = params.pivot.unwrap_or(vec2(x + w / 2., y + h / 2.));
let m = pivot;
let p = [
vec2(x, y) - pivot,
vec2(x + w, y) - pivot,
vec2(x + w, y + h) - pivot,
vec2(x, y + h) - pivot,
];
let r = params.rotation;
let p = [
vec2(
p[0].x * r.cos() - p[0].y * r.sin(),
p[0].x * r.sin() + p[0].y * r.cos(),
) + m,
vec2(
p[1].x * r.cos() - p[1].y * r.sin(),
p[1].x * r.sin() + p[1].y * r.cos(),
) + m,
vec2(
p[2].x * r.cos() - p[2].y * r.sin(),
p[2].x * r.sin() + p[2].y * r.cos(),
) + m,
vec2(
p[3].x * r.cos() - p[3].y * r.sin(),
p[3].x * r.sin() + p[3].y * r.cos(),
) + m,
];
#[rustfmt::skip]
let vertices = [
Vertex::new(p[0].x, p[0].y, 0., sx /texture.width(), sy /texture.height(), color),
Vertex::new(p[1].x, p[1].y, 0., (sx + sw)/texture.width(), sy /texture.height(), color),
Vertex::new(p[2].x, p[2].y, 0., (sx + sw)/texture.width(), (sy + sh)/texture.height(), color),
Vertex::new(p[3].x, p[3].y, 0., sx /texture.width(), (sy + sh)/texture.height(), color),
];
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
context.gl.texture(Some(texture));
context.gl.draw_mode(DrawMode::Triangles);
context.gl.geometry(&vertices, &indices);
}
#[deprecated(since = "0.3.0", note = "Use draw_texture_ex instead")]
pub fn draw_texture_rec(
texture: Texture2D,
x: f32,
y: f32,
w: f32,
h: f32,
sx: f32,
sy: f32,
sw: f32,
sh: f32,
color: Color,
) {
draw_texture_ex(
texture,
x,
y,
color,
DrawTextureParams {
dest_size: Some(vec2(w, h)),
source: Some(Rect {
x: sx,
y: sy,
w: sw,
h: sh,
}),
..Default::default()
},
);
}
pub fn get_screen_data() -> Image {
unsafe {
crate::window::get_internal_gl().flush();
}
let context = get_context();
let texture = Texture2D::from_miniquad_texture(miniquad::Texture::new_render_texture(
get_quad_context(),
miniquad::TextureParams {
width: context.screen_width as _,
height: context.screen_height as _,
..Default::default()
},
));
texture.grab_screen();
texture.get_texture_data()
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Texture2D {
pub(crate) texture: miniquad::Texture,
}
impl Texture2D {
pub fn empty() -> Texture2D {
Texture2D {
texture: miniquad::Texture::empty(),
}
}
pub fn from_file_with_format<'a>(
bytes: &[u8],
format: Option<image::ImageFormat>,
) -> Texture2D {
let img = if let Some(fmt) = format {
image::load_from_memory_with_format(bytes, fmt)
.unwrap_or_else(|e| panic!("{}", e))
.to_rgba8()
} else {
image::load_from_memory(bytes)
.unwrap_or_else(|e| panic!("{}", e))
.to_rgba8()
};
let width = img.width() as u16;
let height = img.height() as u16;
let bytes = img.into_raw();
Self::from_rgba8(width, height, &bytes)
}
pub fn from_image(image: &Image) -> Texture2D {
Texture2D::from_rgba8(image.width, image.height, &image.bytes)
}
pub fn from_miniquad_texture(texture: miniquad::Texture) -> Texture2D {
Texture2D { texture }
}
pub fn from_rgba8(width: u16, height: u16, bytes: &[u8]) -> Texture2D {
let ctx = get_context();
let texture = miniquad::Texture::from_rgba8(get_quad_context(), width, height, bytes);
let texture = Texture2D { texture };
ctx.texture_batcher.add_unbatched(texture);
texture
}
pub fn update(&self, image: &Image) {
assert_eq!(self.texture.width, image.width as u32);
assert_eq!(self.texture.height, image.height as u32);
let ctx = get_quad_context();
self.texture.update(ctx, &image.bytes);
}
pub fn width(&self) -> f32 {
self.texture.width as f32
}
pub fn height(&self) -> f32 {
self.texture.height as f32
}
pub fn set_filter(&self, filter_mode: FilterMode) {
let ctx = get_quad_context();
self.texture.set_filter(ctx, filter_mode);
}
pub fn raw_miniquad_texture_handle(&self) -> miniquad::Texture {
self.texture
}
pub fn grab_screen(&self) {
use miniquad::*;
let (internal_format, _, _) = self.texture.format.into();
unsafe {
gl::glBindTexture(gl::GL_TEXTURE_2D, self.texture.gl_internal_id());
gl::glCopyTexImage2D(
gl::GL_TEXTURE_2D,
0,
internal_format,
0,
0,
self.texture.width as _,
self.texture.height as _,
0,
);
}
}
pub fn get_texture_data(&self) -> Image {
let mut image = Image {
width: self.texture.width as _,
height: self.texture.height as _,
bytes: vec![0; self.texture.width as usize * self.texture.height as usize * 4],
};
self.texture.read_pixels(&mut image.bytes);
image
}
pub fn delete(&self) {
self.raw_miniquad_texture_handle().delete()
}
}
pub(crate) struct Batcher {
unbatched: Vec<Texture2D>,
atlas: crate::text::atlas::Atlas,
}
impl Batcher {
pub fn new(ctx: &mut miniquad::Context) -> Batcher {
Batcher {
unbatched: vec![],
atlas: crate::text::atlas::Atlas::new(ctx, miniquad::FilterMode::Linear),
}
}
pub fn add_unbatched(&mut self, texture: Texture2D) {
self.unbatched.push(texture);
}
pub fn get(&mut self, texture: Texture2D) -> Option<(Texture2D, Rect)> {
let id = texture.raw_miniquad_texture_handle().gl_internal_id();
let uv_rect = self.atlas.get_uv_rect(id as _)?;
Some((self.atlas.texture(), uv_rect))
}
}
pub fn build_textures_atlas() {
let context = get_context();
for texture in context.texture_batcher.unbatched.drain(0..) {
let sprite: Image = texture.get_texture_data();
let id = texture.raw_miniquad_texture_handle().gl_internal_id();
context.texture_batcher.atlas.cache_sprite(id as _, sprite);
}
let texture = context.texture_batcher.atlas.texture();
crate::telemetry::log_string(&format!("Atlas: {} {}", texture.width(), texture.height()));
}