color/internal/helper/gamma_test.go

141 lines
3.6 KiB
Go

package helper
import (
"fmt"
"math"
"testing"
)
func TestLinearize(t *testing.T) {
tests := []struct {
value uint32
want float64
}{
// the minimum and maximum legal values should map to 0 and 1, respectively.
{0x0000, 0.0},
{0xffff, 1.0},
// check what would be 0x01 in 8-bit sRGB.
// this is below the point where it the function switches from linear to exponential.
{0x0101, 0x1.3e312a36f1977p-12},
// check the midpoint of the gamma curve.
{0x7fff, 0x1.b6577fc57aa37p-03},
// We do support values beyond the maximum legal value, although you probably shouldn't depend on it
// as the converse function will map these to the maximum legal value, creating an asymmetry.
{0x10000, 0x1.000246626b604p+00},
{math.MaxUint32, 0x1.2912a0c535107p+38},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("0x%04x", tt.value), func(t *testing.T) {
if got := Linearize(tt.value); !EqFloat64Fuzzy(got, tt.want) {
t.Errorf("Linearize(0x%04x) = %x: want %x", tt.value, got, tt.want)
}
})
}
t.Run("monotonically increasing", func(t *testing.T) {
for i, prev := uint32(1), Linearize(0); i < 0x10000; i++ {
got := Linearize(i)
if got <= prev {
t.Errorf("Linearize(0x%04x) = %x; want > %x", i, got, prev)
}
prev = got
}
})
}
func TestLinearizeF(t *testing.T) {
// the positive finites were indirectly tested by TestLinearize, so we'll just test the
// negative and non-finite inputs.
tests := []struct {
value float64
want float64
}{
{-1, -1 / 12.923210180787861094641554898407},
{0, 0},
{1, 1},
{2, 4.9538457515920408157613451180477},
{math.Inf(-1), math.Inf(-1)},
{math.Inf(1), math.Inf(1)},
{math.NaN(), math.NaN()},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%x", tt.value), func(t *testing.T) {
if got := LinearizeF(tt.value); !EqFloat64Fuzzy(got, tt.want) {
t.Errorf("LinearizeF(%x) = %x: want %x", tt.value, got, tt.want)
}
})
}
}
func TestDelinearize(t *testing.T) {
tests := []struct {
value float64
want uint32
}{
// make sure clamping to legal values is happening.
{-1, 0x0000},
{2, 0xffff},
// again with the next values below 0 and above 1.
{math.Nextafter(0, math.Inf(-1)), 0},
{math.Nextafter(1, math.Inf(1)), 0xffff},
// and lastly, the non-finites.
{math.Inf(-1), 0x0000},
{math.Inf(1), 0xffff},
{math.NaN(), 0x0000},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%x", tt.value), func(t *testing.T) {
if got := Delinearize(tt.value); got != tt.want {
t.Errorf("Delinearize(%x) = 0x%04x: want 0x%04x", tt.value, got, tt.want)
}
})
}
t.Run("lossless conversion of legal values", func(t *testing.T) {
for c := uint32(0); c < 0x10000; c++ {
got := Delinearize(Linearize(c))
if got != c {
t.Errorf("Delinearize(Linearize(0x%04x)) != 0x%04x", c, got)
return
}
}
})
}
func TestDelinearizeF(t *testing.T) {
// values that would have been impossible for Delinearize to represent.
tests := []struct {
value float64
want float64
}{
{-1, -12.923210180787861094641554898407},
{math.Inf(-1), math.Inf(-1)},
{math.Inf(1), math.Inf(1)},
{math.NaN(), math.NaN()},
{2, 1.3532560461493862548965276346496},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("%x", tt.value), func(t *testing.T) {
if got := DelinearizeF(tt.value); !EqFloat64Fuzzy(got, tt.want) {
t.Errorf("DelinearizeF(%x) = %x: want %x", tt.value, got, tt.want)
}
})
}
t.Run("roundtrip conversions", func(t *testing.T) {
for c := -0x1000; c < 0x11000; c++ {
f := float64(c) / 0x10000
got := DelinearizeF(LinearizeF(f))
if !EqFloat64Fuzzy(got, f) {
t.Errorf("DelinearizeF(LinearizeF(%x)) != %x", c, got)
return
}
}
})
}