···
18
18
./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 1920 {{args}}
19
19
20
20
example-image out="out.png" args='':
21
21
-
./shapemaker image --colors colorschemes/palenight.css --resolution 3000 {{out}} {{args}}
21
21
+
./shapemaker image --colors colorschemes/snazzy-light.json --resolution 3000 {{out}} {{args}}
···
219
219
let hatchable = object.hatchable();
220
220
objects.insert(
221
221
format!("{}#{}", name, i),
222
222
-
ColoredObject::from((
223
223
-
object,
224
224
-
if rand::thread_rng().gen_bool(0.5) {
225
225
-
Some(self.random_fill(hatchable))
226
226
-
} else {
227
227
-
None
228
228
-
},
229
229
-
)),
222
222
+
object.color(self.random_fill(hatchable)),
230
223
);
231
224
}
232
225
Layer {
···
380
373
region.start == (0, 0) && region.end == self.grid_size
381
374
}
382
375
376
376
+
pub fn random_region(&self) -> Region {
377
377
+
let start = self.random_point(&self.world_region);
378
378
+
let end = self.random_end_anchor(start, &self.world_region);
379
379
+
Region::from(if start.0 > end.0 {
380
380
+
(end, start)
381
381
+
} else {
382
382
+
(start, end)
383
383
+
})
384
384
+
}
385
385
+
383
386
pub fn random_point(&self, region: &Region) -> Point {
384
387
region.ensure_nonempty().unwrap();
385
388
Point(
···
391
394
pub fn random_fill(&self, hatchable: bool) -> Fill {
392
395
if hatchable {
393
396
match rand::thread_rng().gen_range(1..=2) {
394
394
-
1 => Fill::Solid(random_color()),
397
397
+
1 => Fill::Solid(random_color(self.background)),
395
398
2 => {
396
399
let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2;
397
400
Fill::Hatched(
398
398
-
random_color(),
401
401
+
random_color(self.background),
399
402
Angle(rand::thread_rng().gen_range(0.0..360.0)),
400
403
hatch_size,
401
404
// under a certain hatch size, we can't see the hatching if the ratio is not ½
···
409
412
_ => unreachable!(),
410
413
}
411
414
} else {
412
412
-
Fill::Solid(random_color())
415
415
+
Fill::Solid(random_color(self.background))
413
416
}
414
417
}
415
418
···
27
27
}
28
28
29
29
#[wasm_bindgen]
30
30
-
pub fn random_color() -> Color {
31
31
-
match rand::thread_rng().gen_range(1..=12) {
32
32
-
1 => Color::Black,
33
33
-
2 => Color::White,
34
34
-
3 => Color::Red,
35
35
-
4 => Color::Green,
36
36
-
5 => Color::Blue,
37
37
-
6 => Color::Yellow,
38
38
-
7 => Color::Orange,
39
39
-
8 => Color::Purple,
40
40
-
9 => Color::Brown,
41
41
-
10 => Color::Pink,
42
42
-
11 => Color::Gray,
43
43
-
12 => Color::Cyan,
44
44
-
_ => unreachable!(),
45
45
-
}
30
30
+
pub fn random_color(except: Option<Color>) -> Color {
31
31
+
let all = [
32
32
+
Color::Black,
33
33
+
Color::White,
34
34
+
Color::Red,
35
35
+
Color::Green,
36
36
+
Color::Blue,
37
37
+
Color::Yellow,
38
38
+
Color::Orange,
39
39
+
Color::Purple,
40
40
+
Color::Brown,
41
41
+
Color::Cyan,
42
42
+
Color::Pink,
43
43
+
Color::Gray,
44
44
+
];
45
45
+
let candidates = all
46
46
+
.iter()
47
47
+
.filter(|c| match except {
48
48
+
None => true,
49
49
+
Some(color) => &&color != c,
50
50
+
})
51
51
+
.collect::<Vec<_>>();
52
52
+
53
53
+
*candidates[rand::thread_rng().gen_range(0..candidates.len())]
46
54
}
47
55
48
56
impl Default for Color {
···
1
1
+
use std::env;
2
2
+
1
3
use anyhow::Result;
4
4
+
use itertools::Itertools;
5
5
+
use rand::Rng;
2
6
use shapemaker::{
3
7
cli::{canvas_from_cli, cli_args},
4
8
*,
···
11
15
pub fn run(args: cli::Args) -> Result<()> {
12
16
let mut canvas = canvas_from_cli(&args);
13
17
18
18
+
let qrname = env::var("QRCODE_NAME").unwrap();
19
19
+
14
20
if args.cmd_image && !args.cmd_video {
15
15
-
canvas = examples::dna_analysis_machine();
21
21
+
canvas.set_grid_size(3, 3);
22
22
+
canvas.add_or_replace_layer(canvas.random_layer("root"));
23
23
+
canvas.new_layer("qr");
24
24
+
let qrcode = Object::Image(
25
25
+
vec![
26
26
+
canvas.world_region.topleft(),
27
27
+
canvas.world_region.topright(),
28
28
+
canvas.world_region.bottomright(),
29
29
+
canvas.world_region.bottomleft(),
30
30
+
][rand::thread_rng().gen_range(0..4)]
31
31
+
.region(),
32
32
+
format!("./{qrname}-qr.png"),
33
33
+
);
34
34
+
canvas.root().remove_all_objects_in(&qrcode.region());
35
35
+
canvas.set_background(Color::White);
36
36
+
canvas.add_object("qr", "qr", qrcode, None).unwrap();
37
37
+
canvas.put_layer_on_top("qr");
38
38
+
canvas.root().objects.values_mut().for_each(|o| {
39
39
+
if !o.object.fillable() {
40
40
+
o.fill = Some(Fill::Solid(Color::Black));
41
41
+
}
42
42
+
});
43
43
+
16
44
let rendered = canvas.render(true)?;
17
45
if args.arg_file.ends_with(".svg") {
18
46
std::fs::write(args.arg_file, rendered).unwrap();
···
1
1
-
use std::collections::HashMap;
1
1
+
use std::{cell, collections::HashMap};
2
2
3
3
use crate::{ColorMapping, Fill, Filter, Point, Region, Transformation};
4
4
use itertools::Itertools;
···
24
24
CenteredText(Point, String, f32),
25
25
// FittedText(Region, String),
26
26
Rectangle(Point, Point),
27
27
+
Image(Region, String),
27
28
RawSVG(Box<dyn svg::Node>),
28
29
}
29
30
···
252
253
| Object::Dot(anchor)
253
254
| Object::SmallCircle(anchor) => anchor.translate(dx, dy),
254
255
Object::BigCircle(center) => center.translate(dx, dy),
256
256
+
Object::Image(region, ..) => region.translate(dx, dy),
255
257
Object::RawSVG(_) => {
256
258
unimplemented!()
257
259
}
···
297
299
| Object::Dot(anchor)
298
300
| Object::SmallCircle(anchor) => anchor.region(),
299
301
Object::BigCircle(center) => center.region(),
302
302
+
Object::Image(region, ..) => *region,
300
303
Object::RawSVG(_) => {
301
304
unimplemented!()
302
305
}
···
333
336
Object::SmallCircle(..) => self.render_small_circle(cell_size, object_sizes),
334
337
Object::Dot(..) => self.render_dot(cell_size, object_sizes),
335
338
Object::BigCircle(..) => self.render_big_circle(cell_size),
339
339
+
Object::Image(..) => self.render_image(cell_size),
336
340
Object::RawSVG(..) => self.render_raw_svg(),
337
341
};
338
342
339
343
group.set("data-object", id).add(rendered)
344
344
+
}
345
345
+
346
346
+
fn render_image(&self, cell_size: usize) -> Box<dyn svg::node::Node> {
347
347
+
if let Object::Image(region, path) = self {
348
348
+
let (x, y) = region.start.coords(cell_size);
349
349
+
return Box::new(
350
350
+
svg::node::element::Image::new()
351
351
+
.set("x", x)
352
352
+
.set("y", y)
353
353
+
.set("width", region.width() * cell_size)
354
354
+
.set("height", region.height() * cell_size)
355
355
+
.set("href", path.clone()),
356
356
+
);
357
357
+
}
358
358
+
359
359
+
panic!("Expected Image, got {:?}", self);
340
360
}
341
361
342
362
fn render_raw_svg(&self) -> Box<dyn svg::node::Node> {
···
208
208
209
209
// panics if the region is invalid
210
210
pub fn ensure_valid(self) -> Result<Self> {
211
211
-
if self.start.0 >= self.end.0 || self.start.1 >= self.end.1 {
211
211
+
if self.start.0 > self.end.0 || self.start.1 > self.end.1 {
212
212
return Err(format_err!(
213
213
-
"Invalid region: start ({:?}) >= end ({:?})",
213
213
+
"Invalid region: start ({:?}) > end ({:?})",
214
214
self.start,
215
215
self.end
216
216
));
···
1
1
+
while true
2
2
+
set id (nanoid -s 10)
3
3
+
qrencode "https://shapemaker.ewen.works/found/$id" -o street/$id-qr.png
4
4
+
QRCODE_NAME=street/$id just example-image out.svg "--objects-count 5..15"
5
5
+
if test (read || echo "n") = "y"
6
6
+
cp out.svg street/$id.svg
7
7
+
resvg --width 2000 out.svg street/$id.png
8
8
+
echo resvg --width 2000 street/$id.svg street/$id.png
9
9
+
echo saved street/$id.svg \| street/$id.png
10
10
+
else
11
11
+
rm street/$id-qr.png
12
12
+
end
13
13
+
end