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
//! A crate for converting colors between different color spaces.
//!
//! Color spaces can be converted between one another using [`convert`]:
//!
//! ```
//! use tincture::{LinearRgb, Oklab};
//!
//! let rebeccapurple = LinearRgb {
//!     r: 0.4,
//!     g: 0.2,
//!     b: 0.6,
//! };
//!
//! let oklab: Oklab = tincture::convert(rebeccapurple);
//!
//! assert_eq!(
//!     oklab,
//!     Oklab {
//!         l: 0.66066486,
//!         a: 0.079970956,
//!         b: -0.095915854,
//!     },
//! );
//! ```
//!
//! Variations on the core color spaces do not implement [`CoreColorSpace`], which is necessary for [`convert`].
//! Instead, they implement `From<ACoreColorSpace>`, allowing you to convert this variation to its corresponding core color space and call [`convert`].
//! Examples of variations include [`Oklch`] (a variation on [`Oklab`]) and [`Srgb`] (a variation on [`LinearRgb`]).
//!
//! ```
//! use tincture::{Hue, LinearRgb, Oklab, Oklch, Srgb};
//!
//! // `Oklch` is a variation on `Oklab` (`Oklch` uses polar coordinates).
//! let peach = Oklch {
//!     l: 0.8,
//!     c: 0.25,
//!     h: Hue::from_degrees(40.0).unwrap(),
//! };
//!
//! // This means we can create an `Oklab` using `From`.
//! let oklab = Oklab::from(peach);
//!
//! // We can now convert `oklab` to any other core color space, such as `LinearRgb`.
//! let linear_rgb: LinearRgb = tincture::convert(oklab);
//!
//! // `Srgb` is a variant of `LinearRgb`, so we again create one using `From`.
//! let srgb = Srgb::from(linear_rgb);
//! ```
//!
//! _All_ color spaces implement [`ColorSpace`], which provides the constants `BLACK` and `WHITE`:
//!
//! ```
//! use tincture::{ColorSpace, Srgb};
//!
//! assert_eq!(Srgb::BLACK, Srgb { r: 0.0, g: 0.0, b: 0.0 });
//! assert_eq!(Srgb::WHITE, Srgb { r: 1.0, g: 1.0, b: 1.0 });
//! ```
//!
//! [`ColorSpace`] also provides the [`in_bounds`] method:
//!
//! ```
//! use tincture::{ColorSpace, Srgb};
//!
//! let out_of_bounds = Srgb {
//!     r: 2.0,
//!     g: -100.0,
//!     b: 0.5,
//! };
//!
//! let in_bounds = Srgb {
//!     r: 0.25,
//!     g: 0.75,
//!     b: 0.25,
//! };
//!
//! assert!(!out_of_bounds.in_bounds());
//! assert!(in_bounds.in_bounds());
//! ```

#![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)]
#![allow(clippy::excessive_precision)]

mod hex;
mod hue;
mod linear_rgb;
mod oklab;
mod oklch;
mod srgb;
mod xyz;

pub use hex::Hex;
pub use hue::Hue;
pub use linear_rgb::LinearRgb;
pub use oklab::Oklab;
pub use oklch::Oklch;
pub use srgb::Srgb;
pub use xyz::Xyz;

/// A color space that can be converted to any other `CoreColorSpace`.
pub trait CoreColorSpace {
    /// Convert a color in the XYZ color space to the color space that `Self` represents.
    fn from_xyz(xyz: Xyz) -> Self;

    /// Convert the color of `Self` to the XYZ color space.
    fn to_xyz(self) -> Xyz;
}

/// A color space.
pub trait ColorSpace {
    /// The color ‘black’.
    const BLACK: Self;

    /// The color ‘white’.
    const WHITE: Self;

    /// Checks if the color is in bounds.
    fn in_bounds(self) -> bool;
}

/// Convert a color from one color space to another.
pub fn convert<In: CoreColorSpace, Out: CoreColorSpace>(color: In) -> Out {
    let xyz = color.to_xyz();
    Out::from_xyz(xyz)
}

fn approx_in_range(n: f32, range: std::ops::Range<f32>) -> bool {
    let fudged_range = range.start - 0.005..range.end + 0.005;
    fudged_range.contains(&n)
}