this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

✨ Cool stuff with hatched patterns

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
6d15149a fbeac66c

+239 -73
+4 -4
Justfile
··· 12 12 install: 13 13 cp shapemaker ~/.local/bin/ 14 14 15 - example-video args='': 16 - ./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}} 15 + example-video out="out.mp4" args='': 16 + ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}} 17 17 18 - example-image args='': 19 - ./shapemaker image --colors colorschemes/palenight.css out.svg {{args}} 18 + example-image out="out.png" args='': 19 + ./shapemaker image --colors colorschemes/palenight.css {{out}} {{args}}
out.png

This is a binary file and will not be displayed.

+16
src/anchors.rs
··· 10 10 self.1 += dy; 11 11 } 12 12 13 + pub fn translated(&self, dx: i32, dy: i32) -> Self { 14 + Anchor(self.0 + dx, self.1 + dy) 15 + } 16 + 13 17 pub fn distances(&self, other: &Anchor) -> (usize, usize) { 14 18 ( 15 19 self.0.abs_diff(other.0) as usize, ··· 27 31 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 28 32 #[wasm_bindgen] 29 33 pub struct CenterAnchor(pub i32, pub i32); 34 + 35 + impl From<(usize, usize)> for CenterAnchor { 36 + fn from(value: (usize, usize)) -> Self { 37 + CenterAnchor(value.0 as i32, value.1 as i32) 38 + } 39 + } 40 + 41 + impl From<(usize, usize)> for Anchor { 42 + fn from(value: (usize, usize)) -> Self { 43 + Anchor(value.0 as i32, value.1 as i32) 44 + } 45 + } 30 46 31 47 impl CenterAnchor { 32 48 pub fn translate(&mut self, dx: i32, dy: i32) {
+29 -11
src/canvas.rs
··· 8 8 9 9 use crate::{ 10 10 layer::Layer, objects::Object, random_color, web::console_log, Anchor, CenterAnchor, Color, 11 - ColorMapping, ColoredObject, Fill, Filter, LineSegment, ObjectSizes, Point, Region, 11 + ColorMapping, ColoredObject, Fill, Filter, HatchDirection, LineSegment, ObjectSizes, Point, 12 + Region, 12 13 }; 13 14 14 15 #[derive(Debug, Clone)] ··· 170 171 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 171 172 for i in 0..number_of_objects { 172 173 let object = self.random_object_within(region); 174 + let hatchable = object.hatchable(); 173 175 objects.insert( 174 176 format!("{}#{}", name, i), 175 177 ColoredObject( 176 178 object, 177 179 if rand::thread_rng().gen_bool(0.5) { 178 - Some(self.random_fill()) 180 + Some(self.random_fill(hatchable)) 179 181 } else { 180 182 None 181 183 }, ··· 200 202 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 201 203 for i in 0..number_of_objects { 202 204 let object = self.random_linelike_within(region); 205 + let hatchable = object.fillable(); 203 206 objects.insert( 204 207 format!("{}#{}", layer_name, i), 205 208 ColoredObject( 206 209 object, 207 210 if rand::thread_rng().gen_bool(0.5) { 208 - Some(self.random_fill()) 211 + Some(self.random_fill(hatchable)) 209 212 } else { 210 213 None 211 214 }, ··· 350 353 } 351 354 } 352 355 353 - pub fn random_fill(&self) -> Fill { 354 - Fill::Solid(random_color()) 355 - // match rand::thread_rng().gen_range(1..=3) { 356 - // 1 => Fill::Solid(random_color()), 357 - // 2 => Fill::Hatched, 358 - // 3 => Fill::Dotted, 359 - // _ => unreachable!(), 360 - // } 356 + pub fn random_fill(&self, hatchable: bool) -> Fill { 357 + if hatchable { 358 + match rand::thread_rng().gen_range(1..=2) { 359 + 1 => Fill::Solid(random_color()), 360 + 2 => { 361 + let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2; 362 + Fill::Hatched( 363 + random_color(), 364 + HatchDirection::BottomUpDiagonal, 365 + hatch_size, 366 + // under a certain hatch size, we can't see the hatching if the ratio is not ½ 367 + if hatch_size < 8.0 { 368 + 0.5 369 + } else { 370 + rand::thread_rng().gen_range(1..=4) as f32 / 4.0 371 + }, 372 + ) 373 + } 374 + _ => unreachable!(), 375 + } 376 + } else { 377 + Fill::Solid(random_color()) 378 + } 361 379 } 362 380 363 381 pub fn clear(&mut self) {
+16 -4
src/layer.rs
··· 1 1 use crate::{ColorMapping, ColoredObject, Fill, Filter, Object, ObjectSizes}; 2 + use anyhow::Context; 2 3 use std::collections::HashMap; 3 4 use wasm_bindgen::prelude::*; 4 5 ··· 56 57 self.flush(); 57 58 } 58 59 59 - pub fn add_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { 60 - self.objects.insert(name.to_string(), (object, fill).into()); 60 + pub fn add_object(&mut self, name: &str, object: ColoredObject) { 61 + self.objects.insert(name.to_string(), object); 62 + self.flush(); 63 + } 64 + 65 + pub fn filter_object(&mut self, name: &str, filter: Filter) -> Result<(), String> { 66 + self.objects 67 + .get_mut(name) 68 + .ok_or(format!("Object '{}' not found", name))? 69 + .2 70 + .push(filter); 71 + 61 72 self.flush(); 73 + Ok(()) 62 74 } 63 75 64 76 pub fn remove_object(&mut self, name: &str) { ··· 66 78 self.flush(); 67 79 } 68 80 69 - pub fn replace_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { 81 + pub fn replace_object(&mut self, name: &str, object: ColoredObject) { 70 82 self.remove_object(name); 71 - self.add_object(name, object, fill); 83 + self.add_object(name, object); 72 84 } 73 85 74 86 /// Render the layer to a SVG group element.
+53 -22
src/main.rs
··· 1 1 use itertools::Itertools; 2 + use rand::Rng; 2 3 use shapemaker::{ 3 4 cli::{canvas_from_cli, cli_args}, 4 5 *, ··· 12 13 let mut canvas = canvas_from_cli(&args); 13 14 14 15 if args.cmd_image && !args.cmd_video { 15 - canvas.layers.push(Layer::new("root")); 16 - canvas.set_background(Color::White); 17 - canvas.layer("root").add_object( 18 - "feur", 19 - Object::Rectangle(Anchor(0, 0), Anchor(2, 2)), 20 - Some(Fill::Hatched( 21 - Color::Red, 22 - HatchDirection::BottomUpDiagonal, 23 - 2.0, 24 - 0.25, 25 - )), 26 - ); 16 + let mut layer = Layer::new("root"); 17 + 18 + let red_circle_at = canvas.world_region.enlarged(-1, -1).random_point_within(); 19 + 20 + for (i, Point(x, y)) in canvas 21 + .world_region 22 + .resized(-1, -1) 23 + .enlarged(-2, -2) 24 + .iter() 25 + .enumerate() 26 + { 27 + layer.add_object( 28 + &format!("{}-{}", x, y), 29 + if rand::thread_rng().gen_bool(0.5) && red_circle_at != Point(x, y) { 30 + Object::BigCircle((x, y).into()) 31 + } else { 32 + Object::Rectangle((x, y).into(), Anchor::from((x, y)).translated(1, 1)) 33 + } 34 + .color(if red_circle_at == Point(x, y) { 35 + Fill::Solid(Color::Red) 36 + } else { 37 + Fill::Hatched( 38 + Color::White, 39 + HatchDirection::BottomUpDiagonal, 40 + (i + 1) as f32 / 10.0, 41 + 0.25, 42 + ) 43 + }), 44 + // .filter(Filter::glow(7.0)), 45 + ); 46 + } 47 + canvas.layers.push(layer); 48 + canvas.set_background(Color::Black); 49 + // canvas.layer("root").add_object( 50 + // "feur", 51 + // Object::Rectangle(Anchor(0, 0), Anchor(2, 2)), 52 + // Some(Fill::Hatched( 53 + // Color::Red, 54 + // HatchDirection::BottomUpDiagonal, 55 + // 2.0, 56 + // 0.25, 57 + // )), 58 + // ); 27 59 // canvas.layers[0].paint_all_objects(Fill::Hatched( 28 60 // Color::Red, 29 61 // HatchDirection::BottomUpDiagonal, ··· 63 95 64 96 let mut kicks = Layer::new("anchor kick"); 65 97 66 - let fill = Some(Fill::Translucent(Color::White, 0.0)); 98 + let fill = Fill::Translucent(Color::White, 0.0); 67 99 let circle_at = |x: usize, y: usize| Object::SmallCircle(Anchor(x as i32, y as i32)); 68 100 69 101 let (end_x, end_y) = { 70 102 let Point(x, y) = canvas.world_region.end; 71 103 (x - 2, y - 2) 72 104 }; 73 - kicks.add_object("top left", circle_at(1, 1), fill); 74 - kicks.add_object("top right", circle_at(end_x, 1), fill); 75 - kicks.add_object("bottom left", circle_at(1, end_y), fill); 76 - kicks.add_object("bottom right", circle_at(end_x, end_y), fill); 105 + kicks.add_object("top left", circle_at(1, 1).color(fill)); 106 + kicks.add_object("top right", circle_at(end_x, 1).color(fill)); 107 + kicks.add_object("bottom left", circle_at(1, end_y).color(fill)); 108 + kicks.add_object("bottom right", circle_at(end_x, end_y).color(fill)); 77 109 canvas.add_or_replace_layer(kicks); 78 110 79 111 let mut ch = Layer::new("ch"); 80 - ch.add_object("0", Object::Dot(Anchor(0, 0)), None); 112 + ch.add_object("0", Object::Dot(Anchor(0, 0)).into()); 81 113 canvas.add_or_replace_layer(ch); 82 114 }) 83 115 .sync_audio_with(&args.flag_sync_with.unwrap()) ··· 160 192 let object_name = format!("{}", ctx.ms); 161 193 layer.add_object( 162 194 &object_name, 163 - Object::Dot(world.resized(-1, -1).random_coordinates_within().into()), 164 - Some(Fill::Solid(Color::Cyan)), 195 + Object::Dot(world.resized(-1, -1).random_coordinates_within().into()) 196 + .color(Fill::Solid(Color::Cyan)), 165 197 ); 166 198 167 199 canvas.put_layer_on_top("ch"); ··· 170 202 .when_remaining(10, &|canvas, _| { 171 203 canvas.root().add_object( 172 204 "credits text", 173 - Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))), 174 - None, 205 + Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))).into(), 175 206 ); 176 207 }) 177 208 .command("remove", &|argumentsline, canvas, _| {
+31
src/objects.rs
··· 24 24 RawSVG(Box<dyn svg::Node>), 25 25 } 26 26 27 + impl Object { 28 + pub fn color(self, fill: Fill) -> ColoredObject { 29 + ColoredObject::from((self, Some(fill))) 30 + } 31 + 32 + pub fn filter(self, filter: Filter) -> ColoredObject { 33 + ColoredObject::from((self, None)).filter(filter) 34 + } 35 + } 36 + 27 37 #[derive(Debug, Clone)] 28 38 pub struct ColoredObject(pub Object, pub Option<Fill>, pub Vec<Filter>); 39 + 40 + impl ColoredObject { 41 + pub fn filter(mut self, filter: Filter) -> Self { 42 + self.2.push(filter); 43 + self 44 + } 45 + 46 + pub fn clear_filters(&mut self) { 47 + self.2.clear(); 48 + } 49 + } 50 + 51 + impl From<Object> for ColoredObject { 52 + fn from(value: Object) -> Self { 53 + ColoredObject(value, None, vec![]) 54 + } 55 + } 29 56 30 57 impl From<(Object, Option<Fill>)> for ColoredObject { 31 58 fn from(value: (Object, Option<Fill>)) -> Self { ··· 146 173 self, 147 174 Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..) 148 175 ) 176 + } 177 + 178 + pub fn hatchable(&self) -> bool { 179 + self.fillable() && !matches!(self, Object::Dot(..)) 149 180 } 150 181 151 182 pub fn render(
+41
src/region.rs
··· 32 32 pub end: Point, 33 33 } 34 34 35 + impl Region { 36 + pub fn iter(&self) -> RegionIterator { 37 + self.into() 38 + } 39 + 40 + pub fn random_point_within(&self) -> Point { 41 + Point::from(self.random_coordinates_within()) 42 + } 43 + } 44 + 45 + pub struct RegionIterator { 46 + region: Region, 47 + current: Point, 48 + } 49 + 50 + impl Iterator for RegionIterator { 51 + type Item = Point; 52 + 53 + fn next(&mut self) -> Option<Self::Item> { 54 + if self.current.0 >= self.region.end.0 { 55 + self.current.0 = self.region.start.0; 56 + self.current.1 += 1; 57 + } 58 + if self.current.1 >= self.region.end.1 { 59 + return None; 60 + } 61 + let result = self.current; 62 + self.current.0 += 1; 63 + Some(result) 64 + } 65 + } 66 + 67 + impl From<&Region> for RegionIterator { 68 + fn from(region: &Region) -> Self { 69 + Self { 70 + region: region.clone(), 71 + current: region.start.clone(), 72 + } 73 + } 74 + } 75 + 35 76 impl From<((usize, usize), (usize, usize))> for Region { 36 77 fn from(value: ((usize, usize), (usize, usize))) -> Self { 37 78 Region {
+49 -32
src/web.rs
··· 2 2 use std::sync::Mutex; 3 3 4 4 use once_cell::sync::Lazy; 5 + use rand::Rng; 5 6 use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen}; 6 7 use wasm_bindgen::{JsValue, UnwrapThrowExt}; 7 8 8 9 use crate::{ 9 10 layer, Anchor, Canvas, CenterAnchor, Color, ColorMapping, Fill, Filter, FilterType, 10 - HatchDirection, Layer, Object, 11 + HatchDirection, Layer, Object, Point, 11 12 }; 12 13 13 14 static WEB_CANVAS: Lazy<Mutex<Canvas>> = Lazy::new(|| Mutex::new(Canvas::default_settings())); ··· 57 58 gray: "#81a0a8".into(), 58 59 cyan: "#4fecec".into(), 59 60 }; 61 + canvas.set_grid_size(16, 9); 60 62 61 - canvas.set_grid_size(4, 4); 63 + let mut layer = Layer::new("root"); 62 64 63 - let mut layer = canvas.random_layer(&color.name()); 64 - layer.paint_all_objects(Fill::Hatched( 65 - color.into(), 66 - HatchDirection::BottomUpDiagonal, 67 - opacity, 68 - opacity, 69 - )); 70 - // layer.filter_all_objects(Filter::glow(3.0)); 71 - canvas.add_or_replace_layer(layer); 65 + let draw_in = canvas.world_region.resized(-1, -1).enlarged(-2, -2); 66 + let red_circle_at = draw_in.random_point_within(); 67 + 68 + console_log!("Red circle at {:?}", red_circle_at); 72 69 73 - let window = web_sys::window().expect("no global `window` exists"); 74 - let document = window.document().expect("should have a document on window"); 75 - let body = document.body().expect("document should have a body"); 70 + for (i, Point(x, y)) in draw_in.iter().enumerate() { 71 + console_log!("Adding object at ({}, {})", x, y); 72 + layer.add_object( 73 + &format!("{}-{}", x, y), 74 + if rand::thread_rng().gen_bool(0.5) && red_circle_at != Point(x, y) { 75 + Object::BigCircle((x, y).into()) 76 + } else { 77 + Object::Rectangle((x, y).into(), Anchor::from((x, y)).translated(1, 1)) 78 + } 79 + .color(if red_circle_at == Point(x, y) { 80 + Fill::Solid(Color::Red) 81 + } else { 82 + Fill::Hatched( 83 + Color::White, 84 + HatchDirection::BottomUpDiagonal, 85 + (i + 1) as f32 / 10.0, 86 + 0.25, 87 + ) 88 + }), 89 + // .filter(Filter::glow(7.0)), 90 + ); 91 + } 92 + console_log!("Registering layer"); 93 + canvas.layers.push(layer); 94 + canvas.set_background(Color::Black); 95 + *WEB_CANVAS.lock().unwrap() = canvas; 96 + render_canvas_at(String::from("body")); 76 97 77 - let output = document.create_element("div")?; 78 - output.set_class_name("frame"); 79 - output.set_attribute("data-color", &color.name())?; 80 - output.set_inner_html(&canvas.render(&vec!["*"], false)); 81 - body.append_child(&output)?; 82 98 Ok(()) 83 99 } 84 100 ··· 226 242 pub name: String, 227 243 } 228 244 245 + // #[wasm_bindgen()] 246 + 229 247 #[wasm_bindgen] 230 248 impl LayerWeb { 231 249 pub fn render(&self) -> String { ··· 265 283 ) -> () { 266 284 canvas().layer(name).add_object( 267 285 name, 268 - Object::Line(start, end, thickness), 269 - Some(Fill::Solid(color)), 286 + ( 287 + Object::Line(start, end, thickness), 288 + Some(Fill::Solid(color)), 289 + ) 290 + .into(), 270 291 ) 271 292 } 272 293 pub fn new_curve_outward( ··· 279 300 ) -> () { 280 301 canvas().layer(name).add_object( 281 302 name, 282 - Object::CurveOutward(start, end, thickness), 283 - Some(Fill::Solid(color)), 303 + Object::CurveOutward(start, end, thickness).color(Fill::Solid(color)), 284 304 ) 285 305 } 286 306 pub fn new_curve_inward( ··· 293 313 ) -> () { 294 314 canvas().layer(name).add_object( 295 315 name, 296 - Object::CurveInward(start, end, thickness), 297 - Some(Fill::Solid(color)), 316 + Object::CurveInward(start, end, thickness).color(Fill::Solid(color)), 298 317 ) 299 318 } 300 319 pub fn new_small_circle(&self, name: &str, center: Anchor, color: Color) -> () { 301 320 canvas() 302 321 .layer(name) 303 - .add_object(name, Object::SmallCircle(center), Some(Fill::Solid(color))) 322 + .add_object(name, Object::SmallCircle(center).color(Fill::Solid(color))) 304 323 } 305 324 pub fn new_dot(&self, name: &str, center: Anchor, color: Color) -> () { 306 325 canvas() 307 326 .layer(name) 308 - .add_object(name, Object::Dot(center), Some(Fill::Solid(color))) 327 + .add_object(name, Object::Dot(center).color(Fill::Solid(color))) 309 328 } 310 329 pub fn new_big_circle(&self, name: &str, center: CenterAnchor, color: Color) -> () { 311 330 canvas() 312 331 .layer(name) 313 - .add_object(name, Object::BigCircle(center), Some(Fill::Solid(color))) 332 + .add_object(name, Object::BigCircle(center).color(Fill::Solid(color))) 314 333 } 315 334 pub fn new_text( 316 335 &self, ··· 322 341 ) -> () { 323 342 canvas().layer(name).add_object( 324 343 name, 325 - Object::Text(anchor, text, font_size), 326 - Some(Fill::Solid(color)), 344 + Object::Text(anchor, text, font_size).color(Fill::Solid(color)), 327 345 ) 328 346 } 329 347 pub fn new_rectangle( ··· 335 353 ) -> () { 336 354 canvas().layer(name).add_object( 337 355 name, 338 - Object::Rectangle(topleft, bottomright), 339 - Some(Fill::Solid(color)), 356 + Object::Rectangle(topleft, bottomright).color(Fill::Solid(color)), 340 357 ) 341 358 } 342 359 }