1#![doc(html_root_url = "https://docs.rs/bc-lifehash/0.1.0")]
2#![warn(rust_2018_idioms)]
3
4mod bit_enumerator;
54mod cell_grid;
55mod change_grid;
56mod color;
57mod color_func;
58mod color_grid;
59mod frac_grid;
60mod gradients;
61mod grid;
62mod hsb_color;
63mod patterns;
64
65use std::collections::BTreeSet;
66
67use bit_enumerator::BitEnumerator;
68use cell_grid::CellGrid;
69use change_grid::ChangeGrid;
70use color::{clamped, lerp_from};
71use color_grid::ColorGrid;
72use frac_grid::FracGrid;
73use gradients::select_gradient;
74use patterns::select_pattern;
75use sha2::{Digest, Sha256};
76
77#[derive(Clone, Copy, Debug, PartialEq, Eq)]
78pub enum Version {
79 Version1,
80 Version2,
81 Detailed,
82 Fiducial,
83 GrayscaleFiducial,
84}
85
86pub struct Image {
87 pub width: usize,
88 pub height: usize,
89 pub colors: Vec<u8>,
90}
91
92fn sha256(data: &[u8]) -> Vec<u8> {
93 let mut hasher = Sha256::new();
94 hasher.update(data);
95 hasher.finalize().to_vec()
96}
97
98fn make_image(
99 width: usize,
100 height: usize,
101 float_colors: &[f64],
102 module_size: usize,
103 has_alpha: bool,
104) -> Image {
105 assert!(module_size > 0, "Invalid module size");
106
107 let scaled_width = width * module_size;
108 let scaled_height = height * module_size;
109 let result_components = if has_alpha { 4 } else { 3 };
110 let scaled_capacity = scaled_width * scaled_height * result_components;
111
112 let mut result_colors = vec![0u8; scaled_capacity];
113
114 for target_y in 0..scaled_width {
118 for target_x in 0..scaled_height {
119 let source_x = target_x / module_size;
120 let source_y = target_y / module_size;
121 let source_offset = (source_y * width + source_x) * 3;
122
123 let target_offset =
124 (target_y * scaled_width + target_x) * result_components;
125
126 result_colors[target_offset] =
127 (clamped(float_colors[source_offset]) * 255.0) as u8;
128 result_colors[target_offset + 1] =
129 (clamped(float_colors[source_offset + 1]) * 255.0) as u8;
130 result_colors[target_offset + 2] =
131 (clamped(float_colors[source_offset + 2]) * 255.0) as u8;
132 if has_alpha {
133 result_colors[target_offset + 3] = 255;
134 }
135 }
136 }
137
138 Image {
139 width: scaled_width,
140 height: scaled_height,
141 colors: result_colors,
142 }
143}
144
145pub fn make_from_utf8(
146 s: &str,
147 version: Version,
148 module_size: usize,
149 has_alpha: bool,
150) -> Image {
151 make_from_data(s.as_bytes(), version, module_size, has_alpha)
152}
153
154pub fn make_from_data(
155 data: &[u8],
156 version: Version,
157 module_size: usize,
158 has_alpha: bool,
159) -> Image {
160 let digest = sha256(data);
161 make_from_digest(&digest, version, module_size, has_alpha)
162}
163
164pub fn make_from_digest(
165 digest: &[u8],
166 version: Version,
167 module_size: usize,
168 has_alpha: bool,
169) -> Image {
170 assert_eq!(digest.len(), 32, "Digest must be 32 bytes");
171
172 let (length, max_generations): (usize, usize) = match version {
173 Version::Version1 | Version::Version2 => (16, 150),
174 Version::Detailed | Version::Fiducial | Version::GrayscaleFiducial => {
175 (32, 300)
176 }
177 };
178
179 let mut current_cell_grid = CellGrid::new(length, length);
180 let mut next_cell_grid = CellGrid::new(length, length);
181 let mut current_change_grid = ChangeGrid::new(length, length);
182 let mut next_change_grid = ChangeGrid::new(length, length);
183
184 match version {
185 Version::Version1 => {
186 next_cell_grid.set_data(digest);
187 }
188 Version::Version2 => {
189 let hashed = sha256(digest);
190 next_cell_grid.set_data(&hashed);
191 }
192 Version::Detailed | Version::Fiducial | Version::GrayscaleFiducial => {
193 let mut digest1 = digest.to_vec();
194 if version == Version::GrayscaleFiducial {
195 digest1 = sha256(&digest1);
196 }
197 let digest2 = sha256(&digest1);
198 let digest3 = sha256(&digest2);
199 let digest4 = sha256(&digest3);
200 let mut digest_final = digest1;
201 digest_final.extend_from_slice(&digest2);
202 digest_final.extend_from_slice(&digest3);
203 digest_final.extend_from_slice(&digest4);
204 next_cell_grid.set_data(&digest_final);
205 }
206 }
207
208 next_change_grid.grid.set_all(true);
209
210 let mut history_set: BTreeSet<Vec<u8>> = BTreeSet::new();
211 let mut history: Vec<Vec<u8>> = Vec::new();
212
213 while history.len() < max_generations {
214 std::mem::swap(&mut current_cell_grid, &mut next_cell_grid);
215 std::mem::swap(&mut current_change_grid, &mut next_change_grid);
216
217 let data = current_cell_grid.data();
218 let hash = sha256(&data);
219 if history_set.contains(&hash) {
220 break;
221 }
222 history_set.insert(hash);
223 history.push(data);
224
225 current_cell_grid.next_generation(
226 ¤t_change_grid,
227 &mut next_cell_grid,
228 &mut next_change_grid,
229 );
230 }
231
232 let mut frac_grid = FracGrid::new(length, length);
233 for (i, h) in history.iter().enumerate() {
234 current_cell_grid.set_data(h);
235 let frac =
236 clamped(lerp_from(0.0, history.len() as f64, (i + 1) as f64));
237 frac_grid.overlay(¤t_cell_grid, frac);
238 }
239
240 if version != Version::Version1 {
242 let mut min_value = f64::INFINITY;
243 let mut max_value = f64::NEG_INFINITY;
244 frac_grid.grid.for_all(|x, y| {
245 let value = frac_grid.grid.get_value(x, y);
246 if value < min_value {
247 min_value = value;
248 }
249 if value > max_value {
250 max_value = value;
251 }
252 });
253
254 let width = frac_grid.grid.width;
255 let height = frac_grid.grid.height;
256 for y in 0..height {
257 for x in 0..width {
258 let value = frac_grid.grid.get_value(x, y);
259 let normalized = lerp_from(min_value, max_value, value);
260 frac_grid.grid.set_value(normalized, x, y);
261 }
262 }
263 }
264
265 let mut entropy = BitEnumerator::new(digest.to_vec());
266
267 match version {
268 Version::Detailed => {
269 entropy.next();
270 }
271 Version::Version2 => {
272 entropy.next_uint2();
273 }
274 _ => {}
275 }
276
277 let gradient = select_gradient(&mut entropy, version);
278 let pattern = select_pattern(&mut entropy, version);
279 let color_grid = ColorGrid::new(&frac_grid, &gradient, pattern);
280
281 make_image(
282 color_grid.grid.width,
283 color_grid.grid.height,
284 &color_grid.colors(),
285 module_size,
286 has_alpha,
287 )
288}