Skip to content

Commit

Permalink
✨ Cool stuff with hatched patterns
Browse files Browse the repository at this point in the history
  • Loading branch information
ewen-lbh committed May 3, 2024
1 parent e1b7c54 commit dd48ca2
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 73 deletions.
8 changes: 4 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ start-web:
install:
cp shapemaker ~/.local/bin/

example-video args='':
./shapemaker video --colors colorschemes/palenight.css out.mp4 --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}}
example-video out="out.mp4" args='':
./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}}

example-image args='':
./shapemaker image --colors colorschemes/palenight.css out.svg {{args}}
example-image out="out.png" args='':
./shapemaker image --colors colorschemes/palenight.css {{out}} {{args}}
Binary file added out.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/anchors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ impl Anchor {
self.1 += dy;
}

pub fn translated(&self, dx: i32, dy: i32) -> Self {
Anchor(self.0 + dx, self.1 + dy)
}

pub fn distances(&self, other: &Anchor) -> (usize, usize) {
(
self.0.abs_diff(other.0) as usize,
Expand All @@ -28,6 +32,18 @@ impl From<(i32, i32)> for Anchor {
#[wasm_bindgen]
pub struct CenterAnchor(pub i32, pub i32);

impl From<(usize, usize)> for CenterAnchor {
fn from(value: (usize, usize)) -> Self {
CenterAnchor(value.0 as i32, value.1 as i32)
}
}

impl From<(usize, usize)> for Anchor {
fn from(value: (usize, usize)) -> Self {
Anchor(value.0 as i32, value.1 as i32)
}
}

impl CenterAnchor {
pub fn translate(&mut self, dx: i32, dy: i32) {
self.0 += dx;
Expand Down
40 changes: 29 additions & 11 deletions src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use svg::node::element::Pattern;

use crate::{
layer::Layer, objects::Object, random_color, web::console_log, Anchor, CenterAnchor, Color,
ColorMapping, ColoredObject, Fill, Filter, LineSegment, ObjectSizes, Point, Region,
ColorMapping, ColoredObject, Fill, Filter, HatchDirection, LineSegment, ObjectSizes, Point,
Region,
};

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -170,12 +171,13 @@ impl Canvas {
let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone());
for i in 0..number_of_objects {
let object = self.random_object_within(region);
let hatchable = object.hatchable();
objects.insert(
format!("{}#{}", name, i),
ColoredObject(
object,
if rand::thread_rng().gen_bool(0.5) {
Some(self.random_fill())
Some(self.random_fill(hatchable))
} else {
None
},
Expand All @@ -200,12 +202,13 @@ impl Canvas {
let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone());
for i in 0..number_of_objects {
let object = self.random_linelike_within(region);
let hatchable = object.fillable();
objects.insert(
format!("{}#{}", layer_name, i),
ColoredObject(
object,
if rand::thread_rng().gen_bool(0.5) {
Some(self.random_fill())
Some(self.random_fill(hatchable))
} else {
None
},
Expand Down Expand Up @@ -350,14 +353,29 @@ impl Canvas {
}
}

pub fn random_fill(&self) -> Fill {
Fill::Solid(random_color())
// match rand::thread_rng().gen_range(1..=3) {
// 1 => Fill::Solid(random_color()),
// 2 => Fill::Hatched,
// 3 => Fill::Dotted,
// _ => unreachable!(),
// }
pub fn random_fill(&self, hatchable: bool) -> Fill {
if hatchable {
match rand::thread_rng().gen_range(1..=2) {
1 => Fill::Solid(random_color()),
2 => {
let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2;
Fill::Hatched(
random_color(),
HatchDirection::BottomUpDiagonal,
hatch_size,
// under a certain hatch size, we can't see the hatching if the ratio is not ½
if hatch_size < 8.0 {
0.5
} else {
rand::thread_rng().gen_range(1..=4) as f32 / 4.0
},
)
}
_ => unreachable!(),
}
} else {
Fill::Solid(random_color())
}
}

pub fn clear(&mut self) {
Expand Down
20 changes: 16 additions & 4 deletions src/layer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{ColorMapping, ColoredObject, Fill, Filter, Object, ObjectSizes};
use anyhow::Context;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;

Expand Down Expand Up @@ -56,19 +57,30 @@ impl Layer {
self.flush();
}

pub fn add_object(&mut self, name: &str, object: Object, fill: Option<Fill>) {
self.objects.insert(name.to_string(), (object, fill).into());
pub fn add_object(&mut self, name: &str, object: ColoredObject) {
self.objects.insert(name.to_string(), object);
self.flush();
}

pub fn filter_object(&mut self, name: &str, filter: Filter) -> Result<(), String> {
self.objects
.get_mut(name)
.ok_or(format!("Object '{}' not found", name))?
.2
.push(filter);

self.flush();
Ok(())
}

pub fn remove_object(&mut self, name: &str) {
self.objects.remove(name);
self.flush();
}

pub fn replace_object(&mut self, name: &str, object: Object, fill: Option<Fill>) {
pub fn replace_object(&mut self, name: &str, object: ColoredObject) {
self.remove_object(name);
self.add_object(name, object, fill);
self.add_object(name, object);
}

/// Render the layer to a SVG group element.
Expand Down
75 changes: 53 additions & 22 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use itertools::Itertools;
use rand::Rng;
use shapemaker::{
cli::{canvas_from_cli, cli_args},
*,
Expand All @@ -12,18 +13,49 @@ pub fn run(args: cli::Args) {
let mut canvas = canvas_from_cli(&args);

if args.cmd_image && !args.cmd_video {
canvas.layers.push(Layer::new("root"));
canvas.set_background(Color::White);
canvas.layer("root").add_object(
"feur",
Object::Rectangle(Anchor(0, 0), Anchor(2, 2)),
Some(Fill::Hatched(
Color::Red,
HatchDirection::BottomUpDiagonal,
2.0,
0.25,
)),
);
let mut layer = Layer::new("root");

let red_circle_at = canvas.world_region.enlarged(-1, -1).random_point_within();

for (i, Point(x, y)) in canvas
.world_region
.resized(-1, -1)
.enlarged(-2, -2)
.iter()
.enumerate()
{
layer.add_object(
&format!("{}-{}", x, y),
if rand::thread_rng().gen_bool(0.5) && red_circle_at != Point(x, y) {
Object::BigCircle((x, y).into())
} else {
Object::Rectangle((x, y).into(), Anchor::from((x, y)).translated(1, 1))
}
.color(if red_circle_at == Point(x, y) {
Fill::Solid(Color::Red)
} else {
Fill::Hatched(
Color::White,
HatchDirection::BottomUpDiagonal,
(i + 1) as f32 / 10.0,
0.25,
)
}),
// .filter(Filter::glow(7.0)),
);
}
canvas.layers.push(layer);
canvas.set_background(Color::Black);
// canvas.layer("root").add_object(
// "feur",
// Object::Rectangle(Anchor(0, 0), Anchor(2, 2)),
// Some(Fill::Hatched(
// Color::Red,
// HatchDirection::BottomUpDiagonal,
// 2.0,
// 0.25,
// )),
// );
// canvas.layers[0].paint_all_objects(Fill::Hatched(
// Color::Red,
// HatchDirection::BottomUpDiagonal,
Expand Down Expand Up @@ -63,21 +95,21 @@ pub fn run(args: cli::Args) {

let mut kicks = Layer::new("anchor kick");

let fill = Some(Fill::Translucent(Color::White, 0.0));
let fill = Fill::Translucent(Color::White, 0.0);
let circle_at = |x: usize, y: usize| Object::SmallCircle(Anchor(x as i32, y as i32));

let (end_x, end_y) = {
let Point(x, y) = canvas.world_region.end;
(x - 2, y - 2)
};
kicks.add_object("top left", circle_at(1, 1), fill);
kicks.add_object("top right", circle_at(end_x, 1), fill);
kicks.add_object("bottom left", circle_at(1, end_y), fill);
kicks.add_object("bottom right", circle_at(end_x, end_y), fill);
kicks.add_object("top left", circle_at(1, 1).color(fill));
kicks.add_object("top right", circle_at(end_x, 1).color(fill));
kicks.add_object("bottom left", circle_at(1, end_y).color(fill));
kicks.add_object("bottom right", circle_at(end_x, end_y).color(fill));
canvas.add_or_replace_layer(kicks);

let mut ch = Layer::new("ch");
ch.add_object("0", Object::Dot(Anchor(0, 0)), None);
ch.add_object("0", Object::Dot(Anchor(0, 0)).into());
canvas.add_or_replace_layer(ch);
})
.sync_audio_with(&args.flag_sync_with.unwrap())
Expand Down Expand Up @@ -160,8 +192,8 @@ pub fn run(args: cli::Args) {
let object_name = format!("{}", ctx.ms);
layer.add_object(
&object_name,
Object::Dot(world.resized(-1, -1).random_coordinates_within().into()),
Some(Fill::Solid(Color::Cyan)),
Object::Dot(world.resized(-1, -1).random_coordinates_within().into())
.color(Fill::Solid(Color::Cyan)),
);

canvas.put_layer_on_top("ch");
Expand All @@ -170,8 +202,7 @@ pub fn run(args: cli::Args) {
.when_remaining(10, &|canvas, _| {
canvas.root().add_object(
"credits text",
Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))),
None,
Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))).into(),
);
})
.command("remove", &|argumentsline, canvas, _| {
Expand Down
31 changes: 31 additions & 0 deletions src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,36 @@ pub enum Object {
RawSVG(Box<dyn svg::Node>),
}

impl Object {
pub fn color(self, fill: Fill) -> ColoredObject {
ColoredObject::from((self, Some(fill)))
}

pub fn filter(self, filter: Filter) -> ColoredObject {
ColoredObject::from((self, None)).filter(filter)
}
}

#[derive(Debug, Clone)]
pub struct ColoredObject(pub Object, pub Option<Fill>, pub Vec<Filter>);

impl ColoredObject {
pub fn filter(mut self, filter: Filter) -> Self {
self.2.push(filter);
self
}

pub fn clear_filters(&mut self) {
self.2.clear();
}
}

impl From<Object> for ColoredObject {
fn from(value: Object) -> Self {
ColoredObject(value, None, vec![])
}
}

impl From<(Object, Option<Fill>)> for ColoredObject {
fn from(value: (Object, Option<Fill>)) -> Self {
ColoredObject(value.0, value.1, vec![])
Expand Down Expand Up @@ -148,6 +175,10 @@ impl Object {
)
}

pub fn hatchable(&self) -> bool {
self.fillable() && !matches!(self, Object::Dot(..))
}

pub fn render(
&self,
cell_size: usize,
Expand Down
41 changes: 41 additions & 0 deletions src/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,47 @@ pub struct Region {
pub end: Point,
}

impl Region {
pub fn iter(&self) -> RegionIterator {
self.into()
}

pub fn random_point_within(&self) -> Point {
Point::from(self.random_coordinates_within())
}
}

pub struct RegionIterator {
region: Region,
current: Point,
}

impl Iterator for RegionIterator {
type Item = Point;

fn next(&mut self) -> Option<Self::Item> {
if self.current.0 >= self.region.end.0 {
self.current.0 = self.region.start.0;
self.current.1 += 1;
}
if self.current.1 >= self.region.end.1 {
return None;
}
let result = self.current;
self.current.0 += 1;
Some(result)
}
}

impl From<&Region> for RegionIterator {
fn from(region: &Region) -> Self {
Self {
region: region.clone(),
current: region.start.clone(),
}
}
}

impl From<((usize, usize), (usize, usize))> for Region {
fn from(value: ((usize, usize), (usize, usize))) -> Self {
Region {
Expand Down
Loading

0 comments on commit dd48ca2

Please sign in to comment.