This repository has no description
0

Configure Feed

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

✨ Make grid size changeable, add rendering CLI flags

+427 -277
+3 -1
.gitignore
··· 2 2 shapemaker 3 3 4 4 .vscode 5 - test.svg 5 + test.svg 6 + gallery/myriad/bar* 7 + gallery/myriad/piece*
+1 -1
README.md
··· 1 - # shapemaker 1 + ![shapemaker](title.png) 2 2 3 3 An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid. 4 4
+1 -1
README.md.in
··· 1 - # shapemaker 1 + ![shapemaker](title.png) 2 2 3 3 An experiment into the generation of 2D flat design abstract artwork using limited shape and color combinations, arranged in a 8-point grid. 4 4
+1
colorschemes/palenight.json
··· 1 + {"black": "#676E95", "white": "#ffffff", "red": "#ff5572", "green": "#a9c77d", "blue": "#82AAFF", "yellow": "#FFCB6B", "orange": "#FFCB6B", "purple": "#C792EA", "brown": "#ff5572", "pink": "#C792EA", "gray": "#ffffff", "cyan": "#89DDFF"}
+1
colorschemes/snazzy-light.json
··· 1 + {"black": "#565869", "white": "#FFFFFF", "red": "#FF5C57", "green": "#2DAE58", "blue": "#09A1ED", "yellow": "#F5B900", "orange": "#CF9C00", "purple": "#F767BB", "brown": "#FFAEAC", "pink": "#FF94D2", "gray": "#FAFBF9", "cyan": "#13BBB7"}
+44
gallery/myriad/generate
··· 1 + #!/usr/bin/env fish 2 + 3 + set wipe " " 4 + 5 + for i in (seq 1 16) 6 + for j in (seq 1 9) 7 + echo -ne Making piece $i:$j\r 8 + shapemaker piece-$i-$j.svg --colors $argv[1] \ 9 + --grid-size 2x2 \ 10 + # --render-grid \ 11 + # --canvas-padding 0 \ 12 + # --objects-count 0..1 \ 13 + # --line-width 16 \ 14 + # --empty-shape-stroke 4 \ 15 + # --dot-radius 5 \ 16 + # --small-circle-radius 10 \ 17 + 2>/dev/null & 18 + end 19 + end 20 + 21 + wait 22 + echo Made pieces$wipe 23 + 24 + for f in piece-*.svg 25 + set dest (echo $f | sd '.svg$' '.png') 26 + echo -ne Converting $f to PNG...\r 27 + rm $dest || true 28 + convert -density 200 $f $dest & 29 + end 30 + 31 + wait 32 + echo Converted all pieces to PNG$wipe 33 + 34 + for j in (seq 1 9) 35 + echo -ne Merging pieces into bar $j...\r 36 + convert +append piece-*-$j.png bar-$j.png & 37 + end 38 + 39 + wait 40 + echo Merged pieces into bars$wipe 41 + 42 + echo Merging bars into myriad... 43 + convert -append bar-*.png myriad.png 44 + echo Done.
gallery/myriad/myriad.png

This is a binary file and will not be displayed.

+376 -274
src/main.rs
··· 1 + use rand::distributions::uniform::SampleRange; 1 2 use serde_json; 2 3 use std::fs::File; 3 4 use std::io::BufReader; 5 + use std::ops::Range; 6 + use std::sync::Arc; 4 7 use std::{borrow::Borrow, collections::HashMap}; 5 8 6 9 use docopt::Docopt; ··· 19 22 shapemaker --version 20 23 21 24 Options: 22 - --colors <file> JSON file mapping color names to hex values 23 - The supported color names are: black, white, red, green, blue, yellow, orange, purple, brown, pink, gray, and cyan. 24 - -c --color <mapping> Color mapping in the form of <color>:<hex>. Can be used multiple times. 25 + --colors <file> JSON file mapping color names to hex values 26 + The supported color names are: black, white, red, green, blue, yellow, orange, purple, brown, pink, gray, and cyan. 27 + -c --color <mapping> Color mapping in the form of <color>:<hex>. Can be used multiple times. 28 + --grid-size <WIDTHxHEIGHT> Size of the grid (number of anchor points) [default: 3x3] 29 + Putting one of the dimensions to 1 can cause a crash. 30 + --cell-size <size> Size of a cell in pixels [default: 50] 31 + --canvas-padding <size> Outter canvas padding between cells in pixels [default: 10] 32 + --line-width <size> Width of the lines in pixels [default: 2] 33 + --small-circle-radius <size> Radius of small circles in pixels [default: 5] 34 + --dot-radius <size> Radius of dots in pixels [default: 2] 35 + --empty-shape-stroke <size> Width of the stroke when a closed shape is not filled [default: 0.5] 36 + --render-grid Render the grid of anchor points 37 + --objects-count <range> Number of objects to render [default: 3..6] 38 + --polygon-vertices <range> Number of vertices for polygons [default: 2..6] 39 + 40 + Note: <range>s are inclusive on both ends 41 + 42 + 25 43 "; 26 - 27 - fn default_color_mapping() -> ColorMapping { 28 - ColorMapping { 29 - black: "black".to_string(), 30 - white: "white".to_string(), 31 - red: "red".to_string(), 32 - green: "green".to_string(), 33 - blue: "blue".to_string(), 34 - yellow: "yellow".to_string(), 35 - orange: "orange".to_string(), 36 - purple: "purple".to_string(), 37 - brown: "brown".to_string(), 38 - pink: "pink".to_string(), 39 - gray: "gray".to_string(), 40 - cyan: "cyan".to_string(), 41 - } 42 - } 43 44 44 45 #[derive(Debug, Deserialize)] 45 46 struct Args { ··· 47 48 flag_version: bool, 48 49 flag_color: Vec<String>, 49 50 flag_colors: Option<String>, 51 + flag_grid_size: Option<String>, 52 + flag_cell_size: Option<usize>, 53 + flag_canvas_padding: Option<usize>, 54 + flag_line_width: Option<f32>, 55 + flag_small_circle_radius: Option<f32>, 56 + flag_dot_radius: Option<f32>, 57 + flag_empty_shape_stroke: Option<f32>, 58 + flag_render_grid: bool, 59 + flag_objects_count: Option<String>, 60 + flag_polygon_vertices: Option<String>, 50 61 } 51 62 52 63 fn main() { ··· 59 70 std::process::exit(0); 60 71 } 61 72 62 - let shape = random_shape("test"); 63 - let colormap = if let Some(file) = args.flag_colors { 73 + let mut canvas = Canvas::default_settings(); 74 + 75 + canvas.colormap = if let Some(file) = args.flag_colors { 64 76 let file = File::open(file).unwrap(); 65 77 let reader = BufReader::new(file); 66 78 serde_json::from_reader(reader).unwrap() ··· 75 87 ColorMapping { 76 88 black: colormap 77 89 .get("black") 78 - .unwrap_or(&default_color_mapping().black) 90 + .unwrap_or(&ColorMapping::default().black) 79 91 .to_string(), 80 92 white: colormap 81 93 .get("white") 82 - .unwrap_or(&default_color_mapping().white) 94 + .unwrap_or(&ColorMapping::default().white) 83 95 .to_string(), 84 96 red: colormap 85 97 .get("red") 86 - .unwrap_or(&default_color_mapping().red) 98 + .unwrap_or(&ColorMapping::default().red) 87 99 .to_string(), 88 100 green: colormap 89 101 .get("green") 90 - .unwrap_or(&default_color_mapping().green) 102 + .unwrap_or(&ColorMapping::default().green) 91 103 .to_string(), 92 104 blue: colormap 93 105 .get("blue") 94 - .unwrap_or(&default_color_mapping().blue) 106 + .unwrap_or(&ColorMapping::default().blue) 95 107 .to_string(), 96 108 yellow: colormap 97 109 .get("yellow") 98 - .unwrap_or(&default_color_mapping().yellow) 110 + .unwrap_or(&ColorMapping::default().yellow) 99 111 .to_string(), 100 112 orange: colormap 101 113 .get("orange") 102 - .unwrap_or(&default_color_mapping().orange) 114 + .unwrap_or(&ColorMapping::default().orange) 103 115 .to_string(), 104 116 purple: colormap 105 117 .get("purple") 106 - .unwrap_or(&default_color_mapping().purple) 118 + .unwrap_or(&ColorMapping::default().purple) 107 119 .to_string(), 108 120 brown: colormap 109 121 .get("brown") 110 - .unwrap_or(&default_color_mapping().brown) 122 + .unwrap_or(&ColorMapping::default().brown) 111 123 .to_string(), 112 124 pink: colormap 113 125 .get("pink") 114 - .unwrap_or(&default_color_mapping().pink) 126 + .unwrap_or(&ColorMapping::default().pink) 115 127 .to_string(), 116 128 gray: colormap 117 129 .get("gray") 118 - .unwrap_or(&default_color_mapping().gray) 130 + .unwrap_or(&ColorMapping::default().gray) 119 131 .to_string(), 120 132 cyan: colormap 121 133 .get("cyan") 122 - .unwrap_or(&default_color_mapping().cyan) 134 + .unwrap_or(&ColorMapping::default().cyan) 123 135 .to_string(), 124 136 } 125 137 }; 126 138 127 - if let Err(e) = std::fs::write(args.arg_file, shape.render(colormap)) { 139 + if let Some(dimensions) = args.flag_grid_size { 140 + let mut split = dimensions.split('x'); 141 + let width = split.next().unwrap().parse::<usize>().unwrap(); 142 + let height = split.next().unwrap().parse::<usize>().unwrap(); 143 + canvas.grid_size = (width, height); 144 + } 145 + 146 + if let Some(cell_size) = args.flag_cell_size { 147 + canvas.cell_size = cell_size; 148 + } 149 + 150 + if let Some(canvas_padding) = args.flag_canvas_padding { 151 + canvas.canvas_outter_padding = canvas_padding; 152 + } 153 + 154 + if let Some(line_width) = args.flag_line_width { 155 + canvas.line_width = line_width; 156 + } 157 + 158 + if let Some(small_circle_radius) = args.flag_small_circle_radius { 159 + canvas.small_circle_radius = small_circle_radius; 160 + } 161 + 162 + if let Some(dot_radius) = args.flag_dot_radius { 163 + canvas.dot_radius = dot_radius; 164 + } 165 + 166 + if let Some(empty_shape_stroke) = args.flag_empty_shape_stroke { 167 + canvas.empty_shape_stroke_width = empty_shape_stroke; 168 + } 169 + 170 + canvas.render_grid = args.flag_render_grid; 171 + 172 + if let Some(objects_count) = args.flag_objects_count { 173 + let mut split = objects_count.split(".."); 174 + let min = split.next().unwrap().parse::<usize>().unwrap(); 175 + let max = split.next().unwrap().parse::<usize>().unwrap(); 176 + // +1 because the range is exclusive, using ..= raises a type error 177 + canvas.objects_count_range = min..(max + 1); 178 + } 179 + 180 + if let Some(polygon_vertices) = args.flag_polygon_vertices { 181 + let mut split = polygon_vertices.split(".."); 182 + let min = split.next().unwrap().parse::<usize>().unwrap(); 183 + let max = split.next().unwrap().parse::<usize>().unwrap(); 184 + canvas.polygon_vertices_range = min..(max + 1); 185 + } 186 + 187 + let shape = canvas.random_shape("test"); 188 + 189 + if let Err(e) = std::fs::write(args.arg_file, shape.render(&canvas)) { 128 190 eprintln!("Error: {:?}", e); 129 191 std::process::exit(1); 130 192 } 131 193 } 132 194 133 - fn random_shape(name: &'static str) -> Shape { 134 - let mut objects: Vec<(Object, Option<Fill>)> = vec![]; 135 - let number_of_objects = rand::thread_rng().gen_range(3..7); 136 - for _ in 0..number_of_objects { 137 - let object = random_object(); 138 - objects.push(( 139 - object, 140 - if rand::thread_rng().gen_bool(0.5) { 141 - Some(random_fill()) 142 - } else { 143 - None 144 - }, 145 - )); 146 - } 147 - Shape { name, objects } 195 + #[derive(Debug, Clone)] 196 + struct Canvas { 197 + grid_size: (usize, usize), 198 + cell_size: usize, 199 + objects_count_range: Range<usize>, 200 + polygon_vertices_range: Range<usize>, 201 + canvas_outter_padding: usize, 202 + line_width: f32, 203 + empty_shape_stroke_width: f32, 204 + small_circle_radius: f32, 205 + dot_radius: f32, 206 + render_grid: bool, 207 + colormap: ColorMapping, 148 208 } 149 209 150 - fn random_object() -> Object { 151 - let start = random_anchor(); 152 - match rand::thread_rng().gen_range(1..=7) { 153 - 1 => random_polygon(), 154 - 2 => Object::BigCircle(random_center_anchor()), 155 - 3 => Object::SmallCircle(start), 156 - 4 => Object::Dot(start), 157 - 5 => Object::CurveInward(start, random_end_anchor(start)), 158 - 6 => Object::CurveOutward(start, random_end_anchor(start)), 159 - 7 => Object::Line(random_anchor(), random_anchor()), 160 - _ => unreachable!(), 210 + impl Canvas { 211 + fn default_settings() -> Self { 212 + Self { 213 + grid_size: (3, 3), 214 + cell_size: 50, 215 + objects_count_range: 3..7, 216 + polygon_vertices_range: 2..7, 217 + canvas_outter_padding: 10, 218 + line_width: 2.0, 219 + empty_shape_stroke_width: 0.5, 220 + small_circle_radius: 5.0, 221 + dot_radius: 2.0, 222 + render_grid: false, 223 + colormap: ColorMapping::default(), 224 + } 225 + } 226 + fn random_shape(&self, name: &'static str) -> Shape { 227 + let mut objects: Vec<(Object, Option<Fill>)> = vec![]; 228 + let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 229 + for _ in 0..number_of_objects { 230 + let object = self.random_object(); 231 + objects.push(( 232 + object, 233 + if rand::thread_rng().gen_bool(0.5) { 234 + Some(self.random_fill()) 235 + } else { 236 + None 237 + }, 238 + )); 239 + } 240 + Shape { name, objects } 161 241 } 162 - } 163 242 164 - fn random_end_anchor(start: Anchor) -> Anchor { 165 - match start { 166 - Anchor::TopLeft => match rand::thread_rng().gen_range(1..=2) { 167 - 1 => Anchor::Center, 168 - 2 => Anchor::BottomRight, 169 - _ => unreachable!(), 170 - }, 171 - Anchor::Top => match rand::thread_rng().gen_range(1..=2) { 172 - 1 => Anchor::Left, 173 - 2 => Anchor::Right, 174 - _ => unreachable!(), 175 - }, 176 - Anchor::TopRight => match rand::thread_rng().gen_range(1..=2) { 177 - 1 => Anchor::Center, 178 - 2 => Anchor::BottomLeft, 179 - _ => unreachable!(), 180 - }, 181 - Anchor::Left => match rand::thread_rng().gen_range(1..=2) { 182 - 1 => Anchor::Top, 183 - 2 => Anchor::Bottom, 184 - _ => unreachable!(), 185 - }, 186 - Anchor::Center => match rand::thread_rng().gen_range(1..=4) { 187 - 1 => Anchor::TopLeft, 188 - 2 => Anchor::TopRight, 189 - 3 => Anchor::BottomLeft, 190 - 4 => Anchor::BottomRight, 191 - _ => unreachable!(), 192 - }, 193 - Anchor::Right => match rand::thread_rng().gen_range(1..=2) { 194 - 1 => Anchor::Top, 195 - 2 => Anchor::Bottom, 196 - _ => unreachable!(), 197 - }, 198 - Anchor::BottomLeft => match rand::thread_rng().gen_range(1..=2) { 199 - 1 => Anchor::Center, 200 - 2 => Anchor::TopRight, 201 - _ => unreachable!(), 202 - }, 203 - Anchor::Bottom => match rand::thread_rng().gen_range(1..=2) { 204 - 1 => Anchor::Left, 205 - 2 => Anchor::Right, 243 + fn random_object(&self) -> Object { 244 + let start = self.random_anchor(); 245 + match rand::thread_rng().gen_range(1..=7) { 246 + 1 => self.random_polygon(), 247 + 2 => Object::BigCircle(self.random_center_anchor()), 248 + 3 => Object::SmallCircle(start), 249 + 4 => Object::Dot(start), 250 + 5 => Object::CurveInward(start, self.random_end_anchor(start)), 251 + 6 => Object::CurveOutward(start, self.random_end_anchor(start)), 252 + 7 => Object::Line(self.random_anchor(), self.random_anchor()), 206 253 _ => unreachable!(), 207 - }, 208 - Anchor::BottomRight => match rand::thread_rng().gen_range(1..=2) { 209 - 1 => Anchor::Center, 210 - 2 => Anchor::TopLeft, 211 - _ => unreachable!(), 212 - }, 254 + } 213 255 } 214 - } 256 + 257 + fn random_end_anchor(&self, start: Anchor) -> Anchor { 258 + // End anchors are always a square diagonal from the start anchor (for now) 259 + // that means taking steps of the form n * (one of (1, 1), (1, -1), (-1, 1), (-1, -1)) 260 + // Except that the end anchor needs to stay in the bounds of the shape. 261 + 262 + // Determine all possible end anchors that are in a square diagonal from the start anchor 263 + let mut possible_end_anchors = vec![]; 264 + let grid_width = self.grid_size.0 as i32; 265 + let grid_height = self.grid_size.1 as i32; 266 + 267 + for x in -grid_width..=grid_width { 268 + for y in -grid_height..=grid_height { 269 + let end_anchor = Anchor(start.0 + x, start.1 + y); 270 + 271 + if end_anchor == start { 272 + continue; 273 + } 274 + 275 + // Check that the end anchor is in a square diagonal from the start anchor and that the end anchor is in bounds 276 + if x.abs() == y.abs() 277 + && end_anchor.0.abs() < grid_width 278 + && end_anchor.1.abs() < grid_height 279 + && end_anchor.0 >= 0 280 + && end_anchor.1 >= 0 281 + { 282 + possible_end_anchors.push(end_anchor); 283 + } 284 + } 285 + } 215 286 216 - fn random_polygon() -> Object { 217 - let number_of_anchors = rand::thread_rng().gen_range(2..7); 218 - let start = random_anchor(); 219 - let mut lines: Vec<Line> = vec![]; 220 - for _ in 0..number_of_anchors { 221 - let next_anchor = random_anchor(); 222 - lines.push(random_line(next_anchor)); 287 + // Pick a random end anchor from the possible end anchors 288 + possible_end_anchors[rand::thread_rng().gen_range(0..possible_end_anchors.len())] 223 289 } 224 - Object::Polygon(start, lines) 225 - } 226 290 227 - fn random_line(end: Anchor) -> Line { 228 - match rand::thread_rng().gen_range(1..=3) { 229 - 1 => Line::Line(end), 230 - 2 => Line::InwardCurve(end), 231 - 3 => Line::OutwardCurve(end), 232 - _ => unreachable!(), 291 + fn random_polygon(&self) -> Object { 292 + let number_of_anchors = rand::thread_rng().gen_range(self.polygon_vertices_range.clone()); 293 + let start = self.random_anchor(); 294 + let mut lines: Vec<Line> = vec![]; 295 + for _ in 0..number_of_anchors { 296 + let next_anchor = self.random_anchor(); 297 + lines.push(self.random_line(next_anchor)); 298 + } 299 + Object::Polygon(start, lines) 233 300 } 234 - } 235 301 236 - fn random_anchor() -> Anchor { 237 - match rand::thread_rng().gen_range(1..=9) { 238 - 1 => Anchor::TopLeft, 239 - 2 => Anchor::Top, 240 - 3 => Anchor::TopRight, 241 - 4 => Anchor::Left, 242 - 5 => Anchor::Center, 243 - 6 => Anchor::Right, 244 - 7 => Anchor::BottomLeft, 245 - 8 => Anchor::Bottom, 246 - 9 => Anchor::BottomRight, 247 - _ => unreachable!(), 302 + fn random_line(&self, end: Anchor) -> Line { 303 + match rand::thread_rng().gen_range(1..=3) { 304 + 1 => Line::Line(end), 305 + 2 => Line::InwardCurve(end), 306 + 3 => Line::OutwardCurve(end), 307 + _ => unreachable!(), 308 + } 248 309 } 249 - } 250 310 251 - fn random_center_anchor() -> CenterAnchor { 252 - match rand::thread_rng().gen_range(1..=5) { 253 - 1 => CenterAnchor::TopLeft, 254 - 2 => CenterAnchor::TopRight, 255 - 3 => CenterAnchor::Center, 256 - 4 => CenterAnchor::BottomLeft, 257 - 5 => CenterAnchor::BottomRight, 258 - _ => unreachable!(), 311 + fn random_anchor(&self) -> Anchor { 312 + if rand::thread_rng().gen_bool(1.0 / (self.grid_size.0 * self.grid_size.1) as f64) { 313 + // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 4x4) 314 + Anchor(-1, -1) 315 + } else { 316 + Anchor( 317 + rand::thread_rng().gen_range(0..=self.grid_size.0 - 1) as i32, 318 + rand::thread_rng().gen_range(0..=self.grid_size.1 - 1) as i32, 319 + ) 320 + } 259 321 } 260 - } 261 322 262 - fn random_fill() -> Fill { 263 - Fill::Solid(random_color()) 264 - // match rand::thread_rng().gen_range(1..=3) { 265 - // 1 => Fill::Solid(random_color()), 266 - // 2 => Fill::Hatched, 267 - // 3 => Fill::Dotted, 268 - // _ => unreachable!(), 269 - // } 270 - } 323 + fn random_center_anchor(&self) -> CenterAnchor { 324 + if rand::thread_rng() 325 + .gen_bool(1.0 / ((self.grid_size.0 as i32 - 1) * (self.grid_size.1 as i32 - 1)) as f64) 326 + { 327 + // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 3x3) 328 + CenterAnchor(-1, -1) 329 + } else { 330 + CenterAnchor( 331 + rand::thread_rng().gen_range(0..=self.grid_size.0 - 2) as i32, 332 + rand::thread_rng().gen_range(0..=self.grid_size.1 - 2) as i32, 333 + ) 334 + } 335 + } 336 + 337 + fn random_fill(&self) -> Fill { 338 + Fill::Solid(self.random_color()) 339 + // match rand::thread_rng().gen_range(1..=3) { 340 + // 1 => Fill::Solid(random_color()), 341 + // 2 => Fill::Hatched, 342 + // 3 => Fill::Dotted, 343 + // _ => unreachable!(), 344 + // } 345 + } 271 346 272 - fn random_color() -> Color { 273 - match rand::thread_rng().gen_range(1..=12) { 274 - 1 => Color::Black, 275 - 2 => Color::White, 276 - 3 => Color::Red, 277 - 4 => Color::Green, 278 - 5 => Color::Blue, 279 - 6 => Color::Yellow, 280 - 7 => Color::Orange, 281 - 8 => Color::Purple, 282 - 9 => Color::Brown, 283 - 10 => Color::Pink, 284 - 11 => Color::Gray, 285 - 12 => Color::Cyan, 286 - _ => unreachable!(), 347 + fn random_color(&self) -> Color { 348 + match rand::thread_rng().gen_range(1..=12) { 349 + 1 => Color::Black, 350 + 2 => Color::White, 351 + 3 => Color::Red, 352 + 4 => Color::Green, 353 + 5 => Color::Blue, 354 + 6 => Color::Yellow, 355 + 7 => Color::Orange, 356 + 8 => Color::Purple, 357 + 9 => Color::Brown, 358 + 10 => Color::Pink, 359 + 11 => Color::Gray, 360 + 12 => Color::Cyan, 361 + _ => unreachable!(), 362 + } 287 363 } 288 364 } 289 365 ··· 305 381 } 306 382 307 383 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 308 - enum Anchor { 309 - Top, 310 - TopRight, 311 - Right, 312 - BottomRight, 313 - Bottom, 314 - BottomLeft, 315 - Left, 316 - TopLeft, 317 - Center, 384 + struct Anchor(i32, i32); 385 + 386 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 387 + struct CenterAnchor(i32, i32); 388 + 389 + trait Coordinates { 390 + fn coords(&self, canvas: &Canvas) -> (f32, f32); 391 + fn center() -> Self; 318 392 } 319 393 320 - impl Anchor { 321 - fn x(&self) -> f32 { 394 + impl Coordinates for Anchor { 395 + fn coords(&self, canvas: &Canvas) -> (f32, f32) { 322 396 match self { 323 - Anchor::TopLeft | Anchor::Left | Anchor::BottomLeft => 0.0, 324 - Anchor::Top | Anchor::Center | Anchor::Bottom => 50.0, 325 - Anchor::TopRight | Anchor::Right | Anchor::BottomRight => 100.0, 326 - } 327 - } 328 - fn y(&self) -> f32 { 329 - match self { 330 - Anchor::TopLeft | Anchor::Top | Anchor::TopRight => 0.0, 331 - Anchor::Left | Anchor::Center | Anchor::Right => 50.0, 332 - Anchor::BottomLeft | Anchor::Bottom | Anchor::BottomRight => 100.0, 397 + Anchor(-1, -1) => (canvas.cell_size as f32 / 2.0, canvas.cell_size as f32 / 2.0), 398 + Anchor(i, j) => { 399 + let x = (i * canvas.cell_size as i32) as f32; 400 + let y = (j * canvas.cell_size as i32) as f32; 401 + (x, y) 402 + } 333 403 } 334 404 } 335 - } 336 405 337 - #[derive(Debug, Clone)] 338 - enum CenterAnchor { 339 - TopLeft, 340 - TopRight, 341 - BottomLeft, 342 - BottomRight, 343 - Center, 406 + fn center() -> Self { 407 + Anchor(-1, -1) 408 + } 344 409 } 345 410 346 - impl CenterAnchor { 347 - fn x(&self) -> f32 { 411 + impl Coordinates for CenterAnchor { 412 + fn coords(&self, canvas: &Canvas) -> (f32, f32) { 348 413 match self { 349 - CenterAnchor::TopLeft | CenterAnchor::BottomLeft => 25.0, 350 - CenterAnchor::TopRight | CenterAnchor::BottomRight => 75.0, 351 - CenterAnchor::Center => 50.0, 414 + CenterAnchor(-1, -1) => ((canvas.cell_size / 2) as f32, (canvas.cell_size / 2) as f32), 415 + CenterAnchor(i, j) => { 416 + let x = *i as f32 * canvas.cell_size as f32 + canvas.cell_size as f32 / 2.0; 417 + let y = *j as f32 * canvas.cell_size as f32 + canvas.cell_size as f32 / 2.0; 418 + (x, y) 419 + } 352 420 } 353 421 } 354 422 355 - fn y(&self) -> f32 { 356 - match self { 357 - CenterAnchor::TopLeft | CenterAnchor::TopRight => 25.0, 358 - CenterAnchor::BottomLeft | CenterAnchor::BottomRight => 75.0, 359 - CenterAnchor::Center => 50.0, 360 - } 423 + fn center() -> Self { 424 + CenterAnchor(-1, -1) 361 425 } 362 426 } 363 427 ··· 408 472 } 409 473 410 474 impl ColorMapping { 475 + fn default() -> Self { 476 + ColorMapping { 477 + black: "black".to_string(), 478 + white: "white".to_string(), 479 + red: "red".to_string(), 480 + green: "green".to_string(), 481 + blue: "blue".to_string(), 482 + yellow: "yellow".to_string(), 483 + orange: "orange".to_string(), 484 + purple: "purple".to_string(), 485 + brown: "brown".to_string(), 486 + pink: "pink".to_string(), 487 + gray: "gray".to_string(), 488 + cyan: "cyan".to_string(), 489 + } 490 + } 411 491 fn from_json_file(path: &str) -> ColorMapping { 412 492 let file = File::open(path).unwrap(); 413 493 let reader = BufReader::new(file); ··· 449 529 } 450 530 451 531 impl Shape { 452 - fn render(self, colormap: ColorMapping) -> String { 453 - let default_color = Color::Black.to_string(&colormap); 454 - let background_color = random_color(); 532 + fn render(self, canvas: &Canvas) -> String { 533 + let canvas_width = 534 + canvas.cell_size * (canvas.grid_size.0 - 1) + 2 * canvas.canvas_outter_padding; 535 + let canvas_height = 536 + canvas.cell_size * (canvas.grid_size.1 - 1) + 2 * canvas.canvas_outter_padding; 537 + let default_color = Color::Black.to_string(&canvas.colormap); 538 + let background_color = canvas.random_color(); 455 539 eprintln!("render: background_color({:?})", background_color); 456 540 let mut svg = svg::Document::new().add( 457 541 svg::node::element::Rectangle::new() 458 - .set("x", -10) 459 - .set("y", -10) 460 - .set("width", 130) 461 - .set("height", 130) 462 - .set("fill", background_color.to_string(&colormap)), 542 + .set("x", -(canvas.canvas_outter_padding as i32)) 543 + .set("y", -(canvas.canvas_outter_padding as i32)) 544 + .set("width", canvas_width) 545 + .set("height", canvas_height) 546 + .set("fill", background_color.to_string(&canvas.colormap)), 463 547 ); 464 548 for (object, maybe_fill) in self.objects { 465 549 let mut group = svg::node::element::Group::new(); ··· 467 551 Object::Polygon(start, lines) => { 468 552 eprintln!("render: polygon({:?}, {:?})", start, lines); 469 553 let mut path = svg::node::element::path::Data::new(); 470 - path = path.move_to((start.x(), start.y())); 554 + path = path.move_to(start.coords(&canvas)); 471 555 for line in lines { 472 556 path = match line { 473 557 Line::Line(end) | Line::InwardCurve(end) | Line::OutwardCurve(end) => { 474 - path.line_to((end.x(), end.y())) 558 + path.line_to(end.coords(&canvas)) 475 559 } 476 560 }; 477 561 } ··· 483 567 match maybe_fill { 484 568 // TODO 485 569 Some(Fill::Solid(color)) => { 486 - format!("fill: {};", color.to_string(&colormap)) 570 + format!("fill: {};", color.to_string(&canvas.colormap)) 487 571 } 488 572 _ => format!( 489 - "fill: none; stroke: {}; stroke-width: 0.5px;", 490 - default_color 573 + "fill: none; stroke: {}; stroke-width: {}px;", 574 + default_color, canvas.empty_shape_stroke_width 491 575 ), 492 576 }, 493 577 ); ··· 496 580 eprintln!("render: line({:?}, {:?})", start, end); 497 581 group = group.add( 498 582 svg::node::element::Line::new() 499 - .set("x1", start.x()) 500 - .set("y1", start.y()) 501 - .set("x2", end.x()) 502 - .set("y2", end.y()) 583 + .set("x1", start.coords(&canvas).0) 584 + .set("y1", start.coords(&canvas).1) 585 + .set("x2", end.coords(&canvas).0) 586 + .set("y2", end.coords(&canvas).1) 503 587 .set( 504 588 "style", 505 589 match maybe_fill { ··· 507 591 Some(Fill::Solid(color)) => { 508 592 format!( 509 593 "fill: none; stroke: {}; stroke-width: 2px;", 510 - color.to_string(&colormap) 594 + color.to_string(&canvas.colormap) 511 595 ) 512 596 } 513 597 _ => format!( ··· 527 611 false 528 612 }; 529 613 530 - let midpoint = ((start.x() + end.x()) / 2.0, (start.y() + end.y()) / 2.0); 531 - let start_from_midpoint = (start.x() - midpoint.0, start.y() - midpoint.1); 532 - let end_from_midpoint = (end.x() - midpoint.0, end.y() - midpoint.1); 614 + let (start_x, start_y) = start.coords(&canvas); 615 + let (end_x, end_y) = end.coords(&canvas); 616 + 617 + let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 618 + let start_from_midpoint = (start_x - midpoint.0, start_y - midpoint.1); 619 + let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 533 620 eprintln!(" midpoint: {:?}", midpoint); 534 621 eprintln!( 535 622 " from midpoint: {:?} -> {:?}", 536 623 start_from_midpoint, end_from_midpoint 537 624 ); 538 625 let control = { 539 - let relative = (end.x() - start.x(), end.y() - start.y()); 626 + let relative = (end_x - start_x, end_y - start_y); 540 627 eprintln!(" relative: {:?}", relative); 541 628 // diagonal line is going like this: \ 542 629 if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 ··· 571 658 ) 572 659 } 573 660 // line is horizontal 574 - } else if start.y() == end.y() { 661 + } else if start_y == end_y { 575 662 eprintln!(" horizontal"); 576 663 ( 577 664 midpoint.0, ··· 579 666 + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0, 580 667 ) 581 668 // line is vertical 582 - } else if start.x() == end.x() { 669 + } else if start_x == end_x { 583 670 eprintln!(" vertical"); 584 671 ( 585 672 midpoint.0 ··· 596 683 .set( 597 684 "d", 598 685 svg::node::element::path::Data::new() 599 - .move_to((start.x(), start.y())) 600 - .quadratic_curve_to((control, (end.x(), end.y()))), 686 + .move_to(start.coords(&canvas)) 687 + .quadratic_curve_to((control, end.coords(&canvas))), 601 688 ) 602 689 .set( 603 690 "style", ··· 605 692 // TODO 606 693 Some(Fill::Solid(color)) => { 607 694 format!( 608 - "fill: none; stroke: {}; stroke-width: 2px;", 609 - color.to_string(&colormap) 695 + "fill: none; stroke: {}; stroke-width: {}px;", 696 + color.to_string(&canvas.colormap), 697 + canvas.line_width 610 698 ) 611 699 } 612 700 _ => format!( 613 - "fill: none; stroke: {}; stroke-width: 2px;", 614 - default_color 701 + "fill: none; stroke: {}; stroke-width: {}px;", 702 + default_color, canvas.line_width 615 703 ), 616 704 }, 617 705 ), ··· 621 709 eprintln!("render: small_circle({:?})", center); 622 710 group = group.add( 623 711 svg::node::element::Circle::new() 624 - .set("cx", center.x()) 625 - .set("cy", center.y()) 626 - .set("r", 5) 712 + .set("cx", center.coords(&canvas).0) 713 + .set("cy", center.coords(&canvas).1) 714 + .set("r", canvas.small_circle_radius) 627 715 .set( 628 716 "style", 629 717 match maybe_fill { 630 718 // TODO 631 719 Some(Fill::Solid(color)) => { 632 - format!("fill: {};", color.to_string(&colormap)) 720 + format!("fill: {};", color.to_string(&canvas.colormap)) 633 721 } 634 722 _ => format!( 635 - "fill: none; stroke: {}; stroke-width: 0.5px;", 636 - default_color 723 + "fill: none; stroke: {}; stroke-width: {}px;", 724 + default_color, canvas.empty_shape_stroke_width 637 725 ), 638 726 }, 639 727 ), ··· 643 731 eprintln!("render: dot({:?})", center); 644 732 group = group.add( 645 733 svg::node::element::Circle::new() 646 - .set("cx", center.x()) 647 - .set("cy", center.y()) 648 - .set("r", 2) 734 + .set("cx", center.coords(&canvas).0) 735 + .set("cy", center.coords(&canvas).1) 736 + .set("r", canvas.dot_radius) 649 737 .set( 650 738 "style", 651 739 match maybe_fill { 652 740 // TODO 653 741 Some(Fill::Solid(color)) => { 654 - format!("fill: {};", color.to_string(&colormap)) 742 + format!("fill: {};", color.to_string(&canvas.colormap)) 655 743 } 656 744 _ => format!( 657 - "fill: none; stroke: {}; stroke-width: 0.5px;", 658 - default_color 745 + "fill: none; stroke: {}; stroke-width: {}px;", 746 + default_color, canvas.empty_shape_stroke_width 659 747 ), 660 748 }, 661 749 ), ··· 665 753 eprintln!("render: big_circle({:?})", center); 666 754 group = group.add( 667 755 svg::node::element::Circle::new() 668 - .set("cx", center.x()) 669 - .set("cy", center.y()) 670 - .set("r", 25) 756 + .set("cx", center.coords(&canvas).0) 757 + .set("cy", center.coords(&canvas).1) 758 + .set("r", canvas.cell_size / 2) 671 759 .set( 672 760 "style", 673 761 match maybe_fill { 674 762 // TODO 675 763 Some(Fill::Solid(color)) => { 676 - format!("fill: {};", color.to_string(&colormap)) 764 + format!("fill: {};", color.to_string(&canvas.colormap)) 677 765 } 678 766 _ => format!( 679 767 "fill: none; stroke: {}; stroke-width: 0.5px;", ··· 688 776 svg = svg.add(group); 689 777 } 690 778 // render a dotted grid 691 - if false { 692 - for x in vec![0, 25, 50, 75, 100] { 693 - for y in vec![0, 25, 50, 75, 100] { 779 + if canvas.render_grid { 780 + for i in 0..canvas.grid_size.0 as i32 { 781 + for j in 0..canvas.grid_size.1 as i32 { 782 + let (x, y) = Anchor(i, j).coords(&canvas); 694 783 svg = svg.add( 695 784 svg::node::element::Circle::new() 696 785 .set("cx", x) 697 786 .set("cy", y) 698 - .set("r", 0.5) 699 - .set("fill", "gray"), 787 + .set("r", canvas.line_width / 4.0) 788 + .set("fill", "#000"), 700 789 ); 790 + 791 + // if i < canvas.grid_size.0 as i32 - 1 && j < canvas.grid_size.1 as i32 - 1 { 792 + // let (x, y) = CenterAnchor(i, j).coords(&canvas); 793 + // svg = svg.add( 794 + // svg::node::element::Circle::new() 795 + // .set("cx", x) 796 + // .set("cy", y) 797 + // .set("r", canvas.line_width / 4.0) 798 + // .set("fill", "#fff"), 799 + // ); 800 + // } 701 801 } 702 802 } 703 803 } 704 - svg.set("viewBox", "-10 -10 120 120").to_string() 705 - } 706 - } 707 - 708 - impl Object { 709 - fn closed(self) -> bool { 710 - matches!( 711 - self, 712 - Object::Polygon(_, _) | Object::BigCircle(_) | Object::SmallCircle(_) | Object::Dot(_) 804 + svg.set( 805 + "viewBox", 806 + format!( 807 + "{0} {0} {1} {2}", 808 + -(canvas.canvas_outter_padding as i32), 809 + canvas_width, 810 + canvas_height 811 + ), 713 812 ) 813 + .set("width", canvas_width) 814 + .set("height", canvas_height) 815 + .to_string() 714 816 } 715 817 }
title.png

This is a binary file and will not be displayed.