1
0
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:
2021-04-08 16:28:33 +01:00
parent d9933d52c5
commit c80ea1d0c7
14 changed files with 1389 additions and 14 deletions

2
fractalart-rs/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target
out.png

216
fractalart-rs/Cargo.lock generated Normal file
View 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
View 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
View 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
View 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");
}