1#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum Color {
6 Rgb(f64, f64, f64),
8 Gray(f64),
10 Cmyk(f64, f64, f64, f64),
12}
13
14impl Color {
15 pub fn rgb(r: f64, g: f64, b: f64) -> Self {
17 Color::Rgb(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0))
18 }
19
20 pub fn gray(value: f64) -> Self {
22 Color::Gray(value.clamp(0.0, 1.0))
23 }
24
25 pub fn cmyk(c: f64, m: f64, y: f64, k: f64) -> Self {
27 Color::Cmyk(
28 c.clamp(0.0, 1.0),
29 m.clamp(0.0, 1.0),
30 y.clamp(0.0, 1.0),
31 k.clamp(0.0, 1.0),
32 )
33 }
34
35 pub fn black() -> Self {
37 Color::Gray(0.0)
38 }
39
40 pub fn white() -> Self {
42 Color::Gray(1.0)
43 }
44
45 pub fn red() -> Self {
47 Color::Rgb(1.0, 0.0, 0.0)
48 }
49
50 pub fn green() -> Self {
52 Color::Rgb(0.0, 1.0, 0.0)
53 }
54
55 pub fn blue() -> Self {
57 Color::Rgb(0.0, 0.0, 1.0)
58 }
59
60 pub fn yellow() -> Self {
61 Color::Rgb(1.0, 1.0, 0.0)
62 }
63
64 pub fn cyan() -> Self {
65 Color::Rgb(0.0, 1.0, 1.0)
66 }
67
68 pub fn magenta() -> Self {
69 Color::Rgb(1.0, 0.0, 1.0)
70 }
71
72 pub fn cmyk_cyan() -> Self {
74 Color::Cmyk(1.0, 0.0, 0.0, 0.0)
75 }
76
77 pub fn cmyk_magenta() -> Self {
79 Color::Cmyk(0.0, 1.0, 0.0, 0.0)
80 }
81
82 pub fn cmyk_yellow() -> Self {
84 Color::Cmyk(0.0, 0.0, 1.0, 0.0)
85 }
86
87 pub fn cmyk_black() -> Self {
89 Color::Cmyk(0.0, 0.0, 0.0, 1.0)
90 }
91
92 pub fn r(&self) -> f64 {
94 match self {
95 Color::Rgb(r, _, _) => *r,
96 Color::Gray(g) => *g,
97 Color::Cmyk(c, _, _, k) => (1.0 - c) * (1.0 - k),
98 }
99 }
100
101 pub fn g(&self) -> f64 {
103 match self {
104 Color::Rgb(_, g, _) => *g,
105 Color::Gray(g) => *g,
106 Color::Cmyk(_, m, _, k) => (1.0 - m) * (1.0 - k),
107 }
108 }
109
110 pub fn b(&self) -> f64 {
112 match self {
113 Color::Rgb(_, _, b) => *b,
114 Color::Gray(g) => *g,
115 Color::Cmyk(_, _, y, k) => (1.0 - y) * (1.0 - k),
116 }
117 }
118
119 pub fn cmyk_components(&self) -> (f64, f64, f64, f64) {
121 match self {
122 Color::Cmyk(c, m, y, k) => (*c, *m, *y, *k),
123 Color::Rgb(r, g, b) => {
124 let k = 1.0 - r.max(*g).max(*b);
126 if k >= 1.0 {
127 (0.0, 0.0, 0.0, 1.0)
128 } else {
129 let c = (1.0 - r - k) / (1.0 - k);
130 let m = (1.0 - g - k) / (1.0 - k);
131 let y = (1.0 - b - k) / (1.0 - k);
132 (c, m, y, k)
133 }
134 }
135 Color::Gray(g) => {
136 let k = 1.0 - g;
138 (0.0, 0.0, 0.0, k)
139 }
140 }
141 }
142
143 pub fn to_rgb(&self) -> Color {
145 match self {
146 Color::Rgb(_, _, _) => *self,
147 Color::Gray(g) => Color::Rgb(*g, *g, *g),
148 Color::Cmyk(c, m, y, k) => {
149 let r = (1.0 - c) * (1.0 - k);
151 let g = (1.0 - m) * (1.0 - k);
152 let b = (1.0 - y) * (1.0 - k);
153 Color::Rgb(r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0))
154 }
155 }
156 }
157
158 pub fn to_cmyk(&self) -> Color {
160 match self {
161 Color::Cmyk(_, _, _, _) => *self,
162 _ => {
163 let (c, m, y, k) = self.cmyk_components();
164 Color::Cmyk(c, m, y, k)
165 }
166 }
167 }
168
169 pub fn color_space_name(&self) -> &'static str {
171 match self {
172 Color::Gray(_) => "DeviceGray",
173 Color::Rgb(_, _, _) => "DeviceRGB",
174 Color::Cmyk(_, _, _, _) => "DeviceCMYK",
175 }
176 }
177
178 pub fn is_cmyk(&self) -> bool {
180 matches!(self, Color::Cmyk(_, _, _, _))
181 }
182
183 pub fn is_rgb(&self) -> bool {
185 matches!(self, Color::Rgb(_, _, _))
186 }
187
188 pub fn is_gray(&self) -> bool {
190 matches!(self, Color::Gray(_))
191 }
192
193 pub fn to_pdf_array(&self) -> crate::objects::Object {
195 use crate::objects::Object;
196 match self {
197 Color::Gray(g) => Object::Array(vec![Object::Real(*g)]),
198 Color::Rgb(r, g, b) => {
199 Object::Array(vec![Object::Real(*r), Object::Real(*g), Object::Real(*b)])
200 }
201 Color::Cmyk(c, m, y, k) => Object::Array(vec![
202 Object::Real(*c),
203 Object::Real(*m),
204 Object::Real(*y),
205 Object::Real(*k),
206 ]),
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_rgb_color_creation() {
217 let color = Color::rgb(0.5, 0.7, 0.3);
218 assert_eq!(color, Color::Rgb(0.5, 0.7, 0.3));
219 }
220
221 #[test]
222 fn test_rgb_color_clamping() {
223 let color = Color::rgb(1.5, -0.3, 0.5);
224 assert_eq!(color, Color::Rgb(1.0, 0.0, 0.5));
225 }
226
227 #[test]
228 fn test_gray_color_creation() {
229 let color = Color::gray(0.5);
230 assert_eq!(color, Color::Gray(0.5));
231 }
232
233 #[test]
234 fn test_gray_color_clamping() {
235 let color1 = Color::gray(1.5);
236 assert_eq!(color1, Color::Gray(1.0));
237
238 let color2 = Color::gray(-0.5);
239 assert_eq!(color2, Color::Gray(0.0));
240 }
241
242 #[test]
243 fn test_cmyk_color_creation() {
244 let color = Color::cmyk(0.1, 0.2, 0.3, 0.4);
245 assert_eq!(color, Color::Cmyk(0.1, 0.2, 0.3, 0.4));
246 }
247
248 #[test]
249 fn test_cmyk_color_clamping() {
250 let color = Color::cmyk(1.5, -0.2, 0.5, 2.0);
251 assert_eq!(color, Color::Cmyk(1.0, 0.0, 0.5, 1.0));
252 }
253
254 #[test]
255 fn test_predefined_colors() {
256 assert_eq!(Color::black(), Color::Gray(0.0));
257 assert_eq!(Color::white(), Color::Gray(1.0));
258 assert_eq!(Color::red(), Color::Rgb(1.0, 0.0, 0.0));
259 assert_eq!(Color::green(), Color::Rgb(0.0, 1.0, 0.0));
260 assert_eq!(Color::blue(), Color::Rgb(0.0, 0.0, 1.0));
261 assert_eq!(Color::yellow(), Color::Rgb(1.0, 1.0, 0.0));
262 assert_eq!(Color::cyan(), Color::Rgb(0.0, 1.0, 1.0));
263 assert_eq!(Color::magenta(), Color::Rgb(1.0, 0.0, 1.0));
264 }
265
266 #[test]
267 fn test_color_equality() {
268 let color1 = Color::rgb(0.5, 0.5, 0.5);
269 let color2 = Color::rgb(0.5, 0.5, 0.5);
270 let color3 = Color::rgb(0.5, 0.5, 0.6);
271
272 assert_eq!(color1, color2);
273 assert_ne!(color1, color3);
274
275 let gray1 = Color::gray(0.5);
276 let gray2 = Color::gray(0.5);
277 assert_eq!(gray1, gray2);
278
279 let cmyk1 = Color::cmyk(0.1, 0.2, 0.3, 0.4);
280 let cmyk2 = Color::cmyk(0.1, 0.2, 0.3, 0.4);
281 assert_eq!(cmyk1, cmyk2);
282 }
283
284 #[test]
285 fn test_color_different_types_inequality() {
286 let rgb = Color::rgb(0.5, 0.5, 0.5);
287 let gray = Color::gray(0.5);
288 let cmyk = Color::cmyk(0.5, 0.5, 0.5, 0.5);
289
290 assert_ne!(rgb, gray);
291 assert_ne!(rgb, cmyk);
292 assert_ne!(gray, cmyk);
293 }
294
295 #[test]
296 fn test_color_debug() {
297 let rgb = Color::rgb(0.1, 0.2, 0.3);
298 let debug_str = format!("{rgb:?}");
299 assert!(debug_str.contains("Rgb"));
300 assert!(debug_str.contains("0.1"));
301 assert!(debug_str.contains("0.2"));
302 assert!(debug_str.contains("0.3"));
303
304 let gray = Color::gray(0.5);
305 let gray_debug = format!("{gray:?}");
306 assert!(gray_debug.contains("Gray"));
307 assert!(gray_debug.contains("0.5"));
308
309 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
310 let cmyk_debug = format!("{cmyk:?}");
311 assert!(cmyk_debug.contains("Cmyk"));
312 assert!(cmyk_debug.contains("0.1"));
313 assert!(cmyk_debug.contains("0.2"));
314 assert!(cmyk_debug.contains("0.3"));
315 assert!(cmyk_debug.contains("0.4"));
316 }
317
318 #[test]
319 fn test_color_clone() {
320 let rgb = Color::rgb(0.5, 0.6, 0.7);
321 let rgb_clone = rgb;
322 assert_eq!(rgb, rgb_clone);
323
324 let gray = Color::gray(0.5);
325 let gray_clone = gray;
326 assert_eq!(gray, gray_clone);
327
328 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
329 let cmyk_clone = cmyk;
330 assert_eq!(cmyk, cmyk_clone);
331 }
332
333 #[test]
334 fn test_color_copy() {
335 let rgb = Color::rgb(0.5, 0.6, 0.7);
336 let rgb_copy = rgb; assert_eq!(rgb, rgb_copy);
338
339 assert_eq!(rgb, Color::Rgb(0.5, 0.6, 0.7));
341 assert_eq!(rgb_copy, Color::Rgb(0.5, 0.6, 0.7));
342 }
343
344 #[test]
345 fn test_edge_case_values() {
346 let color = Color::rgb(0.0, 0.5, 1.0);
348 assert_eq!(color, Color::Rgb(0.0, 0.5, 1.0));
349
350 let gray = Color::gray(0.0);
351 assert_eq!(gray, Color::Gray(0.0));
352
353 let gray_max = Color::gray(1.0);
354 assert_eq!(gray_max, Color::Gray(1.0));
355
356 let cmyk = Color::cmyk(0.0, 0.0, 0.0, 0.0);
357 assert_eq!(cmyk, Color::Cmyk(0.0, 0.0, 0.0, 0.0));
358
359 let cmyk_max = Color::cmyk(1.0, 1.0, 1.0, 1.0);
360 assert_eq!(cmyk_max, Color::Cmyk(1.0, 1.0, 1.0, 1.0));
361 }
362
363 #[test]
364 fn test_floating_point_precision() {
365 let color = Color::rgb(0.333333333, 0.666666666, 0.999999999);
366 match color {
367 Color::Rgb(r, g, b) => {
368 assert!((r - 0.333333333).abs() < 1e-9);
369 assert!((g - 0.666666666).abs() < 1e-9);
370 assert!((b - 0.999999999).abs() < 1e-9);
371 }
372 _ => panic!("Expected RGB color"),
373 }
374 }
375
376 #[test]
377 fn test_rgb_clamping_infinity() {
378 let inf_color = Color::rgb(f64::INFINITY, f64::NEG_INFINITY, 0.5);
380 assert_eq!(inf_color, Color::Rgb(1.0, 0.0, 0.5));
381
382 let large_color = Color::rgb(1000.0, -1000.0, 0.5);
384 assert_eq!(large_color, Color::Rgb(1.0, 0.0, 0.5));
385 }
386
387 #[test]
388 fn test_cmyk_all_components() {
389 let cmyk = Color::cmyk(0.1, 0.2, 0.3, 0.4);
391 match cmyk {
392 Color::Cmyk(c, m, y, k) => {
393 assert_eq!(c, 0.1);
394 assert_eq!(m, 0.2);
395 assert_eq!(y, 0.3);
396 assert_eq!(k, 0.4);
397 }
398 _ => panic!("Expected CMYK color"),
399 }
400 }
401
402 #[test]
403 fn test_pattern_matching() {
404 let colors = vec![
405 Color::rgb(0.5, 0.5, 0.5),
406 Color::gray(0.5),
407 Color::cmyk(0.1, 0.2, 0.3, 0.4),
408 ];
409
410 let mut rgb_count = 0;
411 let mut gray_count = 0;
412 let mut cmyk_count = 0;
413
414 for color in colors {
415 match color {
416 Color::Rgb(_, _, _) => rgb_count += 1,
417 Color::Gray(_) => gray_count += 1,
418 Color::Cmyk(_, _, _, _) => cmyk_count += 1,
419 }
420 }
421
422 assert_eq!(rgb_count, 1);
423 assert_eq!(gray_count, 1);
424 assert_eq!(cmyk_count, 1);
425 }
426
427 #[test]
428 fn test_cmyk_pure_colors() {
429 assert_eq!(Color::cmyk_cyan(), Color::Cmyk(1.0, 0.0, 0.0, 0.0));
431 assert_eq!(Color::cmyk_magenta(), Color::Cmyk(0.0, 1.0, 0.0, 0.0));
432 assert_eq!(Color::cmyk_yellow(), Color::Cmyk(0.0, 0.0, 1.0, 0.0));
433 assert_eq!(Color::cmyk_black(), Color::Cmyk(0.0, 0.0, 0.0, 1.0));
434 }
435
436 #[test]
437 fn test_cmyk_to_rgb_conversion() {
438 let pure_cyan = Color::cmyk_cyan().to_rgb();
440 match pure_cyan {
441 Color::Rgb(r, g, b) => {
442 assert_eq!(r, 0.0);
443 assert_eq!(g, 1.0);
444 assert_eq!(b, 1.0);
445 }
446 _ => panic!("Expected RGB color"),
447 }
448
449 let pure_magenta = Color::cmyk_magenta().to_rgb();
450 match pure_magenta {
451 Color::Rgb(r, g, b) => {
452 assert_eq!(r, 1.0);
453 assert_eq!(g, 0.0);
454 assert_eq!(b, 1.0);
455 }
456 _ => panic!("Expected RGB color"),
457 }
458
459 let pure_yellow = Color::cmyk_yellow().to_rgb();
460 match pure_yellow {
461 Color::Rgb(r, g, b) => {
462 assert_eq!(r, 1.0);
463 assert_eq!(g, 1.0);
464 assert_eq!(b, 0.0);
465 }
466 _ => panic!("Expected RGB color"),
467 }
468
469 let pure_black = Color::cmyk_black().to_rgb();
470 match pure_black {
471 Color::Rgb(r, g, b) => {
472 assert_eq!(r, 0.0);
473 assert_eq!(g, 0.0);
474 assert_eq!(b, 0.0);
475 }
476 _ => panic!("Expected RGB color"),
477 }
478 }
479
480 #[test]
481 fn test_rgb_to_cmyk_conversion() {
482 let red = Color::red().to_cmyk();
484 let (c, m, y, k) = red.cmyk_components();
485 assert_eq!(c, 0.0);
486 assert_eq!(m, 1.0);
487 assert_eq!(y, 1.0);
488 assert_eq!(k, 0.0);
489
490 let green = Color::green().to_cmyk();
491 let (c, m, y, k) = green.cmyk_components();
492 assert_eq!(c, 1.0);
493 assert_eq!(m, 0.0);
494 assert_eq!(y, 1.0);
495 assert_eq!(k, 0.0);
496
497 let blue = Color::blue().to_cmyk();
498 let (c, m, y, k) = blue.cmyk_components();
499 assert_eq!(c, 1.0);
500 assert_eq!(m, 1.0);
501 assert_eq!(y, 0.0);
502 assert_eq!(k, 0.0);
503
504 let black = Color::black().to_cmyk();
505 let (c, m, y, k) = black.cmyk_components();
506 assert_eq!(c, 0.0);
507 assert_eq!(m, 0.0);
508 assert_eq!(y, 0.0);
509 assert_eq!(k, 1.0);
510 }
511
512 #[test]
513 fn test_color_space_detection() {
514 assert!(Color::rgb(0.5, 0.5, 0.5).is_rgb());
515 assert!(!Color::rgb(0.5, 0.5, 0.5).is_cmyk());
516 assert!(!Color::rgb(0.5, 0.5, 0.5).is_gray());
517
518 assert!(Color::gray(0.5).is_gray());
519 assert!(!Color::gray(0.5).is_rgb());
520 assert!(!Color::gray(0.5).is_cmyk());
521
522 assert!(Color::cmyk(0.1, 0.2, 0.3, 0.4).is_cmyk());
523 assert!(!Color::cmyk(0.1, 0.2, 0.3, 0.4).is_rgb());
524 assert!(!Color::cmyk(0.1, 0.2, 0.3, 0.4).is_gray());
525 }
526
527 #[test]
528 fn test_color_space_names() {
529 assert_eq!(Color::rgb(0.5, 0.5, 0.5).color_space_name(), "DeviceRGB");
530 assert_eq!(Color::gray(0.5).color_space_name(), "DeviceGray");
531 assert_eq!(
532 Color::cmyk(0.1, 0.2, 0.3, 0.4).color_space_name(),
533 "DeviceCMYK"
534 );
535 }
536
537 #[test]
538 fn test_cmyk_components_extraction() {
539 let cmyk_color = Color::cmyk(0.1, 0.2, 0.3, 0.4);
540 let (c, m, y, k) = cmyk_color.cmyk_components();
541 assert_eq!(c, 0.1);
542 assert_eq!(m, 0.2);
543 assert_eq!(y, 0.3);
544 assert_eq!(k, 0.4);
545
546 let white = Color::white();
548 let (c, m, y, k) = white.cmyk_components();
549 assert_eq!(c, 0.0);
550 assert_eq!(m, 0.0);
551 assert_eq!(y, 0.0);
552 assert_eq!(k, 0.0);
553 }
554
555 #[test]
556 fn test_roundtrip_conversions() {
557 let original_rgb = Color::rgb(0.6, 0.3, 0.9);
559 let converted_cmyk = original_rgb.to_cmyk();
560 let back_to_rgb = converted_cmyk.to_rgb();
561
562 let orig_components = (original_rgb.r(), original_rgb.g(), original_rgb.b());
563 let final_components = (back_to_rgb.r(), back_to_rgb.g(), back_to_rgb.b());
564
565 assert!((orig_components.0 - final_components.0).abs() < 0.001);
567 assert!((orig_components.1 - final_components.1).abs() < 0.001);
568 assert!((orig_components.2 - final_components.2).abs() < 0.001);
569 }
570
571 #[test]
572 fn test_grayscale_to_cmyk_conversion() {
573 let gray = Color::gray(0.7);
574 let (c, m, y, k) = gray.cmyk_components();
575
576 assert_eq!(c, 0.0);
577 assert_eq!(m, 0.0);
578 assert_eq!(y, 0.0);
579 assert!((k - 0.3).abs() < 1e-10); let gray_as_cmyk = gray.to_cmyk();
582 let cmyk_components = gray_as_cmyk.cmyk_components();
583 assert_eq!(cmyk_components.0, 0.0);
584 assert_eq!(cmyk_components.1, 0.0);
585 assert_eq!(cmyk_components.2, 0.0);
586 assert!((cmyk_components.3 - 0.3).abs() < 1e-10);
587 }
588}