diff --git a/examples/custom_app_icon.rs b/examples/custom_app_icon.rs new file mode 100644 index 00000000..ef580949 --- /dev/null +++ b/examples/custom_app_icon.rs @@ -0,0 +1,21 @@ +use macroquad::prelude::*; +use std::fs::read; + +fn conf() -> Conf { + let image = + Image::from_file_with_format(&read("examples/rustacean_happy.png").unwrap(), None).unwrap(); + let icon = image.as_app_icon(); + + Conf { + icon: Some(icon), + ..Default::default() + } +} + +#[macroquad::main(conf)] +async fn main() { + loop { + clear_background(LIGHTGRAY); + next_frame().await; + } +} diff --git a/examples/upscaling.rs b/examples/upscaling.rs new file mode 100644 index 00000000..3d022fea --- /dev/null +++ b/examples/upscaling.rs @@ -0,0 +1,17 @@ +use macroquad::prelude::*; + +#[macroquad::main("Upscaling")] +async fn main() { + let texture: Texture2D = load_texture("examples/rustacean_happy.png").await.unwrap(); + let double = Texture2D::from_image(&texture.get_texture_data().upscale(2)); + let triple = Texture2D::from_image(&texture.get_texture_data().upscale(3)); + + loop { + clear_background(LIGHTGRAY); + draw_texture(&texture, 40., 40., WHITE); + draw_texture(&double, 140., 140., WHITE); + draw_texture(&triple, 240., 240., WHITE); + + next_frame().await + } +} diff --git a/src/texture.rs b/src/texture.rs index c31c3e66..602597f3 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -8,6 +8,7 @@ use crate::{ pub use crate::quad_gl::FilterMode; use crate::quad_gl::{DrawMode, Vertex}; use glam::{vec2, Vec2}; +use miniquad::conf::Icon; use slotmap::{TextureIdSlotMap, TextureSlotId}; use std::sync::Arc; @@ -323,6 +324,97 @@ impl Image { ) .unwrap(); } + + /// Scales an image up by the given factor `n`. + /// Returns the new image. + pub fn upscale(&self, n: u16) -> Self { + let mut new_bytes = vec![]; + let mut current_line_length = 0; + + for pixel in self.get_image_data() { + // repeat n times horizontally + for _ in 0..n { + new_bytes.extend_from_slice(pixel); + } + current_line_length += 1; + + if current_line_length == self.width { + // repeat n - 1 times vertically, because one line already exists + let last_line = new_bytes + [(new_bytes.len() - (4 * self.width() * n as usize))..new_bytes.len()] + .to_vec(); + for _ in 0..n - 1 { + new_bytes.extend_from_slice(&last_line); + } + current_line_length = 0; + } + } + + Self { + width: self.width * n, + height: self.height * n, + bytes: new_bytes, + } + } + + /// Scales an image down by the given factor `n`. + /// Returns the new image. + /// + /// This will panic if the image width or height are not divisible by `n`. + fn downscale(&self, n: u16) -> Self { + assert_eq!(self.width % n, 0, "image width must be a multiple of n"); + assert_eq!(self.height % n, 0, "image height must be a multiple of n"); + + let mut new_bytes = vec![]; + + for line in 0..self.height { + for column in 0..self.width { + if line % n == 0 && column % n == 0 { + let index = usize::from(self.height * line + column); + new_bytes.extend(&self.bytes[(index * 4)..(index * 4 + 4)]); + } + } + } + + Self { + width: self.width / n, + height: self.height / n, + bytes: new_bytes, + } + } + + /// Converts this image into a miniquad icon that can be put into a [`Conf`](crate::prelude::Conf) + /// to use it as the window icon. + /// + /// Note that this function will panic if the image size is not one of `16x16`, `32x32` or `64x64`. + pub fn as_app_icon(&self) -> Icon { + fn image_bytes_to_array(image: &Image) -> [u8; N] { + let mut arr = [0u8; N]; + arr.copy_from_slice(image.bytes.as_slice()); + arr + } + + assert_eq!(self.width() * self.height() * 4, self.bytes.len()); + + match (self.width(), self.height()) { + (16, 16) => Icon { + small: image_bytes_to_array::<1024>(self), + medium: image_bytes_to_array::<4096>(&self.upscale(2)), + big: image_bytes_to_array::<16384>(&self.upscale(4)), + }, + (32, 32) => Icon { + small: image_bytes_to_array::<1024>(&self.downscale(2)), + medium: image_bytes_to_array::<4096>(self), + big: image_bytes_to_array::<16384>(&self.upscale(2)), + }, + (64, 64) => Icon { + small: image_bytes_to_array::<1024>(&self.downscale(4)), + medium: image_bytes_to_array::<4096>(&self.downscale(2)), + big: image_bytes_to_array::<16384>(self), + }, + _ => panic!("invalid image size, must be 16x16, 32x32 or 64x64"), + } + } } /// Loads an [Image] from a file into CPU memory.