2025-03-06 13:01:21 -05:00

104 lines
4.5 KiB
Go

package helper
import (
"image/color"
"math"
)
func XYZtoLMS(x, y, z float64) (_, _, _ float64) {
// https://bottosson.github.io/posts/oklab/
//
// OkLab first defines a transform from xyz to an intermediate space:
//
// [+0.8189330101, +0.3618667424, -0.1288597137]
// [+0.0329845436, +0.9293118715, +0.0361456387]
// [+0.0482003018, +0.2643662691, +0.633851707 ]
//
// Inverse:
//
// [+1.2270138511035210261251539010893, -0.55779998065182223833890733780747, +0.28125614896646780760667886762980 ]
// [-0.040580178423280593980748617561551, +1.1122568696168301049956590765194, -0.071676678665601200581102747142872]
// [-0.076381284505706892872271894590358, -0.42148197841801273056818761141308, +1.5861632204407947575338479416771 ]
return +0.8189330101*x + 0.3618667424*y - 0.1288597137*z,
0.0329845436*x + 0.9293118715*y + 0.0361456387*z,
0.0482003018*x + 0.2643662691*y + 0.633851707*z
}
func LMStoXYZ(l, m, s float64) (_, _, _ float64) {
return 1.2270138511035210261251539010893*l - 0.55779998065182223833890733780747*m + 0.28125614896646780760667886762980*s,
-0.040580178423280593980748617561551*l + 1.1122568696168301049956590765194*m - 0.071676678665601200581102747142872*s,
-0.076381284505706892872271894590358*l - 0.42148197841801273056818761141308*m + 1.5861632204407947575338479416771*s
}
func LRGBtoLMS(r, g, b float64) (_, _, _ float64) {
// we can combine the LRGB to D65 CIE XYZ transform and the XYZ to LMS transform above to go straight from LRGB to LMS:
//
// [+0.41217385032507, +0.5362974607032, +0.05146302925248]
// [+0.21187214048845, +0.6807476834212, +0.10740645682645]
// [+0.08831541121808, +0.2818663070584, +0.63026344660742]
//
// Inverse:
//
// [+4.0767584135565013494237930518854, -3.3072279873944731418619352916485, +0.23072145994488563247301883404900]
// [-1.2681810851624033989047813181437, +2.6092932102856398573991970933594, -0.34111211654775355696796160418220]
// [-0.0040984077180314400491332639337372, -0.70350366010241732765095902557887, +1.7068604529788013559365593912662]
return +0.41217385032507*r + 0.5362974607032*g + 0.05146302925248*b,
0.21187214048845*r + 0.6807476834212*g + 0.10740645682645*b,
0.08831541121808*r + 0.2818663070584*g + 0.63026344660742*b
}
func LMStoLRGB(l, m, s float64) (_, _, _ float64) {
return +4.0767584135565013494237930518854*l - 3.3072279873944731418619352916485*m + 0.23072145994488563247301883404900*s,
-1.2681810851624033989047813181437*l + 2.6092932102856398573991970933594*m - 0.34111211654775355696796160418220*s,
-0.0040984077180314400491332639337372*l - 0.70350366010241732765095902557887*m + 1.7068604529788013559365593912662*s
}
func LMStoOkLab(l, m, s float64) (_, _, _ float64) {
// After a non-linear transformation (cube root), OkLab applies a second matrix:
//
// [+0.2104542553, +0.793617785, -0.0040720468]
// [+1.9779984951, -2.428592205, +0.4505937099]
// [+0.0259040371, +0.7827717662, -0.808675766 ]
//
// Inverse:
//
// [+0.99999999845051981426207542502031, +0.39633779217376785682345989261573, +0.21580375806075880342314146183004 ]
// [+1.0000000088817607767160752456705, -0.10556134232365634941095687705472, -0.063854174771705903405254198817796]
// [+1.0000000546724109177012928651534, -0.089484182094965759689052745863391, -1.2914855378640917399489287529148 ]
lP, mP, sP := math.Cbrt(l), math.Cbrt(m), math.Cbrt(s)
return 0.2104542553*lP + 0.793617785*mP - 0.0040720468*sP,
1.9779984951*lP - 2.428592205*mP + 0.4505937099*sP,
0.0259040371*lP + 0.7827717662*mP - 0.808675766*sP
}
func cube(v float64) float64 {
return v * v * v
}
func OkLabToLMS(l, a, b float64) (_, _, _ float64) {
return cube(0.99999999845051981426207542502031*l + 0.39633779217376785682345989261573*a + 0.21580375806075880342314146183004*b),
cube(1.0000000088817607767160752456705*l - 0.10556134232365634941095687705472*a - 0.063854174771705903405254198817796*b),
cube(1.0000000546724109177012928651534*l - 0.089484182094965759689052745863391*a - 1.2914855378640917399489287529148*b)
}
func ColorToNOkLabA(c color.Color) (lightness, chromaA, chromaB, a float64) {
switch c := c.(type) {
case interface {
NOkLabA() (lightness, chromaA, chromaB, a float64)
}:
return c.NOkLabA()
case interface{ NXYZA() (x, y, z, a float64) }:
x, y, z, a := c.NXYZA()
lightness, chromaA, chromaB = LMStoOkLab(XYZtoLMS(x, y, z))
return lightness, chromaA, chromaB, a
default:
r, g, b, a := ColorToNLRGBA(c)
lightness, chromaA, chromaB = LMStoOkLab(LRGBtoLMS(r, g, b))
return lightness, chromaA, chromaB, a
}
}