Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Theming and Theme Customization #42

Merged
merged 23 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions examples/theme.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::sync::Arc;

use identicon_rs::color::RGB;
use identicon_rs::error::IdenticonError;
use identicon_rs::theme::HSLRange;
use identicon_rs::Identicon;

fn main() -> Result<(), IdenticonError> {
let identicon_theme = HSLRange::new(
0.0,
360.0,
50.0,
90.0,
40.0,
60.0,
vec![RGB {
red: 240,
green: 240,
blue: 240,
}],
)?;

let identicon_theme = Arc::new(identicon_theme);

let conways_glider = String::from("conways-glider");
let test_string = "identicon_rs";

// Stored example
let mut identicon_conways_glider = Identicon::new(&conways_glider);
identicon_conways_glider.set_theme(identicon_theme.clone());
identicon_conways_glider.save_image("output_1.png")?;

// Chained example with no border
Identicon::new(test_string)
.set_border(0)
.set_theme(identicon_theme.clone())
.save_image("output_2.png")?;
Ok(())
}
91 changes: 15 additions & 76 deletions src/color.rs
Original file line number Diff line number Diff line change
@@ -1,83 +1,22 @@
use crate::map_values::map_values;
/// RGB Color Struct
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub struct RGB {
/// The RGB Red Value
pub red: u8,

/// Generates the color of the active pixels.
///
/// This is based on [https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB](https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB).
pub fn generate_color(hash: &[u8]) -> (u8, u8, u8) {
let hsl = Hsl::from(hash);
/// The RGB Green Value
pub green: u8,

// Convert HSL to RGB
let chroma = (1.0 - ((2.0 * hsl.lightness) - 1.0).abs()) * hsl.saturation;
let hue_prime = hsl.hue / 60.0;
let x = chroma * (1.0 - ((hue_prime % 2.0) - 1.0).abs());

let (r_prime, g_prime, b_prime) = if (0.0..1.0).contains(&hue_prime) {
(chroma, x, 0.0)
} else if (1.0..2.0).contains(&hue_prime) {
(x, chroma, 0.0)
} else if (2.0..3.0).contains(&hue_prime) {
(0.0, chroma, x)
} else if (3.0..4.0).contains(&hue_prime) {
(0.0, x, chroma)
} else if (4.0..5.0).contains(&hue_prime) {
(x, 0.0, chroma)
} else if (5.0..6.0).contains(&hue_prime) {
(chroma, 0.0, x)
} else {
// This should not occur as the hue is between 0 and 360, which casts down to between 0-6
(0.0, 0.0, 0.0)
};

// This is blocked by https://github.com/rust-lang/rust/issues/37854
// let (r_prime, g_prime, b_prime) = match hue_prime {
// 0.0..1.0 => (chroma, x, 0.0),
// 1.0..2.0 => (x, chroma, 0.0),
// 2.0..3.0 => (0.0, chroma, x),
// 3.0..4.0 => (0.0, x, chroma),
// 4.0..5.0 => (x, 0.0, chroma),
// 5.0..6.0 => (chroma, 0.0, x),
// _ => (0.0, 0.0, 0.0),
// }

// Lightness modifier
let m = hsl.lightness - chroma * 0.5;

let red = (r_prime + m) * 255.0;
let green = (g_prime + m) * 255.0;
let blue = (b_prime + m) * 255.0;

(red as u8, green as u8, blue as u8)
/// The RGB Blue Value
pub blue: u8,
}

#[derive(Debug)]
pub struct Hsl {
pub hue: f32,
pub saturation: f32,
pub lightness: f32,
}

impl From<&[u8]> for Hsl {
fn from(hash: &[u8]) -> Self {
// Compute hash for hue space in larger bitspace
let hue_hash_1 = (hash[0] as u16 & 0x0f) << 8;
let hue_hash_2 = hash[1] as u16;
let hue_hash = (hue_hash_1 | hue_hash_2) as u32;

// Compute HSL values
let hue = map_values(hue_hash as f32, 0.0, 4095.0, 0.0, 360.0);

// Handle 0 degree hue is equivalent to 360 degree hue
let hue = hue % 360.0;

// Saturation should be between 0.5 and 0.75 for pastel colors
let saturation = map_values(hash[2] as f32, 0.0, 255.0, 50.0, 75.0) / 100.0;

// Lightness should be between 0.6 and 0.70 for pastel colors
let lightness = map_values(hash[3] as f32, 0.0, 255.0, 60.0, 70.0) / 100.0;
Hsl {
hue,
saturation,
lightness,
impl From<(u8, u8, u8)> for RGB {
fn from(value: (u8, u8, u8)) -> Self {
RGB {
red: value.0,
green: value.1,
blue: value.2,
}
}
}
24 changes: 22 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use thiserror::Error;

/// Identicon errors.
use crate::theme;

/// Identicon errors
#[derive(Error, Debug)]
pub enum IdenticonError {
/// Failed to generate the image.
Expand Down Expand Up @@ -34,11 +36,15 @@ pub enum IdenticonError {
/// Currently set scale value.
scale: u32,
},

/// Indicates an issue with the provided theme.
#[error(transparent)]
ThemeError(#[from] theme::error::ThemeError),
}

#[cfg(test)]
mod tests {
use crate::error::IdenticonError;
use crate::{error::IdenticonError, theme::error::ThemeError};

#[test]
fn generate_image_error_works() {
Expand Down Expand Up @@ -76,4 +82,18 @@ mod tests {
"identicon size too large: 5, must be less or equal to identicon scale: 3";
assert_eq!(expected_text, error.to_string());
}

#[test]
fn theme_error_works() {
let theme_error = ThemeError::ThemeProcessingError("bad field".to_string());
let identicon_error: IdenticonError = theme_error.into();
match identicon_error {
IdenticonError::ThemeError(inner_error) => match inner_error {
ThemeError::ThemeProcessingError(_) => (),
_ => panic!("wrong inner error type"),
},
_ => panic!("wrong error type"),
}
// assert_eq!(theme_error, identicon_error);
}
}
8 changes: 8 additions & 0 deletions src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use sha3::{Digest, Sha3_256};

pub fn hash_value(input_value: &str) -> Vec<u8> {
let input_trimmed = input_value.trim();
Sha3_256::digest(input_trimmed.as_bytes())
.as_slice()
.to_vec()
}
Loading