├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ ├── rustdoc.yaml │ └── ci.yaml ├── src ├── details │ ├── descriptions │ │ ├── srgb_u8.md │ │ ├── srgba_u8.md │ │ ├── srgb_f32.md │ │ └── srgba_f32.md │ ├── reprs.rs │ ├── linear_spaces.rs │ ├── component_structs.rs │ ├── traits.rs │ ├── color.rs │ └── encodings.rs └── lib.rs ├── README.md └── Cargo.toml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: fu5ha 4 | ko_fi: gray 5 | -------------------------------------------------------------------------------- /src/details/descriptions/srgb_u8.md: -------------------------------------------------------------------------------- 1 | The fully-encoded form of the sRGB color encoding standard. 2 | 3 | This is one of the most common color encodings. If you have three u8 values (0-255) 4 | or a hex code with 6 digits, this is almost certainly the encoding those values are encoded in. 5 | If you have four u8 values (0-255) or a hex code with 8 digits, you likely have 6 | a color in the [`SrgbAU8`] encoding instead. 7 | 8 | Create a color in this encoding by using [`Color::srgb_u8`] -------------------------------------------------------------------------------- /src/details/descriptions/srgba_u8.md: -------------------------------------------------------------------------------- 1 | The fully-encoded form of the sRGB color encoding standard, with separate alpha component. 2 | 3 | This is one of the most common color encodings. If you have four u8 values (0-255) 4 | or a hex code with 8 digits, this is almost certainly the encoding those values are encoded in. 5 | If you have three u8 values (0-255) or a hex code with 6 digits, you likely have 6 | a color in the [`SrgbU8`] encoding instead. 7 | 8 | Create a color in this encoding using [`Color::srgba_u8`]. -------------------------------------------------------------------------------- /src/details/descriptions/srgb_f32.md: -------------------------------------------------------------------------------- 1 | The non-linear sRGB color encoding (with OETF applied) in 32 bit per component floats. 2 | 3 | This is a moderately common way to specify color values in a color picker. 4 | 5 | If you have floating point values from 0.0 to 1.0 which are directly analogous to 6 | the 0-255 form (i.e. `(0.5, 0.5, 0.5)` should be the same as `(127, 127, 127)`), then this 7 | is the color encoding you have. If you have the same kind of values but with a fourth alpha component, 8 | then you have [`SrgbAF32`] instead. 9 | 10 | Create a color in this encoding using [`Color::srgb_f32`]. -------------------------------------------------------------------------------- /src/details/descriptions/srgba_f32.md: -------------------------------------------------------------------------------- 1 | The non-linear sRGB color encoding (with OETF applied) in 32 bit per component floats with separate alpha. 2 | 3 | This is a moderately common way to specify color values. 4 | If you have four floating point values from 0.0 to 1.0 which are directly analogous to 5 | the 0-255 form (i.e. `(0.5, 0.5, 0.5, 0.5)` should be the same as `(127, 127, 127, 127)`), then this 6 | is the color encoding you have. If you have the same kind of values but with no alpha component, 7 | then you have [`SrgbF32`] instead. 8 | 9 | Create a color in this encoding using [`Color::srgba_f32`]. -------------------------------------------------------------------------------- /src/details/reprs.rs: -------------------------------------------------------------------------------- 1 | use crate::traits::*; 2 | 3 | /// Just a `[u8; 3]`. Used for 8-bits-per-channel, three channel encodings. 4 | pub type U8Repr = [u8; 3]; 5 | 6 | impl ColorRepr for U8Repr { 7 | type Element = u8; 8 | } 9 | 10 | /// Just a `[u8; 4]`. Used for 8-bits-per-channel, four channel encodings. 11 | pub type U8ARepr = [u8; 4]; 12 | 13 | impl ColorRepr for U8ARepr { 14 | type Element = u8; 15 | } 16 | 17 | /// Just a [`glam::Vec3`] (also equivalent in layout to a `[f32; 3]`). Used for 32-bits-per-channel, three channel encodings. 18 | pub type F32Repr = glam::Vec3; 19 | 20 | impl ColorRepr for F32Repr { 21 | type Element = f32; 22 | } 23 | 24 | /// Just a [`glam::Vec4`] (also equivalent in layot to a `[f32; 4]`). Used for 32-bits-per-channel, four channel encodings. 25 | pub type F32ARepr = glam::Vec4; 26 | 27 | impl ColorRepr for F32ARepr { 28 | type Element = f32; 29 | } 30 | -------------------------------------------------------------------------------- /.github/workflows/rustdoc.yaml: -------------------------------------------------------------------------------- 1 | name: rustdoc 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | env: 8 | CARGO_INCREMENTAL: 0 9 | CARGO_NET_RETRY: 10 10 | RUSTUP_MAX_RETRIES: 10 11 | 12 | jobs: 13 | rustdoc: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Install Rust toolchain 21 | uses: actions-rs/toolchain@v1 22 | with: 23 | toolchain: stable 24 | profile: minimal 25 | override: true 26 | components: rustfmt, rust-src 27 | 28 | - name: Build Documentation 29 | run: cargo doc --all --all-features 30 | 31 | - name: Deploy Docs 32 | uses: peaceiris/actions-gh-pages@364c31d33bb99327c77b3a5438a83a357a6729ad # v3.4.0 33 | with: 34 | github_token: ${{ secrets.GITHUB_TOKEN }} 35 | publish_branch: gh-pages 36 | publish_dir: ./target/doc 37 | force_orphan: true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `colstodian` 2 | 3 | [![crates.io](https://img.shields.io/crates/v/colstodian.svg)](https://crates.io/crates/colstodian) 4 | [![docs.rs](https://docs.rs/colstodian/badge.svg)](https://docs.rs/colstodian) 5 | [![ci](https://github.com/fu5ha/colstodian/actions/workflows/ci.yaml/badge.svg)](https://github.com/fu5ha/colstodian/actions) 6 | 7 | ## Introduction 8 | 9 | `colstodian` is a practical, opinionated color management library for games and graphics. 10 | 11 | For more information, see [the latest docs](https://fu5ha.github.io/colstodian/colstodian/) built 12 | from git `main`. 13 | 14 | **NOTE: `main` is currently far divergent from past releases as I rework the crate for a 0.2 release.** 15 | 16 | ## License 17 | 18 | Licensed under either of 19 | 20 | - Apache License, Version 2.0, () 21 | - MIT license () 22 | - Zlib license () 23 | 24 | at your option. 25 | 26 | ### Contribution 27 | 28 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 29 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "colstodian" 3 | version = "0.2.0-alpha.0" 4 | authors = ["Gray Olson "] 5 | license = "MIT OR Apache-2.0 OR Zlib" 6 | edition = "2021" 7 | description = "An opinionated, practical color management library for games and graphics." 8 | documentation = "https://docs.rs/colstodian" 9 | homepage = "https://github.com/fu5ha/colstodian" 10 | repository = "https://github.com/fu5ha/colstodian" 11 | 12 | [package.metadata.docs.rs] 13 | features = ["std", "serde", "bytemuck"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/state/manifest.html 16 | 17 | [dependencies] 18 | bytemuck = { version = "1.13.1", optional = true } 19 | cint = { version = "^0.3.1", features = ["bytemuck"] } 20 | glam = { version = "0.23", default-features = false } # keep in sync with kolor 21 | # kolor = { version = "^0.1.9", default-features = false, features = ["glam", "f32", "color-matrices"], path = "../kolor/build/kolor" } 22 | kolor = { version = "0.1.9", default-features = false, features = ["glam", "f32", "color-matrices"] } 23 | num-traits = { version = "0.2", optional = true, default-features = false } 24 | serde = { version = "1", optional = true, features = ["derive"] } 25 | 26 | [features] 27 | default = ["std", "bytemuck"] 28 | 29 | # enable support for the standard library 30 | std = ["kolor/std", "glam/std"] 31 | 32 | # libm is required when building with no_std 33 | libm = ["kolor/libm", "glam/libm", "num-traits", "num-traits/libm"] 34 | 35 | # add serde Serialize/Deserialize to relevant types 36 | serde = ["dep:serde", "kolor/serde1", "glam/serde"] 37 | 38 | bytemuck = ["dep:bytemuck", "glam/bytemuck"] 39 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/cache@v2 15 | with: 16 | path: | 17 | ~/.cargo/registry 18 | ~/.cargo/git 19 | target 20 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 21 | restore-keys: | 22 | ${{ runner.os }}-cargo- 23 | ${{ runner.os }}- 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | override: true 28 | - uses: actions-rs/cargo@v1 29 | with: 30 | command: test 31 | args: --workspace --all-targets --all-features 32 | - uses: actions-rs/cargo@v1 33 | with: 34 | command: test 35 | args: --workspace --all-targets --no-default-features --features libm 36 | 37 | lint: 38 | name: Lint 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v2 42 | - uses: actions/cache@v2 43 | with: 44 | path: | 45 | ~/.cargo/registry 46 | ~/.cargo/git 47 | target 48 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 49 | restore-keys: | 50 | ${{ runner.os }}-cargo- 51 | ${{ runner.os }}- 52 | - uses: actions-rs/toolchain@v1 53 | with: 54 | toolchain: stable 55 | override: true 56 | components: rustfmt 57 | - uses: actions-rs/cargo@v1 58 | with: 59 | command: fmt 60 | args: --all -- --check 61 | - uses: actions-rs/cargo@v1 62 | with: 63 | command: clippy 64 | args: --all -- -D warnings 65 | - uses: actions-rs/cargo@v1 66 | with: 67 | command: clippy 68 | args: --no-default-features --features libm -- -D warnings 69 | -------------------------------------------------------------------------------- /src/details/linear_spaces.rs: -------------------------------------------------------------------------------- 1 | use crate::traits::*; 2 | 3 | use glam::Vec3; 4 | 5 | use kolor::details::color::*; 6 | 7 | macro_rules! impl_conversion { 8 | ($space:ident to $dst_space:ident => None) => { 9 | impl LinearConvertFromRaw<$space> for $dst_space { 10 | #[inline(always)] 11 | fn linear_part_raw(_: &mut Vec3) {} 12 | } 13 | }; 14 | ($space:ident to $dst_space:ident => $mat:ident) => { 15 | impl LinearConvertFromRaw<$space> for $dst_space { 16 | #[inline(always)] 17 | fn linear_part_raw(color: &mut Vec3) { 18 | *color = kolor::details::generated_matrices::$mat * *color; 19 | } 20 | } 21 | }; 22 | } 23 | 24 | /* canonical conversion template 25 | impl_conversion!(SPACENAME to SPACENAME => None); 26 | impl_conversion!(SPACENAME to Srgb => PRIMARIES_WHITEPOINT_TO_BT_709_D65); 27 | impl_conversion!(SPACENAME to CieXYZ => PRIMARIES_WHITEPOINT_TO_CIE_XYZ_D65); 28 | impl_conversion!(SPACENAME to Bt2020 => PRIMARIES_WHITEPOINT_TO_BT_2020_D65); 29 | impl_conversion!(SPACENAME to AcesCg => PRIMARIES_WHITEPOINT_TO_AP1_D60); 30 | impl_conversion!(SPACENAME to Aces2065 => PRIMARIES_WHITEPOINT_TO_AP0_D60); 31 | impl_conversion!(SPACENAME to DisplayP3 => PRIMARIES_WHITEPOINT_TO_P3_D65); 32 | */ 33 | 34 | /// A type representing the linear part of the sRGB color space. 35 | pub struct Srgb; 36 | 37 | impl LinearColorSpace for Srgb { 38 | const PRIMARIES: RGBPrimaries = RGBPrimaries::BT_709; 39 | const WHITE_POINT: WhitePoint = WhitePoint::D65; 40 | } 41 | 42 | impl_conversion!(Srgb to Srgb => None); 43 | impl_conversion!(Srgb to CieXYZ => BT_709_D65_TO_CIE_XYZ_D65); 44 | impl_conversion!(Srgb to Bt2020 => BT_709_D65_TO_BT_2020_D65); 45 | impl_conversion!(Srgb to AcesCg => BT_709_D65_TO_AP1_D60); 46 | impl_conversion!(Srgb to Aces2065 => BT_709_D65_TO_AP0_D60); 47 | impl_conversion!(Srgb to DisplayP3 => BT_709_D65_TO_P3_D65); 48 | 49 | /// A type representing the reference CIE XYZ 1931 color space. 50 | pub struct CieXYZ; 51 | 52 | impl LinearColorSpace for CieXYZ { 53 | const PRIMARIES: RGBPrimaries = RGBPrimaries::CIE_XYZ; 54 | const WHITE_POINT: WhitePoint = WhitePoint::D65; 55 | } 56 | 57 | impl_conversion!(CieXYZ to CieXYZ => None); 58 | impl_conversion!(CieXYZ to Srgb => CIE_XYZ_D65_TO_BT_709_D65); 59 | impl_conversion!(CieXYZ to Bt2020 => CIE_XYZ_D65_TO_BT_2020_D65); 60 | impl_conversion!(CieXYZ to AcesCg => CIE_XYZ_D65_TO_AP1_D60); 61 | impl_conversion!(CieXYZ to Aces2065 => CIE_XYZ_D65_TO_AP0_D60); 62 | impl_conversion!(CieXYZ to DisplayP3 => CIE_XYZ_D65_TO_P3_D65); 63 | 64 | /// A type representing the BT.2020 linear color space. 65 | pub struct Bt2020; 66 | 67 | impl LinearColorSpace for Bt2020 { 68 | const PRIMARIES: RGBPrimaries = RGBPrimaries::BT_2020; 69 | const WHITE_POINT: WhitePoint = WhitePoint::D65; 70 | } 71 | 72 | /// A type representing the linear ACEScg color space. 73 | pub struct AcesCg; 74 | 75 | impl LinearColorSpace for AcesCg { 76 | const PRIMARIES: RGBPrimaries = RGBPrimaries::AP1; 77 | const WHITE_POINT: WhitePoint = WhitePoint::D60; 78 | } 79 | 80 | /// A type representing the linear ACES 2065 (aka ACES archival) color space. 81 | pub struct Aces2065; 82 | 83 | impl LinearColorSpace for Aces2065 { 84 | const PRIMARIES: RGBPrimaries = RGBPrimaries::AP0; 85 | const WHITE_POINT: WhitePoint = WhitePoint::D60; 86 | } 87 | 88 | /// A type representing the linear part of the Apple Display P3 color space. 89 | pub struct DisplayP3; 90 | 91 | impl LinearColorSpace for DisplayP3 { 92 | const PRIMARIES: RGBPrimaries = RGBPrimaries::P3; 93 | const WHITE_POINT: WhitePoint = WhitePoint::D65; 94 | } 95 | -------------------------------------------------------------------------------- /src/details/component_structs.rs: -------------------------------------------------------------------------------- 1 | //! Structs that act as bags of named components which [`Color`][crate::Color]s of different color encodings 2 | //! may be `Deref`erenced to in order to gain more appropriate dot syntax for that encoding. 3 | //! 4 | //! For example, a [`Color`][crate::Color] in the [`SrgbU8`][crate::encodings::SrgbU8] color space can be `Deref`'d to [`Rgb`], allowing you 5 | //! to do things like `color.r` or `color.g`. 6 | 7 | use core::fmt; 8 | 9 | use crate::reprs::*; 10 | use crate::traits::ComponentStructFor; 11 | 12 | // #[cfg(feature = "bytemuck")] 13 | // macro_rules! impl_bytemuck { 14 | // ($($inner:ident),+) => { 15 | // $( 16 | // unsafe impl bytemuck::Zeroable for $inner {} 17 | // unsafe impl bytemuck::Pod for $inner {} 18 | 19 | // unsafe impl bytemuck::Zeroable for ColAlpha<$inner> {} 20 | // unsafe impl bytemuck::Pod for ColAlpha<$inner> {} 21 | // )+ 22 | // } 23 | // } 24 | 25 | // #[cfg(feature = "bytemuck")] 26 | // impl_bytemuck!(Rgb, ICtCp, Xyz, Lab, LCh); 27 | 28 | /// A bag of components with names R, G, B. Some `Color`s with RGB color encodings 29 | /// will `Deref`/`DerefMut` to this struct so that you can access their components with dot-syntax. 30 | #[repr(C)] 31 | #[derive(Clone, Copy, PartialEq)] 32 | pub struct Rgb { 33 | pub r: T, 34 | pub g: T, 35 | pub b: T, 36 | } 37 | 38 | unsafe impl ComponentStructFor for Rgb { 39 | fn cast(repr: &U8Repr) -> &Self { 40 | // SAFETY: [u8; 3] is guaranteed to have the same layout as Self 41 | unsafe { &*(repr as *const U8Repr as *const Self) } 42 | } 43 | 44 | fn cast_mut(repr: &mut U8Repr) -> &mut Self { 45 | // SAFETY: [u8; 3] is guaranteed to have the same layout as Self 46 | unsafe { &mut *(repr as *mut U8Repr as *mut Self) } 47 | } 48 | } 49 | 50 | unsafe impl ComponentStructFor for Rgb { 51 | fn cast(repr: &F32Repr) -> &Self { 52 | // SAFETY: Vec3 is guaranteed to have the same layout as Self 53 | unsafe { &*(repr as *const F32Repr as *const Self) } 54 | } 55 | 56 | fn cast_mut(repr: &mut F32Repr) -> &mut Self { 57 | // SAFETY: Vec3 is guaranteed to have the same layout as Self 58 | unsafe { &mut *(repr as *mut F32Repr as *mut Self) } 59 | } 60 | } 61 | 62 | #[cfg(not(target_arch = "spirv"))] 63 | impl fmt::Display for Rgb { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | write!(f, "R: {:.3}, G: {:.3}, B: {:.3}", self.r, self.g, self.b) 66 | } 67 | } 68 | 69 | #[cfg(not(target_arch = "spirv"))] 70 | impl fmt::Debug for Rgb { 71 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 72 | write!(f, "R: {}, G: {}, B: {}", self.r, self.g, self.b) 73 | } 74 | } 75 | 76 | /// A bag of components with names R, G, B, A. Some `Color`s with RGBA color encodings 77 | /// will `Deref`/`DerefMut` to this struct so that you can access their components with dot-syntax. 78 | #[repr(C)] 79 | #[derive(Clone, Copy, PartialEq)] 80 | pub struct RgbA { 81 | pub r: T, 82 | pub g: T, 83 | pub b: T, 84 | pub a: T, 85 | } 86 | 87 | unsafe impl ComponentStructFor for RgbA { 88 | fn cast(repr: &U8ARepr) -> &Self { 89 | // SAFETY: [u8; 4] is guaranteed to have the same layout as Self 90 | unsafe { &*(repr as *const U8ARepr as *const Self) } 91 | } 92 | 93 | fn cast_mut(repr: &mut U8ARepr) -> &mut Self { 94 | // SAFETY: [u8; 4] is guaranteed to have the same layout as Self 95 | unsafe { &mut *(repr as *mut U8ARepr as *mut Self) } 96 | } 97 | } 98 | 99 | unsafe impl ComponentStructFor for RgbA { 100 | fn cast(repr: &F32ARepr) -> &Self { 101 | // SAFETY: Vec4 is guaranteed to have the same layout as Self 102 | unsafe { &*(repr as *const F32ARepr as *const Self) } 103 | } 104 | 105 | fn cast_mut(repr: &mut F32ARepr) -> &mut Self { 106 | // SAFETY: Vec4 is guaranteed to have the same layout as Self 107 | unsafe { &mut *(repr as *mut F32ARepr as *mut Self) } 108 | } 109 | } 110 | 111 | #[cfg(not(target_arch = "spirv"))] 112 | impl fmt::Display for RgbA { 113 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 114 | write!( 115 | f, 116 | "R: {:.3}, G: {:.3}, B: {:.3}, A: {:.3}", 117 | self.r, self.g, self.b, self.a 118 | ) 119 | } 120 | } 121 | 122 | #[cfg(not(target_arch = "spirv"))] 123 | impl fmt::Debug for RgbA { 124 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 125 | write!( 126 | f, 127 | "R: {}, G: {}, B: {}, A: {}", 128 | self.r, self.g, self.b, self.a 129 | ) 130 | } 131 | } 132 | 133 | /// A bag of components with names L, A, B. Some `Color`s with Lab color encodings 134 | /// will `Deref`/`DerefMut` to this struct so that you can access their components with dot-syntax. 135 | #[repr(C)] 136 | #[derive(Clone, Copy, PartialEq)] 137 | pub struct Lab { 138 | pub l: T, 139 | pub a: T, 140 | pub b: T, 141 | } 142 | 143 | unsafe impl ComponentStructFor for Lab { 144 | fn cast(repr: &U8Repr) -> &Self { 145 | // SAFETY: [u8; 3] is guaranteed to have the same layout as Self 146 | unsafe { &*(repr as *const U8Repr as *const Self) } 147 | } 148 | 149 | fn cast_mut(repr: &mut U8Repr) -> &mut Self { 150 | // SAFETY: [u8; 3] is guaranteed to have the same layout as Self 151 | unsafe { &mut *(repr as *mut U8Repr as *mut Self) } 152 | } 153 | } 154 | 155 | unsafe impl ComponentStructFor for Lab { 156 | fn cast(repr: &F32Repr) -> &Self { 157 | // SAFETY: Vec3 is guaranteed to have the same layout as Self 158 | unsafe { &*(repr as *const F32Repr as *const Self) } 159 | } 160 | 161 | fn cast_mut(repr: &mut F32Repr) -> &mut Self { 162 | // SAFETY: Vec3 is guaranteed to have the same layout as Self 163 | unsafe { &mut *(repr as *mut F32Repr as *mut Self) } 164 | } 165 | } 166 | 167 | #[cfg(not(target_arch = "spirv"))] 168 | impl fmt::Display for Lab { 169 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 170 | write!(f, "L: {:.3}, a: {:.3}, b: {:.3}", self.l, self.a, self.b) 171 | } 172 | } 173 | 174 | #[cfg(not(target_arch = "spirv"))] 175 | impl fmt::Debug for Lab { 176 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 177 | write!(f, "L: {}, a: {}, b: {}", self.l, self.a, self.b) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/details/traits.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Add, Mul, Sub}; 2 | 3 | use crate::Color; 4 | 5 | use glam::Vec3; 6 | use kolor::details::color::{RGBPrimaries, WhitePoint}; 7 | 8 | /// A type that implements [`ColorEncoding`] represents a collection of metadata 9 | /// that together defines how a color's data is stored and what the values of that data 10 | /// actually mean. 11 | /// 12 | /// You can see a list of all the built-in color encodings in [`crate::details::encodings`]. 13 | pub trait ColorEncoding: Sized + 'static { 14 | /// The raw data representation used by this encoding. 15 | /// 16 | /// You can see a list of all the reprs used by the built-in encodings 17 | /// in [`crate::details::reprs`]. 18 | type Repr: ColorRepr; 19 | 20 | /// The 'bag of components' this color encoding uses. 21 | /// 22 | /// You can see a list of all the built-in component structs in 23 | /// [`crate::details::component_structs`]. 24 | type ComponentStruct: ComponentStructFor; 25 | 26 | /// The [`LinearColorSpace`] used by this encoding. 27 | /// 28 | /// You can see all the built-in linear spaces in 29 | /// [`crate::details::linear_spaces`] 30 | type LinearSpace: LinearColorSpace; 31 | 32 | /// Used in `Debug` and `Display` implementations. 33 | const NAME: &'static str; 34 | 35 | /// Convert from `Self::Repr` to a `glam::Vec3` in the `Self::LinearSpace` color space and a separate 36 | /// (not pre-multiplied) alpha component. If this encoding does not have alpha, return 1.0. 37 | fn src_transform_raw(repr: Self::Repr) -> (Vec3, f32); 38 | 39 | /// Convert from a `glam::Vec3` in `Self::LinearSpace` and separate alpha component to a `Self::Repr` fully 40 | /// encoded in `Self`'s color encoding. If this encoding does not have alpha, you can disregard it. 41 | fn dst_transform_raw(raw: Vec3, alpha: f32) -> Self::Repr; 42 | } 43 | 44 | /// Implementing this trait for a struct marks that it is safe to pointer cast `Repr` as `Self`. 45 | /// 46 | /// # Safety 47 | /// 48 | /// In order to implement this trait, it must be safe to perform the casts implied by the `cast` and 49 | /// `cast_mut` functions. 50 | pub unsafe trait ComponentStructFor: 51 | Sized + Clone + Copy + 'static 52 | { 53 | fn cast(repr: &Repr) -> &Self; 54 | fn cast_mut(repr: &mut Repr) -> &mut Self; 55 | } 56 | 57 | /// Implemented by the raw data representation of a color encoding. 58 | /// 59 | /// You can see a list of all the reprs used by the built-in encodings 60 | /// in [`crate::details::reprs`]. 61 | pub trait ColorRepr: Sized + Clone + Copy + 'static { 62 | /// The type of a single element of this repr 63 | type Element: Sized + Clone + Copy + 'static; 64 | } 65 | 66 | /// Implemented by color encodings that can do alpha compositing. 67 | pub trait AlphaOver: ColorEncoding { 68 | fn composite(over: Color, under: Color) -> Color; 69 | } 70 | 71 | /// Implemented by color encodings that can perform saturate-style clamping. 72 | pub trait Saturate: ColorEncoding { 73 | fn saturate(repr: Self::Repr) -> Self::Repr; 74 | } 75 | 76 | /// Implemented by color encodings which can perform linear interpolation between colors. 77 | /// The interpolation is not necessarily perceptually-linear, it is just linear within the 78 | /// given encoding. 79 | pub trait LinearInterpolate 80 | where 81 | Self: ColorEncoding + WorkingEncoding, 82 | { 83 | fn lerp(from: Color, to: Color, factor: f32) -> Color; 84 | } 85 | 86 | impl LinearInterpolate for E 87 | where 88 | E: ColorEncoding + WorkingEncoding, 89 | E::Repr: Add + Sub + Mul, 90 | { 91 | #[inline] 92 | fn lerp(from: Color, to: Color, factor: f32) -> Color { 93 | Color { 94 | repr: from.repr + ((to.repr - from.repr) * factor), 95 | } 96 | } 97 | } 98 | 99 | /// Implemented by color encodings which are designed to be perceptually-uniform. In general these encodings 100 | /// will produce more visually pleasing results when blending between colors (for example, creating gradients) 101 | /// in many situations. However they are certainly not, silver bullets, and often don't fully deliver on the 102 | /// promise of perceptual uniformity. 103 | pub trait PerceptualEncoding: ColorEncoding + WorkingEncoding {} 104 | 105 | /// Marks a type as representing a color encoding in which it makes sense to be able to perform mathematical 106 | /// operations on the contained color values directly. 107 | pub trait WorkingEncoding: ColorEncoding {} 108 | 109 | /// A type that implements [`LinearColorSpace`] represents a color space which can be defined by a *linear transformation only* 110 | /// (i.e. a 3x3 matrix multiplication) from the CIE XYZ color space. 111 | /// 112 | /// A linear color space is defined by the combination of a set of [Primaries][RGBPrimaries] and a [White Point][WhitePoint]. 113 | /// 114 | /// You can see all the built-in linear spaces in [`crate::details::linear_spaces`] 115 | pub trait LinearColorSpace { 116 | const PRIMARIES: RGBPrimaries; 117 | const WHITE_POINT: WhitePoint; 118 | } 119 | 120 | /// A trait that marks `Self` as being a color encoding which is able to be directly converted from `SrcEnc`, 121 | /// as well as allowing some hooks to perform extra mapping during the conversion if necessary. This is the trait that 122 | /// unlocks the [`.convert::`][Color::convert] method on [`Color`]. 123 | /// 124 | /// In order to be able to [`convert`][Color::convert] from [`Color`] to [`Color`], `EncodingB` 125 | /// must implement [`ConvertFrom`]. 126 | /// 127 | /// If this trait is not implemented for a pair of encodings, then a direct conversion without input or choice from the user 128 | /// is not possible, and a conversion between the encodings will need to be performed manually or in more than one step. 129 | pub trait ConvertFrom 130 | where 131 | SrcEnc: ColorEncoding, 132 | Self: ColorEncoding, 133 | Self::LinearSpace: LinearConvertFromRaw, 134 | { 135 | /// If required or desired, perform a mapping of some kind to the input 136 | /// before it undergoes its source transform. This may be desirable to perform some form of 137 | /// gamut mapping if the src encoding has a larger size of representable colors than the dst encoding. 138 | #[inline(always)] 139 | fn map_src(_src: &mut SrcEnc::Repr) {} 140 | } 141 | 142 | impl ConvertFrom for E 143 | where 144 | E: ColorEncoding, 145 | E::LinearSpace: LinearConvertFromRaw, 146 | { 147 | } 148 | 149 | /// Performs the raw conversion from the [`LinearColorSpace`] represented by `SrcSpc` to 150 | /// the [`LinearColorSpace`] represented by `Self`. 151 | pub trait LinearConvertFromRaw: LinearColorSpace { 152 | fn linear_part_raw(raw: &mut Vec3); 153 | } 154 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An opinionated color management library for games and graphics. 2 | //! 3 | //! # Introduction 4 | //! 5 | //! Color is a really complex and fascinating topic, and I'd love to take you on a little tour 6 | //! and show you how `colstodian` tries to help make it easier to manage. But, if you really just want to get 7 | //! sh\*t done right now, here's the basics: 8 | //! 9 | //! [`Color`] is a unified type representing a color in any [`ColorEncoding`]. 10 | //! The [`ColorEncoding`] defines a bunch of different properties about how the 11 | //! color values are stored and what those values actually mean. For example, 12 | //! [`Color`] is a color with red, green, and blue values that vary from 13 | //! `0-255` and the meaning of those values is defined by the full sRGB color encoding standard. 14 | //! The most common and standard color encodings are exposed in the [`basic_encodings`] module. 15 | //! 16 | //! Many built-in color encodings expose constructor functions on the [`Color`] 17 | //! type. See [the docs for that type][Color] for a full list. The ones you are likely most interested in 18 | //! if you don't know much about color are: 19 | //! 20 | //! * [`Color::srgb_u8`]: If you have three `0-255` values, this is what you want 21 | //! * [`Color::srgb_f32`]: If you have three `0.0..=1.0` values, this is probably what you want 22 | //! * [`Color::linear_srgb`]: If you have three `0.0..=1.0` values that you know are "linear rgb", this is probably what you want 23 | //! 24 | //! If you also have alpha (i.e. four values instead of three), then [`Color::srgba_u8`], [`Color::srgba_f32`], and [`Color::linear_srgba`] 25 | //! are the equivalents of the above with an alpha component. 26 | //! 27 | //! If you want to do math to a color (for example, adding two colors together or multiplying one by a coefficient), 28 | //! you'll want to do so in a color encoding that is conducive to that. Color encodings which have this property implement 29 | //! the [`WorkingEncoding`][details::traits::WorkingEncoding] trait. If a [`Color`] is not encoded in a working encoding, 30 | //! it will not implement common math traits like addition, multiplication, etc. 31 | //! 32 | //! The most common [`WorkingEncoding`] is [`LinearSrgb`]. You can convert a color you have created using any of the 33 | //! constructors above to [`LinearSrgb`] by using the [`.convert::()`][Color::convert] method. 34 | //! 35 | //! If you want to output a color into an image file, the most common color encoding for most common image formats 36 | //! (and the one assumed by viewing programs if a color profile is not embedded) is [`SrgbU8`]. 37 | //! You can convert a color from a working encoding to [`SrgbU8`] for output again with the [`.convert::()`][Color::convert] 38 | //! method. 39 | //! 40 | //! ### Example 41 | //! 42 | //! Here we construct two colors in different ways, convert them both to [`LinearSrgb`] to work with them, and then convert the result 43 | //! to [`SrgbU8`] which can be passed on to be displayed in an image. 44 | //! 45 | //! ``` 46 | //! use colstodian::Color; 47 | //! use colstodian::basic_encodings::{SrgbU8, LinearSrgb}; 48 | //! 49 | //! let color1 = Color::srgb_u8(102, 54, 220); 50 | //! let color2 = Color::srgb_f32(0.5, 0.8, 0.1); 51 | //! 52 | //! let color1_working = color1.convert::(); 53 | //! let color2_working = color2.convert::(); 54 | //! 55 | //! let result_working = color1_working * 0.5 + color2_working; 56 | //! 57 | //! let output = result_working.convert::(); 58 | //! 59 | //! assert_eq!(output, Color::srgb_u8(144, 206, 163)); 60 | //! ``` 61 | //! 62 | //! ## Color Encoding Basics 63 | //! 64 | //! Much like how a 3d vector like a `glam::Vec3` could be used to describe any of: 65 | //! 66 | //! * The motion vector of an object in meters per second 67 | //! * The position of an object relative to a reference point in kilometers 68 | //! * Three "wellness scores" for a character, which each axis representing how happy the character 69 | //! is about some aspect of their life 70 | //! 71 | //! A bag of components that describes "a color" could actually be interpreted in many different 72 | //! ways, and the end result of what those components mean is very different. 73 | //! 74 | //! `colstodian` gathers all the information that defines how a color is represented in data as well as 75 | //! what that data actually means into representative types that implement the [`ColorEncoding`] trait. 76 | //! 77 | //! [`LinearSrgb`]: details::encodings::LinearSrgb 78 | //! [`SrgbU8`]: details::encodings::SrgbU8 79 | #![cfg_attr(not(feature = "std"), no_std)] 80 | #![allow( 81 | clippy::let_and_return, // it makes conversion code more explicit with naming 82 | )] 83 | 84 | /// Contains advanced usage details of the crate. 85 | pub mod details { 86 | pub mod component_structs; 87 | 88 | /// Types representing different [`ColorEncoding`][traits::ColorEncoding]s. 89 | pub mod encodings; 90 | 91 | /// Contains the [`Color`][color::Color] type and helper functions. 92 | pub mod color; 93 | 94 | /// Types representing different [`LinearColorSpace`][traits::LinearColorSpace]s. 95 | #[rustfmt::skip] 96 | pub mod linear_spaces; 97 | 98 | /// The traits which form the backbone of this crate. 99 | pub mod traits; 100 | 101 | /// The underlying data representations ([`ColorRepr`][traits::ColorRepr]s) used by different [`ColorEncoding`][traits::ColorEncoding]s. 102 | pub mod reprs; 103 | } 104 | 105 | pub(crate) use details::*; 106 | 107 | /// Contains a basic set of [`ColorEncoding`]s to get most people going. 108 | /// 109 | /// These are all re-exported from inside the [`details::encodings`] 110 | pub mod basic_encodings { 111 | #[doc(inline)] 112 | pub use crate::details::encodings::LinearSrgb; 113 | #[doc(inline)] 114 | pub use crate::details::encodings::LinearSrgbA; 115 | #[doc(inline)] 116 | pub use crate::details::encodings::SrgbAU8; 117 | #[doc(inline)] 118 | pub use crate::details::encodings::SrgbU8; 119 | } 120 | 121 | #[doc(inline)] 122 | pub use color::Color; 123 | 124 | #[doc(inline)] 125 | pub use traits::ColorEncoding; 126 | 127 | #[doc(inline)] 128 | pub use traits::WorkingEncoding; 129 | 130 | #[doc(inline)] 131 | pub use traits::PerceptualEncoding; 132 | 133 | /// Like [`Into`] but specialized for use with `colstodian` [`Color`] types. 134 | /// 135 | /// This trait exists so that functions can accept colors in a variety of encodings 136 | /// generically in an ergonomic fashion. [`ColorInto`] is blanket implemented generically 137 | /// so that, if you have a function parameter `impl ColorInto>`, 138 | /// a [`Color`] in any other encoding that is able to [`.convert::()`][Color::convert] 139 | /// can be passed into that function as argument directly. 140 | /// 141 | /// See [the docs of the `convert` method on `Color`][Color::convert] for more. 142 | /// 143 | /// # Example 144 | /// 145 | /// ``` 146 | /// # use colstodian::*; 147 | /// # use colstodian::details::encodings::*; 148 | /// # use colstodian::equals_eps::*; 149 | /// type MyColor = Color; 150 | /// 151 | /// fn test_fn(input: impl ColorInto) { 152 | /// let input: MyColor = input.color_into(); 153 | /// let correct = Color::linear_srgb(0.14703, 0.42327, 0.22323); 154 | /// assert_eq_eps!(input, correct, 0.00001); 155 | /// } 156 | /// 157 | /// test_fn(Color::srgb_u8(107, 174, 130)); 158 | /// test_fn(Color::srgb_f32(0.41961, 0.68235, 0.5098)); 159 | /// ``` 160 | pub trait ColorInto { 161 | fn color_into(self) -> DstCol; 162 | } 163 | 164 | use details::traits::ConvertFrom; 165 | use details::traits::LinearConvertFromRaw; 166 | 167 | impl ColorInto> for Color 168 | where 169 | SrcEnc: ColorEncoding, 170 | DstEnc: ColorEncoding + ConvertFrom, 171 | DstEnc::LinearSpace: LinearConvertFromRaw, 172 | { 173 | #[inline(always)] 174 | fn color_into(self) -> Color { 175 | self.convert() 176 | } 177 | } 178 | 179 | /// Helper for use in tests and doctests 180 | #[doc(hidden)] 181 | pub mod equals_eps { 182 | use super::*; 183 | use reprs::*; 184 | use traits::*; 185 | 186 | #[cfg(feature = "libm")] 187 | use num_traits::float::Float; 188 | 189 | /// Check whether `self` and `other` are equal within a margin of `eps`. 190 | pub trait EqualsEps { 191 | fn eq_eps(self, other: Self, eps: T) -> bool; 192 | } 193 | 194 | impl EqualsEps for f32 { 195 | fn eq_eps(self, other: f32, eps: f32) -> bool { 196 | (self - other).abs() <= eps 197 | } 198 | } 199 | 200 | impl EqualsEps for u8 { 201 | fn eq_eps(self, other: u8, eps: u8) -> bool { 202 | (self as i32 - other as i32).unsigned_abs() as u8 <= eps 203 | } 204 | } 205 | 206 | impl EqualsEps for U8Repr { 207 | fn eq_eps(self, other: U8Repr, eps: u8) -> bool { 208 | self[0].eq_eps(other[0], eps) 209 | && self[1].eq_eps(other[1], eps) 210 | && self[2].eq_eps(other[2], eps) 211 | } 212 | } 213 | 214 | impl EqualsEps for U8ARepr { 215 | fn eq_eps(self, other: U8ARepr, eps: u8) -> bool { 216 | self[0].eq_eps(other[0], eps) 217 | && self[1].eq_eps(other[1], eps) 218 | && self[2].eq_eps(other[2], eps) 219 | && self[3].eq_eps(other[3], eps) 220 | } 221 | } 222 | 223 | impl EqualsEps for F32Repr { 224 | fn eq_eps(self, other: F32Repr, eps: f32) -> bool { 225 | self[0].eq_eps(other[0], eps) 226 | && self[1].eq_eps(other[1], eps) 227 | && self[2].eq_eps(other[2], eps) 228 | } 229 | } 230 | 231 | impl EqualsEps for F32ARepr { 232 | fn eq_eps(self, other: F32ARepr, eps: f32) -> bool { 233 | self[0].eq_eps(other[0], eps) 234 | && self[1].eq_eps(other[1], eps) 235 | && self[2].eq_eps(other[2], eps) 236 | && self[3].eq_eps(other[3], eps) 237 | } 238 | } 239 | 240 | impl EqualsEps<::Element> for Color 241 | where 242 | E::Repr: EqualsEps<::Element>, 243 | { 244 | fn eq_eps(self, other: Color, eps: ::Element) -> bool { 245 | self.repr.eq_eps(other.repr, eps) 246 | } 247 | } 248 | 249 | /// Assert that `$left` and `$right` are equal within a margin of `$eps`. 250 | #[macro_export] 251 | macro_rules! assert_eq_eps { 252 | ($left:expr, $right:expr, $eps:expr) => {{ 253 | match (&($left), &($right)) { 254 | (left_val, right_val) => { 255 | if !(left_val.eq_eps(*right_val, $eps)) { 256 | // The reborrows below are intentional. Without them, the stack slot for the 257 | // borrow is initialized even before the values are compared, leading to a 258 | // noticeable slow down. 259 | panic!( 260 | r#"assertion failed: `(left ~= right with epsilon {})` 261 | left: `{:?}`, 262 | right: `{:?}`"#, 263 | $eps, &*left_val, &*right_val 264 | ) 265 | } 266 | } 267 | } 268 | }}; 269 | } 270 | } 271 | 272 | #[cfg(test)] 273 | mod tests { 274 | use super::*; 275 | use encodings::*; 276 | use equals_eps::*; 277 | use glam::Vec3; 278 | 279 | #[test] 280 | fn basic() { 281 | let grey_f32 = Color::srgb_f32(0.5, 0.5, 0.5); 282 | let grey_u8 = Color::srgb_u8(127, 127, 127); 283 | 284 | assert_eq_eps!(grey_f32.convert::(), grey_u8, 0); 285 | 286 | let col = Color::srgb_u8(102, 51, 153); 287 | let correct = Color::linear_srgb(0.13287, 0.0331, 0.31855); 288 | 289 | assert_eq_eps!(col.convert::(), correct, 0.0001); 290 | } 291 | 292 | #[test] 293 | fn deref() { 294 | let col: Color = Color::srgb_f32(0.2, 0.3, 0.4); 295 | let r = col.r; 296 | let g = col.g; 297 | let b = col.b; 298 | 299 | assert_eq_eps!(r, 0.2, 0.0001); 300 | assert_eq_eps!(g, 0.3, 0.0001); 301 | assert_eq_eps!(b, 0.4, 0.0001); 302 | } 303 | 304 | #[test] 305 | fn deref_alpha() { 306 | let col: Color = Color::srgba_f32(0.2, 0.3, 0.4, 0.5); 307 | let r = col.r; 308 | let g = col.g; 309 | let b = col.b; 310 | let alpha = col.a; 311 | 312 | assert_eq_eps!(r, 0.2, 0.0001); 313 | assert_eq_eps!(g, 0.3, 0.0001); 314 | assert_eq_eps!(b, 0.4, 0.0001); 315 | assert_eq_eps!(alpha, 0.5, 0.0001); 316 | } 317 | 318 | #[test] 319 | fn color_into_trait() { 320 | type MyColorTy = Color; 321 | fn test_fn(input: impl ColorInto) { 322 | let input: MyColorTy = input.color_into(); 323 | let correct = Color::linear_srgb(0.14703, 0.42327, 0.22323); 324 | assert_eq_eps!(input, correct, 0.0001); 325 | } 326 | 327 | test_fn(Color::srgb_u8(107, 174, 130)); 328 | test_fn(Color::srgb_f32(0.41961, 0.68235, 0.5098)); 329 | } 330 | 331 | #[test] 332 | fn working_space_math() { 333 | let col = Color::linear_srgb(1.0, 1.0, 1.0); 334 | 335 | let mut col = col * 0.5; 336 | assert_eq_eps!(col, Color::linear_srgb(0.5, 0.5, 0.5), 0.00001); 337 | 338 | col *= Vec3::new(0.5, 2.0, 0.2); 339 | assert_eq_eps!(col, Color::linear_srgb(0.25, 1.0, 0.1), 0.00001); 340 | 341 | let mut col2 = Color::linear_srgb(1.0, 1.0, 1.0) + col; 342 | assert_eq_eps!(col2, Color::linear_srgb(1.25, 2.0, 1.1), 0.00001); 343 | 344 | col2 -= Color::linear_srgb(0.25, 1.0, 0.1); 345 | assert_eq_eps!(col2, Color::linear_srgb(1.0, 1.0, 1.0), 0.00001); 346 | 347 | col2 /= Vec3::new(2.0, 2.0, 2.0); 348 | assert_eq_eps!(col2, Color::linear_srgb(0.5, 0.5, 0.5), 0.00001); 349 | 350 | col2 = col2 / 0.1; 351 | assert_eq_eps!(col2, Color::linear_srgb(5.0, 5.0, 5.0), 0.00001); 352 | } 353 | 354 | #[test] 355 | fn perceptual_blend() { 356 | let start = Color::srgb_u8(105, 220, 58); 357 | let end = Color::srgb_u8(10, 20, 100); 358 | 359 | let blend_oklab = start 360 | .convert::() 361 | .perceptual_blend(end.convert(), 0.5); 362 | 363 | let blend = blend_oklab.convert::(); 364 | 365 | assert_eq_eps!( 366 | blend_oklab, 367 | Color::oklab(0.52740586, -0.085545816, 0.004893869), 368 | 0.0001 369 | ); 370 | assert_eq_eps!(blend, Color::srgb_u8(35, 123, 105), 0); 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /src/details/color.rs: -------------------------------------------------------------------------------- 1 | use crate::traits::*; 2 | 3 | /* 4 | #[cfg(not(target_arch = "spirv"))] 5 | use crate::{ 6 | error::{DowncastError, DynamicConversionError}, 7 | ColorResult, 8 | }; 9 | */ 10 | 11 | use glam::Vec3; 12 | use glam::Vec4; 13 | #[cfg(feature = "serde")] 14 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 15 | 16 | use core::fmt; 17 | use core::ops::*; 18 | 19 | /// A strongly typed color, parameterized by a [`ColorEncoding`]. 20 | /// 21 | /// [`Color`] is a unified type that defines a color in any [`ColorEncoding`]. 22 | /// The [`ColorEncoding`] defines a bunch of different properties about how the 23 | /// color values are stored and what those values actually mean. For example, 24 | /// [`Color`] is a color with red, green, and blue values that vary from 25 | /// `0-255` and the meaning of those values is defined by the full sRGB color encoding standard. 26 | /// The most common and standard color encodings are exposed in the 27 | /// [`basic_encodings`][crate::basic_encodings] module. 28 | /// 29 | /// To create a new [`Color`] value, see the list of constructor helpers in the docs below. 30 | /// 31 | /// There are several ways to work with an existing color. First, you can always access the raw data 32 | /// in the encoding's [`Repr`][ColorEncoding::Repr] directly by accessing `col.repr`. You can also always 33 | /// access the individual named color components through dot-syntax because of the `Deref` and `DerefMut` 34 | /// impls to the encoding's [`ComponentStruct`][ColorEncoding::ComponentStruct]. For example, in an RGB color space, 35 | /// you can access the components with `.r`, `.g`, and `.b`. 36 | /// 37 | /// ``` 38 | /// # use colstodian::Color; 39 | /// # use colstodian::basic_encodings::{LinearSrgb, SrgbU8}; 40 | /// # use glam::Vec3; 41 | /// 42 | /// let col: Color = Color::srgb_u8(100u8, 105u8, 220u8); 43 | /// 44 | /// assert_eq!(col.repr, [100u8, 105u8, 220u8]); 45 | /// 46 | /// let mut col2: Color = Color::linear_srgb(0.5, 1.0, 0.25); 47 | /// 48 | /// assert_eq!(col2.repr, Vec3::new(0.5, 1.0, 0.25)); 49 | /// 50 | /// col2.b = 0.75; 51 | /// 52 | /// assert_eq!(col2.r, 0.5); 53 | /// assert_eq!(col2.g, 1.0); 54 | /// assert_eq!(col2.b, 0.75); 55 | /// ``` 56 | /// 57 | /// In order to do math on color types without accessing the underlying repr directly, you'll need 58 | /// to be in a [`WorkingEncoding`], which is a trait implemented by encodings that support doing 59 | /// such math operations well. 60 | /// 61 | /// You can convert between color encodings using the [`.convert::()`][Color::convert] method. 62 | /// 63 | /// ### Basic Conversion Example 64 | /// 65 | /// Here we construct two colors in different ways, convert them both to [`LinearSrgb`] to work with them, 66 | /// and then convert the resul to [`SrgbU8`] which can be passed on to be displayed in an image. 67 | /// 68 | /// ``` 69 | /// use colstodian::Color; 70 | /// use colstodian::basic_encodings::{SrgbU8, LinearSrgb}; 71 | /// 72 | /// let color1 = Color::srgb_u8(102, 54, 220); 73 | /// let color2 = Color::srgb_f32(0.5, 0.8, 0.1); 74 | /// 75 | /// let color1_working = color1.convert::(); 76 | /// let color2_working = color2.convert::(); 77 | /// 78 | /// let result_working = color1_working * 0.5 + color2_working; 79 | /// 80 | /// let output = result_working.convert::(); 81 | /// 82 | /// assert_eq!(output, Color::srgb_u8(144, 206, 163)); 83 | /// ``` 84 | /// 85 | /// [`LinearSrgb`]: crate::details::encodings::LinearSrgb 86 | /// [`SrgbU8`]: crate::details::encodings::SrgbU8 87 | #[repr(transparent)] 88 | #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] 89 | #[cfg_attr( 90 | feature = "serde", 91 | serde(bound( 92 | serialize = "E::Repr: Serialize", 93 | deserialize = "E::Repr: DeserializeOwned" 94 | )) 95 | )] 96 | pub struct Color { 97 | /// The raw values of the color. Be careful when modifying this directly. 98 | pub repr: E::Repr, 99 | } 100 | 101 | impl Copy for Color {} 102 | 103 | impl Clone for Color { 104 | #[inline(always)] 105 | fn clone(&self) -> Color { 106 | *self 107 | } 108 | } 109 | 110 | impl Color { 111 | /// Creates a [`Color`] from the raw data representation of the specified color encoding. 112 | #[inline(always)] 113 | pub const fn from_repr(repr: E::Repr) -> Self { 114 | Self { repr } 115 | } 116 | } 117 | 118 | impl Color { 119 | /// Converts `self` from one color encoding to another. 120 | /// 121 | /// In order to be able to [`convert`][Color::convert] from `EncodingA` to `EncodingB`, `EncodingB` 122 | /// must implement [`ConvertFrom`]. 123 | /// 124 | /// If that trait is not implemented for a pair of encodings, then a direct conversion without input or choice from the user 125 | /// is not possible, and a conversion between the encodings will need to be performed manually or in more than one step. 126 | /// 127 | /// If you are able to [`convert`][Color::convert] from `EncodingA` to `EncodingB`, then you can also use a 128 | /// `Color` anywhere you need a type that implements [`ColorInto>`][crate::ColorInto]! 129 | /// 130 | /// ## Example 131 | /// 132 | /// ``` 133 | /// # use colstodian::*; 134 | /// # use colstodian::basic_encodings::*; 135 | /// # use colstodian::equals_eps::*; 136 | /// let grey_f32 = Color::srgb_f32(0.5, 0.5, 0.5); 137 | /// let grey_u8 = Color::srgb_u8(127, 127, 127); 138 | /// 139 | /// assert_eq_eps!(grey_f32.convert::(), grey_u8, 0); 140 | /// 141 | /// let col = Color::srgb_u8(102, 51, 153); 142 | /// let col_linear_srgb = col.convert::(); 143 | /// 144 | /// assert_eq_eps!(col_linear_srgb, Color::linear_srgb(0.13287, 0.0331, 0.31855), 0.0001); 145 | /// ``` 146 | pub fn convert(self) -> Color 147 | where 148 | DstEnc: ColorEncoding + ConvertFrom, 149 | DstEnc::LinearSpace: LinearConvertFromRaw, 150 | { 151 | let mut repr = self.repr; 152 | 153 | // src conversion map 154 | >::map_src(&mut repr); 155 | 156 | // src transform 157 | let (mut raw, alpha) = SrcEnc::src_transform_raw(self.repr); 158 | 159 | // linear part 160 | >::linear_part_raw( 161 | &mut raw, 162 | ); 163 | 164 | // dst transform 165 | let dst_repr = DstEnc::dst_transform_raw(raw, alpha); 166 | 167 | Color::from_repr(dst_repr) 168 | } 169 | 170 | /// Interprets this color as `DstEnc`. Requires that `DstEnc`'s `ColorEncoding::Repr` is the same as `self`'s. 171 | /// 172 | /// Using this method assumes you have done an external computation/conversion such that this cast is valid. 173 | #[inline(always)] 174 | pub fn cast>(self) -> Color { 175 | Color { repr: self.repr } 176 | } 177 | } 178 | 179 | impl Color { 180 | /// Clamp the raw element values of `self` within the current color encoding's valid range of values. 181 | #[inline] 182 | pub fn saturate(self) -> Self { 183 | Self::from_repr(::saturate(self.repr)) 184 | } 185 | } 186 | 187 | impl Color 188 | where 189 | E: ColorEncoding + AlphaOver, 190 | { 191 | /// Alpha-composite `self` over `under`. 192 | #[inline(always)] 193 | pub fn alpha_over(self, under: Self) -> Color { 194 | ::composite(self, under) 195 | } 196 | } 197 | 198 | impl Color 199 | where 200 | E: ColorEncoding + PerceptualEncoding + LinearInterpolate, 201 | E::Repr: Add + Sub + Mul, 202 | { 203 | /// Blend `self`'s color values with the color values from `other` with perceptually-linear interpolation. 204 | /// 205 | /// `factor` ranges from `[0..=1.0]`. If `factor` is > `1.0`, results may not be sensical. 206 | #[inline] 207 | pub fn perceptual_blend(self, other: Color, factor: f32) -> Color { 208 | self.lerp(other, factor) 209 | } 210 | } 211 | 212 | impl Color 213 | where 214 | E: ColorEncoding + LinearInterpolate, 215 | E::Repr: Add + Sub + Mul, 216 | { 217 | /// Linearly interpolate from `self`'s value to `other`'s value. Not guaranteed to be perceptually 218 | /// linear or pleasing! 219 | /// 220 | /// If you want a better way to blend colors in a perceptually pleasing way, see [`Color::perceptual_blend`], 221 | /// which requires that the color encoding is a [`PerceptualEncoding`]. 222 | /// 223 | /// `factor` ranges from `[0..=1.0]`. If `factor` is > `1.0`, results may not be sensical. 224 | #[inline] 225 | pub fn lerp(self, other: Self, factor: f32) -> Self { 226 | ::lerp(self, other, factor) 227 | } 228 | } 229 | 230 | impl AsRef for Color 231 | where 232 | E: ColorEncoding, 233 | E::Repr: AsRef, 234 | { 235 | #[inline(always)] 236 | fn as_ref(&self) -> &T { 237 | self.repr.as_ref() 238 | } 239 | } 240 | 241 | impl PartialEq for Color 242 | where 243 | E: ColorEncoding, 244 | E::Repr: PartialEq, 245 | { 246 | #[inline(always)] 247 | fn eq(&self, other: &Color) -> bool { 248 | self.repr == other.repr 249 | } 250 | } 251 | 252 | // SAFETY: Color is transparent with the underlying repr 253 | #[cfg(feature = "bytemuck")] 254 | unsafe impl bytemuck::Zeroable for Color 255 | where 256 | E: ColorEncoding, 257 | E::Repr: bytemuck::Zeroable, 258 | { 259 | } 260 | 261 | // SAFETY: Color is transparent with the underlying repr 262 | #[cfg(feature = "bytemuck")] 263 | unsafe impl bytemuck::Pod for Color 264 | where 265 | E: ColorEncoding, 266 | E::Repr: bytemuck::Pod, 267 | { 268 | } 269 | 270 | // SAFETY: Color is transparent with the underlying repr 271 | #[cfg(feature = "bytemuck")] 272 | unsafe impl bytemuck::TransparentWrapper for Color {} 273 | 274 | impl Deref for Color { 275 | type Target = E::ComponentStruct; 276 | 277 | #[inline(always)] 278 | fn deref(&self) -> &Self::Target { 279 | >::cast(&self.repr) 280 | } 281 | } 282 | 283 | impl DerefMut for Color { 284 | #[inline(always)] 285 | fn deref_mut(&mut self) -> &mut Self::Target { 286 | >::cast_mut(&mut self.repr) 287 | } 288 | } 289 | 290 | #[cfg(not(target_arch = "spirv"))] 291 | impl fmt::Debug for Color 292 | where 293 | E: ColorEncoding, 294 | E::ComponentStruct: fmt::Debug, 295 | { 296 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 297 | write!(f, "Color<{}>({:?})", E::NAME, ::deref(self)) 298 | } 299 | } 300 | 301 | #[cfg(not(target_arch = "spirv"))] 302 | impl fmt::Display for Color 303 | where 304 | E: ColorEncoding, 305 | E::ComponentStruct: fmt::Display, 306 | { 307 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 308 | write!(f, "Color<{}>({})", E::NAME, ::deref(self)) 309 | } 310 | } 311 | 312 | // --------- MATH OP IMPLS ----------- 313 | // 314 | // For working encodings, we want to be able to multiply or divide by 315 | // a unitless quantity of the same shape as the underlying representation 316 | // or element type as a scaling factor. 317 | // 318 | // `col * Repr` 319 | // `col * Repr::Element` 320 | // `Repr * col` 321 | // `Repr::Element * col` 322 | // `col *= Repr` 323 | // `col *= Repr::Element` 324 | // 325 | // `col / Repr` 326 | // `col / Repr::Element` 327 | // `Repr / col` 328 | // `Repr::Element / col` 329 | // `col /= Repr` 330 | // `col /= Repr::Element` 331 | // 332 | // We don't want to be able to multiply or divide a color by another color 333 | // of the same encoding because then we'd just end up with a unitless ratio. 334 | // If someone wants such a quantity, they can access the underlying data and 335 | // do componentwise division themselves, but the fact such an operation is not 336 | // implemented directly on the color type may give pause that the operation is often 337 | // nonsensical. 338 | // 339 | // we also want to be able to add and subtract colors with the same encoding directly 340 | // 341 | // `col + col` 342 | // `col += col` 343 | // 344 | // `col - col` 345 | // `col - col` 346 | 347 | impl Mul for Color 348 | where 349 | E: ColorEncoding + WorkingEncoding, 350 | E::Repr: Mul, 351 | { 352 | type Output = Self; 353 | #[inline(always)] 354 | fn mul(self, rhs: Rhs) -> Self::Output { 355 | Self { 356 | repr: self.repr.mul(rhs), 357 | } 358 | } 359 | } 360 | 361 | impl MulAssign for Color 362 | where 363 | E: ColorEncoding + WorkingEncoding, 364 | E::Repr: MulAssign, 365 | { 366 | #[inline(always)] 367 | fn mul_assign(&mut self, rhs: Rhs) { 368 | self.repr.mul_assign(rhs) 369 | } 370 | } 371 | 372 | impl Mul> for f32 373 | where 374 | E: ColorEncoding + WorkingEncoding, 375 | E::Repr: Mul, 376 | { 377 | type Output = Color; 378 | #[inline(always)] 379 | fn mul(self, mut rhs: Color) -> Self::Output { 380 | rhs.repr = rhs.repr.mul(self); 381 | rhs 382 | } 383 | } 384 | 385 | impl Mul> for Vec3 386 | where 387 | E: ColorEncoding + WorkingEncoding, 388 | E::Repr: Mul, 389 | { 390 | type Output = Color; 391 | #[inline(always)] 392 | fn mul(self, mut rhs: Color) -> Self::Output { 393 | rhs.repr = rhs.repr.mul(self); 394 | rhs 395 | } 396 | } 397 | 398 | impl Mul> for Vec4 399 | where 400 | E: ColorEncoding + WorkingEncoding, 401 | E::Repr: Mul, 402 | { 403 | type Output = Color; 404 | #[inline(always)] 405 | fn mul(self, mut rhs: Color) -> Self::Output { 406 | rhs.repr = rhs.repr.mul(self); 407 | rhs 408 | } 409 | } 410 | 411 | impl Div for Color 412 | where 413 | E: ColorEncoding + WorkingEncoding, 414 | E::Repr: Div, 415 | { 416 | type Output = Self; 417 | #[inline(always)] 418 | fn div(self, rhs: Rhs) -> Self::Output { 419 | Self { 420 | repr: self.repr.div(rhs), 421 | } 422 | } 423 | } 424 | 425 | impl DivAssign for Color 426 | where 427 | E: ColorEncoding + WorkingEncoding, 428 | E::Repr: DivAssign, 429 | { 430 | #[inline(always)] 431 | fn div_assign(&mut self, rhs: Rhs) { 432 | self.repr.div_assign(rhs) 433 | } 434 | } 435 | 436 | impl Div> for f32 437 | where 438 | E: ColorEncoding + WorkingEncoding, 439 | E::Repr: Div, 440 | { 441 | type Output = Color; 442 | #[inline(always)] 443 | fn div(self, mut rhs: Color) -> Self::Output { 444 | rhs.repr = rhs.repr.div(self); 445 | rhs 446 | } 447 | } 448 | 449 | impl Div> for Vec3 450 | where 451 | E: ColorEncoding + WorkingEncoding, 452 | E::Repr: Div, 453 | { 454 | type Output = Color; 455 | #[inline(always)] 456 | fn div(self, mut rhs: Color) -> Self::Output { 457 | rhs.repr = rhs.repr.div(self); 458 | rhs 459 | } 460 | } 461 | 462 | impl Div> for Vec4 463 | where 464 | E: ColorEncoding + WorkingEncoding, 465 | E::Repr: Div, 466 | { 467 | type Output = Color; 468 | #[inline(always)] 469 | fn div(self, mut rhs: Color) -> Self::Output { 470 | rhs.repr = rhs.repr.div(self); 471 | rhs 472 | } 473 | } 474 | 475 | impl Add for Color 476 | where 477 | E: ColorEncoding + WorkingEncoding, 478 | E::Repr: Add, 479 | { 480 | type Output = Self; 481 | #[inline(always)] 482 | fn add(self, rhs: Color) -> Self::Output { 483 | Self { 484 | repr: self.repr.add(rhs.repr), 485 | } 486 | } 487 | } 488 | 489 | impl AddAssign for Color 490 | where 491 | E: ColorEncoding + WorkingEncoding, 492 | E::Repr: AddAssign, 493 | { 494 | #[inline(always)] 495 | fn add_assign(&mut self, rhs: Self) { 496 | self.repr.add_assign(rhs.repr) 497 | } 498 | } 499 | 500 | impl Sub for Color 501 | where 502 | E: ColorEncoding + WorkingEncoding, 503 | E::Repr: Sub, 504 | { 505 | type Output = Self; 506 | #[inline(always)] 507 | fn sub(self, rhs: Color) -> Self::Output { 508 | Self { 509 | repr: self.repr.sub(rhs.repr), 510 | } 511 | } 512 | } 513 | 514 | impl SubAssign for Color 515 | where 516 | E: ColorEncoding + WorkingEncoding, 517 | E::Repr: SubAssign, 518 | { 519 | #[inline(always)] 520 | fn sub_assign(&mut self, rhs: Self) { 521 | self.repr.sub_assign(rhs.repr) 522 | } 523 | } 524 | 525 | // --------- END MATH OP IMPLS ----------- 526 | -------------------------------------------------------------------------------- /src/details/encodings.rs: -------------------------------------------------------------------------------- 1 | use crate::component_structs::*; 2 | use crate::linear_spaces; 3 | use crate::reprs::*; 4 | use crate::traits::*; 5 | use crate::Color; 6 | 7 | use glam::Vec3; 8 | use glam::Vec4; 9 | use glam::Vec4Swizzles; 10 | use kolor::details::color::WhitePoint; 11 | use kolor::details::transform; 12 | 13 | #[inline(always)] 14 | fn u8_to_f32(x: u8) -> f32 { 15 | x as f32 / 255.0 16 | } 17 | 18 | #[inline(always)] 19 | fn f32_to_u8(x: f32) -> u8 { 20 | (x.clamp(0.0, 1.0) * 255.0) as u8 21 | } 22 | 23 | #[doc = include_str!("descriptions/srgb_u8.md")] 24 | pub struct SrgbU8; 25 | 26 | impl Color { 27 | /// Create a [`Color`] in the [`SrgbU8`] encoding. 28 | /// 29 | /// **If you don't know what you're doing and you have RGB values from a color picker that vary 30 | /// from `0-255`, use this.** 31 | /// 32 | /// If you're not sure, see [the `SrgbU8` encoding docs][SrgbU8] for more info. 33 | #[inline(always)] 34 | pub const fn srgb_u8(r: u8, g: u8, b: u8) -> Self { 35 | Color::from_repr([r, g, b]) 36 | } 37 | } 38 | 39 | impl ColorEncoding for SrgbU8 { 40 | type Repr = U8Repr; 41 | 42 | type ComponentStruct = Rgb; 43 | 44 | type LinearSpace = linear_spaces::Srgb; 45 | 46 | const NAME: &'static str = "SrgbU8"; 47 | 48 | #[inline] 49 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 50 | let [x, y, z] = repr; 51 | let raw_electro = Vec3::new(u8_to_f32(x), u8_to_f32(y), u8_to_f32(z)); 52 | let optical = transform::sRGB_eotf(raw_electro, WhitePoint::D65); 53 | (optical, 1.0) 54 | } 55 | 56 | #[inline] 57 | fn dst_transform_raw(raw: glam::Vec3, _: f32) -> Self::Repr { 58 | let electro = transform::sRGB_oetf(raw, WhitePoint::D65); 59 | let repr = [ 60 | f32_to_u8(electro.x), 61 | f32_to_u8(electro.y), 62 | f32_to_u8(electro.z), 63 | ]; 64 | repr 65 | } 66 | } 67 | 68 | impl ConvertFrom for SrgbU8 {} 69 | impl ConvertFrom for SrgbU8 {} 70 | impl ConvertFrom for SrgbU8 {} 71 | impl ConvertFrom for SrgbU8 {} 72 | impl ConvertFrom for SrgbU8 {} 73 | impl ConvertFrom for SrgbU8 {} 74 | impl ConvertFrom for SrgbU8 {} 75 | // TODO: oklab gamut clipping 76 | impl ConvertFrom for SrgbU8 {} 77 | 78 | #[doc = include_str!("descriptions/srgb_f32.md")] 79 | pub struct SrgbF32; 80 | 81 | impl Color { 82 | /// Create a [`Color`] in the [`SrgbF32`] encoding. 83 | /// 84 | /// **If you don't know what you're doing and you have RGB values from a color picker that vary 85 | /// from `0.0..=1.0`, use this.** 86 | /// 87 | /// If you're not sure, see [the `SrgbF32` encoding docs][SrgbF32] for more info. 88 | #[inline(always)] 89 | pub const fn srgb_f32(r: f32, g: f32, b: f32) -> Self { 90 | Color::from_repr(Vec3::new(r, g, b)) 91 | } 92 | } 93 | 94 | impl ColorEncoding for SrgbF32 { 95 | type Repr = F32Repr; 96 | 97 | type ComponentStruct = Rgb; 98 | 99 | type LinearSpace = linear_spaces::Srgb; 100 | 101 | const NAME: &'static str = "SrgbF32"; 102 | 103 | #[inline] 104 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 105 | let optical = transform::sRGB_eotf(repr, WhitePoint::D65); 106 | (optical, 1.0) 107 | } 108 | 109 | #[inline] 110 | fn dst_transform_raw(raw: glam::Vec3, _: f32) -> Self::Repr { 111 | let electro = transform::sRGB_oetf(raw, WhitePoint::D65); 112 | electro 113 | } 114 | } 115 | 116 | impl ConvertFrom for SrgbF32 {} 117 | impl ConvertFrom for SrgbF32 {} 118 | impl ConvertFrom for SrgbF32 {} 119 | impl ConvertFrom for SrgbF32 {} 120 | impl ConvertFrom for SrgbF32 {} 121 | impl ConvertFrom for SrgbF32 {} 122 | impl ConvertFrom for SrgbF32 {} 123 | // TODO: oklab gamut clipping 124 | impl ConvertFrom for SrgbF32 {} 125 | 126 | #[doc = include_str!("descriptions/srgba_u8.md")] 127 | pub struct SrgbAU8; 128 | 129 | impl Color { 130 | /// Create a [`Color`] in the [`SrgbAU8`] encoding. 131 | /// 132 | /// **If you don't know what you're doing and you have RGBA values from a color picker that vary 133 | /// from `0-255`, use this.** 134 | /// 135 | /// If you're not sure, see [the `SrgbAU8` encoding docs][SrgbAU8] for more info. 136 | #[inline(always)] 137 | pub const fn srgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self { 138 | Self::from_repr([r, g, b, a]) 139 | } 140 | } 141 | 142 | impl ColorEncoding for SrgbAU8 { 143 | type Repr = U8ARepr; 144 | 145 | type ComponentStruct = RgbA; 146 | 147 | type LinearSpace = linear_spaces::Srgb; 148 | 149 | const NAME: &'static str = "SrgbAU8"; 150 | 151 | #[inline] 152 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 153 | let [x, y, z, a] = repr; 154 | let raw_electro = Vec3::new(u8_to_f32(x), u8_to_f32(y), u8_to_f32(z)); 155 | let optical = transform::sRGB_eotf(raw_electro, WhitePoint::D65); 156 | let a = u8_to_f32(a); 157 | (optical, a) 158 | } 159 | 160 | #[inline] 161 | fn dst_transform_raw(raw: glam::Vec3, alpha: f32) -> Self::Repr { 162 | let electro = transform::sRGB_oetf(raw, WhitePoint::D65); 163 | let repr = [ 164 | f32_to_u8(electro.x), 165 | f32_to_u8(electro.y), 166 | f32_to_u8(electro.z), 167 | f32_to_u8(alpha), 168 | ]; 169 | repr 170 | } 171 | } 172 | 173 | impl ConvertFrom for SrgbAU8 {} 174 | impl ConvertFrom for SrgbAU8 {} 175 | impl ConvertFrom for SrgbAU8 {} 176 | impl ConvertFrom for SrgbAU8 {} 177 | impl ConvertFrom for SrgbAU8 {} 178 | impl ConvertFrom for SrgbAU8 {} 179 | impl ConvertFrom for SrgbAU8 {} 180 | // TODO: oklab gamut clipping 181 | impl ConvertFrom for SrgbAU8 {} 182 | 183 | #[doc = include_str!("descriptions/srgba_f32.md")] 184 | pub struct SrgbAF32; 185 | 186 | impl Color { 187 | /// Create a [`Color`] in the [`SrgbAF32`] encoding. 188 | /// 189 | /// **If you don't know what you're doing and you have RGB values from a color picker that vary 190 | /// from `0.0..=1.0`, use this.** 191 | /// 192 | /// If you're not sure, see [the `SrgbAF32` encoding docs][SrgbAF32] for more info. 193 | #[inline(always)] 194 | pub const fn srgba_f32(r: f32, g: f32, b: f32, a: f32) -> Self { 195 | Color::from_repr(Vec4::new(r, g, b, a)) 196 | } 197 | } 198 | 199 | impl ColorEncoding for SrgbAF32 { 200 | type Repr = F32ARepr; 201 | 202 | type ComponentStruct = RgbA; 203 | 204 | type LinearSpace = linear_spaces::Srgb; 205 | 206 | const NAME: &'static str = "SrgbAF32"; 207 | 208 | #[inline] 209 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 210 | let optical = transform::sRGB_eotf(repr.xyz(), WhitePoint::D65); 211 | (optical, repr.w) 212 | } 213 | 214 | #[inline] 215 | fn dst_transform_raw(raw: glam::Vec3, alpha: f32) -> Self::Repr { 216 | let electro = transform::sRGB_oetf(raw, WhitePoint::D65); 217 | electro.extend(alpha) 218 | } 219 | } 220 | 221 | impl ConvertFrom for SrgbAF32 {} 222 | impl ConvertFrom for SrgbAF32 {} 223 | impl ConvertFrom for SrgbAF32 {} 224 | impl ConvertFrom for SrgbAF32 {} 225 | impl ConvertFrom for SrgbAF32 {} 226 | impl ConvertFrom for SrgbAF32 {} 227 | impl ConvertFrom for SrgbAF32 {} 228 | // TODO: oklab gamut clipping 229 | impl ConvertFrom for SrgbAF32 {} 230 | 231 | /// The fully-encoded form of the sRGB color encoding standard, with *premultiplied* alpha component. 232 | /// 233 | /// Premultiplied means that the color components are already multiplied by the alpha component. Such multiplication 234 | /// happens *before* the sRGB OETF is applied. 235 | /// 236 | /// This is not a common way for humans to specify colors directly, but is a moderately common way to encode 237 | /// textures before uploading them to the GPU or otherwise using them in a rendering pipeline. 238 | /// 239 | /// This color encoding is defined as the strict sRGB color encoding standard, with 240 | /// OETF applied and encoded into 8 bits per component. The alpha component is linearly encoded 241 | /// into 8 bits, i.e. the sRGB OETF is not applied. 242 | pub struct SrgbAU8Premultiplied; 243 | 244 | impl ColorEncoding for SrgbAU8Premultiplied { 245 | type Repr = U8ARepr; 246 | 247 | type ComponentStruct = RgbA; 248 | 249 | type LinearSpace = linear_spaces::Srgb; 250 | 251 | const NAME: &'static str = "SrgbAU8Premultiplied"; 252 | 253 | #[inline] 254 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 255 | let [x, y, z, a] = repr; 256 | let raw_electro = Vec3::new(u8_to_f32(x), u8_to_f32(y), u8_to_f32(z)); 257 | let optical = transform::sRGB_eotf(raw_electro, WhitePoint::D65); 258 | let a = u8_to_f32(a); 259 | let separated = optical / a; 260 | (separated, a) 261 | } 262 | 263 | #[inline] 264 | fn dst_transform_raw(raw: glam::Vec3, alpha: f32) -> Self::Repr { 265 | let premultiplied = raw * alpha; 266 | let electro = transform::sRGB_oetf(premultiplied, WhitePoint::D65); 267 | let repr = [ 268 | f32_to_u8(electro.x), 269 | f32_to_u8(electro.y), 270 | f32_to_u8(electro.z), 271 | f32_to_u8(alpha), 272 | ]; 273 | repr 274 | } 275 | } 276 | 277 | impl ConvertFrom for SrgbAU8Premultiplied {} 278 | impl ConvertFrom for SrgbAU8Premultiplied {} 279 | impl ConvertFrom for SrgbAU8Premultiplied {} 280 | impl ConvertFrom for SrgbAU8Premultiplied {} 281 | impl ConvertFrom for SrgbAU8Premultiplied {} 282 | impl ConvertFrom for SrgbAU8Premultiplied {} 283 | impl ConvertFrom for SrgbAU8Premultiplied {} 284 | // TODO: oklab gamut clipping 285 | impl ConvertFrom for SrgbAU8Premultiplied {} 286 | 287 | impl AlphaOver for SrgbAU8Premultiplied { 288 | fn composite(over: Color, under: Color) -> Color { 289 | let over = over.convert::(); 290 | let under = under.convert::(); 291 | let comp = over.alpha_over(under); 292 | comp.convert::() 293 | } 294 | } 295 | 296 | /// The linear form of the sRGB color encoding standard. 297 | /// 298 | /// This is a moderately rare way to specify color values. 299 | /// 300 | /// If you have three f32s which are *not* directly related to the u8 form, or you otherwise know should be 301 | /// "linear rgb" values, then this is the encoding you have. If you instead have four values with an alpha 302 | /// component where the alpha component varies independently of the color components, you have [`LinearSrgbA`] values. 303 | /// If you have four values with an alpha component and the rgb components are modified directly when the alpha component 304 | /// changes as well, you have [`LinearSrgbAPremultiplied`] values. 305 | pub struct LinearSrgb; 306 | 307 | impl Color { 308 | /// Create a [`Color`] in the [`LinearSrgb`] encoding. 309 | /// 310 | /// If you're not sure, you should probably use [`Color::srgb_f32`] instead. 311 | /// See [the `LinearSrgb` encoding docs][LinearSrgb] for more info. 312 | #[inline(always)] 313 | pub fn linear_srgb(r: f32, g: f32, b: f32) -> Self { 314 | Color::from_repr(Vec3::new(r, g, b)) 315 | } 316 | } 317 | 318 | impl ColorEncoding for LinearSrgb { 319 | type Repr = F32Repr; 320 | 321 | type ComponentStruct = Rgb; 322 | 323 | type LinearSpace = linear_spaces::Srgb; 324 | 325 | const NAME: &'static str = "LinearSrgb"; 326 | 327 | #[inline(always)] 328 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 329 | (repr, 1.0) 330 | } 331 | 332 | #[inline(always)] 333 | fn dst_transform_raw(raw: glam::Vec3, _: f32) -> Self::Repr { 334 | raw 335 | } 336 | } 337 | 338 | impl ConvertFrom for LinearSrgb {} 339 | impl ConvertFrom for LinearSrgb {} 340 | impl ConvertFrom for LinearSrgb {} 341 | impl ConvertFrom for LinearSrgb {} 342 | impl ConvertFrom for LinearSrgb {} 343 | impl ConvertFrom for LinearSrgb {} 344 | impl ConvertFrom for LinearSrgb {} 345 | // TODO: oklab gamut clipping 346 | impl ConvertFrom for LinearSrgb {} 347 | 348 | impl WorkingEncoding for LinearSrgb {} 349 | 350 | /// The linear form of the sRGB color encoding standard with a separate alpha component. 351 | /// 352 | /// This is a moderately common way to specify color values. 353 | /// 354 | /// If you have four f32s which are *not* directly related to the u8 form, or you otherwise know should be 355 | /// "linear rgb" values, and the alpha component varies independently of the color componewnts, 356 | /// then this is the encoding you have. If you instead have three values, you have [`LinearSrgb`] values. 357 | /// If you have four values with an alpha component and the rgb components are modified directly when the alpha component 358 | /// changes as well, you have [`LinearSrgbAPremultiplied`] values. 359 | pub struct LinearSrgbA; 360 | 361 | impl Color { 362 | /// Create a [`Color`] in the [`LinearSrgbA`] encoding. 363 | /// 364 | /// If you're not sure, you should probably use [`Color::srgba_f32`] instead. 365 | /// See [the `LinearSrgbA` encoding docs][LinearSrgbA] for more info. 366 | #[inline(always)] 367 | pub fn linear_srgba(r: f32, g: f32, b: f32, a: f32) -> Self { 368 | Color::from_repr(Vec4::new(r, g, b, a)) 369 | } 370 | } 371 | 372 | impl ColorEncoding for LinearSrgbA { 373 | type Repr = F32ARepr; 374 | 375 | type ComponentStruct = RgbA; 376 | 377 | type LinearSpace = linear_spaces::Srgb; 378 | 379 | const NAME: &'static str = "LinearSrgbA"; 380 | 381 | #[inline(always)] 382 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 383 | (repr.xyz(), repr.w) 384 | } 385 | 386 | #[inline(always)] 387 | fn dst_transform_raw(raw: glam::Vec3, alpha: f32) -> Self::Repr { 388 | raw.extend(alpha) 389 | } 390 | } 391 | 392 | impl ConvertFrom for LinearSrgbA {} 393 | impl ConvertFrom for LinearSrgbA {} 394 | impl ConvertFrom for LinearSrgbA {} 395 | impl ConvertFrom for LinearSrgbA {} 396 | impl ConvertFrom for LinearSrgbA {} 397 | impl ConvertFrom for LinearSrgbA {} 398 | impl ConvertFrom for LinearSrgbA {} 399 | // TODO: oklab gamut clipping 400 | impl ConvertFrom for LinearSrgbA {} 401 | 402 | impl WorkingEncoding for LinearSrgbA {} 403 | 404 | impl AlphaOver for LinearSrgbA { 405 | fn composite(over: Color, under: Color) -> Color { 406 | let over = over.convert::(); 407 | let under = under.convert::(); 408 | let comp = over.alpha_over(under); 409 | comp.convert::() 410 | } 411 | } 412 | 413 | /// The linear form of the sRGB color encoding standard with a *premultiplied* alpha component. 414 | /// 415 | /// "Premultiplied" alpha means that the value of the color components has been multiplied by the 416 | /// alpha component. This operation is unintuitive when specifying color values, but it is the 417 | /// "most correct" way to store color values with an alpha component when performing operations 418 | /// like blending and compositing on them. 419 | /// 420 | /// This is a relatively rare way to specify color values. 421 | /// 422 | /// If you have four f32s which are *not* directly related to the u8 form, or you otherwise know should be 423 | /// "linear rgb" values, and the alpha component varies independently of the color componewnts, 424 | /// then this is the encoding you have. If you instead have three values, you have [`LinearSrgb`] values. 425 | /// If you have four values with an alpha component and the rgb components are modified directly when the alpha component 426 | /// changes as well, you have [`LinearSrgbAPremultiplied`] values. 427 | pub struct LinearSrgbAPremultiplied; 428 | 429 | impl Color { 430 | /// Create a [`Color`] in the [`LinearSrgbAPremultiplied`] encoding. 431 | /// 432 | /// "Premultiplied" alpha means that the value of the color components has been multiplied by the 433 | /// alpha component. This operation is unintuitive when specifying color values, but it is the 434 | /// "most correct" way to store color values with an alpha component when performing operations 435 | /// like blending and compositing on them. 436 | /// 437 | /// If you're not sure, see [the `LinearSrgbA` encoding docs][LinearSrgbA] for more info. 438 | #[inline(always)] 439 | pub fn linear_srgba_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { 440 | Color::from_repr(Vec4::new(r, g, b, a)) 441 | } 442 | } 443 | 444 | impl ColorEncoding for LinearSrgbAPremultiplied { 445 | type Repr = F32ARepr; 446 | 447 | type ComponentStruct = RgbA; 448 | 449 | type LinearSpace = linear_spaces::Srgb; 450 | 451 | const NAME: &'static str = "LinearSrgbAPremultiplied"; 452 | 453 | #[inline(always)] 454 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 455 | let separated = repr.xyz() / repr.w; 456 | (separated, repr.w) 457 | } 458 | 459 | #[inline(always)] 460 | fn dst_transform_raw(raw: glam::Vec3, alpha: f32) -> Self::Repr { 461 | let premultiplied = raw * alpha; 462 | premultiplied.extend(alpha) 463 | } 464 | } 465 | 466 | impl ConvertFrom for LinearSrgbAPremultiplied {} 467 | impl ConvertFrom for LinearSrgbAPremultiplied {} 468 | impl ConvertFrom for LinearSrgbAPremultiplied {} 469 | impl ConvertFrom for LinearSrgbAPremultiplied {} 470 | impl ConvertFrom for LinearSrgbAPremultiplied {} 471 | impl ConvertFrom for LinearSrgbAPremultiplied {} 472 | impl ConvertFrom for LinearSrgbAPremultiplied {} 473 | // TODO: oklab gamut clipping 474 | impl ConvertFrom for LinearSrgbAPremultiplied {} 475 | 476 | impl AlphaOver for LinearSrgbAPremultiplied { 477 | #[inline] 478 | fn composite(over: Color, under: Color) -> Color { 479 | Color::from_repr(over.repr + under.repr * (1.0 - over.repr.w)) 480 | } 481 | } 482 | 483 | /// A 32-bit-per-component version of the Oklab perceptually-uniform color space. 484 | pub struct Oklab; 485 | 486 | impl Color { 487 | /// Create a [`Color`] in the [`Oklab`] color encoding. 488 | /// 489 | /// This is fairly rare, it would be more common to specify colors in another color encoding like 490 | /// [`SrgbU8`] and then convert them to [`Oklab`] to blend them together. 491 | #[inline(always)] 492 | pub fn oklab(l: f32, a: f32, b: f32) -> Self { 493 | Color::from_repr(Vec3::new(l, a, b)) 494 | } 495 | } 496 | 497 | impl ColorEncoding for Oklab { 498 | type Repr = F32Repr; 499 | 500 | type ComponentStruct = Lab; 501 | 502 | type LinearSpace = linear_spaces::CieXYZ; 503 | 504 | const NAME: &'static str = "Oklab"; 505 | 506 | #[inline(always)] 507 | fn src_transform_raw(repr: Self::Repr) -> (glam::Vec3, f32) { 508 | let xyz = transform::Oklab_to_XYZ(repr, WhitePoint::D65); 509 | (xyz, 1.0) 510 | } 511 | 512 | #[inline(always)] 513 | fn dst_transform_raw(raw: glam::Vec3, _: f32) -> Self::Repr { 514 | let oklab = transform::XYZ_to_Oklab(raw, WhitePoint::D65); 515 | oklab 516 | } 517 | } 518 | 519 | impl ConvertFrom for Oklab {} 520 | impl ConvertFrom for Oklab {} 521 | impl ConvertFrom for Oklab {} 522 | impl ConvertFrom for Oklab {} 523 | impl ConvertFrom for Oklab {} 524 | impl ConvertFrom for Oklab {} 525 | impl ConvertFrom for Oklab {} 526 | impl ConvertFrom for Oklab {} 527 | 528 | impl WorkingEncoding for Oklab {} 529 | impl PerceptualEncoding for Oklab {} 530 | --------------------------------------------------------------------------------