this repo has no description
3
fork

Configure Feed

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

✨ Reinstate video rendering alongside wasm binary

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
474071f8 d6dc0861

+370 -308
+5 -1
Cargo.toml
··· 4 4 edition = "2021" 5 5 6 6 [lib] 7 - crate-type = ["cdylib"] 7 + crate-type = ["cdylib", "lib"] 8 + 9 + [[bin]] 10 + name = "shapemaker" 11 + path = "src/main.rs" 8 12 9 13 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 14
+1 -1
Justfile
··· 1 1 build: 2 - cargo build 2 + cargo build --bin shapemaker 3 3 cp target/debug/shapemaker . 4 4 5 5 web:
-265
example/main.rs
··· 1 - mod cli; 2 - pub use cli::{canvas_from_cli, cli_args}; 3 - use shapemaker::{Anchor, Canvas, Color, Fill, Layer, Object, Region, Video}; 4 - 5 - fn main() { 6 - run(cli_args()); 7 - } 8 - 9 - pub fn run(args: cli::Args) { 10 - let mut canvas = canvas_from_cli(&args); 11 - 12 - if args.cmd_image && !args.cmd_video { 13 - canvas.layers.push(canvas.random_layer("root")); 14 - canvas.set_background(Color::White); 15 - let aspect_ratio = canvas.grid_size.0 as f32 / canvas.grid_size.1 as f32; 16 - match Canvas::save_as_png( 17 - &args.arg_file, 18 - aspect_ratio, 19 - args.flag_resolution.unwrap_or(1000), 20 - canvas.render(&vec!["*"], true), 21 - ) { 22 - Ok(_) => println!("Image saved to {}", args.arg_file), 23 - Err(e) => println!("Error saving image: {}", e), 24 - } 25 - return; 26 - } 27 - 28 - let mut video = Video::<State>::new(canvas); 29 - video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 30 - video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000; 31 - video.fps = args.flag_fps.unwrap_or(30); 32 - video.audiofile = args.flag_audio.unwrap().into(); 33 - video = video 34 - .init(&|canvas: _, context: _| { 35 - context.extra = State { 36 - bass_pattern_at: Region::from_origin_and_size((6, 3), (3, 3)), 37 - first_kick_happened: false, 38 - }; 39 - canvas.set_background(Color::Black); 40 - 41 - let mut kicks = Layer::new("anchor kick"); 42 - 43 - let fill = Some(Fill::Translucent(Color::White, 0.0)); 44 - let circle_at = |x: usize, y: usize| Object::SmallCircle(Anchor(x as i32, y as i32)); 45 - 46 - let (end_x, end_y) = { 47 - let (x, y) = canvas.world_region.end; 48 - (x - 2, y - 2) 49 - }; 50 - kicks.add_object("top left", circle_at(1, 1), fill); 51 - kicks.add_object("top right", circle_at(end_x, 1), fill); 52 - kicks.add_object("bottom left", circle_at(1, end_y), fill); 53 - kicks.add_object("bottom right", circle_at(end_x, end_y), fill); 54 - canvas.add_or_replace_layer(kicks); 55 - 56 - let mut ch = Layer::new("ch"); 57 - ch.add_object("0", Object::Dot(Anchor(0, 0)), None); 58 - canvas.add_or_replace_layer(ch); 59 - }) 60 - .sync_audio_with(&args.flag_sync_with.unwrap()) 61 - .on_note("anchor kick", &|canvas, ctx| { 62 - // ctx.extra.bass_pattern_at = region_cycle(&canvas.world_region, None); 63 - canvas 64 - .layer("anchor kick") 65 - .paint_all_objects(Fill::Translucent(Color::White, 1.0)); 66 - 67 - canvas.layer("anchor kick").flush(); 68 - 69 - ctx.later_ms(200, &fade_out_kick_circles) 70 - }) 71 - .on_note("bass", &|canvas, ctx| { 72 - let mut new_layer = canvas.random_layer_within("bass", &ctx.extra.bass_pattern_at); 73 - new_layer.paint_all_objects(Fill::Solid(Color::White)); 74 - canvas.add_or_replace_layer(new_layer); 75 - }) 76 - .on_note("powerful clap hit, clap, perclap", &|canvas, ctx| { 77 - let mut new_layer = 78 - canvas.random_layer_within("claps", &ctx.extra.bass_pattern_at.translated(2, 0)); 79 - new_layer.paint_all_objects(Fill::Solid(Color::Red)); 80 - canvas.add_or_replace_layer(new_layer) 81 - }) 82 - .on_note( 83 - "rimshot, glitchy percs, hitting percs, glitchy percs", 84 - &|canvas, ctx| { 85 - let mut new_layer = canvas 86 - .random_layer_within("percs", &ctx.extra.bass_pattern_at.translated(2, 0)); 87 - new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5)); 88 - canvas.add_or_replace_layer(new_layer); 89 - }, 90 - ) 91 - .on_note("qanda", &|canvas, ctx| { 92 - let mut new_layer = canvas.random_linelikes_within( 93 - "qanda", 94 - &ctx.extra.bass_pattern_at.translated(-1, -1).enlarged(1, 1), 95 - ); 96 - new_layer.paint_all_objects(Fill::Solid(Color::Orange)); 97 - new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 98 - * 4.0 99 - * ctx.stem("qanda").velocity_relative(); 100 - canvas.add_or_replace_layer(new_layer) 101 - }) 102 - .on_note("brokenup", &|canvas, ctx| { 103 - let mut new_layer = canvas 104 - .random_linelikes_within("brokenup", &ctx.extra.bass_pattern_at.translated(0, -2)); 105 - new_layer.paint_all_objects(Fill::Solid(Color::Yellow)); 106 - new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 107 - * 4.0 108 - * ctx.stem("brokenup").velocity_relative(); 109 - canvas.add_or_replace_layer(new_layer); 110 - }) 111 - .on_note("goup", &|canvas, ctx| { 112 - let mut new_layer = 113 - canvas.random_linelikes_within("goup", &ctx.extra.bass_pattern_at.translated(0, 2)); 114 - new_layer.paint_all_objects(Fill::Solid(Color::Green)); 115 - new_layer.object_sizes.default_line_width = 116 - canvas.object_sizes.default_line_width * 4.0 * ctx.stem("goup").velocity_relative(); 117 - canvas.add_or_replace_layer(new_layer); 118 - }) 119 - .on_note("ch", &|canvas, ctx| { 120 - let world = canvas.world_region.clone(); 121 - 122 - // keep only the last 2 dots 123 - let dots_to_keep = canvas 124 - .layer("ch") 125 - .objects 126 - .iter() 127 - .sorted_by_key(|(name, _)| name.parse::<usize>().unwrap()) 128 - .rev() 129 - .take(2) 130 - .map(|(name, _)| (name.clone())) 131 - .collect::<Vec<_>>(); 132 - 133 - let layer = canvas.layer("ch"); 134 - layer.object_sizes.empty_shape_stroke_width = 2.0; 135 - layer.objects.retain(|name, _| dots_to_keep.contains(name)); 136 - 137 - let object_name = format!("{}", ctx.ms); 138 - layer.add_object( 139 - &object_name, 140 - Object::Dot(world.resized(-1, -1).random_coordinates_within().into()), 141 - Some(Fill::Solid(Color::Cyan)), 142 - ); 143 - 144 - canvas.put_layer_on_top("ch"); 145 - canvas.layer("ch").flush(); 146 - }) 147 - .when_remaining(10, &|canvas, _| { 148 - canvas.root().add_object( 149 - "credits text", 150 - Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))), 151 - None, 152 - ); 153 - }) 154 - .command("remove", &|argumentsline, canvas, _| { 155 - let args = argumentsline.splitn(3, ' ').collect::<Vec<_>>(); 156 - canvas.remove_object(args[0]); 157 - }); 158 - 159 - if args.flag_preview { 160 - video.preview_on(8888); 161 - } else { 162 - video.render_to(args.arg_file, args.flag_workers.unwrap_or(8), false); 163 - } 164 - } 165 - 166 - fn fade_out_kick_circles(canvas: &mut Canvas) { 167 - canvas 168 - .layer("anchor kick") 169 - .paint_all_objects(Fill::Translucent(Color::White, 0.0)); 170 - 171 - canvas.layer("anchor kick").flush(); 172 - } 173 - 174 - fn update_stem_position( 175 - ctx: &mut shapemaker::Context<State>, 176 - canvas: &mut Canvas, 177 - layer_name: &str, 178 - offset: usize, 179 - ) { 180 - let (dx, dy) = ctx.extra.bass_pattern_at 181 - - region_cycle_with_offset( 182 - &canvas.world_region, 183 - Some(&ctx.extra.bass_pattern_at), 184 - offset, 185 - ); 186 - match canvas.layer_safe(layer_name) { 187 - Some(l) => l.move_all_objects(dx, dy), 188 - _ => (), 189 - } 190 - } 191 - 192 - #[derive(Default)] 193 - struct State { 194 - first_kick_happened: bool, 195 - bass_pattern_at: Region, 196 - } 197 - 198 - fn color_cycle(current_color: Color) -> Color { 199 - match current_color { 200 - Color::Blue => Color::Cyan, 201 - Color::Cyan => Color::Green, 202 - Color::Green => Color::Yellow, 203 - Color::Yellow => Color::Orange, 204 - Color::Orange => Color::Red, 205 - Color::Red => Color::Purple, 206 - Color::Purple => Color::Pink, 207 - Color::Pink => Color::White, 208 - Color::White => Color::Blue, 209 - _ => unreachable!(), 210 - } 211 - } 212 - 213 - fn region_cycle_with_offset(world: &Region, current: Option<&Region>, offset: usize) -> Region { 214 - if offset == 0 { 215 - return current.unwrap().clone(); 216 - } 217 - 218 - if offset == 1 { 219 - return region_cycle(world, current); 220 - } 221 - 222 - region_cycle_with_offset(world, current, offset - 1) 223 - } 224 - 225 - fn hat_region_cycle(world: &Region, current: &Region) -> (i32, i32) { 226 - let (end_x, end_y) = { 227 - let (x, y) = world.end; 228 - (x - 2, y - 2) 229 - }; 230 - 231 - match current.start { 232 - // top row 233 - (x, 1) if x < end_x => (1, 0), 234 - // right column 235 - (x, y) if x == end_x && y < end_y => (0, 1), 236 - // bottom row 237 - (x, y) if y == end_y && x > 1 => (-1, 0), 238 - // left column 239 - (1, y) if y > 1 => (0, -1), 240 - _ => unreachable!(), 241 - } 242 - } 243 - 244 - fn region_cycle(world: &Region, current: Option<&Region>) -> Region { 245 - let mut region = if let Some(current) = current { 246 - current.clone() 247 - } else { 248 - Region::from_origin_and_size((1, 1), (2, 2)) 249 - }; 250 - 251 - let size = (region.width(), region.height()); 252 - // Move along x axis if possible 253 - if region.end.0 + size.0 <= world.end.0 - 1 { 254 - region.translate(size.0 as i32, 0) 255 - } 256 - // Else go to x=0 and move along y axis 257 - else if region.end.1 + size.1 <= world.end.1 - 1 { 258 - region = Region::new(2, region.end.1, size.0 + 2, region.end.1 + size.1) 259 - } 260 - // Else go to origin 261 - else { 262 - region = Region::from_origin_and_size((1, 1), size) 263 - } 264 - region 265 - }
+15 -4
src/canvas.rs
··· 2 2 use std::{cmp, collections::HashMap, io::Write, ops::Range}; 3 3 4 4 use chrono::DateTime; 5 + use itertools::Itertools; 5 6 use rand::Rng; 6 7 7 8 use crate::{ ··· 156 157 } 157 158 158 159 pub fn add_or_replace_layer(&mut self, layer: Layer) { 159 - console_log!("ijogjrjoigeojigjoijoigerjoi"); 160 160 if let Some(existing_layer) = self.layer_safe(&layer.name) { 161 161 existing_layer.replace(layer); 162 162 } else { ··· 195 195 } 196 196 197 197 pub fn random_linelikes_within(&self, layer_name: &str, region: &Region) -> Layer { 198 - console_log!("iurgrhierihA"); 199 198 let mut objects: HashMap<String, ColoredObject> = HashMap::new(); 200 199 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 201 200 for i in 0..number_of_objects { ··· 249 248 250 249 pub fn random_linelike_within(&self, region: &Region) -> Object { 251 250 let start = self.random_anchor(region); 252 - console_log!("start: {:?}", start); 253 251 match rand::thread_rng().gen_range(1..=3) { 254 252 1 => Object::CurveInward( 255 253 start, ··· 322 320 } 323 321 324 322 pub fn random_anchor(&self, region: &Region) -> Anchor { 325 - console_log!("region: {:?}", region); 326 323 if self.region_is_whole_grid(region) 327 324 && rand::thread_rng().gen_bool(1.0 / (self.grid_size.0 * self.grid_size.1) as f64) 328 325 { ··· 409 406 self.cell_size * (self.grid_size.1 - 1) + 2 * self.canvas_outter_padding 410 407 } 411 408 409 + /// returns a list of all unique filters used throughout the canvas 410 + /// used to only generate one definition per filter 411 + /// 412 + fn unique_filters(&self) -> Vec<Filter> { 413 + self.layers 414 + .iter() 415 + .flat_map(|layer| layer.objects.iter().flat_map(|(_, o)| o.2.clone())) 416 + .unique() 417 + .collect() 418 + } 419 + 412 420 pub fn render(&mut self, layers: &Vec<&str>, render_background: bool) -> String { 413 421 let background_color = self.background.unwrap_or_default(); 414 422 let mut svg = svg::Document::new(); ··· 431 439 svg = svg.add(layer.render(self.colormap.clone(), self.cell_size, layer.object_sizes)); 432 440 } 433 441 442 + for filter in self.unique_filters() { 443 + svg = svg.add(filter.definition()) 444 + } 434 445 435 446 svg.set( 436 447 "viewBox",
+1 -1
src/cli.rs
··· 1 1 use docopt::Docopt; 2 2 use serde::Deserialize; 3 - use shapemaker::{Canvas, ColorMapping}; 3 + use crate::{Canvas, ColorMapping}; 4 4 5 5 const USAGE: &str = " 6 6 ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
+25 -2
src/filter.rs
··· 1 + use std::hash::Hash; 2 + 1 3 use wasm_bindgen::prelude::*; 2 4 3 5 use crate::RenderCSS; ··· 17 19 pub parameter: f32, 18 20 } 19 21 22 + #[wasm_bindgen] 20 23 impl Filter { 21 - pub fn name(&self) -> &str { 24 + pub fn name(&self) -> String { 22 25 match self.kind { 23 26 FilterType::Glow => "glow", 24 27 FilterType::NaturalShadow => "natural-shadow-filter", 25 28 FilterType::Saturation => "saturation", 26 29 } 30 + .to_owned() 31 + } 32 + 33 + pub fn glow(intensity: f32) -> Self { 34 + Self { 35 + kind: FilterType::Glow, 36 + parameter: intensity, 37 + } 27 38 } 28 39 29 40 pub fn id(&self) -> String { ··· 33 44 self.parameter.to_string().replace(".", "_") 34 45 ) 35 46 } 47 + } 36 48 49 + impl Filter { 37 50 pub fn definition(&self) -> svg::node::element::Filter { 38 51 match self.kind { 39 52 FilterType::Glow => { ··· 113 126 ) 114 127 } 115 128 } 129 + .set("id", self.id()) 130 + .set("filterUnit", "userSpaceOnUse") 116 131 } 117 132 } 118 133 119 134 impl RenderCSS for Filter { 120 135 fn render_fill_css(&self, _colormap: &crate::ColorMapping) -> String { 121 - format!("filter: url(#{});", self.name()) 136 + format!("filter: url(#{}); overflow: visible;", self.id()) 122 137 } 123 138 124 139 fn render_stroke_css(&self, colormap: &crate::ColorMapping) -> String { ··· 132 147 self.kind == other.kind && (self.parameter - other.parameter).abs() < f32::EPSILON 133 148 } 134 149 } 150 + 151 + impl Hash for Filter { 152 + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { 153 + self.id().hash(state) 154 + } 155 + } 156 + 157 + impl Eq for Filter {}
+4 -1
src/lib.rs
··· 1 1 mod color; 2 + pub mod cli; 2 3 mod objects; 3 4 pub use color::*; 4 5 pub use objects::*; ··· 9 10 mod region; 10 11 pub use region::*; 11 12 mod web; 12 - pub use web::{log, main}; 13 + pub use web::log; 13 14 mod audio; 14 15 pub use audio::*; 15 16 mod sync; ··· 867 868 spinner.end(&format!("Built video to {}", output_file)); 868 869 } 869 870 } 871 + 872 + fn main() {}
+264
src/main.rs
··· 1 + use itertools::Itertools; 2 + use shapemaker::{cli::{canvas_from_cli, cli_args}, *}; 3 + 4 + pub fn main() { 5 + run(cli_args()); 6 + } 7 + 8 + pub fn run(args: cli::Args) { 9 + let mut canvas = canvas_from_cli(&args); 10 + 11 + if args.cmd_image && !args.cmd_video { 12 + canvas.layers.push(canvas.random_layer("root")); 13 + canvas.set_background(Color::White); 14 + let aspect_ratio = canvas.grid_size.0 as f32 / canvas.grid_size.1 as f32; 15 + match Canvas::save_as_png( 16 + &args.arg_file, 17 + aspect_ratio, 18 + args.flag_resolution.unwrap_or(1000), 19 + canvas.render(&vec!["*"], true), 20 + ) { 21 + Ok(_) => println!("Image saved to {}", args.arg_file), 22 + Err(e) => println!("Error saving image: {}", e), 23 + } 24 + return; 25 + } 26 + 27 + let mut video = Video::<State>::new(canvas); 28 + video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 29 + video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000; 30 + video.fps = args.flag_fps.unwrap_or(30); 31 + video.audiofile = args.flag_audio.unwrap().into(); 32 + video = video 33 + .init(&|canvas: _, context: _| { 34 + context.extra = State { 35 + bass_pattern_at: Region::from_origin_and_size((6, 3), (3, 3)), 36 + first_kick_happened: false, 37 + }; 38 + canvas.set_background(Color::Black); 39 + 40 + let mut kicks = Layer::new("anchor kick"); 41 + 42 + let fill = Some(Fill::Translucent(Color::White, 0.0)); 43 + let circle_at = |x: usize, y: usize| Object::SmallCircle(Anchor(x as i32, y as i32)); 44 + 45 + let (end_x, end_y) = { 46 + let Point(x, y) = canvas.world_region.end; 47 + (x - 2, y - 2) 48 + }; 49 + kicks.add_object("top left", circle_at(1, 1), fill); 50 + kicks.add_object("top right", circle_at(end_x, 1), fill); 51 + kicks.add_object("bottom left", circle_at(1, end_y), fill); 52 + kicks.add_object("bottom right", circle_at(end_x, end_y), fill); 53 + canvas.add_or_replace_layer(kicks); 54 + 55 + let mut ch = Layer::new("ch"); 56 + ch.add_object("0", Object::Dot(Anchor(0, 0)), None); 57 + canvas.add_or_replace_layer(ch); 58 + }) 59 + .sync_audio_with(&args.flag_sync_with.unwrap()) 60 + .on_note("anchor kick", &|canvas, ctx| { 61 + // ctx.extra.bass_pattern_at = region_cycle(&canvas.world_region, None); 62 + canvas 63 + .layer("anchor kick") 64 + .paint_all_objects(Fill::Translucent(Color::White, 1.0)); 65 + 66 + canvas.layer("anchor kick").flush(); 67 + 68 + ctx.later_ms(200, &fade_out_kick_circles) 69 + }) 70 + .on_note("bass", &|canvas, ctx| { 71 + let mut new_layer = canvas.random_layer_within("bass", &ctx.extra.bass_pattern_at); 72 + new_layer.paint_all_objects(Fill::Solid(Color::White)); 73 + canvas.add_or_replace_layer(new_layer); 74 + }) 75 + .on_note("powerful clap hit, clap, perclap", &|canvas, ctx| { 76 + let mut new_layer = 77 + canvas.random_layer_within("claps", &ctx.extra.bass_pattern_at.translated(2, 0)); 78 + new_layer.paint_all_objects(Fill::Solid(Color::Red)); 79 + canvas.add_or_replace_layer(new_layer) 80 + }) 81 + .on_note( 82 + "rimshot, glitchy percs, hitting percs, glitchy percs", 83 + &|canvas, ctx| { 84 + let mut new_layer = canvas 85 + .random_layer_within("percs", &ctx.extra.bass_pattern_at.translated(2, 0)); 86 + new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5)); 87 + canvas.add_or_replace_layer(new_layer); 88 + }, 89 + ) 90 + .on_note("qanda", &|canvas, ctx| { 91 + let mut new_layer = canvas.random_linelikes_within( 92 + "qanda", 93 + &ctx.extra.bass_pattern_at.translated(-1, -1).enlarged(1, 1), 94 + ); 95 + new_layer.paint_all_objects(Fill::Solid(Color::Orange)); 96 + new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 97 + * 4.0 98 + * ctx.stem("qanda").velocity_relative(); 99 + canvas.add_or_replace_layer(new_layer) 100 + }) 101 + .on_note("brokenup", &|canvas, ctx| { 102 + let mut new_layer = canvas 103 + .random_linelikes_within("brokenup", &ctx.extra.bass_pattern_at.translated(0, -2)); 104 + new_layer.paint_all_objects(Fill::Solid(Color::Yellow)); 105 + new_layer.object_sizes.default_line_width = canvas.object_sizes.default_line_width 106 + * 4.0 107 + * ctx.stem("brokenup").velocity_relative(); 108 + canvas.add_or_replace_layer(new_layer); 109 + }) 110 + .on_note("goup", &|canvas, ctx| { 111 + let mut new_layer = 112 + canvas.random_linelikes_within("goup", &ctx.extra.bass_pattern_at.translated(0, 2)); 113 + new_layer.paint_all_objects(Fill::Solid(Color::Green)); 114 + new_layer.object_sizes.default_line_width = 115 + canvas.object_sizes.default_line_width * 4.0 * ctx.stem("goup").velocity_relative(); 116 + canvas.add_or_replace_layer(new_layer); 117 + }) 118 + .on_note("ch", &|canvas, ctx| { 119 + let world = canvas.world_region.clone(); 120 + 121 + // keep only the last 2 dots 122 + let dots_to_keep = canvas 123 + .layer("ch") 124 + .objects 125 + .iter() 126 + .sorted_by_key(|(name, _)| name.parse::<usize>().unwrap()) 127 + .rev() 128 + .take(2) 129 + .map(|(name, _)| (name.clone())) 130 + .collect::<Vec<_>>(); 131 + 132 + let layer = canvas.layer("ch"); 133 + layer.object_sizes.empty_shape_stroke_width = 2.0; 134 + layer.objects.retain(|name, _| dots_to_keep.contains(name)); 135 + 136 + let object_name = format!("{}", ctx.ms); 137 + layer.add_object( 138 + &object_name, 139 + Object::Dot(world.resized(-1, -1).random_coordinates_within().into()), 140 + Some(Fill::Solid(Color::Cyan)), 141 + ); 142 + 143 + canvas.put_layer_on_top("ch"); 144 + canvas.layer("ch").flush(); 145 + }) 146 + .when_remaining(10, &|canvas, _| { 147 + canvas.root().add_object( 148 + "credits text", 149 + Object::RawSVG(Box::new(svg::node::Text::new("by ewen-lbh"))), 150 + None, 151 + ); 152 + }) 153 + .command("remove", &|argumentsline, canvas, _| { 154 + let args = argumentsline.splitn(3, ' ').collect::<Vec<_>>(); 155 + canvas.remove_object(args[0]); 156 + }); 157 + 158 + if args.flag_preview { 159 + video.preview_on(8888); 160 + } else { 161 + video.render_to(args.arg_file, args.flag_workers.unwrap_or(8), false); 162 + } 163 + } 164 + 165 + fn fade_out_kick_circles(canvas: &mut Canvas) { 166 + canvas 167 + .layer("anchor kick") 168 + .paint_all_objects(Fill::Translucent(Color::White, 0.0)); 169 + 170 + canvas.layer("anchor kick").flush(); 171 + } 172 + 173 + fn update_stem_position( 174 + ctx: &mut shapemaker::Context<State>, 175 + canvas: &mut Canvas, 176 + layer_name: &str, 177 + offset: usize, 178 + ) { 179 + let (dx, dy) = ctx.extra.bass_pattern_at 180 + - region_cycle_with_offset( 181 + &canvas.world_region, 182 + Some(&ctx.extra.bass_pattern_at), 183 + offset, 184 + ); 185 + match canvas.layer_safe(layer_name) { 186 + Some(l) => l.move_all_objects(dx, dy), 187 + _ => (), 188 + } 189 + } 190 + 191 + #[derive(Default)] 192 + struct State { 193 + first_kick_happened: bool, 194 + bass_pattern_at: Region, 195 + } 196 + 197 + fn color_cycle(current_color: Color) -> Color { 198 + match current_color { 199 + Color::Blue => Color::Cyan, 200 + Color::Cyan => Color::Green, 201 + Color::Green => Color::Yellow, 202 + Color::Yellow => Color::Orange, 203 + Color::Orange => Color::Red, 204 + Color::Red => Color::Purple, 205 + Color::Purple => Color::Pink, 206 + Color::Pink => Color::White, 207 + Color::White => Color::Blue, 208 + _ => unreachable!(), 209 + } 210 + } 211 + 212 + fn region_cycle_with_offset(world: &Region, current: Option<&Region>, offset: usize) -> Region { 213 + if offset == 0 { 214 + return current.unwrap().clone(); 215 + } 216 + 217 + if offset == 1 { 218 + return region_cycle(world, current); 219 + } 220 + 221 + region_cycle_with_offset(world, current, offset - 1) 222 + } 223 + 224 + fn hat_region_cycle(world: &Region, current: &Region) -> (i32, i32) { 225 + let (end_x, end_y) = { 226 + let Point(x, y) = world.end; 227 + (x - 2, y - 2) 228 + }; 229 + 230 + match current.start { 231 + // top row 232 + Point(x, 1) if x < end_x => (1, 0), 233 + // right column 234 + Point(x, y) if x == end_x && y < end_y => (0, 1), 235 + // bottom row 236 + Point(x, y) if y == end_y && x > 1 => (-1, 0), 237 + // left column 238 + Point(1, y) if y > 1 => (0, -1), 239 + _ => unreachable!(), 240 + } 241 + } 242 + 243 + fn region_cycle(world: &Region, current: Option<&Region>) -> Region { 244 + let mut region = if let Some(current) = current { 245 + current.clone() 246 + } else { 247 + Region::from_origin_and_size((1, 1), (2, 2)) 248 + }; 249 + 250 + let size = (region.width(), region.height()); 251 + // Move along x axis if possible 252 + if region.end.0 + size.0 <= world.end.0 - 1 { 253 + region.translate(size.0 as i32, 0) 254 + } 255 + // Else go to x=0 and move along y axis 256 + else if region.end.1 + size.1 <= world.end.1 - 1 { 257 + region = Region::new(2, region.end.1, size.0 + 2, region.end.1 + size.1) 258 + } 259 + // Else go to origin 260 + else { 261 + region = Region::from_origin_and_size((1, 1), size) 262 + } 263 + region 264 + }
+1 -8
src/objects.rs
··· 174 174 175 175 let mut css = String::new(); 176 176 if !matches!(self, Object::RawSVG(..)) { 177 - // group = group.set("style", fill.render_css(colormap, !self.fillable())); 178 177 css = fill.render_css(colormap, !self.fillable()); 179 178 } 180 179 ··· 185 184 .join(" ") 186 185 .as_ref(); 187 186 188 - group = group.set("data-object", id).set("style", css); 189 - 190 - for f in filter { 191 - group = group.add(f.definition().set("id", f.id())) 192 - } 193 - 194 - group 187 + group.set("data-object", id).set("style", css) 195 188 } 196 189 197 190 fn render_raw_svg(&self) -> Box<dyn svg::node::Node> {
+45 -17
src/web.rs
··· 6 6 use wasm_bindgen::{JsValue, UnwrapThrowExt}; 7 7 8 8 use crate::{ 9 - layer, Anchor, Canvas, CenterAnchor, Color, ColorMapping, Fill, Filter, Layer, Object, 9 + layer, Anchor, Canvas, CenterAnchor, Color, ColorMapping, Fill, Filter, FilterType, Layer, 10 + Object, 10 11 }; 11 12 12 13 static WEB_CANVAS: Lazy<Mutex<Canvas>> = Lazy::new(|| Mutex::new(Canvas::default_settings())); ··· 16 17 } 17 18 18 19 #[wasm_bindgen(start)] 19 - pub fn main() -> Result<(), JsValue> { 20 + pub fn js_init() -> Result<(), JsValue> { 20 21 render_image(0.0, Color::Black)?; 21 22 Ok(()) 22 23 } ··· 59 60 60 61 canvas.set_grid_size(4, 4); 61 62 62 - console_log!("Amerika ya :D {}", "Hallo :D ".repeat(8)); 63 - 64 63 let mut layer = canvas.random_layer(&color.name()); 65 64 layer.paint_all_objects(Fill::Translucent(color.into(), opacity)); 65 + // layer.filter_all_objects(Filter::glow(3.0)); 66 66 canvas.add_or_replace_layer(layer); 67 67 68 68 let window = web_sys::window().expect("no global `window` exists"); ··· 78 78 } 79 79 80 80 #[wasm_bindgen] 81 + pub fn render_canvas_into(selector: String) -> () { 82 + let svgstring = canvas().render(&vec!["*"], false); 83 + append_new_div_inside(svgstring, selector) 84 + } 85 + 86 + #[wasm_bindgen] 87 + pub fn render_canvas_at(selector: String) -> () { 88 + let svgstring = canvas().render(&vec!["*"], false); 89 + replace_content_with(svgstring, selector) 90 + } 91 + 92 + #[wasm_bindgen] 81 93 pub enum MidiEvent { 82 94 Note, 83 95 ControlChange, ··· 172 184 173 185 #[wasm_bindgen] 174 186 pub fn random_linelikes(name: &str) -> LayerWeb { 175 - console_log!("Canvas is {:?}", canvas()); 176 187 let layer = canvas().random_linelikes(name); 177 188 canvas().add_or_replace_layer(layer); 178 189 LayerWeb { ··· 180 191 } 181 192 } 182 193 194 + fn document() -> web_sys::Document { 195 + let window = web_sys::window().expect_throw("no global `window` exists"); 196 + window 197 + .document() 198 + .expect_throw("should have a document on window") 199 + } 200 + 201 + fn query_selector(selector: String) -> web_sys::Element { 202 + document() 203 + .query_selector(&selector) 204 + .expect_throw(&format!("selector '{}' not found", selector)) 205 + .expect_throw("could not get the element, but is was found (shouldn't happen)") 206 + } 207 + 208 + fn append_new_div_inside(content: String, selector: String) -> () { 209 + let output = document().create_element("div").unwrap(); 210 + output.set_class_name("frame"); 211 + output.set_inner_html(&content); 212 + query_selector(selector).append_child(&output).unwrap(); 213 + } 214 + 215 + fn replace_content_with(content: String, selector: String) -> () { 216 + query_selector(selector).set_inner_html(&content); 217 + } 218 + 183 219 #[wasm_bindgen(getter_with_clone)] 184 220 pub struct LayerWeb { 185 221 pub name: String, ··· 192 228 } 193 229 194 230 pub fn render_into(&self, selector: String) -> () { 195 - let window = web_sys::window().expect_throw("no global `window` exists"); 196 - let document = window 197 - .document() 198 - .expect_throw("should have a document on window"); 199 - let element = document 200 - .query_selector(&selector) 201 - .expect_throw(&format!("selector '{}' not found", selector)) 202 - .expect_throw("could not get the element, but is was found (shouldn't happen)"); 231 + append_new_div_inside(self.render(), selector) 232 + } 203 233 204 - let output = document.create_element("div").unwrap(); 205 - output.set_class_name("frame"); 206 - output.set_inner_html(&self.render()); 207 - element.append_child(&output).unwrap(); 234 + pub fn render_at(self, selector: String) -> () { 235 + replace_content_with(self.render(), selector) 208 236 } 209 237 210 238 pub fn paint_all(&self, color: Color, opacity: Option<f32>, filter: Filter) -> () {
+9 -8
web/index.html
··· 4 4 <meta charset="UTF-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 6 <title>Shapemaker Web</title> 7 - </head> 8 - <body> 9 - <div class="prev"></div> 10 7 <script type="module"> 11 8 import init, { 12 9 render_image, ··· 19 16 Filter, 20 17 ColorMapping, 21 18 random_color, 19 + render_canvas_at, 22 20 } from "./shapemaker.js" 23 21 async function run() { 24 22 await init() ··· 53 51 function randomColor() { 54 52 const colors = [Color.Cyan, Color.Pink] 55 53 const random = Math.floor(Math.random() * colors.length) 56 - return colors[random]; 54 + return colors[random] 57 55 } 58 56 59 57 function fadeOutElement(el) { ··· 70 68 71 69 window.pedal_held = false 72 70 window.previousColor = null 71 + window.numberOfFrames = 0 73 72 74 73 window.addEventListener("keypress", (e) => { 75 74 if (e.key === " ") { 76 - const layer = random_linelikes("feur") 75 + const layer = random_linelikes(`feur${window.numberOfFrames++}`) 77 76 let color = randomColor() 78 77 window.previousColor = color 79 - layer.paint_all(color, 1, Filter.Glow) 80 - layer.render_into("body") 78 + layer.paint_all(color, 1, Filter.glow(7)) 79 + render_canvas_at("body") 81 80 } 82 81 }) 83 82 ··· 141 140 align-items: center; 142 141 } 143 142 </style> 143 + </head> 144 + <body> 145 + <div class="prev"></div> 144 146 </body> 145 - <!-- <button onclick="render_image('white')">Render Image</button> --> 146 147 </html>