#[cfg(feature = "rgb-aces-2065-1")]
mod aces_2065_1;
#[cfg(feature = "rgb-aces-cc")]
mod aces_cc;
#[cfg(feature = "rgb-aces-cct")]
mod aces_cct;
#[cfg(feature = "rgb-aces-cg")]
mod aces_cg;
#[cfg(feature = "rgb-adobe-rgb")]
mod adobe_rgb;
#[cfg(feature = "rgb-apple-rgb")]
mod apple_rgb;
#[cfg(feature = "rgb-arri-wide-gamut-3")]
mod arri_wide_gamut_3;
#[cfg(feature = "rgb-arri-wide-gamut-4")]
mod arri_wide_gamut_4;
#[cfg(feature = "rgb-best-rgb")]
mod best_rgb;
#[cfg(feature = "rgb-beta-rgb")]
mod beta_rgb;
#[cfg(feature = "rgb-blackmagic-wide-gamut")]
mod blackmagic_wide_gamut;
#[cfg(feature = "rgb-bruce-rgb")]
mod bruce_rgb;
#[cfg(feature = "rgb-canon-cinema-gamut")]
mod canon_cinema_gamut;
#[cfg(feature = "rgb-cie-rgb")]
mod cie_rgb;
#[cfg(feature = "rgb-colormatch-rgb")]
mod colormatch_rgb;
#[cfg(feature = "rgb-davinci-wide-gamut")]
mod davinci_wide_gamut;
#[cfg(feature = "rgb-dci-p3")]
mod dci_p3;
#[cfg(feature = "rgb-display-p3")]
mod display_p3;
#[cfg(feature = "rgb-don-rgb-4")]
mod don_rgb_4;
#[cfg(feature = "rgb-eci-rgb-v2")]
mod eci_rgb;
#[cfg(feature = "rgb-ektargb-ps5")]
mod ektaspace_ps5;
#[cfg(feature = "rgb-filmlight-e-gamut")]
mod filmlight_egamut;
#[cfg(feature = "rgb-linear-srgb")]
mod linear_srgb;
#[cfg(feature = "rgb-ntsc")]
mod ntsc;
#[cfg(feature = "rgb-pal-secam")]
mod pal_secam;
#[cfg(feature = "rgb-panasonic-v-gamut")]
mod panasonic_vgamut;
#[cfg(feature = "rgb-prophoto-rgb")]
mod prophoto_rgb;
#[cfg(feature = "rgb-rec-2020")]
mod rec2020;
#[cfg(feature = "rgb-rec-2100-hlg")]
mod rec2100_hlg;
#[cfg(feature = "rgb-rec-2100-pq")]
mod rec2100_pq;
#[cfg(feature = "rgb-rec-601")]
mod rec601;
#[cfg(feature = "rgb-rec-709")]
mod rec709;
#[cfg(feature = "rgb-red-wide-gamut-rgb")]
mod red_wide_gamut;
#[cfg(feature = "rgb-scrgb")]
mod scrgb;
#[cfg(feature = "rgb-smpte-c")]
mod smpte_c;
#[cfg(feature = "rgb-sony-s-gamut-3")]
mod sony_sgamut3;
#[cfg(feature = "rgb-sony-s-gamut-3-cine")]
mod sony_sgamut3_cine;
mod standard;
#[cfg(feature = "rgb-wide-gamut-rgb")]
mod wide_gamut_rgb;

use std::{
  fmt::{Display, Formatter, Result as FmtResult},
  marker::PhantomData,
  ops::{Add, Div, Mul, Sub},
};

#[cfg(feature = "rgb-aces-2065-1")]
pub use aces_2065_1::Aces2065_1;
#[cfg(feature = "rgb-aces-cc")]
pub use aces_cc::AcesCc;
#[cfg(feature = "rgb-aces-cct")]
pub use aces_cct::AcesCct;
#[cfg(feature = "rgb-aces-cg")]
pub use aces_cg::AcesCg;
#[cfg(feature = "rgb-adobe-rgb")]
pub use adobe_rgb::AdobeRgb;
#[cfg(feature = "rgb-apple-rgb")]
pub use apple_rgb::AppleRgb;
#[cfg(feature = "rgb-arri-wide-gamut-3")]
pub use arri_wide_gamut_3::ArriWideGamut3;
#[cfg(feature = "rgb-arri-wide-gamut-4")]
pub use arri_wide_gamut_4::ArriWideGamut4;
#[cfg(feature = "rgb-best-rgb")]
pub use best_rgb::BestRgb;
#[cfg(feature = "rgb-beta-rgb")]
pub use beta_rgb::BetaRgb;
#[cfg(feature = "rgb-blackmagic-wide-gamut")]
pub use blackmagic_wide_gamut::BlackmagicWideGamut;
#[cfg(feature = "rgb-bruce-rgb")]
pub use bruce_rgb::BruceRgb;
#[cfg(feature = "rgb-canon-cinema-gamut")]
pub use canon_cinema_gamut::CanonCinemaGamut;
#[cfg(feature = "rgb-cie-rgb")]
pub use cie_rgb::CieRgb;
#[cfg(feature = "rgb-colormatch-rgb")]
pub use colormatch_rgb::ColorMatchRgb;
#[cfg(feature = "rgb-davinci-wide-gamut")]
pub use davinci_wide_gamut::DaVinciWideGamut;
#[cfg(feature = "rgb-dci-p3")]
pub use dci_p3::DciP3;
#[cfg(feature = "rgb-display-p3")]
pub use display_p3::DisplayP3;
#[cfg(feature = "rgb-don-rgb-4")]
pub use don_rgb_4::DonRgb4;
#[cfg(feature = "rgb-eci-rgb-v2")]
pub use eci_rgb::EciRgbV2;
#[cfg(feature = "rgb-ektargb-ps5")]
pub use ektaspace_ps5::EktaSpacePs5;
#[cfg(feature = "rgb-filmlight-e-gamut")]
pub use filmlight_egamut::FilmlightEGamut;
#[cfg(feature = "rgb-linear-srgb")]
pub use linear_srgb::LinearSrgb;
#[cfg(feature = "rgb-ntsc")]
pub use ntsc::Ntsc;
#[cfg(feature = "rgb-pal-secam")]
pub use pal_secam::PalSecam;
#[cfg(feature = "rgb-panasonic-v-gamut")]
pub use panasonic_vgamut::PanasonicVGamut;
#[cfg(feature = "rgb-prophoto-rgb")]
pub use prophoto_rgb::ProPhotoRgb;
#[cfg(feature = "rgb-rec-601")]
pub use rec601::Rec601;
#[cfg(feature = "rgb-rec-709")]
pub use rec709::Rec709;
#[cfg(feature = "rgb-rec-2020")]
pub use rec2020::Rec2020;
#[cfg(feature = "rgb-rec-2100-hlg")]
pub use rec2100_hlg::Rec2100Hlg;
#[cfg(feature = "rgb-rec-2100-pq")]
pub use rec2100_pq::Rec2100Pq;
#[cfg(feature = "rgb-red-wide-gamut-rgb")]
pub use red_wide_gamut::RedWideGamutRgb;
#[cfg(feature = "rgb-scrgb")]
pub use scrgb::ScRgb;
#[cfg(feature = "rgb-smpte-c")]
pub use smpte_c::SmpteC;
#[cfg(feature = "rgb-sony-s-gamut-3")]
pub use sony_sgamut3::SonySGamut3;
#[cfg(feature = "rgb-sony-s-gamut-3-cine")]
pub use sony_sgamut3_cine::SonySGamut3Cine;
pub use standard::Srgb;
#[cfg(feature = "rgb-wide-gamut-rgb")]
pub use wide_gamut_rgb::WideGamutRgb;

use super::{LinearRgb, RgbSpec};
#[cfg(feature = "space-cmy")]
use crate::space::Cmy;
#[cfg(feature = "space-cmyk")]
use crate::space::Cmyk;
#[cfg(feature = "space-hpluv")]
use crate::space::Hpluv;
#[cfg(feature = "space-hsi")]
use crate::space::Hsi;
#[cfg(feature = "space-hsl")]
use crate::space::Hsl;
#[cfg(feature = "space-hsluv")]
use crate::space::Hsluv;
#[cfg(feature = "space-hwb")]
use crate::space::Hwb;
#[cfg(feature = "space-lab")]
use crate::space::Lab;
#[cfg(feature = "space-lch")]
use crate::space::Lch;
#[cfg(feature = "space-lchuv")]
use crate::space::Lchuv;
#[cfg(feature = "space-luv")]
use crate::space::Luv;
#[cfg(feature = "space-okhsl")]
use crate::space::Okhsl;
#[cfg(feature = "space-okhsv")]
use crate::space::Okhsv;
#[cfg(feature = "space-okhwb")]
use crate::space::Okhwb;
#[cfg(feature = "space-oklab")]
use crate::space::Oklab;
#[cfg(feature = "space-oklch")]
use crate::space::Oklch;
#[cfg(feature = "space-xyy")]
use crate::space::Xyy;
#[cfg(feature = "space-hsv")]
use crate::space::{Hsb, Hsv};
use crate::{
  ColorimetricContext, Error,
  component::Component,
  space::{ColorSpace, Lms, Xyz},
};

/// An encoded RGB color in a specific color space.
///
/// The type parameter `S` determines which RGB space (sRGB, Display P3, etc.)
/// this color belongs to. Defaults to [`Srgb`] when not specified.
/// Components are stored as normalized values in the 0.0-1.0 range.
#[derive(Clone, Copy, Debug)]
pub struct Rgb<S = Srgb>
where
  S: RgbSpec,
{
  alpha: Component,
  b: Component,
  context: ColorimetricContext,
  g: Component,
  r: Component,
  _spec: PhantomData<S>,
}

impl<S> Rgb<S>
where
  S: RgbSpec,
{
  /// Black (0, 0, 0).
  pub const BLACK: Self = Self::new_const(0, 0, 0);
  /// White (255, 255, 255).
  pub const WHITE: Self = Self::new_const(255, 255, 255);

  /// Parses a hex color code (e.g., "#FF5733" or "F00") into an RGB color.
  pub fn from_hexcode(hexcode: impl Into<String>) -> Result<Self, Error> {
    let hexcode = hexcode.into();
    let hex = hexcode.strip_prefix('#').unwrap_or(&hexcode);

    let (r, g, b) = match hex.len() {
      3 => {
        let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        (r, g, b)
      }
      6 => {
        let r = u8::from_str_radix(&hex[0..2], 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        let g = u8::from_str_radix(&hex[2..4], 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        let b = u8::from_str_radix(&hex[4..6], 16).map_err(|_| Error::InvalidHexCharacter {
          input: hexcode.clone(),
        })?;
        (r, g, b)
      }
      len => {
        return Err(Error::InvalidHexLength {
          input: hexcode,
          length: len,
        });
      }
    };

    Ok(Self::new(r, g, b))
  }

  /// Creates an RGB color from normalized component values.
  ///
  /// Values outside 0.0-1.0 are preserved to retain out-of-gamut information.
  pub fn from_normalized(r: impl Into<Component>, g: impl Into<Component>, b: impl Into<Component>) -> Self {
    Self {
      alpha: Component::new(1.0),
      b: b.into(),
      context: S::CONTEXT,
      g: g.into(),
      r: r.into(),
      _spec: PhantomData,
    }
  }

  /// Creates an RGB color from 8-bit (0-255) component values.
  pub fn new(r: u8, g: u8, b: u8) -> Self {
    Self {
      alpha: Component::new(1.0),
      b: Component::from(b) / 255.0,
      context: S::CONTEXT,
      g: Component::from(g) / 255.0,
      r: Component::from(r) / 255.0,
      _spec: PhantomData,
    }
  }

  /// Creates an RGB color from 8-bit values in a const context.
  pub const fn new_const(r: u8, g: u8, b: u8) -> Self {
    let r = Component::new_const(r as f64 / 255.0);
    let g = Component::new_const(g as f64 / 255.0);
    let b = Component::new_const(b as f64 / 255.0);

    Self {
      alpha: Component::new_const(1.0),
      b,
      context: S::CONTEXT,
      g,
      r,
      _spec: PhantomData,
    }
  }

  /// Returns the normalized blue component (0.0-1.0).
  pub fn b(&self) -> f64 {
    self.b.0
  }

  /// Returns the blue component as a u8 (0-255).
  pub fn blue(&self) -> u8 {
    (self.b.0 * 255.0).round() as u8
  }

  /// Clamps all components to the 0.0-1.0 range.
  pub fn clip_to_gamut(&mut self) {
    if self.is_in_gamut() {
      return;
    }

    self.r = self.r.clamp(0.0, 1.0);
    self.g = self.g.clamp(0.0, 1.0);
    self.b = self.b.clamp(0.0, 1.0);
  }

  /// Returns the [R, G, B] components as normalized values.
  pub fn components(&self) -> [f64; 3] {
    [self.r.0, self.g.0, self.b.0]
  }

  /// Reduces chroma in CIELAB space until the color fits the gamut.
  #[cfg(feature = "space-lab")]
  pub fn compress_to_gamut(&mut self) {
    if self.is_in_gamut() {
      return;
    }

    let lab = self.to_xyz().to_lab();
    let [l, a, b] = lab.components();
    let chroma = (a * a + b * b).sqrt();

    if chroma < 1e-10 {
      return self.clip_to_gamut();
    }

    let mut min_scale = 0.0_f64;
    let mut max_scale = 1.0_f64;

    for _ in 0..10 {
      let mid = (min_scale + max_scale) / 2.0;
      let test = Lab::new(l, a * mid, b * mid).to_xyz().to_rgb::<S>();
      if test.is_in_gamut() {
        min_scale = mid;
      } else {
        max_scale = mid;
      }
    }

    let mut result = Lab::new(l, a * min_scale, b * min_scale).to_xyz().to_rgb::<S>();
    result.clip_to_gamut();
    self.set_components(result.components())
  }

  /// Returns the viewing context for this color space.
  pub fn context(&self) -> &ColorimetricContext {
    &self.context
  }

  /// Decreases the blue channel by the given normalized amount (0.0-1.0).
  pub fn decrement_b(&mut self, amount: impl Into<Component>) {
    self.b = (self.b - amount.into()).clamp(0.0, 1.0);
  }

  /// Decreases the blue channel by the given amount (0-255 scale).
  pub fn decrement_blue(&mut self, amount: impl Into<Component>) {
    self.b = (self.b - amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Decreases the green channel by the given normalized amount (0.0-1.0).
  pub fn decrement_g(&mut self, amount: impl Into<Component>) {
    self.g = (self.g - amount.into()).clamp(0.0, 1.0);
  }

  /// Decreases the green channel by the given amount (0-255 scale).
  pub fn decrement_green(&mut self, amount: impl Into<Component>) {
    self.g = (self.g - amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Decreases the red channel by the given normalized amount (0.0-1.0).
  pub fn decrement_r(&mut self, amount: impl Into<Component>) {
    self.r = (self.r - amount.into()).clamp(0.0, 1.0);
  }

  /// Decreases the red channel by the given amount (0-255 scale).
  pub fn decrement_red(&mut self, amount: impl Into<Component>) {
    self.r = (self.r - amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Flattens the alpha channel against black, compositing the color.
  pub fn flatten_alpha(&mut self) {
    self.flatten_alpha_against(Self::BLACK)
  }

  /// Flattens the alpha channel against the given background color.
  pub fn flatten_alpha_against(&mut self, background: impl Into<Self>) {
    let alpha = self.alpha.0;
    let [r, g, b] = self.components();
    let [br, bg, bb] = background.into().components();

    self.r = Component::new((r * alpha) + (br * (1.0 - alpha)));
    self.g = Component::new((g * alpha) + (bg * (1.0 - alpha)));
    self.b = Component::new((b * alpha) + (bb * (1.0 - alpha)));
    self.alpha = Component::new(1.0);
  }

  /// Alias for [`Self::flatten_alpha`].
  pub fn flatten_opacity(&mut self) {
    self.flatten_alpha()
  }

  /// Alias for [`Self::flatten_alpha_against`].
  pub fn flatten_opacity_against(&mut self, background: impl Into<Self>) {
    self.flatten_alpha_against(background)
  }

  /// Returns the normalized green component (0.0-1.0).
  pub fn g(&self) -> f64 {
    self.g.0
  }

  /// Generates a sequence of evenly-spaced colors between `self` and `other` in linear-light RGB.
  ///
  /// Returns `steps` colors including both endpoints, interpolated in linearized RGB
  /// for physically correct additive light mixing. When `steps` is 0 the result is empty.
  /// When `steps` is 1 the result contains only `self`.
  ///
  /// Accepts any color type that can be converted to [`Xyz`].
  pub fn gradient_linear(&self, other: impl Into<Xyz>, steps: usize) -> Vec<Self> {
    if steps == 0 {
      return Vec::new();
    }
    let other = other.into();
    if steps == 1 {
      return vec![self.mix_linear(other, 0.0)];
    }
    let divisor = (steps - 1) as f64;
    (0..steps).map(|i| self.mix_linear(other, i as f64 / divisor)).collect()
  }

  /// Returns the green component as a u8 (0-255).
  pub fn green(&self) -> u8 {
    (self.g.0 * 255.0).round() as u8
  }

  /// Increases the blue channel by the given normalized amount (0.0-1.0).
  pub fn increment_b(&mut self, amount: impl Into<Component>) {
    self.b = (self.b + amount.into()).clamp(0.0, 1.0);
  }

  /// Increases the blue channel by the given amount (0-255 scale).
  pub fn increment_blue(&mut self, amount: impl Into<Component>) {
    self.b = (self.b + amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Increases the green channel by the given normalized amount (0.0-1.0).
  pub fn increment_g(&mut self, amount: impl Into<Component>) {
    self.g = (self.g + amount.into()).clamp(0.0, 1.0);
  }

  /// Increases the green channel by the given amount (0-255 scale).
  pub fn increment_green(&mut self, amount: impl Into<Component>) {
    self.g = (self.g + amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Increases the red channel by the given normalized amount (0.0-1.0).
  pub fn increment_r(&mut self, amount: impl Into<Component>) {
    self.r = (self.r + amount.into()).clamp(0.0, 1.0);
  }

  /// Increases the red channel by the given amount (0-255 scale).
  pub fn increment_red(&mut self, amount: impl Into<Component>) {
    self.r = (self.r + amount.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Returns `true` if all components are within the 0.0-1.0 range.
  pub fn is_in_gamut(&self) -> bool {
    (0.0..=1.0).contains(&self.r.0) && (0.0..=1.0).contains(&self.g.0) && (0.0..=1.0).contains(&self.b.0)
  }

  /// Interpolates between `self` and `other` at parameter `t` in linear-light RGB.
  ///
  /// When `t` is 0.0 the result matches `self`, when 1.0 it matches `other`.
  /// Values outside 0.0–1.0 extrapolate beyond the endpoints. Both colors are
  /// linearized before interpolation and re-encoded afterward, producing physically
  /// correct additive light mixing.
  ///
  /// Accepts any color type that can be converted to [`Xyz`].
  pub fn mix_linear(&self, other: impl Into<Xyz>, t: f64) -> Self {
    let other_rgb = Rgb::<S>::from(other.into());
    let a = self.to_linear();
    let b = other_rgb.to_linear();

    let r = Component::new(a.r()).lerp(b.r(), t);
    let g = Component::new(a.g()).lerp(b.g(), t);
    let bl = Component::new(a.b()).lerp(b.b(), t);
    let alpha = Component::new(self.alpha()).lerp(other_rgb.alpha(), t);

    LinearRgb::<S>::from_normalized(r, g, bl).with_alpha(alpha).to_encoded()
  }

  /// Interpolates `self` toward `other` at parameter `t` in linear-light RGB, mutating in place.
  ///
  /// See [`mix_linear`](Self::mix_linear) for details on the interpolation behavior.
  pub fn mixed_with_linear(&mut self, other: impl Into<Xyz>, t: f64) {
    let result = self.mix_linear(other, t);
    self.r = result.r;
    self.g = result.g;
    self.b = result.b;
    self.alpha = result.alpha;
  }

  /// Maps to gamut by scaling LMS components relative to the reference white.
  pub fn perceptually_map_to_gamut(&mut self) {
    let lms = self.to_xyz().to_lms();
    let [l, m, s] = lms.components();

    let white_lms = self.context.reference_white().to_lms();
    let [wl, wm, ws] = white_lms.components();

    let nl = if wl.abs() > 1e-10 { l / wl } else { 0.0 };
    let nm = if wm.abs() > 1e-10 { m / wm } else { 0.0 };
    let ns = if ws.abs() > 1e-10 { s / ws } else { 0.0 };

    let max_normalized = nl.max(nm).max(ns);

    if max_normalized <= 1.0 {
      return;
    }

    let scale = 1.0 / max_normalized;
    let mut scaled = Lms::new(l * scale, m * scale, s * scale)
      .with_context(*lms.context())
      .to_xyz()
      .to_rgb::<S>();
    scaled.clip_to_gamut();

    self.set_components(scaled.components())
  }

  /// Returns the normalized red component (0.0-1.0).
  pub fn r(&self) -> f64 {
    self.r.0
  }

  /// Returns the red component as a u8 (0-255).
  pub fn red(&self) -> u8 {
    (self.r.0 * 255.0).round() as u8
  }

  /// Scales the blue channel by the given factor, clamping to 0.0-1.0.
  pub fn scale_b(&mut self, factor: impl Into<Component>) {
    self.b = (self.b * factor.into()).clamp(0.0, 1.0);
  }

  /// Alias for [`Self::scale_b`].
  pub fn scale_blue(&mut self, factor: impl Into<Component>) {
    self.scale_b(factor);
  }

  /// Scales the green channel by the given factor, clamping to 0.0-1.0.
  pub fn scale_g(&mut self, factor: impl Into<Component>) {
    self.g = (self.g * factor.into()).clamp(0.0, 1.0);
  }

  /// Alias for [`Self::scale_g`].
  pub fn scale_green(&mut self, factor: impl Into<Component>) {
    self.scale_g(factor);
  }

  /// Scales the red channel by the given factor, clamping to 0.0-1.0.
  pub fn scale_r(&mut self, factor: impl Into<Component>) {
    self.r = (self.r * factor.into()).clamp(0.0, 1.0);
  }

  /// Alias for [`Self::scale_r`].
  pub fn scale_red(&mut self, factor: impl Into<Component>) {
    self.scale_r(factor);
  }

  /// Scales linear RGB components to fit within the gamut.
  pub fn scale_to_gamut(&mut self) {
    if self.is_in_gamut() {
      return;
    }

    let linear = self.to_linear();
    let [r, g, b] = linear.components();

    let max_value = r.abs().max(g.abs()).max(b.abs());
    let scale = if max_value > 1.0 { 1.0 / max_value } else { 1.0 };

    let mut result = LinearRgb::<S>::from_normalized(r * scale, g * scale, b * scale).to_encoded();
    result.clip_to_gamut();
    self.set_components(result.components())
  }

  /// Sets the blue channel to the given normalized value (0.0-1.0).
  pub fn set_b(&mut self, b: impl Into<Component>) {
    self.b = b.into().clamp(0.0, 1.0);
  }

  /// Sets the blue channel to the given value (0-255 scale).
  pub fn set_blue(&mut self, blue: impl Into<Component>) {
    self.b = (blue.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Sets the [R, G, B] components from normalized values (0.0-1.0).
  pub fn set_components(&mut self, components: [impl Into<Component> + Clone; 3]) {
    self.set_r(components[0].clone());
    self.set_g(components[1].clone());
    self.set_b(components[2].clone());
  }

  /// Sets the green channel to the given normalized value (0.0-1.0).
  pub fn set_g(&mut self, g: impl Into<Component>) {
    self.g = g.into().clamp(0.0, 1.0);
  }

  /// Sets the green channel to the given value (0-255 scale).
  pub fn set_green(&mut self, green: impl Into<Component>) {
    self.g = (green.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Sets the red channel to the given normalized value (0.0-1.0).
  pub fn set_r(&mut self, r: impl Into<Component>) {
    self.r = r.into().clamp(0.0, 1.0);
  }

  /// Sets the red channel to the given value (0-255 scale).
  pub fn set_red(&mut self, red: impl Into<Component>) {
    self.r = (red.into() / 255.0).clamp(0.0, 1.0);
  }

  /// Converts to CMY in this color space.
  #[cfg(feature = "space-cmy")]
  pub fn to_cmy(&self) -> Cmy<S> {
    Cmy::new(
      (1.0 - self.r.0) * 100.0,
      (1.0 - self.g.0) * 100.0,
      (1.0 - self.b.0) * 100.0,
    )
    .with_alpha(self.alpha)
  }

  /// Converts to CMYK in this color space.
  #[cfg(feature = "space-cmyk")]
  pub fn to_cmyk(&self) -> Cmyk<S> {
    let r = self.r.0;
    let g = self.g.0;
    let b = self.b.0;
    let k = 1.0 - r.max(g).max(b);

    if (k - 1.0).abs() < f64::EPSILON {
      return Cmyk::new(0.0, 0.0, 0.0, 100.0).with_alpha(self.alpha);
    }

    Cmyk::new(
      ((1.0 - r - k) / (1.0 - k)) * 100.0,
      ((1.0 - g - k) / (1.0 - k)) * 100.0,
      ((1.0 - b - k) / (1.0 - k)) * 100.0,
      k * 100.0,
    )
    .with_alpha(self.alpha)
  }

  /// Returns this color as a hex string (e.g., `#ff5733`).
  ///
  /// Always lowercase, 6-digit format. Alpha is not included.
  ///
  /// ```
  /// use farg::space::{Rgb, Srgb};
  ///
  /// let color = Rgb::<Srgb>::new(255, 87, 51);
  /// assert_eq!(color.to_hex(), "#ff5733");
  /// ```
  pub fn to_hex(&self) -> String {
    format!("#{:02x}{:02x}{:02x}", self.red(), self.green(), self.blue())
  }

  /// Converts to HSB in this color space. Alias for [`Self::to_hsv`].
  #[cfg(feature = "space-hsv")]
  pub fn to_hsb(&self) -> Hsb<S> {
    self.to_hsv()
  }

  /// Converts to HSI in this color space.
  #[cfg(feature = "space-hsi")]
  pub fn to_hsi(&self) -> Hsi<S> {
    let r = self.r.0;
    let g = self.g.0;
    let b = self.b.0;

    let i = (r + g + b) / 3.0;

    if i <= 0.0 {
      return Hsi::new(0.0, 0.0, 0.0).with_alpha(self.alpha);
    }

    let min = r.min(g).min(b);
    let s = 1.0 - min / i;

    let max = r.max(g).max(b);
    let delta = max - min;

    if delta <= 0.0 {
      return Hsi::new(0.0, 0.0, i * 100.0).with_alpha(self.alpha);
    }

    let h = if (max - r).abs() < f64::EPSILON {
      ((g - b) / delta).rem_euclid(6.0) / 6.0
    } else if (max - g).abs() < f64::EPSILON {
      (2.0 + (b - r) / delta) / 6.0
    } else {
      (4.0 + (r - g) / delta) / 6.0
    };

    Hsi::new(h * 360.0, s * 100.0, i * 100.0).with_alpha(self.alpha)
  }

  /// Converts to HSL in this color space.
  #[cfg(feature = "space-hsl")]
  pub fn to_hsl(&self) -> Hsl<S> {
    let r = self.r.0;
    let g = self.g.0;
    let b = self.b.0;

    let max = r.max(g).max(b);
    let min = r.min(g).min(b);
    let delta = max - min;
    let l = (max + min) / 2.0;

    if delta <= 0.0 {
      return Hsl::new(0.0, 0.0, l * 100.0).with_alpha(self.alpha);
    }

    let s = if l <= 0.5 {
      delta / (max + min)
    } else {
      delta / (2.0 - max - min)
    };

    let h = if (max - r).abs() < f64::EPSILON {
      ((g - b) / delta).rem_euclid(6.0) / 6.0
    } else if (max - g).abs() < f64::EPSILON {
      (2.0 + (b - r) / delta) / 6.0
    } else {
      (4.0 + (r - g) / delta) / 6.0
    };

    Hsl::new(h * 360.0, s * 100.0, l * 100.0).with_alpha(self.alpha)
  }

  /// Converts to HSLuv.
  #[cfg(feature = "space-hsluv")]
  pub fn to_hsluv(&self) -> crate::space::Hsluv {
    crate::space::Hsluv::from(self.to_xyz()).with_alpha(self.alpha)
  }

  /// Converts to HSV in this color space.
  #[cfg(feature = "space-hsv")]
  pub fn to_hsv(&self) -> Hsv<S> {
    let r = self.r.0;
    let g = self.g.0;
    let b = self.b.0;

    let max = r.max(g).max(b);
    let min = r.min(g).min(b);
    let delta = max - min;

    if delta <= 0.0 {
      return Hsv::new(0.0, 0.0, max * 100.0).with_alpha(self.alpha);
    }

    let s = delta / max;

    let h = if (max - r).abs() < f64::EPSILON {
      ((g - b) / delta).rem_euclid(6.0) / 6.0
    } else if (max - g).abs() < f64::EPSILON {
      (2.0 + (b - r) / delta) / 6.0
    } else {
      (4.0 + (r - g) / delta) / 6.0
    };

    Hsv::new(h * 360.0, s * 100.0, max * 100.0).with_alpha(self.alpha)
  }

  /// Converts to HWB in this color space.
  #[cfg(feature = "space-hwb")]
  pub fn to_hwb(&self) -> Hwb<S> {
    let r = self.r.0;
    let g = self.g.0;
    let b = self.b.0;

    let max = r.max(g).max(b);
    let min = r.min(g).min(b);
    let delta = max - min;

    if delta <= 0.0 {
      return Hwb::new(0.0, min * 100.0, (1.0 - max) * 100.0).with_alpha(self.alpha);
    }

    let h = if (max - r).abs() < f64::EPSILON {
      ((g - b) / delta).rem_euclid(6.0) / 6.0
    } else if (max - g).abs() < f64::EPSILON {
      (2.0 + (b - r) / delta) / 6.0
    } else {
      (4.0 + (r - g) / delta) / 6.0
    };

    Hwb::new(h * 360.0, min * 100.0, (1.0 - max) * 100.0).with_alpha(self.alpha)
  }

  /// Decodes to linear RGB by applying the inverse transfer function.
  pub fn to_linear(&self) -> LinearRgb<S> {
    let r = S::TRANSFER_FUNCTION.decode(self.r);
    let g = S::TRANSFER_FUNCTION.decode(self.g);
    let b = S::TRANSFER_FUNCTION.decode(self.b);
    LinearRgb::from_normalized(r, g, b).with_alpha(self.alpha)
  }

  /// Converts to the Oklab perceptual color space via linear sRGB.
  #[cfg(feature = "space-oklab")]
  pub fn to_oklab(&self) -> Oklab {
    let linear_srgb = self.to_rgb::<Srgb>().to_linear().components();

    let lms_linear = Oklab::LINEAR_RGB_MATRIX * linear_srgb;
    let lms_nonlinear = [lms_linear[0].cbrt(), lms_linear[1].cbrt(), lms_linear[2].cbrt()];
    let [l, a, b] = Oklab::LINEAR_LMS_MATRIX * lms_nonlinear;

    Oklab::new(l, a, b).with_alpha(self.alpha)
  }

  /// Converts to a different RGB color space via XYZ.
  pub fn to_rgb<OS>(&self) -> Rgb<OS>
  where
    OS: RgbSpec,
  {
    if S::NAME == OS::NAME {
      Rgb::<OS>::from_normalized(self.r(), self.g(), self.b()).with_alpha(self.alpha())
    } else {
      self.to_xyz().to_rgb::<OS>().with_alpha(self.alpha())
    }
  }

  /// Converts to CIE XYZ via linear RGB and the space's RGB-to-XYZ matrix.
  pub fn to_xyz(&self) -> Xyz {
    let linear = self.to_linear();
    let [x, y, z] = *S::xyz_matrix() * linear.components();
    Xyz::new(x, y, z).with_context(self.context).with_alpha(self.alpha)
  }

  /// Returns a new color with the given alpha value on a 0.0 to 1.0 scale.
  pub fn with_alpha(&self, alpha: impl Into<Component>) -> Self {
    Self {
      alpha: alpha.into().clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the alpha channel flattened against black.
  pub fn with_alpha_flattened(&self) -> Self {
    let mut rgb = *self;
    rgb.flatten_alpha();
    rgb
  }

  /// Returns a new color with the alpha channel flattened against the given background.
  pub fn with_alpha_flattened_against(&self, background: impl Into<Self>) -> Self {
    let mut rgb = *self;
    rgb.flatten_alpha_against(background);
    rgb
  }

  /// Returns a new color with the given normalized blue channel value (0.0-1.0).
  pub fn with_b(&self, b: impl Into<Component>) -> Self {
    Self {
      b: b.into().clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the blue channel decreased by the given normalized amount (0.0-1.0).
  pub fn with_b_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_b(amount);
    rgb
  }

  /// Returns a new color with the blue channel increased by the given normalized amount (0.0-1.0).
  pub fn with_b_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_b(amount);
    rgb
  }

  /// Returns a new color with the blue channel scaled by the given factor.
  pub fn with_b_scaled_by(&self, factor: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.scale_b(factor);
    rgb
  }

  /// Returns a new color with the given blue channel value (0-255 scale).
  pub fn with_blue(&self, blue: impl Into<Component>) -> Self {
    Self {
      b: (blue.into() / 255.0).clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the blue channel decreased by the given amount (0-255 scale).
  pub fn with_blue_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_blue(amount);
    rgb
  }

  /// Returns a new color with the blue channel increased by the given amount (0-255 scale).
  pub fn with_blue_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_blue(amount);
    rgb
  }

  /// Alias for [`Self::with_b_scaled_by`].
  pub fn with_blue_scaled_by(&self, factor: impl Into<Component>) -> Self {
    self.with_b_scaled_by(factor)
  }

  /// Returns a new color with the given normalized green channel value (0.0-1.0).
  pub fn with_g(&self, g: impl Into<Component>) -> Self {
    Self {
      g: g.into().clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the green channel decreased by the given normalized amount (0.0-1.0).
  pub fn with_g_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_g(amount);
    rgb
  }

  /// Returns a new color with the green channel increased by the given normalized amount (0.0-1.0).
  pub fn with_g_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_g(amount);
    rgb
  }

  /// Returns a new color with the green channel scaled by the given factor.
  pub fn with_g_scaled_by(&self, factor: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.scale_g(factor);
    rgb
  }

  /// Returns a new color with all components clamped to the 0.0-1.0 range.
  pub fn with_gamut_clipped(&self) -> Self {
    let mut rgb = *self;
    rgb.clip_to_gamut();
    rgb
  }

  /// Returns a new color with chroma reduced in CIELAB space until the color fits the gamut.
  #[cfg(feature = "space-lab")]
  pub fn with_gamut_compressed(&self) -> Self {
    let mut rgb = *self;
    rgb.compress_to_gamut();
    rgb
  }

  /// Returns a new color mapped to gamut by scaling LMS components relative to the reference white.
  pub fn with_gamut_perceptually_mapped(&self) -> Self {
    let mut rgb = *self;
    rgb.perceptually_map_to_gamut();
    rgb
  }

  /// Returns a new color with linear RGB components scaled to fit within the gamut.
  pub fn with_gamut_scaled(&self) -> Self {
    let mut rgb = *self;
    rgb.scale_to_gamut();
    rgb
  }

  /// Returns a new color with the given green channel value (0-255 scale).
  pub fn with_green(&self, green: impl Into<Component>) -> Self {
    Self {
      g: (green.into() / 255.0).clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the green channel decreased by the given amount (0-255 scale).
  pub fn with_green_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_green(amount);
    rgb
  }

  /// Returns a new color with the green channel increased by the given amount (0-255 scale).
  pub fn with_green_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_green(amount);
    rgb
  }

  /// Alias for [`Self::with_g_scaled_by`].
  pub fn with_green_scaled_by(&self, factor: impl Into<Component>) -> Self {
    self.with_g_scaled_by(factor)
  }

  /// Alias for [`Self::with_alpha_flattened`].
  pub fn with_opacity_flattened(&self) -> Self {
    self.with_alpha_flattened()
  }

  /// Alias for [`Self::with_alpha_flattened_against`].
  pub fn with_opacity_flattened_against(&self, background: impl Into<Self>) -> Self {
    self.with_alpha_flattened_against(background)
  }

  /// Returns a new color with the given normalized red channel value (0.0-1.0).
  pub fn with_r(&self, r: impl Into<Component>) -> Self {
    Self {
      r: r.into().clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the red channel decreased by the given normalized amount (0.0-1.0).
  pub fn with_r_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_r(amount);
    rgb
  }

  /// Returns a new color with the red channel increased by the given normalized amount (0.0-1.0).
  pub fn with_r_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_r(amount);
    rgb
  }

  /// Returns a new color with the red channel scaled by the given factor.
  pub fn with_r_scaled_by(&self, factor: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.scale_r(factor);
    rgb
  }

  /// Returns a new color with the given red channel value (0-255 scale).
  pub fn with_red(&self, red: impl Into<Component>) -> Self {
    Self {
      r: (red.into() / 255.0).clamp(0.0, 1.0),
      ..*self
    }
  }

  /// Returns a new color with the red channel decreased by the given amount (0-255 scale).
  pub fn with_red_decremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.decrement_red(amount);
    rgb
  }

  /// Returns a new color with the red channel increased by the given amount (0-255 scale).
  pub fn with_red_incremented_by(&self, amount: impl Into<Component>) -> Self {
    let mut rgb = *self;
    rgb.increment_red(amount);
    rgb
  }

  /// Alias for [`Self::with_r_scaled_by`].
  pub fn with_red_scaled_by(&self, factor: impl Into<Component>) -> Self {
    self.with_r_scaled_by(factor)
  }
}

impl<S, T> Add<T> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Self>,
{
  type Output = Self;

  fn add(self, rhs: T) -> Self {
    let rhs = rhs.into();
    let r = (self.r + rhs.r).clamp(0.0, 1.0);
    let g = (self.g + rhs.g).clamp(0.0, 1.0);
    let b = (self.b + rhs.b).clamp(0.0, 1.0);
    Self::from_normalized(r, g, b).with_alpha(self.alpha)
  }
}

impl<S> ColorSpace<3> for Rgb<S>
where
  S: RgbSpec,
{
  fn alpha(&self) -> f64 {
    self.alpha.0
  }

  fn components(&self) -> [f64; 3] {
    self.components()
  }

  fn set_alpha(&mut self, alpha: impl Into<Component>) {
    self.alpha = alpha.into().clamp(0.0, 1.0);
  }

  fn set_components(&mut self, components: [impl Into<Component> + Clone; 3]) {
    self.set_components(components)
  }

  fn to_rgb<OS>(&self) -> Rgb<OS>
  where
    OS: RgbSpec,
  {
    self.to_rgb::<OS>()
  }

  fn to_xyz(&self) -> Xyz {
    self.to_xyz()
  }
}

#[cfg(feature = "serde")]
impl<'de, S> serde::Deserialize<'de> for Rgb<S>
where
  S: RgbSpec,
{
  fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
    #[derive(serde::Deserialize)]
    struct RgbData {
      r: Component,
      g: Component,
      b: Component,
      #[serde(default = "crate::component::default_alpha")]
      alpha: Component,
    }

    let data = RgbData::deserialize(deserializer)?;
    Ok(Self {
      r: data.r,
      g: data.g,
      b: data.b,
      alpha: data.alpha,
      context: S::CONTEXT,
      _spec: PhantomData,
    })
  }
}

impl<S> Display for Rgb<S>
where
  S: RgbSpec,
{
  fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
    if self.alpha.0 < 1.0 {
      write!(
        f,
        "{}({}, {}, {}, {:.0}%)",
        S::NAME,
        self.red(),
        self.green(),
        self.blue(),
        self.opacity()
      )
    } else {
      write!(f, "{}({}, {}, {})", S::NAME, self.red(), self.green(), self.blue())
    }
  }
}

impl<S, T> Div<T> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Self>,
{
  type Output = Self;

  fn div(self, rhs: T) -> Self {
    let rhs = rhs.into();
    let r = (self.r / rhs.r).clamp(0.0, 1.0);
    let g = (self.g / rhs.g).clamp(0.0, 1.0);
    let b = (self.b / rhs.b).clamp(0.0, 1.0);
    Self::from_normalized(r, g, b).with_alpha(self.alpha)
  }
}

impl<S, T> From<[T; 3]> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Component>,
{
  fn from([r, g, b]: [T; 3]) -> Self {
    Self::from_normalized(r, g, b)
  }
}

#[cfg(feature = "space-cmy")]
impl<OS, S> From<Cmy<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(cmy: Cmy<OS>) -> Self {
    cmy.to_rgb::<S>()
  }
}

#[cfg(feature = "space-cmyk")]
impl<OS, S> From<Cmyk<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(cmyk: Cmyk<OS>) -> Self {
    cmyk.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hpluv")]
impl<S> From<Hpluv> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(hpluv: Hpluv) -> Self {
    hpluv.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hsi")]
impl<OS, S> From<Hsi<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(hsi: Hsi<OS>) -> Self {
    hsi.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hsl")]
impl<OS, S> From<Hsl<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(hsl: Hsl<OS>) -> Self {
    hsl.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hsluv")]
impl<S> From<Hsluv> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(hsluv: Hsluv) -> Self {
    hsluv.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hsv")]
impl<OS, S> From<Hsv<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(hsv: Hsv<OS>) -> Self {
    hsv.to_rgb::<S>()
  }
}

#[cfg(feature = "space-hwb")]
impl<OS, S> From<Hwb<OS>> for Rgb<S>
where
  OS: RgbSpec,
  S: RgbSpec,
{
  fn from(hwb: Hwb<OS>) -> Self {
    hwb.to_rgb::<S>()
  }
}

#[cfg(feature = "space-lab")]
impl<S> From<Lab> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(lab: Lab) -> Self {
    lab.to_rgb::<S>()
  }
}

#[cfg(feature = "space-lch")]
impl<S> From<Lch> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(lch: Lch) -> Self {
    lch.to_rgb::<S>()
  }
}

#[cfg(feature = "space-lchuv")]
impl<S> From<Lchuv> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(lchuv: Lchuv) -> Self {
    lchuv.to_rgb::<S>()
  }
}

impl<S> From<Lms> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(lms: Lms) -> Self {
    lms.to_rgb::<S>()
  }
}

#[cfg(feature = "space-luv")]
impl<S> From<Luv> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(luv: Luv) -> Self {
    luv.to_rgb::<S>()
  }
}

#[cfg(feature = "space-okhsl")]
impl<S> From<Okhsl> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(okhsl: Okhsl) -> Self {
    okhsl.to_rgb::<S>()
  }
}

#[cfg(feature = "space-okhsv")]
impl<S> From<Okhsv> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(okhsv: Okhsv) -> Self {
    okhsv.to_rgb::<S>()
  }
}

#[cfg(feature = "space-okhwb")]
impl<S> From<Okhwb> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(okhwb: Okhwb) -> Self {
    okhwb.to_rgb::<S>()
  }
}

#[cfg(feature = "space-oklab")]
impl<S> From<Oklab> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(oklab: Oklab) -> Self {
    oklab.to_rgb::<S>()
  }
}

#[cfg(feature = "space-oklch")]
impl<S> From<Oklch> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(oklch: Oklch) -> Self {
    oklch.to_rgb::<S>()
  }
}

#[cfg(feature = "space-xyy")]
impl<S> From<Xyy> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(xyy: Xyy) -> Self {
    xyy.to_rgb::<S>()
  }
}

impl<S> From<Xyz> for Rgb<S>
where
  S: RgbSpec,
{
  fn from(xyz: Xyz) -> Self {
    xyz.to_rgb::<S>()
  }
}

impl<S, T> Mul<T> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Self>,
{
  type Output = Self;

  fn mul(self, rhs: T) -> Self {
    let rhs = rhs.into();
    let r = (self.r * rhs.r).clamp(0.0, 1.0);
    let g = (self.g * rhs.g).clamp(0.0, 1.0);
    let b = (self.b * rhs.b).clamp(0.0, 1.0);
    Self::from_normalized(r, g, b).with_alpha(self.alpha)
  }
}

impl<S, T> PartialEq<T> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Rgb<S>> + Copy,
{
  fn eq(&self, other: &T) -> bool {
    let other = (*other).into();
    self.alpha == other.alpha
      && self.red() == other.red()
      && self.green() == other.green()
      && self.blue() == other.blue()
  }
}

#[cfg(feature = "serde")]
impl<S> serde::Serialize for Rgb<S>
where
  S: RgbSpec,
{
  fn serialize<Ser: serde::Serializer>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error> {
    use serde::ser::SerializeStruct;

    let field_count = if self.alpha.0 < 1.0 { 4 } else { 3 };
    let mut state = serializer.serialize_struct("Rgb", field_count)?;
    state.serialize_field("r", &self.r)?;
    state.serialize_field("g", &self.g)?;
    state.serialize_field("b", &self.b)?;
    if self.alpha.0 < 1.0 {
      state.serialize_field("alpha", &self.alpha)?;
    }
    state.end()
  }
}

impl<S, T> Sub<T> for Rgb<S>
where
  S: RgbSpec,
  T: Into<Self>,
{
  type Output = Self;

  fn sub(self, rhs: T) -> Self {
    let rhs = rhs.into();
    let r = (self.r - rhs.r).clamp(0.0, 1.0);
    let g = (self.g - rhs.g).clamp(0.0, 1.0);
    let b = (self.b - rhs.b).clamp(0.0, 1.0);
    Self::from_normalized(r, g, b).with_alpha(self.alpha)
  }
}

impl<S> TryFrom<&str> for Rgb<S>
where
  S: RgbSpec,
{
  type Error = crate::Error;

  fn try_from(value: &str) -> Result<Self, Self::Error> {
    Self::from_hexcode(value)
  }
}

impl<S> TryFrom<String> for Rgb<S>
where
  S: RgbSpec,
{
  type Error = crate::Error;

  fn try_from(value: String) -> Result<Self, Self::Error> {
    Self::from_hexcode(value)
  }
}

#[cfg(test)]
mod test {
  use super::*;

  mod default_type_parameter {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_defaults_to_srgb_with_type_annotation() {
      let rgb: Rgb = Rgb::new(255, 0, 0);

      assert_eq!(format!("{}", rgb), "sRGB(255, 0, 0)");
    }

    #[test]
    fn it_defaults_to_srgb_for_from_hexcode() {
      let rgb: Rgb = Rgb::from_hexcode("#FF0000").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 0);
    }

    #[test]
    fn it_defaults_to_srgb_for_from_normalized() {
      let rgb: Rgb = Rgb::from_normalized(1.0, 0.0, 0.0);

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 0);
    }
  }

  mod add {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_adds_two_rgb_values() {
      let a = Rgb::<Srgb>::from_normalized(0.2, 0.3, 0.4);
      let b = Rgb::<Srgb>::from_normalized(0.1, 0.2, 0.3);
      let result = a + b;

      assert!((result.r() - 0.3).abs() < 1e-10);
      assert!((result.g() - 0.5).abs() < 1e-10);
      assert!((result.b() - 0.7).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_result_to_1() {
      let a = Rgb::<Srgb>::from_normalized(0.7, 0.8, 0.9);
      let b = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = a + b;

      assert_eq!(result.r(), 1.0);
      assert_eq!(result.g(), 1.0);
      assert_eq!(result.b(), 1.0);
    }

    #[test]
    fn it_preserves_alpha_from_lhs() {
      let a = Rgb::<Srgb>::from_normalized(0.2, 0.3, 0.4).with_alpha(0.5);
      let b = Rgb::<Srgb>::from_normalized(0.1, 0.2, 0.3);
      let result = a + b;

      assert!((result.alpha() - 0.5).abs() < 1e-10);
    }
  }

  mod clip_to_gamut {
    use super::*;

    #[test]
    fn it_clamps_components_to_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      rgb.clip_to_gamut();

      assert!((rgb.r() - 1.0).abs() < 1e-10);
      assert!((rgb.g() - 0.0).abs() < 1e-10);
      assert!((rgb.b() - 0.5).abs() < 1e-10);
      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_unchanged_when_in_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.3, 0.8);
      rgb.clip_to_gamut();

      assert!((rgb.r() - 0.5).abs() < 1e-10);
      assert!((rgb.g() - 0.3).abs() < 1e-10);
      assert!((rgb.b() - 0.8).abs() < 1e-10);
    }

    #[test]
    fn it_preserves_alpha() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5).with_alpha(0.7);
      rgb.clip_to_gamut();

      assert!((rgb.alpha() - 0.7).abs() < 1e-10);
    }
  }

  #[cfg(feature = "space-lab")]
  mod compress_to_gamut {
    use super::*;

    #[test]
    fn it_brings_out_of_gamut_color_into_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      rgb.compress_to_gamut();

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_unchanged_when_in_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.3, 0.8);
      let orig = rgb;
      rgb.compress_to_gamut();

      assert!((rgb.r() - orig.r()).abs() < 1e-10);
      assert!((rgb.g() - orig.g()).abs() < 1e-10);
      assert!((rgb.b() - orig.b()).abs() < 1e-10);
    }

    #[test]
    fn it_handles_achromatic_colors() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 1.5, 1.5);
      rgb.compress_to_gamut();

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_preserves_alpha() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, -0.1).with_alpha(0.4);
      rgb.compress_to_gamut();

      assert!((rgb.alpha() - 0.4).abs() < 1e-10);
    }
  }

  mod decrement_b {
    use super::*;

    #[test]
    fn it_decrements_blue_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      rgb.decrement_b(0.25);

      assert!((rgb.b() - 0.25).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.1);
      rgb.decrement_b(0.5);

      assert!((rgb.b()).abs() < 1e-10);
    }
  }

  mod decrement_blue {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_decrements_blue_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.decrement_blue(64);

      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 32);
      rgb.decrement_blue(64);

      assert_eq!(rgb.blue(), 0);
    }
  }

  mod decrement_g {
    use super::*;

    #[test]
    fn it_decrements_green_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      rgb.decrement_g(0.25);

      assert!((rgb.g() - 0.25).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.1, 0.5);
      rgb.decrement_g(0.5);

      assert!((rgb.g()).abs() < 1e-10);
    }
  }

  mod decrement_green {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_decrements_green_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.decrement_green(64);

      assert_eq!(rgb.green(), 64);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::new(128, 32, 128);
      rgb.decrement_green(64);

      assert_eq!(rgb.green(), 0);
    }
  }

  mod decrement_r {
    use super::*;

    #[test]
    fn it_decrements_red_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      rgb.decrement_r(0.25);

      assert!((rgb.r() - 0.25).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.1, 0.5, 0.5);
      rgb.decrement_r(0.5);

      assert!((rgb.r()).abs() < 1e-10);
    }
  }

  mod decrement_red {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_decrements_red_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.decrement_red(64);

      assert_eq!(rgb.red(), 64);
    }

    #[test]
    fn it_clamps_to_zero() {
      let mut rgb = Rgb::<Srgb>::new(32, 128, 128);
      rgb.decrement_red(64);

      assert_eq!(rgb.red(), 0);
    }
  }

  mod display {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_formats_with_space_name_and_8bit_values() {
      let rgb = Rgb::<Srgb>::new(255, 128, 64);

      assert_eq!(format!("{}", rgb), "sRGB(255, 128, 64)");
    }

    #[test]
    fn it_includes_opacity_when_alpha_below_one() {
      let rgb = Rgb::<Srgb>::new(255, 128, 64).with_alpha(0.5);

      assert_eq!(format!("{}", rgb), "sRGB(255, 128, 64, 50%)");
    }

    #[test]
    fn it_omits_opacity_when_fully_opaque() {
      let rgb = Rgb::<Srgb>::new(255, 128, 64);

      assert_eq!(format!("{}", rgb), "sRGB(255, 128, 64)");
    }
  }

  mod div {
    use super::*;

    #[test]
    fn it_divides_two_rgb_values() {
      let a = Rgb::<Srgb>::from_normalized(0.8, 0.6, 0.4);
      let b = Rgb::<Srgb>::from_normalized(0.4, 0.3, 0.2);
      let result = a / b;

      assert!((result.r() - 1.0).abs() < 1e-10);
      assert!((result.g() - 1.0).abs() < 1e-10);
      assert!((result.b() - 1.0).abs() < 1e-10);
    }
  }

  mod from_hexcode {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_parses_6_digit_hex_with_hash() {
      let rgb = Rgb::<Srgb>::from_hexcode("#FF8040").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 128);
      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_parses_6_digit_hex_without_hash() {
      let rgb = Rgb::<Srgb>::from_hexcode("FF8040").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 128);
      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_parses_3_digit_shorthand_with_hash() {
      let rgb = Rgb::<Srgb>::from_hexcode("#F84").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 136);
      assert_eq!(rgb.blue(), 68);
    }

    #[test]
    fn it_parses_3_digit_shorthand_without_hash() {
      let rgb = Rgb::<Srgb>::from_hexcode("F84").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 136);
      assert_eq!(rgb.blue(), 68);
    }

    #[test]
    fn it_parses_lowercase_hex() {
      let rgb = Rgb::<Srgb>::from_hexcode("#ff8040").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 128);
      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_returns_error_for_invalid_length() {
      let result = Rgb::<Srgb>::from_hexcode("#FF80");

      assert_eq!(
        result.unwrap_err(),
        crate::Error::InvalidHexLength {
          input: "#FF80".to_string(),
          length: 4
        }
      );
    }

    #[test]
    fn it_returns_error_for_invalid_characters() {
      let result = Rgb::<Srgb>::from_hexcode("#GGHHII");

      assert_eq!(
        result.unwrap_err(),
        crate::Error::InvalidHexCharacter {
          input: "#GGHHII".to_string()
        }
      );
    }

    #[test]
    fn it_parses_black_and_white() {
      let black = Rgb::<Srgb>::from_hexcode("#000000").unwrap();
      let white = Rgb::<Srgb>::from_hexcode("#FFFFFF").unwrap();

      assert_eq!(black.red(), 0);
      assert_eq!(black.green(), 0);
      assert_eq!(black.blue(), 0);
      assert_eq!(white.red(), 255);
      assert_eq!(white.green(), 255);
      assert_eq!(white.blue(), 255);
    }
  }

  mod from_normalized {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_preserves_out_of_range_values() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, -0.5, 0.5);

      assert_eq!(rgb.r(), 1.5);
      assert_eq!(rgb.g(), -0.5);
      assert_eq!(rgb.b(), 0.5);
    }
  }

  mod from_array {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_creates_rgb_from_normalized_array() {
      let rgb: Rgb<Srgb> = [0.5, 0.25, 0.75].into();

      assert_eq!(rgb.r(), 0.5);
      assert_eq!(rgb.g(), 0.25);
      assert_eq!(rgb.b(), 0.75);
    }
  }

  #[cfg(feature = "space-cmyk")]
  mod from_cmyk {
    use super::*;

    #[test]
    fn it_converts_pure_cyan() {
      let cmyk = Cmyk::<Srgb>::new(100.0, 0.0, 0.0, 0.0);
      let rgb: Rgb<Srgb> = cmyk.into();

      assert_eq!(rgb.red(), 0);
      assert_eq!(rgb.green(), 255);
      assert_eq!(rgb.blue(), 255);
    }

    #[test]
    fn it_converts_black() {
      let cmyk = Cmyk::<Srgb>::new(0.0, 0.0, 0.0, 100.0);
      let rgb: Rgb<Srgb> = cmyk.into();

      assert_eq!(rgb.red(), 0);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 0);
    }
  }

  mod flatten_alpha {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_composites_against_black() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      rgb.flatten_alpha();

      assert_eq!(rgb.red(), 128);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 0);
      assert!((rgb.alpha() - 1.0).abs() < 1e-10);
    }

    #[test]
    fn it_is_noop_for_fully_opaque() {
      let mut rgb = Rgb::<Srgb>::new(200, 100, 50);
      rgb.flatten_alpha();

      assert_eq!(rgb.red(), 200);
      assert_eq!(rgb.green(), 100);
      assert_eq!(rgb.blue(), 50);
    }

    #[test]
    fn it_produces_black_for_fully_transparent() {
      let mut rgb = Rgb::<Srgb>::new(255, 128, 64);
      rgb.set_alpha(0.0);
      rgb.flatten_alpha();

      assert_eq!(rgb.red(), 0);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 0);
    }
  }

  mod flatten_alpha_against {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_composites_against_background() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      let bg = Rgb::<Srgb>::new(0, 0, 255);
      rgb.flatten_alpha_against(bg);

      assert_eq!(rgb.red(), 128);
      assert_eq!(rgb.green(), 0);
      assert_eq!(rgb.blue(), 128);
      assert!((rgb.alpha() - 1.0).abs() < 1e-10);
    }

    #[test]
    fn it_returns_background_for_fully_transparent() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.0);
      let bg = Rgb::<Srgb>::new(0, 255, 0);
      rgb.flatten_alpha_against(bg);

      assert_eq!(rgb.red(), 0);
      assert_eq!(rgb.green(), 255);
      assert_eq!(rgb.blue(), 0);
    }
  }

  mod gradient_linear {
    use super::*;

    #[test]
    fn zero_steps_is_empty() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      assert!(c1.gradient_linear(c2.to_xyz(), 0).is_empty());
    }

    #[test]
    fn one_step_returns_self() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      let steps = c1.gradient_linear(c2.to_xyz(), 1);
      assert_eq!(steps.len(), 1);
      assert_eq!(steps[0].red(), c1.red());
    }

    #[test]
    fn two_steps_returns_endpoints() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      let steps = c1.gradient_linear(c2.to_xyz(), 2);
      assert_eq!(steps.len(), 2);
      assert_eq!(steps[0].red(), 255);
      assert_eq!(steps[1].blue(), 255);
    }

    #[test]
    fn five_steps_correct_count() {
      let c1 = Rgb::<Srgb>::new(0, 0, 0);
      let c2 = Rgb::<Srgb>::new(255, 255, 255);
      assert_eq!(c1.gradient_linear(c2.to_xyz(), 5).len(), 5);
    }
  }

  mod increment_b {
    use super::*;

    #[test]
    fn it_increments_blue_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.25);
      rgb.increment_b(0.25);

      assert!((rgb.b() - 0.5).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_1() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.8);
      rgb.increment_b(0.5);

      assert!((rgb.b() - 1.0).abs() < 1e-10);
    }
  }

  mod increment_blue {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_increments_blue_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 64);
      rgb.increment_blue(64);

      assert_eq!(rgb.blue(), 128);
    }

    #[test]
    fn it_clamps_to_255() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 200);
      rgb.increment_blue(100);

      assert_eq!(rgb.blue(), 255);
    }
  }

  mod increment_g {
    use super::*;

    #[test]
    fn it_increments_green_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.25, 0.5);
      rgb.increment_g(0.25);

      assert!((rgb.g() - 0.5).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_1() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.8, 0.5);
      rgb.increment_g(0.5);

      assert!((rgb.g() - 1.0).abs() < 1e-10);
    }
  }

  mod increment_green {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_increments_green_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(128, 64, 128);
      rgb.increment_green(64);

      assert_eq!(rgb.green(), 128);
    }

    #[test]
    fn it_clamps_to_255() {
      let mut rgb = Rgb::<Srgb>::new(128, 200, 128);
      rgb.increment_green(100);

      assert_eq!(rgb.green(), 255);
    }
  }

  mod increment_r {
    use super::*;

    #[test]
    fn it_increments_red_by_normalized_amount() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.25, 0.5, 0.5);
      rgb.increment_r(0.25);

      assert!((rgb.r() - 0.5).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_to_1() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.8, 0.5, 0.5);
      rgb.increment_r(0.5);

      assert!((rgb.r() - 1.0).abs() < 1e-10);
    }
  }

  mod increment_red {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_increments_red_by_u8_amount() {
      let mut rgb = Rgb::<Srgb>::new(64, 128, 128);
      rgb.increment_red(64);

      assert_eq!(rgb.red(), 128);
    }

    #[test]
    fn it_clamps_to_255() {
      let mut rgb = Rgb::<Srgb>::new(200, 128, 128);
      rgb.increment_red(100);

      assert_eq!(rgb.red(), 255);
    }
  }

  mod is_in_gamut {
    use super::*;

    #[test]
    fn it_returns_true_when_all_components_in_range() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_true_at_boundary_values() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 1.0, 0.0);

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_false_when_component_exceeds_one() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);

      assert!(!rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_false_when_component_below_zero() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, -0.1, 0.5);

      assert!(!rgb.is_in_gamut());
    }
  }

  mod mix_linear {
    use super::*;

    const EPSILON: f64 = 1e-4;

    #[test]
    fn at_zero_returns_self() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      let result = c1.mix_linear(c2.to_xyz(), 0.0);
      assert_eq!(result.red(), 255);
      assert_eq!(result.green(), 0);
      assert_eq!(result.blue(), 0);
    }

    #[test]
    fn at_one_returns_other() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      let result = c1.mix_linear(c2.to_xyz(), 1.0);
      assert_eq!(result.red(), 0);
      assert_eq!(result.green(), 0);
      assert_eq!(result.blue(), 255);
    }

    #[test]
    fn midpoint_is_between() {
      let c1 = Rgb::<Srgb>::new(0, 0, 0);
      let c2 = Rgb::<Srgb>::new(255, 255, 255);
      let mid = c1.mix_linear(c2.to_xyz(), 0.5);
      assert!(mid.r() > 0.0 && mid.r() < 1.0);
    }

    #[test]
    fn alpha_interpolation() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0).with_alpha(0.0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255).with_alpha(1.0);
      let mid = c1.mix_linear(c2.to_xyz(), 0.5);
      assert!((mid.alpha() - 0.5).abs() < EPSILON);
    }

    #[test]
    fn cross_type() {
      let rgb = Rgb::<Srgb>::new(255, 0, 0);
      let xyz = Xyz::new(0.18048, 0.07219, 0.95030);
      let _result = rgb.mix_linear(xyz, 0.5);
    }
  }

  mod mixed_with_linear {
    use super::*;

    #[test]
    fn it_mutates_in_place() {
      let c1 = Rgb::<Srgb>::new(255, 0, 0);
      let c2 = Rgb::<Srgb>::new(0, 0, 255);
      let expected = c1.mix_linear(c2.to_xyz(), 0.5);
      let mut color = c1;
      color.mixed_with_linear(c2.to_xyz(), 0.5);
      assert!((color.r() - expected.r()).abs() < 1e-10);
      assert!((color.g() - expected.g()).abs() < 1e-10);
      assert!((color.b() - expected.b()).abs() < 1e-10);
      assert!((color.alpha() - expected.alpha()).abs() < 1e-10);
    }
  }

  mod mul {
    use super::*;

    #[test]
    fn it_multiplies_two_rgb_values() {
      let a = Rgb::<Srgb>::from_normalized(0.5, 0.4, 0.8);
      let b = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = a * b;

      assert!((result.r() - 0.25).abs() < 1e-10);
      assert!((result.g() - 0.2).abs() < 1e-10);
      assert!((result.b() - 0.4).abs() < 1e-10);
    }
  }

  mod partial_eq {
    use pretty_assertions::{assert_eq, assert_ne};

    use super::*;

    #[test]
    fn it_compares_equal_rgb_values() {
      let a = Rgb::<Srgb>::new(128, 64, 32);
      let b = Rgb::<Srgb>::new(128, 64, 32);

      assert_eq!(a, b);
    }

    #[test]
    fn it_compares_unequal_rgb_values() {
      let a = Rgb::<Srgb>::new(128, 64, 32);
      let b = Rgb::<Srgb>::new(128, 64, 33);

      assert_ne!(a, b);
    }

    #[test]
    fn it_compares_unequal_when_alpha_differs() {
      let a = Rgb::<Srgb>::new(128, 64, 32).with_alpha(0.5);
      let b = Rgb::<Srgb>::new(128, 64, 32);

      assert_ne!(a, b);
    }
  }

  mod perceptually_map_to_gamut {
    use super::*;

    #[test]
    fn it_brings_out_of_gamut_color_into_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      rgb.perceptually_map_to_gamut();

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_unchanged_when_in_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.3, 0.8);
      let orig = rgb;
      rgb.perceptually_map_to_gamut();

      assert!((rgb.r() - orig.r()).abs() < 1e-10);
      assert!((rgb.g() - orig.g()).abs() < 1e-10);
      assert!((rgb.b() - orig.b()).abs() < 1e-10);
    }

    #[test]
    fn it_preserves_alpha() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5).with_alpha(0.6);
      rgb.perceptually_map_to_gamut();

      assert!((rgb.alpha() - 0.6).abs() < 1e-10);
    }
  }

  mod scale_b {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_scales_blue_by_factor() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.scale_b(0.5);

      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_clamps_to_1() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 200);
      rgb.scale_b(2.0);

      assert_eq!(rgb.blue(), 255);
    }
  }

  mod scale_g {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_scales_green_by_factor() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.scale_g(0.5);

      assert_eq!(rgb.green(), 64);
    }
  }

  mod scale_r {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_scales_red_by_factor() {
      let mut rgb = Rgb::<Srgb>::new(128, 128, 128);
      rgb.scale_r(0.5);

      assert_eq!(rgb.red(), 64);
    }
  }

  mod scale_to_gamut {
    use super::*;

    #[test]
    fn it_scales_to_bring_into_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);
      rgb.scale_to_gamut();

      assert!(rgb.is_in_gamut());
    }

    #[test]
    fn it_returns_unchanged_when_in_gamut() {
      let mut rgb = Rgb::<Srgb>::from_normalized(0.5, 0.3, 0.8);
      let orig = rgb;
      rgb.scale_to_gamut();

      assert!((rgb.r() - orig.r()).abs() < 1e-10);
      assert!((rgb.g() - orig.g()).abs() < 1e-10);
      assert!((rgb.b() - orig.b()).abs() < 1e-10);
    }

    #[test]
    fn it_preserves_alpha() {
      let mut rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5).with_alpha(0.5);
      rgb.scale_to_gamut();

      assert!((rgb.alpha() - 0.5).abs() < 1e-10);
    }
  }

  mod sub {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_subtracts_two_rgb_values() {
      let a = Rgb::<Srgb>::from_normalized(0.8, 0.6, 0.4);
      let b = Rgb::<Srgb>::from_normalized(0.3, 0.2, 0.1);
      let result = a - b;

      assert!((result.r() - 0.5).abs() < 1e-10);
      assert!((result.g() - 0.4).abs() < 1e-10);
      assert!((result.b() - 0.3).abs() < 1e-10);
    }

    #[test]
    fn it_clamps_result_to_0() {
      let a = Rgb::<Srgb>::from_normalized(0.2, 0.2, 0.2);
      let b = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = a - b;

      assert_eq!(result.r(), 0.0);
      assert_eq!(result.g(), 0.0);
      assert_eq!(result.b(), 0.0);
    }
  }

  mod to_css {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_outputs_opaque_rgb() {
      let color = Rgb::<Srgb>::new(255, 87, 51);
      assert_eq!(color.to_css(), "rgb(255 87 51)");
    }

    #[test]
    fn it_outputs_translucent_rgb() {
      let color = Rgb::<Srgb>::new(255, 87, 51).with_alpha(0.5);
      assert_eq!(color.to_css(), "rgb(255 87 51 / 0.5)");
    }

    #[test]
    fn it_outputs_black() {
      let color = Rgb::<Srgb>::new(0, 0, 0);
      assert_eq!(color.to_css(), "rgb(0 0 0)");
    }

    #[test]
    fn it_outputs_white() {
      let color = Rgb::<Srgb>::new(255, 255, 255);
      assert_eq!(color.to_css(), "rgb(255 255 255)");
    }
  }

  mod to_hex {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_outputs_hex() {
      let color = Rgb::<Srgb>::new(255, 87, 51);
      assert_eq!(color.to_hex(), "#ff5733");
    }

    #[test]
    fn it_outputs_black() {
      let color = Rgb::<Srgb>::new(0, 0, 0);
      assert_eq!(color.to_hex(), "#000000");
    }

    #[test]
    fn it_outputs_white() {
      let color = Rgb::<Srgb>::new(255, 255, 255);
      assert_eq!(color.to_hex(), "#ffffff");
    }
  }

  #[cfg(feature = "space-cmyk")]
  mod to_cmyk {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_converts_pure_red() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 0.0, 0.0);
      let cmyk: Cmyk<Srgb> = rgb.to_cmyk();

      assert!((cmyk.cyan()).abs() < 1e-10);
      assert!((cmyk.magenta() - 100.0).abs() < 1e-10);
      assert!((cmyk.yellow() - 100.0).abs() < 1e-10);
      assert!((cmyk.key()).abs() < 1e-10);
    }

    #[test]
    fn it_converts_white() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 1.0, 1.0);
      let cmyk: Cmyk<Srgb> = rgb.to_cmyk();

      assert!((cmyk.cyan()).abs() < 1e-10);
      assert!((cmyk.magenta()).abs() < 1e-10);
      assert!((cmyk.yellow()).abs() < 1e-10);
      assert!((cmyk.key()).abs() < 1e-10);
    }

    #[test]
    fn it_converts_black() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 0.0, 0.0);
      let cmyk: Cmyk<Srgb> = rgb.to_cmyk();

      assert!((cmyk.cyan()).abs() < 1e-10);
      assert!((cmyk.magenta()).abs() < 1e-10);
      assert!((cmyk.yellow()).abs() < 1e-10);
      assert!((cmyk.key() - 100.0).abs() < 1e-10);
    }

    #[test]
    fn it_roundtrips_preserving_color() {
      let original = Rgb::<Srgb>::from_normalized(0.8, 0.4, 0.2);
      let cmyk: Cmyk<Srgb> = original.to_cmyk();
      let back: Rgb<Srgb> = cmyk.to_rgb();

      assert_eq!(original.red(), back.red());
      assert_eq!(original.green(), back.green());
      assert_eq!(original.blue(), back.blue());
    }
  }

  mod to_linear {
    use super::*;

    #[test]
    fn it_converts_to_linear_rgb() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let linear = rgb.to_linear();

      assert!(linear.r() < rgb.r());
      assert!(linear.g() < rgb.g());
      assert!(linear.b() < rgb.b());
    }

    #[test]
    fn it_roundtrips_with_to_encoded() {
      let original = Rgb::<Srgb>::new(128, 64, 200);
      let linear = original.to_linear();
      let back = linear.to_encoded();

      assert_eq!(back.red(), original.red());
      assert_eq!(back.green(), original.green());
      assert_eq!(back.blue(), original.blue());
    }

    #[test]
    fn it_preserves_alpha() {
      let rgb = Rgb::<Srgb>::new(128, 64, 200).with_alpha(0.3);
      let linear = rgb.to_linear();

      assert!((linear.alpha() - 0.3).abs() < 1e-10);
    }
  }

  mod to_lms {
    use super::*;

    #[test]
    fn it_converts_to_lms_via_xyz() {
      let rgb = Rgb::<Srgb>::new(200, 100, 50);
      let lms = rgb.to_lms();

      assert!(lms.l().is_finite());
      assert!(lms.m().is_finite());
      assert!(lms.s().is_finite());
    }

    #[test]
    fn it_roundtrips_with_from_lms() {
      let original = Rgb::<Srgb>::new(200, 100, 50);
      let lms = original.to_lms();
      let back: Rgb<Srgb> = Rgb::from(lms);

      assert_eq!(back.red(), original.red());
      assert_eq!(back.green(), original.green());
      assert_eq!(back.blue(), original.blue());
    }
  }

  #[cfg(feature = "space-hwb")]
  mod to_hwb {
    use super::*;

    #[test]
    fn it_converts_pure_red() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 0.0, 0.0);
      let hwb = rgb.to_hwb();

      assert!((hwb.hue() - 0.0).abs() < 1e-10);
      assert!((hwb.whiteness() - 0.0).abs() < 1e-10);
      assert!((hwb.blackness() - 0.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_pure_green() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 1.0, 0.0);
      let hwb = rgb.to_hwb();

      assert!((hwb.hue() - 120.0).abs() < 1e-10);
      assert!((hwb.whiteness() - 0.0).abs() < 1e-10);
      assert!((hwb.blackness() - 0.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_pure_blue() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 0.0, 1.0);
      let hwb = rgb.to_hwb();

      assert!((hwb.hue() - 240.0).abs() < 1e-10);
      assert!((hwb.whiteness() - 0.0).abs() < 1e-10);
      assert!((hwb.blackness() - 0.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_black_to_full_blackness() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 0.0, 0.0);
      let hwb = rgb.to_hwb();

      assert!((hwb.whiteness()).abs() < 1e-10);
      assert!((hwb.blackness() - 100.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_white_to_full_whiteness() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 1.0, 1.0);
      let hwb = rgb.to_hwb();

      assert!((hwb.whiteness() - 100.0).abs() < 1e-10);
      assert!((hwb.blackness()).abs() < 1e-10);
    }

    #[test]
    fn it_converts_gray_to_equal_whiteness_blackness() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let hwb = rgb.to_hwb();

      assert!((hwb.whiteness() - 50.0).abs() < 1e-10);
      assert!((hwb.blackness() - 50.0).abs() < 1e-10);
    }

    #[test]
    fn it_roundtrips_with_from_hwb() {
      use pretty_assertions::assert_eq;

      let original = Rgb::<Srgb>::new(200, 100, 50);
      let hwb = original.to_hwb();
      let back: Rgb<Srgb> = Rgb::from(hwb);

      assert_eq!(back.red(), original.red());
      assert_eq!(back.green(), original.green());
      assert_eq!(back.blue(), original.blue());
    }
  }

  #[cfg(feature = "space-hsl")]
  mod to_hsl {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_converts_pure_red() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 0.0, 0.0);
      let hsl = rgb.to_hsl();

      assert!((hsl.hue() - 0.0).abs() < 1e-10);
      assert!((hsl.saturation() - 100.0).abs() < 1e-10);
      assert!((hsl.lightness() - 50.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_pure_green() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 1.0, 0.0);
      let hsl = rgb.to_hsl();

      assert!((hsl.hue() - 120.0).abs() < 1e-10);
      assert!((hsl.saturation() - 100.0).abs() < 1e-10);
      assert!((hsl.lightness() - 50.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_pure_blue() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 0.0, 1.0);
      let hsl = rgb.to_hsl();

      assert!((hsl.hue() - 240.0).abs() < 1e-10);
      assert!((hsl.saturation() - 100.0).abs() < 1e-10);
      assert!((hsl.lightness() - 50.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_black_to_zero_saturation() {
      let rgb = Rgb::<Srgb>::from_normalized(0.0, 0.0, 0.0);
      let hsl = rgb.to_hsl();

      assert!((hsl.saturation()).abs() < 1e-10);
      assert!((hsl.lightness()).abs() < 1e-10);
    }

    #[test]
    fn it_converts_white_to_zero_saturation() {
      let rgb = Rgb::<Srgb>::from_normalized(1.0, 1.0, 1.0);
      let hsl = rgb.to_hsl();

      assert!((hsl.saturation()).abs() < 1e-10);
      assert!((hsl.lightness() - 100.0).abs() < 1e-10);
    }

    #[test]
    fn it_converts_gray_to_zero_saturation() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let hsl = rgb.to_hsl();

      assert!((hsl.saturation()).abs() < 1e-10);
      assert!((hsl.lightness() - 50.0).abs() < 1e-10);
    }

    #[test]
    fn it_roundtrips_with_from_hsl() {
      let original = Rgb::<Srgb>::new(200, 100, 50);
      let hsl = original.to_hsl();
      let back: Rgb<Srgb> = Rgb::from(hsl);

      assert_eq!(back.red(), original.red());
      assert_eq!(back.green(), original.green());
      assert_eq!(back.blue(), original.blue());
    }

    #[test]
    fn it_preserves_alpha() {
      let rgb = Rgb::<Srgb>::new(200, 100, 50).with_alpha(0.4);
      let hsl = rgb.to_hsl();

      assert!((hsl.alpha() - 0.4).abs() < 1e-10);
    }
  }

  #[cfg(feature = "space-oklab")]
  mod to_oklab {
    use super::*;

    #[test]
    fn it_converts_to_oklab() {
      let rgb = Rgb::<Srgb>::new(128, 64, 200);
      let oklab = rgb.to_oklab();

      assert!(oklab.l() > 0.0);
      assert!(oklab.a().is_finite());
      assert!(oklab.b().is_finite());
    }

    #[test]
    fn it_converts_white() {
      let rgb = Rgb::<Srgb>::new(255, 255, 255);
      let oklab = rgb.to_oklab();

      assert!((oklab.l() - 1.0).abs() < 1e-3);
      assert!(oklab.a().abs() < 1e-3);
      assert!(oklab.b().abs() < 1e-3);
    }

    #[test]
    fn it_converts_black() {
      let rgb = Rgb::<Srgb>::new(0, 0, 0);
      let oklab = rgb.to_oklab();

      assert!(oklab.l().abs() < 1e-3);
      assert!(oklab.a().abs() < 1e-3);
      assert!(oklab.b().abs() < 1e-3);
    }

    #[test]
    fn it_roundtrips_through_oklab() {
      let original = Rgb::<Srgb>::new(128, 64, 200);
      let roundtrip = original.to_oklab().to_rgb::<Srgb>();

      assert_eq!(roundtrip.red(), original.red());
      assert_eq!(roundtrip.green(), original.green());
      assert_eq!(roundtrip.blue(), original.blue());
    }

    #[test]
    fn it_preserves_alpha() {
      let rgb = Rgb::<Srgb>::new(128, 64, 200).with_alpha(0.5);
      let oklab = rgb.to_oklab();

      assert!((oklab.alpha() - 0.5).abs() < 1e-10);
    }
  }

  mod to_rgb {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_same_values_for_same_space() {
      let rgb = Rgb::<Srgb>::new(200, 100, 50);
      let result: Rgb<Srgb> = rgb.to_rgb();

      assert_eq!(result.red(), rgb.red());
      assert_eq!(result.green(), rgb.green());
      assert_eq!(result.blue(), rgb.blue());
    }
  }

  mod to_xyz {
    use super::*;

    #[test]
    fn it_converts_white_to_approximately_d65() {
      let white = Rgb::<Srgb>::new(255, 255, 255);
      let xyz = white.to_xyz();

      assert!((xyz.x() - 0.95047).abs() < 0.01);
      assert!((xyz.y() - 1.0).abs() < 0.01);
      assert!((xyz.z() - 1.08883).abs() < 0.01);
    }

    #[test]
    fn it_converts_black_to_origin() {
      let black = Rgb::<Srgb>::new(0, 0, 0);
      let xyz = black.to_xyz();

      assert!(xyz.x().abs() < 1e-10);
      assert!(xyz.y().abs() < 1e-10);
      assert!(xyz.z().abs() < 1e-10);
    }

    #[test]
    fn it_roundtrips_with_from_xyz() {
      let original = Rgb::<Srgb>::new(200, 100, 50);
      let xyz = original.to_xyz();
      let back: Rgb<Srgb> = Rgb::from(xyz);

      assert_eq!(back.red(), original.red());
      assert_eq!(back.green(), original.green());
      assert_eq!(back.blue(), original.blue());
    }

    #[test]
    fn it_preserves_alpha() {
      let rgb = Rgb::<Srgb>::new(200, 100, 50).with_alpha(0.7);
      let xyz = rgb.to_xyz();

      assert!((xyz.alpha() - 0.7).abs() < 1e-10);
    }
  }

  mod try_from_str {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_parses_valid_hexcode() {
      let rgb: Rgb<Srgb> = Rgb::try_from("#FF8040").unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 128);
      assert_eq!(rgb.blue(), 64);
    }

    #[test]
    fn it_returns_error_for_invalid_hexcode() {
      let result: Result<Rgb<Srgb>, _> = Rgb::try_from("#GGHHII");

      assert!(result.is_err());
    }
  }

  mod try_from_string {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_parses_valid_hexcode() {
      let rgb: Rgb<Srgb> = Rgb::try_from("#FF8040".to_string()).unwrap();

      assert_eq!(rgb.red(), 255);
      assert_eq!(rgb.green(), 128);
      assert_eq!(rgb.blue(), 64);
    }
  }

  mod with_alpha_flattened {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_alpha_flattened() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      let result = rgb.with_alpha_flattened();

      assert_eq!(result.red(), 128);
      assert_eq!(result.green(), 0);
      assert_eq!(result.blue(), 0);
      assert!((result.alpha() - 1.0).abs() < 1e-10);
    }

    #[test]
    fn it_does_not_mutate_original() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      let _ = rgb.with_alpha_flattened();

      assert!((rgb.alpha() - 0.5).abs() < 1e-10);
    }
  }

  mod with_alpha_flattened_against {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_alpha_flattened_against_background() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      let bg = Rgb::<Srgb>::new(0, 0, 255);
      let result = rgb.with_alpha_flattened_against(bg);

      assert_eq!(result.red(), 128);
      assert_eq!(result.green(), 0);
      assert_eq!(result.blue(), 128);
    }

    #[test]
    fn it_does_not_mutate_original() {
      let mut rgb = Rgb::<Srgb>::new(255, 0, 0);
      rgb.set_alpha(0.5);
      let bg = Rgb::<Srgb>::new(0, 0, 255);
      let _ = rgb.with_alpha_flattened_against(bg);

      assert!((rgb.alpha() - 0.5).abs() < 1e-10);
    }
  }

  mod with_b {
    use super::*;

    #[test]
    fn it_returns_rgb_with_new_normalized_blue() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_b(0.25);

      assert!((result.b() - 0.25).abs() < 1e-10);
      assert!((result.r() - 0.5).abs() < 1e-10);
      assert!((result.g() - 0.5).abs() < 1e-10);
    }
  }

  mod with_b_decremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_blue() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_b_decremented_by(0.25);

      assert!((result.b() - 0.25).abs() < 1e-10);
      assert!((rgb.b() - 0.5).abs() < 1e-10);
    }
  }

  mod with_b_incremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_blue() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.25);
      let result = rgb.with_b_incremented_by(0.25);

      assert!((result.b() - 0.5).abs() < 1e-10);
    }
  }

  mod with_b_scaled_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_scaled_blue() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_b_scaled_by(0.5);

      assert_eq!(result.blue(), 64);
    }
  }

  mod with_blue {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_new_u8_blue() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_blue(64);

      assert_eq!(result.blue(), 64);
      assert_eq!(result.red(), 128);
      assert_eq!(result.green(), 128);
    }
  }

  mod with_blue_decremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_blue() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_blue_decremented_by(64);

      assert_eq!(result.blue(), 64);
      assert_eq!(rgb.blue(), 128);
    }
  }

  mod with_blue_incremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_blue() {
      let rgb = Rgb::<Srgb>::new(128, 128, 64);
      let result = rgb.with_blue_incremented_by(64);

      assert_eq!(result.blue(), 128);
    }
  }

  mod with_gamut_clipped {
    use super::*;

    #[test]
    fn it_returns_clipped_color() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      let result = rgb.with_gamut_clipped();

      assert!(result.is_in_gamut());
      assert!((result.r() - 1.0).abs() < 1e-10);
      assert!((result.g() - 0.0).abs() < 1e-10);
      assert!((result.b() - 0.5).abs() < 1e-10);
    }

    #[test]
    fn it_does_not_mutate_original() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      let _ = rgb.with_gamut_clipped();

      assert!((rgb.r() - 1.5).abs() < 1e-10);
      assert!((rgb.g() - -0.2).abs() < 1e-10);
    }
  }

  #[cfg(feature = "space-lab")]
  mod with_gamut_compressed {
    use super::*;

    #[test]
    fn it_returns_compressed_color() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      let result = rgb.with_gamut_compressed();

      assert!(result.is_in_gamut());
    }

    #[test]
    fn it_does_not_mutate_original() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, -0.2, 0.5);
      let _ = rgb.with_gamut_compressed();

      assert!((rgb.r() - 1.5).abs() < 1e-10);
      assert!((rgb.g() - -0.2).abs() < 1e-10);
    }
  }

  mod with_gamut_perceptually_mapped {
    use super::*;

    #[test]
    fn it_returns_perceptually_mapped_color() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);
      let result = rgb.with_gamut_perceptually_mapped();

      assert!(result.is_in_gamut());
    }

    #[test]
    fn it_does_not_mutate_original() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);
      let _ = rgb.with_gamut_perceptually_mapped();

      assert!((rgb.r() - 1.5).abs() < 1e-10);
    }
  }

  mod with_gamut_scaled {
    use super::*;

    #[test]
    fn it_returns_scaled_color() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);
      let result = rgb.with_gamut_scaled();

      assert!(result.is_in_gamut());
    }

    #[test]
    fn it_does_not_mutate_original() {
      let rgb = Rgb::<Srgb>::from_normalized(1.5, 0.5, 0.5);
      let _ = rgb.with_gamut_scaled();

      assert!((rgb.r() - 1.5).abs() < 1e-10);
    }
  }

  mod with_g {
    use super::*;

    #[test]
    fn it_returns_rgb_with_new_normalized_green() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_g(0.25);

      assert!((result.g() - 0.25).abs() < 1e-10);
      assert!((result.r() - 0.5).abs() < 1e-10);
      assert!((result.b() - 0.5).abs() < 1e-10);
    }
  }

  mod with_g_decremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_green() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_g_decremented_by(0.25);

      assert!((result.g() - 0.25).abs() < 1e-10);
    }
  }

  mod with_g_incremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_green() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.25, 0.5);
      let result = rgb.with_g_incremented_by(0.25);

      assert!((result.g() - 0.5).abs() < 1e-10);
    }
  }

  mod with_g_scaled_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_scaled_green() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_g_scaled_by(0.5);

      assert_eq!(result.green(), 64);
    }
  }

  mod with_green {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_new_u8_green() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_green(64);

      assert_eq!(result.green(), 64);
      assert_eq!(result.red(), 128);
      assert_eq!(result.blue(), 128);
    }
  }

  mod with_green_decremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_green() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_green_decremented_by(64);

      assert_eq!(result.green(), 64);
    }
  }

  mod with_green_incremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_green() {
      let rgb = Rgb::<Srgb>::new(128, 64, 128);
      let result = rgb.with_green_incremented_by(64);

      assert_eq!(result.green(), 128);
    }
  }

  mod with_r {
    use super::*;

    #[test]
    fn it_returns_rgb_with_new_normalized_red() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_r(0.25);

      assert!((result.r() - 0.25).abs() < 1e-10);
      assert!((result.g() - 0.5).abs() < 1e-10);
      assert!((result.b() - 0.5).abs() < 1e-10);
    }
  }

  mod with_r_decremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_red() {
      let rgb = Rgb::<Srgb>::from_normalized(0.5, 0.5, 0.5);
      let result = rgb.with_r_decremented_by(0.25);

      assert!((result.r() - 0.25).abs() < 1e-10);
    }
  }

  mod with_r_incremented_by {
    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_red() {
      let rgb = Rgb::<Srgb>::from_normalized(0.25, 0.5, 0.5);
      let result = rgb.with_r_incremented_by(0.25);

      assert!((result.r() - 0.5).abs() < 1e-10);
    }
  }

  mod with_r_scaled_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_scaled_red() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_r_scaled_by(0.5);

      assert_eq!(result.red(), 64);
    }
  }

  mod with_red {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_new_u8_red() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_red(64);

      assert_eq!(result.red(), 64);
      assert_eq!(result.green(), 128);
      assert_eq!(result.blue(), 128);
    }
  }

  mod with_red_decremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_decremented_red() {
      let rgb = Rgb::<Srgb>::new(128, 128, 128);
      let result = rgb.with_red_decremented_by(64);

      assert_eq!(result.red(), 64);
    }
  }

  mod with_red_incremented_by {
    use pretty_assertions::assert_eq;

    use super::*;

    #[test]
    fn it_returns_rgb_with_incremented_red() {
      let rgb = Rgb::<Srgb>::new(64, 128, 128);
      let result = rgb.with_red_incremented_by(64);

      assert_eq!(result.red(), 128);
    }
  }
}
