1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
use blake2_rfc::blake2b::blake2b;
use palette::{FromColor, FromComponent, Hsl, RgbHue, Srgb};
/// Struct to store default coloring schemes
struct SchemeElement {
freq: u8,
colors: [usize; 19],
}
/// Function to set default coloring schemes, taken as is from js code
#[rustfmt::skip]
fn default_schemes() -> [SchemeElement; 7] {
[
SchemeElement {
// "target"
freq: 1,
colors: [0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 1],
},
SchemeElement {
// "cube",
freq: 20,
colors: [0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 5],
},
SchemeElement {
// "quazar",
freq: 16,
colors: [1, 2, 3, 1, 2, 4, 5, 5, 4, 1, 2, 3, 1, 2, 4, 5, 5, 4, 0],
},
SchemeElement {
// "flower",
freq: 32,
colors: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 3],
},
SchemeElement {
// "cyclic",
freq: 32,
colors: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6],
},
SchemeElement {
// "vmirror",
freq: 128,
colors: [0, 1, 2, 3, 4, 5, 3, 4, 2, 0, 1, 6, 7, 8, 9, 7, 8, 6, 10],
},
SchemeElement {
// "hmirror",
freq: 128,
colors: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 6, 7, 5, 3, 4, 2, 11],
},
]
}
/// Circle color, in RGBA format
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
pub alpha: u8,
}
impl Color {
/// convert `Color` into `[u8; 4]` array, for `png` image pixel-by-pixel generation
#[cfg(feature = "pix")]
pub fn to_array(&self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
}
/// convert `Color` into hex string, for `svg` image generation
#[cfg(feature = "vec")]
pub fn to_hex(&self) -> String {
format!("#{}", hex::encode([self.red, self.green, self.blue]))
}
/// set `Color` to default background, needed only for `png` images
#[cfg(feature = "pix")]
pub fn background() -> Self {
Self {
red: 255,
green: 255,
blue: 255,
alpha: 0,
}
}
/// set `Color` to default color of the large circle enclosing the small ones
pub fn foreground() -> Self {
Self {
red: 238,
green: 238,
blue: 238,
alpha: 255,
}
}
/// function to derive color from `u8` number and saturation component
/// calculated elsewhere;
/// is accessible and used only for `u8` numbers other than 0 and 255;
/// no check here is done for b value;
fn derive(b: u8, sat_component: f64) -> Self {
// HSL color hue in degrees
// calculated as integer, same as in js code
// transformation to u16 is done to avoid overflow
let h = (b as u16 % 64 * 360) / 64;
// recalculated into `RgbHue`, to be used as HSL hue component
let h_component = RgbHue::from_degrees(h as f64);
// HSL lightness in percents
let l: u8 = match b / 64 {
0 => 53,
1 => 15,
2 => 35,
_ => 75,
};
// recalculated in HSL lightness component (component range is 0.00 to 1.00)
let l_component: f64 = (l as f64) / 100f64;
// defining HSL color
let color_hsl = Hsl::new(h_component, sat_component, l_component);
// transforming HSL color into RGB color, possibly lossy, TODO check if too lossy
let color_srgb = Srgb::from_color(color_hsl);
// getting red, green, blue components, transforming them in 0..255 range of u8
let red = u8::from_component(color_srgb.red);
let green = u8::from_component(color_srgb.green);
let blue = u8::from_component(color_srgb.blue);
// finalize color, set alpha value to 255
Self {
red,
green,
blue,
alpha: 255,
}
}
}
/// Function to calculate identicon colors from `&[u8]` input slice.
/// Total 19 colors are always produced.
pub fn get_colors(into_id: &[u8]) -> [Color; 19] {
let into_zero = &[0u8; 32];
let zero = blake2b(64, &[], into_zero).as_bytes().to_vec();
let id_prep = blake2b(64, &[], into_id).as_bytes().to_vec();
let mut id: Vec<u8> = Vec::with_capacity(64);
for (i, x) in id_prep.iter().enumerate() {
let new = x.wrapping_sub(zero[i]);
id.push(new);
}
// Since `id[29]` is u8, `sat` could range from 30 to 109, i.e. it always fits into u8.
// Transformation of id[29] into u16 is to avoid overflow in multiplication
// (wrapping could be used, but is more bulky).
// TODO For color calculation `sat` is used as saturation in percents
// (this is taken as is from js code).
// However, this way `sat_component` could have values above 1.00.
// Palette crate does not check at this moment that `sat_component` is not
// overflowing 1.00, and produces some kind of resulting color.
// Need to find out what should have happened if the sat values are above 100.
let sat = (((id[29] as u16 * 70 / 256 + 26) % 80) + 30) as u8;
let sat_component: f64 = (sat as f64) / 100f64;
// calculating palette: set of 32 RGBA colors to be used in drawing
// only id vector is used for this calculation
let mut my_palette: Vec<Color> = Vec::with_capacity(64);
for (i, x) in id.iter().enumerate() {
let b = x.wrapping_add((i as u8 % 28).wrapping_mul(58));
let new = match b {
0 => Color {
red: 4,
green: 4,
blue: 4,
alpha: 255,
},
255 => Color::foreground(), // small circle is transparent, thus whatever is underneath it goes into `png` data, underneath is the foreground-colored large circle
_ => Color::derive(b, sat_component),
};
my_palette.push(new);
}
// loading default coloring schemes
let schemes = default_schemes();
// `total` is the sum of frequencies for all scheme elements in coloring schemes,
// in current setting is always 357
let mut total = 0;
for x in schemes.iter() {
total += x.freq as u32;
}
// `d` is used to determine the coloring scheme to be used.
// Transformation into u32 is used to avoid overflow.
let d = (id[30] as u32 + (id[31] as u32) * 256) % total;
// determining the coloring scheme to be used
let my_scheme = choose_scheme(schemes, d);
// calculating rotation for the coloring scheme
let rot = (id[28] % 6) * 3;
// picking colors from palette using coloring scheme with rotation applied
let mut my_colors: Vec<Color> = Vec::with_capacity(19);
for i in 0..19 {
let num_color = {
if i < 18 {
(i + rot) % 18
} else {
18
}
} as usize;
let num_palette = my_scheme.colors[num_color];
let color = my_palette[num_palette];
my_colors.push(color);
}
my_colors
.try_into()
.expect("always generate 19-element set")
}
/// Function to choose the coloring scheme based on value d.
/// Note that d is calculated as remainder of division by total sum of frequencies,
/// so it can not exceed the total sum of frequencies
fn choose_scheme(schemes: [SchemeElement; 7], d: u32) -> SchemeElement {
let mut sum = 0;
let mut found_scheme = None;
for x in schemes {
sum += x.freq as u32;
if d < sum {
found_scheme = Some(x);
break;
}
}
found_scheme.expect("should always be determined: d is calculated as remainder of division by total sum of frequencies, so it can not exceed the total sum of frequencies")
}
#[cfg(test)]
mod tests {
use super::*;
const ALICE: &[u8] = &[
212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88,
133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125,
]; // Alice public key; corresponds to hexadecimal "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" and base58 "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" in westend network
const BOB: &[u8] = &[
142, 175, 4, 21, 22, 135, 115, 99, 38, 201, 254, 161, 126, 37, 252, 82, 135, 97, 54, 147,
201, 18, 144, 156, 178, 38, 170, 71, 148, 242, 106, 72,
]; // Bob public key; corresponds to hexadecimal "8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48" and base58 "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" in westend network
/// made with a color picking GIMP tool using the icon from polkadot website
/// alpha set to 255 for all
#[rustfmt::skip]
fn alice_website() -> [Color; 19] {
[
Color{red: 165, green: 227, blue: 156, alpha: 255},
Color{red: 60, green: 40, blue: 17, alpha: 255},
Color{red: 184, green: 68, blue: 202, alpha: 255},
Color{red: 139, green: 39, blue: 88, alpha: 255},
Color{red: 135, green: 68, blue: 202, alpha: 255},
Color{red: 225, green: 156, blue: 227, alpha: 255},
Color{red: 139, green: 39, blue: 88, alpha: 255},
Color{red: 135, green: 68, blue: 202, alpha: 255},
Color{red: 184, green: 68, blue: 202, alpha: 255},
Color{red: 165, green: 227, blue: 156, alpha: 255},
Color{red: 60, green: 40, blue: 17, alpha: 255},
Color{red: 162, green: 202, blue: 68, alpha: 255},
Color{red: 39, green: 139, blue: 139, alpha: 255},
Color{red: 187, green: 202, blue: 68, alpha: 255},
Color{red: 38, green: 60, blue: 17, alpha: 255},
Color{red: 39, green: 139, blue: 139, alpha: 255},
Color{red: 187, green: 202, blue: 68, alpha: 255},
Color{red: 162, green: 202, blue: 68, alpha: 255},
Color{red: 61, green: 39, blue: 139, alpha: 255},
]
}
/// made with a color picking GIMP tool using the icon from polkadot website
/// alpha set to 255 for all
#[rustfmt::skip]
fn bob_website() -> [Color; 19] {
[
Color{red: 58, green: 120, blue: 61, alpha: 255},
Color{red: 200, green: 214, blue: 169, alpha: 255},
Color{red: 214, green: 169, blue: 182, alpha: 255},
Color{red: 36, green: 52, blue: 25, alpha: 255},
Color{red: 127, green: 93, blue: 177, alpha: 255},
Color{red: 214, green: 169, blue: 182, alpha: 255},
Color{red: 58, green: 120, blue: 61, alpha: 255},
Color{red: 200, green: 214, blue: 169, alpha: 255},
Color{red: 52, green: 25, blue: 30, alpha: 255},
Color{red: 113, green: 177, blue: 93, alpha: 255},
Color{red: 58, green: 120, blue: 114, alpha: 255},
Color{red: 58, green: 120, blue: 108, alpha: 255},
Color{red: 118, green: 93, blue: 177, alpha: 255},
Color{red: 25, green: 52, blue: 39, alpha: 255},
Color{red: 58, green: 120, blue: 108, alpha: 255},
Color{red: 113, green: 177, blue: 93, alpha: 255},
Color{red: 58, green: 120, blue: 114, alpha: 255},
Color{red: 52, green: 25, blue: 30, alpha: 255},
Color{red: 33, green: 25, blue: 52, alpha: 255},
]
}
#[test]
fn colors_alice() {
let alice_calculated = get_colors(ALICE);
assert!(
alice_website() == alice_calculated,
"Got different Alice colors:\n{:?}",
alice_calculated
);
}
#[test]
fn colors_bob() {
let bob_calculated = get_colors(BOB);
assert!(
bob_website() == bob_calculated,
"Got different Bob colors:\n{:?}",
bob_calculated
);
}
}