Skip to content

Commit 2a3ba7d

Browse files
committed
w
1 parent 19793f9 commit 2a3ba7d

File tree

2 files changed

+98
-222
lines changed

2 files changed

+98
-222
lines changed

src/color_quiz.rs

Lines changed: 6 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pub struct ColorQuiz {
1010
}
1111

1212
impl ColorQuiz {
13-
/// Generate a random color quiz
1413
pub fn generate<R: Rng>(rng: &mut R) -> Self {
1514
Self {
1615
r: rng.gen_range(0..=255),
@@ -19,7 +18,6 @@ impl ColorQuiz {
1918
}
2019
}
2120

22-
/// Generate a 16:9 image (640x360) with the color
2321
pub fn generate_image(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
2422
let width = 640u32;
2523
let height = 360u32;
@@ -33,7 +31,6 @@ impl ColorQuiz {
3331

3432
let mut writer = encoder.write_header()?;
3533

36-
// Create image data - each pixel is RGB (3 bytes)
3734
let pixel_count = (width * height) as usize;
3835
let mut data = Vec::with_capacity(pixel_count * 3);
3936

@@ -49,24 +46,14 @@ impl ColorQuiz {
4946
Ok(buffer.into_inner())
5047
}
5148

52-
/// Validate user's answer in hex or oklch format
5349
pub fn validate_answer(&self, user_answer: &str) -> bool {
5450
let answer = user_answer.trim();
5551

56-
// Try hex format first
57-
if answer.starts_with('#') {
58-
return self.validate_hex(answer);
52+
if !answer.starts_with('#') {
53+
return false;
5954
}
6055

61-
// Try oklch format
62-
self.validate_oklch(answer)
63-
}
64-
65-
/// Validate hex color format: #RRGGBB
66-
/// Tolerance: total difference of 20 across all channels
67-
fn validate_hex(&self, hex: &str) -> bool {
68-
// Remove the # and parse
69-
let hex = hex.trim_start_matches('#');
56+
let hex = answer.trim_start_matches('#');
7057

7158
if hex.len() != 6 {
7259
return false;
@@ -81,115 +68,11 @@ impl ColorQuiz {
8168
+ (self.g as i32 - g as i32).abs()
8269
+ (self.b as i32 - b as i32).abs();
8370

84-
return diff <= 20;
71+
return diff <= 25;
8572
}
8673

8774
false
8875
}
89-
90-
/// Validate oklch color format
91-
/// Supports: oklch(45.0% 0.306 65.4), 45.0% 0.306 65.4, 45.0%, 0.306, 65.4
92-
/// Converts RGB to OKLCH and checks similarity
93-
fn validate_oklch(&self, input: &str) -> bool {
94-
// Parse the input
95-
let parsed = self.parse_oklch(input);
96-
if parsed.is_none() {
97-
return false;
98-
}
99-
100-
let (l_user, c_user, h_user) = parsed.unwrap();
101-
102-
// Convert our RGB to OKLCH
103-
let (l_actual, c_actual, h_actual) = rgb_to_oklch(self.r, self.g, self.b);
104-
105-
// Check if close enough
106-
// L: within 5% (0-100 scale)
107-
// C: within 0.05
108-
// H: within 10 degrees (handle wrap-around at 360)
109-
let l_diff = (l_actual - l_user).abs();
110-
let c_diff = (c_actual - c_user).abs();
111-
let h_diff = {
112-
let diff = (h_actual - h_user).abs();
113-
diff.min(360.0 - diff) // Handle wrap-around
114-
};
115-
116-
l_diff <= 5.0 && c_diff <= 0.05 && h_diff <= 10.0
117-
}
118-
119-
/// Parse OKLCH from various formats
120-
fn parse_oklch(&self, input: &str) -> Option<(f64, f64, f64)> {
121-
let input = input.trim();
122-
123-
// Remove oklch( and ) if present
124-
let inner = input
125-
.strip_prefix("oklch(")
126-
.and_then(|s| s.strip_suffix(')'))
127-
.unwrap_or(input);
128-
129-
// Split by whitespace or comma
130-
let parts: Vec<&str> = inner
131-
.split(|c: char| c.is_whitespace() || c == ',')
132-
.filter(|s| !s.is_empty())
133-
.collect();
134-
135-
if parts.len() != 3 {
136-
return None;
137-
}
138-
139-
// Parse L (can have % suffix)
140-
let l = parts[0].trim_end_matches('%').parse::<f64>().ok()?;
141-
142-
// Parse C
143-
let c = parts[1].parse::<f64>().ok()?;
144-
145-
// Parse H
146-
let h = parts[2].parse::<f64>().ok()?;
147-
148-
Some((l, c, h))
149-
}
150-
}
151-
152-
/// Convert RGB to OKLCH color space
153-
/// This is a simplified approximation
154-
fn rgb_to_oklch(r: u8, g: u8, b: u8) -> (f64, f64, f64) {
155-
// First convert to linear RGB
156-
let r_linear = srgb_to_linear(r as f64 / 255.0);
157-
let g_linear = srgb_to_linear(g as f64 / 255.0);
158-
let b_linear = srgb_to_linear(b as f64 / 255.0);
159-
160-
// Convert to OKLab using the matrix transformation
161-
let l = 0.4122214708 * r_linear + 0.5363325363 * g_linear + 0.0514459929 * b_linear;
162-
let m = 0.2119034982 * r_linear + 0.6806995451 * g_linear + 0.1073969566 * b_linear;
163-
let s = 0.0883024619 * r_linear + 0.2817188376 * g_linear + 0.6299787005 * b_linear;
164-
165-
let l_ = l.cbrt();
166-
let m_ = m.cbrt();
167-
let s_ = s.cbrt();
168-
169-
let l_oklab = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_;
170-
let a_oklab = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_;
171-
let b_oklab = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_;
172-
173-
// Convert OKLab to OKLCH
174-
let l_oklch = l_oklab * 100.0; // Convert to percentage
175-
let c_oklch = (a_oklab * a_oklab + b_oklab * b_oklab).sqrt();
176-
let h_oklch = b_oklab.atan2(a_oklab).to_degrees();
177-
let h_oklch = if h_oklch < 0.0 {
178-
h_oklch + 360.0
179-
} else {
180-
h_oklch
181-
};
182-
183-
(l_oklch, c_oklch, h_oklch)
184-
}
185-
186-
/// Convert sRGB to linear RGB
187-
fn srgb_to_linear(c: f64) -> f64 {
188-
if c <= 0.04045 {
189-
c / 12.92
190-
} else {
191-
((c + 0.055) / 1.055).powf(2.4)
192-
}
19376
}
19477

19578
#[cfg(test)]
@@ -204,40 +87,9 @@ mod tests {
20487
b: 200,
20588
};
20689

207-
// Exact match
20890
assert!(quiz.validate_answer("#c8c8c8"));
209-
210-
// Within tolerance (diff = 10)
21191
assert!(quiz.validate_answer("#bec8ca"));
212-
213-
// At boundary (diff = 20)
214-
assert!(quiz.validate_answer("#b4c8c8"));
215-
216-
// Outside tolerance (diff = 21)
217-
assert!(!quiz.validate_answer("#b3c8c8"));
218-
}
219-
220-
#[test]
221-
fn test_oklch_parsing() {
222-
let quiz = ColorQuiz {
223-
r: 200,
224-
g: 200,
225-
b: 200,
226-
};
227-
228-
// Test various formats
229-
assert!(quiz.parse_oklch("oklch(45.0% 0.306 65.4)").is_some());
230-
assert!(quiz.parse_oklch("45.0% 0.306 65.4").is_some());
231-
assert!(quiz.parse_oklch("45.0%, 0.306, 65.4").is_some());
232-
assert!(quiz.parse_oklch("45 0.306 65.4").is_some());
233-
}
234-
235-
#[test]
236-
fn test_rgb_to_oklch() {
237-
// Test with a known color (approximate values)
238-
let (l, c, h) = rgb_to_oklch(255, 0, 0); // Red
239-
assert!(l > 60.0 && l < 70.0); // Lightness around 62-63%
240-
assert!(c > 0.25); // High chroma
241-
assert!(h > 20.0 && h < 40.0); // Hue around 29 degrees
92+
assert!(quiz.validate_answer("#b3c8c8"));
93+
assert!(!quiz.validate_answer("#aec8c8"));
24294
}
24395
}

0 commit comments

Comments
 (0)