1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
//! Edge case tests for MunsellSpace library.
//!
//! These tests specifically target boundary conditions, extreme values,
//! and corner cases that might not be covered by regular unit tests.
#[cfg(test)]
#[allow(deprecated)] // Tests may use deprecated APIs for backward compatibility verification
mod edge_case_tests {
use crate::{MunsellConverter, MunsellColor, RgbColor, MunsellError};
use crate::mathematical::MathematicalMunsellConverter;
use crate::iscc::IsccNbsClassifier;
/// Test extreme RGB values at color space boundaries
#[test]
fn test_rgb_boundary_values() {
let converter = MunsellConverter::new().unwrap();
// Test all corners of RGB cube
let corners = [
[0, 0, 0], // Black
[255, 255, 255], // White
[255, 0, 0], // Pure Red
[0, 255, 0], // Pure Green
[0, 0, 255], // Pure Blue
[255, 255, 0], // Yellow
[255, 0, 255], // Magenta
[0, 255, 255], // Cyan
];
for &rgb in &corners {
let result = converter.srgb_to_munsell(rgb);
assert!(result.is_ok(), "Failed to convert corner RGB{:?}: {:?}", rgb, result);
let munsell = result.unwrap();
// Basic sanity checks
assert!(munsell.value >= 0.0 && munsell.value <= 10.0,
"Value out of range for RGB{:?}: {}", rgb, munsell.value);
if let Some(chroma) = munsell.chroma {
assert!(chroma >= 0.0, "Negative chroma for RGB{:?}: {}", rgb, chroma);
}
// Notation should parse successfully
assert!(MunsellColor::from_notation(&munsell.notation).is_ok(),
"Generated invalid notation for RGB{:?}: '{}'", rgb, munsell.notation);
}
}
/// Test near-boundary RGB values that might cause precision issues
#[test]
fn test_near_boundary_rgb_precision() {
let converter = MunsellConverter::new().unwrap();
let near_boundary_values = [
[1, 1, 1], // Nearly black
[254, 254, 254], // Nearly white
[1, 0, 0], // Nearly pure red
[0, 1, 0], // Nearly pure green
[0, 0, 1], // Nearly pure blue
[254, 255, 255], // Nearly cyan
[255, 254, 255], // Nearly magenta
[255, 255, 254], // Nearly yellow
];
for &rgb in &near_boundary_values {
let result = converter.srgb_to_munsell(rgb);
assert!(result.is_ok(), "Failed to convert near-boundary RGB{:?}: {:?}", rgb, result);
let munsell = result.unwrap();
// Check for reasonable bounds (no NaN/Inf)
assert!(munsell.value.is_finite(), "Non-finite value for RGB{:?}: {}", rgb, munsell.value);
if let Some(chroma) = munsell.chroma {
assert!(chroma.is_finite(), "Non-finite chroma for RGB{:?}: {}", rgb, chroma);
assert!(chroma >= 0.0, "Negative chroma for RGB{:?}: {}", rgb, chroma);
}
}
}
/// Test Munsell notation parsing edge cases
#[test]
fn test_munsell_notation_edge_cases() {
// Test various formatting edge cases
let edge_case_notations = [
"N 0.0", // Standard neutral
"N 10.0", // Maximum value neutral
"N 5.5", // Decimal neutral
"10RP 1.0/1.0", // Minimum non-zero values
"0.1R 9.9/0.1", // Very small hue and chroma
"9.9R 0.1/30.0", // High chroma, low value
"5.0Y 5.0/20.0", // High but reasonable chroma
"2.5PB 8.5/12.0", // Complex decimal values
];
for notation in &edge_case_notations {
let result = MunsellColor::from_notation(notation);
match result {
Ok(munsell) => {
// Successful parse - verify consistency
assert!(munsell.value >= 0.0 && munsell.value <= 10.0,
"Parsed value out of range for '{}': {}", notation, munsell.value);
if munsell.is_chromatic() {
assert!(munsell.hue.is_some(), "Chromatic color missing hue: '{}'", notation);
assert!(munsell.chroma.is_some(), "Chromatic color missing chroma: '{}'", notation);
}
// Round-trip test
let regenerated = munsell.notation;
let reparsed = MunsellColor::from_notation(®enerated);
assert!(reparsed.is_ok(), "Round-trip failed for '{}' -> '{}'", notation, regenerated);
}
Err(e) => {
// Failed parse - acceptable for some edge cases
println!("Note: '{}' failed to parse (might be expected): {}", notation, e);
}
}
}
}
/// Test invalid Munsell notation strings
#[test]
fn test_invalid_munsell_notations() {
let invalid_notations = [
"", // Empty string
"X", // Invalid hue family
"N", // Missing value
"N -1.0", // Negative value
"N 11.0", // Value too high
"5R", // Missing value/chroma
"5R 5.0", // Missing chroma separator
"5R 5.0 10.0", // Missing chroma separator
"5R 5.0/-1.0", // Negative chroma
"100R 5.0/10.0", // Hue value too high
"-5R 5.0/10.0", // Negative hue value
"5ABC 5.0/10.0", // Invalid hue family
"N 5.0/10.0", // Neutral with chroma
];
for notation in &invalid_notations {
let result = MunsellColor::from_notation(notation);
assert!(result.is_err(), "Expected error for invalid notation '{}', got: {:?}", notation, result);
// Verify it's the right kind of error
match result.unwrap_err() {
MunsellError::InvalidNotation { .. } => {}, // Expected
other => panic!("Expected InvalidNotation error for '{}', got: {:?}", notation, other),
}
}
}
/// Test RgbColor validation edge cases
#[test]
fn test_rgb_color_validation() {
// Valid RGB values - RgbColor::new() actually returns RgbColor directly, not Result
let valid_colors = [
[0, 0, 0],
[255, 255, 255],
[128, 64, 192],
];
for &rgb in &valid_colors {
let color = RgbColor::new(rgb[0], rgb[1], rgb[2]);
assert_eq!(color.r, rgb[0]);
assert_eq!(color.g, rgb[1]);
assert_eq!(color.b, rgb[2]);
}
}
/// Test ISCC-NBS classification edge cases
#[test]
fn test_iscc_nbs_edge_cases() {
let classifier = IsccNbsClassifier::new().unwrap();
// Test colors at edge of classification ranges
let edge_case_colors = [
([0, 0, 0], "black"),
([255, 255, 255], "white"),
([128, 128, 128], "gray"),
([255, 0, 0], "red"),
([0, 255, 0], "green"),
([0, 0, 255], "blue"),
];
for &(rgb, expected_contains) in &edge_case_colors {
let result = classifier.classify_srgb(rgb);
assert!(result.is_ok(), "Failed to classify RGB{:?}: {:?}", rgb, result);
if let Ok(Some(metadata)) = result {
let descriptor = metadata.iscc_nbs_descriptor();
println!("RGB{:?} -> '{}'", rgb, descriptor);
// Very basic check - descriptor should contain expected color
// (This is lenient as exact matching depends on complex color space mapping)
let descriptor_lower = descriptor.to_lowercase();
if !descriptor_lower.contains(expected_contains) {
println!("Note: RGB{:?} classified as '{}', expected to contain '{}'",
rgb, descriptor, expected_contains);
}
} else {
println!("Note: RGB{:?} not classified in ISCC-NBS system", rgb);
}
}
}
/// Test mathematical converter with edge cases
#[test]
fn test_mathematical_converter_edge_cases() {
let converter = MathematicalMunsellConverter::new().unwrap();
// Test extreme RGB values instead of xyY since we don't have xyy_to_srgb
let extreme_rgb_values = [
[0, 0, 0], // Complete black
[255, 255, 255], // White
[1, 1, 1], // Very dark
[254, 254, 254], // Nearly white
[255, 0, 0], // Pure red
[0, 255, 0], // Pure green
[0, 0, 255], // Pure blue
];
for &rgb in &extreme_rgb_values {
// Test xyY conversion
let xyy_result = converter.srgb_to_xyy(rgb);
match xyy_result {
Ok(xyy) => {
// Should be valid xyY values
assert!(xyy.x >= 0.0 && xyy.x <= 1.0, "xyY x out of range: {}", xyy.x);
assert!(xyy.y >= 0.0 && xyy.y <= 1.0, "xyY y out of range: {}", xyy.y);
assert!(xyy.y_luminance >= 0.0, "xyY Y negative: {}", xyy.y_luminance);
// Test Munsell conversion
let munsell_result = converter.xyy_to_munsell_specification(xyy);
assert!(munsell_result.is_ok(), "Munsell conversion failed for RGB{:?}", rgb);
}
Err(e) => {
// Some extreme values might legitimately fail
println!("Note: RGB{:?} failed xyY conversion: {}", rgb, e);
}
}
}
}
/// Test batch processing with edge cases
#[test]
fn test_batch_processing_edge_cases() {
let converter = MunsellConverter::new().unwrap();
// Empty batch
let empty_batch: Vec<[u8; 3]> = vec![];
let result = converter.convert_batch(&empty_batch);
assert!(result.is_ok(), "Empty batch should succeed");
assert_eq!(result.unwrap().len(), 0);
// Single item batch
let single_batch = vec![[128, 128, 128]];
let result = converter.convert_batch(&single_batch);
assert!(result.is_ok(), "Single item batch should succeed");
assert_eq!(result.unwrap().len(), 1);
// Large batch with duplicates
let large_batch = vec![[255, 0, 0]; 1000]; // 1000 identical red colors
let result = converter.convert_batch(&large_batch);
assert!(result.is_ok(), "Large batch should succeed");
assert_eq!(result.unwrap().len(), 1000);
// Batch with all corners
let corner_batch = vec![
[0, 0, 0], [255, 255, 255], [255, 0, 0], [0, 255, 0],
[0, 0, 255], [255, 255, 0], [255, 0, 255], [0, 255, 255],
];
let result = converter.convert_batch(&corner_batch);
assert!(result.is_ok(), "Corner batch should succeed: {:?}", result);
assert_eq!(result.unwrap().len(), 8);
}
/// Test floating point precision edge cases
#[test]
fn test_floating_point_precision() {
let converter = MathematicalMunsellConverter::new().unwrap();
// Test values near floating point precision limits
let precision_test_values = [
0,
1, // Very small positive
254, // Just under 255
255, // Maximum
128, // Middle value
127, // Just under middle
129, // Just over middle
];
for &test_val in &precision_test_values {
// Use test value in various contexts to check for precision issues
let test_rgb = [test_val, 128, 128];
let result = converter.srgb_to_xyy(test_rgb);
match result {
Ok(xyy) => {
// Check for NaN/Inf
assert!(xyy.x.is_finite(), "Non-finite x for test_val {}: {}", test_val, xyy.x);
assert!(xyy.y.is_finite(), "Non-finite y for test_val {}: {}", test_val, xyy.y);
assert!(xyy.y_luminance.is_finite(), "Non-finite Y for test_val {}: {}", test_val, xyy.y_luminance);
// Check for reasonable bounds
assert!(xyy.x >= 0.0 && xyy.x <= 1.0, "x out of range for test_val {}: {}", test_val, xyy.x);
assert!(xyy.y >= 0.0 && xyy.y <= 1.0, "y out of range for test_val {}: {}", test_val, xyy.y);
assert!(xyy.y_luminance >= 0.0, "Y negative for test_val {}: {}", test_val, xyy.y_luminance);
}
Err(e) => {
println!("Note: Precision test failed for {}: {}", test_val, e);
}
}
}
}
}