diff --git a/web/src/contrast.rs b/web/src/contrast.rs new file mode 100644 index 0000000..fd5bfb6 --- /dev/null +++ b/web/src/contrast.rs @@ -0,0 +1,72 @@ +#![cfg(feature = "ogp_images")] + +use image::{Pixel, Rgb, Rgba}; +use pronouns_today::util::pcg64; + +pub fn generate_color_pair(state: &mut u128) -> ([u8; 3], [u8; 3]) { + let mut background = random_color(state); + let mut foreground; + loop { + foreground = random_color(state); + if meets_criteria(&foreground, &background) { + break; + } + background = random_color(state); + if meets_criteria(&foreground, &background) { + break; + } + } + (foreground, background) +} + +pub fn generate_color_pair_rgba(state: &mut u128) -> (Rgba, Rgba) { + let (color1, color2) = generate_color_pair(state); + + let (color1, color2): (Rgb, Rgb) = ( + color1.into(), + color2.into(), + ); + + (color1.to_rgba(), color2.to_rgba()) +} + +pub fn relative_lum(color: &[u8; 3]) -> f32 { + let color_mapped: Vec = color.iter() + .map(|val| { + let val = *val as f32 / 255.0; + if val < 0.03928 { + val / 12.92 + } else { + ((val+0.055)/1.055).powf(2.4) + } + }) + .collect(); + return 0.2126 * color_mapped[0] + 0.7152 * color_mapped[1] + 0.0722 * color_mapped[2] +} + +pub fn check_contrast(color1: &[u8; 3], color2: &[u8; 3]) -> f32 { + let a = 0.05 + relative_lum(color1); + let b = 0.05 + relative_lum(color2); + (a / b).max(b / a) +} + +pub fn get_saturation(color: &[u8; 3]) -> f32 { + let chroma_max = *(color.iter().max().unwrap()); + let chroma_min = *(color.iter().min().unwrap()); + return ((chroma_max - chroma_min) as f32) / chroma_max as f32; +} + +pub fn random_color(state: &mut u128) -> [u8; 3] { + let random = pcg64(state).to_le_bytes(); + return [ + random[0], + random[1], + random[2], + ] +} + +pub fn meets_criteria(foreground: &[u8; 3], background: &[u8; 3]) -> bool { + return check_contrast(foreground, background) >= 5.0 && + get_saturation(background) < 0.65 && + get_saturation(foreground) < 0.65 +} diff --git a/web/src/main.rs b/web/src/main.rs index d644862..f0ecc78 100644 --- a/web/src/main.rs +++ b/web/src/main.rs @@ -1,4 +1,5 @@ pub mod ogp_images; +pub mod contrast; use std::collections::HashMap; use std::fmt::{self, Display}; diff --git a/web/src/ogp_images.rs b/web/src/ogp_images.rs index 82fcfe8..76032e5 100644 --- a/web/src/ogp_images.rs +++ b/web/src/ogp_images.rs @@ -1,8 +1,10 @@ #![cfg(feature = "ogp_images")] +use crate::contrast::generate_color_pair_rgba; + use image::{DynamicImage, ImageBuffer, Pixel, Rgb, Rgba}; use lazy_static::lazy_static; -use pronouns_today::Pronoun; +use pronouns_today::{Pronoun, util::{self, pcg64_seed, seed_with_today}}; use rusttype::{Font, Scale, point}; const SCALE: u32 = 400; @@ -13,9 +15,16 @@ lazy_static! { .expect("Invalid font data in build. This build and binary is invalid, and cannot be used."); } -pub fn render(pronouns: &Pronoun) -> ImageBuffer, Vec> { - // TODO: Use a real seed here - let (color1, color2) = generate_color_pair(&[13, 12]); +pub fn render_today(pronouns: &Pronoun, name: &str) -> ImageBuffer, Vec> { + let mut state = util::INITIAL_STATE; + + seed_with_today(&mut state); + pcg64_seed(&mut state, name.as_bytes()); + + render(pronouns, &mut state) +} +pub fn render(pronouns: &Pronoun, pcg64: &mut u128) -> ImageBuffer, Vec> { + let (color1, color2) = generate_color_pair_rgba(pcg64); render_with_colors(pronouns, color1, color2) } @@ -74,7 +83,3 @@ pub fn render_with_colors(pronouns: &Pronoun, color1: Rgba, color2: Rgba image::DynamicImage::ImageRgba8(output_image).into_rgb8() } - -pub fn generate_color_pair(seed: &[u8]) -> (Rgba, Rgba) { - todo!(); -}