summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2024-02-06 04:16:27 -0500
committerJesse Luehrs <doy@tozt.net>2024-02-06 04:19:33 -0500
commitf4dfb0efcdca221eecdc10ff2d36381b77c3bf3c (patch)
tree25b8e42dc80cb4fa588cdac6d07d62e5a3e37f78
parentac44cd4e898b0a9bbadd3fa1d9170a0490ae2efa (diff)
downloadsmw-tools-f4dfb0efcdca221eecdc10ff2d36381b77c3bf3c.tar.gz
smw-tools-f4dfb0efcdca221eecdc10ff2d36381b77c3bf3c.zip
initial tile editor implementation
-rw-r--r--.rustfmt.toml2
-rw-r--r--Cargo.lock100
-rw-r--r--Cargo.toml2
-rw-r--r--src/bin/tile-editor.rs174
-rw-r--r--src/main.rs3
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",
+]
diff --git a/Cargo.toml b/Cargo.toml
index d53bbd4..7fd1459 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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!");
-}