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
//! Traits related to traditional color theory.
//!
//! Traditional color theory is sometimes used as a guide when selecting colors
//! for artistic purposes. While it's not the same as modern color science, and
//! much more subjective, it may still be a helpful set of principles.
//!
//! This module is primarily based on the 12 color wheel, meaning that they use
//! colors that are separated by 30° around the hue circle. There are however
//! some concepts, such as [`Complementary`] colors, that are generally
//! independent from the 12 color wheel concept.
//!
//! Most of the traits in this module require the color space to have a hue
//! component. You will often see people use [`Hsv`][crate::Hsv] or
//! [`Hsl`][crate::Hsl] when demonstrating some of these techniques, but Palette
//! lets you use any hue based color space. Some traits are also implemented for
//! other color spaces, when it's possible to avoid converting them to their hue
//! based counterparts.

use crate::{angle::HalfRotation, num::Real, ShiftHue};

/// Represents the complementary color scheme.
///
/// A complementary color scheme consists of two colors on the opposite sides of
/// the color wheel.
pub trait Complementary: Sized {
    /// Return the complementary color of `self`.
    ///
    /// This is the same as if the hue of `self` would be rotated by 180°.
    ///
    /// The following example makes a complementary color pair:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(300deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::Complementary};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 8.0, 0.5);
    /// let complementary = primary.complementary();
    ///
    /// let hues = (
    ///     primary.hue.into_positive_degrees(),
    ///     complementary.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (120.0, 300.0));
    /// ```
    fn complementary(self) -> Self;
}

impl<T> Complementary for T
where
    T: ShiftHue,
    T::Scalar: HalfRotation,
{
    fn complementary(self) -> Self {
        self.shift_hue(T::Scalar::half_rotation())
    }
}

/// Represents the split complementary color scheme.
///
/// A split complementary color scheme consists of three colors, where the
/// second and third are adjacent to (30° away from) the complementary color of
/// the first.
pub trait SplitComplementary: Sized {
    /// Return the two split complementary colors of `self`.
    ///
    /// The colors are ordered by ascending hue, or `(hue+150°, hue+210°)`.
    /// Combined with the input color, these make up 3 adjacent colors.
    ///
    /// The following example makes a split complementary color scheme:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(270deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(330deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::SplitComplementary};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 8.0, 0.5);
    /// let (complementary1, complementary2) = primary.split_complementary();
    ///
    /// let hues = (
    ///     primary.hue.into_positive_degrees(),
    ///     complementary1.hue.into_positive_degrees(),
    ///     complementary2.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (120.0, 270.0, 330.0));
    /// ```
    fn split_complementary(self) -> (Self, Self);
}

impl<T> SplitComplementary for T
where
    T: ShiftHue + Clone,
    T::Scalar: Real,
{
    fn split_complementary(self) -> (Self, Self) {
        let first = self.clone().shift_hue(T::Scalar::from_f64(150.0));
        let second = self.shift_hue(T::Scalar::from_f64(210.0));

        (first, second)
    }
}

/// Represents the analogous color scheme on a 12 color wheel.
///
/// An analogous color scheme consists of three colors next to each other (30°
/// apart) on the color wheel.
pub trait Analogous: Sized {
    /// Return the two additional colors of an analogous color scheme.
    ///
    /// The colors are ordered by ascending hue difference, or `(hue-30°,
    /// hue+30°)`. Combined with the input color, these make up 3 adjacent
    /// colors.
    ///
    /// The following example makes a 3 color analogous scheme:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(90deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(150deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::Analogous};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 0.8, 0.5);
    /// let (analog_down, analog_up) = primary.analogous();
    ///
    /// let hues = (
    ///     analog_down.hue.into_positive_degrees(),
    ///     primary.hue.into_positive_degrees(),
    ///     analog_up.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (90.0, 120.0, 150.0));
    /// ```
    fn analogous(self) -> (Self, Self);

    /// Return the next two analogous colors, after the colors `analogous` returns.
    ///
    /// The colors are ordered by ascending hue difference, or `(hue-60°,
    /// hue+60°)`. Combined with the input color and the colors from
    /// `analogous`, these make up 5 adjacent colors.
    ///
    /// The following example makes a 5 color analogous scheme:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(60deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(90deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(150deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(180deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::Analogous};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 0.8, 0.5);
    /// let (analog_down1, analog_up1) = primary.analogous();
    /// let (analog_down2, analog_up2) = primary.analogous_secondary();
    ///
    /// let hues = (
    ///     analog_down2.hue.into_positive_degrees(),
    ///     analog_down1.hue.into_positive_degrees(),
    ///     primary.hue.into_positive_degrees(),
    ///     analog_up1.hue.into_positive_degrees(),
    ///     analog_up2.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (60.0, 90.0, 120.0, 150.0, 180.0));
    /// ```
    fn analogous_secondary(self) -> (Self, Self);
}

impl<T> Analogous for T
where
    T: ShiftHue + Clone,
    T::Scalar: Real,
{
    fn analogous(self) -> (Self, Self) {
        let first = self.clone().shift_hue(T::Scalar::from_f64(330.0));
        let second = self.shift_hue(T::Scalar::from_f64(30.0));

        (first, second)
    }

    fn analogous_secondary(self) -> (Self, Self) {
        let first = self.clone().shift_hue(T::Scalar::from_f64(300.0));
        let second = self.shift_hue(T::Scalar::from_f64(60.0));

        (first, second)
    }
}

/// Represents the triadic color scheme.
///
/// A triadic color scheme consists of thee colors at a 120° distance from each
/// other.
pub trait Triadic: Sized {
    /// Return the two additional colors of a triadic color scheme.
    ///
    /// The colors are ordered by ascending relative hues, or `(hue+120°,
    /// hue+240°)`.
    ///
    /// The following example makes a triadic scheme:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(240deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(0deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::Triadic};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 0.8, 0.5);
    /// let (triadic1, triadic2) = primary.triadic();
    ///
    /// let hues = (
    ///     primary.hue.into_positive_degrees(),
    ///     triadic1.hue.into_positive_degrees(),
    ///     triadic2.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (120.0, 240.0, 0.0));
    /// ```
    fn triadic(self) -> (Self, Self);
}

impl<T> Triadic for T
where
    T: ShiftHue + Clone,
    T::Scalar: Real,
{
    fn triadic(self) -> (Self, Self) {
        let first = self.clone().shift_hue(T::Scalar::from_f64(120.0));
        let second = self.shift_hue(T::Scalar::from_f64(240.0));

        (first, second)
    }
}

/// Represents the tetradic, or square, color scheme.
///
/// A tetradic color scheme consists of four colors at a 90° distance from each
/// other. These form two pairs of complementary colors.
#[doc(alias = "Square")]
pub trait Tetradic: Sized {
    /// Return the three additional colors of a tetradic color scheme.
    ///
    /// The colors are ordered by ascending relative hues, or `(hue+90°,
    /// hue+180°, hue+270°)`.
    ///
    /// The following example makes a tetradic scheme:
    ///
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(120deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(210deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(300deg, 80%, 50%);"></div>
    /// <div style="display: inline-block; width: 3em; height: 1em; border: 1px solid black; background: hsl(30deg, 80%, 50%);"></div>
    ///
    /// ```
    /// use palette::{Hsl, color_theory::Tetradic};
    ///
    /// let primary = Hsl::new_srgb(120.0f32, 0.8, 0.5);
    /// let (tetradic1, tetradic2, tetradic3) = primary.tetradic();
    ///
    /// let hues = (
    ///     primary.hue.into_positive_degrees(),
    ///     tetradic1.hue.into_positive_degrees(),
    ///     tetradic2.hue.into_positive_degrees(),
    ///     tetradic3.hue.into_positive_degrees(),
    /// );
    ///
    /// assert_eq!(hues, (120.0, 210.0, 300.0, 30.0));
    /// ```
    fn tetradic(self) -> (Self, Self, Self);
}

impl<T> Tetradic for T
where
    T: ShiftHue + Clone,
    T::Scalar: Real,
{
    fn tetradic(self) -> (Self, Self, Self) {
        let first = self.clone().shift_hue(T::Scalar::from_f64(90.0));
        let second = self.clone().shift_hue(T::Scalar::from_f64(180.0));
        let third = self.shift_hue(T::Scalar::from_f64(270.0));

        (first, second, third)
    }
}