diff --git a/Cargo.lock b/Cargo.lock index c30458e..126a457 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "anstream" version = "0.6.18" @@ -58,6 +64,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.9.0" @@ -74,6 +86,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" + +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "cc" version = "1.2.16" @@ -135,6 +159,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "either" version = "1.15.0" @@ -157,11 +190,31 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "grapher" version = "0.1.0" dependencies = [ "clap", + "image", "mlua", ] @@ -171,6 +224,18 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "image" +version = "0.25.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +dependencies = [ + "bytemuck", + "byteorder-lite", + "num-traits", + "png", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -224,6 +289,16 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mlua" version = "0.10.3" @@ -295,6 +370,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -319,7 +407,7 @@ version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags", + "bitflags 2.9.0", ] [[package]] @@ -334,7 +422,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -373,6 +461,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "smallvec" version = "1.14.0" diff --git a/Cargo.toml b/Cargo.toml index dc0f7ab..a074cdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,5 @@ edition = "2021" [dependencies] clap = { version = "4.5.32", features = ["derive"] } +image = { version = "0.25.5", features = ["png"], default-features = false } mlua = { version = "0.10.3", features = ["lua54", "vendored"] } diff --git a/src/main.rs b/src/main.rs index 9d78218..277a145 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,9 @@ -use std::{num::NonZero, process}; +use std::{path::PathBuf, process}; use clap::Parser; +use image::{ImageBuffer, Pixel}; use mlua::Lua; + fn main() { let Args { width, @@ -11,9 +13,13 @@ fn main() { ymin, xmax, ymax, + output_file, } = Args::parse(); + if output_file.extension().is_none_or(|s| s != "png") { + eprintln!("i'm only doing pngs"); + process::exit(2) + } let lua = Lua::new(); - // so we can write unqualified sin, cos, etc lua.load("setmetatable(_G, {__index = math})") .exec() @@ -41,7 +47,6 @@ fn main() { let pos_to_sample = |ix: u16, iy: u16| -> (f32, f32) { // TODO: bad - let (width, height): (u16, u16) = (width.into(), height.into()); let (width, height): (f32, f32) = (width.into(), height.into()); let (x, y) = (f32::from(ix), f32::from(iy)); let (xt, yt) = (x / width, y / height); @@ -51,8 +56,8 @@ fn main() { let (mut min, mut max) = (-1.0f32, 1.0f32); // first, find the min and max of the function // (literally doubles our runtime :D) - for iy in 0..(height.into()) { - for ix in 0..(width.into()) { + for iy in 0..height { + for ix in 0..width { let (x, y) = pos_to_sample(ix, iy); let i = f(x, y); min = min.min(i); @@ -61,35 +66,31 @@ fn main() { } let (min, max) = (min, max); - for iy in (0..(height.into())).rev() { - for ix in 0..(width.into()) { - let (x, y) = pos_to_sample(ix, iy); - let i = f(x, y); - let scaled = i / (max - min); - #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] - // cube rooting makes things sharper - let channel = (scaled.cbrt() * 256.) as u8; - put_pixel(channel, channel, channel); - put_pixel(channel, channel, channel); - } - println!(); + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] + let img = ImageBuffer::from_fn(width.into(), height.into(), |x, y| { + let (x, y) = pos_to_sample(x as u16, y as u16); + let i = f(x, y); + let scaled = i / (max - min); + // cube rooting makes things sharper + let channel = (scaled.cbrt() * 256.) as u8; + image::Luma([channel]).to_rgb() + }); + if let Err(e) = img.save(output_file) { + eprintln!("error saving image: {e}"); + process::exit(4); } - // reset style - print!("\x1b[0m"); -} - -fn put_pixel(r: u8, g: u8, b: u8) { - print!("\x1b[48;2;{r};{g};{b}m "); } #[derive(Debug, Parser)] struct Args { #[arg(long)] - width: NonZero<u16>, + width: u16, #[arg(long)] - height: NonZero<u16>, + height: u16, #[arg(short, long)] expr: String, + #[arg(short, long)] + output_file: PathBuf, #[arg(default_value_t = -1.0)] xmin: f32, #[arg(default_value_t = -1.0)]