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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
//! This file defines the [`Color`] trait, the foundational defining trait of
//! the entire library. Despite the dizzying amount of things [`Color`] can do
//! in Scarlet, especially with its extending traits, the definition is quite
//! simple: anything that can be converted to and from the [CIE 1931 XYZ
//! space](https://en.wikipedia.org/wiki/CIE_1931_color_space). This color space
//! is common to use as a master space, and Scarlet is no different. What makes
//! XYZ unique is that it can be computed directly from the spectral data of a
//! color. Although Scarlet does not implement this due to its scope, this
//! property makes it possible to derive XYZ colors from real-world data,
//! something that no other color space can do the same way.
//!
//! The thing that makes [`XYZColor`], the base implementation of the CIE 1931
//! XYZ space, special is that it is the only color object in Scarlet that keeps
//! track of its own illuminant data. Every other color space assumes a viewing
//! environment, but because XYZ color maps directly to neural perception it
//! keeps track of what environment the color is being viewed in. This allows
//! Scarlet to translate between color spaces that have different assumptions
//! seamlessly. (If you notice that Scarlet's values for conversions differ from
//! other sources, this may be why: some sources don't do this properly or
//! implement it differently. Scarlet generally follows best practices and
//! industry standards, but file an issue if you feel this is not true.) The
//! essential workflow of [`Color`], and therefore Scarlet, is generally like
//! this: convert between different color spaces using the generic [`convert<T:
//! Color>()`](trait.Color.html#method.convert) method, which allows any
//! [`Color`] to be interconverted to any other representation. Leverage the
//! specific attributes of each color space if need be (for example, using the
//! hue or luminance attributes), and then convert back to a suitable display
//! space. The many other methods of [`Color`] make some of the more common such
//! patterns simple to do.
//!

use std::collections::HashMap;
use std::convert::From;
use std::error::Error;
use std::fmt;
use std::marker::Sized;
use std::num::ParseIntError;
use std::result::Result::Err;
use std::str::FromStr;
use std::string::ToString;

use super::coord::Coord;
use colors::cielabcolor::CIELABColor;
use colors::cielchcolor::CIELCHColor;
use consts;
use consts::BRADFORD_TRANSFORM as BRADFORD;
use consts::BRADFORD_TRANSFORM_LU as BRADFORD_LU;
use consts::STANDARD_RGB_TRANSFORM as SRGB;
use consts::STANDARD_RGB_TRANSFORM_LU as SRGB_LU;
use csscolor::{parse_rgb_str, CSSParseError};
use illuminants::Illuminant;

use nalgebra::base::Vector;
use nalgebra::vector;

#[cfg(feature = "terminal")]
use termion::color::{Bg, Fg, Reset, Rgb};

/// A point in the CIE 1931 XYZ color space. Although any point in XYZ coordinate space is technically
/// valid, in this library XYZ colors are treated as normalized so that Y=1 is the white point of
/// whatever illuminant is being worked with.
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct XYZColor {
    /// The X axis of the CIE 1931 XYZ space, roughly representing the long-wavelength receptors in
    /// the human eye: the red receptors. Usually between 0 and 1, but can range more than that.
    pub x: f64,
    /// The Y axis of the CIE 1931 XYZ space, roughly representing the middle-wavelength receptors in
    /// the human eye. In CIE 1931, this is fudged a little to correspond exactly with perceived
    /// luminance, so while this doesn't exactly map to middle-wavelength receptors it has a far more
    /// useful analogue.
    pub y: f64,
    /// The Z axis of the CIE 1931 XYZ space, roughly representing the short-wavelength receptors in
    /// the human eye. Usually between 0 and 1, but can range more than that.
    pub z: f64,
    /// The illuminant that is assumed to be the lighting environment for this color. Although XYZ
    /// itself describes the human response to a color and so is independent of lighting, it is
    /// useful to consider the question "how would an object in one light look different in
    /// another?" and so, to contain all the information needed to track this, the illuminant is
    /// set. See the [`color_adapt()`] method to examine how this is used in the wild.
    ///
    /// [`color_adapt()`]: #method.color_adapt
    pub illuminant: Illuminant,
}

impl XYZColor {
    /// Converts from one illuminant to a different one, such that a human receiving both sets of
    /// sensory stimuli in the corresponding lighting conditions would perceive an object with that
    /// color as not having changed. This process, called [*chromatic
    /// adaptation*](https://en.wikipedia.org/wiki/Chromatic_adaptation), happens subconsciously all
    /// the time: when someone walks into the shade, we don't interpret that shift as their face
    /// turning blue. This process is not at all simple to compute, however, and many different
    /// algorithms for doing so exist: it is most likely that each person has their own idiosyncrasies
    /// with chromatic adaptation and so there is no perfect solution. Scarlet implements the
    /// *Bradford transform*, which is generally acknowledged to be one of the leading chromatic
    /// adaptation transforms. Nonetheless, for exact color science work other models are more
    /// appropriate, such as CIECAM02 if you can measure viewing conditions exactly. This transform
    /// may not give very good results when used with custom illuminants that wildly differ, but with
    /// the standard illuminants it does a very good job.
    /// # Example: The Fabled Dress
    /// The most accessible way of describing color transformation is to take a look at [this
    /// image](https://upload.wikimedia.org/wikipedia/en/a/a8/The_Dress_%28viral_phenomenon%29.png),
    /// otherwise known as "the dress". This showcases in a very apparent fashion the problems with
    /// very ambiguous lighting in chromatic adaptation: the photo is cropped to the point that some
    /// of the population perceives it to be in deep shade and for the dress to therefore be white and
    /// gold, while others perceive instead harsh sunlight and therefore perceive it as black and
    /// blue. (For reference, it is actually black and blue.) Scarlet can help us answer the question
    /// "how would this look to an observer with either judgment about the lighting conditions?"
    /// without needing the human eye!
    /// First, we use a photo editor to pick out two colors that represent both colors of the
    /// dress. Then, we'll change the illuminant directly (without using chromatic adaptation, because
    /// we want to actually change the color), and then we'll adapt back to D65 to represent on a
    /// screen the different colors.
    ///
    /// ```rust
    /// # use scarlet::prelude::*;
    /// let dress_bg = RGBColor::from_hex_code("#7d6e47").unwrap().to_xyz(Illuminant::D65);
    /// let dress_fg = RGBColor::from_hex_code("#9aabd6").unwrap().to_xyz(Illuminant::D65);
    /// // proposed sunlight illuminant: daylight in North America
    /// // We could exaggerate the effect by creating an illuminant with greater Y value at the white
    /// // point, but this will do
    /// let sunlight = Illuminant::D50;
    /// // proposed "shade" illuminant: created by picking the brightest point on the dress without
    /// // glare subjectively, and then treating that as white
    /// let shade_white = RGBColor::from_hex_code("#b0c5e4").unwrap().to_xyz(Illuminant::D65);
    /// let shade = Illuminant::Custom([shade_white.x, shade_white.y, shade_white.z]);
    /// // make copies of the colors and set illuminants
    /// let mut black = dress_bg;
    /// let mut blue = dress_fg;
    /// let mut gold = dress_bg;
    /// let mut white = dress_fg;
    /// black.illuminant = sunlight;
    /// blue.illuminant = sunlight;
    /// gold.illuminant = shade;
    /// white.illuminant = shade;
    /// // we can just print them out now: the chromatic adaptation is done automatically to get back
    /// // to the color space of the viewing monitor. This isn't exact, mostly because the shade
    /// // illuminant is entirely fudged, but it's surprisingly good
    /// let black_rgb: RGBColor = black.convert();
    /// let blue_rgb: RGBColor = blue.convert();
    /// let gold_rgb: RGBColor = gold.convert();
    /// let white_rgb: RGBColor = white.convert();
    /// println!("Black: {} Blue: {}", black_rgb.to_string(), blue_rgb.to_string());
    /// println!("Gold: {}, White: {}", gold_rgb.to_string(), white_rgb.to_string());
    /// ```
    pub fn color_adapt(&self, other_illuminant: Illuminant) -> XYZColor {
        // no need to transform if same illuminant
        if other_illuminant == self.illuminant {
            *self
        } else {
            // convert to Bradford RGB space
            // &* needed because lazy_static uses a different type which implements Deref
            let rgb = *BRADFORD * vector![self.x, self.y, self.z];

            // get the RGB values for the white point of the illuminant we are currently using and
            // the one we want: wr here stands for "white reference", i.e., the one we're converting
            // to
            let rgb_w = *BRADFORD * Vector::from(self.illuminant.white_point().to_vec());
            let rgb_wr = *BRADFORD * Vector::from(other_illuminant.white_point().to_vec());

            // perform the transform
            // this usually includes a parameter indicating how much you want to adapt, but it's
            // assumed that we want total adaptation: D = 1. Maybe this could change someday?

            // because each white point has already been normalized to Y = 1, we don't need ap
            // factor for it, which simplifies calculation even more than setting D = 1 and makes it
            // just a linear transform
            // scale by the ratio of luminance: it should always be 1, but with rounding error it
            // isn't
            let r_c = rgb[0] * rgb_wr[0] / rgb_w[0];
            let g_c = rgb[1] * rgb_wr[1] / rgb_w[1];
            // there's a slight nonlinearity here that I will omit
            let b_c = rgb[2] * rgb_wr[2] / rgb_w[2];
            // convert back to XYZ using inverse of previous matrix

            // using LU decomposition for accuracy
            let xyz_c = BRADFORD_LU
                .solve(&vector![r_c, g_c, b_c])
                .expect("Matrix is invertible.");
            XYZColor {
                x: xyz_c[0],
                y: xyz_c[1],
                z: xyz_c[2],
                illuminant: other_illuminant,
            }
        }
    }
    /// Returns `true` if the given other XYZ color's coordinates are all within acceptable error of
    /// each other, which helps account for necessary floating-point errors in conversions. To test
    /// whether two colors are indistinguishable to humans, use instead
    /// [`Color::visually_indistinguishable`].
    /// # Example
    ///
    /// ```
    /// # use scarlet::color::XYZColor;
    /// # use scarlet::illuminants::Illuminant;
    /// let xyz1 = XYZColor{x: 0.3, y: 0., z: 0., illuminant: Illuminant::D65};
    /// // note that the difference in illuminant won't be taken into account
    /// let xyz2 = XYZColor{x: 0.1 + 0.1 + 0.1, y: 0., z: 0., illuminant: Illuminant::D55};
    /// // note that because of rounding error these aren't exactly equal!
    /// assert!(xyz1.x != xyz2.x);
    /// // using approx_equal, we can avoid these sorts of errors
    /// assert!(xyz1.approx_equal(&xyz2));
    /// ```
    ///
    /// [`Color::visually_indistinguishable`]: ../color/trait.Color.html#method.visually_indistinguishable
    pub fn approx_equal(&self, other: &XYZColor) -> bool {
        (self.x - other.x).abs() <= 1e-15
            && (self.y - other.y).abs() <= 1e-15
            && (self.z - other.z).abs() <= 1e-15
    }

    /// Returns `true` if the given other XYZ color would look identically in a different color
    /// space. Uses an approximate float equality that helps resolve errors due to floating-point
    /// representation, only testing if the two floats are within 0.001 of each other.
    /// # Example
    ///
    /// ```
    /// # use scarlet::color::XYZColor;
    /// # use scarlet::illuminants::Illuminant;
    /// assert!(XYZColor::white_point(Illuminant::D65).approx_visually_equal(&XYZColor::white_point(Illuminant::D50)));
    /// ```
    pub fn approx_visually_equal(&self, other: &XYZColor) -> bool {
        let other_c = other.color_adapt(self.illuminant);
        self.approx_equal(&other_c)
    }
    /// Gets the XYZColor corresponding to pure white in the given light environment.
    /// # Example
    ///
    /// ```
    /// # use scarlet::color::XYZColor;
    /// # use scarlet::illuminants::Illuminant;
    /// let white1 = XYZColor::white_point(Illuminant::D65);
    /// let white2 = XYZColor::white_point(Illuminant::D50);
    /// assert!(white1.approx_visually_equal(&white2));
    /// ```
    pub fn white_point(illuminant: Illuminant) -> XYZColor {
        let wp = illuminant.white_point();
        XYZColor {
            x: wp[0],
            y: wp[1],
            z: wp[2],
            illuminant,
        }
    }
}

/// A trait that represents any color representation that can be converted to and from the CIE 1931 XYZ
/// color space. See module-level documentation for more information and examples.
pub trait Color: Sized {
    /// Converts from a color in CIE 1931 XYZ to the given color type.
    ///
    /// # Example
    ///
    /// ```
    /// # use scarlet::color::XYZColor;
    /// # use scarlet::prelude::*;
    /// # use std::error::Error;
    /// # fn try_main() -> Result<(), Box<Error>> {
    /// let rgb1 = RGBColor::from_hex_code("#ffffff")?;
    /// // any illuminant would work: Scarlet takes care of that automatically
    /// let rgb2 = RGBColor::from_xyz(XYZColor::white_point(Illuminant::D65));
    /// assert_eq!(rgb1.to_string(), rgb2.to_string());
    /// # Ok(())
    /// # }
    /// # fn main () {
    /// #     try_main().unwrap();
    /// # }
    /// ```
    fn from_xyz(xyz: XYZColor) -> Self;
    /// Converts from the given color type to a color in CIE 1931 XYZ space. Because most color types
    /// don't include illuminant information, it is provided instead, as an enum. For most
    /// applications, D50 or D65 is a good choice.
    ///
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::{CIELABColor, CIELCHColor};
    /// // CIELAB is implicitly D50
    /// let lab = CIELABColor{l: 100., a: 0., b: 0.};
    /// // sRGB is implicitly D65
    /// let rgb = RGBColor{r: 1., g: 1., b: 1.};
    /// // conversion to a different illuminant keeps their difference
    /// let lab_xyz = lab.to_xyz(Illuminant::D75);
    /// let rgb_xyz = rgb.to_xyz(Illuminant::D75);
    /// assert!(!lab_xyz.approx_equal(&rgb_xyz));
    /// // on the other hand, CIELCH is in D50, so its white will be the same as CIELAB
    /// let lch_xyz = CIELCHColor{l: 100., c: 0., h: 0.}.to_xyz(Illuminant::D75);
    /// assert!(lab_xyz.approx_equal(&lch_xyz));
    /// ```
    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor;
    /// Converts generic colors from one representation to another. This is done by going back and
    /// forth from the CIE 1931 XYZ space, using the illuminant D50 (although this should not affect
    /// the results). Just like [`collect()`] and other methods in the standard library, the use of
    /// type inference will usually allow for clean syntax, but occasionally the turbofish is
    /// necessary.
    ///
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::color::XYZColor;
    /// let xyz = XYZColor{x: 0.2, y: 0.6, z: 0.3, illuminant: Illuminant::D65};
    /// // how would this look like as the closest hex code?
    ///
    /// // the following two lines are equivalent. The first is preferred for simple variable
    /// // allocation, but in more complex scenarios sometimes it's unnecessarily cumbersome
    /// let rgb1: RGBColor = xyz.convert();
    /// let rgb2 = xyz.convert::<RGBColor>();
    /// assert_eq!(rgb1.to_string(), rgb2.to_string());
    /// println!("{}", rgb1.to_string());
    /// ```
    ///
    /// [`collect()`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.collect
    fn convert<T: Color>(&self) -> T {
        // theoretically, the illuminant shouldn't matter as long as the color conversions are
        // correct. D50 is a common gamut for use in internal conversions, so for spaces like CIELAB
        // it will produce the least error
        T::from_xyz(self.to_xyz(Illuminant::D50))
    }
    /// "Colors" a given piece of text with terminal escape codes to allow it to be printed out in the
    /// given foreground color. Will cause problems with terminals that do not support truecolor.
    /// Requires the `terminal` feature.
    ///
    /// # Example
    /// This demo prints out a square of colors that have the same luminance in CIELAB and HSL to
    /// compare the validity of their lightness correlates. It can also simply be used to test whether
    /// a terminal supports printing color. Note that, in some terminal emulators, this can be very
    /// slow: it's unclear why.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::{CIELABColor, HSLColor};
    /// let mut line;
    /// println!("");
    /// for i in 0..20 {
    ///     line = String::from("");
    ///     for j in 0..20 {
    ///         let lab = CIELABColor{l: 50., a: 5. * i as f64, b: 5. * j as f64};
    ///         line.push_str(lab.write_colored_str("#").as_str());
    ///     }
    ///     println!("{}", line);
    /// }
    /// println!("");
    /// for i in 0..20 {
    ///     line = String::from("");
    ///     for j in 0..20 {
    ///         let hsl = HSLColor{h: i as f64 * 18., s: j as f64 * 0.05, l: 0.50};
    ///         line.push_str(hsl.write_colored_str("#").as_str());
    ///     }
    ///     println!("{}", line);
    /// }
    /// ```
    #[cfg(feature = "terminal")]
    fn write_colored_str(&self, text: &str) -> String {
        let rgb: RGBColor = self.convert();
        rgb.base_write_colored_str(text)
    }
    /// Returns a string which, when printed in a truecolor-supporting terminal, will have both the
    /// foreground and background of the desired color, appearing as a complete square. Requires the
    /// `terminal` feature
    ///
    /// # Example
    /// This is the same one as above, but with a complete block of color instead of the # mark.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::{CIELABColor, HSLColor};
    /// let mut line;
    /// println!("");
    /// for i in 0..20 {
    ///     line = String::from("");
    ///     for j in 0..20 {
    ///         let lab = CIELABColor{l: 50., a: 5. * i as f64, b: 5. * j as f64};
    ///         line.push_str(lab.write_color().as_str());
    ///     }
    ///     println!("{}", line);
    /// }
    /// println!("");
    /// for i in 0..20 {
    ///     line = String::from("");
    ///     for j in 0..20 {
    ///         let hsl = HSLColor{h: i as f64 * 18., s: j as f64 * 0.05, l: 0.50};
    ///         line.push_str(hsl.write_color().as_str());
    ///     }
    ///     println!("{}", line);
    /// }
    /// ```
    #[cfg(feature = "terminal")]
    fn write_color(&self) -> String {
        let rgb: RGBColor = self.convert();
        rgb.base_write_color()
    }

    /// Gets the generally most accurate version of hue for a given color: the hue coordinate in
    /// CIELCH. There are generally considered four "unique hues" that humans perceive as not
    /// decomposable into other hues (when mixing additively): these are red, yellow, green, and
    /// blue. These unique hues have values of 0, 90, 180, and 270 degrees respectively, with other
    /// colors interpolated between them. This returned value will never be outside the range 0 to
    /// 360. For more information, you can start at [the Wikpedia page](https://en.wikipedia.org/wiki/Hue).
    ///
    /// This generally shouldn't differ all that much from HSL or HSV, but it is slightly more
    /// accurate to human perception and so is generally superior. This should be preferred over
    /// manually converting to HSL or HSV.
    ///
    /// # Example
    /// One problem with using RGB to work with lightness and hue is that it fails to account for
    /// hue shifts as lightness changes, such as the difference between yellow and brown. When this
    /// causes a shift from red towards blue, it's called the [*Purkinje
    /// effect*](https://en.wikipedia.org/wiki/Purkinje_effect). This example demonstrates how this
    /// can trip up color manipulation if you don't use more perceptually accurate color spaces.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let bright_red = RGBColor{r: 0.9, g: 0., b: 0.};
    /// // One would think that adding or subtracting red here would keep the hue constant
    /// let darker_red = RGBColor{r: 0.3, g: 0., b: 0.};
    /// // However, note that the hue has shifted towards the blue end of the spectrum: in this case,
    /// // closer to 0 by a substantial amount
    /// println!("{} {}", bright_red.hue(), darker_red.hue());
    /// assert!(bright_red.hue() - darker_red.hue() >= 8.);
    /// ```
    fn hue(&self) -> f64 {
        let lch: CIELCHColor = self.convert();
        lch.h
    }

    /// Sets a perceptually-accurate version hue of a color, even if the space itself does not have a
    /// conception of hue. This uses the CIELCH version of hue. To use another one, simply convert and
    /// set it manually. If the given hue is not between 0 and 360, it is shifted in that range by
    /// adding multiples of 360.
    /// # Example
    /// This example shows that RGB primaries are not exact standins for the hue they're named for,
    /// and using Scarlet can improve color accuracy.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let blue = RGBColor{r: 0., g: 0., b: 1.};
    /// // this is a setter, so we make a copy first so we have two colors
    /// let mut red = blue;
    /// red.set_hue(0.); // "ideal" red
    /// // not the same red as RGB's red!
    /// println!("{}", red.to_string());
    /// assert!(!red.visually_indistinguishable(&RGBColor{r: 1., g: 0., b: 0.}));
    /// ```
    fn set_hue(&mut self, new_hue: f64) {
        let mut lch: CIELCHColor = self.convert();
        lch.h = if (0.0..=360.0).contains(&new_hue) {
            new_hue
        } else if new_hue < 0.0 {
            new_hue - 360.0 * (new_hue / 360.0).floor()
        } else {
            new_hue - 360.0 * (new_hue / 360.0).ceil()
        };
        *self = lch.convert();
    }

    /// Gets a perceptually-accurate version of lightness as a value from 0 to 100, where 0 is black
    /// and 100 is pure white. The exact value used is CIELAB's definition of luminance, which is
    /// generally considered a very good standard. Note that this is nonlinear with respect to the
    /// physical amount of light emitted: a material with 18% reflectance has a lightness value of 50,
    /// not 18.
    /// # Examples
    /// HSL and HSV are often used to get luminance. We'll see why this can be horrifically
    /// inaccurate.
    ///
    /// HSL uses the average of the largest and smallest RGB components. This doesn't account for the
    /// fact that some colors have inherently more or less brightness (for instance, yellow looks much
    /// brighter than purple). This is sometimes called *chroma*: we would say that purple has high
    /// chroma. (In Scarlet, chroma usually means something else: check the [`chroma`](#method.chroma) method for more
    /// info.)
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::HSLColor;
    /// let purple = HSLColor{h: 300., s: 0.8, l: 0.5};
    /// let yellow = HSLColor{h: 60., s: 0.8, l: 0.5};
    /// // these have completely different perceptual luminance values
    /// println!("{} {}", purple.lightness(), yellow.lightness());
    /// assert!(yellow.lightness() - purple.lightness() >= 30.);
    /// ```
    /// HSV has to take the cake: it simply uses the maximum RGB component. This means that for
    /// highly-saturated colors with high chroma, it gives results that aren't even remotely close to
    /// the true perception of lightness.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::HSVColor;
    /// let purple = HSVColor{h: 300., s: 1., v: 1.};
    /// let white = HSVColor{h: 300., s: 0., v: 1.};
    /// println!("{} {}", purple.lightness(), white.lightness());
    /// assert!(white.lightness() - purple.lightness() >= 39.);
    /// ```
    /// Hue has only small differences across different color systems, but as you can see lightness is
    /// a completely different story. HSL/HSV and CIELAB can disagree by up to a third of the entire
    /// range of lightness! This means that any use of HSL or HSV for luminance is liable to be
    /// extraordinarily inaccurate if used for widely different chromas. Thus, use of this method is
    /// always preferred unless you explicitly need HSL or HSV.
    fn lightness(&self) -> f64 {
        let lab: CIELABColor = self.convert();
        lab.l
    }

    /// Sets a perceptually-accurate version of lightness, which ranges between 0 and 100 for visible
    /// colors. Any values outside of this range will be clamped within it.
    /// # Example
    /// As we saw in the [`lightness`](#method.lightness) method, purple and yellow tend to trip up HSV and HSL: the
    /// color system doesn't account for how much brighter the color yellow is compared to the color
    /// purple. What would equiluminant purple and yellow look like? We can find out.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::HSLColor;
    /// let purple = HSLColor{h: 300., s: 0.8, l: 0.8};
    /// let mut yellow = HSLColor{h: 60., s: 0.8, l: 0.8};
    /// // increasing purple's brightness to yellow results in colors outside the HSL gamut, so we'll
    /// // do it the other way
    /// yellow.set_lightness(purple.lightness());
    /// // note that the hue has to shift a little, at least according to HSL, but they barely disagree
    /// println!("{}", yellow.h); // prints 60.611 or thereabouts
    /// // the L component has to shift a lot to achieve perceptual equiluminance, as well as a ton of
    /// // desaturation, because a saturated dark yellow is really more like brown and is a different
    /// // hue or out of gamut
    /// assert!(purple.l - yellow.l > 0.15);
    /// // essentially, the different hue and saturation is worth .15 luminance
    /// assert!(yellow.s < 0.4);  // saturation has decreased a lot
    /// ```
    fn set_lightness(&mut self, new_lightness: f64) {
        let mut lab: CIELABColor = self.convert();
        lab.l = if (0.0..=100.0).contains(&new_lightness) {
            new_lightness
        } else if new_lightness < 0.0 {
            0.0
        } else {
            100.0
        };
        *self = lab.convert()
    }

    /// Gets a perceptually-accurate version of *chroma*, defined as colorfulness relative to a
    /// similarly illuminated white. This has no explicit upper bound, but is always positive and
    /// generally between 0 and 180 for visible colors. This is done using the CIELCH model.
    /// # Example
    /// Chroma differs from saturation in that it doesn't account for lightness as much as saturation:
    /// there are just fewer colors at really low light levels, and so most colors appear less
    /// colorful. This can either be the desired measure of this effect, or it can be more suitable to
    /// use saturation. A comparison:
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let dark_purple = RGBColor{r: 0.4, g: 0., b: 0.4};
    /// let bright_purple = RGBColor{r: 0.8, g: 0., b: 0.8};
    /// println!("{} {}", dark_purple.chroma(), bright_purple.chroma());
    /// // chromas differ widely: about 57 for the first and 94 for the second
    /// assert!(bright_purple.chroma() - dark_purple.chroma() >= 35.);
    /// ```
    fn chroma(&self) -> f64 {
        let lch: CIELCHColor = self.convert();
        lch.c
    }

    /// Sets a perceptually-accurate version of *chroma*, defined as colorfulness relative to a
    /// similarly illuminated white. Uses CIELCH's defintion of chroma for implementation. Any value
    /// below 0 will be clamped up to 0, but because the upper bound depends on the hue and
    /// lightness no clamping will be done. This means that this method has a higher chance than
    /// normal of producing imaginary colors and any output from this method should be checked.
    /// # Example
    /// We can use the purple example from above, and see what an equivalent chroma to the dark purple
    /// would look like at a high lightness.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let dark_purple = RGBColor{r: 0.4, g: 0., b: 0.4};
    /// let bright_purple = RGBColor{r: 0.8, g: 0., b: 0.8};
    /// let mut changed_purple = bright_purple;
    /// changed_purple.set_chroma(dark_purple.chroma());
    /// println!("{} {}", bright_purple.to_string(), changed_purple.to_string());
    /// // prints #CC00CC #AC4FA8
    /// ```
    fn set_chroma(&mut self, new_chroma: f64) {
        let mut lch: CIELCHColor = self.convert();
        lch.c = if new_chroma < 0.0 { 0.0 } else { new_chroma };
        *self = lch.convert();
    }

    /// Gets a perceptually-accurate version of *saturation*, defined as chroma relative to
    /// lightness. Generally ranges from 0 to around 10, although exact bounds are tricky. from This
    /// means that e.g., a very dark purple could be very highly saturated even if it does not seem
    /// so relative to lighter colors. This is computed using the CIELCH model and computing chroma
    /// divided by lightness: if the lightness is 0, the saturation is also said to be 0. There is
    /// no official formula except ones that require more information than this model of colors has,
    /// but the CIELCH formula is fairly standard.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let red = RGBColor{r: 1., g: 0.2, b: 0.2};
    /// let dark_red = RGBColor{r: 0.7, g: 0., b: 0.};
    /// assert!(dark_red.saturation() > red.saturation());
    /// assert!(dark_red.chroma() < red.chroma());
    /// ```
    fn saturation(&self) -> f64 {
        let lch: CIELCHColor = self.convert();
        if lch.l == 0.0 {
            0.0
        } else {
            lch.c / lch.l
        }
    }

    /// Sets a perceptually-accurate version of *saturation*, defined as chroma relative to
    /// lightness. Does this without modifying lightness or hue. Any negative value will be clamped
    /// to 0, but because the maximum saturation is not well-defined any positive value will be used
    /// as is: this means that this method is more likely than others to produce imaginary
    /// colors. Uses the CIELCH color space. Generally, saturation ranges from 0 to about 1, but it
    /// can go higher.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let red = RGBColor{r: 0.5, g: 0.2, b: 0.2};
    /// let mut changed_red = red;
    /// changed_red.set_saturation(1.5);
    /// println!("{} {}", red.to_string(), changed_red.to_string());
    /// // prints #803333 #8B262C
    /// ```
    fn set_saturation(&mut self, new_sat: f64) {
        let mut lch: CIELCHColor = self.convert();
        lch.c = if new_sat < 0.0 { 0.0 } else { new_sat * lch.l };
        *self = lch.convert();
    }
    /// Returns a new [`Color`] of the same type as before, but with chromaticity removed: effectively,
    /// a color created solely using a mix of black and white that has the same lightness as
    /// before. This uses the CIELAB luminance definition, which is considered a good standard and is
    /// perceptually accurate for the most part.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # use scarlet::colors::HSVColor;
    /// let rgb = RGBColor{r: 0.7, g: 0.5, b: 0.9};
    /// let hsv = HSVColor{h: 290., s: 0.5, v: 0.8};
    /// // type annotation is superfluous: just note how grayscale works within the type of a color.
    /// let rgb_grey: RGBColor = rgb.grayscale();
    /// let hsv_grey: HSVColor = hsv.grayscale();
    /// // saturation may not be truly zero because of different illuminants and definitions of grey,
    /// // but it's pretty close
    /// println!("{:?} {:?}", hsv_grey, rgb_grey);
    /// assert!(hsv_grey.s < 0.001);
    /// // ditto for RGB
    /// assert!((rgb_grey.r - rgb_grey.g).abs() <= 0.01);
    /// assert!((rgb_grey.r - rgb_grey.b).abs() <= 0.01);
    /// assert!((rgb_grey.g - rgb_grey.b).abs() <= 0.01);
    /// ```
    fn grayscale(&self) -> Self
    where
        Self: Sized,
    {
        let mut lch: CIELCHColor = self.convert();
        lch.c = 0.0;
        lch.convert()
    }

    /// Returns a metric of the distance between the given color and another that attempts to
    /// accurately reflect human perception. This is done by using the CIEDE2000 difference formula,
    /// the current international and industry standard. The result, being a distance, will never be
    /// negative: it has no defined upper bound, although anything larger than 100 would be very
    /// extreme. A distance of 1.0 is conservatively the smallest possible noticeable difference:
    /// anything that is below 1.0 is almost guaranteed to be indistinguishable to most people.
    ///
    /// It's important to note that, just like chromatic adaptation, there's no One True Function for
    /// determining color difference. This is a best effort by the scientific community, but
    /// individual variance, difficulty of testing, and the idiosyncrasies of human vision make this
    /// difficult. For the vast majority of applications, however, this should work correctly. It
    /// works best with small differences, so keep that in mind: it's relatively hard to quantify
    /// whether bright pink and brown are more or less similar than bright blue and dark red.
    ///
    /// For more, check out the [associated
    /// guide](https://github.com/nicholas-miklaucic/scarlet/blob/master/color_distance.md).
    ///
    /// # Examples
    ///
    /// Using the distance between points in RGB space, or really any color space, as a way of
    /// measuring difference runs into some problems, which we can examine using a more accurate
    /// function. The main problem, as the below image shows
    /// [(source)](https://commons.wikimedia.org/wiki/File:CIExy1931_MacAdam.png), is that our
    /// sensitivity to color variance shifts a lot depending on what hue the colors being compared
    /// are. (In the image, the ellipses are drawn ten times as large as the smallest perceptible
    /// difference: the larger the ellipse, the less sensitive the human eye is to changes in that
    /// region.) Perceptual uniformity is the goal for color spaces like CIELAB, but this is a
    /// failure point.
    ///
    /// ![MacAdam ellipses showing areas of indistinguishability scaled by a factor of 10. The green
    /// ellipses are much wider than the
    /// blue.](https://upload.wikimedia.org/wikipedia/commons/thumb/f/f4/CIExy1931_MacAdam.png/800px-CIExy1931_MacAdam.png)
    ///
    /// The other problem is that our sensitivity to lightness also shifts a lot depending on the
    /// conditions: we're not as at distinguishing dark grey from black, but better at
    /// distinguishing very light grey from white. We can examine these phenomena using Scarlet.
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let dark_grey = RGBColor{r: 0.05, g: 0.05, b: 0.05};
    /// let black = RGBColor{r: 0.0, g: 0.0, b: 0.0};
    /// let light_grey = RGBColor{r: 0.95, g: 0.95, b: 0.95};
    /// let white = RGBColor{r: 1., g: 1., b: 1.,};
    /// // RGB already includes a factor to attempt to compensate for the color difference due to
    /// // lighting. As we'll see, however, it's not enough to compensate for this.
    /// println!("{} {} {} {}", dark_grey.to_string(), black.to_string(), light_grey.to_string(),
    /// white.to_string());
    /// // prints #0D0D0D #000000 #F2F2F2 #FFFFFF
    /// //
    /// // noticeable error: not very large at this scale, but the effect exaggerates for very similar colors
    /// assert!(dark_grey.distance(&black) < 0.9 * light_grey.distance(&white));
    /// ```
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let mut green1 = RGBColor{r: 0.05, g: 0.9, b: 0.05};
    /// let mut green2 = RGBColor{r: 0.05, g: 0.91, b: 0.05};
    /// let blue1 = RGBColor{r: 0.05, g: 0.05, b: 0.9};
    /// let blue2 = RGBColor{r: 0.05, g: 0.05, b: 0.91};
    /// // to remove the effect of lightness on color perception, equalize them
    /// green1.set_lightness(blue1.lightness());
    /// green2.set_lightness(blue2.lightness());
    /// // In RGB these have the same difference. This formula accounts for the perceptual distance, however.
    /// println!("{} {} {} {}", green1.to_string(), green2.to_string(), blue1.to_string(),
    /// blue2.to_string());
    /// // prints #0DE60D #0DEB0D #0D0DE6 #0D0DEB
    /// //
    /// // very small error, but nonetheless roughly 1% off
    /// assert!(green1.distance(&green2) / blue1.distance(&blue2) < 0.992);
    /// ```
    fn distance<T: Color>(&self, other: &T) -> f64 {
        // implementation reference found here:
        // https://pdfs.semanticscholar.org/969b/c38ea067dd22a47a44bcb59c23807037c8d8.pdf

        // I'm going to match the notation in that text pretty much exactly: it's the only way to
        // keep this both concise and readable

        // first convert to LAB
        let lab1: CIELABColor = self.convert();
        let lab2: CIELABColor = other.convert();
        // step 1: calculation of C and h
        // the method hypot returns sqrt(a^2 + b^2)
        let c_star_1: f64 = lab1.a.hypot(lab1.b);
        let c_star_2: f64 = lab2.a.hypot(lab2.b);

        let c_bar_ab: f64 = (c_star_1 + c_star_2) / 2.0;
        let g = 0.5 * (1.0 - ((c_bar_ab.powi(7)) / (c_bar_ab.powi(7) + 25.0f64.powi(7))).sqrt());

        let a_prime_1 = (1.0 + g) * lab1.a;
        let a_prime_2 = (1.0 + g) * lab2.a;

        let c_prime_1 = a_prime_1.hypot(lab1.b);
        let c_prime_2 = a_prime_2.hypot(lab2.b);

        // this closure simply does the atan2 like CIELCH, but safely accounts for a == b == 0
        // we're gonna do this twice, so I just use a closure
        let h_func = |a: f64, b: f64| {
            if a == 0.0 && b == 0.0 {
                0.0
            } else {
                let val = b.atan2(a).to_degrees();
                if val < 0.0 {
                    val + 360.0
                } else {
                    val
                }
            }
        };

        let h_prime_1 = h_func(a_prime_1, lab1.b);
        let h_prime_2 = h_func(a_prime_2, lab2.b);

        // step 2: computing delta L, delta C, and delta H
        // take a deep breath, you got this!

        let delta_l = lab2.l - lab1.l;
        let delta_c = c_prime_2 - c_prime_1;
        // essentially, compute the difference in hue but keep it in the right range
        let delta_angle_h = if c_prime_1 * c_prime_2 == 0.0 {
            0.0
        } else if (h_prime_2 - h_prime_1).abs() <= 180.0 {
            h_prime_2 - h_prime_1
        } else if h_prime_2 - h_prime_1 > 180.0 {
            h_prime_2 - h_prime_1 - 360.0
        } else {
            h_prime_2 - h_prime_1 + 360.0
        };
        // now get the Cartesian equivalent of the angle difference in hue
        // this also corrects for chromaticity mattering less at low luminances
        let delta_h =
            2.0 * (c_prime_1 * c_prime_2).sqrt() * (delta_angle_h / 2.0).to_radians().sin();

        // step 3: the color difference
        // if you're reading this, it's not too late to back out
        let l_bar_prime = (lab1.l + lab2.l) / 2.0;
        let c_bar_prime = (c_prime_1 + c_prime_2) / 2.0;
        let h_bar_prime = if c_prime_1 * c_prime_2 == 0.0 {
            h_prime_1 + h_prime_2
        } else if (h_prime_2 - h_prime_1).abs() <= 180.0 {
            (h_prime_1 + h_prime_2) / 2.0
        } else if h_prime_1 + h_prime_2 < 360.0 {
            (h_prime_1 + h_prime_2 + 360.0) / 2.0
        } else {
            (h_prime_1 + h_prime_2 - 360.0) / 2.0
        };

        // we're gonna use this a lot
        let deg_cos = |x: f64| x.to_radians().cos();

        let t = 1.0 - 0.17 * deg_cos(h_bar_prime - 30.0)
            + 0.24 * deg_cos(2.0 * h_bar_prime)
            + 0.32 * deg_cos(3.0 * h_bar_prime + 6.0)
            - 0.20 * deg_cos(4.0 * h_bar_prime - 63.0);

        let delta_theta = 30.0 * (-((h_bar_prime - 275.0) / 25.0).powi(2)).exp();
        let r_c = 2.0 * (c_bar_prime.powi(7) / (c_bar_prime.powi(7) + 25.0f64.powi(7))).sqrt();
        let s_l = 1.0
            + ((0.015 * (l_bar_prime - 50.0).powi(2))
                / (20.0 + (l_bar_prime - 50.0).powi(2)).sqrt());
        let s_c = 1.0 + 0.045 * c_bar_prime;
        let s_h = 1.0 + 0.015 * c_bar_prime * t;
        let r_t = -r_c * (2.0 * delta_theta).to_radians().sin();
        // finally, the end result
        // in the original there are three parametric weights, used for weighting differences in
        // lightness, chroma, or hue. In pretty much any application, including this one, all of
        // these are 1, so they're omitted
        ((delta_l / s_l).powi(2)
            + (delta_c / s_c).powi(2)
            + (delta_h / s_h).powi(2)
            + r_t * (delta_c / s_c) * (delta_h / s_h))
            .sqrt()
    }
    /// Using the metric that two colors with a CIEDE2000 distance of less than 1 are
    /// indistinguishable, determines whether two colors are visually distinguishable from each
    /// other. For more, check out [this guide](../color_distance.html).
    ///
    /// # Examples
    ///
    /// ```
    /// # use scarlet::color::{RGBColor, Color};
    ///
    /// let color1 = RGBColor::from_hex_code("#123456").unwrap();
    /// let color2 = RGBColor::from_hex_code("#123556").unwrap();
    /// let color3 = RGBColor::from_hex_code("#333333").unwrap();
    ///
    /// assert!(color1.visually_indistinguishable(&color2)); // yes, they are visually indistinguishable
    /// assert!(color2.visually_indistinguishable(&color1)); // yes, the same two points
    /// assert!(!color1.visually_indistinguishable(&color3)); // not visually distinguishable
    /// ```
    fn visually_indistinguishable<T: Color>(&self, other: &T) -> bool {
        self.distance(other) <= 1.0
    }
}

impl Color for XYZColor {
    fn from_xyz(xyz: XYZColor) -> XYZColor {
        xyz
    }
    #[allow(unused_variables)]
    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
        *self
    }
}

#[derive(Debug, Copy, Clone)]
/// A color with red, green, and blue primaries of specified intensity, specifically in the sRGB
/// gamut: most computer screens use this to display colors. The attributes `r`, `g`, and `b` are
/// floating-point numbers from 0 to 1 for visible colors, allowing the avoidance of rounding errors
/// or clamping errors when converting to and from RGB. Many conveniences are afforded so that
/// working with RGB as if it were instead three integers from 0-255 is painless. Note that the
/// integers generated from the underlying floating-point numbers round away from 0.
///
/// Examples of this abound: this is used ubiquitously in Scarlet. Check the
/// [`Color`] documentation for plenty.
///
/// [`Color`]: ../color/trait.Color.html
pub struct RGBColor {
    /// The red component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
    pub r: f64,
    /// The green component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
    pub g: f64,
    /// The blue component. Ranges from 0 to 1 for numbers displayable by sRGB machines.
    pub b: f64,
}

impl RGBColor {
    /// Gets an 8-byte version of the red component, as a `u8`. Clamps values outside of the range 0-1
    /// and discretizes, so this may not correspond to the exact values kept internally.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let super_red = RGBColor{r: 1.2, g: 0., b: 0.};
    /// let non_integral_red = RGBColor{r: 0.999, g: 0., b: 0.};
    /// // the first one will get clamped in range, the second one will be rounded
    /// assert_eq!(super_red.int_r(), non_integral_red.int_r());
    /// assert_eq!(super_red.int_r(), 255);
    /// ```
    pub fn int_r(&self) -> u8 {
        // first clamp, then multiply by 255, round, and discretize
        if self.r < 0.0 {
            0_u8
        } else if self.r > 1.0 {
            255_u8
        } else {
            (self.r * 255.0).round() as u8
        }
    }
    /// Gets an 8-byte version of the green component, as a `u8`. Clamps values outside of the range 0-1
    /// and discretizes, so this may not correspond to the exact values kept internally.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let super_green = RGBColor{r: 0., g: 1.2, b: 0.};
    /// let non_integral_green = RGBColor{r: 0., g: 0.999, b: 0.};
    /// // the first one will get clamped in range, the second one will be rounded
    /// assert_eq!(super_green.int_g(), non_integral_green.int_g());
    /// assert_eq!(super_green.int_g(), 255);
    /// ```
    pub fn int_g(&self) -> u8 {
        // first clamp, then multiply by 255, round, and discretize
        if self.g < 0.0 {
            0_u8
        } else if self.g > 1.0 {
            255_u8
        } else {
            (self.g * 255.0).round() as u8
        }
    }
    /// Gets an 8-byte version of the blue component, as a `u8`. Clamps values outside of the range 0-1
    /// and discretizes, so this may not correspond to the exact values kept internally.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let super_blue = RGBColor{r: 0., g: 0., b: 1.2};
    /// let non_integral_blue = RGBColor{r: 0., g: 0., b: 0.999};
    /// // the first one will get clamped in range, the second one will be rounded
    /// assert_eq!(super_blue.int_b(), non_integral_blue.int_b());
    /// assert_eq!(super_blue.int_b(), 255);
    /// ```
    pub fn int_b(&self) -> u8 {
        // first clamp, then multiply by 255, round, and discretize
        if self.b < 0.0 {
            0_u8
        } else if self.b > 1.0 {
            255_u8
        } else {
            (self.b * 255.0).round() as u8
        }
    }
    /// Purely for convenience: gives a tuple with the three integer versions of the components. Used
    /// over standard conversion traits to avoid ambiguous operations.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// let color = RGBColor{r: 0.3, g: 0.6, b: 0.7};
    /// assert_eq!(color.int_rgb_tup(), (color.int_r(), color.int_g(), color.int_b()));
    /// ```
    pub fn int_rgb_tup(&self) -> (u8, u8, u8) {
        (self.int_r(), self.int_g(), self.int_b())
    }
    /// Given a string, returns that string wrapped in codes that will color the foreground. Used
    /// for the trait implementation of write_colored_str, which should be used instead. Requires
    /// the `terminal` feature.
    #[cfg(feature = "terminal")]
    fn base_write_colored_str(&self, text: &str) -> String {
        format!(
            "{code}{text}{reset}",
            code = Fg(Rgb(self.int_r(), self.int_g(), self.int_b())),
            text = text,
            reset = Fg(Reset)
        )
    }
    /// Used for the Color `write_color()` method. Requires the `terminal` feature.
    #[cfg(feature = "terminal")]
    fn base_write_color(&self) -> String {
        format!(
            "{bg}{fg}{text}{reset_fg}{reset_bg}",
            bg = Bg(Rgb(self.int_r(), self.int_g(), self.int_b())),
            fg = Fg(Rgb(self.int_r(), self.int_g(), self.int_b())),
            text = "■",
            reset_fg = Fg(Reset),
            reset_bg = Bg(Reset),
        )
    }
}

impl PartialEq for RGBColor {
    fn eq(&self, other: &RGBColor) -> bool {
        self.r == other.r && self.g == other.g && self.b == other.b
    }
}

impl From<(u8, u8, u8)> for RGBColor {
    fn from(rgb: (u8, u8, u8)) -> RGBColor {
        let (r, g, b) = rgb;
        RGBColor {
            r: f64::from(r) / 255.0,
            g: f64::from(g) / 255.0,
            b: f64::from(b) / 255.0,
        }
    }
}

impl From<RGBColor> for (u8, u8, u8) {
    fn from(val: RGBColor) -> Self {
        (val.int_r(), val.int_g(), val.int_b())
    }
}

impl From<Coord> for RGBColor {
    fn from(c: Coord) -> RGBColor {
        RGBColor {
            r: c.x,
            g: c.y,
            b: c.z,
        }
    }
}

impl From<RGBColor> for Coord {
    fn from(val: RGBColor) -> Self {
        Coord {
            x: val.r,
            y: val.g,
            z: val.b,
        }
    }
}

impl ToString for RGBColor {
    fn to_string(&self) -> String {
        format!(
            "#{:02X}{:02X}{:02X}",
            self.int_r(),
            self.int_g(),
            self.int_b()
        )
    }
}

impl Color for RGBColor {
    fn from_xyz(xyz: XYZColor) -> RGBColor {
        // sRGB uses D65 as the assumed illuminant: convert the given value to that
        let xyz_d65 = xyz.color_adapt(Illuminant::D65);
        // first, get linear RGB values (i.e., without gamma correction)
        // https://en.wikipedia.org/wiki/SRGB#Specification_of_the_transformation

        let lin_rgb_vec = *SRGB * vector![xyz_d65.x, xyz_d65.y, xyz_d65.z];
        // now we scale for gamma correction
        let gamma_correct = |x: &f64| {
            if x <= &0.0031308 {
                12.92 * x
            } else {
                1.055 * x.powf(1.0 / 2.4) - 0.055
            }
        };
        let float_vec: Vec<f64> = lin_rgb_vec.iter().map(gamma_correct).collect();
        RGBColor {
            r: float_vec[0],
            g: float_vec[1],
            b: float_vec[2],
        }
    }
    fn to_xyz(&self, illuminant: Illuminant) -> XYZColor {
        let uncorrect_gamma = |x: &f64| {
            if x <= &0.04045 {
                x / 12.92
            } else {
                ((x + 0.055) / 1.055).powf(2.4)
            }
        };
        let rgb_vec = vector![
            uncorrect_gamma(&self.r),
            uncorrect_gamma(&self.g),
            uncorrect_gamma(&self.b)
        ];

        // invert the matrix multiplication used in from_xyz()
        // use LU decomposition for accuracy
        let xyz_vec = SRGB_LU.solve(&rgb_vec).expect("Matrix is invertible.");

        // sRGB, which this is based on, uses D65 as white, but you can convert to whatever
        // illuminant is specified
        let converted = XYZColor {
            x: xyz_vec[0],
            y: xyz_vec[1],
            z: xyz_vec[2],
            illuminant: Illuminant::D65,
        };
        converted.color_adapt(illuminant)
    }
}

/// An error type that results from an invalid attempt to convert a string into an RGB color.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum RGBParseError {
    /// This indicates that function syntax was acceptable, but the numbers were out of range, such as
    /// the invalid string `"rgb(554, 23, 553)"`.
    OutOfRange,
    /// This indicates that the hex string was malformed in some way.
    InvalidHexSyntax,
    /// This indicates a syntax error in the string that was supposed to be a valid rgb( function.
    InvalidFuncSyntax,
    /// This indicated an invalid color name was supplied to the `from_color_name()` function.
    InvalidX11Name,
}

impl fmt::Display for RGBParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "RGB parsing error")
    }
}

impl From<ParseIntError> for RGBParseError {
    fn from(_err: ParseIntError) -> RGBParseError {
        RGBParseError::OutOfRange
    }
}

impl From<CSSParseError> for RGBParseError {
    fn from(_err: CSSParseError) -> RGBParseError {
        RGBParseError::InvalidFuncSyntax
    }
}

impl Error for RGBParseError {
    fn description(&self) -> &str {
        match *self {
            RGBParseError::OutOfRange => "RGB coordinates out of range",
            RGBParseError::InvalidHexSyntax => "Invalid hex code syntax",
            RGBParseError::InvalidFuncSyntax => "Invalid \"rgb(\" function call syntax",
            RGBParseError::InvalidX11Name => "Invalid X11 color name",
        }
    }
}

impl RGBColor {
    /// Given a string that represents a hex code, returns the RGB color that the given hex code
    /// represents. Four formats are accepted: `"#rgb"` as a shorthand for `"#rrggbb"`, `#rrggbb` by
    /// itself, and either of those formats without `#`: `"rgb"` or `"rrggbb"` are acceptable. Returns
    /// a ColorParseError if the given string does not follow one of these formats.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # fn try_main() -> Result<(), RGBParseError> {
    /// let fuchsia = RGBColor::from_hex_code("#ff00ff")?;
    /// // if 3 digits, interprets as doubled
    /// let fuchsia2 = RGBColor::from_hex_code("f0f")?;
    /// assert_eq!(fuchsia.int_rgb_tup(), fuchsia2.int_rgb_tup());
    /// assert_eq!(fuchsia.int_rgb_tup(), (255, 0, 255));
    /// let err = RGBColor::from_hex_code("#afafa");
    /// let err2 = RGBColor::from_hex_code("#gafd22");
    /// assert_eq!(err, err2);
    /// # Ok(())
    /// # }
    /// # try_main().unwrap();
    /// ```
    // otherwise you have really long lines with different reasons for throwing the same error
    #[allow(clippy::if_same_then_else)]
    pub fn from_hex_code(hex: &str) -> Result<RGBColor, RGBParseError> {
        let mut chars: Vec<char> = hex.chars().collect();
        // check if leading hex, remove if so
        if chars[0] == '#' {
            chars.remove(0);
        }
        // can only have 3 or 6 characters: error if not so
        if chars.len() != 3 && chars.len() != 6 {
            Err(RGBParseError::InvalidHexSyntax)
        // now split on invalid hex
        } else if !chars.iter().all(|&c| "0123456789ABCDEFabcdef".contains(c)) {
            Err(RGBParseError::InvalidHexSyntax)
        // split on whether it's #rgb or #rrggbb
        } else if chars.len() == 6 {
            let mut rgb: Vec<u8> = Vec::new();
            for _i in 0..3 {
                // this should never fail, logically, but if by some miracle it did it'd just
                // return an OutOfRangeError
                rgb.push(
                    u8::from_str_radix(chars.drain(..2).collect::<String>().as_str(), 16).unwrap(),
                );
            }
            Ok(RGBColor::from((rgb[0], rgb[1], rgb[2])))
        } else {
            // len must be 3 from earlier
            let mut rgb: Vec<u8> = Vec::new();
            for _i in 0..3 {
                // again, this shouldn't ever fail, but if it did it'd just return an
                // OutOfRangeError
                let c: Vec<char> = chars.drain(..1).collect();
                rgb.push(
                    u8::from_str_radix(c.iter().chain(c.iter()).collect::<String>().as_str(), 16)
                        .unwrap(),
                );
            }
            Ok(RGBColor::from((rgb[0], rgb[1], rgb[2])))
        }
    }
    /// Gets the RGB color corresponding to an X11 color name. Case is ignored.
    /// # Example
    ///
    /// ```
    /// # use scarlet::prelude::*;
    /// # fn try_main() -> Result<(), RGBParseError> {
    /// let fuchsia = RGBColor::from_color_name("fuchsia")?;
    /// let fuchsia2 = RGBColor::from_color_name("FuCHSiA")?;
    /// assert_eq!(fuchsia.int_rgb_tup(), fuchsia2.int_rgb_tup());
    /// assert_eq!(fuchsia.int_rgb_tup(), (255, 0, 255));
    /// let err = RGBColor::from_color_name("fuccshai");
    /// let err2 = RGBColor::from_color_name("foobar");
    /// assert_eq!(err, err2);
    /// # Ok(())
    /// # }
    /// # try_main().unwrap();
    /// ```
    pub fn from_color_name(name: &str) -> Result<RGBColor, RGBParseError> {
        // this is the full list of X11 color names
        // I used a Python script to process it from this site:
        // https://github.com/bahamas10/css-color-names/blob/master/css-color-names.json
        // I added the special "transparent" referring to #00000000
        let color_names: Vec<&str> = consts::X11_NAMES.to_vec();
        let color_codes: Vec<&str> = consts::X11_COLOR_CODES.to_vec();
        let mut names_to_codes = HashMap::new();

        for (i, color_name) in color_names.iter().enumerate() {
            names_to_codes.insert(color_name, color_codes[i]);
        }

        // now just return the converted value or raise one if not in hashmap
        match names_to_codes.get(&name.to_lowercase().as_str()) {
            None => Err(RGBParseError::InvalidX11Name),
            Some(x) => Self::from_hex_code(x),
        }
    }
}

impl FromStr for RGBColor {
    type Err = RGBParseError;

    fn from_str(s: &str) -> Result<RGBColor, RGBParseError> {
        match RGBColor::from_hex_code(s) {
            Err(_e) => match RGBColor::from_color_name(s) {
                Err(_e) => match parse_rgb_str(s) {
                    Err(_e) => Err(_e.into()),
                    Ok(nums) => Ok(RGBColor::from(nums)),
                },
                Ok(rgb) => Ok(rgb),
            },
            Ok(rgb) => Ok(rgb),
        }
    }
}

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use super::*;
    use consts::TEST_PRECISION;

    #[test]
    fn test_visual_distinguishability() {
        let color1 = RGBColor::from_hex_code("#123456").unwrap();
        let color2 = RGBColor::from_hex_code("#123556").unwrap();
        let color3 = RGBColor::from_hex_code("#333333").unwrap();
        assert!(color1.visually_indistinguishable(&color2));
        assert!(color2.visually_indistinguishable(&color1));
        assert!(!color1.visually_indistinguishable(&color3));
    }

    #[cfg(feature = "terminal")]
    #[test]
    #[ignore]
    fn can_display_colors() {
        let range = 120;
        let mut col;
        let mut line;
        let mut c;
        let mut h;
        println!();
        for i in 0..range {
            h = (i as f64) / (range as f64) * 360.;
            line = String::new();
            for j in 0..range {
                c = j as f64;
                col = CIELCHColor {
                    l: 70.,
                    c: c / 2.,
                    h,
                };
                line += col.write_color().as_str();
            }
            println!("{}", line);
        }
        println!();
    }

    #[test]
    fn xyz_to_rgb() {
        let xyz = XYZColor {
            x: 0.41874,
            y: 0.21967,
            z: 0.05649,
            illuminant: Illuminant::D65,
        };
        let rgb: RGBColor = xyz.convert();
        assert_eq!(rgb.int_r(), 254);
        assert_eq!(rgb.int_g(), 23);
        assert_eq!(rgb.int_b(), 55);
    }

    #[test]
    fn rgb_to_xyz() {
        let rgb = RGBColor::from((45, 28, 156));
        let xyz: XYZColor = rgb.to_xyz(Illuminant::D65);
        // these won't match exactly cuz floats, so I just check within a margin
        assert!((xyz.x - 0.0750).abs() <= 0.01);
        assert!((xyz.y - 0.0379).abs() <= 0.01);
        assert!((xyz.z - 0.3178).abs() <= 0.01);
        assert!(rgb.distance(&xyz) <= TEST_PRECISION);
    }
    #[test]
    fn test_rgb_to_string() {
        let c1 = RGBColor::from((0, 0, 0));
        let c2 = RGBColor::from((244, 182, 33));
        let c3 = RGBColor::from((0, 255, 0));
        assert_eq!(c1.to_string(), "#000000");
        assert_eq!(c2.to_string(), "#F4B621");
        assert_eq!(c3.to_string(), "#00FF00");
    }
    #[test]
    fn test_xyz_color_adaptation() {
        // I can literally not find a single API or something that does this so I can check the
        // values, so I'll just hope that it's good enough to check that converting between several
        // illuminants and back again gets something good
        let c1 = XYZColor {
            x: 0.5,
            y: 0.75,
            z: 0.6,
            illuminant: Illuminant::D65,
        };
        let c2 = c1.color_adapt(Illuminant::D50).color_adapt(Illuminant::D55);
        let c3 = c1.color_adapt(Illuminant::D75).color_adapt(Illuminant::D55);
        assert!((c3.x - c2.x).abs() <= 0.01);
        assert!((c3.y - c2.y).abs() <= 0.01);
        assert!((c3.z - c2.z).abs() <= 0.01);
        assert!(c2.distance(&c3) <= TEST_PRECISION);
    }
    #[test]
    fn test_error_buildup_color_adaptation() {
        // this is essentially just seeing how consistent the inverse function is for the Bradford
        // transform
        let xyz = XYZColor {
            x: 0.5,
            y: 0.4,
            z: 0.6,
            illuminant: Illuminant::D65,
        };
        let mut xyz2;
        const MAX_ITERS_UNTIL_UNACCEPTABLE_ERROR: usize = 100;
        for i in 0..MAX_ITERS_UNTIL_UNACCEPTABLE_ERROR {
            let lum = [
                Illuminant::D50,
                Illuminant::D55,
                Illuminant::D65,
                Illuminant::D75,
            ][i % 4];
            xyz2 = xyz.color_adapt(lum);
            assert!(xyz2.approx_visually_equal(&xyz));
        }
    }
    #[test]
    fn test_chromatic_adapation_to_same_light() {
        let xyz = XYZColor {
            x: 0.4,
            y: 0.6,
            z: 0.2,
            illuminant: Illuminant::D65,
        };
        let xyz2 = xyz.color_adapt(Illuminant::D65);
        assert_eq!(xyz, xyz2);
    }
    #[cfg(feature = "terminal")]
    #[test]
    #[ignore]
    fn fun_dress_color_adaptation_demo() {
        // the famous dress colors, taken completely out of the lighting conditions using GIMP
        let dress_bg = RGBColor::from_hex_code("#7d6e47")
            .unwrap()
            .to_xyz(Illuminant::D65);
        let dress_fg = RGBColor::from_hex_code("#9aabd6")
            .unwrap()
            .to_xyz(Illuminant::D65);

        // helper closure to print block of color
        let block_size = 50;
        let print_col = |c: XYZColor| {
            println!();
            for _i in 0..block_size {
                println!("{}", c.write_color().repeat(block_size));
            }
        };

        // make two "proposed" illuminants: different observers disagree on which one from the image!
        // bright sunlight, clearly the incorrect one (actually, correct, just the one I don't see)
        let sunlight = Illuminant::D50; // essentially daylight in East US, approximately
                                        // dark shade, clearly the correct one (joking, it's the one I see)
                                        // just taking a point in the image that looks like white in shade
        let dress_wp = RGBColor::from_hex_code("#69718b").unwrap();
        let shade_wp = dress_wp.to_xyz(Illuminant::D65);
        let shade = Illuminant::Custom([shade_wp.x, shade_wp.y, shade_wp.z]);
        // print alternate blocks of color: first the dress interpreted in sunlight (black and blue),
        // then the dress interpreted in shade (white and gold)
        let mut black = dress_bg;
        let mut blue = dress_fg;
        black.illuminant = sunlight;
        blue.illuminant = sunlight;

        let mut gold = dress_bg;
        let mut white = dress_fg;
        gold.illuminant = shade;
        white.illuminant = shade;

        let black_rgb: RGBColor = black.convert();
        let blue_rgb: RGBColor = blue.convert();
        let gold_rgb: RGBColor = gold.convert();
        let white_rgb: RGBColor = white.convert();
        println!(
            "Black: {} Blue: {}",
            black_rgb.to_string(),
            blue_rgb.to_string()
        );
        println!(
            "Gold: {}, White: {}",
            gold_rgb.to_string(),
            white_rgb.to_string()
        );
        print_col(black);
        print_col(blue);
        print_col(gold);
        print_col(white);
    }

    #[cfg(feature = "terminal")]
    #[test]
    #[ignore]
    fn fun_color_adaptation_demo() {
        println!();
        let w: usize = 120;
        let h: usize = 60;
        let d50_wp = Illuminant::D50.white_point();
        let d75_wp = Illuminant::D75.white_point();
        let d50 = XYZColor {
            x: d50_wp[0],
            y: d50_wp[1],
            z: d50_wp[2],
            illuminant: Illuminant::D65,
        };
        let d75 = XYZColor {
            x: d75_wp[0],
            y: d75_wp[1],
            z: d75_wp[2],
            illuminant: Illuminant::D65,
        };
        for _ in 0..h + 1 {
            println!(
                "{}{}",
                d50.write_color().repeat(w / 2),
                d75.write_color().repeat(w / 2)
            );
        }

        println!();
        println!();
        let y = 0.5;
        println!();
        for i in 0..(h + 1) {
            let mut line = String::from("");
            let x = i as f64 * 0.9 / h as f64;
            for j in 0..(w / 2) {
                let z = j as f64 * 0.9 / w as f64;
                line.push_str(
                    XYZColor {
                        x,
                        y,
                        z,
                        illuminant: Illuminant::D50,
                    }
                    .write_color()
                    .as_str(),
                );
            }
            for j in (w / 2)..(w + 1) {
                let z = j as f64 * 0.9 / w as f64;
                line.push_str(
                    XYZColor {
                        x,
                        y,
                        z,
                        illuminant: Illuminant::D75,
                    }
                    .write_color()
                    .as_str(),
                );
            }
            println!("{}", line);
        }
        println!();
        println!();
        for i in 0..(h + 1) {
            let mut line = String::from("");
            let x = i as f64 * 0.9 / h as f64;
            for j in 0..w {
                let z = j as f64 * 0.9 / w as f64;
                line.push_str(
                    XYZColor {
                        x,
                        y,
                        z,
                        illuminant: Illuminant::D65,
                    }
                    .write_color()
                    .as_str(),
                );
            }
            println!("{}", line);
        }
    }
    #[test]
    fn test_rgb_from_hex() {
        // test rgb format
        let rgb = RGBColor::from_hex_code("#172844").unwrap();
        assert_eq!(rgb.int_r(), 23);
        assert_eq!(rgb.int_g(), 40);
        assert_eq!(rgb.int_b(), 68);
        // test with letters and no hex
        let rgb = RGBColor::from_hex_code("a1F1dB").unwrap();
        assert_eq!(rgb.int_r(), 161);
        assert_eq!(rgb.int_g(), 241);
        assert_eq!(rgb.int_b(), 219);
        // test for error if 7 chars
        let rgb = RGBColor::from_hex_code("#1244444");
        assert!(matches!(rgb, Err(x) if x == RGBParseError::InvalidHexSyntax));
        // test for error if invalid hex chars
        let rgb = RGBColor::from_hex_code("#ffggbb");
        assert!(matches!(rgb, Err(x) if x == RGBParseError::InvalidHexSyntax));
    }
    #[test]
    fn test_rgb_from_name() {
        let rgb = RGBColor::from_color_name("yeLlowgreEn").unwrap();
        assert_eq!(rgb.int_r(), 154);
        assert_eq!(rgb.int_g(), 205);
        assert_eq!(rgb.int_b(), 50);
        // test error
        let rgb = RGBColor::from_color_name("thisisnotavalidnamelol");
        assert!(match rgb {
            Err(x) if x == RGBParseError::InvalidX11Name => true,
            _ => false,
        });
    }
    #[test]
    fn test_rgb_from_func() {
        let rgb: RGBColor = "rgb(67%, 205, .937)".parse().unwrap();
        assert_eq!(*"#ABCDEF", rgb.to_string());
        assert_eq!(
            Err(RGBParseError::InvalidFuncSyntax),
            "rgb(53%%, 23, 44)".parse::<RGBColor>()
        );
    }
    #[test]
    fn test_string_parsing_all() {
        assert_eq!(
            *"#123456",
            "rgb(18, 52, 86)".parse::<RGBColor>().unwrap().to_string()
        );
        assert_eq!(
            *"#123456",
            "#123456".parse::<RGBColor>().unwrap().to_string()
        );
        assert_eq!(*"#000000", "black".parse::<RGBColor>().unwrap().to_string());
    }
    #[test]
    fn test_to_string() {
        for hex in ["#000000", "#ABCDEF", "#1A2B3C", "#D00A12", "#40AA50"].iter() {
            assert_eq!(*hex, RGBColor::from_hex_code(hex).unwrap().to_string());
        }
    }
    #[cfg(feature = "terminal")]
    #[test]
    #[ignore]
    fn lightness_demo() {
        use colors::{CIELABColor, HSLColor};
        let mut line;
        println!();
        for i in 0..20 {
            line = String::from("");
            for j in 0..20 {
                let lab = CIELABColor {
                    l: 50.,
                    a: 5. * i as f64,
                    b: 5. * j as f64,
                };
                line.push_str(lab.write_colored_str("#").as_str());
            }
            println!("{}", line);
        }
        println!();
        for i in 0..20 {
            line = String::from("");
            for j in 0..20 {
                let hsl = HSLColor {
                    h: i as f64 * 18.,
                    s: j as f64 * 0.05,
                    l: 0.50,
                };
                line.push_str(hsl.write_colored_str("#").as_str());
            }
            println!("{}", line);
        }
    }
    #[test]
    fn test_ciede2000() {
        // this implements the fancy test cases found here:
        // https://pdfs.semanticscholar.org/969b/c38ea067dd22a47a44bcb59c23807037c8d8.pdf
        let l_1 = vec![
            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0,
            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 60.2574, 63.0109, 61.2901,
            35.0831, 22.7233, 36.4612, 90.8027, 90.9257, 6.7747, 2.0776,
        ];
        let l_2 = vec![
            50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0, 50.0,
            50.0, 50.0, 73.0, 61.0, 56.0, 58.0, 50.0, 50.0, 50.0, 50.0, 60.4626, 62.8187, 61.4292,
            35.0232, 23.0331, 36.2715, 91.1528, 88.6381, 5.8714, 0.9033,
        ];
        let a_1 = vec![
            2.6772, 3.1571, 2.8361, -1.3802, -1.1848, -0.9009, 0.0, -1.0, 2.49, 2.49, 2.49, 2.49,
            -0.001, -0.001, -0.001, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, 2.5, -34.0099,
            -31.0961, 3.7196, -44.1164, 20.0904, 47.858, -2.0831, -0.5406, -0.2908, 0.0795,
        ];
        let a_2 = vec![
            0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, -2.49, -2.49, -2.49, -2.49, 0.0009, 0.001,
            0.0011, 0.0, 25.0, -5.0, -27.0, 24.0, 3.1736, 3.2972, 1.8634, 3.2592, -34.1751,
            -29.7946, 2.248, -40.0716, 14.973, 50.5065, -1.6435, -0.8985, -0.0985, -0.0636,
        ];
        let b_1 = vec![
            -79.7751, -77.2803, -74.02, -84.2814, -84.8006, -85.5211, 0.0, 2.0, -0.001, -0.001,
            -0.001, -0.001, 2.49, 2.49, 2.49, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 36.2677,
            -5.8663, -5.3901, 3.7933, -46.6940, 18.3852, 1.441, -0.9208, -2.4247, -1.135,
        ];
        let b_2 = vec![
            -82.7485, -82.7485, -82.7485, -82.7485, -82.7485, -82.7485, 2.0, 0.0, 0.0009, 0.001,
            0.0011, 0.0012, -2.49, -2.49, -2.49, -2.5, -18.0, 29.0, -3.0, 15.0, 0.5854, 0.0,
            0.5757, 0.3350, 39.4387, -4.0864, -4.962, 1.5901, -42.5619, 21.2231, 0.0447, -0.7239,
            -2.2286, -0.5514,
        ];
        let d_e = vec![
            2.0425, 2.8615, 3.4412, 1.0, 1.0, 1.0, 2.3669, 2.3669, 7.1792, 7.1792, 7.2195, 7.2195,
            4.8045, 4.8045, 4.7461, 4.3065, 27.1492, 22.8977, 31.9030, 19.4535, 1.0, 1.0, 1.0, 1.0,
            1.2644, 1.263, 1.8731, 1.8645, 2.0373, 1.4146, 1.4441, 1.5381, 0.6377, 0.9082,
        ];
        assert_eq!(l_1.len(), 34);
        assert_eq!(l_2.len(), 34);
        assert_eq!(a_1.len(), 34);
        assert_eq!(a_2.len(), 34);
        assert_eq!(b_1.len(), 34);
        assert_eq!(b_2.len(), 34);
        assert_eq!(d_e.len(), 34);
        for i in 0..34 {
            let lab1 = CIELABColor {
                l: l_1[i],
                a: a_1[i],
                b: b_1[i],
            };
            let lab2 = CIELABColor {
                l: l_2[i],
                a: a_2[i],
                b: b_2[i],
            };
            // only good to 4 decimal points
            assert!((lab1.distance(&lab2) - d_e[i]).abs() <= 1e-4);
            assert!((lab2.distance(&lab1) - d_e[i]).abs() <= 1e-4);
        }
    }
    #[test]
    fn test_hue_chroma_lightness_saturation() {
        let mut rgb;
        let mut rgb2;
        for code in [
            "#12000D", "#FAFA22", "#FF0000", "#0000FF", "#FF0FDF", "#2266AA", "#001200", "#FFAAFF",
            "#003462", "#466223", "#AAFFBC",
        ]
        .iter()
        {
            // hue
            rgb = RGBColor::from_hex_code(code).unwrap();
            let h = rgb.hue();
            rgb.set_hue(345.0);
            assert!((rgb.hue() - 345.0).abs() <= 1e-4);
            rgb2 = rgb;
            rgb2.set_hue(h);
            assert_eq!(rgb2.to_string(), String::from(*code));

            // chroma
            rgb = RGBColor::from_hex_code(code).unwrap();
            let c = rgb.chroma();
            rgb.set_chroma(45.0);
            assert!((rgb.chroma() - 45.0).abs() <= 1e-4);
            rgb2 = rgb;
            rgb2.set_chroma(c);
            assert_eq!(rgb2.to_string(), String::from(*code));

            // lightness
            rgb = RGBColor::from_hex_code(code).unwrap();
            let l = rgb.lightness();
            rgb.set_lightness(23.0);
            assert!((rgb.lightness() - 23.0).abs() <= 1e-4);
            rgb2 = rgb;
            rgb2.set_lightness(l);
            assert_eq!(rgb2.to_string(), String::from(*code));

            // saturation
            rgb = RGBColor::from_hex_code(code).unwrap();
            let s = rgb.saturation();
            rgb.set_saturation(0.4);
            assert!((rgb.saturation() - 0.4).abs() <= 1e-4);
            rgb2 = rgb;
            rgb2.set_saturation(s);
            assert_eq!(rgb2.to_string(), String::from(*code));
        }
    }
    #[test]
    #[ignore]
    fn color_scheme() {
        let mut colors: Vec<RGBColor> = vec![];
        for i in 0..8 {
            colors.push(
                CIELCHColor {
                    l: i as f64 / 7. * 100.,
                    c: 0.,
                    h: 0.,
                }
                .convert(),
            );
        }
        for j in 0..8 {
            colors.push(
                CIELCHColor {
                    l: 50.,
                    c: 70.,
                    h: j as f64 / 8. * 360. + 10.,
                }
                .convert(),
            );
        }
        println!();
        for color in colors {
            println!("{}", color.to_string());
        }
    }
}