From 1d8012dd7c67d4096c6577dfe6a3c49b59f9caa7 Mon Sep 17 00:00:00 2001 From: Ewen Le Bihan Date: Sat, 11 May 2024 18:07:07 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Implement=20Fill::Dotted?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 7 + Cargo.toml | 1 + Justfile | 4 +- out.svg | 970 ++++++++++++++++++++++++++++-------------------- src/canvas.rs | 34 +- src/examples.rs | 21 +- src/fill.rs | 28 +- src/main.rs | 25 +- src/objects.rs | 13 +- src/web.rs | 5 - street.fish | 13 - 11 files changed, 643 insertions(+), 478 deletions(-) delete mode 100644 street.fish diff --git a/Cargo.lock b/Cargo.lock index 3376ad6..0c1b54a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,6 +598,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rust-analyzer" version = "0.0.1" @@ -687,6 +693,7 @@ dependencies = [ "nanoid", "once_cell", "rand", + "roxmltree", "rust-analyzer", "serde", "serde_cbor", diff --git a/Cargo.toml b/Cargo.toml index 372a81d..42f078a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ nanoid = "0.4.0" console = { version = "0.15.8", features = ["windows-console-colors"] } backtrace = "0.3.71" slug = "0.1.5" +roxmltree = "0.19.0" [dev-dependencies] diff --git a/Justfile b/Justfile index f1159c0..83c2697 100644 --- a/Justfile +++ b/Justfile @@ -1,3 +1,5 @@ +export RUST_BACKTRACE := "1" + build: cargo build --bin shapemaker cp target/debug/shapemaker . @@ -18,4 +20,4 @@ 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 out="out.png" args='': - ./shapemaker image --colors colorschemes/snazzy-light.json --resolution 3000 {{out}} {{args}} + ./shapemaker image --colors colorschemes/palenight.css --resolution 3000 {{out}} {{args}} diff --git a/out.svg b/out.svg index 7e856b7..be51e09 100644 --- a/out.svg +++ b/out.svg @@ -1,575 +1,737 @@ - - - - - + + + + + +e + - - + + +m + - - + + +a + - - + + +e + - - + + +r + - - + + +p + - - + + +k + - - + + +s + - - + + +h + - - + + +a + - - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + - - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - - - - - + - - - + + + - - - + + + - - - + + + - - - + + + - + + + + + + + + + - - - + + + + + + + - - - + + + - - - + + + + + + + - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + + + + + \ No newline at end of file diff --git a/src/canvas.rs b/src/canvas.rs index 64c5ab8..7f10c4d 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -393,23 +393,21 @@ impl Canvas { pub fn random_fill(&self, hatchable: bool) -> Fill { if hatchable { - match rand::thread_rng().gen_range(1..=2) { - 1 => Fill::Solid(random_color(self.background)), - 2 => { - let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2; - Fill::Hatched( - random_color(self.background), - Angle(rand::thread_rng().gen_range(0.0..360.0)), - 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!(), + if rand::thread_rng().gen_bool(0.75) { + Fill::Solid(random_color(self.background)) + } else { + let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2; + Fill::Hatched( + random_color(self.background), + Angle(rand::thread_rng().gen_range(0.0..360.0)), + 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 + }, + ) } } else { Fill::Solid(random_color(self.background)) @@ -488,7 +486,7 @@ impl Canvas { self.layers .iter() .flat_map(|layer| layer.objects.iter().flat_map(|(_, o)| o.fill)) - .filter(|fill| matches!(fill, Fill::Hatched(..))) + .filter(|fill| matches!(fill, Fill::Hatched(..) | Fill::Dotted(..))) .unique_by(|fill| fill.pattern_id()) .collect() } diff --git a/src/examples.rs b/src/examples.rs index 32ca2b2..8823957 100644 --- a/src/examples.rs +++ b/src/examples.rs @@ -59,17 +59,24 @@ pub fn dna_analysis_machine() -> Canvas { hatches_layer.add_object( point, - if rand::thread_rng().gen_bool(0.5) { + if rand::thread_rng().gen_bool(0.5) || point == red_circle_at { Object::BigCircle(point) } else { Object::Rectangle(point, point) } - .color(Fill::Hatched( - Color::White, - Angle(rand::thread_rng().gen_range(0.0..360.0)), - (i + 5) as f32 / 10.0, - 0.25, - )), + .color( + // Fill::Dotted(Color::White, (i + 8) as f32 / 10.0, (i + 3) as f32 / 10.0), + if point == red_circle_at { + Fill::Dotted(Color::White, 3.0, 2.0) + } else { + Fill::Hatched( + Color::White, + Angle(rand::thread_rng().gen_range(0.0..360.0)), + (i + 5) as f32 / 10.0, + 0.25, + ) + }, + ), ); } diff --git a/src/fill.rs b/src/fill.rs index e55ac79..984ee0a 100644 --- a/src/fill.rs +++ b/src/fill.rs @@ -43,7 +43,7 @@ pub enum Fill { Solid(Color), Translucent(Color, f32), Hatched(Color, Angle, f32, f32), - Dotted(Color, f32), + Dotted(Color, f32, f32), } // Operations that can be applied on fills. @@ -86,8 +86,7 @@ impl RenderCSS for Fill { Fill::Translucent(color, opacity) => { format!("fill: {}; opacity: {};", color.render(colormap), opacity) } - Fill::Dotted(..) => unimplemented!(), - Fill::Hatched(..) => { + Fill::Dotted(..) | Fill::Hatched(..) => { format!("fill: url(#{});", self.pattern_id()) } } @@ -115,13 +114,16 @@ impl Fill { pub fn pattern_id(&self) -> String { if let Fill::Hatched(color, angle, thickness, spacing) = self { return format!( - "pattern-{}-{}-{}-{}", + "pattern-hatched-{}-{}-{}-{}", angle, color.name(), thickness, spacing ); } + if let Fill::Dotted(color, diameter, spacing) = self { + return format!("pattern-dotted-{}-{}-{}", color.name(), diameter, spacing); + } String::from("") } @@ -171,6 +173,24 @@ impl Fill { Some(pattern) } + Fill::Dotted(color, diameter, spacing) => { + let box_size = diameter + 2.0 * spacing; + let pattern = svg::node::element::Pattern::new() + .set("id", self.pattern_id()) + .set("patternUnits", "userSpaceOnUse") + .set("height", box_size) + .set("width", box_size) + .set("viewBox", format!("0,0,{},{}", box_size, box_size)) + .add( + svg::node::element::Circle::new() + .set("cx", box_size / 2.0) + .set("cy", box_size / 2.0) + .set("r", diameter / 2.0) + .set("fill", color.render(colormapping)), + ); + + Some(pattern) + } _ => None, } } diff --git a/src/main.rs b/src/main.rs index 9645824..183178c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,31 +15,8 @@ pub fn main() -> Result<()> { pub fn run(args: cli::Args) -> Result<()> { let mut canvas = canvas_from_cli(&args); - let qrname = env::var("QRCODE_NAME").unwrap(); - if args.cmd_image && !args.cmd_video { - canvas.set_grid_size(3, 3); - canvas.add_or_replace_layer(canvas.random_layer("root")); - canvas.new_layer("qr"); - let qrcode = Object::Image( - vec![ - canvas.world_region.topleft(), - canvas.world_region.topright(), - canvas.world_region.bottomright(), - canvas.world_region.bottomleft(), - ][rand::thread_rng().gen_range(0..4)] - .region(), - format!("./{qrname}-qr.png"), - ); - canvas.root().remove_all_objects_in(&qrcode.region()); - canvas.set_background(Color::White); - canvas.add_object("qr", "qr", qrcode, None).unwrap(); - canvas.put_layer_on_top("qr"); - canvas.root().objects.values_mut().for_each(|o| { - if !o.object.fillable() { - o.fill = Some(Fill::Solid(Color::Black)); - } - }); + canvas = examples::title(); let rendered = canvas.render(true)?; if args.arg_file.ends_with(".svg") { diff --git a/src/objects.rs b/src/objects.rs index 587df92..9448856 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -1,5 +1,3 @@ -use std::{cell, collections::HashMap}; - use crate::{ColorMapping, Fill, Filter, Point, Region, Transformation}; use itertools::Itertools; use wasm_bindgen::prelude::*; @@ -36,6 +34,10 @@ impl Object { pub fn filter(self, filter: Filter) -> ColoredObject { ColoredObject::from((self, None)).filter(filter) } + + pub fn transform(self, transformation: Transformation) -> ColoredObject { + ColoredObject::from((self, None)).transform(transformation) + } } #[derive(Debug, Clone)] @@ -52,6 +54,11 @@ impl ColoredObject { self } + pub fn transform(mut self, transformation: Transformation) -> Self { + self.transformations.push(transformation); + self + } + pub fn clear_filters(&mut self) { self.filters.clear(); } @@ -78,6 +85,8 @@ impl ColoredObject { css = self.fill.render_css(colormap, !self.object.fillable()); } + css += "transform-box: fill-box;"; + css += self .filters .iter() diff --git a/src/web.rs b/src/web.rs index 56c4cff..d0719c2 100644 --- a/src/web.rs +++ b/src/web.rs @@ -14,11 +14,6 @@ fn canvas() -> std::sync::MutexGuard<'static, Canvas> { WEB_CANVAS.lock().unwrap() } -#[wasm_bindgen(start)] -pub fn js_init() -> Result<(), JsValue> { - render_image(0.0, Color::Black)?; - Ok(()) -} // Can't bind Color.name directly, see https://github.com/rustwasm/wasm-bindgen/issues/1715 #[wasm_bindgen] diff --git a/street.fish b/street.fish deleted file mode 100644 index 6e37aec..0000000 --- a/street.fish +++ /dev/null @@ -1,13 +0,0 @@ -while true - set id (nanoid -s 10) - qrencode "https://shapemaker.ewen.works/found/$id" -o street/$id-qr.png - QRCODE_NAME=street/$id just example-image out.svg "--objects-count 5..15" - if test (read || echo "n") = "y" - cp out.svg street/$id.svg - resvg --width 2000 out.svg street/$id.png - echo resvg --width 2000 street/$id.svg street/$id.png - echo saved street/$id.svg \| street/$id.png - else - rm street/$id-qr.png - end -end