1import package::maths::{PI_2,PI,FRAC_PI_3}; 3const HUE_GUARD: f32 = 0.0001; 6fn gamma(value: f32) -> f32 { 13 return pow((value + 0.055) / 1.055, 2.4); 18fn inverse_gamma(value: f32) -> f32 { 23 if value <= 0.0031308 { 26 return 1.055 * pow(value, 1.0 / 2.4) - 0.055; 30fn srgb_to_linear_rgb(color: vec3<f32>) -> vec3<f32> { 38fn linear_rgb_to_srgb(color: vec3<f32>) -> vec3<f32> { 40 inverse_gamma(color.x), 41 inverse_gamma(color.y), 42 inverse_gamma(color.z) 46fn linear_to_srgb(color: vec3<f32>) -> vec3<f32> { 47 return linear_rgb_to_srgb(color); 50fn srgb_to_linear(color: vec3<f32>) -> vec3<f32> { 51 return srgb_to_linear_rgb(color); 55fn oklab_to_linear_rgb(c: vec3<f32>) -> vec3<f32> { 56 let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z; 57 let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z; 58 let s_ = c.x - 0.08948418 * c.y - 1.2914855 * c.z; 63 4.0767417 * l - 3.3077116 * m + 0.23096994 * s, 64 -1.268438 * l + 2.6097574 * m - 0.34131938 * s, 65 -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s, 69fn okhsl_to_oklab(okhsl: vec3<f32>) -> vec3<f32> { 75 return vec3<f32>(1.0, 0.0, 0.0); 78 return vec3<f32>(0.0, 0.0, 0.0); 81 let a_ = cos(2.0 * PI * h); 82 let b_ = sin(2.0 * PI * h); 83 let L = okhsl_toe_inv(l); 85 let cs = okhsl_get_Cs(L, a_, b_); 102 k_2 = 1.0 - k_1 / C_mid; 103 C = t * k_1 / (1.0 - k_2 * t); 105 t = (s - mid) / (1.0 - mid); 107 k_1 = (1.0 - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0; 108 k_2 = 1.0 - k_1 / (C_max - C_mid); 109 C = k_0 + t * k_1 / (1.0 - k_2 * t); 112 return vec3<f32>(L, C * a_, C * b_); 115fn okhsl_to_linear_rgb(okhsl: vec3<f32>) -> vec3<f32> { 116 let oklab = okhsl_to_oklab(okhsl); 117 return oklab_to_linear_rgb(oklab); 121fn linear_rgb_to_oklab(c: vec3<f32>) -> vec3<f32> { 122 let l_ = pow(c.x * 0.4122214708 + c.y * 0.5363325363 + c.z * 0.0514459929, 1.0 / 3.0); 123 let m_ = pow(c.x * 0.2119034982 + c.y * 0.6806995451 + c.z * 0.1073969566, 1.0 / 3.0); 124 let s_ = pow(c.x * 0.0883024619 + c.y * 0.2817188376 + c.z * 0.6299787005, 1.0 / 3.0); 126 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, 127 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, 128 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_, 132fn hsl_to_linear_rgb(hsl: vec3<f32>) -> vec3<f32> { 136 let c = (1.0 - abs(2.0 * l - 1.0)) * s; 138 let x = c * (1.0 - abs(hp % 2.0 - 1.0)); 142 if 0.0 <= hp && hp < 1.0 { 143 r = c; g = x; b = 0.0; 144 } else if 1.0 <= hp && hp < 2.0 { 145 r = x; g = c; b = 0.0; 146 } else if 2.0 <= hp && hp < 3.0 { 147 r = 0.0; g = c; b = x; 148 } else if 3.0 <= hp && hp < 4.0 { 149 r = 0.0; g = x; b = c; 150 } else if 4.0 <= hp && hp < 5.0 { 151 r = x; g = 0.0; b = c; 152 } else if 5.0 <= hp && hp < 6.0 { 153 r = c; g = 0.0; b = x; 156 return srgb_to_linear_rgb(vec3(r + m, g + m, b + m)); 159fn hsv_to_linear_rgb(hsva: vec3<f32>) -> vec3<f32> { 160 let h = hsva.x * 6.0; 164 let x = c * (1.0 - abs(h % 2.0 - 1.0)); 169 if 0.0 <= h && h < 1.0 { 170 r = c; g = x; b = 0.0; 171 } else if 1.0 <= h && h < 2.0 { 172 r = x; g = c; b = 0.0; 173 } else if 2.0 <= h && h < 3.0 { 174 r = 0.0; g = c; b = x; 175 } else if 3.0 <= h && h < 4.0 { 176 r = 0.0; g = x; b = c; 177 } else if 4.0 <= h && h < 5.0 { 178 r = x; g = 0.0; b = c; 179 } else if 5.0 <= h && h < 6.0 { 180 r = c; g = 0.0; b = x; 182 return srgb_to_linear_rgb(vec3(r + m, g + m, b + m)); 185fn hwb_to_hsv(hwb: vec3<f32>) -> vec3<f32> { 187 let whiteness = hwb.y; 188 let blackness = hwb.z; 190 let value = 1. - blackness; 193 saturation = 1. - (whiteness / value); 197 return vec3(hue, saturation, value); 200fn hwb_to_linear_rgb(hwb: vec3<f32>) -> vec3<f32> { 201 let hsv = hwb_to_hsv(hwb); 202 return hsv_to_linear_rgb(hsv); 205fn oklch_to_linear_rgb(c: vec3<f32>) -> vec3<f32> { 206 let hue = c.z * PI_2; 207 return oklab_to_linear_rgb(vec3(c.x, c.y * cos(hue), c.y * sin(hue))); 210fn mix_oklch(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 218 } else if b.y < HUE_GUARD { 222 let hue_diff = g - h; 223 if abs(hue_diff) > 0.5 { 225 h += (hue_diff - 1.) * t; 227 h += (hue_diff + 1.) * t; 239fn mix_oklch_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 244 } else if b.y < HUE_GUARD { 248 let hue_diff = g - h; 249 if abs(hue_diff) < 0.5 { 251 h += (hue_diff - 1.) * t; 253 h += (hue_diff + 1.) * t; 265fn mix_hsl(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 273 } else if b.y < HUE_GUARD { 278 fract(h + (fract(g - h + 0.5) - 0.5) * t), 284fn mix_hsl_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 289 } else if b.y < HUE_GUARD { 293 let d = fract(g - h + 0.5) - 0.5; 295 fract(h + (d + select(1., -1., 0. < d)) * t), 301fn mix_okhsl(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 307 } else if b.y < HUE_GUARD { 312 fract(h + (fract(g - h + 0.5) - 0.5) * t), 318fn mix_okhsl_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 324 } else if b.y < HUE_GUARD { 328 let d = fract(g - h + 0.5) - 0.5; 330 fract(h + (d + select(1.0, -1.0, 0.0 < d)) * t), 336fn mix_hsv(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 344 } else if b.y < HUE_GUARD { 348 let hue_diff = g - h; 349 if abs(hue_diff) > 0.5 { 351 h += (hue_diff - 1.0) * t; 353 h += (hue_diff + 1.0) * t; 365fn mix_hsv_long(a: vec3<f32>, b: vec3<f32>, t: f32) -> vec3<f32> { 370 } else if b.y < HUE_GUARD { 374 let hue_diff = g - h; 375 if abs(hue_diff) < 0.5 { 377 h += (hue_diff - 1.0) * t; 379 h += (hue_diff + 1.0) * t; 397fn hsv_to_rgb(hsv: vec3<f32>) -> vec3<f32> { 398 let n = vec3(5.0, 3.0, 1.0); 399 let k = (n + hsv.x / FRAC_PI_3) % 6.0; 400 return hsv.z - hsv.z * hsv.y * max(vec3(0.0), min(k, min(4.0 - k, vec3(1.0)))); 409fn rgb_to_hsv(rgb: vec3<f32>) -> vec3<f32> { 410 let x_max = max(rgb.r, max(rgb.g, rgb.b)); 411 let x_min = min(rgb.r, min(rgb.g, rgb.b)); 412 let c = x_max - x_min; 414 var swizzle = vec3<f32>(0.0); 415 if (x_max == rgb.r) { 416 swizzle = vec3(rgb.gb, 0.0); 417 } else if (x_max == rgb.g) { 418 swizzle = vec3(rgb.br, 2.0); 420 swizzle = vec3(rgb.rg, 4.0); 423 let h = FRAC_PI_3 * fract(((swizzle.x - swizzle.y) / c + swizzle.z) / 6.0) * 6.0; 431 return vec3(h, s, x_max); 437fn okhsl_toe_inv(x: f32) -> f32 { 440 let k_3 = (1.0 + k_1) / (1.0 + k_2); 441 return (x * x + k_1 * x) / (k_3 * (x + k_2)); 444fn okhsl_to_ST(cusp: vec2<f32>) -> vec2<f32> { 446 return vec2<f32>(cusp.y / cusp.x, cusp.y / (1.0 - cusp.x)); 451fn okhsl_get_ST_mid(a_: f32, b_: f32) -> vec2<f32> { 457 + a_ * (-2.1370494 - 10.02301 * b_ 458 + a_ * (-4.2489457 + 5.387708 * b_ + 4.69891 * a_)))); 461 + 1.0 / (1.6132032 - 0.6812438 * b_ 466 + a_ * (0.00299215 - 0.45399568 * b_ - 0.14661872 * a_)))); 468 return vec2<f32>(S, T); 473fn okhsl_compute_max_saturation(a: f32, b: f32) -> f32 { 474 var k0: f32 = 0.0; var k1: f32 = 0.0; var k2: f32 = 0.0; 475 var k3: f32 = 0.0; var k4: f32 = 0.0; 476 var wl: f32 = 0.0; var wm: f32 = 0.0; var ws: f32 = 0.0; 478 if (-1.8817033 * a - 0.8093649 * b > 1.0) { 480 k0 = 1.1908628; k1 = 1.7657673; k2 = 0.5966264; 481 k3 = 0.755152; k4 = 0.5677124; 482 wl = 4.0767417; wm = -3.3077116; ws = 0.23096994; 483 } else if (1.8144411 * a - 1.1944528 * b > 1.0) { 485 k0 = 0.73956515; k1 = -0.45954404; k2 = 0.08285427; 486 k3 = 0.1254107; k4 = 0.14503204; 487 wl = -1.268438; wm = 2.6097574; ws = -0.34131938; 490 k0 = 1.3573365; k1 = -0.00915799; k2 = -1.1513021; 491 k3 = -0.50559606; k4 = 0.00692167; 492 wl = -0.0041960863; wm = -0.7034186; ws = 1.7076147; 495 var S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b; 499 let k_l = 0.39633778 * a + 0.21580376 * b; 500 let k_m = -0.105561346 * a - 0.06385417 * b; 501 let k_s = -0.08948418 * a - 1.2914855 * b; 503 let l_ = 1.0 + S * k_l; 504 let m_ = 1.0 + S * k_m; 505 let s_ = 1.0 + S * k_s; 507 let l = l_ * l_ * l_; 508 let m = m_ * m_ * m_; 509 let s = s_ * s_ * s_; 511 let l_dS = 3.0 * k_l * l_ * l_; 512 let m_dS = 3.0 * k_m * m_ * m_; 513 let s_dS = 3.0 * k_s * s_ * s_; 515 let l_dS2 = 6.0 * k_l * k_l * l_; 516 let m_dS2 = 6.0 * k_m * k_m * m_; 517 let s_dS2 = 6.0 * k_s * k_s * s_; 519 let f = wl * l + wm * m + ws * s; 520 let f1 = wl * l_dS + wm * m_dS + ws * s_dS; 521 let f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2; 523 S = S - f * f1 / (f1 * f1 - 0.5 * f * f2); 527fn okhsl_find_cusp(a: f32, b: f32) -> vec2<f32> { 528 let S_cusp = okhsl_compute_max_saturation(a, b); 529 let rgb_at_max = oklab_to_linear_rgb(vec3<f32>(1.0, S_cusp * a, S_cusp * b)); 530 let L_cusp = pow(1.0 / max(rgb_at_max.r, max(rgb_at_max.g, rgb_at_max.b)), 1.0 / 3.0); 531 return vec2<f32>(L_cusp, L_cusp * S_cusp); 536fn okhsl_find_gamut_intersection(a: f32, b: f32, L1: f32, C1: f32, L0: f32, cusp: vec2<f32>) -> f32 { 541 if (((L1 - L0) * cusp_C - (cusp_L - L0) * C1) <= 0.0) { 543 t = cusp_C * L0 / (C1 * cusp_L + cusp_C * (L0 - L1)); 546 t = cusp_C * (L0 - 1.0) / (C1 * (cusp_L - 1.0) + cusp_C * (L0 - L1)); 554 let k_l = 0.39633778 * a + 0.21580376 * b; 555 let k_m = -0.105561346 * a - 0.06385417 * b; 556 let k_s = -0.08948418 * a - 1.2914855 * b; 558 let l_dt = dL + dC * k_l; 559 let m_dt = dL + dC * k_m; 560 let s_dt = dL + dC * k_s; 562 for (var i = 0; i < 1; i = i + 1) { 563 let L = L0 * (1.0 - t) + t * L1; 566 let l_ = L + C * k_l; 567 let m_ = L + C * k_m; 568 let s_ = L + C * k_s; 570 let l = l_ * l_ * l_; 571 let m = m_ * m_ * m_; 572 let s = s_ * s_ * s_; 574 let ldt = 3.0 * l_dt * l_ * l_; 575 let mdt = 3.0 * m_dt * m_ * m_; 576 let sdt = 3.0 * s_dt * s_ * s_; 578 let ldt2 = 6.0 * l_dt * l_dt * l_; 579 let mdt2 = 6.0 * m_dt * m_dt * m_; 580 let sdt2 = 6.0 * s_dt * s_dt * s_; 582 let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s - 1.0; 583 let r1 = 4.0767417 * ldt - 3.3077116 * mdt + 0.23096994 * sdt; 584 let r2 = 4.0767417 * ldt2 - 3.3077116 * mdt2 + 0.23096994 * sdt2; 586 let u_r = r1 / (r1 * r1 - 0.5 * r * r2); 587 let t_r = select(3.40282347e+38, -r * u_r, u_r >= 0.0); 589 let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s - 1.0; 590 let g1 = -1.268438 * ldt + 2.6097574 * mdt - 0.34131938 * sdt; 591 let g2 = -1.268438 * ldt2 + 2.6097574 * mdt2 - 0.34131938 * sdt2; 593 let u_g = g1 / (g1 * g1 - 0.5 * g * g2); 594 let t_g = select(3.40282347e+38, -g * u_g, u_g >= 0.0); 596 let b_val = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s - 1.0; 597 let b1 = -0.0041960863 * ldt - 0.7034186 * mdt + 1.7076147 * sdt; 598 let b2 = -0.0041960863 * ldt2 - 0.7034186 * mdt2 + 1.7076147 * sdt2; 600 let u_b = b1 / (b1 * b1 - 0.5 * b_val * b2); 601 let t_b = select(3.40282347e+38, -b_val * u_b, u_b >= 0.0); 603 t = t + min(t_r, min(t_g, t_b)); 610fn okhsl_get_Cs(L: f32, a_: f32, b_: f32) -> vec3<f32> { 611 let cusp = okhsl_find_cusp(a_, b_); 612 let C_max = okhsl_find_gamut_intersection(a_, b_, L, 1.0, L, cusp); 613 let ST_max = okhsl_to_ST(cusp); 615 let k = C_max / min(L * ST_max.x, (1.0 - L) * ST_max.y); 617 let ST_mid = okhsl_get_ST_mid(a_, b_); 618 let C_a = L * ST_mid.x; 619 let C_b = (1.0 - L) * ST_mid.y; 620 let C_mid = 0.9 * k * sqrt(sqrt(1.0 / (1.0 / (C_a * C_a * C_a * C_a) + 1.0 / (C_b * C_b * C_b * C_b)))); 623 let C_0_b = (1.0 - L) * 0.8; 624 let C_0 = sqrt(1.0 / (1.0 / (C_0_a * C_0_a) + 1.0 / (C_0_b * C_0_b))); 626 return vec3<f32>(C_0, C_mid, C_max);