Skip to main content

typst_library/visualize/
color.rs

1use std::fmt::{self, Debug, Formatter};
2use std::hash::{Hash, Hasher};
3use std::str::FromStr;
4use std::sync::{Arc, LazyLock};
5
6use ecow::{EcoString, EcoVec, eco_format};
7use moxcms::{ColorProfile, Layout, RenderingIntent, TransformOptions};
8use palette::encoding::{self, Linear};
9use palette::{
10    Alpha, Darken, Desaturate, FromColor, Lighten, OklabHue, RgbHue, Saturate, ShiftHue,
11};
12use typst_syntax::{Span, Spanned};
13
14use crate::diag::{At, HintedStrResult, SourceResult, StrResult, bail};
15use crate::foundations::{
16    Args, Array, Func, IntoValue, Module, Repr, Scope, Smart, Str, Value, array, cast,
17    func, repr, scope, ty,
18};
19use crate::layout::{Angle, Ratio};
20
21// Type aliases for `palette` internal types in f32.
22pub type Oklab = palette::oklab::Oklaba<f32>;
23pub type Oklch = palette::oklch::Oklcha<f32>;
24pub type LinearRgb = palette::rgb::Rgba<Linear<encoding::Srgb>, f32>;
25pub type Rgb = palette::rgb::Rgba<encoding::Srgb, f32>;
26pub type Hsl = palette::hsl::Hsla<encoding::Srgb, f32>;
27pub type Hsv = palette::hsv::Hsva<encoding::Srgb, f32>;
28pub type Luma = palette::luma::Lumaa<encoding::Srgb, f32>;
29
30/// The ICC profile used to convert from CMYK to RGB.
31///
32/// This is a minimal CMYK profile that only contains the necessary information
33/// to convert from CMYK to RGB. It is based on the CGATS TR 001-1995
34/// specification. See
35/// <https://github.com/saucecontrol/Compact-ICC-Profiles#cmyk>.
36static CMYK_TO_XYZ: LazyLock<ColorProfile> = LazyLock::new(|| {
37    ColorProfile::new_from_slice(typst_assets::icc::CMYK_TO_XYZ).unwrap()
38});
39
40/// The target sRGB profile.
41static SRGB_PROFILE: LazyLock<ColorProfile> = LazyLock::new(ColorProfile::new_srgb);
42
43static TO_SRGB: LazyLock<Arc<moxcms::Transform8BitExecutor>> = LazyLock::new(|| {
44    CMYK_TO_XYZ
45        .create_transform_8bit(
46            Layout::Rgba,
47            &SRGB_PROFILE,
48            Layout::Rgb,
49            TransformOptions {
50                // Our input profile only supports perceptual intent.
51                rendering_intent: RenderingIntent::Perceptual,
52                ..TransformOptions::default()
53            },
54        )
55        .unwrap()
56});
57
58/// A color in a specific color space.
59///
60/// Typst supports:
61/// - sRGB through the @color.rgb[`rgb` function]
62/// - Device CMYK through the @color.cmyk[`cmyk` function]
63/// - D65 Gray through the @color.luma[`luma` function]
64/// - Oklab through the @color.oklab[`oklab` function]
65/// - Oklch through the @color.oklch[`oklch` function]
66/// - Linear RGB through the @color.linear-rgb[`color.linear-rgb` function]
67/// - HSL through the @color.hsl[`color.hsl` function]
68/// - HSV through the @color.hsv[`color.hsv` function]
69/// - Color spaces described by spot colorants through the
70///   @color.spot[`color.spot` type]
71///
72/// All color spaces except for CMYK and spot colorants have alpha channels.
73///
74/// Throughout the documentation, we use the term _process color_ for colors
75/// that can be blended with each other (currently all colors other than spot
76/// colors). In this term, _process_ signifies that the shade is being created
77/// throughout the printing process instead of ahead of time.
78///
79/// = Example <example>
80/// ```example
81/// #rect(fill: aqua)
82/// ```
83///
84/// = Predefined colors <predefined-colors>
85/// Typst defines the following built-in colors:
86///
87/// #docs-table(
88///   table.header[Color][Definition],
89///
90///   [`black`],
91///   [`{luma(0)}`],
92///
93///   [`gray`],
94///   [`{luma(170)}`],
95///
96///   [`silver`],
97///   [`{luma(221)}`],
98///
99///   [`white`],
100///   [`{luma(255)}`],
101///
102///   [`navy`],
103///   [`{rgb("#001f3f")}`],
104///
105///   [`blue`],
106///   [`{rgb("#0074d9")}`],
107///
108///   [`aqua`],
109///   [`{rgb("#7fdbff")}`],
110///
111///   [`teal`],
112///   [`{rgb("#39cccc")}`],
113///
114///   [`eastern`],
115///   [`{rgb("#239dad")}`],
116///
117///   [`purple`],
118///   [`{rgb("#b10dc9")}`],
119///
120///   [`fuchsia`],
121///   [`{rgb("#f012be")}`],
122///
123///   [`maroon`],
124///   [`{rgb("#85144b")}`],
125///
126///   [`red`],
127///   [`{rgb("#ff4136")}`],
128///
129///   [`orange`],
130///   [`{rgb("#ff851b")}`],
131///
132///   [`yellow`],
133///   [`{rgb("#ffdc00")}`],
134///
135///   [`olive`],
136///   [`{rgb("#3d9970")}`],
137///
138///   [`green`],
139///   [`{rgb("#2ecc40")}`],
140///
141///   [`lime`],
142///   [`{rgb("#01ff70")}`],
143/// )
144///
145/// The predefined colors and the most important color constructors are
146/// available globally and also in the color type's scope, so you can write
147/// either `color.red` or just `red`.
148///
149/// ```preview
150/// #let colors = (
151///   "black", "gray", "silver", "white",
152///   "navy", "blue", "aqua", "teal",
153///   "eastern", "purple", "fuchsia",
154///   "maroon", "red", "orange", "yellow",
155///   "olive", "green", "lime",
156/// )
157///
158/// #set text(font: "PT Sans")
159/// #set page(width: auto)
160/// #grid(
161///   columns: 9,
162///   gutter: 10pt,
163///   ..colors.map(name => {
164///       let col = eval(name)
165///       let luminance = luma(col).components().first()
166///       set text(fill: white) if luminance < 50%
167///       set square(stroke: black) if col == white
168///       set align(center + horizon)
169///       square(size: 50pt,  fill: col, name)
170///   })
171/// )
172/// ```
173///
174/// = Predefined color maps <predefined-color-maps>
175/// Typst also includes a number of preset color maps that can be used for
176/// @gradient:stops[gradients]. These are simply arrays of colors defined in the
177/// module `color.map`.
178///
179/// ```example
180/// #circle(fill: gradient.linear(..color.map.crest))
181/// ```
182///
183/// #docs-table(
184///   table.header[Map][Details],
185///
186///   [`turbo`],
187///   [
188///     A perceptually uniform rainbow-like color map. Read
189///     #link("https://ai.googleblog.com/2019/08/turbo-improved-rainbow-colormap-for.html")[this blog post]
190///     for more details.
191///   ],
192///
193///   [`cividis`],
194///   [
195///     A blue to gray to yellow color map. See
196///     #link("https://bids.github.io/colormap/")[this blog post] for more
197///     details.
198///   ],
199///
200///   [`rainbow`],
201///   [
202///     Cycles through the full color spectrum. This color map is best used by
203///     setting the interpolation color space to @color.hsl[HSL]. The rainbow
204///     gradient is *not suitable* for data visualization because it is not
205///     perceptually uniform, so the differences between values become unclear
206///     to your readers. It should only be used for decorative purposes.
207///   ],
208///
209///   [`spectral`],
210///   [Red to yellow to blue color map.],
211///
212///   [`viridis`],
213///   [A purple to teal to yellow color map.],
214///
215///   [`inferno`],
216///   [A black to red to yellow color map.],
217///
218///   [`magma`],
219///   [A black to purple to yellow color map.],
220///
221///   [`plasma`],
222///   [A purple to pink to yellow color map.],
223///
224///   [`rocket`],
225///   [A black to red to white color map.],
226///
227///   [`mako`],
228///   [A black to teal to white color map.],
229///
230///   [`coolwarm`],
231///   [A blue to white to red color map with smooth transitions.],
232///
233///   [`vlag`],
234///   [A light blue to white to red color map.],
235///
236///   [`icefire`],
237///   [A light teal to black to orange color map.],
238///
239///   [`flare`],
240///   [A orange to purple color map that is perceptually uniform.],
241///
242///   [`crest`],
243///   [A light green to blue color map.],
244/// )
245///
246/// Some popular presets are not included because they are not available under a
247/// free licence. Others, like
248/// #link("https://jakevdp.github.io/blog/2014/10/16/how-bad-is-your-colormap/")[Jet],
249/// are not included because they are not color blind friendly. Feel free to use
250/// or create a package with other presets that are useful to you!
251///
252/// ```preview
253/// #set page(width: auto, height: auto)
254/// #set text(font: "PT Sans", size: 8pt)
255///
256/// #let maps = (
257///   "turbo", "cividis", "rainbow", "spectral",
258///   "viridis", "inferno", "magma", "plasma",
259///   "rocket", "mako", "coolwarm", "vlag",
260///   "icefire", "flare", "crest",
261/// )
262///
263/// #stack(dir: ltr, spacing: 3pt, ..maps.map((name) => {
264///   let map = eval("color.map." + name)
265///   stack(
266///     dir: ttb,
267///     block(
268///       width: 15pt,
269///       height: 100pt,
270///       fill: gradient.linear(..map, angle: 90deg),
271///     ),
272///     block(
273///       width: 15pt,
274///       height: 32pt,
275///       move(dy: 8pt, rotate(90deg, name)),
276///     ),
277///   )
278/// }))
279/// ```
280#[ty(scope, cast)]
281#[derive(Clone, PartialEq, Eq, Hash)]
282pub enum Color {
283    /// A process color.
284    Process(ProcessColor),
285    /// A spot color.
286    Spot(SpotColor),
287}
288
289#[scope]
290impl Color {
291    pub const BLACK: Self = Self::Process(ProcessColor::Luma(Luma::new(0.0, 1.0)));
292    pub const GRAY: Self = Self::Process(ProcessColor::Luma(Luma::new(0.6666666, 1.0)));
293    pub const WHITE: Self = Self::Process(ProcessColor::Luma(Luma::new(1.0, 1.0)));
294    pub const SILVER: Self = Self::Process(ProcessColor::Luma(Luma::new(0.8666667, 1.0)));
295    pub const NAVY: Self =
296        Self::Process(ProcessColor::Rgb(Rgb::new(0.0, 0.121569, 0.247059, 1.0)));
297    pub const BLUE: Self =
298        Self::Process(ProcessColor::Rgb(Rgb::new(0.0, 0.454902, 0.85098, 1.0)));
299    pub const AQUA: Self =
300        Self::Process(ProcessColor::Rgb(Rgb::new(0.4980392, 0.858823, 1.0, 1.0)));
301    pub const TEAL: Self =
302        Self::Process(ProcessColor::Rgb(Rgb::new(0.223529, 0.8, 0.8, 1.0)));
303    pub const EASTERN: Self =
304        Self::Process(ProcessColor::Rgb(Rgb::new(0.13725, 0.615686, 0.678431, 1.0)));
305    pub const PURPLE: Self =
306        Self::Process(ProcessColor::Rgb(Rgb::new(0.694118, 0.050980, 0.788235, 1.0)));
307    pub const FUCHSIA: Self =
308        Self::Process(ProcessColor::Rgb(Rgb::new(0.941177, 0.070588, 0.745098, 1.0)));
309    pub const MAROON: Self =
310        Self::Process(ProcessColor::Rgb(Rgb::new(0.521569, 0.078431, 0.294118, 1.0)));
311    pub const RED: Self =
312        Self::Process(ProcessColor::Rgb(Rgb::new(1.0, 0.254902, 0.211765, 1.0)));
313    pub const ORANGE: Self =
314        Self::Process(ProcessColor::Rgb(Rgb::new(1.0, 0.521569, 0.105882, 1.0)));
315    pub const YELLOW: Self =
316        Self::Process(ProcessColor::Rgb(Rgb::new(1.0, 0.8627451, 0.0, 1.0)));
317    pub const OLIVE: Self =
318        Self::Process(ProcessColor::Rgb(Rgb::new(0.239216, 0.6, 0.4392157, 1.0)));
319    pub const GREEN: Self =
320        Self::Process(ProcessColor::Rgb(Rgb::new(0.1803922, 0.8, 0.2509804, 1.0)));
321    pub const LIME: Self =
322        Self::Process(ProcessColor::Rgb(Rgb::new(0.0039216, 1.0, 0.4392157, 1.0)));
323
324    /// The module of preset color maps.
325    pub const MAP: fn() -> Module = || typst_utils::singleton!(Module, map()).clone();
326
327    /// Create a grayscale color.
328    ///
329    /// A grayscale color is represented internally by a single `lightness`
330    /// component.
331    ///
332    /// These components are also available using the
333    /// @color.components[`components`] method.
334    ///
335    /// ```example
336    /// #for x in range(250, step: 50) {
337    ///   box(square(fill: luma(x)))
338    /// }
339    /// ```
340    #[func]
341    pub fn luma(
342        args: &mut Args,
343        /// The lightness component.
344        #[external]
345        lightness: Component,
346        /// The alpha component.
347        #[external]
348        alpha: RatioComponent,
349        /// Alternatively: The color to convert to grayscale.
350        ///
351        /// If this is given, the `lightness` should not be given.
352        #[external]
353        color: Color,
354    ) -> SourceResult<Color> {
355        Ok(Self::Process(ProcessColor::Luma(
356            if let Some(color) = args.find::<Color>()? {
357                color.to_luma()
358            } else {
359                let Component(gray) =
360                    args.expect("gray component").unwrap_or(Component(Ratio::one()));
361                let RatioComponent(alpha) =
362                    args.eat()?.unwrap_or(RatioComponent(Ratio::one()));
363                Luma::new(gray.get() as f32, alpha.get() as f32)
364            },
365        )))
366    }
367
368    /// Create an #link("https://bottosson.github.io/posts/oklab/")[Oklab]
369    /// color.
370    ///
371    /// This color space is well suited for the following use cases:
372    /// - Color manipulation such as saturating while keeping perceived hue
373    /// - Creating grayscale images with uniform perceived lightness
374    /// - Creating smooth and uniform color transition and gradients
375    ///
376    /// A linear Oklab color is represented internally by an array of four
377    /// components:
378    /// - lightness (@ratio)
379    /// - a (@float or @ratio. Ratios are relative to `{0.4}`; meaning `{50%}`
380    ///   is equal to `{0.2}`)
381    /// - b (@float or @ratio. Ratios are relative to `{0.4}`; meaning `{50%}`
382    ///   is equal to `{0.2}`)
383    /// - alpha (@ratio)
384    ///
385    /// These components are also available using the
386    /// @color.components[`components`] method.
387    ///
388    /// ```example
389    /// #square(
390    ///   fill: oklab(27%, 20%, -3%, 50%)
391    /// )
392    /// ```
393    #[func]
394    pub fn oklab(
395        args: &mut Args,
396        /// The lightness component.
397        #[external]
398        lightness: RatioComponent,
399        /// The a ("green/red") component.
400        #[external]
401        a: ChromaComponent,
402        /// The b ("blue/yellow") component.
403        #[external]
404        b: ChromaComponent,
405        /// The alpha component.
406        #[external]
407        alpha: RatioComponent,
408        /// Alternatively: The color to convert to Oklab.
409        ///
410        /// If this is given, the individual components should not be given.
411        #[external]
412        color: Color,
413    ) -> SourceResult<Color> {
414        Ok(Self::Process(ProcessColor::Oklab(
415            if let Some(color) = args.find::<Color>()? {
416                color.to_oklab()
417            } else {
418                let RatioComponent(l) = args.expect("lightness component")?;
419                let ChromaComponent(a) = args.expect("A component")?;
420                let ChromaComponent(b) = args.expect("B component")?;
421                let RatioComponent(alpha) =
422                    args.eat()?.unwrap_or(RatioComponent(Ratio::one()));
423                Oklab::new(l.get() as f32, a, b, alpha.get() as f32)
424            },
425        )))
426    }
427
428    /// Create an #link("https://bottosson.github.io/posts/oklab/")[Oklch]
429    /// color.
430    ///
431    /// This color space is well suited for the following use cases:
432    /// - Color manipulation involving lightness, chroma, and hue
433    /// - Creating grayscale images with uniform perceived lightness
434    /// - Creating smooth and uniform color transition and gradients
435    ///
436    /// A linear Oklch color is represented internally by an array of four
437    /// components:
438    /// - lightness (@ratio)
439    /// - chroma (@float or @ratio. Ratios are relative to `{0.4}`; meaning
440    ///   `{50%}` is equal to `{0.2}`)
441    /// - hue (@angle)
442    /// - alpha (@ratio)
443    ///
444    /// These components are also available using the
445    /// @color.components[`components`] method.
446    ///
447    /// ```example
448    /// #square(
449    ///   fill: oklch(40%, 0.2, 160deg, 50%)
450    /// )
451    /// ```
452    #[func]
453    pub fn oklch(
454        args: &mut Args,
455        /// The lightness component.
456        #[external]
457        lightness: RatioComponent,
458        /// The chroma component.
459        #[external]
460        chroma: ChromaComponent,
461        /// The hue component.
462        #[external]
463        hue: Angle,
464        /// The alpha component.
465        #[external]
466        alpha: RatioComponent,
467        /// Alternatively: The color to convert to Oklch.
468        ///
469        /// If this is given, the individual components should not be given.
470        #[external]
471        color: Color,
472    ) -> SourceResult<Color> {
473        Ok(Self::Process(ProcessColor::Oklch(
474            if let Some(color) = args.find::<Color>()? {
475                color.to_oklch()
476            } else {
477                let RatioComponent(l) = args.expect("lightness component")?;
478                let ChromaComponent(c) = args.expect("chroma component")?;
479                let h: Angle = args.expect("hue component")?;
480                let RatioComponent(alpha) =
481                    args.eat()?.unwrap_or(RatioComponent(Ratio::one()));
482                Oklch::new(
483                    l.get() as f32,
484                    c,
485                    OklabHue::from_degrees(h.to_deg() as f32),
486                    alpha.get() as f32,
487                )
488            },
489        )))
490    }
491
492    /// Create an RGB(A) color with linear luma.
493    ///
494    /// This color space is similar to sRGB, but with the distinction that the
495    /// color component are not gamma corrected. This makes it easier to perform
496    /// color operations such as blending and interpolation. Although, you
497    /// should prefer to use the @color.oklab[`oklab` function] for these.
498    ///
499    /// A linear RGB(A) color is represented internally by an array of four
500    /// components:
501    /// - red (@ratio)
502    /// - green (@ratio)
503    /// - blue (@ratio)
504    /// - alpha (@ratio)
505    ///
506    /// These components are also available using the
507    /// @color.components[`components`] method.
508    ///
509    /// ```example
510    /// #square(fill: color.linear-rgb(
511    ///   30%, 50%, 10%,
512    /// ))
513    /// ```
514    #[func(title = "Linear RGB")]
515    pub fn linear_rgb(
516        args: &mut Args,
517        /// The red component.
518        #[external]
519        red: Component,
520        /// The green component.
521        #[external]
522        green: Component,
523        /// The blue component.
524        #[external]
525        blue: Component,
526        /// The alpha component.
527        #[external]
528        alpha: Component,
529        /// Alternatively: The color to convert to linear RGB(A).
530        ///
531        /// If this is given, the individual components should not be given.
532        #[external]
533        color: Color,
534    ) -> SourceResult<Color> {
535        Ok(Self::Process(ProcessColor::LinearRgb(
536            if let Some(color) = args.find::<Color>()? {
537                color.to_linear_rgb()
538            } else {
539                let Component(r) = args.expect("red component")?;
540                let Component(g) = args.expect("green component")?;
541                let Component(b) = args.expect("blue component")?;
542                let Component(a) = args.eat()?.unwrap_or(Component(Ratio::one()));
543                LinearRgb::new(
544                    r.get() as f32,
545                    g.get() as f32,
546                    b.get() as f32,
547                    a.get() as f32,
548                )
549            },
550        )))
551    }
552
553    /// Create an RGB(A) color.
554    ///
555    /// The color is specified in the sRGB color space.
556    ///
557    /// An RGB(A) color is represented internally by an array of four
558    /// components:
559    /// - red (@ratio)
560    /// - green (@ratio)
561    /// - blue (@ratio)
562    /// - alpha (@ratio)
563    ///
564    /// These components are also available using the
565    /// @color.components[`components`] method.
566    ///
567    /// ```example
568    /// #square(fill: rgb("#b1f2eb"))
569    /// #square(fill: rgb(87, 127, 230))
570    /// #square(fill: rgb(25%, 13%, 65%))
571    /// ```
572    #[func(title = "RGB")]
573    pub fn rgb(
574        args: &mut Args,
575        /// The red component.
576        #[external]
577        red: Component,
578        /// The green component.
579        #[external]
580        green: Component,
581        /// The blue component.
582        #[external]
583        blue: Component,
584        /// The alpha component.
585        #[external]
586        alpha: Component,
587        /// Alternatively: The color in hexadecimal notation.
588        ///
589        /// Accepts three, four, six or eight hexadecimal digits and optionally
590        /// a leading hash.
591        ///
592        /// If this is given, the individual components should not be given.
593        ///
594        /// ```example
595        /// #text(16pt, rgb("#239dad"))[
596        ///   *Typst*
597        /// ]
598        /// ```
599        #[external]
600        hex: Str,
601        /// Alternatively: The color to convert to RGB(a).
602        ///
603        /// If this is given, the individual components should not be given.
604        #[external]
605        color: Color,
606    ) -> SourceResult<Color> {
607        Ok(if let Some(string) = args.find::<Spanned<Str>>()? {
608            Self::from_str(&string.v).at(string.span)?
609        } else if let Some(color) = args.find::<Color>()? {
610            Self::Process(ProcessColor::Rgb(color.to_rgb()))
611        } else {
612            let Component(r) = args.expect("red component")?;
613            let Component(g) = args.expect("green component")?;
614            let Component(b) = args.expect("blue component")?;
615            let Component(a) = args.eat()?.unwrap_or(Component(Ratio::one()));
616            Self::Process(ProcessColor::Rgb(Rgb::new(
617                r.get() as f32,
618                g.get() as f32,
619                b.get() as f32,
620                a.get() as f32,
621            )))
622        })
623    }
624
625    /// Create a CMYK color.
626    ///
627    /// This is useful if you want to target a specific printer. The conversion
628    /// to RGB for display preview might differ from how your printer reproduces
629    /// the color.
630    ///
631    /// A CMYK color is represented internally by an array of four components:
632    /// - cyan (@ratio)
633    /// - magenta (@ratio)
634    /// - yellow (@ratio)
635    /// - key (@ratio)
636    ///
637    /// These components are also available using the
638    /// @color.components[`components`] method.
639    ///
640    /// Note that CMYK colors are not currently supported when PDF/A output is
641    /// enabled.
642    ///
643    /// ```example
644    /// #square(
645    ///   fill: cmyk(27%, 0%, 3%, 5%)
646    /// )
647    /// ```
648    #[func(title = "CMYK")]
649    pub fn cmyk(
650        args: &mut Args,
651        /// The cyan component.
652        #[external]
653        cyan: RatioComponent,
654        /// The magenta component.
655        #[external]
656        magenta: RatioComponent,
657        /// The yellow component.
658        #[external]
659        yellow: RatioComponent,
660        /// The key component.
661        #[external]
662        key: RatioComponent,
663        /// Alternatively: The color to convert to CMYK.
664        ///
665        /// If this is given, the individual components should not be given.
666        #[external]
667        color: Color,
668    ) -> SourceResult<Color> {
669        Ok(Self::Process(ProcessColor::Cmyk(
670            if let Some(color) = args.find::<Color>()? {
671                color.to_cmyk()
672            } else {
673                let RatioComponent(c) = args.expect("cyan component")?;
674                let RatioComponent(m) = args.expect("magenta component")?;
675                let RatioComponent(y) = args.expect("yellow component")?;
676                let RatioComponent(k) = args.expect("key/black component")?;
677                Cmyk::new(c.get() as f32, m.get() as f32, y.get() as f32, k.get() as f32)
678            },
679        )))
680    }
681
682    /// Create an HSL color.
683    ///
684    /// This color space is useful for specifying colors by hue, saturation and
685    /// lightness. It is also useful for color manipulation, such as saturating
686    /// while keeping perceived hue.
687    ///
688    /// An HSL color is represented internally by an array of four components:
689    /// - hue (@angle)
690    /// - saturation (@ratio)
691    /// - lightness (@ratio)
692    /// - alpha (@ratio)
693    ///
694    /// These components are also available using the
695    /// @color.components[`components`] method.
696    ///
697    /// ```example
698    /// #square(
699    ///   fill: color.hsl(30deg, 50%, 60%)
700    /// )
701    /// ```
702    #[func(title = "HSL")]
703    pub fn hsl(
704        args: &mut Args,
705        /// The hue angle.
706        #[external]
707        hue: Angle,
708        /// The saturation component.
709        #[external]
710        saturation: Component,
711        /// The lightness component.
712        #[external]
713        lightness: Component,
714        /// The alpha component.
715        #[external]
716        alpha: Component,
717        /// Alternatively: The color to convert to HSL.
718        ///
719        /// If this is given, the individual components should not be given.
720        #[external]
721        color: Color,
722    ) -> SourceResult<Color> {
723        Ok(Self::Process(ProcessColor::Hsl(if let Some(color) = args.find::<Color>()? {
724            color.to_hsl()
725        } else {
726            let h: Angle = args.expect("hue component")?;
727            let Component(s) = args.expect("saturation component")?;
728            let Component(l) = args.expect("lightness component")?;
729            let Component(a) = args.eat()?.unwrap_or(Component(Ratio::one()));
730            Hsl::new(
731                RgbHue::from_degrees(h.to_deg() as f32),
732                s.get() as f32,
733                l.get() as f32,
734                a.get() as f32,
735            )
736        })))
737    }
738
739    /// Create an HSV color.
740    ///
741    /// This color space is useful for specifying colors by hue, saturation and
742    /// value. It is also useful for color manipulation, such as saturating
743    /// while keeping perceived hue.
744    ///
745    /// An HSV color is represented internally by an array of four components:
746    /// - hue (@angle)
747    /// - saturation (@ratio)
748    /// - value (@ratio)
749    /// - alpha (@ratio)
750    ///
751    /// These components are also available using the
752    /// @color.components[`components`] method.
753    ///
754    /// ```example
755    /// #square(
756    ///   fill: color.hsv(30deg, 50%, 60%)
757    /// )
758    /// ```
759    #[func(title = "HSV")]
760    pub fn hsv(
761        args: &mut Args,
762        /// The hue angle.
763        #[external]
764        hue: Angle,
765        /// The saturation component.
766        #[external]
767        saturation: Component,
768        /// The value component.
769        #[external]
770        value: Component,
771        /// The alpha component.
772        #[external]
773        alpha: Component,
774        /// Alternatively: The color to convert to HSL.
775        ///
776        /// If this is given, the individual components should not be given.
777        #[external]
778        color: Color,
779    ) -> SourceResult<Color> {
780        Ok(Self::Process(ProcessColor::Hsv(if let Some(color) = args.find::<Color>()? {
781            color.to_hsv()
782        } else {
783            let h: Angle = args.expect("hue component")?;
784            let Component(s) = args.expect("saturation component")?;
785            let Component(v) = args.expect("value component")?;
786            let Component(a) = args.eat()?.unwrap_or(Component(Ratio::one()));
787            Hsv::new(
788                RgbHue::from_degrees(h.to_deg() as f32),
789                s.get() as f32,
790                v.get() as f32,
791                a.get() as f32,
792            )
793        })))
794    }
795
796    /// Extracts the components of this color.
797    ///
798    /// The size and values of this array depends on the color space. You can
799    /// obtain the color space using @color.space[`space`]. Below is a table of
800    /// the color spaces and their components:
801    ///
802    /// #docs-table(
803    ///   table.header[Color space][C1][C2][C3][C4],
804    ///
805    ///   [@color.luma[`luma`]],
806    ///   [Lightness],
807    ///   [],
808    ///   [],
809    ///   [],
810    ///
811    ///   [@color.oklab[`oklab`]],
812    ///   [Lightness],
813    ///   [`a`],
814    ///   [`b`],
815    ///   [Alpha],
816    ///
817    ///   [@color.oklch[`oklch`]],
818    ///   [Lightness],
819    ///   [Chroma],
820    ///   [Hue],
821    ///   [Alpha],
822    ///
823    ///   [@color.linear-rgb[`linear-rgb`]],
824    ///   [Red],
825    ///   [Green],
826    ///   [Blue],
827    ///   [Alpha],
828    ///
829    ///   [@color.rgb[`rgb`]],
830    ///   [Red],
831    ///   [Green],
832    ///   [Blue],
833    ///   [Alpha],
834    ///
835    ///   [@color.cmyk[`cmyk`]],
836    ///   [Cyan],
837    ///   [Magenta],
838    ///   [Yellow],
839    ///   [Key],
840    ///
841    ///   [@color.hsl[`hsl`]],
842    ///   [Hue],
843    ///   [Saturation],
844    ///   [Lightness],
845    ///   [Alpha],
846    ///
847    ///   [@color.hsv[`hsv`]],
848    ///   [Hue],
849    ///   [Saturation],
850    ///   [Value],
851    ///   [Alpha],
852    ///
853    ///   [@color.spot[`spot`]],
854    ///   [Tint],
855    ///   none,
856    ///   none,
857    ///   none,
858    /// )
859    ///
860    /// For the meaning and type of each individual value, see the documentation
861    /// of the corresponding color space. The alpha component is optional and
862    /// only included if the `alpha` argument is `true`. The length of the
863    /// returned array depends on the number of components and whether the alpha
864    /// component is included.
865    ///
866    /// ```example
867    /// // note that the alpha component is included by default
868    /// #rgb(40%, 60%, 80%).components()
869    /// ```
870    #[func]
871    pub fn components(
872        &self,
873        /// Whether to include the alpha component.
874        #[named]
875        #[default(true)]
876        alpha: bool,
877    ) -> Array {
878        match self {
879            Self::Process(c) => c.components(alpha),
880            Self::Spot(c) => array![c.tint],
881        }
882    }
883
884    /// Returns the constructor function for this color's space.
885    ///
886    /// Returns one of:
887    /// - @color.luma[`luma`]
888    /// - @color.oklab[`oklab`]
889    /// - @color.oklch[`oklch`]
890    /// - @color.linear-rgb[`linear-rgb`]
891    /// - @color.rgb[`rgb`]
892    /// - @color.cmyk[`cmyk`]
893    /// - @color.hsl[`hsl`]
894    /// - @color.hsv[`hsv`]
895    ///
896    /// ```example
897    /// #let color = cmyk(1%, 2%, 3%, 4%)
898    /// #(color.space() == cmyk)
899    /// ```
900    #[func]
901    pub fn space(&self) -> ColorSpace {
902        match self {
903            Self::Process(c) => c.space().into(),
904            Self::Spot(c) => ColorSpace::Spot(c.colorant.as_ref().clone()),
905        }
906    }
907
908    /// Returns the color's RGB(A) hex representation (such as `#ffaa32` or
909    /// `#020304fe`). The alpha component (last two digits in `#020304fe`) is
910    /// omitted if it is equal to `ff` (255 / 100%).
911    #[func]
912    pub fn to_hex(&self) -> EcoString {
913        match self {
914            Self::Process(c) => c.to_hex(),
915            Self::Spot(c) => c.fallback().to_hex(),
916        }
917    }
918
919    /// Lightens a color by a given factor.
920    #[func]
921    pub fn lighten(
922        &self,
923        /// The factor to lighten the color by.
924        factor: Ratio,
925    ) -> Color {
926        match self {
927            Self::Process(c) => Self::Process(c.lighten(factor)),
928            Self::Spot(c) => Self::Spot(c.lighten(factor)),
929        }
930    }
931
932    /// Darkens a color by a given factor.
933    #[func]
934    pub fn darken(
935        &self,
936        /// The factor to darken the color by.
937        factor: Ratio,
938    ) -> Color {
939        match self {
940            Self::Process(c) => Self::Process(c.darken(factor)),
941            Self::Spot(c) => Self::Spot(c.darken(factor)),
942        }
943    }
944
945    /// Increases the saturation of a color by a given factor.
946    ///
947    /// Only process colors can be saturated. If you want to saturate a spot
948    /// color, convert it into a process color first.
949    #[func]
950    pub fn saturate(
951        &self,
952        span: Span,
953        /// The factor to saturate the color by.
954        factor: Ratio,
955    ) -> SourceResult<Color> {
956        Ok(match self {
957            Self::Process(c) => Self::Process(c.saturate(span, factor)?),
958            Self::Spot(_) => bail!(
959                span,
960                "cannot saturate spot color";
961                hint: "try using the fallback color instead";
962            ),
963        })
964    }
965
966    /// Decreases the saturation of a color by a given factor.
967    ///
968    /// Only process colors can be desaturated. If you want to desaturate a spot
969    /// color, convert it into a process color first.
970    #[func]
971    pub fn desaturate(
972        &self,
973        span: Span,
974        /// The factor to desaturate the color by.
975        factor: Ratio,
976    ) -> SourceResult<Color> {
977        Ok(match self {
978            Self::Process(c) => Self::Process(c.desaturate(span, factor)?),
979            Self::Spot(_) => bail!(
980                span,
981                "cannot desaturate spot color";
982                hint: "try using the fallback color instead";
983            ),
984        })
985    }
986
987    /// Produces the complementary color using a provided color space. You can
988    /// think of it as the opposite side on a color wheel.
989    ///
990    /// ```example
991    /// #square(fill: yellow)
992    /// #square(fill: yellow.negate())
993    /// #square(fill: yellow.negate(space: rgb))
994    /// ```
995    #[func]
996    pub fn negate(
997        &self,
998        /// The color space used for the transformation. By default, a
999        /// perceptual color space is used.
1000        #[named]
1001        #[default]
1002        space: Smart<ColorSpace>,
1003    ) -> HintedStrResult<Color> {
1004        let target_space = space.unwrap_or_else(|| match self.space() {
1005            ColorSpace::Process(_) => ColorSpace::Process(ProcessColorSpace::Oklab),
1006            ColorSpace::Spot(s) => ColorSpace::Spot(s),
1007        });
1008
1009        let result = match self.to_space(&target_space)? {
1010            Self::Process(c) => Self::Process(c.negate(c.space())),
1011            Self::Spot(c) => Self::Spot(c.negate()),
1012        };
1013        result.to_space(&self.space())
1014    }
1015
1016    /// Rotates the hue of the color by a given angle.
1017    ///
1018    /// This function only works on color models with a well-defined hue
1019    /// component, i.e. Oklch, HSL, and HSV.
1020    #[func]
1021    pub fn rotate(
1022        &self,
1023        span: Span,
1024        /// The angle to rotate the hue by.
1025        angle: Angle,
1026        /// The color space used to rotate. By default, this happens in a
1027        /// perceptual color space (@color.oklch[`oklch`]).
1028        #[named]
1029        #[default(ProcessColorSpace::Oklch)]
1030        space: ProcessColorSpace,
1031    ) -> SourceResult<Color> {
1032        Ok(match space {
1033            ProcessColorSpace::Oklch => {
1034                let oklch = self.to_oklch();
1035                let rotated = oklch.shift_hue(angle.to_deg() as f32);
1036                Self::Process(ProcessColor::Oklch(rotated))
1037                    .to_space(&self.space())
1038                    .at(span)?
1039            }
1040            ProcessColorSpace::Hsl => {
1041                let hsl = self.to_hsl();
1042                let rotated = hsl.shift_hue(angle.to_deg() as f32);
1043                Self::Process(ProcessColor::Hsl(rotated))
1044                    .to_space(&self.space())
1045                    .at(span)?
1046            }
1047            ProcessColorSpace::Hsv => {
1048                let hsv = self.to_hsv();
1049                let rotated = hsv.shift_hue(angle.to_deg() as f32);
1050                Self::Process(ProcessColor::Hsv(rotated))
1051                    .to_space(&self.space())
1052                    .at(span)?
1053            }
1054            _ => bail!(span, "this color space does not support hue rotation"),
1055        })
1056    }
1057
1058    /// Create a color by mixing two or more colors.
1059    ///
1060    /// In color spaces with a hue component (HSL, HSV, Oklch), only two colors
1061    /// can be mixed at once. Mixing more than two colors in such a space will
1062    /// result in an error!
1063    ///
1064    /// ```example
1065    /// #set block(height: 20pt, width: 100%)
1066    /// #block(fill: red.mix(blue))
1067    /// #block(fill: red.mix(blue, space: rgb))
1068    /// #block(fill: color.mix(red, blue, white))
1069    /// #block(fill: color.mix((red, 70%), (blue, 30%)))
1070    /// ```
1071    #[func]
1072    pub fn mix(
1073        /// The colors, optionally with weights, specified as a pair (array of
1074        /// length two) of color and weight (float or ratio).
1075        ///
1076        /// The weights do not need to add to `{100%}`, they are relative to the
1077        /// sum of all weights.
1078        #[variadic]
1079        colors: Vec<WeightedColor>,
1080        /// The color space to mix in. By default, this happens in a perceptual
1081        /// color space (@color.oklab[`oklab`]) or, if all colors use the same
1082        /// spot colorant, using that colorant.
1083        ///
1084        /// All colors will be converted into this color space.
1085        #[named]
1086        #[default]
1087        space: Smart<ColorSpace>,
1088    ) -> HintedStrResult<Color> {
1089        Self::mix_iter(colors, space)
1090    }
1091
1092    /// Makes a color more transparent by a given factor.
1093    ///
1094    /// This method is relative to the existing alpha value. If the scale is
1095    /// positive, calculates `alpha - alpha * scale`. Negative scales behave
1096    /// like `color.opacify(-scale)`.
1097    ///
1098    /// ```example
1099    /// #block(fill: red)[opaque]
1100    /// #block(fill: red.transparentize(50%))[half red]
1101    /// #block(fill: red.transparentize(75%))[quarter red]
1102    /// ```
1103    #[func]
1104    pub fn transparentize(
1105        &self,
1106        /// The factor to change the alpha value by.
1107        scale: Ratio,
1108    ) -> StrResult<Color> {
1109        self.scale_alpha(-scale)
1110    }
1111
1112    /// Makes a color more opaque by a given scale.
1113    ///
1114    /// This method is relative to the existing alpha value. If the scale is
1115    /// positive, calculates `alpha + scale - alpha * scale`. Negative scales
1116    /// behave like `color.transparentize(-scale)`.
1117    ///
1118    /// ```example
1119    /// #let half-red = red.transparentize(50%)
1120    /// #block(fill: half-red.opacify(100%))[opaque]
1121    /// #block(fill: half-red.opacify(50%))[three quarters red]
1122    /// #block(fill: half-red.opacify(-50%))[one quarter red]
1123    /// ```
1124    #[func]
1125    pub fn opacify(
1126        &self,
1127        /// The scale to change the alpha value by.
1128        scale: Ratio,
1129    ) -> StrResult<Color> {
1130        self.scale_alpha(scale)
1131    }
1132
1133    type SpotColorant;
1134}
1135
1136impl Color {
1137    /// Same as [`Color::mix`], but takes an iterator instead of a vector.
1138    pub fn mix_iter(
1139        colors: impl IntoIterator<
1140            Item = WeightedColor,
1141            IntoIter = impl ExactSizeIterator<Item = WeightedColor>,
1142        > + Clone,
1143        space: Smart<ColorSpace>,
1144    ) -> HintedStrResult<Color> {
1145        let space =
1146            resolve_color_space(space, colors.clone().into_iter().map(|wc| wc.color));
1147
1148        let mut colors = colors.into_iter();
1149        if space.hue_index().is_some() && colors.len() > 2 {
1150            bail!("cannot mix more than two colors in a hue-based space");
1151        }
1152
1153        let m = if space.hue_index().is_some() && colors.len() == 2 {
1154            let mut m = [0.0; 4];
1155
1156            let WeightedColor { color: c0, weight: w0 } = colors.next().unwrap();
1157            let WeightedColor { color: c1, weight: w1 } = colors.next().unwrap();
1158
1159            let c0 = c0.to_space(&space)?.to_vec4();
1160            let c1 = c1.to_space(&space)?.to_vec4();
1161            let w0 = w0 as f32;
1162            let w1 = w1 as f32;
1163
1164            if w0 + w1 <= 0.0 {
1165                bail!("sum of weights must be positive");
1166            }
1167
1168            for i in 0..4 {
1169                m[i] = (w0 * c0[i] + w1 * c1[i]) / (w0 + w1);
1170            }
1171
1172            // Ensure that the hue circle is traversed in the short direction.
1173            if let Some(index) = space.hue_index()
1174                && (c0[index] - c1[index]).abs() > 180.0
1175            {
1176                let (h0, h1) = if c0[index] < c1[index] {
1177                    (c0[index] + 360.0, c1[index])
1178                } else {
1179                    (c0[index], c1[index] + 360.0)
1180                };
1181                m[index] = (w0 * h0 + w1 * h1) / (w0 + w1);
1182            }
1183
1184            m
1185        } else {
1186            let mut total = 0.0;
1187            let mut acc = [0.0; 4];
1188
1189            for WeightedColor { color, weight } in colors {
1190                let weight = weight as f32;
1191                let v = color.to_space(&space)?.to_vec4();
1192                acc[0] += weight * v[0];
1193                acc[1] += weight * v[1];
1194                acc[2] += weight * v[2];
1195                acc[3] += weight * v[3];
1196                total += weight;
1197            }
1198
1199            if total <= 0.0 {
1200                bail!("sum of weights must be positive");
1201            }
1202
1203            acc.map(|v| v / total)
1204        };
1205
1206        Ok(match space {
1207            ColorSpace::Process(ProcessColorSpace::Oklab) => {
1208                Color::Process(ProcessColor::Oklab(Oklab::new(m[0], m[1], m[2], m[3])))
1209            }
1210            ColorSpace::Process(ProcessColorSpace::Oklch) => {
1211                Color::Process(ProcessColor::Oklch(Oklch::new(m[0], m[1], m[2], m[3])))
1212            }
1213            ColorSpace::Process(ProcessColorSpace::Srgb) => {
1214                Color::Process(ProcessColor::Rgb(Rgb::new(m[0], m[1], m[2], m[3])))
1215            }
1216            ColorSpace::Process(ProcessColorSpace::LinearRgb) => Color::Process(
1217                ProcessColor::LinearRgb(LinearRgb::new(m[0], m[1], m[2], m[3])),
1218            ),
1219            ColorSpace::Process(ProcessColorSpace::Hsl) => Color::Process(
1220                ProcessColor::Hsl(Hsl::new(RgbHue::from_degrees(m[0]), m[1], m[2], m[3])),
1221            ),
1222            ColorSpace::Process(ProcessColorSpace::Hsv) => Color::Process(
1223                ProcessColor::Hsv(Hsv::new(RgbHue::from_degrees(m[0]), m[1], m[2], m[3])),
1224            ),
1225            ColorSpace::Process(ProcessColorSpace::Cmyk) => {
1226                Color::Process(ProcessColor::Cmyk(Cmyk::new(m[0], m[1], m[2], m[3])))
1227            }
1228            ColorSpace::Process(ProcessColorSpace::D65Gray) => {
1229                Color::Process(ProcessColor::Luma(Luma::new(m[0], m[3])))
1230            }
1231            ColorSpace::Spot(colorant) => Color::Spot(SpotColor::new(
1232                Arc::new(colorant),
1233                Ratio::new(m[0].clamp(0.0, 1.0) as f64),
1234            )),
1235        })
1236    }
1237
1238    /// Construct a new RGBA color from 8-bit values.
1239    pub fn from_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
1240        Self::Process(ProcessColor::from_u8(r, g, b, a))
1241    }
1242
1243    /// Converts a 32-bit integer to an RGBA color.
1244    pub fn from_u32(color: u32) -> Self {
1245        Self::Process(ProcessColor::from_u32(color))
1246    }
1247
1248    /// Returns the alpha channel of the color, if it has one.
1249    pub fn alpha(&self) -> Option<f32> {
1250        match self {
1251            Color::Process(c) => c.alpha(),
1252            Color::Spot(_) => None,
1253        }
1254    }
1255
1256    /// Sets the alpha channel of the color, if it has one.
1257    pub fn with_alpha(self, alpha: f32) -> Self {
1258        match self {
1259            Color::Process(c) => c.with_alpha(alpha).into(),
1260            Color::Spot(_) => self,
1261        }
1262    }
1263
1264    /// Scales the alpha value of a color by a given amount.
1265    ///
1266    /// For positive scales, computes `alpha + scale - alpha * scale`. For
1267    /// non-positive scales, computes `alpha + alpha * scale`.
1268    ///
1269    /// Note that this function will fail for colors in a color space without an
1270    /// alpha component.
1271    fn scale_alpha(&self, scale: Ratio) -> StrResult<Self> {
1272        Ok(match self {
1273            Color::Process(c) => c.scale_alpha(scale)?.into(),
1274            Color::Spot(_) => bail!("spot colors do not have an alpha component"),
1275        })
1276    }
1277
1278    /// Converts the color to a vec of four floats.
1279    pub fn to_vec4(&self) -> [f32; 4] {
1280        match self {
1281            Color::Process(c) => c.to_vec4(),
1282            Color::Spot(c) => [c.tint.get() as f32; 4],
1283        }
1284    }
1285
1286    /// Converts the color to a vec of four [`u8`]s.
1287    pub fn to_vec4_u8(&self) -> [u8; 4] {
1288        self.to_vec4().map(|x| (x * 255.0).round() as u8)
1289    }
1290
1291    pub fn to_space(&self, space: &ColorSpace) -> HintedStrResult<Self> {
1292        Ok(match (space, self) {
1293            (ColorSpace::Process(s), Self::Process(c)) => c.to_space(*s).into(),
1294            (ColorSpace::Process(s), Self::Spot(c)) => c.fallback().to_space(*s).into(),
1295            (ColorSpace::Spot(s), Self::Spot(c)) => {
1296                if s == c.colorant.as_ref() {
1297                    self.clone()
1298                } else {
1299                    bail!(
1300                        "cannot convert spot color to different spot colorant";
1301                        hint: "colorant of the color is `{}`", c.colorant.name.repr();
1302                        hint: "colorant of the color space is `{}`", s.name.repr();
1303                    )
1304                }
1305            }
1306            (ColorSpace::Spot(s), Self::Process(_)) => {
1307                bail!(
1308                    "cannot convert process color to spot colorant space";
1309                    hint: "process color cannot be expressed in spot colorant `{}`", s.name.repr();
1310                    hint: "try converting the spot color to the process space instead";
1311                )
1312            }
1313        })
1314    }
1315
1316    pub fn to_process_space(&self, space: ProcessColorSpace) -> Self {
1317        match self {
1318            Self::Process(c) => c.to_space(space).into(),
1319            Self::Spot(c) => c.fallback().to_space(space).into(),
1320        }
1321    }
1322
1323    pub fn to_luma(&self) -> Luma {
1324        match self {
1325            Self::Process(c) => c.to_luma(),
1326            Self::Spot(c) => c.fallback().to_luma(),
1327        }
1328    }
1329
1330    pub fn to_oklab(&self) -> Oklab {
1331        match self {
1332            Self::Process(c) => c.to_oklab(),
1333            Self::Spot(c) => c.fallback().to_oklab(),
1334        }
1335    }
1336
1337    pub fn to_oklch(&self) -> Oklch {
1338        match self {
1339            Self::Process(c) => c.to_oklch(),
1340            Self::Spot(c) => c.fallback().to_oklch(),
1341        }
1342    }
1343
1344    pub fn to_rgb(&self) -> Rgb {
1345        match self {
1346            Self::Process(c) => c.to_rgb(),
1347            Self::Spot(c) => c.fallback().to_rgb(),
1348        }
1349    }
1350
1351    pub fn to_linear_rgb(&self) -> LinearRgb {
1352        match self {
1353            Self::Process(c) => c.to_linear_rgb(),
1354            Self::Spot(c) => c.fallback().to_linear_rgb(),
1355        }
1356    }
1357
1358    pub fn to_cmyk(&self) -> Cmyk {
1359        match self {
1360            Self::Process(c) => c.to_cmyk(),
1361            Self::Spot(c) => c.fallback().to_cmyk(),
1362        }
1363    }
1364
1365    pub fn to_hsl(&self) -> Hsl {
1366        match self {
1367            Self::Process(c) => c.to_hsl(),
1368            Self::Spot(c) => c.fallback().to_hsl(),
1369        }
1370    }
1371
1372    pub fn to_hsv(&self) -> Hsv {
1373        match self {
1374            Self::Process(c) => c.to_hsv(),
1375            Self::Spot(c) => c.fallback().to_hsv(),
1376        }
1377    }
1378
1379    /// Convert to `ProcessColor`, using fallback for spot colors.
1380    ///
1381    /// This is useful for rendering and export where spot colors cannot be
1382    /// expressed and need to be converted to their fallback representation.
1383    pub fn to_process(&self) -> ProcessColor {
1384        match self {
1385            Self::Process(c) => *c,
1386            Self::Spot(c) => c.fallback(),
1387        }
1388    }
1389}
1390
1391impl Debug for Color {
1392    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1393        match self {
1394            Self::Process(c) => Debug::fmt(c, f),
1395            Self::Spot(c) => Debug::fmt(c, f),
1396        }
1397    }
1398}
1399
1400impl Repr for Color {
1401    fn repr(&self) -> EcoString {
1402        match self {
1403            Self::Process(c) => Repr::repr(c),
1404            Self::Spot(c) => Repr::repr(c),
1405        }
1406    }
1407}
1408
1409impl FromStr for Color {
1410    type Err = &'static str;
1411
1412    fn from_str(s: &str) -> Result<Self, Self::Err> {
1413        Ok(ProcessColor::from_str(s)?.into())
1414    }
1415}
1416
1417impl<C> From<C> for Color
1418where
1419    C: Into<ProcessColor>,
1420{
1421    fn from(color: C) -> Self {
1422        Self::Process(color.into())
1423    }
1424}
1425
1426impl From<SpotColor> for Color {
1427    fn from(c: SpotColor) -> Self {
1428        Self::Spot(c)
1429    }
1430}
1431
1432/// Colors with a well-defined color space.
1433#[derive(Copy, Clone)]
1434pub enum ProcessColor {
1435    /// A 32-bit luma color.
1436    Luma(Luma),
1437    /// A 32-bit L\*a\*b\* color in the Oklab color space.
1438    Oklab(Oklab),
1439    /// A 32-bit LCh color in the Oklab color space.
1440    Oklch(Oklch),
1441    /// A 32-bit RGB color.
1442    Rgb(Rgb),
1443    /// A 32-bit linear RGB color.
1444    LinearRgb(LinearRgb),
1445    /// A 32-bit CMYK color.
1446    Cmyk(Cmyk),
1447    /// A 32-bit HSL color.
1448    Hsl(Hsl),
1449    /// A 32-bit HSV color.
1450    Hsv(Hsv),
1451}
1452
1453impl ProcessColor {
1454    /// Extracts the components of this color.
1455    pub fn components(self, alpha: bool) -> Array {
1456        let mut components = match self {
1457            Self::Luma(c) => {
1458                array![Ratio::new(c.luma.into()), Ratio::new(c.alpha.into())]
1459            }
1460            Self::Oklab(c) => {
1461                array![
1462                    Ratio::new(c.l.into()),
1463                    f64::from(c.a),
1464                    f64::from(c.b),
1465                    Ratio::new(c.alpha.into())
1466                ]
1467            }
1468            Self::Oklch(c) => {
1469                array![
1470                    Ratio::new(c.l.into()),
1471                    f64::from(c.chroma),
1472                    hue_angle(c.hue.into_degrees()),
1473                    Ratio::new(c.alpha.into()),
1474                ]
1475            }
1476            Self::LinearRgb(c) => {
1477                array![
1478                    Ratio::new(c.red.into()),
1479                    Ratio::new(c.green.into()),
1480                    Ratio::new(c.blue.into()),
1481                    Ratio::new(c.alpha.into()),
1482                ]
1483            }
1484            Self::Rgb(c) => {
1485                array![
1486                    Ratio::new(c.red.into()),
1487                    Ratio::new(c.green.into()),
1488                    Ratio::new(c.blue.into()),
1489                    Ratio::new(c.alpha.into()),
1490                ]
1491            }
1492            Self::Cmyk(c) => {
1493                array![
1494                    Ratio::new(c.c.into()),
1495                    Ratio::new(c.m.into()),
1496                    Ratio::new(c.y.into()),
1497                    Ratio::new(c.k.into())
1498                ]
1499            }
1500            Self::Hsl(c) => {
1501                array![
1502                    hue_angle(c.hue.into_degrees()),
1503                    Ratio::new(c.saturation.into()),
1504                    Ratio::new(c.lightness.into()),
1505                    Ratio::new(c.alpha.into()),
1506                ]
1507            }
1508            Self::Hsv(c) => {
1509                array![
1510                    hue_angle(c.hue.into_degrees()),
1511                    Ratio::new(c.saturation.into()),
1512                    Ratio::new(c.value.into()),
1513                    Ratio::new(c.alpha.into()),
1514                ]
1515            }
1516        };
1517        // Remove the alpha component if the corresponding argument was set.
1518        if !alpha && !matches!(self, Self::Cmyk(_)) {
1519            let _ = components.pop();
1520        }
1521        components
1522    }
1523
1524    /// Returns the color's RGB(A) hex representation.
1525    pub fn to_hex(&self) -> EcoString {
1526        let (r, g, b, a) = self.to_rgb().into_format::<u8, u8>().into_components();
1527        if a != 255 {
1528            eco_format!("#{r:02x}{g:02x}{b:02x}{a:02x}")
1529        } else {
1530            eco_format!("#{r:02x}{g:02x}{b:02x}")
1531        }
1532    }
1533
1534    /// Returns the constructor function for this color's space.
1535    pub fn space(self) -> ProcessColorSpace {
1536        match self {
1537            Self::Luma(_) => ProcessColorSpace::D65Gray,
1538            Self::Oklab(_) => ProcessColorSpace::Oklab,
1539            Self::Oklch(_) => ProcessColorSpace::Oklch,
1540            Self::LinearRgb(_) => ProcessColorSpace::LinearRgb,
1541            Self::Rgb(_) => ProcessColorSpace::Srgb,
1542            Self::Cmyk(_) => ProcessColorSpace::Cmyk,
1543            Self::Hsl(_) => ProcessColorSpace::Hsl,
1544            Self::Hsv(_) => ProcessColorSpace::Hsv,
1545        }
1546    }
1547
1548    /// Lightens a color by a given factor.
1549    pub fn lighten(self, factor: Ratio) -> ProcessColor {
1550        let factor = factor.get() as f32;
1551        match self {
1552            Self::Luma(c) => Self::Luma(c.lighten(factor)),
1553            Self::Oklab(c) => Self::Oklab(c.lighten(factor)),
1554            Self::Oklch(c) => Self::Oklch(c.lighten(factor)),
1555            Self::LinearRgb(c) => Self::LinearRgb(c.lighten(factor)),
1556            Self::Rgb(c) => Self::Rgb(c.lighten(factor)),
1557            Self::Cmyk(c) => Self::Cmyk(c.lighten(factor)),
1558            Self::Hsl(c) => Self::Hsl(c.lighten(factor)),
1559            Self::Hsv(c) => Self::Hsv(c.lighten(factor)),
1560        }
1561    }
1562
1563    /// Darkens a color by a given factor.
1564    pub fn darken(self, factor: Ratio) -> ProcessColor {
1565        let factor = factor.get() as f32;
1566        match self {
1567            Self::Luma(c) => Self::Luma(c.darken(factor)),
1568            Self::Oklab(c) => Self::Oklab(c.darken(factor)),
1569            Self::Oklch(c) => Self::Oklch(c.darken(factor)),
1570            Self::LinearRgb(c) => Self::LinearRgb(c.darken(factor)),
1571            Self::Rgb(c) => Self::Rgb(c.darken(factor)),
1572            Self::Cmyk(c) => Self::Cmyk(c.darken(factor)),
1573            Self::Hsl(c) => Self::Hsl(c.darken(factor)),
1574            Self::Hsv(c) => Self::Hsv(c.darken(factor)),
1575        }
1576    }
1577
1578    /// Increases the saturation of a color by a given factor.
1579    pub fn saturate(self, span: Span, factor: Ratio) -> SourceResult<ProcessColor> {
1580        let f = factor.get() as f32;
1581        Ok(match self {
1582            Self::Luma(_) => bail!(
1583                span, "cannot saturate grayscale color";
1584                hint: "try converting your color to RGB first";
1585            ),
1586            Self::Hsl(c) => Self::Hsl(c.saturate(f)),
1587            Self::Hsv(c) => Self::Hsv(c.saturate(f)),
1588            Self::Oklab(_)
1589            | Self::Oklch(_)
1590            | Self::LinearRgb(_)
1591            | Self::Rgb(_)
1592            | Self::Cmyk(_) => {
1593                Self::Hsv(self.to_hsv().saturate(f)).to_space(self.space())
1594            }
1595        })
1596    }
1597
1598    /// Decreases the saturation of a color by a given factor.
1599    pub fn desaturate(self, span: Span, factor: Ratio) -> SourceResult<ProcessColor> {
1600        let f = factor.get() as f32;
1601        Ok(match self {
1602            Self::Luma(_) => bail!(
1603                span, "cannot desaturate grayscale color";
1604                hint: "try converting your color to RGB first";
1605            ),
1606            Self::Hsl(c) => Self::Hsl(c.desaturate(f)),
1607            Self::Hsv(c) => Self::Hsv(c.desaturate(f)),
1608            Self::Oklab(_)
1609            | Self::Oklch(_)
1610            | Self::LinearRgb(_)
1611            | Self::Rgb(_)
1612            | Self::Cmyk(_) => {
1613                Self::Hsv(self.to_hsv().desaturate(f)).to_space(self.space())
1614            }
1615        })
1616    }
1617
1618    /// Produces the complementary color using a provided color space.
1619    pub fn negate(self, space: ProcessColorSpace) -> ProcessColor {
1620        let result = match self.to_space(space) {
1621            Self::Luma(c) => Self::Luma(Luma::new(1.0 - c.luma, c.alpha)),
1622            Self::Oklab(c) => Self::Oklab(Oklab::new(1.0 - c.l, -c.a, -c.b, c.alpha)),
1623            Self::Oklch(c) => Self::Oklch(Oklch::new(
1624                1.0 - c.l,
1625                c.chroma,
1626                OklabHue::from_degrees(c.hue.into_degrees() + 180.0),
1627                c.alpha,
1628            )),
1629            Self::LinearRgb(c) => Self::LinearRgb(LinearRgb::new(
1630                1.0 - c.red,
1631                1.0 - c.green,
1632                1.0 - c.blue,
1633                c.alpha,
1634            )),
1635            Self::Rgb(c) => {
1636                Self::Rgb(Rgb::new(1.0 - c.red, 1.0 - c.green, 1.0 - c.blue, c.alpha))
1637            }
1638            Self::Cmyk(c) => Self::Cmyk(Cmyk::new(1.0 - c.c, 1.0 - c.m, 1.0 - c.y, c.k)),
1639            Self::Hsl(c) => Self::Hsl(Hsl::new(
1640                RgbHue::from_degrees(c.hue.into_degrees() + 180.0),
1641                c.saturation,
1642                c.lightness,
1643                c.alpha,
1644            )),
1645            Self::Hsv(c) => Self::Hsv(Hsv::new(
1646                RgbHue::from_degrees(c.hue.into_degrees() + 180.0),
1647                c.saturation,
1648                c.value,
1649                c.alpha,
1650            )),
1651        };
1652        result.to_space(self.space())
1653    }
1654
1655    /// Construct a new RGBA color from 8-bit values.
1656    pub fn from_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
1657        Self::Rgb(Rgb::new(
1658            f32::from(r) / 255.0,
1659            f32::from(g) / 255.0,
1660            f32::from(b) / 255.0,
1661            f32::from(a) / 255.0,
1662        ))
1663    }
1664
1665    /// Converts a 32-bit integer to an RGBA color.
1666    pub fn from_u32(color: u32) -> Self {
1667        Self::from_u8(
1668            ((color >> 24) & 0xFF) as u8,
1669            ((color >> 16) & 0xFF) as u8,
1670            ((color >> 8) & 0xFF) as u8,
1671            (color & 0xFF) as u8,
1672        )
1673    }
1674
1675    /// Returns the alpha channel of the color, if it has one.
1676    pub fn alpha(self) -> Option<f32> {
1677        match self {
1678            Self::Cmyk(_) => None,
1679            Self::Luma(c) => Some(c.alpha),
1680            Self::Oklab(c) => Some(c.alpha),
1681            Self::Oklch(c) => Some(c.alpha),
1682            Self::Rgb(c) => Some(c.alpha),
1683            Self::LinearRgb(c) => Some(c.alpha),
1684            Self::Hsl(c) => Some(c.alpha),
1685            Self::Hsv(c) => Some(c.alpha),
1686        }
1687    }
1688
1689    /// Sets the alpha channel of the color, if it has one.
1690    pub fn with_alpha(mut self, alpha: f32) -> Self {
1691        match &mut self {
1692            Self::Cmyk(_) => {}
1693            Self::Luma(c) => c.alpha = alpha,
1694            Self::Oklab(c) => c.alpha = alpha,
1695            Self::Oklch(c) => c.alpha = alpha,
1696            Self::Rgb(c) => c.alpha = alpha,
1697            Self::LinearRgb(c) => c.alpha = alpha,
1698            Self::Hsl(c) => c.alpha = alpha,
1699            Self::Hsv(c) => c.alpha = alpha,
1700        }
1701
1702        self
1703    }
1704
1705    /// Scales the alpha value of a color by a given amount.
1706    ///
1707    /// For positive scales, computes `alpha + scale - alpha * scale`.
1708    /// For non-positive scales, computes `alpha + alpha * scale`.
1709    fn scale_alpha(self, scale: Ratio) -> StrResult<Self> {
1710        #[inline]
1711        fn transform<C>(mut color: Alpha<C, f32>, scale: Ratio) -> Alpha<C, f32> {
1712            let scale = scale.get() as f32;
1713            let factor = if scale > 0.0 { 1.0 - color.alpha } else { color.alpha };
1714            color.alpha = (color.alpha + scale * factor).clamp(0.0, 1.0);
1715            color
1716        }
1717
1718        Ok(match self {
1719            Self::Luma(c) => Self::Luma(transform(c, scale)),
1720            Self::Oklab(c) => Self::Oklab(transform(c, scale)),
1721            Self::Oklch(c) => Self::Oklch(transform(c, scale)),
1722            Self::Rgb(c) => Self::Rgb(transform(c, scale)),
1723            Self::LinearRgb(c) => Self::LinearRgb(transform(c, scale)),
1724            Self::Cmyk(_) => bail!("CMYK does not have an alpha component"),
1725            Self::Hsl(c) => Self::Hsl(transform(c, scale)),
1726            Self::Hsv(c) => Self::Hsv(transform(c, scale)),
1727        })
1728    }
1729
1730    /// Converts the color to a vec of four floats.
1731    pub fn to_vec4(self) -> [f32; 4] {
1732        match self {
1733            Self::Luma(c) => [c.luma, c.luma, c.luma, c.alpha],
1734            Self::Oklab(c) => [c.l, c.a, c.b, c.alpha],
1735            Self::Oklch(c) => {
1736                [c.l, c.chroma, c.hue.into_degrees().rem_euclid(360.0), c.alpha]
1737            }
1738            Self::Rgb(c) => [c.red, c.green, c.blue, c.alpha],
1739            Self::LinearRgb(c) => [c.red, c.green, c.blue, c.alpha],
1740            Self::Cmyk(c) => [c.c, c.m, c.y, c.k],
1741            Self::Hsl(c) => [
1742                c.hue.into_degrees().rem_euclid(360.0),
1743                c.saturation,
1744                c.lightness,
1745                c.alpha,
1746            ],
1747            Self::Hsv(c) => {
1748                [c.hue.into_degrees().rem_euclid(360.0), c.saturation, c.value, c.alpha]
1749            }
1750        }
1751    }
1752
1753    /// Converts the color to a vec of four [`u8`]s.
1754    pub fn to_vec4_u8(&self) -> [u8; 4] {
1755        self.to_vec4().map(|x| (x * 255.0).round() as u8)
1756    }
1757
1758    pub fn to_space(&self, space: ProcessColorSpace) -> Self {
1759        match space {
1760            ProcessColorSpace::D65Gray => Self::Luma(self.to_luma()),
1761            ProcessColorSpace::Oklab => Self::Oklab(self.to_oklab()),
1762            ProcessColorSpace::Oklch => Self::Oklch(self.to_oklch()),
1763            ProcessColorSpace::Srgb => Self::Rgb(self.to_rgb()),
1764            ProcessColorSpace::LinearRgb => Self::LinearRgb(self.to_linear_rgb()),
1765            ProcessColorSpace::Cmyk => Self::Cmyk(self.to_cmyk()),
1766            ProcessColorSpace::Hsl => Self::Hsl(self.to_hsl()),
1767            ProcessColorSpace::Hsv => Self::Hsv(self.to_hsv()),
1768        }
1769    }
1770
1771    pub fn to_luma(self) -> Luma {
1772        match self {
1773            Self::Luma(c) => c,
1774            Self::Oklab(c) => Luma::from_color(*c),
1775            Self::Oklch(c) => Luma::from_color(*c),
1776            Self::Rgb(c) => Luma::from_color(*c),
1777            Self::LinearRgb(c) => Luma::from_color(*c),
1778            Self::Cmyk(c) => Luma::from_color(c.to_rgba()),
1779            Self::Hsl(c) => Luma::from_color(*c),
1780            Self::Hsv(c) => Luma::from_color(*c),
1781        }
1782    }
1783
1784    pub fn to_oklab(self) -> Oklab {
1785        match self {
1786            Self::Luma(c) => Oklab::from_color(c),
1787            Self::Oklab(c) => c,
1788            Self::Oklch(c) => Oklab::from_color(c),
1789            Self::Rgb(c) => Oklab::from_color(c),
1790            Self::LinearRgb(c) => Oklab::from_color(c),
1791            Self::Cmyk(c) => Oklab::from_color(c.to_rgba()),
1792            Self::Hsl(c) => Oklab::from_color(c),
1793            Self::Hsv(c) => Oklab::from_color(c),
1794        }
1795    }
1796
1797    pub fn to_oklch(self) -> Oklch {
1798        match self {
1799            Self::Luma(c) => Oklch::from_color(c),
1800            Self::Oklab(c) => Oklch::from_color(c),
1801            Self::Oklch(c) => c,
1802            Self::Rgb(c) => Oklch::from_color(c),
1803            Self::LinearRgb(c) => Oklch::from_color(c),
1804            Self::Cmyk(c) => Oklch::from_color(c.to_rgba()),
1805            Self::Hsl(c) => Oklch::from_color(c),
1806            Self::Hsv(c) => Oklch::from_color(c),
1807        }
1808    }
1809
1810    pub fn to_rgb(self) -> Rgb {
1811        match self {
1812            Self::Luma(c) => Rgb::from_color(c),
1813            Self::Oklab(c) => Rgb::from_color(c),
1814            Self::Oklch(c) => Rgb::from_color(c),
1815            Self::Rgb(c) => c,
1816            Self::LinearRgb(c) => Rgb::from_linear(c),
1817            Self::Cmyk(c) => Rgb::from_color(c.to_rgba()),
1818            Self::Hsl(c) => Rgb::from_color(c),
1819            Self::Hsv(c) => Rgb::from_color(c),
1820        }
1821    }
1822
1823    pub fn to_linear_rgb(self) -> LinearRgb {
1824        match self {
1825            Self::Luma(c) => LinearRgb::from_color(c),
1826            Self::Oklab(c) => LinearRgb::from_color(c),
1827            Self::Oklch(c) => LinearRgb::from_color(c),
1828            Self::Rgb(c) => LinearRgb::from_color(c),
1829            Self::LinearRgb(c) => c,
1830            Self::Cmyk(c) => LinearRgb::from_color(c.to_rgba()),
1831            Self::Hsl(c) => Rgb::from_color(c).into_linear(),
1832            Self::Hsv(c) => Rgb::from_color(c).into_linear(),
1833        }
1834    }
1835
1836    pub fn to_cmyk(self) -> Cmyk {
1837        match self {
1838            Self::Luma(c) => Cmyk::from_luma(c),
1839            Self::Oklab(c) => Cmyk::from_rgba(Rgb::from_color(c)),
1840            Self::Oklch(c) => Cmyk::from_rgba(Rgb::from_color(c)),
1841            Self::Rgb(c) => Cmyk::from_rgba(c),
1842            Self::LinearRgb(c) => Cmyk::from_rgba(Rgb::from_linear(c)),
1843            Self::Cmyk(c) => c,
1844            Self::Hsl(c) => Cmyk::from_rgba(Rgb::from_color(c)),
1845            Self::Hsv(c) => Cmyk::from_rgba(Rgb::from_color(c)),
1846        }
1847    }
1848
1849    pub fn to_hsl(self) -> Hsl {
1850        match self {
1851            Self::Luma(c) => Hsl::from_color(c),
1852            Self::Oklab(c) => Hsl::from_color(c),
1853            Self::Oklch(c) => Hsl::from_color(c),
1854            Self::Rgb(c) => Hsl::from_color(c),
1855            Self::LinearRgb(c) => Hsl::from_color(Rgb::from_linear(c)),
1856            Self::Cmyk(c) => Hsl::from_color(c.to_rgba()),
1857            Self::Hsl(c) => c,
1858            Self::Hsv(c) => Hsl::from_color(c),
1859        }
1860    }
1861
1862    pub fn to_hsv(self) -> Hsv {
1863        match self {
1864            Self::Luma(c) => Hsv::from_color(c),
1865            Self::Oklab(c) => Hsv::from_color(c),
1866            Self::Oklch(c) => Hsv::from_color(c),
1867            Self::Rgb(c) => Hsv::from_color(c),
1868            Self::LinearRgb(c) => Hsv::from_color(Rgb::from_linear(c)),
1869            Self::Cmyk(c) => Hsv::from_color(c.to_rgba()),
1870            Self::Hsl(c) => Hsv::from_color(c),
1871            Self::Hsv(c) => c,
1872        }
1873    }
1874}
1875
1876impl Debug for ProcessColor {
1877    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1878        match self {
1879            Self::Luma(v) => write!(f, "Luma({}, {})", v.luma, v.alpha),
1880            Self::Oklab(v) => write!(f, "Oklab({}, {}, {}, {})", v.l, v.a, v.b, v.alpha),
1881            Self::Oklch(v) => {
1882                write!(
1883                    f,
1884                    "Oklch({}, {}, {:?}, {})",
1885                    v.l,
1886                    v.chroma,
1887                    hue_angle(v.hue.into_degrees()),
1888                    v.alpha
1889                )
1890            }
1891            Self::Rgb(v) => {
1892                write!(f, "Rgb({}, {}, {}, {})", v.red, v.green, v.blue, v.alpha)
1893            }
1894            Self::LinearRgb(v) => {
1895                write!(f, "LinearRgb({}, {}, {}, {})", v.red, v.green, v.blue, v.alpha)
1896            }
1897            Self::Cmyk(v) => write!(f, "Cmyk({}, {}, {}, {})", v.c, v.m, v.y, v.k),
1898            Self::Hsl(v) => write!(
1899                f,
1900                "Hsl({:?}, {}, {}, {})",
1901                hue_angle(v.hue.into_degrees()),
1902                v.saturation,
1903                v.lightness,
1904                v.alpha
1905            ),
1906            Self::Hsv(v) => write!(
1907                f,
1908                "Hsv({:?}, {}, {}, {})",
1909                hue_angle(v.hue.into_degrees()),
1910                v.saturation,
1911                v.value,
1912                v.alpha
1913            ),
1914        }
1915    }
1916}
1917
1918impl Repr for ProcessColor {
1919    fn repr(&self) -> EcoString {
1920        match self {
1921            Self::Luma(c) => {
1922                if c.alpha == 1.0 {
1923                    eco_format!("luma({})", Ratio::new(c.luma.into()).repr())
1924                } else {
1925                    eco_format!(
1926                        "luma({}, {})",
1927                        Ratio::new(c.luma.into()).repr(),
1928                        Ratio::new(c.alpha.into()).repr(),
1929                    )
1930                }
1931            }
1932            Self::Rgb(_) => eco_format!("rgb({})", self.to_hex().repr()),
1933            Self::LinearRgb(c) => {
1934                if c.alpha == 1.0 {
1935                    eco_format!(
1936                        "color.linear-rgb({}, {}, {})",
1937                        Ratio::new(c.red.into()).repr(),
1938                        Ratio::new(c.green.into()).repr(),
1939                        Ratio::new(c.blue.into()).repr(),
1940                    )
1941                } else {
1942                    eco_format!(
1943                        "color.linear-rgb({}, {}, {}, {})",
1944                        Ratio::new(c.red.into()).repr(),
1945                        Ratio::new(c.green.into()).repr(),
1946                        Ratio::new(c.blue.into()).repr(),
1947                        Ratio::new(c.alpha.into()).repr(),
1948                    )
1949                }
1950            }
1951            Self::Cmyk(c) => {
1952                eco_format!(
1953                    "cmyk({}, {}, {}, {})",
1954                    Ratio::new(c.c.into()).repr(),
1955                    Ratio::new(c.m.into()).repr(),
1956                    Ratio::new(c.y.into()).repr(),
1957                    Ratio::new(c.k.into()).repr(),
1958                )
1959            }
1960            Self::Oklab(c) => {
1961                if c.alpha == 1.0 {
1962                    eco_format!(
1963                        "oklab({}, {}, {})",
1964                        Ratio::new(c.l.into()).repr(),
1965                        repr::format_float_component(c.a.into()),
1966                        repr::format_float_component(c.b.into()),
1967                    )
1968                } else {
1969                    eco_format!(
1970                        "oklab({}, {}, {}, {})",
1971                        Ratio::new(c.l.into()).repr(),
1972                        repr::format_float_component(c.a.into()),
1973                        repr::format_float_component(c.b.into()),
1974                        Ratio::new(c.alpha.into()).repr(),
1975                    )
1976                }
1977            }
1978            Self::Oklch(c) => {
1979                if c.alpha == 1.0 {
1980                    eco_format!(
1981                        "oklch({}, {}, {})",
1982                        Ratio::new(c.l.into()).repr(),
1983                        repr::format_float_component(c.chroma.into()),
1984                        hue_angle(c.hue.into_degrees()).repr(),
1985                    )
1986                } else {
1987                    eco_format!(
1988                        "oklch({}, {}, {}, {})",
1989                        Ratio::new(c.l.into()).repr(),
1990                        repr::format_float_component(c.chroma.into()),
1991                        hue_angle(c.hue.into_degrees()).repr(),
1992                        Ratio::new(c.alpha.into()).repr(),
1993                    )
1994                }
1995            }
1996            Self::Hsl(c) => {
1997                if c.alpha == 1.0 {
1998                    eco_format!(
1999                        "color.hsl({}, {}, {})",
2000                        hue_angle(c.hue.into_degrees()).repr(),
2001                        Ratio::new(c.saturation.into()).repr(),
2002                        Ratio::new(c.lightness.into()).repr(),
2003                    )
2004                } else {
2005                    eco_format!(
2006                        "color.hsl({}, {}, {}, {})",
2007                        hue_angle(c.hue.into_degrees()).repr(),
2008                        Ratio::new(c.saturation.into()).repr(),
2009                        Ratio::new(c.lightness.into()).repr(),
2010                        Ratio::new(c.alpha.into()).repr(),
2011                    )
2012                }
2013            }
2014            Self::Hsv(c) => {
2015                if c.alpha == 1.0 {
2016                    eco_format!(
2017                        "color.hsv({}, {}, {})",
2018                        hue_angle(c.hue.into_degrees()).repr(),
2019                        Ratio::new(c.saturation.into()).repr(),
2020                        Ratio::new(c.value.into()).repr(),
2021                    )
2022                } else {
2023                    eco_format!(
2024                        "color.hsv({}, {}, {}, {})",
2025                        hue_angle(c.hue.into_degrees()).repr(),
2026                        Ratio::new(c.saturation.into()).repr(),
2027                        Ratio::new(c.value.into()).repr(),
2028                        Ratio::new(c.alpha.into()).repr(),
2029                    )
2030                }
2031            }
2032        }
2033    }
2034}
2035
2036impl Eq for ProcessColor {}
2037
2038impl PartialEq for ProcessColor {
2039    fn eq(&self, other: &Self) -> bool {
2040        match (self, other) {
2041            // Lower precision for comparison to avoid rounding errors.
2042            // Keeps backward compatibility with previous versions of Typst.
2043            (Self::Rgb(_), Self::Rgb(_)) => self.to_vec4_u8() == other.to_vec4_u8(),
2044            (Self::Luma(a), Self::Luma(b)) => {
2045                (a.luma * 255.0).round() as u8 == (b.luma * 255.0).round() as u8
2046            }
2047            (Self::Oklab(a), Self::Oklab(b)) => a == b,
2048            (Self::Oklch(a), Self::Oklch(b)) => a == b,
2049            (Self::LinearRgb(a), Self::LinearRgb(b)) => a == b,
2050            (Self::Cmyk(a), Self::Cmyk(b)) => a == b,
2051            (Self::Hsl(a), Self::Hsl(b)) => a == b,
2052            (Self::Hsv(a), Self::Hsv(b)) => a == b,
2053            _ => false,
2054        }
2055    }
2056}
2057
2058impl Hash for ProcessColor {
2059    fn hash<H: Hasher>(&self, state: &mut H) {
2060        core::mem::discriminant(self).hash(state);
2061        let [x, y, z, w] = self.to_vec4();
2062        x.to_bits().hash(state);
2063        y.to_bits().hash(state);
2064        z.to_bits().hash(state);
2065        w.to_bits().hash(state);
2066    }
2067}
2068
2069impl FromStr for ProcessColor {
2070    type Err = &'static str;
2071
2072    /// Constructs a new color from hex strings like the following:
2073    /// - `#aef` (shorthand, with leading hash),
2074    /// - `7a03c2` (without alpha),
2075    /// - `abcdefff` (with alpha).
2076    ///
2077    /// The hash is optional and both lower and upper case are fine.
2078    fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
2079        let hex_str = hex_str.strip_prefix('#').unwrap_or(hex_str);
2080        if hex_str.chars().any(|c| !c.is_ascii_hexdigit()) {
2081            return Err("color string contains non-hexadecimal letters");
2082        }
2083
2084        let len = hex_str.len();
2085        let long = len == 6 || len == 8;
2086        let short = len == 3 || len == 4;
2087        let alpha = len == 4 || len == 8;
2088        if !long && !short {
2089            return Err("color string has wrong length");
2090        }
2091
2092        let mut values: [u8; 4] = [u8::MAX; 4];
2093        for elem in if alpha { 0..4 } else { 0..3 } {
2094            let item_len = if long { 2 } else { 1 };
2095            let pos = elem * item_len;
2096
2097            let item = &hex_str[pos..(pos + item_len)];
2098            values[elem] = u8::from_str_radix(item, 16).unwrap();
2099
2100            if short {
2101                // Duplicate number for shorthand notation, i.e. `a` -> `aa`
2102                values[elem] += values[elem] * 16;
2103            }
2104        }
2105
2106        Ok(Self::from_u8(values[0], values[1], values[2], values[3]))
2107    }
2108}
2109
2110impl From<Luma> for ProcessColor {
2111    fn from(c: Luma) -> Self {
2112        Self::Luma(c)
2113    }
2114}
2115
2116impl From<Oklab> for ProcessColor {
2117    fn from(c: Oklab) -> Self {
2118        Self::Oklab(c)
2119    }
2120}
2121
2122impl From<Oklch> for ProcessColor {
2123    fn from(c: Oklch) -> Self {
2124        Self::Oklch(c)
2125    }
2126}
2127
2128impl From<Rgb> for ProcessColor {
2129    fn from(c: Rgb) -> Self {
2130        Self::Rgb(c)
2131    }
2132}
2133
2134impl From<LinearRgb> for ProcessColor {
2135    fn from(c: LinearRgb) -> Self {
2136        Self::LinearRgb(c)
2137    }
2138}
2139
2140impl From<Cmyk> for ProcessColor {
2141    fn from(c: Cmyk) -> Self {
2142        Self::Cmyk(c)
2143    }
2144}
2145
2146impl From<Hsl> for ProcessColor {
2147    fn from(c: Hsl) -> Self {
2148        Self::Hsl(c)
2149    }
2150}
2151
2152impl From<Hsv> for ProcessColor {
2153    fn from(c: Hsv) -> Self {
2154        Self::Hsv(c)
2155    }
2156}
2157
2158cast! {
2159    ProcessColor,
2160    self => Color::from(self).into_value(),
2161    v: Color => match v {
2162        Color::Process(c) => c,
2163        Color::Spot(_) => bail!("spot color found where process color is required")
2164    },
2165}
2166
2167fn hue_angle(degrees: f32) -> Angle {
2168    Angle::deg(f64::from(degrees).rem_euclid(360.0))
2169}
2170
2171/// An 8-bit CMYK color.
2172#[derive(Debug, Copy, Clone, PartialEq)]
2173pub struct Cmyk {
2174    /// The cyan component.
2175    pub c: f32,
2176    /// The magenta component.
2177    pub m: f32,
2178    /// The yellow component.
2179    pub y: f32,
2180    /// The key (black) component.
2181    pub k: f32,
2182}
2183
2184impl Cmyk {
2185    fn new(c: f32, m: f32, y: f32, k: f32) -> Self {
2186        Self { c, m, y, k }
2187    }
2188
2189    fn from_luma(luma: Luma) -> Self {
2190        let l = 1.0 - luma.luma;
2191        Cmyk::new(l * 0.75, l * 0.68, l * 0.67, l * 0.90)
2192    }
2193
2194    // This still uses naive conversion, because the profile doesn't
2195    // support converting _to_ CMYK.
2196    fn from_rgba(rgba: Rgb) -> Self {
2197        let r = rgba.red;
2198        let g = rgba.green;
2199        let b = rgba.blue;
2200
2201        let k = 1.0 - r.max(g).max(b);
2202        if k == 1.0 {
2203            return Cmyk::new(0.0, 0.0, 0.0, 1.0);
2204        }
2205
2206        let c = (1.0 - r - k) / (1.0 - k);
2207        let m = (1.0 - g - k) / (1.0 - k);
2208        let y = (1.0 - b - k) / (1.0 - k);
2209
2210        Cmyk::new(c, m, y, k)
2211    }
2212
2213    fn to_rgba(self) -> Rgb {
2214        let mut dest: [u8; 3] = [0; 3];
2215        TO_SRGB
2216            .transform(
2217                &[
2218                    (self.c * 255.0).round() as u8,
2219                    (self.m * 255.0).round() as u8,
2220                    (self.y * 255.0).round() as u8,
2221                    (self.k * 255.0).round() as u8,
2222                ],
2223                &mut dest,
2224            )
2225            .unwrap();
2226
2227        Rgb::new(
2228            f32::from(dest[0]) / 255.0,
2229            f32::from(dest[1]) / 255.0,
2230            f32::from(dest[2]) / 255.0,
2231            1.0,
2232        )
2233    }
2234
2235    fn lighten(self, factor: f32) -> Self {
2236        let lighten = |u: f32| (u - u * factor).clamp(0.0, 1.0);
2237        Self::new(lighten(self.c), lighten(self.m), lighten(self.y), lighten(self.k))
2238    }
2239
2240    fn darken(self, factor: f32) -> Self {
2241        let darken = |u: f32| (u + (1.0 - u) * factor).clamp(0.0, 1.0);
2242        Self::new(darken(self.c), darken(self.m), darken(self.y), darken(self.k))
2243    }
2244}
2245
2246/// A spot color.
2247#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2248pub struct SpotColor {
2249    /// Which colorant to apply.
2250    pub colorant: Arc<SpotColorant>,
2251    /// How much of the colorant to apply to the medium.
2252    pub tint: Ratio,
2253}
2254
2255impl SpotColor {
2256    pub fn new(colorant: Arc<SpotColorant>, tint: Ratio) -> Self {
2257        Self { tint, colorant }
2258    }
2259}
2260
2261impl SpotColor {
2262    /// Lightens the spot color by a given factor.
2263    pub fn lighten(&self, factor: Ratio) -> SpotColor {
2264        let tint = self.tint.get();
2265        SpotColor::new(
2266            self.colorant.clone(),
2267            Ratio::new((tint - tint * factor.get()).clamp(0.0, 1.0)),
2268        )
2269    }
2270
2271    /// Darkens the spot color by a given factor.
2272    pub fn darken(&self, factor: Ratio) -> SpotColor {
2273        let tint = self.tint.get();
2274        SpotColor::new(
2275            self.colorant.clone(),
2276            Ratio::new((tint + (1.0 - tint) * factor.get()).clamp(0.0, 1.0)),
2277        )
2278    }
2279
2280    /// Negates the spot color (inverts the tint).
2281    pub fn negate(&self) -> SpotColor {
2282        SpotColor::new(self.colorant.clone(), Ratio::new(1.0 - self.tint.get()))
2283    }
2284
2285    /// Returns the fallback process color with the tint applied.
2286    pub fn fallback(&self) -> ProcessColor {
2287        self.colorant.fallback.lighten(Ratio::one() - self.tint)
2288    }
2289}
2290
2291impl Repr for SpotColor {
2292    fn repr(&self) -> EcoString {
2293        eco_format!("{}.tint({})", self.colorant.repr(), self.tint.repr())
2294    }
2295}
2296
2297/// A spot colorant from which spot colors can be created.
2298///
2299/// Use spot colors to request a precise pigment in a professional print
2300/// environment. Once you have created a spot colorant, you can create
2301/// colors using its @color.spot.tint[`tint` method].
2302#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2303#[ty(scope, name = "spot", title = "Spot Colorant")]
2304pub struct SpotColorant {
2305    /// Name of the spot colorant to use, if any.
2306    pub name: Option<SpotColorantName>,
2307    /// How to render this color if the specified colorant is not available.
2308    pub fallback: ProcessColor,
2309}
2310
2311impl Repr for SpotColorant {
2312    fn repr(&self) -> EcoString {
2313        eco_format!("color.spot({}, {})", self.name.repr(), self.fallback.repr())
2314    }
2315}
2316
2317#[scope]
2318impl SpotColorant {
2319    /// Create a new spot colorant.
2320    #[func(constructor)]
2321    pub fn construct(
2322        /// Name of the spot colorant to use.
2323        ///
2324        /// In production, this name will be manually checked and matched to a
2325        /// colorant, so this value needs to be unambiguous. It's best to
2326        /// reference a color from a registry like PANTONE, HKS, RAL, Toyo &
2327        /// DIC, etc.
2328        ///
2329        /// Values in here may be treated case-sensitively during production:
2330        /// `{"PANTONE 2221 C"}` and `{"PANTONE 2221 c"}` may be treated as
2331        /// separate colors. Ensure that you are using a consistent naming
2332        /// convention, either referencing a registry or through coordination
2333        /// with your production printing experts.
2334        ///
2335        /// If this value is `{"all"}` and your print will involve multiple
2336        /// #link("https://en.wikipedia.org/wiki/Offset_printing#Plates")[color plates],
2337        /// use of this colorant will result in the specified tint being applied
2338        /// equally to all plates. If you choose `{none}`, no colorant will be
2339        /// applied when using this color. Instead, you can use a spot color
2340        /// with the name `{none}` to indicate cuts or varnishes. Be sure to
2341        /// discuss this with your production printer!
2342        ///
2343        /// We do not recommend using the names `{"Cyan"}`, `{"Magenta"}`,
2344        /// `{"Yellow"}`, `{"Key"}`, `{"Black"}`, or their translations to your
2345        /// local language. Depending on your printer, they may or may not be
2346        /// interpreted as CMYK process colors.
2347        name: Option<SpotColorantName>,
2348        /// How to render this color if the specified colorant is not available.
2349        ///
2350        /// Many mediums, like on-screen preview and household printers, will not
2351        /// have this specific spot colorant available. To display an approximation
2352        /// of the intended print, another, available color is used instead.
2353        fallback: ProcessColor,
2354    ) -> SourceResult<SpotColorant> {
2355        Ok(Self { name, fallback })
2356    }
2357
2358    /// Create a spot color at a specific tint of this colorant.
2359    ///
2360    /// The tint represents what percentage of the colorant is applied. A tint
2361    /// of `{100%}` means the colorant is applied at full strength, while `{0%}`
2362    /// means no colorant is applied.
2363    ///
2364    /// ```example
2365    /// #let pantone = color.spot(
2366    ///   "PANTONE 2221 C",
2367    ///   rgb("#239dad")
2368    /// )
2369    ///
2370    /// #square(fill: pantone.tint(100%))
2371    /// #square(fill: pantone.tint(70%))
2372    /// #square(fill: pantone.tint(40%))
2373    /// ```
2374    #[func]
2375    pub fn tint(
2376        &self,
2377        /// The tint percentage, between `{0%}` and `{100%}`.
2378        value: Spanned<Ratio>,
2379    ) -> SourceResult<Color> {
2380        if value.v < Ratio::zero() {
2381            bail!(value.span, "spot color tint must be positive")
2382        }
2383
2384        if value.v > Ratio::one() {
2385            bail!(value.span, "spot color tint must not exceed 100%")
2386        }
2387
2388        Ok(Color::Spot(SpotColor::new(Arc::new(self.clone()), value.v)))
2389    }
2390}
2391
2392/// The name of a spot colorant.
2393#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2394pub enum SpotColorantName {
2395    /// The special `{"all"}` colorant that applies to all separation colorants.
2396    All,
2397    /// A custom colorant name (e.g., `{"PANTONE 2221 C"}`).
2398    Custom(EcoString),
2399}
2400
2401impl Repr for SpotColorantName {
2402    fn repr(&self) -> EcoString {
2403        match self {
2404            Self::All => "all".repr(),
2405            Self::Custom(name) => name.repr(),
2406        }
2407    }
2408}
2409
2410cast! {
2411    SpotColorantName,
2412    self => match self {
2413        Self::All => "all".into_value(),
2414        Self::Custom(name) => name.into_value(),
2415    },
2416    /// Use all available color plates instead of only a specific colorant.
2417    "all" => Self::All,
2418    /// Use a specific colorant.
2419    s: EcoString => Self::Custom(s),
2420}
2421
2422/// A color with a weight.
2423#[derive(Clone)]
2424pub struct WeightedColor {
2425    color: Color,
2426    weight: f64,
2427}
2428
2429impl WeightedColor {
2430    /// Create a new weighted color.
2431    pub const fn new(color: Color, weight: f64) -> Self {
2432        Self { color, weight }
2433    }
2434}
2435
2436cast! {
2437    WeightedColor,
2438    self => array![self.color, Value::Float(self.weight)].into_value(),
2439    color: Color => Self { color, weight: 1.0 },
2440    v: Array => {
2441        let mut iter = v.into_iter();
2442        match (iter.next(), iter.next(), iter.next()) {
2443            (Some(c), Some(w), None) => Self {
2444                color: c.cast()?,
2445                weight: w.cast::<Weight>()?.0,
2446            },
2447            _ => bail!("expected a color or color-weight pair"),
2448        }
2449    }
2450}
2451
2452/// A weight for color mixing.
2453struct Weight(f64);
2454
2455cast! {
2456    Weight,
2457    v: f64 => Self(v),
2458    v: Ratio => Self(v.get()),
2459}
2460
2461/// Resolves `space` against an iterator of participating colors. If the space
2462/// is `Custom`, it is returned immediately. Otherwise, if all colors are of the
2463/// same spot colorant, that is returned and Oklab otherwise.
2464pub(crate) fn resolve_color_space(
2465    space: Smart<ColorSpace>,
2466    colors: impl Iterator<Item = Color>,
2467) -> ColorSpace {
2468    enum Verdict {
2469        InitialAuto,
2470        Space(ColorSpace),
2471        MayBeSpot(SpotColorant),
2472    }
2473
2474    impl Verdict {
2475        const fn fallback_space() -> ColorSpace {
2476            ColorSpace::Process(ProcessColorSpace::Oklab)
2477        }
2478
2479        const fn fallback() -> Self {
2480            Self::Space(Self::fallback_space())
2481        }
2482    }
2483
2484    let mut verdict = match space {
2485        Smart::Auto => Verdict::InitialAuto,
2486        Smart::Custom(s) => return s,
2487    };
2488
2489    for color in colors {
2490        match &verdict {
2491            Verdict::InitialAuto => {
2492                verdict = match color.space() {
2493                    ColorSpace::Process(_) => Verdict::fallback(),
2494                    ColorSpace::Spot(colorant) => Verdict::MayBeSpot(colorant),
2495                }
2496            }
2497            Verdict::MayBeSpot(colorant) => {
2498                verdict = match color.space() {
2499                    ColorSpace::Spot(other) if &other == colorant => verdict,
2500                    _ => Verdict::fallback(),
2501                }
2502            }
2503            Verdict::Space(_) => break,
2504        }
2505    }
2506
2507    match verdict {
2508        Verdict::InitialAuto => Verdict::fallback_space(),
2509        Verdict::Space(s) => s,
2510        Verdict::MayBeSpot(colorant) => ColorSpace::Spot(colorant),
2511    }
2512}
2513
2514/// A color space for color manipulation.
2515#[derive(Debug, Clone, Eq, PartialEq, Hash)]
2516pub enum ColorSpace {
2517    /// A mixable color space with a well-defined color model.
2518    Process(ProcessColorSpace),
2519    /// A spot colorant.
2520    Spot(SpotColorant),
2521}
2522
2523impl ColorSpace {
2524    /// Returns the index of the hue component in this color space, if it has
2525    /// one.
2526    pub fn hue_index(&self) -> Option<usize> {
2527        match self {
2528            Self::Process(s) => s.hue_index(),
2529            Self::Spot(_) => None,
2530        }
2531    }
2532}
2533
2534cast! {
2535    ColorSpace,
2536    self => match self {
2537        Self::Process(s) => s.variant_constructor(),
2538        Self::Spot(s) => s.into_value()
2539    },
2540    spot: SpotColorant => Self::Spot(spot),
2541    v: Value => {
2542        let expected = "expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, `color.hsv`, or spot colorant";
2543
2544        let Value::Func(func) = v else {
2545            bail!("{expected}, found {}", v.ty());
2546        };
2547
2548        match ProcessColorSpace::try_from_constructor(func) {
2549            Some(space) => Self::Process(space),
2550            None => bail!("{expected}")
2551        }
2552    },
2553}
2554
2555/// A color space for color manipulation.
2556#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
2557pub enum ProcessColorSpace {
2558    /// The perceptual Oklab color space.
2559    Oklab,
2560    /// The perceptual Oklch color space.
2561    Oklch,
2562    /// The standard RGB color space.
2563    Srgb,
2564    /// The D65-gray color space.
2565    D65Gray,
2566    /// The linear RGB color space.
2567    LinearRgb,
2568    /// The HSL color space.
2569    Hsl,
2570    /// The HSV color space.
2571    Hsv,
2572    /// The CMYK color space.
2573    Cmyk,
2574}
2575
2576impl ProcessColorSpace {
2577    /// Returns the index of the hue component in this color space, if it has
2578    /// one.
2579    pub fn hue_index(self) -> Option<usize> {
2580        match self {
2581            Self::Hsl | Self::Hsv => Some(0),
2582            Self::Oklch => Some(2),
2583            _ => None,
2584        }
2585    }
2586
2587    /// Return the value for each variant's constructor.
2588    fn variant_constructor(self) -> Value {
2589        match self {
2590            Self::Oklab => Color::oklab_data(),
2591            Self::Oklch => Color::oklch_data(),
2592            Self::Srgb => Color::rgb_data(),
2593            Self::D65Gray => Color::luma_data(),
2594            Self::LinearRgb => Color::linear_rgb_data(),
2595            Self::Hsl => Color::hsl_data(),
2596            Self::Hsv => Color::hsv_data(),
2597            Self::Cmyk => Color::cmyk_data(),
2598        }
2599        .into_value()
2600    }
2601
2602    /// Given a constructor, return the matching variant.
2603    fn try_from_constructor(func: Func) -> Option<Self> {
2604        // Here comparing the function pointer since it's `Eq`
2605        // whereas the `NativeFuncData` is not.
2606        Some(if func == Color::oklab_data() {
2607            Self::Oklab
2608        } else if func == Color::oklch_data() {
2609            Self::Oklch
2610        } else if func == Color::rgb_data() {
2611            Self::Srgb
2612        } else if func == Color::luma_data() {
2613            Self::D65Gray
2614        } else if func == Color::linear_rgb_data() {
2615            Self::LinearRgb
2616        } else if func == Color::hsl_data() {
2617            Self::Hsl
2618        } else if func == Color::hsv_data() {
2619            Self::Hsv
2620        } else if func == Color::cmyk_data() {
2621            Self::Cmyk
2622        } else {
2623            return None;
2624        })
2625    }
2626}
2627
2628impl From<ProcessColorSpace> for ColorSpace {
2629    fn from(value: ProcessColorSpace) -> Self {
2630        Self::Process(value)
2631    }
2632}
2633
2634cast! {
2635    ProcessColorSpace,
2636    self => self.variant_constructor(),
2637    v: Value => {
2638        let expected = "expected `rgb`, `luma`, `cmyk`, `oklab`, `oklch`, `color.linear-rgb`, `color.hsl`, or `color.hsv`";
2639
2640        let Value::Func(func) = v else {
2641            bail!("{expected}, found {}", v.ty());
2642        };
2643
2644        match Self::try_from_constructor(func) {
2645            Some(space) => space,
2646            None => bail!("{expected}")
2647        }
2648    },
2649}
2650
2651/// A component that must be a ratio.
2652pub struct RatioComponent(Ratio);
2653
2654cast! {
2655    RatioComponent,
2656    self => self.0.into_value(),
2657    v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
2658        Self(v)
2659    } else {
2660        bail!("ratio must be between 0% and 100%");
2661    },
2662}
2663
2664/// A chroma color component.
2665///
2666/// Must either be:
2667/// - a ratio, in which case it is relative to 0.4.
2668/// - a float, in which case it is taken literally.
2669pub struct ChromaComponent(f32);
2670
2671cast! {
2672    ChromaComponent,
2673    v: f64 => Self(v as f32),
2674    v: Ratio => Self((v.get() * 0.4) as f32),
2675}
2676
2677/// An integer or ratio component.
2678pub struct Component(Ratio);
2679
2680cast! {
2681    Component,
2682    self => self.0.into_value(),
2683    v: i64 => match v {
2684        0 ..= 255 => Self(Ratio::new(v as f64 / 255.0)),
2685        _ => bail!("number must be between 0 and 255"),
2686    },
2687    v: Ratio => if (0.0 ..= 1.0).contains(&v.get()) {
2688        Self(v)
2689    } else {
2690        bail!("ratio must be between 0% and 100%");
2691    },
2692}
2693
2694/// A module with all preset color maps.
2695fn map() -> Module {
2696    let mut scope = Scope::new();
2697    scope.define("turbo", turbo());
2698    scope.define("cividis", cividis());
2699    scope.define("rainbow", rainbow());
2700    scope.define("spectral", spectral());
2701    scope.define("viridis", viridis());
2702    scope.define("inferno", inferno());
2703    scope.define("magma", magma());
2704    scope.define("plasma", plasma());
2705    scope.define("rocket", rocket());
2706    scope.define("mako", mako());
2707    scope.define("coolwarm", coolwarm());
2708    scope.define("vlag", vlag());
2709    scope.define("icefire", icefire());
2710    scope.define("flare", flare());
2711    scope.define("crest", crest());
2712    Module::new("map", scope)
2713}
2714
2715/// Defines a gradient preset as a series of colors expressed as u32s.
2716macro_rules! preset {
2717    ($name:ident; $($colors:literal),* $(,)*) => {
2718        fn $name() -> Array {
2719            Array::from(
2720                [$(Color::from_u32($colors)),*]
2721                    .into_iter()
2722                    .map(|c| c.into_value())
2723                    .collect::<EcoVec<_>>()
2724            )
2725        }
2726    };
2727}
2728
2729preset!(turbo; 0x23171bff, 0x271a28ff, 0x2b1c33ff, 0x2f1e3fff, 0x32204aff, 0x362354ff, 0x39255fff, 0x3b2768ff, 0x3e2a72ff, 0x402c7bff, 0x422f83ff, 0x44318bff, 0x453493ff, 0x46369bff, 0x4839a2ff, 0x493ca8ff, 0x493eafff, 0x4a41b5ff, 0x4a44bbff, 0x4b46c0ff, 0x4b49c5ff, 0x4b4ccaff, 0x4b4ecfff, 0x4b51d3ff, 0x4a54d7ff, 0x4a56dbff, 0x4959deff, 0x495ce2ff, 0x485fe5ff, 0x4761e7ff, 0x4664eaff, 0x4567ecff, 0x446aeeff, 0x446df0ff, 0x426ff2ff, 0x4172f3ff, 0x4075f5ff, 0x3f78f6ff, 0x3e7af7ff, 0x3d7df7ff, 0x3c80f8ff, 0x3a83f9ff, 0x3985f9ff, 0x3888f9ff, 0x378bf9ff, 0x368df9ff, 0x3590f8ff, 0x3393f8ff, 0x3295f7ff, 0x3198f7ff, 0x309bf6ff, 0x2f9df5ff, 0x2ea0f4ff, 0x2da2f3ff, 0x2ca5f1ff, 0x2ba7f0ff, 0x2aaaefff, 0x2aacedff, 0x29afecff, 0x28b1eaff, 0x28b4e8ff, 0x27b6e6ff, 0x27b8e5ff, 0x26bbe3ff, 0x26bde1ff, 0x26bfdfff, 0x25c1dcff, 0x25c3daff, 0x25c6d8ff, 0x25c8d6ff, 0x25cad3ff, 0x25ccd1ff, 0x25cecfff, 0x26d0ccff, 0x26d2caff, 0x26d4c8ff, 0x27d6c5ff, 0x27d8c3ff, 0x28d9c0ff, 0x29dbbeff, 0x29ddbbff, 0x2adfb8ff, 0x2be0b6ff, 0x2ce2b3ff, 0x2de3b1ff, 0x2ee5aeff, 0x30e6acff, 0x31e8a9ff, 0x32e9a6ff, 0x34eba4ff, 0x35eca1ff, 0x37ed9fff, 0x39ef9cff, 0x3af09aff, 0x3cf197ff, 0x3ef295ff, 0x40f392ff, 0x42f490ff, 0x44f58dff, 0x46f68bff, 0x48f788ff, 0x4af786ff, 0x4df884ff, 0x4ff981ff, 0x51fa7fff, 0x54fa7dff, 0x56fb7aff, 0x59fb78ff, 0x5cfc76ff, 0x5efc74ff, 0x61fd71ff, 0x64fd6fff, 0x66fd6dff, 0x69fd6bff, 0x6cfd69ff, 0x6ffe67ff, 0x72fe65ff, 0x75fe63ff, 0x78fe61ff, 0x7bfe5fff, 0x7efd5dff, 0x81fd5cff, 0x84fd5aff, 0x87fd58ff, 0x8afc56ff, 0x8dfc55ff, 0x90fb53ff, 0x93fb51ff, 0x96fa50ff, 0x99fa4eff, 0x9cf94dff, 0x9ff84bff, 0xa2f84aff, 0xa6f748ff, 0xa9f647ff, 0xacf546ff, 0xaff444ff, 0xb2f343ff, 0xb5f242ff, 0xb8f141ff, 0xbbf03fff, 0xbeef3eff, 0xc1ed3dff, 0xc3ec3cff, 0xc6eb3bff, 0xc9e93aff, 0xcce839ff, 0xcfe738ff, 0xd1e537ff, 0xd4e336ff, 0xd7e235ff, 0xd9e034ff, 0xdcdf33ff, 0xdedd32ff, 0xe0db32ff, 0xe3d931ff, 0xe5d730ff, 0xe7d52fff, 0xe9d42fff, 0xecd22eff, 0xeed02dff, 0xf0ce2cff, 0xf1cb2cff, 0xf3c92bff, 0xf5c72bff, 0xf7c52aff, 0xf8c329ff, 0xfac029ff, 0xfbbe28ff, 0xfdbc28ff, 0xfeb927ff, 0xffb727ff, 0xffb526ff, 0xffb226ff, 0xffb025ff, 0xffad25ff, 0xffab24ff, 0xffa824ff, 0xffa623ff, 0xffa323ff, 0xffa022ff, 0xff9e22ff, 0xff9b21ff, 0xff9921ff, 0xff9621ff, 0xff9320ff, 0xff9020ff, 0xff8e1fff, 0xff8b1fff, 0xff881eff, 0xff851eff, 0xff831dff, 0xff801dff, 0xff7d1dff, 0xff7a1cff, 0xff781cff, 0xff751bff, 0xff721bff, 0xff6f1aff, 0xfd6c1aff, 0xfc6a19ff, 0xfa6719ff, 0xf96418ff, 0xf76118ff, 0xf65f18ff, 0xf45c17ff, 0xf25916ff, 0xf05716ff, 0xee5415ff, 0xec5115ff, 0xea4f14ff, 0xe84c14ff, 0xe64913ff, 0xe44713ff, 0xe24412ff, 0xdf4212ff, 0xdd3f11ff, 0xda3d10ff, 0xd83a10ff, 0xd5380fff, 0xd3360fff, 0xd0330eff, 0xce310dff, 0xcb2f0dff, 0xc92d0cff, 0xc62a0bff, 0xc3280bff, 0xc1260aff, 0xbe2409ff, 0xbb2309ff, 0xb92108ff, 0xb61f07ff, 0xb41d07ff, 0xb11b06ff, 0xaf1a05ff, 0xac1805ff, 0xaa1704ff, 0xa81604ff, 0xa51403ff, 0xa31302ff, 0xa11202ff, 0x9f1101ff, 0x9d1000ff, 0x9b0f00ff, 0x9a0e00ff, 0x980e00ff, 0x960d00ff, 0x950c00ff, 0x940c00ff, 0x930c00ff, 0x920c00ff, 0x910b00ff, 0x910c00ff, 0x900c00ff, 0x900c00ff, 0x900c00ff);
2730preset!(cividis; 0x002051ff, 0x002153ff, 0x002255ff, 0x002356ff, 0x002358ff, 0x002459ff, 0x00255aff, 0x00255cff, 0x00265dff, 0x00275eff, 0x00275fff, 0x002860ff, 0x002961ff, 0x002962ff, 0x002a63ff, 0x002b64ff, 0x012b65ff, 0x022c65ff, 0x032d66ff, 0x042d67ff, 0x052e67ff, 0x052f68ff, 0x063069ff, 0x073069ff, 0x08316aff, 0x09326aff, 0x0b326aff, 0x0c336bff, 0x0d346bff, 0x0e346bff, 0x0f356cff, 0x10366cff, 0x12376cff, 0x13376dff, 0x14386dff, 0x15396dff, 0x17396dff, 0x183a6dff, 0x193b6dff, 0x1a3b6dff, 0x1c3c6eff, 0x1d3d6eff, 0x1e3e6eff, 0x203e6eff, 0x213f6eff, 0x23406eff, 0x24406eff, 0x25416eff, 0x27426eff, 0x28436eff, 0x29436eff, 0x2b446eff, 0x2c456eff, 0x2e456eff, 0x2f466eff, 0x30476eff, 0x32486eff, 0x33486eff, 0x34496eff, 0x364a6eff, 0x374a6eff, 0x394b6eff, 0x3a4c6eff, 0x3b4d6eff, 0x3d4d6eff, 0x3e4e6eff, 0x3f4f6eff, 0x414f6eff, 0x42506eff, 0x43516dff, 0x44526dff, 0x46526dff, 0x47536dff, 0x48546dff, 0x4a546dff, 0x4b556dff, 0x4c566dff, 0x4d576dff, 0x4e576eff, 0x50586eff, 0x51596eff, 0x52596eff, 0x535a6eff, 0x545b6eff, 0x565c6eff, 0x575c6eff, 0x585d6eff, 0x595e6eff, 0x5a5e6eff, 0x5b5f6eff, 0x5c606eff, 0x5d616eff, 0x5e616eff, 0x60626eff, 0x61636fff, 0x62646fff, 0x63646fff, 0x64656fff, 0x65666fff, 0x66666fff, 0x67676fff, 0x686870ff, 0x696970ff, 0x6a6970ff, 0x6b6a70ff, 0x6c6b70ff, 0x6d6c70ff, 0x6d6c71ff, 0x6e6d71ff, 0x6f6e71ff, 0x706f71ff, 0x716f71ff, 0x727071ff, 0x737172ff, 0x747172ff, 0x757272ff, 0x767372ff, 0x767472ff, 0x777473ff, 0x787573ff, 0x797673ff, 0x7a7773ff, 0x7b7774ff, 0x7b7874ff, 0x7c7974ff, 0x7d7a74ff, 0x7e7a74ff, 0x7f7b75ff, 0x807c75ff, 0x807d75ff, 0x817d75ff, 0x827e75ff, 0x837f76ff, 0x848076ff, 0x858076ff, 0x858176ff, 0x868276ff, 0x878376ff, 0x888477ff, 0x898477ff, 0x898577ff, 0x8a8677ff, 0x8b8777ff, 0x8c8777ff, 0x8d8877ff, 0x8e8978ff, 0x8e8a78ff, 0x8f8a78ff, 0x908b78ff, 0x918c78ff, 0x928d78ff, 0x938e78ff, 0x938e78ff, 0x948f78ff, 0x959078ff, 0x969178ff, 0x979278ff, 0x989278ff, 0x999378ff, 0x9a9478ff, 0x9b9578ff, 0x9b9678ff, 0x9c9678ff, 0x9d9778ff, 0x9e9878ff, 0x9f9978ff, 0xa09a78ff, 0xa19a78ff, 0xa29b78ff, 0xa39c78ff, 0xa49d78ff, 0xa59e77ff, 0xa69e77ff, 0xa79f77ff, 0xa8a077ff, 0xa9a177ff, 0xaaa276ff, 0xaba376ff, 0xaca376ff, 0xada476ff, 0xaea575ff, 0xafa675ff, 0xb0a775ff, 0xb2a874ff, 0xb3a874ff, 0xb4a974ff, 0xb5aa73ff, 0xb6ab73ff, 0xb7ac72ff, 0xb8ad72ff, 0xbaae72ff, 0xbbae71ff, 0xbcaf71ff, 0xbdb070ff, 0xbeb170ff, 0xbfb26fff, 0xc1b36fff, 0xc2b46eff, 0xc3b56dff, 0xc4b56dff, 0xc5b66cff, 0xc7b76cff, 0xc8b86bff, 0xc9b96aff, 0xcaba6aff, 0xccbb69ff, 0xcdbc68ff, 0xcebc68ff, 0xcfbd67ff, 0xd1be66ff, 0xd2bf66ff, 0xd3c065ff, 0xd4c164ff, 0xd6c263ff, 0xd7c363ff, 0xd8c462ff, 0xd9c561ff, 0xdbc660ff, 0xdcc660ff, 0xddc75fff, 0xdec85eff, 0xe0c95dff, 0xe1ca5cff, 0xe2cb5cff, 0xe3cc5bff, 0xe4cd5aff, 0xe6ce59ff, 0xe7cf58ff, 0xe8d058ff, 0xe9d157ff, 0xead256ff, 0xebd355ff, 0xecd454ff, 0xedd453ff, 0xeed553ff, 0xf0d652ff, 0xf1d751ff, 0xf1d850ff, 0xf2d950ff, 0xf3da4fff, 0xf4db4eff, 0xf5dc4dff, 0xf6dd4dff, 0xf7de4cff, 0xf8df4bff, 0xf8e04bff, 0xf9e14aff, 0xfae249ff, 0xfae349ff, 0xfbe448ff, 0xfbe548ff, 0xfce647ff, 0xfce746ff, 0xfde846ff, 0xfde946ff, 0xfdea45ff);
2731preset!(rainbow;  0x7c4bbbff, 0x7f4bbcff, 0x824bbdff, 0x854abeff, 0x884abeff, 0x8b4abfff, 0x8e49bfff, 0x9149c0ff, 0x9449c0ff, 0x9748c0ff, 0x9a48c1ff, 0x9e48c1ff, 0xa148c1ff, 0xa447c1ff, 0xa747c1ff, 0xaa47c0ff, 0xad47c0ff, 0xb046c0ff, 0xb446bfff, 0xb746bfff, 0xba46beff, 0xbd46beff, 0xc046bdff, 0xc346bcff, 0xc646bbff, 0xc946baff, 0xcc46b9ff, 0xcf46b8ff, 0xd246b7ff, 0xd446b5ff, 0xd747b4ff, 0xda47b3ff, 0xdd47b1ff, 0xdf47b0ff, 0xe248aeff, 0xe448acff, 0xe748abff, 0xe949a9ff, 0xec49a7ff, 0xee4aa5ff, 0xf04ba3ff, 0xf34ba1ff, 0xf54c9fff, 0xf74c9dff, 0xf94d9bff, 0xfb4e98ff, 0xfd4f96ff, 0xfe5094ff, 0xff5191ff, 0xff528fff, 0xff538dff, 0xff548aff, 0xff5588ff, 0xff5685ff, 0xff5783ff, 0xff5880ff, 0xff5a7eff, 0xff5b7bff, 0xff5c79ff, 0xff5e76ff, 0xff5f74ff, 0xff6171ff, 0xff626fff, 0xff646cff, 0xff666aff, 0xff6767ff, 0xff6965ff, 0xff6b63ff, 0xff6d60ff, 0xff6e5eff, 0xff705cff, 0xff7259ff, 0xff7457ff, 0xff7655ff, 0xff7853ff, 0xff7a51ff, 0xff7c4fff, 0xff7f4dff, 0xff814bff, 0xff8349ff, 0xff8547ff, 0xff8745ff, 0xff8a44ff, 0xff8c42ff, 0xff8e40ff, 0xff913fff, 0xff933eff, 0xff953cff, 0xff983bff, 0xfd9a3aff, 0xfb9c39ff, 0xfa9f38ff, 0xf8a137ff, 0xf6a436ff, 0xf4a636ff, 0xf2a935ff, 0xf0ab35ff, 0xeeae34ff, 0xecb034ff, 0xeab234ff, 0xe8b534ff, 0xe6b734ff, 0xe4ba34ff, 0xe1bc34ff, 0xdfbf35ff, 0xddc135ff, 0xdbc336ff, 0xd9c636ff, 0xd6c837ff, 0xd4ca38ff, 0xd2cd39ff, 0xd0cf3aff, 0xcdd13bff, 0xcbd33dff, 0xc9d63eff, 0xc7d840ff, 0xc5da41ff, 0xc3dc43ff, 0xc1de45ff, 0xbfe047ff, 0xbde249ff, 0xbbe44bff, 0xb9e64dff, 0xb7e84fff, 0xb5ea52ff, 0xb3ec54ff, 0xb2ed57ff, 0xb0ef59ff, 0xadf05aff, 0xaaf15aff, 0xa6f159ff, 0xa2f259ff, 0x9ff259ff, 0x9bf358ff, 0x97f358ff, 0x94f459ff, 0x90f459ff, 0x8df559ff, 0x89f559ff, 0x85f65aff, 0x82f65bff, 0x7ff65bff, 0x7ef75cff, 0x7cf75dff, 0x7bf75eff, 0x7af75fff, 0x79f760ff, 0x78f762ff, 0x77f763ff, 0x76f764ff, 0x75f766ff, 0x74f768ff, 0x73f769ff, 0x72f76bff, 0x71f76dff, 0x70f76fff, 0x6ff671ff, 0x6ef673ff, 0x6df675ff, 0x6df577ff, 0x6cf579ff, 0x6bf47cff, 0x6af37eff, 0x69f380ff, 0x68f283ff, 0x67f185ff, 0x66f188ff, 0x66f08aff, 0x65ef8dff, 0x64ee8fff, 0x63ed92ff, 0x62ec94ff, 0x62eb97ff, 0x61ea9aff, 0x60e89cff, 0x5fe79fff, 0x5fe6a1ff, 0x5ee4a4ff, 0x5de3a7ff, 0x5ce2a9ff, 0x5ce0acff, 0x5bdfafff, 0x5addb1ff, 0x5adbb4ff, 0x59dab6ff, 0x58d8b9ff, 0x58d6bbff, 0x57d5beff, 0x56d3c0ff, 0x56d1c2ff, 0x55cfc5ff, 0x54cdc7ff, 0x54cbc9ff, 0x53c9cbff, 0x52c7cdff, 0x52c5cfff, 0x51c3d1ff, 0x51c1d3ff, 0x50bfd5ff, 0x50bdd7ff, 0x4fbbd9ff, 0x4eb9daff, 0x4eb6dcff, 0x4db4ddff, 0x4db2dfff, 0x4cb0e0ff, 0x4caee2ff, 0x4babe3ff, 0x4ba9e4ff, 0x4aa7e5ff, 0x4aa4e6ff, 0x49a2e7ff, 0x49a0e8ff, 0x489ee8ff, 0x489be9ff, 0x4799e9ff, 0x4797eaff, 0x4694eaff, 0x4692eaff, 0x4690ebff, 0x458eebff, 0x478bebff, 0x4889ebff, 0x4a87eaff, 0x4c85eaff, 0x4e82eaff, 0x5080e9ff, 0x527ee9ff, 0x537ce8ff, 0x557ae7ff, 0x5778e7ff, 0x5975e6ff, 0x5b73e5ff, 0x5c71e4ff, 0x5e6fe3ff, 0x606de1ff, 0x626be0ff, 0x6369dfff, 0x6567ddff, 0x6765dcff, 0x6864daff, 0x6a62d9ff, 0x6b60d7ff, 0x6d5ed5ff, 0x6e5cd3ff, 0x705bd1ff, 0x7159cfff, 0x7357cdff, 0x7456cbff, 0x7554c9ff, 0x7652c7ff, 0x7751c5ff, 0x794fc2ff, 0x7a4ec0ff, 0x7b4dbeff, 0x7c4bbbff);
2732preset!(spectral; 0x9e0142ff, 0xd53e4fff, 0xf46d43ff, 0xfdae61ff, 0xfee08bff, 0xffffbfff, 0xe6f598ff, 0xabdda4ff, 0x66c2a5ff, 0x3288bdff, 0x5e4fa2ff);
2733preset!(viridis; 0x440154ff, 0x482777ff, 0x3f4a8aff, 0x31678eff, 0x26838fff, 0x1f9d8aff, 0x6cce5aff, 0xb6de2bff, 0xfee825ff);
2734preset!(inferno; 0x000004ff, 0x170b3aff, 0x420a68ff, 0x6b176eff, 0x932667ff, 0xbb3654ff, 0xdd513aff, 0xf3771aff, 0xfca50aff, 0xf6d644ff, 0xfcffa4ff);
2735preset!(magma; 0x000004ff, 0x140e37ff, 0x3b0f70ff, 0x641a80ff, 0x8c2981ff, 0xb63679ff, 0xde4968ff, 0xf66f5cff, 0xfe9f6dff, 0xfece91ff, 0xfcfdbfff);
2736preset!(plasma; 0x0d0887ff, 0x42039dff, 0x6a00a8ff, 0x900da3ff, 0xb12a90ff, 0xcb4678ff, 0xe16462ff, 0xf1834bff, 0xfca636ff, 0xfccd25ff, 0xf0f921ff);
2737preset!(rocket; 0x3051aff, 0x4051aff, 0x5061bff, 0x6071cff, 0x7071dff, 0x8081eff, 0xa091fff, 0xb0920ff, 0xd0a21ff, 0xe0b22ff, 0x100b23ff, 0x110c24ff, 0x130d25ff, 0x140e26ff, 0x160e27ff, 0x170f28ff, 0x180f29ff, 0x1a102aff, 0x1b112bff, 0x1d112cff, 0x1e122dff, 0x20122eff, 0x211330ff, 0x221331ff, 0x241432ff, 0x251433ff, 0x271534ff, 0x281535ff, 0x2a1636ff, 0x2b1637ff, 0x2d1738ff, 0x2e1739ff, 0x30173aff, 0x31183bff, 0x33183cff, 0x34193dff, 0x35193eff, 0x37193fff, 0x381a40ff, 0x3a1a41ff, 0x3c1a42ff, 0x3d1a42ff, 0x3f1b43ff, 0x401b44ff, 0x421b45ff, 0x431c46ff, 0x451c47ff, 0x461c48ff, 0x481c48ff, 0x491d49ff, 0x4b1d4aff, 0x4c1d4bff, 0x4e1d4bff, 0x501d4cff, 0x511e4dff, 0x531e4dff, 0x541e4eff, 0x561e4fff, 0x581e4fff, 0x591e50ff, 0x5b1e51ff, 0x5c1e51ff, 0x5e1f52ff, 0x601f52ff, 0x611f53ff, 0x631f53ff, 0x641f54ff, 0x661f54ff, 0x681f55ff, 0x691f55ff, 0x6b1f56ff, 0x6d1f56ff, 0x6e1f57ff, 0x701f57ff, 0x711f57ff, 0x731f58ff, 0x751f58ff, 0x761f58ff, 0x781f59ff, 0x7a1f59ff, 0x7b1f59ff, 0x7d1f5aff, 0x7f1e5aff, 0x811e5aff, 0x821e5aff, 0x841e5aff, 0x861e5bff, 0x871e5bff, 0x891e5bff, 0x8b1d5bff, 0x8c1d5bff, 0x8e1d5bff, 0x901d5bff, 0x921c5bff, 0x931c5bff, 0x951c5bff, 0x971c5bff, 0x981b5bff, 0x9a1b5bff, 0x9c1b5bff, 0x9e1a5bff, 0x9f1a5bff, 0xa11a5bff, 0xa3195bff, 0xa4195bff, 0xa6195aff, 0xa8185aff, 0xaa185aff, 0xab185aff, 0xad1759ff, 0xaf1759ff, 0xb01759ff, 0xb21758ff, 0xb41658ff, 0xb51657ff, 0xb71657ff, 0xb91657ff, 0xba1656ff, 0xbc1656ff, 0xbd1655ff, 0xbf1654ff, 0xc11754ff, 0xc21753ff, 0xc41753ff, 0xc51852ff, 0xc71951ff, 0xc81951ff, 0xca1a50ff, 0xcb1b4fff, 0xcd1c4eff, 0xce1d4eff, 0xcf1e4dff, 0xd11f4cff, 0xd2204cff, 0xd3214bff, 0xd5224aff, 0xd62449ff, 0xd72549ff, 0xd82748ff, 0xd92847ff, 0xdb2946ff, 0xdc2b46ff, 0xdd2c45ff, 0xde2e44ff, 0xdf2f44ff, 0xe03143ff, 0xe13342ff, 0xe23442ff, 0xe33641ff, 0xe43841ff, 0xe53940ff, 0xe63b40ff, 0xe73d3fff, 0xe83f3fff, 0xe8403eff, 0xe9423eff, 0xea443eff, 0xeb463eff, 0xeb483eff, 0xec4a3eff, 0xec4c3eff, 0xed4e3eff, 0xed503eff, 0xee523fff, 0xee543fff, 0xef5640ff, 0xef5840ff, 0xef5a41ff, 0xf05c42ff, 0xf05e42ff, 0xf06043ff, 0xf16244ff, 0xf16445ff, 0xf16646ff, 0xf26747ff, 0xf26948ff, 0xf26b49ff, 0xf26d4bff, 0xf26f4cff, 0xf3714dff, 0xf3734eff, 0xf37450ff, 0xf37651ff, 0xf37852ff, 0xf47a54ff, 0xf47c55ff, 0xf47d57ff, 0xf47f58ff, 0xf4815aff, 0xf4835bff, 0xf4845dff, 0xf4865eff, 0xf58860ff, 0xf58a61ff, 0xf58b63ff, 0xf58d64ff, 0xf58f66ff, 0xf59067ff, 0xf59269ff, 0xf5946bff, 0xf5966cff, 0xf5976eff, 0xf59970ff, 0xf69b71ff, 0xf69c73ff, 0xf69e75ff, 0xf6a077ff, 0xf6a178ff, 0xf6a37aff, 0xf6a47cff, 0xf6a67eff, 0xf6a880ff, 0xf6a981ff, 0xf6ab83ff, 0xf6ad85ff, 0xf6ae87ff, 0xf6b089ff, 0xf6b18bff, 0xf6b38dff, 0xf6b48fff, 0xf6b691ff, 0xf6b893ff, 0xf6b995ff, 0xf6bb97ff, 0xf6bc99ff, 0xf6be9bff, 0xf6bf9dff, 0xf6c19fff, 0xf7c2a2ff, 0xf7c4a4ff, 0xf7c6a6ff, 0xf7c7a8ff, 0xf7c9aaff, 0xf7caacff, 0xf7ccafff, 0xf7cdb1ff, 0xf7cfb3ff, 0xf7d0b5ff, 0xf8d1b8ff, 0xf8d3baff, 0xf8d4bcff, 0xf8d6beff, 0xf8d7c0ff, 0xf8d9c3ff, 0xf8dac5ff, 0xf8dcc7ff, 0xf9ddc9ff, 0xf9dfcbff, 0xf9e0cdff, 0xf9e2d0ff, 0xf9e3d2ff, 0xf9e5d4ff, 0xfae6d6ff, 0xfae8d8ff, 0xfae9daff, 0xfaebddff);
2738preset!(mako; 0xb0405ff, 0xd0406ff, 0xe0508ff, 0xf0609ff, 0x10060aff, 0x11070cff, 0x12080dff, 0x13090fff, 0x140910ff, 0x150a12ff, 0x160b13ff, 0x170c15ff, 0x180d16ff, 0x190e18ff, 0x1a0e19ff, 0x1b0f1aff, 0x1c101cff, 0x1d111dff, 0x1e111fff, 0x1f1220ff, 0x201322ff, 0x211423ff, 0x221425ff, 0x231526ff, 0x241628ff, 0x251729ff, 0x26172bff, 0x27182dff, 0x28192eff, 0x291930ff, 0x291a31ff, 0x2a1b33ff, 0x2b1c35ff, 0x2c1c36ff, 0x2d1d38ff, 0x2e1e39ff, 0x2e1e3bff, 0x2f1f3dff, 0x30203eff, 0x312140ff, 0x312142ff, 0x322243ff, 0x332345ff, 0x342447ff, 0x342548ff, 0x35254aff, 0x35264cff, 0x36274dff, 0x37284fff, 0x372851ff, 0x382953ff, 0x382a54ff, 0x392b56ff, 0x3a2c58ff, 0x3a2c59ff, 0x3b2d5bff, 0x3b2e5dff, 0x3b2f5fff, 0x3c3060ff, 0x3c3162ff, 0x3d3164ff, 0x3d3266ff, 0x3e3367ff, 0x3e3469ff, 0x3e356bff, 0x3f366dff, 0x3f366fff, 0x3f3770ff, 0x403872ff, 0x403974ff, 0x403a76ff, 0x403b78ff, 0x403c79ff, 0x413d7bff, 0x413e7dff, 0x413e7fff, 0x413f80ff, 0x414082ff, 0x414184ff, 0x414285ff, 0x414387ff, 0x414488ff, 0x40468aff, 0x40478bff, 0x40488dff, 0x40498eff, 0x3f4a8fff, 0x3f4b90ff, 0x3f4c92ff, 0x3e4d93ff, 0x3e4f94ff, 0x3e5095ff, 0x3d5195ff, 0x3d5296ff, 0x3c5397ff, 0x3c5598ff, 0x3b5698ff, 0x3b5799ff, 0x3b589aff, 0x3a599aff, 0x3a5b9bff, 0x3a5c9bff, 0x395d9cff, 0x395e9cff, 0x385f9cff, 0x38619dff, 0x38629dff, 0x38639dff, 0x37649eff, 0x37659eff, 0x37669eff, 0x37689fff, 0x36699fff, 0x366a9fff, 0x366b9fff, 0x366ca0ff, 0x366da0ff, 0x366fa0ff, 0x3670a0ff, 0x3671a0ff, 0x3572a1ff, 0x3573a1ff, 0x3574a1ff, 0x3575a1ff, 0x3576a2ff, 0x3578a2ff, 0x3579a2ff, 0x357aa2ff, 0x357ba3ff, 0x357ca3ff, 0x357da3ff, 0x357ea4ff, 0x347fa4ff, 0x3480a4ff, 0x3482a4ff, 0x3483a5ff, 0x3484a5ff, 0x3485a5ff, 0x3486a5ff, 0x3487a6ff, 0x3488a6ff, 0x3489a6ff, 0x348ba6ff, 0x348ca7ff, 0x348da7ff, 0x348ea7ff, 0x348fa7ff, 0x3490a8ff, 0x3491a8ff, 0x3492a8ff, 0x3493a8ff, 0x3495a9ff, 0x3496a9ff, 0x3497a9ff, 0x3498a9ff, 0x3499aaff, 0x349aaaff, 0x359baaff, 0x359caaff, 0x359eaaff, 0x359fabff, 0x35a0abff, 0x35a1abff, 0x36a2abff, 0x36a3abff, 0x36a4abff, 0x37a5acff, 0x37a6acff, 0x37a8acff, 0x38a9acff, 0x38aaacff, 0x39abacff, 0x39acacff, 0x3aadacff, 0x3aaeadff, 0x3bafadff, 0x3cb1adff, 0x3cb2adff, 0x3db3adff, 0x3eb4adff, 0x3fb5adff, 0x3fb6adff, 0x40b7adff, 0x41b8adff, 0x42b9adff, 0x43baadff, 0x44bcadff, 0x45bdadff, 0x46beadff, 0x47bfadff, 0x48c0adff, 0x49c1adff, 0x4bc2adff, 0x4cc3adff, 0x4dc4adff, 0x4fc5adff, 0x50c6adff, 0x52c7adff, 0x53c9adff, 0x55caadff, 0x57cbadff, 0x59ccadff, 0x5bcdadff, 0x5ecdadff, 0x60ceacff, 0x62cfacff, 0x65d0adff, 0x68d1adff, 0x6ad2adff, 0x6dd3adff, 0x70d4adff, 0x73d4adff, 0x76d5aeff, 0x79d6aeff, 0x7cd6afff, 0x7fd7afff, 0x82d8b0ff, 0x85d9b1ff, 0x88d9b1ff, 0x8bdab2ff, 0x8edbb3ff, 0x91dbb4ff, 0x94dcb5ff, 0x96ddb5ff, 0x99ddb6ff, 0x9cdeb7ff, 0x9edfb8ff, 0xa1dfb9ff, 0xa4e0bbff, 0xa6e1bcff, 0xa9e1bdff, 0xabe2beff, 0xaee3c0ff, 0xb0e4c1ff, 0xb2e4c2ff, 0xb5e5c4ff, 0xb7e6c5ff, 0xb9e6c7ff, 0xbbe7c8ff, 0xbee8caff, 0xc0e9ccff, 0xc2e9cdff, 0xc4eacfff, 0xc6ebd1ff, 0xc8ecd2ff, 0xcaedd4ff, 0xccedd6ff, 0xceeed7ff, 0xd0efd9ff, 0xd2f0dbff, 0xd4f1dcff, 0xd6f1deff, 0xd8f2e0ff, 0xdaf3e1ff, 0xdcf4e3ff, 0xdef5e5ff);
2739preset!(coolwarm; 0x3b4cc0ff, 0x3c4ec2ff, 0x3d50c3ff, 0x3e51c5ff, 0x3f53c6ff, 0x4055c8ff, 0x4257c9ff, 0x4358cbff, 0x445accff, 0x455cceff, 0x465ecfff, 0x485fd1ff, 0x4961d2ff, 0x4a63d3ff, 0x4b64d5ff, 0x4c66d6ff, 0x4e68d8ff, 0x4f69d9ff, 0x506bdaff, 0x516ddbff, 0x536eddff, 0x5470deff, 0x5572dfff, 0x5673e0ff, 0x5875e1ff, 0x5977e3ff, 0x5a78e4ff, 0x5b7ae5ff, 0x5d7ce6ff, 0x5e7de7ff, 0x5f7fe8ff, 0x6180e9ff, 0x6282eaff, 0x6384ebff, 0x6485ecff, 0x6687edff, 0x6788eeff, 0x688aefff, 0x6a8befff, 0x6b8df0ff, 0x6c8ff1ff, 0x6e90f2ff, 0x6f92f3ff, 0x7093f3ff, 0x7295f4ff, 0x7396f5ff, 0x7597f6ff, 0x7699f6ff, 0x779af7ff, 0x799cf8ff, 0x7a9df8ff, 0x7b9ff9ff, 0x7da0f9ff, 0x7ea1faff, 0x80a3faff, 0x81a4fbff, 0x82a6fbff, 0x84a7fcff, 0x85a8fcff, 0x86a9fcff, 0x88abfdff, 0x89acfdff, 0x8badfdff, 0x8caffeff, 0x8db0feff, 0x8fb1feff, 0x90b2feff, 0x92b4feff, 0x93b5feff, 0x94b6ffff, 0x96b7ffff, 0x97b8ffff, 0x98b9ffff, 0x9abbffff, 0x9bbcffff, 0x9dbdffff, 0x9ebeffff, 0x9fbfffff, 0xa1c0ffff, 0xa2c1ffff, 0xa3c2feff, 0xa5c3feff, 0xa6c4feff, 0xa7c5feff, 0xa9c6fdff, 0xaac7fdff, 0xabc8fdff, 0xadc9fdff, 0xaec9fcff, 0xafcafcff, 0xb1cbfcff, 0xb2ccfbff, 0xb3cdfbff, 0xb5cdfaff, 0xb6cefaff, 0xb7cff9ff, 0xb9d0f9ff, 0xbad0f8ff, 0xbbd1f8ff, 0xbcd2f7ff, 0xbed2f6ff, 0xbfd3f6ff, 0xc0d4f5ff, 0xc1d4f4ff, 0xc3d5f4ff, 0xc4d5f3ff, 0xc5d6f2ff, 0xc6d6f1ff, 0xc7d7f0ff, 0xc9d7f0ff, 0xcad8efff, 0xcbd8eeff, 0xccd9edff, 0xcdd9ecff, 0xcedaebff, 0xcfdaeaff, 0xd1dae9ff, 0xd2dbe8ff, 0xd3dbe7ff, 0xd4dbe6ff, 0xd5dbe5ff, 0xd6dce4ff, 0xd7dce3ff, 0xd8dce2ff, 0xd9dce1ff, 0xdadce0ff, 0xdbdcdeff, 0xdcddddff, 0xdddcdcff, 0xdedcdbff, 0xdfdbd9ff, 0xe0dbd8ff, 0xe1dad6ff, 0xe2dad5ff, 0xe3d9d3ff, 0xe4d9d2ff, 0xe5d8d1ff, 0xe6d7cfff, 0xe7d7ceff, 0xe8d6ccff, 0xe9d5cbff, 0xead5c9ff, 0xead4c8ff, 0xebd3c6ff, 0xecd3c5ff, 0xedd2c3ff, 0xedd1c2ff, 0xeed0c0ff, 0xefcfbfff, 0xefcebdff, 0xf0cdbbff, 0xf1cdbaff, 0xf1ccb8ff, 0xf2cbb7ff, 0xf2cab5ff, 0xf2c9b4ff, 0xf3c8b2ff, 0xf3c7b1ff, 0xf4c6afff, 0xf4c5adff, 0xf5c4acff, 0xf5c2aaff, 0xf5c1a9ff, 0xf5c0a7ff, 0xf6bfa6ff, 0xf6bea4ff, 0xf6bda2ff, 0xf7bca1ff, 0xf7ba9fff, 0xf7b99eff, 0xf7b89cff, 0xf7b79bff, 0xf7b599ff, 0xf7b497ff, 0xf7b396ff, 0xf7b194ff, 0xf7b093ff, 0xf7af91ff, 0xf7ad90ff, 0xf7ac8eff, 0xf7aa8cff, 0xf7a98bff, 0xf7a889ff, 0xf7a688ff, 0xf6a586ff, 0xf6a385ff, 0xf6a283ff, 0xf5a081ff, 0xf59f80ff, 0xf59d7eff, 0xf59c7dff, 0xf49a7bff, 0xf4987aff, 0xf39778ff, 0xf39577ff, 0xf39475ff, 0xf29274ff, 0xf29072ff, 0xf18f71ff, 0xf18d6fff, 0xf08b6eff, 0xf08a6cff, 0xef886bff, 0xee8669ff, 0xee8468ff, 0xed8366ff, 0xec8165ff, 0xec7f63ff, 0xeb7d62ff, 0xea7b60ff, 0xe97a5fff, 0xe9785dff, 0xe8765cff, 0xe7745bff, 0xe67259ff, 0xe57058ff, 0xe46e56ff, 0xe36c55ff, 0xe36b54ff, 0xe26952ff, 0xe16751ff, 0xe0654fff, 0xdf634eff, 0xde614dff, 0xdd5f4bff, 0xdc5d4aff, 0xda5a49ff, 0xd95847ff, 0xd85646ff, 0xd75445ff, 0xd65244ff, 0xd55042ff, 0xd44e41ff, 0xd24b40ff, 0xd1493fff, 0xd0473dff, 0xcf453cff, 0xcd423bff, 0xcc403aff, 0xcb3e38ff, 0xca3b37ff, 0xc83836ff, 0xc73635ff, 0xc53334ff, 0xc43032ff, 0xc32e31ff, 0xc12b30ff, 0xc0282fff, 0xbe242eff, 0xbd1f2dff, 0xbb1b2cff, 0xba162bff, 0xb8122aff, 0xb70d28ff, 0xb50927ff, 0xb40426ff);
2740preset!(vlag; 0x2369bdff, 0x266abdff, 0x296cbcff, 0x2c6dbcff, 0x2f6ebcff, 0x316fbcff, 0x3470bcff, 0x3671bcff, 0x3972bcff, 0x3b73bcff, 0x3d74bcff, 0x3f75bcff, 0x4276bcff, 0x4477bcff, 0x4678bcff, 0x4879bcff, 0x4a7bbcff, 0x4c7cbcff, 0x4e7dbcff, 0x507ebcff, 0x517fbcff, 0x5380bcff, 0x5581bcff, 0x5782bcff, 0x5983bdff, 0x5b84bdff, 0x5c85bdff, 0x5e86bdff, 0x6087bdff, 0x6288bdff, 0x6489beff, 0x658abeff, 0x678bbeff, 0x698cbeff, 0x6a8dbfff, 0x6c8ebfff, 0x6e90bfff, 0x6f91bfff, 0x7192c0ff, 0x7393c0ff, 0x7594c0ff, 0x7695c1ff, 0x7896c1ff, 0x7997c1ff, 0x7b98c2ff, 0x7d99c2ff, 0x7e9ac2ff, 0x809bc3ff, 0x829cc3ff, 0x839dc4ff, 0x859ec4ff, 0x87a0c4ff, 0x88a1c5ff, 0x8aa2c5ff, 0x8ba3c6ff, 0x8da4c6ff, 0x8fa5c7ff, 0x90a6c7ff, 0x92a7c8ff, 0x93a8c8ff, 0x95a9c8ff, 0x97abc9ff, 0x98acc9ff, 0x9aadcaff, 0x9baecbff, 0x9dafcbff, 0x9fb0ccff, 0xa0b1ccff, 0xa2b2cdff, 0xa3b4cdff, 0xa5b5ceff, 0xa7b6ceff, 0xa8b7cfff, 0xaab8d0ff, 0xabb9d0ff, 0xadbbd1ff, 0xafbcd1ff, 0xb0bdd2ff, 0xb2bed3ff, 0xb3bfd3ff, 0xb5c0d4ff, 0xb7c2d5ff, 0xb8c3d5ff, 0xbac4d6ff, 0xbbc5d7ff, 0xbdc6d7ff, 0xbfc8d8ff, 0xc0c9d9ff, 0xc2cadaff, 0xc3cbdaff, 0xc5cddbff, 0xc7cedcff, 0xc8cfddff, 0xcad0ddff, 0xcbd1deff, 0xcdd3dfff, 0xcfd4e0ff, 0xd0d5e0ff, 0xd2d7e1ff, 0xd4d8e2ff, 0xd5d9e3ff, 0xd7dae4ff, 0xd9dce5ff, 0xdadde5ff, 0xdcdee6ff, 0xdde0e7ff, 0xdfe1e8ff, 0xe1e2e9ff, 0xe2e3eaff, 0xe4e5ebff, 0xe6e6ecff, 0xe7e7ecff, 0xe9e9edff, 0xebeaeeff, 0xecebefff, 0xeeedf0ff, 0xefeef1ff, 0xf1eff2ff, 0xf2f0f2ff, 0xf3f1f3ff, 0xf5f2f4ff, 0xf6f3f4ff, 0xf7f4f4ff, 0xf8f4f5ff, 0xf9f5f5ff, 0xf9f5f5ff, 0xfaf5f5ff, 0xfaf5f5ff, 0xfaf5f4ff, 0xfaf5f4ff, 0xfaf4f3ff, 0xfaf3f3ff, 0xfaf3f2ff, 0xfaf2f1ff, 0xfaf0efff, 0xf9efeeff, 0xf9eeedff, 0xf8edebff, 0xf7ebeaff, 0xf7eae8ff, 0xf6e8e7ff, 0xf5e7e5ff, 0xf5e5e4ff, 0xf4e3e2ff, 0xf3e2e0ff, 0xf2e0dfff, 0xf2dfddff, 0xf1dddbff, 0xf0dbdaff, 0xefdad8ff, 0xefd8d6ff, 0xeed7d5ff, 0xedd5d3ff, 0xecd3d2ff, 0xecd2d0ff, 0xebd0ceff, 0xeacfcdff, 0xeacdcbff, 0xe9cbc9ff, 0xe8cac8ff, 0xe7c8c6ff, 0xe7c7c5ff, 0xe6c5c3ff, 0xe5c3c1ff, 0xe5c2c0ff, 0xe4c0beff, 0xe3bfbdff, 0xe3bdbbff, 0xe2bcb9ff, 0xe1bab8ff, 0xe1b9b6ff, 0xe0b7b5ff, 0xdfb5b3ff, 0xdfb4b2ff, 0xdeb2b0ff, 0xdeb1aeff, 0xddafadff, 0xdcaeabff, 0xdcacaaff, 0xdbaba8ff, 0xdaa9a7ff, 0xdaa8a5ff, 0xd9a6a4ff, 0xd9a5a2ff, 0xd8a3a0ff, 0xd7a29fff, 0xd7a09dff, 0xd69f9cff, 0xd59d9aff, 0xd59c99ff, 0xd49a97ff, 0xd49896ff, 0xd39794ff, 0xd29593ff, 0xd29491ff, 0xd19290ff, 0xd1918eff, 0xd08f8dff, 0xcf8e8bff, 0xcf8c8aff, 0xce8b88ff, 0xcd8987ff, 0xcd8885ff, 0xcc8784ff, 0xcc8582ff, 0xcb8481ff, 0xca827fff, 0xca817eff, 0xc97f7dff, 0xc87e7bff, 0xc87c7aff, 0xc77b78ff, 0xc77977ff, 0xc67875ff, 0xc57674ff, 0xc57572ff, 0xc47371ff, 0xc3726fff, 0xc3706eff, 0xc26f6dff, 0xc16d6bff, 0xc16c6aff, 0xc06a68ff, 0xc06967ff, 0xbf6765ff, 0xbe6664ff, 0xbe6463ff, 0xbd6361ff, 0xbc6160ff, 0xbc605eff, 0xbb5e5dff, 0xba5d5cff, 0xb95b5aff, 0xb95a59ff, 0xb85857ff, 0xb75756ff, 0xb75555ff, 0xb65453ff, 0xb55252ff, 0xb55151ff, 0xb44f4fff, 0xb34d4eff, 0xb24c4cff, 0xb24a4bff, 0xb1494aff, 0xb04748ff, 0xaf4647ff, 0xaf4446ff, 0xae4244ff, 0xad4143ff, 0xac3f42ff, 0xac3e40ff, 0xab3c3fff, 0xaa3a3eff, 0xa9393cff, 0xa9373bff);
2741preset!(icefire; 0xbde7dbff, 0xbae5daff, 0xb7e3d9ff, 0xb4e1d9ff, 0xb2dfd8ff, 0xafddd7ff, 0xacdbd7ff, 0xa9d9d6ff, 0xa7d7d5ff, 0xa4d5d5ff, 0xa1d3d4ff, 0x9ed1d3ff, 0x9bcfd3ff, 0x98cdd2ff, 0x95cbd2ff, 0x93cad1ff, 0x90c8d1ff, 0x8dc6d0ff, 0x8ac4d0ff, 0x87c2cfff, 0x84c1cfff, 0x81bfcfff, 0x7ebdceff, 0x7bbbceff, 0x78b9ceff, 0x75b8ceff, 0x72b6ceff, 0x6eb4cdff, 0x6bb2cdff, 0x68b0cdff, 0x65afcdff, 0x63adcdff, 0x60abcdff, 0x5da9cdff, 0x5aa7cdff, 0x58a5cdff, 0x55a3cdff, 0x53a2cdff, 0x50a0cdff, 0x4e9ecdff, 0x4c9ccdff, 0x499aceff, 0x4798ceff, 0x4596ceff, 0x4394ceff, 0x4192ceff, 0x3f90ceff, 0x3e8ecfff, 0x3c8ccfff, 0x3a89cfff, 0x3987cfff, 0x3885d0ff, 0x3783d0ff, 0x3781d0ff, 0x377fd0ff, 0x377cd0ff, 0x377ad0ff, 0x3878cfff, 0x3975cfff, 0x3a73ceff, 0x3b71cdff, 0x3d6eccff, 0x3e6ccbff, 0x3f69c9ff, 0x4167c7ff, 0x4265c5ff, 0x4363c3ff, 0x4560c1ff, 0x465ebeff, 0x475cbcff, 0x475ab9ff, 0x4858b6ff, 0x4956b3ff, 0x4954b0ff, 0x4952adff, 0x4a50a9ff, 0x4a4fa5ff, 0x494da1ff, 0x494c9eff, 0x494a9aff, 0x484996ff, 0x474792ff, 0x47468eff, 0x46458aff, 0x454386ff, 0x444282ff, 0x43417fff, 0x42407bff, 0x413e77ff, 0x3f3d74ff, 0x3e3c70ff, 0x3d3b6dff, 0x3c3a69ff, 0x3b3866ff, 0x393763ff, 0x38365fff, 0x37355cff, 0x363459ff, 0x343356ff, 0x333153ff, 0x323050ff, 0x312f4dff, 0x302e4aff, 0x2e2d48ff, 0x2d2c45ff, 0x2c2b42ff, 0x2b2a40ff, 0x2a293dff, 0x29283bff, 0x282739ff, 0x272636ff, 0x262534ff, 0x252532ff, 0x242430ff, 0x24232eff, 0x23222dff, 0x22222bff, 0x222129ff, 0x212028ff, 0x212026ff, 0x202025ff, 0x201f24ff, 0x1f1f23ff, 0x1f1f21ff, 0x1f1e21ff, 0x1f1e20ff, 0x1f1e1fff, 0x1f1e1eff, 0x1f1e1eff, 0x201e1eff, 0x211e1eff, 0x221e1eff, 0x231e1eff, 0x251e1fff, 0x261e1fff, 0x271e1fff, 0x291e20ff, 0x2a1e20ff, 0x2c1e21ff, 0x2d1f21ff, 0x2f1f22ff, 0x311f23ff, 0x332023ff, 0x352024ff, 0x372025ff, 0x392126ff, 0x3b2127ff, 0x3d2228ff, 0x3f2228ff, 0x412329ff, 0x43232aff, 0x46242bff, 0x48242cff, 0x4a252eff, 0x4d252fff, 0x4f2630ff, 0x522731ff, 0x542732ff, 0x572833ff, 0x5a2834ff, 0x5c2935ff, 0x5f2936ff, 0x622937ff, 0x642a38ff, 0x672a39ff, 0x6a2b3aff, 0x6d2b3bff, 0x702b3cff, 0x722c3dff, 0x752c3eff, 0x782c3fff, 0x7b2d40ff, 0x7e2d40ff, 0x812d41ff, 0x842d42ff, 0x872d42ff, 0x8a2e43ff, 0x8d2e43ff, 0x902e44ff, 0x932e44ff, 0x962e44ff, 0x992e44ff, 0x9c2f45ff, 0x9f2f44ff, 0xa22f44ff, 0xa52f44ff, 0xa83044ff, 0xab3043ff, 0xae3143ff, 0xb13242ff, 0xb33341ff, 0xb63441ff, 0xb93540ff, 0xbb363fff, 0xbe373eff, 0xc0393dff, 0xc33a3cff, 0xc53c3cff, 0xc73d3bff, 0xc93f3aff, 0xcc4139ff, 0xce4338ff, 0xd04537ff, 0xd24737ff, 0xd34936ff, 0xd54b35ff, 0xd74e35ff, 0xd95034ff, 0xda5334ff, 0xdc5534ff, 0xde5733ff, 0xdf5a33ff, 0xe15c33ff, 0xe25f33ff, 0xe36233ff, 0xe56433ff, 0xe66734ff, 0xe76a34ff, 0xe86d35ff, 0xe96f36ff, 0xea7238ff, 0xeb753aff, 0xec783bff, 0xed7b3eff, 0xed7e40ff, 0xee8142ff, 0xef8445ff, 0xef8748ff, 0xf0894bff, 0xf18c4eff, 0xf18f51ff, 0xf29255ff, 0xf29558ff, 0xf3985bff, 0xf39a5fff, 0xf49d63ff, 0xf5a066ff, 0xf5a36aff, 0xf6a56dff, 0xf6a871ff, 0xf7ab75ff, 0xf7ae79ff, 0xf8b07cff, 0xf8b380ff, 0xf9b684ff, 0xfab887ff, 0xfabb8bff, 0xfbbe8fff, 0xfbc192ff, 0xfcc396ff, 0xfcc69aff, 0xfdc99eff, 0xfdcca1ff, 0xfecea5ff, 0xfed1a9ff, 0xffd4acff);
2742preset!(flare; 0xedb081ff, 0xedaf80ff, 0xedae7fff, 0xedad7fff, 0xedac7eff, 0xedab7eff, 0xecaa7dff, 0xeca97cff, 0xeca87cff, 0xeca77bff, 0xeca67bff, 0xeca57aff, 0xeca479ff, 0xeca379ff, 0xeca278ff, 0xeca178ff, 0xeca077ff, 0xec9f76ff, 0xeb9e76ff, 0xeb9d75ff, 0xeb9c75ff, 0xeb9b74ff, 0xeb9a73ff, 0xeb9973ff, 0xeb9972ff, 0xeb9872ff, 0xeb9771ff, 0xea9671ff, 0xea9570ff, 0xea946fff, 0xea936fff, 0xea926eff, 0xea916eff, 0xea906dff, 0xea8f6cff, 0xea8e6cff, 0xe98d6bff, 0xe98c6bff, 0xe98b6aff, 0xe98a6aff, 0xe98969ff, 0xe98868ff, 0xe98768ff, 0xe98667ff, 0xe88567ff, 0xe88466ff, 0xe88366ff, 0xe88265ff, 0xe88165ff, 0xe88064ff, 0xe87f64ff, 0xe77e63ff, 0xe77d63ff, 0xe77c63ff, 0xe77b62ff, 0xe77a62ff, 0xe67961ff, 0xe67861ff, 0xe67760ff, 0xe67660ff, 0xe67560ff, 0xe5745fff, 0xe5735fff, 0xe5725fff, 0xe5715eff, 0xe5705eff, 0xe46f5eff, 0xe46e5eff, 0xe46d5dff, 0xe46c5dff, 0xe36b5dff, 0xe36a5dff, 0xe3695dff, 0xe3685cff, 0xe2675cff, 0xe2665cff, 0xe2655cff, 0xe1645cff, 0xe1635cff, 0xe1625cff, 0xe0615cff, 0xe0605cff, 0xe05f5cff, 0xdf5f5cff, 0xdf5e5cff, 0xde5d5cff, 0xde5c5cff, 0xde5b5cff, 0xdd5a5cff, 0xdd595cff, 0xdc585cff, 0xdc575cff, 0xdb565dff, 0xdb565dff, 0xda555dff, 0xda545dff, 0xd9535dff, 0xd9525eff, 0xd8525eff, 0xd7515eff, 0xd7505eff, 0xd64f5fff, 0xd64f5fff, 0xd54e5fff, 0xd44d60ff, 0xd44c60ff, 0xd34c60ff, 0xd24b60ff, 0xd24a61ff, 0xd14a61ff, 0xd04962ff, 0xd04962ff, 0xcf4862ff, 0xce4763ff, 0xcd4763ff, 0xcc4663ff, 0xcc4664ff, 0xcb4564ff, 0xca4564ff, 0xc94465ff, 0xc84465ff, 0xc84365ff, 0xc74366ff, 0xc64366ff, 0xc54266ff, 0xc44267ff, 0xc34167ff, 0xc24167ff, 0xc14168ff, 0xc14068ff, 0xc04068ff, 0xbf4069ff, 0xbe3f69ff, 0xbd3f69ff, 0xbc3f69ff, 0xbb3f6aff, 0xba3e6aff, 0xb93e6aff, 0xb83e6bff, 0xb73d6bff, 0xb63d6bff, 0xb53d6bff, 0xb43d6bff, 0xb33c6cff, 0xb23c6cff, 0xb13c6cff, 0xb13c6cff, 0xb03b6dff, 0xaf3b6dff, 0xae3b6dff, 0xad3b6dff, 0xac3a6dff, 0xab3a6dff, 0xaa3a6eff, 0xa93a6eff, 0xa8396eff, 0xa7396eff, 0xa6396eff, 0xa5396eff, 0xa4386fff, 0xa3386fff, 0xa2386fff, 0xa1386fff, 0xa1376fff, 0xa0376fff, 0x9f376fff, 0x9e3770ff, 0x9d3670ff, 0x9c3670ff, 0x9b3670ff, 0x9a3670ff, 0x993570ff, 0x983570ff, 0x973570ff, 0x963570ff, 0x953470ff, 0x943470ff, 0x943471ff, 0x933471ff, 0x923371ff, 0x913371ff, 0x903371ff, 0x8f3371ff, 0x8e3271ff, 0x8d3271ff, 0x8c3271ff, 0x8b3271ff, 0x8a3171ff, 0x893171ff, 0x883171ff, 0x873171ff, 0x873171ff, 0x863071ff, 0x853071ff, 0x843071ff, 0x833070ff, 0x822f70ff, 0x812f70ff, 0x802f70ff, 0x7f2f70ff, 0x7e2f70ff, 0x7d2e70ff, 0x7c2e70ff, 0x7b2e70ff, 0x7a2e70ff, 0x792e6fff, 0x782e6fff, 0x772d6fff, 0x762d6fff, 0x752d6fff, 0x752d6fff, 0x742d6eff, 0x732c6eff, 0x722c6eff, 0x712c6eff, 0x702c6eff, 0x6f2c6dff, 0x6e2c6dff, 0x6d2b6dff, 0x6c2b6dff, 0x6b2b6cff, 0x6a2b6cff, 0x692b6cff, 0x682a6cff, 0x672a6bff, 0x662a6bff, 0x652a6bff, 0x642a6aff, 0x642a6aff, 0x63296aff, 0x62296aff, 0x612969ff, 0x602969ff, 0x5f2969ff, 0x5e2868ff, 0x5d2868ff, 0x5c2868ff, 0x5b2867ff, 0x5a2767ff, 0x592767ff, 0x582766ff, 0x582766ff, 0x572766ff, 0x562666ff, 0x552665ff, 0x542665ff, 0x532665ff, 0x522564ff, 0x512564ff, 0x502564ff, 0x4f2463ff, 0x4f2463ff, 0x4e2463ff, 0x4d2463ff, 0x4c2362ff, 0x4b2362ff);
2743preset!(crest; 0xa5cd90ff, 0xa4cc90ff, 0xa3cc91ff, 0xa2cb91ff, 0xa0cb91ff, 0x9fca91ff, 0x9eca91ff, 0x9dc991ff, 0x9cc891ff, 0x9bc891ff, 0x9ac791ff, 0x99c791ff, 0x98c691ff, 0x96c691ff, 0x95c591ff, 0x94c591ff, 0x93c491ff, 0x92c491ff, 0x91c391ff, 0x90c391ff, 0x8fc291ff, 0x8ec291ff, 0x8dc191ff, 0x8bc191ff, 0x8ac091ff, 0x89bf91ff, 0x88bf91ff, 0x87be91ff, 0x86be91ff, 0x85bd91ff, 0x84bd91ff, 0x82bc91ff, 0x81bc91ff, 0x80bb91ff, 0x7fbb91ff, 0x7eba91ff, 0x7dba91ff, 0x7cb991ff, 0x7bb991ff, 0x79b891ff, 0x78b891ff, 0x77b791ff, 0x76b791ff, 0x75b690ff, 0x74b690ff, 0x73b590ff, 0x72b490ff, 0x71b490ff, 0x70b390ff, 0x6fb390ff, 0x6eb290ff, 0x6db290ff, 0x6cb190ff, 0x6bb190ff, 0x6ab090ff, 0x69b090ff, 0x68af90ff, 0x67ae90ff, 0x66ae90ff, 0x65ad90ff, 0x64ad90ff, 0x63ac90ff, 0x62ac90ff, 0x62ab90ff, 0x61aa90ff, 0x60aa90ff, 0x5fa990ff, 0x5ea990ff, 0x5da890ff, 0x5ca890ff, 0x5ba790ff, 0x5ba690ff, 0x5aa690ff, 0x59a590ff, 0x58a590ff, 0x57a490ff, 0x57a490ff, 0x56a390ff, 0x55a290ff, 0x54a290ff, 0x53a190ff, 0x53a190ff, 0x52a090ff, 0x519f90ff, 0x509f90ff, 0x509e90ff, 0x4f9e90ff, 0x4e9d90ff, 0x4e9d90ff, 0x4d9c90ff, 0x4c9b90ff, 0x4b9b90ff, 0x4b9a8fff, 0x4a9a8fff, 0x49998fff, 0x49988fff, 0x48988fff, 0x47978fff, 0x47978fff, 0x46968fff, 0x45958fff, 0x45958fff, 0x44948fff, 0x43948fff, 0x43938fff, 0x42928fff, 0x41928fff, 0x41918fff, 0x40918fff, 0x40908eff, 0x3f8f8eff, 0x3e8f8eff, 0x3e8e8eff, 0x3d8e8eff, 0x3c8d8eff, 0x3c8c8eff, 0x3b8c8eff, 0x3a8b8eff, 0x3a8b8eff, 0x398a8eff, 0x388a8eff, 0x38898eff, 0x37888eff, 0x37888dff, 0x36878dff, 0x35878dff, 0x35868dff, 0x34858dff, 0x33858dff, 0x33848dff, 0x32848dff, 0x31838dff, 0x31828dff, 0x30828dff, 0x2f818dff, 0x2f818dff, 0x2e808dff, 0x2d808cff, 0x2d7f8cff, 0x2c7e8cff, 0x2c7e8cff, 0x2b7d8cff, 0x2a7d8cff, 0x2a7c8cff, 0x297b8cff, 0x287b8cff, 0x287a8cff, 0x277a8cff, 0x27798cff, 0x26788cff, 0x25788cff, 0x25778cff, 0x24778bff, 0x24768bff, 0x23758bff, 0x23758bff, 0x22748bff, 0x22748bff, 0x21738bff, 0x21728bff, 0x20728bff, 0x20718bff, 0x20718bff, 0x1f708bff, 0x1f6f8aff, 0x1e6f8aff, 0x1e6e8aff, 0x1e6d8aff, 0x1e6d8aff, 0x1d6c8aff, 0x1d6c8aff, 0x1d6b8aff, 0x1d6a8aff, 0x1d6a8aff, 0x1c6989ff, 0x1c6889ff, 0x1c6889ff, 0x1c6789ff, 0x1c6689ff, 0x1c6689ff, 0x1c6589ff, 0x1c6488ff, 0x1c6488ff, 0x1c6388ff, 0x1d6388ff, 0x1d6288ff, 0x1d6188ff, 0x1d6187ff, 0x1d6087ff, 0x1d5f87ff, 0x1d5f87ff, 0x1e5e87ff, 0x1e5d86ff, 0x1e5d86ff, 0x1e5c86ff, 0x1e5b86ff, 0x1f5b86ff, 0x1f5a85ff, 0x1f5985ff, 0x1f5985ff, 0x205885ff, 0x205784ff, 0x205784ff, 0x205684ff, 0x215584ff, 0x215583ff, 0x215483ff, 0x225383ff, 0x225283ff, 0x225282ff, 0x225182ff, 0x235082ff, 0x235081ff, 0x234f81ff, 0x244e81ff, 0x244e80ff, 0x244d80ff, 0x254c80ff, 0x254c7fff, 0x254b7fff, 0x254a7fff, 0x26497eff, 0x26497eff, 0x26487eff, 0x27477dff, 0x27477dff, 0x27467cff, 0x27457cff, 0x28457cff, 0x28447bff, 0x28437bff, 0x28427aff, 0x29427aff, 0x29417aff, 0x294079ff, 0x294079ff, 0x2a3f78ff, 0x2a3e78ff, 0x2a3d78ff, 0x2a3d77ff, 0x2a3c77ff, 0x2a3b76ff, 0x2b3b76ff, 0x2b3a76ff, 0x2b3975ff, 0x2b3875ff, 0x2b3875ff, 0x2b3774ff, 0x2b3674ff, 0x2c3574ff, 0x2c3573ff, 0x2c3473ff, 0x2c3373ff, 0x2c3272ff, 0x2c3172ff, 0x2c3172ff);
2744
2745#[cfg(test)]
2746mod tests {
2747    use super::*;
2748
2749    #[test]
2750    fn test_parse_color_strings() {
2751        #[track_caller]
2752        fn test(hex: &str, r: u8, g: u8, b: u8, a: u8) {
2753            assert_eq!(Color::from_str(hex), Ok(Color::from_u8(r, g, b, a)));
2754        }
2755
2756        test("f61243ff", 0xf6, 0x12, 0x43, 255);
2757        test("b3d8b3", 0xb3, 0xd8, 0xb3, 255);
2758        test("fCd2a9AD", 0xfc, 0xd2, 0xa9, 0xad);
2759        test("233", 0x22, 0x33, 0x33, 255);
2760        test("111b", 0x11, 0x11, 0x11, 0xbb);
2761    }
2762
2763    #[test]
2764    fn test_parse_invalid_colors() {
2765        #[track_caller]
2766        fn test(hex: &str, message: &str) {
2767            assert_eq!(Color::from_str(hex), Err(message));
2768        }
2769
2770        test("a5", "color string has wrong length");
2771        test("12345", "color string has wrong length");
2772        test("f075ff011", "color string has wrong length");
2773        test("hmmm", "color string contains non-hexadecimal letters");
2774        test("14B2AH", "color string contains non-hexadecimal letters");
2775    }
2776}