├── .gitignore ├── Cargo.toml ├── LICENSE ├── examples ├── circles.rs ├── matt.jpg ├── roboto.ttf ├── text.rs ├── tiled.rs └── window.rs ├── readme.md └── src ├── glyphs.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.png 5 | main.rs 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [[example]] 2 | name = 'circles' 3 | 4 | [[example]] 5 | name = 'text' 6 | 7 | [[example]] 8 | name = 'tiled' 9 | 10 | [[example]] 11 | name = 'window' 12 | required-features = ['piston_window_texture'] 13 | 14 | [dependencies] 15 | bit-vec = '0.6.3' 16 | image = '0.23.14' 17 | piston-texture = '0.8.0' 18 | png = '0.16.8' 19 | rayon = '1.5.0' 20 | 21 | [dependencies.piston2d-graphics] 22 | features = ['glyph_cache_rusttype'] 23 | version = '0.40.0' 24 | 25 | [dependencies.piston_window] 26 | optional = true 27 | version = '0.120.0' 28 | 29 | [features] 30 | default = [] 31 | piston_window_texture = ['piston_window'] 32 | 33 | [package] 34 | authors = ['Kai Schmidt '] 35 | categories = [ 36 | 'multimedia', 37 | 'multimedia::images', 38 | 'rendering', 39 | ] 40 | description = '''A buffer which can be used as a render target for Piston's graphics library. This buffer can be loaded from and/or saved to a file on disk. This allows for things like screenshots in games.''' 41 | documentation = 'https://docs.rs/graphics_buffer/' 42 | edition = '2018' 43 | keywords = [ 44 | 'piston', 45 | 'image', 46 | 'rendering', 47 | 'graphics', 48 | ] 49 | license = 'MIT' 50 | name = 'graphics_buffer' 51 | readme = 'readme.md' 52 | repository = 'https://github.com/kaikalii/graphics_buffer' 53 | version = '0.7.7' 54 | [package.metadata.docs.rs] 55 | features = ['piston_window_texture'] 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kai Schmidt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/circles.rs: -------------------------------------------------------------------------------- 1 | use graphics::ellipse; 2 | use graphics_buffer::*; 3 | 4 | fn main() { 5 | // Create a new RenderBuffer 6 | let mut buffer = RenderBuffer::new(100, 100); 7 | buffer.clear([0.0, 0.0, 0.0, 0.0]); 8 | 9 | // Big red circle 10 | ellipse( 11 | [1.0, 0.0, 0.0, 0.7], 12 | [0.0, 0.0, 100.0, 100.0], 13 | IDENTITY, 14 | &mut buffer, 15 | ); 16 | // Small blue circle 17 | ellipse( 18 | [0.0, 0.0, 1.0, 0.7], 19 | [0.0, 0.0, 50.0, 50.0], 20 | IDENTITY, 21 | &mut buffer, 22 | ); 23 | // Small green circle 24 | ellipse( 25 | [0.0, 1.0, 0.0, 0.7], 26 | [50.0, 50.0, 50.0, 50.0], 27 | IDENTITY, 28 | &mut buffer, 29 | ); 30 | 31 | // Save the buffer 32 | buffer.save("circles.png").unwrap(); 33 | } 34 | -------------------------------------------------------------------------------- /examples/matt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaikalii/graphics_buffer/f6f622f9782af014f3fb5bae22b7953cc862034b/examples/matt.jpg -------------------------------------------------------------------------------- /examples/roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaikalii/graphics_buffer/f6f622f9782af014f3fb5bae22b7953cc862034b/examples/roboto.ttf -------------------------------------------------------------------------------- /examples/text.rs: -------------------------------------------------------------------------------- 1 | use graphics::{text, Transformed}; 2 | use graphics_buffer::*; 3 | 4 | fn main() { 5 | // Initalize the buffer 6 | let mut buffer = RenderBuffer::new(150, 40); 7 | buffer.clear([0.0, 0.0, 0.0, 1.0]); 8 | 9 | // Load the font and initialize glyphs 10 | let mut glyphs = buffer_glyphs_from_bytes(include_bytes!("roboto.ttf")).unwrap(); 11 | 12 | // Draw text 13 | text( 14 | [1.0; 4], 15 | 30, 16 | "Oh boy!", 17 | &mut glyphs, 18 | IDENTITY.trans(10.0, 30.0), 19 | &mut buffer, 20 | ) 21 | .unwrap(); 22 | 23 | // Save the image 24 | buffer.save("text.png").unwrap(); 25 | } 26 | -------------------------------------------------------------------------------- /examples/tiled.rs: -------------------------------------------------------------------------------- 1 | use graphics::{Image, Transformed}; 2 | use graphics_buffer::*; 3 | 4 | fn main() { 5 | // Load Matt Damon 6 | let matt = RenderBuffer::decode_from_bytes(include_bytes!("matt.jpg")).unwrap(); 7 | 8 | // Initalize the buffer 9 | let mut buffer = RenderBuffer::new(matt.width() * 2, matt.height() * 2); 10 | buffer.clear([0.0, 0.0, 0.0, 1.0]); 11 | 12 | // Tile the image with different colors 13 | for (color, (x, y)) in &[ 14 | ([1.0, 0.2, 0.2, 1.0], (0.0, 0.0)), // red, top left 15 | ([1.0, 1.0, 0.0, 1.0], (matt.width() as f64, 0.0)), // yellow, top right 16 | ([0.0, 1.0, 0.0, 1.0], (0.0, matt.height() as f64)), // green, bottom left 17 | ( 18 | [0.2, 0.2, 1.0, 1.0], // blue 19 | (matt.width() as f64, matt.height() as f64), // bottom right 20 | ), 21 | ] { 22 | Image::new_color(*color).draw( 23 | &matt, 24 | &Default::default(), 25 | IDENTITY.trans(*x, *y), 26 | &mut buffer, 27 | ); 28 | } 29 | 30 | // Save the image 31 | buffer.save("tiled.png").unwrap(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/window.rs: -------------------------------------------------------------------------------- 1 | use graphics::{ellipse, image, text, Transformed}; 2 | use graphics_buffer::*; 3 | use piston_window::{ 4 | clear, Event, Loop, PistonWindow, TextureSettings, UpdateArgs, WindowSettings, 5 | }; 6 | 7 | fn main() { 8 | // Load Matt Damon 9 | let matt = RenderBuffer::decode_from_bytes(include_bytes!("matt.jpg")).unwrap(); 10 | 11 | // Load the font and initialize glyphs 12 | let mut glyphs = buffer_glyphs_from_bytes(include_bytes!("roboto.ttf")).unwrap(); 13 | 14 | // Initalize the buffer 15 | let mut buffer = RenderBuffer::new(matt.width(), matt.height()); 16 | buffer.clear([0.0, 0.0, 0.0, 1.0]); 17 | 18 | // Draw Matt to the buffer 19 | image(&matt, IDENTITY, &mut buffer); 20 | 21 | // Give Matt red eyes 22 | const RED: [f32; 4] = [1.0, 0.0, 0.0, 0.7]; 23 | const DIAMETER: f64 = 40.0; 24 | ellipse( 25 | RED, 26 | [115.0, 175.0, DIAMETER, DIAMETER], 27 | IDENTITY, 28 | &mut buffer, 29 | ); 30 | ellipse( 31 | RED, 32 | [210.0, 195.0, DIAMETER, DIAMETER], 33 | IDENTITY, 34 | &mut buffer, 35 | ); 36 | 37 | // Let people know he is woke 38 | text( 39 | [0.0, 1.0, 0.0, 1.0], 40 | 70, 41 | "# w o k e", 42 | &mut glyphs, 43 | IDENTITY.trans(0.0, 70.0), 44 | &mut buffer, 45 | ) 46 | .unwrap(); 47 | 48 | // Create a window 49 | let mut window: PistonWindow = WindowSettings::new( 50 | "piston_window texture example", 51 | (matt.height(), matt.height()), 52 | ) 53 | .exit_on_esc(true) 54 | .build() 55 | .unwrap(); 56 | 57 | // Create a texture from red-eyed Matt 58 | let matt_texture = buffer 59 | .to_g2d_texture( 60 | &mut window.create_texture_context(), 61 | &TextureSettings::new(), 62 | ) 63 | .unwrap(); 64 | 65 | // Initialize a rotation 66 | let mut rot = 0.0; 67 | 68 | // Run the event loop 69 | while let Some(event) = window.next() { 70 | match event { 71 | Event::Loop(Loop::Render(..)) => { 72 | window.draw_2d(&event, |context, graphics, _| { 73 | // Clear window with black 74 | clear([0.0, 0.0, 0.0, 1.0], graphics); 75 | // Draw matt rotated and scaled 76 | image( 77 | &matt_texture, 78 | context 79 | .transform 80 | .trans(matt.height() as f64 / 2.0, matt.height() as f64 / 2.0) 81 | .scale(0.5, 0.5) 82 | .rot_rad(rot), 83 | graphics, 84 | ); 85 | }); 86 | } 87 | // Rotate on update 88 | Event::Loop(Loop::Update(UpdateArgs { dt, .. })) => rot += dt, 89 | _ => (), 90 | } 91 | } 92 | 93 | // Save the image 94 | buffer.save("red_eyes.png").unwrap(); 95 | } 96 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | This library provides a buffer type, `RenderBuffer`, which can be used as a render target for [Piston's graphics library](https://github.com/PistonDevelopers/graphics). This buffer can be loaded from and/or saved to a file on disk. This allows for things like screenshots in games. 4 | 5 | There is also an optional feature for `RenderBuffer` that allows it to be converted into a `G2dTexture` so that it can be rendered with [`piston_window`](https://github.com/PistonDevelopers/piston_window). To enable this, add `features = ["piston_window_texture"]` to the `graphics_buffer` dependency in your `cargo.toml`. 6 | 7 | [API Documentation](https://docs.rs/graphics_buffer/) 8 | 9 | ### Usage 10 | 11 | Add this to your `cargo.toml` : 12 | 13 | ```toml 14 | graphics_buffer = "0.6.0" 15 | piston2d-graphics = "0.30.0" 16 | ``` 17 | 18 | or, if you want to be able to draw the texture to a window using [`piston_window`](https://github.com/PistonDevelopers/piston_window) : 19 | 20 | ```toml 21 | graphics_buffer = { version = "0.6.0", features = ["piston_window_texture"] } 22 | piston2d-graphics = "0.30.0" 23 | piston_window = "0.89.0" 24 | ``` 25 | 26 | Here is a simple example that draws three circles and saves the image to a file: 27 | 28 | ```rust 29 | use graphics::ellipse; 30 | use graphics_buffer::*; 31 | 32 | fn main() { 33 | // Create a new RenderBuffer 34 | let mut buffer = RenderBuffer::new(100, 100); 35 | buffer.clear([0.0, 0.0, 0.0, 0.0]); 36 | 37 | // Big red circle 38 | ellipse( 39 | [1.0, 0.0, 0.0, 0.7], 40 | [0.0, 0.0, 100.0, 100.0], 41 | IDENTITY, 42 | &mut buffer, 43 | ); 44 | // Small blue circle 45 | ellipse( 46 | [0.0, 0.0, 1.0, 0.7], 47 | [0.0, 0.0, 50.0, 50.0], 48 | IDENTITY, 49 | &mut buffer, 50 | ); 51 | // Small green circle 52 | ellipse( 53 | [0.0, 1.0, 0.0, 0.7], 54 | [50.0, 50.0, 50.0, 50.0], 55 | IDENTITY, 56 | &mut buffer, 57 | ); 58 | 59 | // Save the buffer 60 | buffer.save("circles.png").unwrap(); 61 | } 62 | ``` 63 | 64 | ### Contributing 65 | 66 | Feel free to open an issue or PR if you want to contribute. There are definitely places for improvement, especially in the rendering code. 67 | -------------------------------------------------------------------------------- /src/glyphs.rs: -------------------------------------------------------------------------------- 1 | use std::{io, path::Path}; 2 | 3 | use graphics::glyph_cache::rusttype; 4 | use texture::TextureSettings; 5 | 6 | use crate::RenderBuffer; 7 | 8 | /// A character cache for drawing text to a `RenderBuffer`. 9 | /// 10 | /// If the link to the `GlyphCache` type is not working, 11 | /// try generating the docs yourself. 12 | pub type BufferGlyphs<'a> = rusttype::GlyphCache<'a, (), RenderBuffer>; 13 | 14 | /// Create a `BufferGlyphs` from some font data 15 | #[allow(clippy::result_unit_err)] 16 | pub fn buffer_glyphs_from_bytes(font_data: &[u8]) -> Result { 17 | BufferGlyphs::from_bytes(font_data, (), TextureSettings::new()) 18 | } 19 | 20 | /// Create a `BufferGlyphs` from a path to some font 21 | pub fn buffer_glyphs_from_path<'a, P: AsRef>(font_path: P) -> io::Result> { 22 | BufferGlyphs::new(font_path, (), TextureSettings::new()) 23 | } 24 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | /*! 4 | This library provides a buffer which can be used as a render target for 5 | [Piston's graphics library](https://github.com/PistonDevelopers/graphics). 6 | This buffer can be loaded from and/or saved to a file on disk. This allows 7 | for things like screenshots in games. 8 | 9 | There is also an optional feature for `RenderBuffer` that allows it to be 10 | converted into a `G2dTexture` so that it can be rendered with 11 | [`piston_window`](https://github.com/PistonDevelopers/piston_window). To 12 | enable this, add `features = ["piston_window_texture"]` to the `graphics_buffer` 13 | dependency in your `cargo.toml`. More about this feature can be found in 14 | the [`RenderBuffer` documentation](struct.RenderBuffer.html). 15 | */ 16 | 17 | mod glyphs; 18 | pub use crate::glyphs::*; 19 | 20 | use std::{error, fmt, fs::File, ops, path::Path}; 21 | 22 | use bit_vec::BitVec; 23 | use graphics::{draw_state::DrawState, math::Matrix2d, types::Color, Graphics, ImageSize}; 24 | use image::{DynamicImage, GenericImageView, ImageResult, Rgba, RgbaImage}; 25 | #[cfg(feature = "piston_window_texture")] 26 | use piston_window::{G2dTexture, G2dTextureContext}; 27 | use png::{Decoder as PngDecoder, Limits}; 28 | use rayon::prelude::*; 29 | use texture::{CreateTexture, Format, TextureOp, TextureSettings, UpdateTexture}; 30 | 31 | /// The identity matrix: `[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]`. 32 | pub const IDENTITY: Matrix2d = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]; 33 | 34 | /// An Error type for `RenderBuffer`. 35 | #[derive(Debug, Clone)] 36 | pub enum Error { 37 | /// Pixels/bytes mismatch when creating texture 38 | SizeMismatch(usize, usize), 39 | } 40 | 41 | impl fmt::Display for Error { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | match self { 44 | Error::SizeMismatch(len, area) => write!( 45 | f, 46 | "Container is too small for the given dimensions. \ 47 | \nContainer has {} bytes, which encode {} pixels, \ 48 | \nbut the given demensions contain {} pixels", 49 | len, 50 | len / 4, 51 | area 52 | ), 53 | } 54 | } 55 | } 56 | 57 | impl error::Error for Error {} 58 | 59 | /** 60 | A buffer that can be rendered to with Piston's graphics library. 61 | */ 62 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 63 | pub struct RenderBuffer { 64 | inner: RgbaImage, 65 | used: Vec, 66 | } 67 | 68 | impl RenderBuffer { 69 | /// Create a new `RenderBuffer` with the given witdth or height. 70 | pub fn new(width: u32, height: u32) -> RenderBuffer { 71 | RenderBuffer { 72 | inner: RgbaImage::new(width, height), 73 | used: vec![BitVec::from_elem(height as usize, false); width as usize], 74 | } 75 | } 76 | /// Creates a new `RenderBuffer` by opening it from a file. 77 | pub fn open>(path: P) -> Result> { 78 | if path 79 | .as_ref() 80 | .extension() 81 | .map(|ext| ext == "png") 82 | .unwrap_or(false) 83 | { 84 | let (info, mut reader) = PngDecoder::new_with_limits( 85 | File::open(&path)?, 86 | Limits { 87 | bytes: std::usize::MAX, 88 | }, 89 | ) 90 | .read_info()?; 91 | let mut buf = vec![0; info.buffer_size()]; 92 | reader.next_frame(&mut buf)?; 93 | Ok( 94 | if let Some(image) = image::RgbaImage::from_raw(info.width, info.height, buf) { 95 | image.into() 96 | } else { 97 | image::open(path)?.into() 98 | }, 99 | ) 100 | } else { 101 | Ok(image::open(path)?.into()) 102 | } 103 | } 104 | /// Creates a new `RenderBuffer` by decoding image data. 105 | pub fn decode_from_bytes(bytes: &[u8]) -> ImageResult { 106 | image::load_from_memory(bytes).map(RenderBuffer::from) 107 | } 108 | /// Clear the buffer with a color. 109 | pub fn clear(&mut self, color: [f32; 4]) { 110 | self.clear_color(color); 111 | } 112 | /// Returns the color of the pixel at the given coordinates. 113 | pub fn pixel(&self, x: u32, y: u32) -> [f32; 4] { 114 | color_rgba_f32(*self.inner.get_pixel(x, y)) 115 | } 116 | /// Sets the color of the pixel at the given coordinates. 117 | pub fn set_pixel(&mut self, x: u32, y: u32, color: [f32; 4]) { 118 | self.inner.put_pixel(x, y, color_f32_rgba(&color)); 119 | } 120 | fn reset_used(&mut self) { 121 | let (width, height) = self.inner.dimensions(); 122 | self.used = vec![BitVec::from_elem(height as usize, false); width as usize]; 123 | } 124 | /// Creates a `G2dTexture` from the `RenderBuffer` for drawing to a `PistonWindow`. 125 | #[cfg(feature = "piston_window_texture")] 126 | pub fn to_g2d_texture( 127 | &self, 128 | context: &mut G2dTextureContext, 129 | settings: &TextureSettings, 130 | ) -> Result> { 131 | Ok(G2dTexture::from_image(context, &self.inner, settings)?) 132 | } 133 | } 134 | 135 | impl TextureOp<()> for RenderBuffer { 136 | type Error = Error; 137 | } 138 | 139 | impl CreateTexture<()> for RenderBuffer { 140 | fn create>( 141 | _factory: &mut (), 142 | _format: Format, 143 | memory: &[u8], 144 | size: S, 145 | _settings: &TextureSettings, 146 | ) -> Result { 147 | let size = size.into(); 148 | Ok(RenderBuffer::from( 149 | RgbaImage::from_raw(size[0], size[1], memory.to_vec()) 150 | .ok_or_else(|| Error::SizeMismatch(memory.len(), (size[0] * size[1]) as usize))?, 151 | )) 152 | } 153 | } 154 | 155 | impl UpdateTexture<()> for RenderBuffer { 156 | fn update( 157 | &mut self, 158 | _factory: &mut (), 159 | _format: Format, 160 | memory: &[u8], 161 | offset: O, 162 | size: S, 163 | ) -> Result<(), Self::Error> 164 | where 165 | O: Into<[u32; 2]>, 166 | S: Into<[u32; 2]>, 167 | { 168 | let offset = offset.into(); 169 | let size = size.into(); 170 | let new_image = RenderBuffer::from( 171 | RgbaImage::from_raw(size[0], size[1], memory.to_vec()) 172 | .ok_or_else(|| Error::SizeMismatch(memory.len(), (size[0] * size[1]) as usize))?, 173 | ); 174 | for i in 0..size[0] { 175 | for j in 0..size[1] { 176 | let color = new_image.pixel(i, j); 177 | self.set_pixel(i + offset[0], j + offset[1], color); 178 | } 179 | } 180 | Ok(()) 181 | } 182 | } 183 | 184 | impl From for RenderBuffer { 185 | fn from(image: RgbaImage) -> Self { 186 | let (width, height) = image.dimensions(); 187 | RenderBuffer { 188 | inner: image, 189 | used: vec![BitVec::from_elem(height as usize, false); width as usize], 190 | } 191 | } 192 | } 193 | 194 | impl From for RenderBuffer { 195 | fn from(image: DynamicImage) -> Self { 196 | let (width, height) = image.dimensions(); 197 | RenderBuffer { 198 | inner: image.to_rgba8(), 199 | used: vec![BitVec::from_elem(height as usize, false); width as usize], 200 | } 201 | } 202 | } 203 | 204 | impl ops::Deref for RenderBuffer { 205 | type Target = RgbaImage; 206 | fn deref(&self) -> &Self::Target { 207 | &self.inner 208 | } 209 | } 210 | 211 | impl ImageSize for RenderBuffer { 212 | fn get_size(&self) -> (u32, u32) { 213 | self.inner.dimensions() 214 | } 215 | } 216 | 217 | impl Graphics for RenderBuffer { 218 | type Texture = RenderBuffer; 219 | fn clear_color(&mut self, color: Color) { 220 | for (_, _, pixel) in self.inner.enumerate_pixels_mut() { 221 | *pixel = color_f32_rgba(&color); 222 | } 223 | } 224 | fn clear_stencil(&mut self, _value: u8) {} 225 | fn tri_list(&mut self, _draw_state: &DrawState, color: &[f32; 4], mut f: F) 226 | where 227 | F: FnMut(&mut dyn FnMut(&[[f32; 2]])), 228 | { 229 | self.reset_used(); 230 | // Render Triangles 231 | f(&mut |vertices| { 232 | for tri in vertices.chunks(3) { 233 | // Get tri bounds for efficiency 234 | let mut tl = [0f32, 0f32]; 235 | let mut br = [0f32, 0f32]; 236 | for v in tri { 237 | tl[0] = tl[0].min(v[0]); 238 | tl[1] = tl[1].min(v[1]); 239 | br[0] = br[0].max(v[0]); 240 | br[1] = br[1].max(v[1]); 241 | } 242 | let tl = [tl[0].floor().max(0.0) as i32, tl[1].floor().max(0.0) as i32]; 243 | let br = [ 244 | br[0].ceil().min(self.width() as f32) as i32, 245 | br[1].ceil().min(self.height() as f32) as i32, 246 | ]; 247 | // Render 248 | let inner = &self.inner; 249 | let used = &self.used; 250 | (tl[0]..br[0]).into_par_iter().for_each(|x| { 251 | let mut entered = false; 252 | for y in tl[1]..br[1] { 253 | if triangle_contains(tri, [x as f32, y as f32]) { 254 | entered = true; 255 | if !used[x as usize].get(y as usize).unwrap_or(true) { 256 | let under_color = 257 | color_rgba_f32(*inner.get_pixel(x as u32, y as u32)); 258 | let layered_color = layer_color(&color, &under_color); 259 | unsafe { 260 | (inner as *const RgbaImage as *mut RgbaImage) 261 | .as_mut() 262 | .unwrap() 263 | .put_pixel( 264 | x as u32, 265 | y as u32, 266 | color_f32_rgba(&layered_color), 267 | ); 268 | (used as *const Vec as *mut Vec) 269 | .as_mut() 270 | .unwrap()[x as usize] 271 | .set(y as usize, true); 272 | } 273 | } 274 | } else if entered { 275 | break; 276 | } 277 | } 278 | }); 279 | } 280 | }); 281 | } 282 | fn tri_list_uv( 283 | &mut self, 284 | _draw_state: &DrawState, 285 | color: &[f32; 4], 286 | texture: &Self::Texture, 287 | mut f: F, 288 | ) where 289 | F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]])), 290 | { 291 | self.reset_used(); 292 | // Render Triangles 293 | f(&mut |vertices, tex_vertices| { 294 | for (tri, tex_tri) in vertices.chunks(3).zip(tex_vertices.chunks(3)) { 295 | // Get tri bounds for efficiency 296 | let mut tl = [0f32, 0f32]; 297 | let mut br = [0f32, 0f32]; 298 | for v in tri { 299 | tl[0] = tl[0].min(v[0]); 300 | tl[1] = tl[1].min(v[1]); 301 | br[0] = br[0].max(v[0]); 302 | br[1] = br[1].max(v[1]); 303 | } 304 | let tl = [tl[0].floor().max(0.0) as i32, tl[1].floor().max(0.0) as i32]; 305 | let br = [ 306 | br[0].ceil().min((self.width() - 1) as f32) as i32, 307 | br[1].ceil().min((self.height() - 1) as f32) as i32, 308 | ]; 309 | let avg_y = ((tri[0][1] + tri[1][1] + tri[2][1]) / 3.0) as i32; 310 | let vert_center = (br[1] - tl[1]) / 2; 311 | let vertical_balance_top = avg_y < vert_center; 312 | // Render 313 | let scaled_tex_tri = tri_image_scale(tex_tri, texture.get_size()); 314 | let inner = &self.inner; 315 | let used = &self.used; 316 | (tl[0]..br[0]).into_par_iter().for_each(|x| { 317 | let mut entered = false; 318 | let range: Box> = if vertical_balance_top { 319 | Box::new(tl[1]..br[1]) 320 | } else { 321 | Box::new((tl[1]..br[1]).rev()) 322 | }; 323 | for y in range { 324 | if triangle_contains(tri, [x as f32, y as f32]) { 325 | entered = true; 326 | let mapped_point = 327 | map_to_triangle([x as f32, y as f32], tri, &scaled_tex_tri); 328 | let texel = color_rgba_f32(*texture.get_pixel( 329 | (mapped_point[0].round() as u32).min(texture.width() - 1), 330 | (mapped_point[1].round() as u32).min(texture.height() - 1), 331 | )); 332 | let over_color = color_mul(color, &texel); 333 | let under_color = color_rgba_f32(*inner.get_pixel(x as u32, y as u32)); 334 | let layered_color = layer_color(&over_color, &under_color); 335 | unsafe { 336 | (inner as *const RgbaImage as *mut RgbaImage) 337 | .as_mut() 338 | .unwrap() 339 | .put_pixel(x as u32, y as u32, color_f32_rgba(&layered_color)); 340 | (used as *const Vec as *mut Vec) 341 | .as_mut() 342 | .unwrap()[x as usize] 343 | .set(y as usize, true); 344 | } 345 | } else if entered { 346 | break; 347 | } 348 | } 349 | }); 350 | } 351 | }); 352 | } 353 | 354 | fn tri_list_c(&mut self, _: &DrawState, _: F) 355 | where 356 | F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 4]])), 357 | { 358 | unimplemented!("::tri_list_c is currently unimplemented") 359 | } 360 | 361 | fn tri_list_uv_c(&mut self, _: &DrawState, _: &Self::Texture, _: F) 362 | where 363 | F: FnMut(&mut dyn FnMut(&[[f32; 2]], &[[f32; 2]], &[[f32; 4]])), 364 | { 365 | unimplemented!("::tri_list_uv_c is currently unimplemented") 366 | } 367 | } 368 | 369 | fn color_f32_rgba(color: &[f32; 4]) -> Rgba { 370 | Rgba([ 371 | (color[0] * 255.0) as u8, 372 | (color[1] * 255.0) as u8, 373 | (color[2] * 255.0) as u8, 374 | (color[3] * 255.0) as u8, 375 | ]) 376 | } 377 | 378 | fn color_rgba_f32(color: Rgba) -> [f32; 4] { 379 | [ 380 | f32::from(color[0]) / 255.0, 381 | f32::from(color[1]) / 255.0, 382 | f32::from(color[2]) / 255.0, 383 | f32::from(color[3]) / 255.0, 384 | ] 385 | } 386 | 387 | fn color_mul(a: &[f32; 4], b: &[f32; 4]) -> [f32; 4] { 388 | [a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]] 389 | } 390 | 391 | fn layer_color(over: &[f32; 4], under: &[f32; 4]) -> [f32; 4] { 392 | let over_weight = 1.0 - (1.0 - over[3]).powf(2.0); 393 | let under_weight = 1.0 - over_weight; 394 | [ 395 | over_weight * over[0] + under_weight * under[0], 396 | over_weight * over[1] + under_weight * under[1], 397 | over_weight * over[2] + under_weight * under[2], 398 | (over[3].powf(2.0) + under[3].powf(2.0)).sqrt().min(1.0), 399 | ] 400 | } 401 | 402 | fn sign(p1: [f32; 2], p2: [f32; 2], p3: [f32; 2]) -> f32 { 403 | (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) 404 | } 405 | 406 | fn triangle_contains(tri: &[[f32; 2]], point: [f32; 2]) -> bool { 407 | let b1 = sign(point, tri[0], tri[1]) < 0.0; 408 | let b2 = sign(point, tri[1], tri[2]) < 0.0; 409 | let b3 = sign(point, tri[2], tri[0]) < 0.0; 410 | b1 == b2 && b2 == b3 411 | } 412 | 413 | #[allow(clippy::many_single_char_names)] 414 | fn map_to_triangle(point: [f32; 2], from_tri: &[[f32; 2]], to_tri: &[[f32; 2]]) -> [f32; 2] { 415 | let t = from_tri; 416 | let p = point; 417 | // Computer some values that are used multiple times 418 | let a = t[1][1] - t[2][1]; 419 | let b = p[0] - t[2][0]; 420 | let c = t[2][0] - t[1][0]; 421 | let d = p[1] - t[2][1]; 422 | let e = t[0][0] - t[2][0]; 423 | let f = t[0][1] - t[2][1]; 424 | let g = t[2][1] - t[0][1]; 425 | let ae_cf = a * e + c * f; 426 | let bary_a = (a * b + c * d) / ae_cf; 427 | let bary_b = (g * b + e * d) / ae_cf; 428 | let bary_c = 1.0 - bary_a - bary_b; 429 | [ 430 | bary_a * to_tri[0][0] + bary_b * to_tri[1][0] + bary_c * to_tri[2][0], 431 | bary_a * to_tri[0][1] + bary_b * to_tri[1][1] + bary_c * to_tri[2][1], 432 | ] 433 | } 434 | 435 | fn point_image_scale(point: [f32; 2], size: (u32, u32)) -> [f32; 2] { 436 | [point[0] * size.0 as f32, point[1] * size.1 as f32] 437 | } 438 | 439 | fn tri_image_scale(tri: &[[f32; 2]], size: (u32, u32)) -> [[f32; 2]; 3] { 440 | [ 441 | point_image_scale(tri[0], size), 442 | point_image_scale(tri[1], size), 443 | point_image_scale(tri[2], size), 444 | ] 445 | } 446 | --------------------------------------------------------------------------------