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