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());
}
}
}