1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
2
3use anyhow::Result;
4use indoc::formatdoc;
5use resvg::{
6 tiny_skia::{Pixmap, Transform},
7 usvg::{self, TreeParsing},
8};
9use std::{fs, path::Path};
10use zkp_u256::U256;
11
12const COLORS: [&str; 36] = [
13 "#FF0000", "#FF2B00", "#FF5500", "#FF8000", "#FFAA00", "#FFD500", "#FFFF00", "#D4FF00",
14 "#AAFF00", "#80FF00", "#55FF00", "#2BFF00", "#00FF00", "#00FF2A", "#00FF2A", "#00FF80",
15 "#00FFAA", "#00FFD4", "#00FFFF", "#00D4FF", "#00AAFF", "#0080FF", "#0055FF", "#002AFF",
16 "#0000FF", "#2A00FF", "#0000FF", "#5500FF", "#8000FF", "#AA00FF", "#D500FF", "#FF00FF",
17 "#FF00D5", "#FF00AA", "#FF0080", "#FF0055",
18];
19
20pub struct Marble {
21 seed: U256,
22}
23
24impl Marble {
25 pub fn new<T>(seed: T) -> Self
27 where
28 U256: From<T>,
29 {
30 Self {
31 seed: U256::from(seed),
32 }
33 }
34
35 fn random_number<T>(&mut self, max: T) -> u32
36 where
37 U256: From<T>,
38 {
39 let max = U256::from(max);
40
41 let result = self.seed.clone() % max.clone();
42 self.seed /= max;
43
44 result.as_u32()
45 }
46
47 fn random_color(&mut self) -> &str {
48 let index = self.random_number(COLORS.len()) as usize;
49
50 COLORS[index]
51 }
52
53 #[must_use]
55 pub fn build_svg(&mut self) -> String {
56 let mut shapes = vec![
57 formatdoc!(
58 r#"
59 <g filter="url(#blur)" opacity=".8">
60 <path fill="{color}" d="M78.824-16.686c17.78 14.541 4.24 87.76-2.637 82.948-4.194-2.935-9.153-27.765-22.32-38.405-8.418-6.802-23.488-1.839-33.086-1.137-24.614 1.8 40.115-58.069 58.043-43.406Z"/>
61 </g>
62 "#,
63 color = self.random_color()
64 ),
65 formatdoc!(
66 r#"
67 <g filter="url(#blur)" opacity=".9">
68 <ellipse cx="33.545" cy="32.494" fill="{color}" rx="33.545" ry="32.494" transform="matrix(-.48289 -.87568 .7985 -.602 9.46 74.034)"/>
69 </g>
70 "#,
71 color = self.random_color()
72 ),
73 formatdoc!(
74 r#"
75 <g filter="url(#blur)" opacity=".8">
76 <ellipse cx="39.533" cy="39.042" fill="{color}" rx="39.533" ry="39.042" transform="matrix(-.2882 -.95757 .93652 -.35062 13.847 67.74)" />
77 </g>
78 "#,
79 color = self.random_color()
80 ),
81 ];
82
83 shapes.sort_by(|_, _| self.random_number(2).cmp(&1));
84
85 formatdoc!(
86 r##"
87 <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 80 80" transform="rotate({rotation} 40 40)">
88 <g clip-path="url(#a)">
89 <circle cx="40" cy="40" r="40" fill="#F8F8F8" />
90 {shapes}
91 </g>
92 <defs>
93 <filter id="blur" width="300" height="300" x="0" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse">
94 <feGaussianBlur result="effect1_foregroundBlur_557_59789" stdDeviation="9.6" />
95 </filter>
96 <clipPath id="a">
97 <rect width="80" height="80" fill="#fff" rx="40" />
98 </clipPath>
99 </defs>
100 </svg>
101 "##,
102 rotation = self.random_number(359),
103 shapes = shapes.join("")
104 )
105 }
106
107 pub fn render_png(&mut self, width: u32, height: u32) -> Result<Vec<u8>> {
115 let svg = self.build_svg();
116 let tree = usvg::Tree::from_data(svg.as_bytes(), &usvg::Options::default())?;
117
118 let mut pixmap = Pixmap::new(width, height).ok_or_else(|| {
119 anyhow::anyhow!("Failed to create pixmap with size {}x{}", width, height)
120 })?;
121
122 resvg::render(
123 &tree,
124 resvg::FitTo::Size(width, height),
125 Transform::default(),
126 pixmap.as_mut(),
127 )
128 .ok_or_else(|| anyhow::anyhow!("Failed to render SVG"))?;
129
130 Ok(pixmap.encode_png()?)
131 }
132
133 pub fn save_png<P: AsRef<Path>>(&mut self, width: u32, height: u32, path: P) -> Result<()> {
139 fs::write(path, self.render_png(width, height)?)?;
140
141 Ok(())
142 }
143}