Last active
February 9, 2019 18:08
-
-
Save CryZe/1882c3726c1f91fb5aeb44f5998c5030 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use { | |
derive_more::{Add, Mul}, | |
euc::{buffer::Buffer2d, rasterizer, Pipeline, Target}, | |
image::RgbaImage, | |
livesplit_core::{ | |
self as livesplit_core, | |
layout::{Layout, LayoutSettings, LayoutState}, | |
rendering::{Backend, Mesh, Renderer, Rgba as LSColor, Transform}, | |
run::parser::composite, | |
Run, Segment, TimeSpan, Timer, TimingMethod, | |
}, | |
std::{fs::File, io::BufReader, rc::Rc}, | |
vek::{Mat3, Rgba, Vec2, Vec3}, | |
}; | |
struct SoftwareBackend { | |
dims: [usize; 2], | |
color: AlphaBlended, | |
} | |
impl Backend for SoftwareBackend { | |
type Mesh = Vec<Vertex>; | |
type Texture = Rc<(Vec<u8>, f32, f32, usize)>; | |
fn create_mesh(&mut self, Mesh { vertices, indices }: &Mesh) -> Self::Mesh { | |
indices | |
.iter() | |
.map(|&index| { | |
let v = vertices[index as usize]; | |
Vertex { | |
position: Vec2::new(v.x, v.y), | |
texcoord: Vec2::new(v.u, v.v), | |
} | |
}) | |
.collect() | |
} | |
fn render_mesh( | |
&mut self, | |
mesh: &Self::Mesh, | |
transform: Transform, | |
[tl, tr, br, bl]: [LSColor; 4], | |
texture: Option<&Self::Texture>, | |
) { | |
let [x1, y1, z1, x2, y2, z2] = transform.to_column_major_array(); | |
MyPipeline::draw::<rasterizer::Triangles<_>, _>( | |
&Uniforms { | |
transform: Mat3::new(x1, x2, 0.0, y1, y2, 0.0, z1, z2, 0.0), | |
color_tl: Rgba::new(tl[0], tl[1], tl[2], tl[3]), | |
color_tr: Rgba::new(tr[0], tr[1], tr[2], tr[3]), | |
color_bl: Rgba::new(bl[0], bl[1], bl[2], bl[3]), | |
color_br: Rgba::new(br[0], br[1], br[2], br[3]), | |
texture: texture.cloned(), | |
}, | |
mesh, | |
&mut self.color, | |
&mut NoDepth(self.dims), | |
); | |
} | |
fn free_mesh(&mut self, _: Self::Mesh) {} | |
fn create_texture(&mut self, width: u32, height: u32, data: &[u8]) -> Self::Texture { | |
Rc::new(( | |
data.to_owned(), | |
width as f32, | |
height as f32, | |
width as usize * 4, | |
)) | |
} | |
fn free_texture(&mut self, _: Self::Texture) {} | |
fn resize(&mut self, _: f32) {} | |
} | |
struct MyPipeline; | |
struct NoDepth([usize; 2]); | |
impl Target for NoDepth { | |
type Item = f32; | |
fn size(&self) -> [usize; 2] { | |
self.0 | |
} | |
unsafe fn set(&mut self, _pos: [usize; 2], _item: Self::Item) {} | |
unsafe fn get(&self, _pos: [usize; 2]) -> &Self::Item { | |
&1.0 | |
} | |
fn clear(&mut self, _fill: Self::Item) {} | |
} | |
struct AlphaBlended(Buffer2d<Rgba<f32>>); | |
impl Target for AlphaBlended { | |
type Item = Rgba<f32>; | |
fn size(&self) -> [usize; 2] { | |
self.0.size() | |
} | |
unsafe fn set(&mut self, pos: [usize; 2], src: Self::Item) { | |
let dst = self.0.get(pos); | |
self.0.set( | |
pos, | |
Rgba::new( | |
src.a * src.r + (1.0 - src.a) * dst.r, | |
src.a * src.g + (1.0 - src.a) * dst.g, | |
src.a * src.b + (1.0 - src.a) * dst.b, | |
src.a + (1.0 - src.a) * dst.a, | |
), | |
); | |
} | |
unsafe fn get(&self, pos: [usize; 2]) -> &Self::Item { | |
self.0.get(pos) | |
} | |
fn clear(&mut self, fill: Self::Item) { | |
self.0.clear(fill) | |
} | |
} | |
struct Uniforms { | |
transform: Mat3<f32>, | |
color_tl: Rgba<f32>, | |
color_tr: Rgba<f32>, | |
color_bl: Rgba<f32>, | |
color_br: Rgba<f32>, | |
texture: Option<Rc<(Vec<u8>, f32, f32, usize)>>, | |
} | |
struct Vertex { | |
position: Vec2<f32>, | |
texcoord: Vec2<f32>, | |
} | |
#[derive(Clone, Mul, Add)] | |
struct VsOut { | |
color: Rgba<f32>, | |
texcoord: Vec2<f32>, | |
} | |
impl Pipeline for MyPipeline { | |
type Uniform = Uniforms; | |
type Vertex = Vertex; | |
type VsOut = VsOut; | |
type Pixel = Rgba<f32>; | |
fn vert(uniforms: &Self::Uniform, vertex: &Self::Vertex) -> ([f32; 3], Self::VsOut) { | |
let left = | |
uniforms.color_tl * (1.0 - vertex.texcoord.y) + uniforms.color_bl * vertex.texcoord.y; | |
let right = | |
uniforms.color_tr * (1.0 - vertex.texcoord.y) + uniforms.color_br * vertex.texcoord.y; | |
let color = left * (1.0 - vertex.texcoord.x) + right * vertex.texcoord.x; | |
let pos = Vec3::new(vertex.position.x, vertex.position.y, 1.0) * uniforms.transform; | |
( | |
[2.0 * pos.x - 1.0, 2.0 * pos.y - 1.0, 0.0], | |
VsOut { | |
color, | |
texcoord: vertex.texcoord, | |
}, | |
) | |
} | |
fn frag(uniforms: &Self::Uniform, vsout: &Self::VsOut) -> Self::Pixel { | |
if let Some(texture) = &uniforms.texture { | |
let (ref tex_data, width, height, stride) = **texture; | |
let x = vsout.texcoord.x * width; | |
let y = vsout.texcoord.y * height; | |
let pixel = &tex_data[stride * y as usize + x as usize * 4..]; | |
let r = pixel[0]; | |
let g = pixel[1]; | |
let b = pixel[2]; | |
let a = pixel[3]; | |
return Rgba::new( | |
r as f32 / 255.0, | |
g as f32 / 255.0, | |
b as f32 / 255.0, | |
a as f32 / 255.0, | |
) * vsout.color; | |
} | |
vsout.color | |
} | |
} | |
fn render(state: &LayoutState, [width, height]: [usize; 2]) -> RgbaImage { | |
let mut backend = SoftwareBackend { | |
color: AlphaBlended(Buffer2d::new( | |
[width, height], | |
Rgba::new(0.0, 0.0, 0.0, 0.0), | |
)), | |
dims: [width, height], | |
}; | |
Renderer::new().render(&mut backend, (width as _, height as _), &state); | |
let mut buf = Vec::with_capacity(width * height * 4); | |
for pixel in backend.color.0.as_ref() { | |
buf.extend( | |
pixel | |
.map(|e| (e * 255.0) as u8) | |
.into_array() | |
.iter() | |
.cloned(), | |
) | |
} | |
RgbaImage::from_raw(width as _, height as _, buf).unwrap() | |
} | |
fn main() { | |
let path = r"C:\Users\Christopher Serr\Documents\Splits\8dj.lss"; | |
let file = BufReader::new(File::open(path).unwrap()); | |
let mut run = composite::parse(file, Some(path.into()), true).unwrap().run; | |
// let mut run = Run::new(); | |
// run.set_game_name("Game"); | |
// run.set_category_name("Category"); | |
// run.push_segment(Segment::new("Time")); | |
run.fix_splits(); | |
let mut timer = Timer::new(run).unwrap(); | |
// timer.set_current_timing_method(TimingMethod::GameTime); | |
timer.start(); | |
// timer.set_game_time(TimeSpan::from_seconds(83.45)); | |
timer.split(); | |
let mut file = BufReader::new( | |
File::open(r"C:\Users\Christopher Serr\Documents\Splits\cool_columns.ls1l").unwrap(), | |
); | |
let mut layout = Layout::from_settings(LayoutSettings::from_json(&mut file).unwrap()); | |
// let mut layout = Layout::default_layout(); | |
// layout.general_settings_mut().background = livesplit_core::settings::Gradient::Plain( | |
// livesplit_core::settings::Color::hsla(0.0, 0.0, 0.06, 0.75), | |
// ); | |
let state = layout.state(&timer); | |
let super_sample_factor = 4; | |
let image = render( | |
&state, | |
[300 * super_sample_factor, 500 * super_sample_factor], | |
); | |
let image = image::imageops::thumbnail( | |
&image, | |
image.width() / super_sample_factor as u32, | |
image.height() / super_sample_factor as u32, | |
); | |
image.save("livesplit.png").unwrap(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment