Skip to content

Commit 20b1406

Browse files
committed
Merge branch 'master' of https://github.com/Zentiph/codimate
2 parents 21de14d + 623497e commit 20b1406

File tree

5 files changed

+166
-0
lines changed

5 files changed

+166
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ hex = "0.4"
1818
[profile.release]
1919
lto = "thin"
2020
codegen-units = 1
21+
22+
[features]
23+
default = []
24+
srgb_lut = [] # bool

src/color/lut.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// A lookup table for color to linear conversions,
2+
// sacrificing memory for speed (replacing heavy pow calculations).
3+
// This should primarily be used for CPU-based calculations,
4+
// since GPUs have built-in pow functions and don't need this optimization.
5+
6+
#![allow(dead_code)]
7+
8+
// Compile-time params
9+
10+
use std::sync::OnceLock;
11+
12+
/// linear -> sRGB table size
13+
const N_ENC: usize = 4096;
14+
15+
static SRGB_TO_LINEAR_F32: OnceLock<[f32; 256]> = OnceLock::new();
16+
static LINEAR_TO_SRGB_U8: OnceLock<[u8; N_ENC]> = OnceLock::new();
17+
18+
#[inline]
19+
fn build_srgb_to_linear_f32() -> [f32; 256] {
20+
let mut t = [0.0f32; 256];
21+
for (v, item) in t.iter_mut().enumerate() {
22+
let x = (v as f32) / 255.0;
23+
*item = if x <= 0.04045 {
24+
x / 12.92
25+
} else {
26+
((x + 0.055) / 1.055).powf(2.4)
27+
};
28+
}
29+
t
30+
}
31+
32+
#[inline]
33+
fn build_linear_to_srgb_u8() -> [u8; N_ENC] {
34+
let mut t = [0u8; N_ENC];
35+
for (i, item) in t.iter_mut().enumerate() {
36+
let x = (i as f32) / (N_ENC as f32 - 1.0); // 0..1
37+
let y = if x <= 0.003_130_8 {
38+
12.92 * x
39+
} else {
40+
1.055 * x.powf(1.0 / 2.4) - 0.055
41+
};
42+
*item = ((y.clamp(0.0, 1.0) * 255.0) + 0.5).floor() as u8;
43+
}
44+
t
45+
}
46+
47+
#[inline]
48+
fn get_srgb_to_linear_f32() -> &'static [f32; 256] {
49+
SRGB_TO_LINEAR_F32.get_or_init(build_srgb_to_linear_f32)
50+
}
51+
52+
#[inline]
53+
fn get_linear_to_srgb_u8() -> &'static [u8; N_ENC] {
54+
LINEAR_TO_SRGB_U8.get_or_init(build_linear_to_srgb_u8)
55+
}
56+
57+
// we'll hide the public api behind a feature
58+
59+
#[cfg(feature = "srgb_lut")]
60+
#[inline]
61+
pub(crate) fn decode_srgb_lut_f32(v: u8) -> f32 {
62+
get_srgb_to_linear_f32()[v as usize]
63+
}
64+
65+
#[cfg(feature = "srgb_lut")]
66+
#[inline]
67+
pub(crate) fn encode_srgb_lut_f32(x: f32) -> u8 {
68+
let x = x.clamp(0.0, 1.0);
69+
let idx = x * (N_ENC as f32 - 1.0);
70+
let i = idx as usize;
71+
if i >= N_ENC - 1 {
72+
return get_linear_to_srgb_u8()[N_ENC - 1];
73+
}
74+
let f = idx - i as f32;
75+
let a = get_linear_to_srgb_u8()[i] as u16;
76+
let b = get_linear_to_srgb_u8()[i + 1] as u16;
77+
// linear interp in integer space, then round
78+
let y = a as f32 + (b as f32 - a as f32) * f;
79+
(y + 0.5).floor() as u8
80+
}

src/color/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
#[cfg(feature = "srgb_lut")]
2+
pub mod lut;
13
pub mod model;
24
pub mod parse;

src/color/model.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ use std::fmt::{self};
1010

1111
use crate::traits::float::{Float, clamp_generic};
1212

13+
/// Decode an 8 bit sRGB value into a linear float using a lookup table.
14+
#[cfg(feature = "srgb_lut")]
15+
#[inline]
16+
fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
17+
T::from_f32(crate::color::lut::decode_srgb_lut_f32(srgb_u8))
18+
}
19+
1320
/// Decode an 8 bit sRGB value into a linear float.
21+
#[cfg(not(feature = "srgb_lut"))]
1422
#[inline]
1523
fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
1624
let srgb = T::from_f64((srgb_u8 as f64) / 255.0);
@@ -29,7 +37,15 @@ fn decode_srgb<T: Float>(srgb_u8: u8) -> T {
2937
}
3038
}
3139

40+
/// Encode an 8 bit sRGB value into a linear float using a lookup table.
41+
#[cfg(feature = "srgb_lut")]
42+
#[inline]
43+
fn encode_srgb<T: Float>(lin: T) -> u8 {
44+
crate::color::lut::encode_srgb_lut_f32(lin.to_f32())
45+
}
46+
3247
/// Encode a linear float into an 8 bit sRGB value.
48+
#[cfg(not(feature = "srgb_lut"))]
3349
#[inline]
3450
fn encode_srgb<T: Float>(lin: T) -> u8 {
3551
let l = lin.clamp01();
@@ -73,6 +89,8 @@ impl Default for Color {
7389

7490
impl Color {
7591
// Linear interpolation in sRGB space; use `lerp_linear` for perceptual correctness.
92+
#[must_use]
93+
#[inline]
7694
pub fn lerp(self, other: Color, t: f32) -> Color {
7795
let t = t.clamp(0.0, 1.0);
7896
let lerp8 = |a: u8, b: u8| -> u8 {
@@ -90,6 +108,8 @@ impl Color {
90108
}
91109

92110
// Linear interp in linear space
111+
#[must_use]
112+
#[inline]
93113
pub fn lerp_linear<T: Float>(self, other: Color, t: T) -> Color {
94114
let t = t.clamp01();
95115
let a = self.into_linear::<T>();
@@ -107,6 +127,8 @@ impl Color {
107127
// Porter-Duff "over" in linear space
108128
// for speed over accuracy, use `over_srgb_fast`
109129
// https://keithp.com/~keithp/porterduff/p253-porter.pdf
130+
#[must_use]
131+
#[inline]
110132
pub fn over<T: Float>(self, bg: Color) -> Color {
111133
let fg = self.into_linear::<T>();
112134
let bg = bg.into_linear::<T>();
@@ -127,6 +149,8 @@ impl Color {
127149
}
128150

129151
// Faster (but slightly less accurate) "over" in sRGB space.
152+
#[must_use]
153+
#[inline]
130154
pub fn over_srgb_fast(self, mut dst: Color) -> Color {
131155
let sa = self.a as f32 / 255.0;
132156
if sa <= 0.0 {
@@ -154,6 +178,8 @@ impl Color {
154178
dst
155179
}
156180

181+
#[must_use]
182+
#[inline]
157183
pub fn with_alpha(self, a: u8) -> Self {
158184
Self {
159185
r: self.r,
@@ -163,6 +189,7 @@ impl Color {
163189
}
164190
}
165191

192+
#[inline]
166193
pub fn from_rgb(rgb: [u8; 3]) -> Self {
167194
Self {
168195
r: rgb[0],
@@ -172,10 +199,13 @@ impl Color {
172199
}
173200
}
174201

202+
#[must_use]
203+
#[inline]
175204
pub fn into_rgb(self) -> [u8; 3] {
176205
[self.r, self.g, self.b]
177206
}
178207

208+
#[inline]
179209
pub fn from_rgba(rgba: [u8; 4]) -> Self {
180210
Self {
181211
r: rgba[0],
@@ -185,18 +215,26 @@ impl Color {
185215
}
186216
}
187217

218+
#[must_use]
219+
#[inline]
188220
pub fn into_rgba(self) -> [u8; 4] {
189221
[self.r, self.g, self.b, self.a]
190222
}
191223

224+
#[must_use]
225+
#[inline]
192226
pub fn into_hex6(self) -> String {
193227
format!("{:02x}{:02x}{:02x}", self.r, self.g, self.b)
194228
}
195229

230+
#[must_use]
231+
#[inline]
196232
pub fn into_hex8(self) -> String {
197233
format!("{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a)
198234
}
199235

236+
#[must_use]
237+
#[inline]
200238
pub fn from_hsl_f32(hsl: [f32; 3]) -> Self {
201239
// solution from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
202240
let (mut h, s, l) = (hsl[0], hsl[1] / 100.0, hsl[2] / 100.0);
@@ -223,6 +261,8 @@ impl Color {
223261
}
224262
}
225263

264+
#[must_use]
265+
#[inline]
226266
pub fn from_hsl_f64(hsl: [f64; 3]) -> Self {
227267
// solution from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
228268
let (mut h, s, l) = (hsl[0], hsl[1] / 100.0, hsl[2] / 100.0);
@@ -249,6 +289,8 @@ impl Color {
249289
}
250290
}
251291

292+
#[must_use]
293+
#[inline]
252294
pub fn from_hsla_f32(hsla: [f32; 4]) -> Self {
253295
// solution from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
254296
let (h, s, l) = (
@@ -278,6 +320,8 @@ impl Color {
278320
}
279321
}
280322

323+
#[must_use]
324+
#[inline]
281325
pub fn from_hsla_f64(hsla: [f64; 4]) -> Self {
282326
// solution from https://www.rapidtables.com/convert/color/hsl-to-rgb.html
283327
let (h, s, l) = (
@@ -307,6 +351,8 @@ impl Color {
307351
}
308352
}
309353

354+
#[must_use]
355+
#[inline]
310356
pub fn into_hsl_f32(self) -> [f32; 3] {
311357
// solution from https://www.rapidtables.com/convert/color/rgb-to-hsl.html
312358
let r_prime = (self.r as f32) / 255.0;
@@ -341,6 +387,8 @@ impl Color {
341387
[h, s, l]
342388
}
343389

390+
#[must_use]
391+
#[inline]
344392
pub fn into_hsl_f64(self) -> [f64; 3] {
345393
// solution from https://www.rapidtables.com/convert/color/rgb-to-hsl.html
346394
let r_prime = (self.r as f64) / 255.0;
@@ -375,6 +423,8 @@ impl Color {
375423
[h, s, l]
376424
}
377425

426+
#[must_use]
427+
#[inline]
378428
pub fn into_hsla_f32(self) -> [f32; 4] {
379429
// solution from https://www.rapidtables.com/convert/color/rgb-to-hsl.html
380430
let r_prime = (self.r as f32) / 255.0;
@@ -409,6 +459,8 @@ impl Color {
409459
[h, s, l, (self.a as f32) / 255.0]
410460
}
411461

462+
#[must_use]
463+
#[inline]
412464
pub fn into_hsla_f64(self) -> [f64; 4] {
413465
// solution from https://www.rapidtables.com/convert/color/rgb-to-hsl.html
414466
let r_prime = (self.r as f64) / 255.0;
@@ -444,6 +496,7 @@ impl Color {
444496
}
445497

446498
// encode linear light -> sRGB (D65, IEC 61966-2-1)
499+
#[must_use]
447500
#[inline]
448501
pub fn from_linear<T: Float>(lin: [T; 4]) -> Self {
449502
Self {
@@ -458,6 +511,7 @@ impl Color {
458511
}
459512

460513
// decode sRGB -> linear light (D65, IEC 61966-2-1)
514+
#[must_use]
461515
#[inline]
462516
pub fn into_linear<T: Float>(self) -> [T; 4] {
463517
[
@@ -468,6 +522,8 @@ impl Color {
468522
]
469523
}
470524

525+
#[must_use]
526+
#[inline]
471527
pub fn from_oklab<T: Float>(ok: [T; 3]) -> Self {
472528
// source: https://bottosson.github.io/posts/oklab/
473529

@@ -520,6 +576,8 @@ impl Color {
520576
])
521577
}
522578

579+
#[must_use]
580+
#[inline]
523581
pub fn into_oklab<T: Float>(self) -> [T; 3] {
524582
// source: https://bottosson.github.io/posts/oklab/
525583

src/traits/float.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ pub trait Float: Copy + PartialOrd {
2828
fn mul(self, rhs: Self) -> Self;
2929
fn div(self, rhs: Self) -> Self;
3030

31+
fn abs(self) -> Self;
3132
fn powf(self, e: Self) -> Self;
3233
fn cbrt(self) -> Self;
34+
fn rem_euclid(self, rhs: Self) -> Self;
35+
3336
/// Clamp this Float between 0.0 and 1.0
37+
#[inline]
3438
fn clamp01(self) -> Self {
3539
clamp_generic(self, Self::ZERO, Self::ONE)
3640
}
@@ -70,6 +74,10 @@ impl Float for f32 {
7074
self / rhs
7175
}
7276

77+
#[inline]
78+
fn abs(self) -> Self {
79+
self.abs()
80+
}
7381
#[inline]
7482
fn powf(self, e: Self) -> Self {
7583
f32::powf(self, e)
@@ -78,6 +86,11 @@ impl Float for f32 {
7886
fn cbrt(self) -> Self {
7987
f32::cbrt(self)
8088
}
89+
#[inline]
90+
fn rem_euclid(self, rhs: Self) -> Self {
91+
let r = self % rhs;
92+
if r < 0.0 { r + rhs.abs() } else { r }
93+
}
8194
}
8295

8396
impl Float for f64 {
@@ -114,6 +127,10 @@ impl Float for f64 {
114127
self / rhs
115128
}
116129

130+
#[inline]
131+
fn abs(self) -> Self {
132+
self.abs()
133+
}
117134
#[inline]
118135
fn powf(self, e: Self) -> Self {
119136
f64::powf(self, e)
@@ -122,4 +139,9 @@ impl Float for f64 {
122139
fn cbrt(self) -> Self {
123140
f64::cbrt(self)
124141
}
142+
#[inline]
143+
fn rem_euclid(self, rhs: Self) -> Self {
144+
let r = self % rhs;
145+
if r < 0.0 { r + rhs.abs() } else { r }
146+
}
125147
}

0 commit comments

Comments
 (0)