diff --git a/Cargo.toml b/Cargo.toml index 6d75a68f..af29369a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ members = [ "emulator/mgba", "emulator/mgba-sys", "emulator/test-runner", + "emulator/screenshot-generator", "website/backtrace", ] diff --git a/emulator/screenshot-generator/Cargo.toml b/emulator/screenshot-generator/Cargo.toml new file mode 100644 index 00000000..1b3001c2 --- /dev/null +++ b/emulator/screenshot-generator/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "screenshot-generator" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +mgba = { path = "../mgba" } +clap = { version = "4", features = ["derive"] } +anyhow = "1" +image = { version = "0.24", default-features = false, features = [ "png", "bmp" ] } +agb-gbafix = { path = "../../agb-gbafix" } \ No newline at end of file diff --git a/emulator/screenshot-generator/src/image_generate.rs b/emulator/screenshot-generator/src/image_generate.rs new file mode 100644 index 00000000..b4ed863c --- /dev/null +++ b/emulator/screenshot-generator/src/image_generate.rs @@ -0,0 +1,23 @@ +use image::{DynamicImage, GenericImage, Rgba}; + +const WIDTH: usize = 240; +const HEIGHT: usize = 160; + +pub fn generate_image(video_buffer: &[u32]) -> DynamicImage { + let mut dynamic_image = DynamicImage::new( + WIDTH.try_into().unwrap(), + HEIGHT.try_into().unwrap(), + image::ColorType::Rgba8, + ); + for y in 0..HEIGHT { + for x in 0..WIDTH { + let video_pixel = video_buffer[x + y * WIDTH]; + let mut pixels = video_pixel.to_le_bytes(); + pixels[3] = 255; + + dynamic_image.put_pixel(x.try_into().unwrap(), y.try_into().unwrap(), Rgba(pixels)); + } + } + + dynamic_image +} diff --git a/emulator/screenshot-generator/src/main.rs b/emulator/screenshot-generator/src/main.rs new file mode 100644 index 00000000..2146b414 --- /dev/null +++ b/emulator/screenshot-generator/src/main.rs @@ -0,0 +1,96 @@ +use std::{ + error::Error, + fs::File, + io::{BufWriter, Read}, + path::{Path, PathBuf}, +}; + +use anyhow::anyhow; +use clap::Parser; +use image::DynamicImage; +use image_generate::generate_image; +use mgba::{LogLevel, Logger, MCore, MemoryBacked, VFile}; + +mod image_generate; + +static LOGGER: Logger = Logger::new(my_logger); + +fn my_logger(_category: &str, _level: LogLevel, _s: String) {} + +#[derive(Parser)] +struct CliArguments { + #[arg(long)] + rom: PathBuf, + #[arg(long)] + frames: usize, + #[arg(long)] + output: PathBuf, +} + +struct ScreenshotGenerator { + mgba: MCore, +} + +impl ScreenshotGenerator { + fn new(rom: V) -> Result> { + let mut mgba = MCore::new().ok_or(anyhow!("cannot create core"))?; + + mgba::set_global_default_logger(&LOGGER); + + mgba.load_rom(rom); + + Ok(Self { mgba }) + } + + fn run(mut self, frames: usize) -> DynamicImage { + for _ in 0..frames { + self.mgba.frame(); + } + + generate_image(self.mgba.video_buffer()) + } +} + +fn main() -> Result<(), Box> { + let args = CliArguments::parse(); + + let rom = load_rom(args.rom)?; + let rom = MemoryBacked::new(rom); + + let image = ScreenshotGenerator::new(rom)?.run(args.frames); + + let mut output = BufWriter::new( + File::options() + .write(true) + .create(true) + .truncate(true) + .open(args.output)?, + ); + image.write_to(&mut output, image::ImageOutputFormat::Png)?; + + Ok(()) +} + +fn load_rom>(path: P) -> anyhow::Result> { + let mut input_file = File::open(path)?; + let mut input_file_buffer = Vec::new(); + + input_file.read_to_end(&mut input_file_buffer)?; + + let mut elf_buffer = Vec::new(); + + let inculde_debug_info = false; + if agb_gbafix::write_gba_file( + &input_file_buffer, + Default::default(), + agb_gbafix::PaddingBehaviour::DoNotPad, + inculde_debug_info, + &mut elf_buffer, + ) + .is_ok() + { + Ok(elf_buffer) + } else { + Ok(input_file_buffer) + } +}