initial commit
This commit is contained in:
commit
e25067842d
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "recall-challenge"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.5.0"
|
||||
clap = { version = "4.5.11", features = ["derive"] }
|
||||
futures = "0.3.30"
|
||||
minifb = "0.27.0"
|
||||
ndarray = "0.15.6"
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.120"
|
||||
thiserror = "1.0.63"
|
||||
tokio = { version = "1.39.1", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
|
@ -0,0 +1,301 @@
|
|||
use clap::Parser;
|
||||
use minifb::{Window, WindowOptions};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
const NUM_CHANNELS: u32 = 3;
|
||||
|
||||
use ndarray::Array3;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct Config {
|
||||
#[clap(short, long)]
|
||||
input: PathBuf,
|
||||
#[clap(short, long)]
|
||||
output: PathBuf,
|
||||
#[clap(short, long)]
|
||||
rules: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Rules {
|
||||
size: [u32; 2],
|
||||
rects: Vec<Rects>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Source {
|
||||
x: u32,
|
||||
y: u32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Destination {
|
||||
x: u32,
|
||||
y: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Rects {
|
||||
#[serde(rename = "src")]
|
||||
source: Source,
|
||||
#[serde(rename = "dest")]
|
||||
destination: Destination,
|
||||
// TODO: What's the range of values for alpha?
|
||||
alpha: f32,
|
||||
// TODO: What's the range of values for z?
|
||||
z: i32,
|
||||
}
|
||||
|
||||
impl PartialEq for Rects {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.z == other.z
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Rects {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.z.cmp(&other.z))
|
||||
}
|
||||
}
|
||||
|
||||
struct Video<'a> {
|
||||
width: u32,
|
||||
height: u32,
|
||||
num_frames: u32,
|
||||
reader: &'a mut dyn Read,
|
||||
}
|
||||
|
||||
impl<'a> Video<'a> {
|
||||
fn try_from_reader(reader: &'a mut dyn Read) -> Result<Self, Error> {
|
||||
let width = {
|
||||
let mut buf = [0u8; 4];
|
||||
reader
|
||||
.read_exact(&mut buf)
|
||||
.map_err(|_| Error::InvalidVideoHeader)?;
|
||||
u32::from_be_bytes(buf)
|
||||
};
|
||||
|
||||
let height = {
|
||||
let mut buf = [0u8; 4];
|
||||
reader
|
||||
.read_exact(&mut buf)
|
||||
.map_err(|_| Error::InvalidVideoHeader)?;
|
||||
u32::from_be_bytes(buf)
|
||||
};
|
||||
|
||||
let num_frames = {
|
||||
let mut buf = [0u8; 4];
|
||||
reader
|
||||
.read_exact(&mut buf)
|
||||
.map_err(|_| Error::InvalidVideoHeader)?;
|
||||
u32::from_be_bytes(buf)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
num_frames,
|
||||
reader,
|
||||
})
|
||||
}
|
||||
|
||||
fn next_frame(&mut self) -> Result<Frame, Error> {
|
||||
let frame_size = (self.width * self.height * NUM_CHANNELS) as usize;
|
||||
let mut frame_data = vec![0; frame_size];
|
||||
|
||||
// Because we're reading from a stream, each time we call next we'll be moved
|
||||
// to the next frame
|
||||
self.reader.read_exact(&mut frame_data).unwrap();
|
||||
Frame::try_from_data(self.width, self.height, frame_data)
|
||||
}
|
||||
}
|
||||
|
||||
struct Frame {
|
||||
width: u32,
|
||||
height: u32,
|
||||
data: Array3<u8>,
|
||||
}
|
||||
|
||||
// struct Pixel {
|
||||
// r: u8,
|
||||
// g: u8,
|
||||
// b: u8,
|
||||
// }
|
||||
|
||||
impl Frame {
|
||||
fn new(width: u32, height: u32) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
data: Array3::<u8>::zeros((width as usize, height as usize, NUM_CHANNELS as usize)),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_data(width: u32, height: u32, data: Vec<u8>) -> Result<Self, Error> {
|
||||
let data = Array3::<u8>::from_shape_vec(
|
||||
(width as usize, height as usize, NUM_CHANNELS as usize),
|
||||
data,
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_32bit(&self) -> Vec<u32> {
|
||||
self.data
|
||||
.iter()
|
||||
.map(|&x| {
|
||||
let r = x as u32;
|
||||
let g = x as u32;
|
||||
let b = x as u32;
|
||||
(r << 16) | (g << 8) | b
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("Invalid video header")]
|
||||
InvalidVideoHeader,
|
||||
#[error("Invalid frame shape")]
|
||||
InvalidFrameSize(#[from] ndarray::ShapeError),
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let config = Config::parse();
|
||||
|
||||
let rules: Rules = serde_json::from_reader(std::fs::File::open(config.rules)?)?;
|
||||
let reader = &mut std::io::BufReader::new(std::fs::File::open(config.input)?);
|
||||
let mut input_video = Video::try_from_reader(reader)?;
|
||||
|
||||
let mut window = Window::new(
|
||||
"Test - ESC to exit",
|
||||
input_video.width as usize,
|
||||
input_video.height as usize,
|
||||
WindowOptions::default(),
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("{}", e);
|
||||
});
|
||||
|
||||
while window.is_open() && !window.is_key_down(minifb::Key::Escape) {
|
||||
let input_frame = input_video.next_frame()?;
|
||||
let new_frame = apply_rules(&input_frame, &rules);
|
||||
|
||||
window
|
||||
.update_with_buffer(
|
||||
&new_frame.as_32bit(),
|
||||
new_frame.width as usize,
|
||||
new_frame.height as usize,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_rules(frame: &Frame, rules: &Rules) -> Frame {
|
||||
rules.rects.iter().fold(
|
||||
Frame::new(rules.size[0], rules.size[1]),
|
||||
|mutate_frame, rule| apply_rule(&frame, mutate_frame, rule),
|
||||
)
|
||||
}
|
||||
|
||||
fn apply_rule(input_frame: &Frame, mut mutate_frame: Frame, rule: &Rects) -> Frame {
|
||||
// println!(
|
||||
// "[Z index {}] Copy ({} + {}, {} + {}) to ({}, {})",
|
||||
// rule.z,
|
||||
// rule.source.x,
|
||||
// rule.source.width,
|
||||
// rule.source.y,
|
||||
// rule.source.height,
|
||||
// rule.destination.x,
|
||||
// rule.destination.y,
|
||||
// );
|
||||
|
||||
for (x_count, x) in (rule.source.x..(rule.source.x + rule.source.width)).enumerate() {
|
||||
for (y_count, y) in (rule.source.y..(rule.source.y + rule.source.height)).enumerate() {
|
||||
for z in 0..NUM_CHANNELS as usize {
|
||||
let pixel = input_frame.data[[x as usize, y as usize, z]];
|
||||
// println!("Pixel at ({}, {}) = {}", x, y, pixel);
|
||||
mutate_frame.data[[
|
||||
(rule.destination.x + x_count as u32) as usize,
|
||||
(rule.destination.y + y_count as u32) as usize,
|
||||
z,
|
||||
]] = apply_alpha(
|
||||
pixel,
|
||||
mutate_frame.data[[x as usize, y as usize, z]],
|
||||
rule.alpha,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutate_frame
|
||||
}
|
||||
|
||||
fn apply_alpha(foreground: u8, background: u8, alpha: f32) -> u8 {
|
||||
(foreground as f32 * alpha + background as f32 * (1.0 - alpha)) as u8
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
// Pretty assert is too slow for this amount of data
|
||||
// use pretty_assertions::assert_eq;
|
||||
#[test]
|
||||
fn basic() {
|
||||
let input_video_path = PathBuf::from(format!(
|
||||
"{}/fixtures/test/input.rvid",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
));
|
||||
|
||||
let output_video_path = PathBuf::from(format!(
|
||||
"{}/fixtures/test/expected.rvid",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
));
|
||||
let rules_path = PathBuf::from(format!(
|
||||
"{}/fixtures/test/rules.json",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
));
|
||||
|
||||
println!("{:?}", input_video_path);
|
||||
|
||||
let mut rules: Rules =
|
||||
serde_json::from_reader(std::fs::File::open(rules_path).unwrap()).unwrap();
|
||||
rules.rects.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
let in_reader = &mut std::io::BufReader::new(std::fs::File::open(input_video_path).unwrap());
|
||||
let mut input_video = Video::try_from_reader(in_reader).unwrap();
|
||||
|
||||
let out_reader = &mut std::io::BufReader::new(std::fs::File::open(output_video_path).unwrap());
|
||||
let mut output_video = Video::try_from_reader(out_reader).unwrap();
|
||||
|
||||
let input_frame = input_video.next_frame().unwrap();
|
||||
let expected_frame = output_video.next_frame().unwrap();
|
||||
|
||||
let new_frame = apply_rules(&input_frame, &rules);
|
||||
|
||||
// write the new frame to a file
|
||||
let out = std::fs::File::create("./generated.frame").unwrap();
|
||||
let mut writer = std::io::BufWriter::new(out);
|
||||
writer.write_all(&new_frame.data.into_raw_vec()).unwrap();
|
||||
|
||||
let out = std::fs::File::create("./expected.frame").unwrap();
|
||||
let mut writer = std::io::BufWriter::new(out);
|
||||
writer
|
||||
.write_all(&expected_frame.data.into_raw_vec())
|
||||
.unwrap();
|
||||
|
||||
// dbg!(&new_frame.data);
|
||||
|
||||
// assert_eq!(new_frame.data, expected_frame.data);
|
||||
}
|
Loading…
Reference in New Issue