diff --git a/src/canvas.rs b/src/canvas.rs index dbea979..38d47ae 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -28,7 +28,7 @@ pub trait Application { //--Canvas struct----------------------------------------------------------------------------------- pub struct Canvas { default_texture: TextureHandle, - clear_color: Pixel, + clear_color: Color, renderer: WgpuRenderer, } @@ -149,7 +149,7 @@ impl Canvas { sprite.render(&mut self.renderer); } - pub fn set_clear_color(&mut self, color: Pixel) { + pub fn set_clear_color(&mut self, color: Color) { self.clear_color = color; } diff --git a/src/main.rs b/src/main.rs index 0532dc6..c2f1420 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ impl Application for ExampleApp { fn init(canvas: &mut Canvas) -> Result { use canvas::{ - utils::Size, + utils::{Size, Color}, sprite::{Sprite, Center}, }; @@ -81,6 +81,7 @@ impl Application for ExampleApp { let mut txt_sprite = canvas.create_text_sprite("00", Size {w: 100, h: 100}, 22.0); txt_sprite.set_center(Center::BotLeft); txt_sprite.set_position(Position {x:100, y:100}); + txt_sprite.set_bg_color(Color::WHITE); txt_sprite.set_scale(1.0); canvas.clear(); @@ -119,9 +120,9 @@ impl Application for ExampleApp { state.txt_sprite.set_text_size(elapsed as f32); } match state.frame_counter { - 0 => state.txt_sprite.set_color(Color::RED), - 20 => state.txt_sprite.set_color(Color::BLUE), - 40 => state.txt_sprite.set_color(Color::GREEN), + 0 => state.txt_sprite.set_fg_color(Color::RED), + 20 => state.txt_sprite.set_fg_color(Color::BLUE), + 40 => state.txt_sprite.set_fg_color(Color::GREEN), _ => (), } //state.sub_sprite.for_each(|pix| unsafe {pix.flat = pix.flat.wrapping_add(1);}); diff --git a/src/renderer.rs b/src/renderer.rs index 30a17d7..412255c 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -4,7 +4,7 @@ use log::{debug, error, info, trace, warn}; use raw_window_handle::HasRawWindowHandle; use wgpu; -use crate::utils::{Pixel, Size}; +use crate::utils::{Color, Size}; pub mod utils; @@ -100,7 +100,7 @@ impl WgpuRenderer { self.surface_size } - pub fn clear(&mut self, color: Pixel) { + pub fn clear(&mut self, color: Color) { let view = self.create_texture_view(); let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { @@ -225,16 +225,23 @@ impl WgpuRenderer { //--Renderer struct utils--------------------------------------------------------------------------- -impl From for wgpu::Color { +//impl From for wgpu::Color { +// +// fn from(value: Pixel) -> Self { +// Self { +// r: f64::from(value.r) / 255.0, +// g: f64::from(value.g) / 255.0, +// b: f64::from(value.b) / 255.0, +// a: f64::from(value.a) / 255.0, +// } +// } +//} - fn from(pix: Pixel) -> Self { - let rgba = pix.to_rgba(); - Self { - r: (rgba.r as f64) / 255.0, - g: (rgba.g as f64) / 255.0, - b: (rgba.b as f64) / 255.0, - a: (rgba.a as f64) / 255.0, - } - } +impl From for wgpu::Color { + + fn from(value: Color) -> Self { + value.into_wgpu_color() + } } + diff --git a/src/sprite/text_sprite.rs b/src/sprite/text_sprite.rs index d9a7c45..c1c5e91 100644 --- a/src/sprite/text_sprite.rs +++ b/src/sprite/text_sprite.rs @@ -18,7 +18,8 @@ pub struct TextSprite { text: String, text_size: f32, - text_color: Pixel, + fg_color: Color, + bg_color: Color, font: fontdue::Font, layout: fontdue::layout::Layout, @@ -41,29 +42,8 @@ impl TextSprite { layout.append(&[&font], &TextStyle::new(text, text_size, 0)); //draw text to the texture - let mut raw_texture : Vec:: - = vec![Color::NONE; (size.h * size.w).try_into().unwrap()]; - - //draw each glyph at the corresponding position on the texture - for glyph in layout.glyphs() { - let (_metrics, bitmap) = font.rasterize_config(glyph.key); - - let glyph_x = glyph.x as usize; - let glyph_y = glyph.y as usize; - let size_w = size.w as usize; - let size_h = size.h as usize; - - //only try to draw a glyph if its origin fits in the texture - if glyph_x < size_w && glyph_y < size_h { - let origin = glyph_x + size_w * glyph_y; - - //glyphs that do not fit on the texture are cropped at the edge of the latter - for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) { - for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) { - - raw_texture[origin + x + y * size_w] - = Color::BLACK.with_alpha(bitmap[x + y * glyph.width]); - } } } } + let raw_texture : Vec:: + = vec![Color::NONE.into(); (size.h * size.w).try_into().unwrap()]; //create the texture let texture = Texture::create(renderer, bytemuck::cast_slice(&raw_texture), size); @@ -75,10 +55,11 @@ impl TextSprite { texture_sprite, text: text.to_string(), text_size, - text_color: Color::BLACK, + fg_color: Color::BLACK, + bg_color: Color::NONE, font, layout, - text_generation_needed: false, + text_generation_needed: true, } } @@ -92,9 +73,18 @@ impl TextSprite { self.text_generation_needed = true; } - pub fn set_color(&mut self, color: Pixel) { - self.text_color = color; - self.text_generation_needed = true; + pub fn set_fg_color(&mut self, color: Color) { + if self.fg_color != color { + self.fg_color = color; + self.text_generation_needed = true; + } + } + + pub fn set_bg_color(&mut self, color: Color) { + if self.bg_color != color { + self.bg_color = color; + self.text_generation_needed = true; + } } fn generate_text(&mut self) { @@ -104,7 +94,7 @@ impl TextSprite { &fontdue::layout::TextStyle::new(&self.text, self.text_size, 0)); let size = self.texture_sprite.get_size(); - self.texture_sprite.fill(Color::NONE); + self.texture_sprite.fill(self.bg_color.into()); //draw each glyph at the corresponding position on the texture for glyph in self.layout.glyphs() { @@ -122,12 +112,15 @@ impl TextSprite { for x in 0..std::cmp::min(glyph.width, size_w - glyph_x) { for y in 0..std::cmp::min(glyph.height, size_h - glyph_y) { + self.fg_color.set_alpha(bitmap[x + y * glyph.width]); + self.texture_sprite.set_pixel( Position { x: (glyph_x + x) as i32, y: (glyph_y + y) as i32, }, - self.text_color.with_alpha(bitmap[x + y * glyph.width]) + + Color::alpha_blend(&self.fg_color, &self.bg_color).into() ); } } } } } diff --git a/src/utils.rs b/src/utils.rs index 62e5cf6..df4d559 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -105,57 +105,110 @@ impl From for Vector2 { } //--Pixel struct------------------------------------------------------------------------------------ -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -#[repr(C)] -pub struct Rgba { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, +#[repr(packed, C)] +#[derive(Copy, Clone, PartialEq, bytemuck::Pod, bytemuck::Zeroable)] +pub struct Pixel { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, } -#[derive(Copy, Clone, bytemuck::Zeroable)] -pub union Pixel { - pub flat: u32, - rgba: Rgba, -} +impl From for Pixel { -unsafe impl bytemuck::Pod for Pixel {} + fn from(value: Color) -> Self { + value.into_pixel() + } +} impl Pixel { - pub fn rgb(r: u8, g: u8, b: u8) -> Pixel { - Pixel { - rgba: Rgba{r, g, b, a: 255}, - } - } + pub const fn from_hex(hex: u32) -> Self { + let bytes = hex.to_be_bytes(); + Self { r: bytes[0], g: bytes[1], b: bytes[2], a: bytes[3] } + } - pub fn rgba(r: u8, g: u8, b: u8, a: u8) -> Pixel { - Pixel { - rgba: Rgba{r, g, b, a}, - } - } - - pub fn with_alpha(self, a: u8) -> Pixel { - unsafe { Pixel::rgba(self.rgba.r, self.rgba.g, self.rgba.b, a) } - } - - pub fn to_rgba(&self) -> Rgba { - unsafe { self.rgba } - } } //--Color values------------------------------------------------------------------------------------ #[non_exhaustive] -pub struct Color; - -impl Color { - pub const NONE: Pixel = Pixel {flat: 0x00000000}; - pub const WHITE: Pixel = Pixel {flat: 0xffffffff}; - pub const BLACK: Pixel = Pixel {flat: 0xff000000}; - pub const RED: Pixel = Pixel {flat: 0xff0000ff}; - pub const GREEN: Pixel = Pixel {flat: 0xff00ff00}; - pub const BLUE: Pixel = Pixel {flat: 0xffff0000}; +#[derive(Copy, Clone, PartialEq)] +pub struct Color { + r: f32, + g: f32, + b: f32, + a: f32, } +impl From for Color { + + fn from(value: Pixel) -> Self { + Self { + r: f32::from(value.r) / 255.0, + g: f32::from(value.g) / 255.0, + b: f32::from(value.b) / 255.0, + a: f32::from(value.a) / 255.0, + } + } +} + +impl Color { + + pub fn set_alpha(&mut self, a: u8) { + self.a = f32::from(a) / 255.0; + } + + pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Pixel { r, g, b, a: 255 }.into() + } + + pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self { + Pixel { r, g, b, a }.into() + } + + pub fn from_hex(hex: u32) -> Self { + Pixel::from_hex(hex).into() + } + + pub fn into_pixel(self) -> Pixel { + //unsafe block is used here for "to_int_unchecked" since the struct's invariant garentees + //that the values are within [0.0, 1.0], meaning we won't ever overflow an u8 + unsafe { + Pixel { + r: self.r.to_int_unchecked::() * 255, + g: self.g.to_int_unchecked::() * 255, + b: self.b.to_int_unchecked::() * 255, + a: self.a.to_int_unchecked::() * 255, + } + } + } + + pub fn into_wgpu_color(self) -> wgpu::Color { + wgpu::Color { + r: f64::from(self.r), + g: f64::from(self.g), + b: f64::from(self.b), + a: f64::from(self.a), + } + } + + pub fn alpha_blend(&self, other: &Self) -> Self { + //apply alpha compisting's "over" operator, see + //[https://en.wikipedia.org/wiki/Alpha_compositing] for more detail + let a = self.a + other.a * (1.0 - self.a); + Self { + r: (self.r * self.a + other.r * other.a * (1.0 - self.a)) / a, + g: (self.g * self.a + other.g * other.a * (1.0 - self.a)) / a, + b: (self.b * self.a + other.b * other.a * (1.0 - self.a)) / a, + a, + } + } + + pub const NONE: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 0.0}; + pub const WHITE: Self = Self {r: 1.0, g: 1.0, b: 1.0, a: 1.0}; + pub const BLACK: Self = Self {r: 0.0, g: 0.0, b: 0.0, a: 1.0}; + pub const RED: Self = Self {r: 1.0, g: 0.0, b: 0.0, a: 1.0}; + pub const GREEN: Self = Self {r: 0.0, g: 1.0, b: 0.0, a: 1.0}; + pub const BLUE: Self = Self {r: 0.0, g: 0.0, b: 1.0, a: 1.0}; +}