diff options
author | Jesse Luehrs <doy@tozt.net> | 2024-02-06 04:16:27 -0500 |
---|---|---|
committer | Jesse Luehrs <doy@tozt.net> | 2024-02-06 04:19:33 -0500 |
commit | f4dfb0efcdca221eecdc10ff2d36381b77c3bf3c (patch) | |
tree | 25b8e42dc80cb4fa588cdac6d07d62e5a3e37f78 | |
parent | ac44cd4e898b0a9bbadd3fa1d9170a0490ae2efa (diff) | |
download | smw-tools-f4dfb0efcdca221eecdc10ff2d36381b77c3bf3c.tar.gz smw-tools-f4dfb0efcdca221eecdc10ff2d36381b77c3bf3c.zip |
initial tile editor implementation
-rw-r--r-- | .rustfmt.toml | 2 | ||||
-rw-r--r-- | Cargo.lock | 100 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/bin/tile-editor.rs | 174 | ||||
-rw-r--r-- | src/main.rs | 3 |
5 files changed, 278 insertions, 3 deletions
diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..55b0b14 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +edition = "2018" +max_width = 78 diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ca82db1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,100 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bytemuck" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2490600f404f2b94c167e31d3ed1d5f3c225a0f3b80230053b3e0b7b962bd9" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "image" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "smw-tools" +version = "0.1.0" +dependencies = [ + "image", + "regex", +] @@ -6,3 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +image = { version = "0.24.8", default-features = false, features = ["pnm"] } +regex = "1.10.3" diff --git a/src/bin/tile-editor.rs b/src/bin/tile-editor.rs new file mode 100644 index 0000000..1e361fb --- /dev/null +++ b/src/bin/tile-editor.rs @@ -0,0 +1,174 @@ +use std::io::{BufRead as _, Read as _, Seek as _, Write as _}; + +use image::Pixel as _; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Tile([u8; 32]); + +impl Tile { + fn load_from_file<F: AsRef<std::path::Path>>( + filename: F, + idx: usize, + ) -> Self { + let mut fh = std::fs::File::open(filename).unwrap(); + let mut buf = [0; 32]; + fh.seek(std::io::SeekFrom::Start(u64::try_from(idx * 32).unwrap())) + .unwrap(); + fh.read_exact(&mut buf).unwrap(); + Self(buf) + } + + fn write_to_file<F: AsRef<std::path::Path>>( + self, + filename: F, + idx: usize, + ) { + let mut fh = std::fs::OpenOptions::new() + .write(true) + .open(filename) + .unwrap(); + fh.seek(std::io::SeekFrom::Start(u64::try_from(idx * 32).unwrap())) + .unwrap(); + fh.write_all(&self.0).unwrap(); + } + + fn from_image_at(im: &image::GrayImage, idx: usize) -> Self { + let (width, _) = im.dimensions(); + assert_eq!(width, 128); + + let tile_row = idx / 16; + let tile_col = idx % 16; + + let mut bytes = [0; 32]; + for row_offset in 0..8 { + for col_offset in 0..8 { + let row = tile_row * 8 + row_offset; + let col = tile_col * 8 + col_offset; + let pixel = im.get_pixel( + col.try_into().unwrap(), + row.try_into().unwrap(), + ); + let val = pixel.channels()[0] / 16; + let b1 = val & 0x01; + let b2 = (val >> 1) & 0x01; + let b3 = (val >> 2) & 0x01; + let b4 = (val >> 3) & 0x01; + bytes[row_offset * 2] |= b1 << (7 - col_offset); + bytes[row_offset * 2 + 1] |= b2 << (7 - col_offset); + bytes[row_offset * 2 + 16] |= b3 << (7 - col_offset); + bytes[row_offset * 2 + 17] |= b4 << (7 - col_offset); + } + } + + Self(bytes) + } + + fn to_image(self) -> image::GrayImage { + image::GrayImage::from_fn(8, 8, |x, y| { + let row = usize::try_from(y).unwrap(); + let col = usize::try_from(x).unwrap(); + image::Luma::from([((self.0[row * 2] >> (7 - col)) & 0x01) + | (((self.0[row * 2 + 1] >> (7 - col)) & 0x01) << 1) + | (((self.0[row * 2 + 16] >> (7 - col)) & 0x01) << 2) + | (((self.0[row * 2 + 17] >> (7 - col)) & 0x01) << 3)]) + }) + } +} + +// Graphics/GFX05.bin: +// 0C Graphics/GFX02.bin:0E:2 +#[derive(Debug)] +struct TilemapEdit { + from_file: std::path::PathBuf, + from_idx: usize, + to_file: std::path::PathBuf, + to_idx: usize, + size: usize, +} + +impl TilemapEdit { + fn apply(&self) { + let mut offsets = vec![]; + for x in 0..self.size { + for y in 0..self.size { + offsets.push(16 * x + y); + } + } + for offset in offsets { + let tile = + match self.from_file.extension().and_then(|s| s.to_str()) { + Some("bin") => Tile::load_from_file( + &self.from_file, + self.from_idx + offset, + ), + Some("pgm") => { + let im = + image::io::Reader::open(&self.from_file).unwrap(); + let im = im.decode().unwrap(); + let im = image::GrayImage::from(im); + Tile::from_image_at(&im, self.from_idx + offset) + } + _ => unimplemented!(), + }; + tile.write_to_file(&self.to_file, self.to_idx + offset); + } + } +} + +#[derive(Debug)] +struct TilemapEdits(Vec<TilemapEdit>); + +impl TilemapEdits { + fn load<P: AsRef<std::path::Path>>(filename: P) -> Self { + let fh = std::fs::File::open(filename).unwrap(); + let fh = std::io::BufReader::new(fh); + let rx = regex::Regex::new( + r"^([0-9a-fA-F]+) (.*\.(?:pgm|bin)):([0-9a-fA-F]+)(?::([124]))$", + ) + .unwrap(); + + let mut current_file = None; + let mut edits = vec![]; + for line in fh.lines() { + let line = line.unwrap(); + if line.is_empty() { + continue; + } + + if line.starts_with(' ') { + let line = line.trim_start(); + let cap = rx.captures(line).unwrap(); + edits.push(TilemapEdit { + from_file: cap.get(2).unwrap().as_str().into(), + from_idx: usize::from_str_radix( + cap.get(3).unwrap().as_str(), + 16, + ) + .unwrap(), + to_file: current_file.clone().unwrap(), + to_idx: usize::from_str_radix( + cap.get(1).unwrap().as_str(), + 16, + ) + .unwrap(), + size: cap + .get(4) + .map_or(1, |s| s.as_str().parse().unwrap()), + }) + } else { + assert!(line.ends_with(".bin:")); + current_file = Some(line.strip_suffix(':').unwrap().into()); + } + } + + Self(edits) + } +} + +fn main() { + let edits_file = std::env::args().nth(1).unwrap(); + let edits = TilemapEdits::load(edits_file); + for edit in edits.0 { + edit.apply() + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} |