mirror of
https://github.com/osmarks/random-stuff
synced 2025-10-22 17:37:38 +00:00
New things, documentation
This commit is contained in:
2
fractalart-rs/.gitignore
vendored
Normal file
2
fractalart-rs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
target
|
||||
out.png
|
216
fractalart-rs/Cargo.lock
generated
Normal file
216
fractalart-rs/Cargo.lock
generated
Normal file
@@ -0,0 +1,216 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "adler32"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deflate"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fractalart"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gumdrop",
|
||||
"hsl",
|
||||
"image",
|
||||
"nanorand",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gumdrop"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46571f5d540478cf70d2a42dd0d6d8e9f4b9cc7531544b93311e657b86568a0b"
|
||||
dependencies = [
|
||||
"gumdrop_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gumdrop_derive"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "915ef07c710d84733522461de2a734d4d62a3fd39a4d4f404c2f385ef8618d05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hsl"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "575fb7f1167f3b88ed825e90eb14918ac460461fdeaa3965c6a50951dee1c970"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.23.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2397fc43bd5648b7117aabb3c5e62d0e62c194826ec77b0b4d0c41e62744635"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1378b66f7c93a1c0f8464a19bf47df8795083842e5090f4b7305973d5a22d0"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-iter"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.16.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c150bf7479fafe3dd8740dbe48cc33b2a3efb7b0fe3483aced8bbc39f6d0238d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"deflate",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3757cb9d89161a2f24e1cf78efa0c1fcff485d18e3f55e0aa3480824ddaa0f3f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936cae2873c940d92e697597c5eee105fb570cd5689c695806f672883653349b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
12
fractalart-rs/Cargo.toml
Normal file
12
fractalart-rs/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "fractalart"
|
||||
version = "0.1.0"
|
||||
authors = ["osmarks <osmarks@protonmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
image = { version = "0.23", features = ["png"], default-features = false }
|
||||
nanorand = { version = "0.5", features = ["std", "rdseed", "wyrand", "tls"], default-features = false }
|
||||
hsl = "0.1.1"
|
||||
gumdrop = "0.8"
|
||||
smallvec = "1.4.1"
|
11
fractalart-rs/README.md
Normal file
11
fractalart-rs/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# FractalArt-rs
|
||||
|
||||
This is [this](https://github.com/TomSmeets/FractalArt) "fractal" wallpaper thing, ported to Rust and with significant performance improvements and more configuration.
|
||||
It is lacking the set-as-wallpaper feature of the original, but it should be possible to put this in a shellscript or something with whatever works on your platform.
|
||||
|
||||
## Usage
|
||||
|
||||
Compile via `cargo build --release`.
|
||||
The resulting binary is in `target/release/fractalart`. With no other arguments, it will generate a 1000x1000 image and save it to `out.png`. You can use `-W`/`-H`/`-o` to change that.
|
||||
The initial seed color can also be configured with `-u` (hue; this will default to a randomly generated one), `-s` and `-l`. The random seed is set with `-r`; if the other parameters are the same, then the same seed will give the same image.
|
||||
Images generated by this are slightly different to the original program's because of the use of 16-bit integers instead of floats (it might actually be possible to go down to `u8`s without quality problems, but I haven't tried), and also the different default variance (`-v`); the original program hardcodes this to the equivalent of 1573, but I picked 2048, as a rounder number.
|
140
fractalart-rs/src/main.rs
Normal file
140
fractalart-rs/src/main.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use image::{Rgb, ImageBuffer, RgbImage};
|
||||
use std::cmp::{max, min};
|
||||
use nanorand::{WyRand, RNG};
|
||||
use gumdrop::Options;
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
type R16Image = ImageBuffer<Rgb<u16>, Vec<u16>>;
|
||||
|
||||
// convert HSL to RGB, wrapping the hsl crate
|
||||
fn hsl_to_rgb(h: f64, s: f64, l: f64) -> Rgb<u8> {
|
||||
let (r, g, b) = (hsl::HSL { h, s, l }).to_rgb();
|
||||
Rgb::from([r, g, b])
|
||||
}
|
||||
|
||||
type IPos = (i32, i32);
|
||||
|
||||
// checks if a position is inside the given width/height
|
||||
fn is_inside((w, h): IPos, (x, y): IPos) -> bool {
|
||||
x < w && y < h && x >= 0 && y >= 0
|
||||
}
|
||||
|
||||
// generate a square "ring" around a point with supplied distance
|
||||
// drops all values outside of grid
|
||||
fn ring_at(dim: IPos, (x, y): IPos, l: i32) -> impl Iterator<Item = IPos> {
|
||||
// use smallvec for higher efficiency if generating small ring for next_color
|
||||
// it might be better to just have a separate codepath entirely for size-1 rings really
|
||||
// or make it work as an iterator properly
|
||||
let mut out: SmallVec<[_; 8]> = smallvec![];
|
||||
// top and bottom of ring
|
||||
for n in -l..=l {
|
||||
out.push((n + x, l + y));
|
||||
out.push((n + x, -l + y));
|
||||
}
|
||||
// sides of ring
|
||||
for n in (1 - l)..l {
|
||||
out.push((l + x, n + y));
|
||||
out.push((-l + x, n + y));
|
||||
}
|
||||
out.into_iter().filter(move |c| is_inside(dim, *c))
|
||||
}
|
||||
|
||||
// randomly increase/decrease one of the channels in a color by `range`
|
||||
fn mod_channel(rng: &mut WyRand, range: u16, n: u16) -> u16 {
|
||||
// to avoid conversion to signed integers here, do things of some sort
|
||||
let rand: u16 = rng.generate::<u16>() % (range * 2 + 2);
|
||||
let o = ((n as u32) + (rand as u32)).saturating_sub(range as u32);
|
||||
// avoid weird artifacts - just directly using `as` truncates it, i.e. drops the high bytes, which leads to integer-overflow-like issues
|
||||
min(o, 65535) as u16
|
||||
}
|
||||
|
||||
// randomly adjust all the channels in a color by `range`
|
||||
fn mod_color(rng: &mut WyRand, range: u16, col: Rgb<u16>) -> Rgb<u16> {
|
||||
Rgb([mod_channel(rng, range, col[0]), mod_channel(rng, range, col[1]), mod_channel(rng, range, col[2])])
|
||||
}
|
||||
|
||||
// the original Haskell implementation has a grid of booleans and colors for this
|
||||
// doing that in this would probably introduce more complexity and reduce efficiency significantly, so just reserve black for uninitialized pixels
|
||||
const BLANK: Rgb<u16> = Rgb([0, 0, 0]);
|
||||
|
||||
// get the next color to use by randomly selecting an adjacent nonblank pixel and randomizing it a bit
|
||||
fn next_color(opts: &CLIOptions, rng: &mut WyRand, im: &R16Image, pos: IPos) -> Rgb<u16> {
|
||||
let (w, h) = im.dimensions();
|
||||
let dim = (w as i32, h as i32);
|
||||
let mut colors: SmallVec<[_; 8]> = smallvec![];
|
||||
for (x, y) in ring_at(dim, pos, 1) {
|
||||
let px = *im.get_pixel(x as u32, y as u32);
|
||||
if px != BLANK {
|
||||
colors.push(px);
|
||||
}
|
||||
}
|
||||
let chosen = colors[rng.generate_range(0, colors.len())];
|
||||
mod_color(rng, opts.variance / colors.len() as u16, chosen)
|
||||
}
|
||||
|
||||
// run an iteration by filling in all the pixels in a ring around the start position
|
||||
fn iter(opts: &CLIOptions, rng: &mut WyRand, im: &mut R16Image, pos: IPos, i: i32) {
|
||||
let (w, h) = im.dimensions();
|
||||
let dim = (w as i32, h as i32);
|
||||
|
||||
for (x, y) in ring_at(dim, pos, i) {
|
||||
im.put_pixel(x as u32, y as u32, next_color(opts, rng, im, (x, y)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Options, Debug)]
|
||||
struct CLIOptions {
|
||||
#[options(help = "width of generated image", short = "H", default = "1000")]
|
||||
width: u32,
|
||||
#[options(help = "height of generated image", short = "W", default = "1000")]
|
||||
height: u32,
|
||||
#[options(help = "filename to save generated image to", short = "o", default = "./out.png")]
|
||||
filename: String,
|
||||
// required for gumdrop to provide help text
|
||||
#[options(help = "display this help", short = "h")]
|
||||
help: bool,
|
||||
#[options(help = "max color channel difference from previous pixel", short = "v", default = "2048")]
|
||||
variance: u16,
|
||||
#[options(help = "base color saturation", short = "s", default = "1.0")]
|
||||
saturation: f64,
|
||||
#[options(help = "base color lightness", short = "l", default = "0.6")]
|
||||
lightness: f64,
|
||||
#[options(help = "base color hue (default: random)", short = "u")]
|
||||
hue: Option<f64>,
|
||||
#[options(help = "random seed (default: random)", short="r")]
|
||||
seed: Option<u64>
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let opts = CLIOptions::parse_args_default_or_exit();
|
||||
println!("{:?}", opts);
|
||||
|
||||
let mut rng = match opts.seed {
|
||||
Some(seed) => nanorand::WyRand::new_seed(seed),
|
||||
None => nanorand::WyRand::new()
|
||||
};
|
||||
let w = opts.width;
|
||||
let h = opts.height;
|
||||
let start_x = rng.generate_range(0, w - 1);
|
||||
let start_y = rng.generate_range(0, h - 1);
|
||||
|
||||
// generate a starting color
|
||||
let hue = opts.hue.unwrap_or_else(|| rng.generate_range(0u64, 360 * 65536) as f64 / 65536.);
|
||||
let start_color = hsl_to_rgb(hue, opts.saturation, opts.lightness);
|
||||
let start_color = Rgb([start_color[0] as u16 * 256, start_color[1] as u16 * 256, start_color[2] as u16 * 256]);
|
||||
|
||||
let iterations = max(max(start_x, w - 1 - start_x), max(start_y, h - 1 - start_y)) as i32;
|
||||
let mut im = R16Image::from_pixel(w, h, BLANK);
|
||||
im.put_pixel(start_x, start_y, start_color);
|
||||
for i in 1..=iterations {
|
||||
iter(&opts, &mut rng, &mut im, (start_x as i32, start_y as i32), i);
|
||||
}
|
||||
|
||||
// discard low bits of image pixels before saving, as monitors mostly can't render these and it wastes space
|
||||
let low_color_depth_image = RgbImage::from_fn(w, h, |x, y| {
|
||||
let Rgb([r, g, b]) = im.get_pixel(x, y);
|
||||
Rgb([(r >> 8) as u8, (g >> 8) as u8, (b >> 8) as u8])
|
||||
});
|
||||
|
||||
low_color_depth_image.save(&opts.filename).expect("failed to save image");
|
||||
}
|
Reference in New Issue
Block a user