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 } }