Added OKLCH color space
This commit is contained in:
parent
13f4c5fb0f
commit
30838ca0b6
@ -13,7 +13,23 @@ func BenchmarkRGB(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkFprint(b *testing.B) {
|
func BenchmarkLCH(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
color.LCH(0.5, 0.5, 0.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFprintColorized(b *testing.B) {
|
||||||
|
color.Terminal = true
|
||||||
|
c := color.RGB(1.0, 1.0, 1.0)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
c.Fprint(io.Discard, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFprintRaw(b *testing.B) {
|
||||||
|
color.Terminal = false
|
||||||
c := color.RGB(1.0, 1.0, 1.0)
|
c := color.RGB(1.0, 1.0, 1.0)
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
|
12
Color.go
12
Color.go
@ -5,18 +5,18 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Component is a type definition for the data type of a single color component.
|
// Value is a type definition for the data type of a single color component.
|
||||||
type Component float32
|
type Value = float64
|
||||||
|
|
||||||
// Color represents an RGB color.
|
// Color represents an RGB color.
|
||||||
type Color struct {
|
type Color struct {
|
||||||
R Component
|
R Value
|
||||||
G Component
|
G Value
|
||||||
B Component
|
B Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// RGB creates a new color with red, green and blue values in the range of 0.0 to 1.0.
|
// RGB creates a new color with red, green and blue values in the range of 0.0 to 1.0.
|
||||||
func RGB(r Component, g Component, b Component) Color {
|
func RGB(r Value, g Value, b Value) Color {
|
||||||
return Color{r, g, b}
|
return Color{r, g, b}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package color_test
|
package color_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -8,36 +9,73 @@ import (
|
|||||||
"git.akyoto.dev/go/color"
|
"git.akyoto.dev/go/color"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestColors(t *testing.T) {
|
|
||||||
color.Terminal = true
|
|
||||||
testColors(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoColors(t *testing.T) {
|
func TestNoColors(t *testing.T) {
|
||||||
color.Terminal = false
|
color.Terminal = false
|
||||||
testColors(t)
|
testRGBColors(t)
|
||||||
|
testLCHColors(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testColors(t *testing.T) {
|
func TestColors(t *testing.T) {
|
||||||
colors := map[string]color.Color{
|
color.Terminal = true
|
||||||
"black": color.RGB(0.0, 0.0, 0.0),
|
testRGBColors(t)
|
||||||
"white": color.RGB(1.0, 1.0, 1.0),
|
testLCHColors(t)
|
||||||
"red": color.RGB(1.0, 0.0, 0.0),
|
}
|
||||||
"green": color.RGB(0.0, 1.0, 0.0),
|
|
||||||
"blue": color.RGB(0.0, 0.0, 1.0),
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, value := range colors {
|
func TestRainbow(t *testing.T) {
|
||||||
assert.True(t, value.R >= 0.0)
|
color.Terminal = true
|
||||||
assert.True(t, value.G >= 0.0)
|
|
||||||
assert.True(t, value.B >= 0.0)
|
|
||||||
|
|
||||||
assert.True(t, value.R <= 1.0)
|
for chroma := range 5 {
|
||||||
assert.True(t, value.G <= 1.0)
|
for lightness := range 21 {
|
||||||
assert.True(t, value.B <= 1.0)
|
for hue := range 80 {
|
||||||
|
c := color.LCH(color.Value(lightness)*0.05, color.Value(chroma)*0.05, color.Value(hue)*4.5)
|
||||||
|
c.Print("█")
|
||||||
|
}
|
||||||
|
|
||||||
value.Fprint(io.Discard, name)
|
fmt.Println()
|
||||||
value.Print(name)
|
}
|
||||||
value.Println(name)
|
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testRGBColors(t *testing.T) {
|
||||||
|
rgbColors := map[string]color.Color{
|
||||||
|
"RGB.black": color.RGB(0.0, 0.0, 0.0),
|
||||||
|
"RGB.white": color.RGB(1.0, 1.0, 1.0),
|
||||||
|
"RGB.red": color.RGB(1.0, 0.0, 0.0),
|
||||||
|
"RGB.green": color.RGB(0.0, 1.0, 0.0),
|
||||||
|
"RGB.blue": color.RGB(0.0, 0.0, 1.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, c := range rgbColors {
|
||||||
|
testColor(t, c, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testLCHColors(t *testing.T) {
|
||||||
|
lchColors := map[string]color.Color{
|
||||||
|
"LCH.black": color.LCH(0.0, 0.0, 0.0),
|
||||||
|
"LCH.white": color.LCH(1.0, 0.0, 0.0),
|
||||||
|
"LCH.red": color.LCH(0.6, 0.5, 20.0),
|
||||||
|
"LCH.green": color.LCH(0.6, 0.5, 161.0),
|
||||||
|
"LCH.blue": color.LCH(0.6, 0.5, 240.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, c := range lchColors {
|
||||||
|
testColor(t, c, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testColor(t *testing.T, c color.Color, name string) {
|
||||||
|
assert.True(t, c.R >= 0.0)
|
||||||
|
assert.True(t, c.G >= 0.0)
|
||||||
|
assert.True(t, c.B >= 0.0)
|
||||||
|
|
||||||
|
assert.True(t, c.R <= 1.0)
|
||||||
|
assert.True(t, c.G <= 1.0)
|
||||||
|
assert.True(t, c.B <= 1.0)
|
||||||
|
|
||||||
|
c.Fprint(io.Discard, name)
|
||||||
|
c.Print(name)
|
||||||
|
c.Println(name)
|
||||||
|
}
|
||||||
|
56
LCH.go
Normal file
56
LCH.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package color
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LCH represents a color using lightness, chroma and hue (oklch).
|
||||||
|
func LCH(lightness Value, chroma Value, hue Value) Color {
|
||||||
|
hue *= math.Pi / 180.0
|
||||||
|
a := chroma * math.Cos(hue)
|
||||||
|
b := chroma * math.Sin(hue)
|
||||||
|
|
||||||
|
r, g, b := labToRGB(lightness, a, b)
|
||||||
|
|
||||||
|
r = gammaCorrection(r)
|
||||||
|
g = gammaCorrection(g)
|
||||||
|
b = gammaCorrection(b)
|
||||||
|
|
||||||
|
return Color{r, g, b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LAB to linear sRGB
|
||||||
|
// Source: https://bottosson.github.io/posts/oklab/
|
||||||
|
func labToRGB(lightness Value, a Value, b Value) (Value, Value, Value) {
|
||||||
|
l := lightness + 0.3963377773761749*a + 0.21580375730991364*b
|
||||||
|
m := lightness - 0.10556134581565857*a - 0.0638541728258133*b
|
||||||
|
s := lightness - 0.08948418498039246*a - 1.2914855480194092*b
|
||||||
|
|
||||||
|
l = l * l * l
|
||||||
|
m = m * m * m
|
||||||
|
s = s * s * s
|
||||||
|
|
||||||
|
red := 4.0767416621*l - 3.3077115913*m + 0.2309699292*s
|
||||||
|
green := -1.2684380046*l + 2.6097574011*m - 0.3413193965*s
|
||||||
|
blue := -0.0041960863*l - 0.7034186147*m + 1.7076147010*s
|
||||||
|
|
||||||
|
return red, green, blue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gamma correction
|
||||||
|
// Source: IEC 61966-2-2
|
||||||
|
func gammaCorrection(x Value) Value {
|
||||||
|
if x < 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if x <= 0.0031308 {
|
||||||
|
return 12.92 * x
|
||||||
|
}
|
||||||
|
|
||||||
|
if x < 1.0 {
|
||||||
|
return 1.055*math.Pow(x, 1/2.4) - 0.055
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0
|
||||||
|
}
|
14
README.md
14
README.md
@ -4,7 +4,9 @@ Adds color to your terminal output.
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Truecolor output
|
- RGB color space
|
||||||
|
- OKLCH color space
|
||||||
|
- Truecolor terminal output
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -22,15 +24,19 @@ red.Println("red text")
|
|||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
```
|
```
|
||||||
PASS: TestColor
|
PASS: TestNoColors
|
||||||
|
PASS: TestColors
|
||||||
|
PASS: TestRainbow
|
||||||
coverage: 100.0% of statements
|
coverage: 100.0% of statements
|
||||||
```
|
```
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
```
|
```
|
||||||
BenchmarkRGB-12 1000000000 0.3139 ns/op 0 B/op 0 allocs/op
|
BenchmarkRGB-12 1000000000 0.3132 ns/op 0 B/op 0 allocs/op
|
||||||
BenchmarkFprint-12 25758495 45.38 ns/op 0 B/op 0 allocs/op
|
BenchmarkLCH-12 11214220 107.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkFprintColorized-12 6356535 188.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkFprintRaw-12 27374659 43.76 ns/op 0 B/op 0 allocs/op
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
Loading…
Reference in New Issue
Block a user