#![doc(html_root_url = "https://docs.rs/bc-lifehash/0.1.0")]
#![warn(rust_2018_idioms)]
mod bit_enumerator;
mod cell_grid;
mod change_grid;
mod color;
mod color_func;
mod color_grid;
mod frac_grid;
mod gradients;
mod grid;
mod hsb_color;
mod patterns;
use std::collections::BTreeSet;
use bit_enumerator::BitEnumerator;
use cell_grid::CellGrid;
use change_grid::ChangeGrid;
use color::{clamped, lerp_from};
use color_grid::ColorGrid;
use frac_grid::FracGrid;
use gradients::select_gradient;
use patterns::select_pattern;
use sha2::{Digest, Sha256};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Version {
Version1,
Version2,
Detailed,
Fiducial,
GrayscaleFiducial,
}
pub struct Image {
pub width: usize,
pub height: usize,
pub colors: Vec<u8>,
}
fn sha256(data: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().to_vec()
}
fn make_image(
width: usize,
height: usize,
float_colors: &[f64],
module_size: usize,
has_alpha: bool,
) -> Image {
assert!(module_size > 0, "Invalid module size");
let scaled_width = width * module_size;
let scaled_height = height * module_size;
let result_components = if has_alpha { 4 } else { 3 };
let scaled_capacity = scaled_width * scaled_height * result_components;
let mut result_colors = vec![0u8; scaled_capacity];
for target_y in 0..scaled_width {
for target_x in 0..scaled_height {
let source_x = target_x / module_size;
let source_y = target_y / module_size;
let source_offset = (source_y * width + source_x) * 3;
let target_offset =
(target_y * scaled_width + target_x) * result_components;
result_colors[target_offset] =
(clamped(float_colors[source_offset]) * 255.0) as u8;
result_colors[target_offset + 1] =
(clamped(float_colors[source_offset + 1]) * 255.0) as u8;
result_colors[target_offset + 2] =
(clamped(float_colors[source_offset + 2]) * 255.0) as u8;
if has_alpha {
result_colors[target_offset + 3] = 255;
}
}
}
Image {
width: scaled_width,
height: scaled_height,
colors: result_colors,
}
}
pub fn make_from_utf8(
s: &str,
version: Version,
module_size: usize,
has_alpha: bool,
) -> Image {
make_from_data(s.as_bytes(), version, module_size, has_alpha)
}
pub fn make_from_data(
data: &[u8],
version: Version,
module_size: usize,
has_alpha: bool,
) -> Image {
let digest = sha256(data);
make_from_digest(&digest, version, module_size, has_alpha)
}
pub fn make_from_digest(
digest: &[u8],
version: Version,
module_size: usize,
has_alpha: bool,
) -> Image {
assert_eq!(digest.len(), 32, "Digest must be 32 bytes");
let (length, max_generations): (usize, usize) = match version {
Version::Version1 | Version::Version2 => (16, 150),
Version::Detailed | Version::Fiducial | Version::GrayscaleFiducial => {
(32, 300)
}
};
let mut current_cell_grid = CellGrid::new(length, length);
let mut next_cell_grid = CellGrid::new(length, length);
let mut current_change_grid = ChangeGrid::new(length, length);
let mut next_change_grid = ChangeGrid::new(length, length);
match version {
Version::Version1 => {
next_cell_grid.set_data(digest);
}
Version::Version2 => {
let hashed = sha256(digest);
next_cell_grid.set_data(&hashed);
}
Version::Detailed | Version::Fiducial | Version::GrayscaleFiducial => {
let mut digest1 = digest.to_vec();
if version == Version::GrayscaleFiducial {
digest1 = sha256(&digest1);
}
let digest2 = sha256(&digest1);
let digest3 = sha256(&digest2);
let digest4 = sha256(&digest3);
let mut digest_final = digest1;
digest_final.extend_from_slice(&digest2);
digest_final.extend_from_slice(&digest3);
digest_final.extend_from_slice(&digest4);
next_cell_grid.set_data(&digest_final);
}
}
next_change_grid.grid.set_all(true);
let mut history_set: BTreeSet<Vec<u8>> = BTreeSet::new();
let mut history: Vec<Vec<u8>> = Vec::new();
while history.len() < max_generations {
std::mem::swap(&mut current_cell_grid, &mut next_cell_grid);
std::mem::swap(&mut current_change_grid, &mut next_change_grid);
let data = current_cell_grid.data();
let hash = sha256(&data);
if history_set.contains(&hash) {
break;
}
history_set.insert(hash);
history.push(data);
current_cell_grid.next_generation(
¤t_change_grid,
&mut next_cell_grid,
&mut next_change_grid,
);
}
let mut frac_grid = FracGrid::new(length, length);
for (i, h) in history.iter().enumerate() {
current_cell_grid.set_data(h);
let frac =
clamped(lerp_from(0.0, history.len() as f64, (i + 1) as f64));
frac_grid.overlay(¤t_cell_grid, frac);
}
if version != Version::Version1 {
let mut min_value = f64::INFINITY;
let mut max_value = f64::NEG_INFINITY;
frac_grid.grid.for_all(|x, y| {
let value = frac_grid.grid.get_value(x, y);
if value < min_value {
min_value = value;
}
if value > max_value {
max_value = value;
}
});
let width = frac_grid.grid.width;
let height = frac_grid.grid.height;
for y in 0..height {
for x in 0..width {
let value = frac_grid.grid.get_value(x, y);
let normalized = lerp_from(min_value, max_value, value);
frac_grid.grid.set_value(normalized, x, y);
}
}
}
let mut entropy = BitEnumerator::new(digest.to_vec());
match version {
Version::Detailed => {
entropy.next();
}
Version::Version2 => {
entropy.next_uint2();
}
_ => {}
}
let gradient = select_gradient(&mut entropy, version);
let pattern = select_pattern(&mut entropy, version);
let color_grid = ColorGrid::new(&frac_grid, &gradient, pattern);
make_image(
color_grid.grid.width,
color_grid.grid.height,
&color_grid.colors(),
module_size,
has_alpha,
)
}