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