1package pattern
2
3import (
4 "image"
5 "math"
6 "testing"
7)
8
9func TestPatchesTileTheSurface(t *testing.T) {
10 for _, mode := range []string{"sdr", "hdr"} {
11 for _, size := range [][2]int{{640, 360}, {641, 361}} {
12 w, h := size[0], size[1]
13 patches := Patches(mode, w, h)
14 if len(patches) != Columns*Rows {
15 t.Fatalf("%s %dx%d: %d patches", mode, w, h, len(patches))
16 }
17 covered := make([]int, w*h)
18 for _, p := range patches {
19 if !p.Rect.In(image.Rect(0, 0, w, h)) || p.Rect.Empty() {
20 t.Fatalf("%s %dx%d: bad rect %v", mode, w, h, p.Rect)
21 }
22 for y := p.Rect.Min.Y; y < p.Rect.Max.Y; y++ {
23 for x := p.Rect.Min.X; x < p.Rect.Max.X; x++ {
24 covered[y*w+x]++
25 }
26 }
27 }
28 for i, n := range covered {
29 if n != 1 {
30 t.Fatalf("%s %dx%d: pixel (%d,%d) covered %d times", mode, w, h, i%w, i/w, n)
31 }
32 }
33 }
34 }
35}
36
37func TestPatchesModes(t *testing.T) {
38 if Patches("bogus", 640, 360) != nil {
39 t.Fatal("unknown mode must yield nil")
40 }
41 sdr := Patches("sdr", 600, 200)
42 if sdr[5].SRGB != [3]uint8{255, 128, 0} || sdr[11].SRGB != [3]uint8{255, 255, 0} {
43 t.Fatalf("sdr patches: %v", sdr)
44 }
45 hdr := Patches("hdr", 600, 200)
46 if hdr[3].Nits != [3]float64{203, 203, 203} || hdr[5].Nits != [3]float64{1000, 1000, 1000} {
47 t.Fatalf("hdr neutrals: %v", hdr)
48 }
49 // BT.709 red at 203 nits in BT.2020.
50 want := [3]float64{0.627404 * 203, 0.069097 * 203, 0.016391 * 203}
51 for i, v := range hdr[9].Nits {
52 if math.Abs(v-want[i]) > 1e-9 {
53 t.Fatalf("709 red = %v, want %v", hdr[9].Nits, want)
54 }
55 }
56 if c := sdr[0].Centre(); c != image.Pt(50, 50) {
57 t.Fatalf("centre = %v", c)
58 }
59}
60
61func TestPQEncode(t *testing.T) {
62 if v := PQEncode(203); math.Abs(v-0.5807) > 1e-4 {
63 t.Fatalf("PQEncode(203) = %v", v)
64 }
65 if v := PQEncode(10000); math.Abs(v-1) > 1e-4 {
66 t.Fatalf("PQEncode(10000) = %v", v)
67 }
68 if PQEncode(0) != 0 || PQEncode(-1) != 0 {
69 t.Fatal("PQEncode must clamp to zero")
70 }
71}
72
73func TestPQDecodeInvertsPQEncode(t *testing.T) {
74 for _, nits := range []float64{0.001, 0.02, 1, 10, 100, 203, 400, 1000, 4000, 10000} {
75 if got := PQDecode(PQEncode(nits)); math.Abs(got-nits) > nits*1e-9+1e-9 {
76 t.Errorf("PQDecode(PQEncode(%v)) = %v", nits, got)
77 }
78 }
79 for _, v := range []float64{0, 0.0313, 0.1508, 0.5807, 0.75, 1} {
80 if got := PQEncode(PQDecode(v)); math.Abs(got-v) > 1e-9 {
81 t.Errorf("PQEncode(PQDecode(%v)) = %v", v, got)
82 }
83 }
84 if PQDecode(0) != 0 || PQDecode(-1) != 0 || math.Abs(PQDecode(1)-10000) > 1e-6 {
85 t.Fatal("PQDecode endpoints")
86 }
87 // The compositor's near-black residue (0.0313 PQ) is a few hundredths of a nit.
88 if v := PQDecode(0.0313); v < 0.01 || v > 0.05 {
89 t.Fatalf("PQDecode(0.0313) = %v nits", v)
90 }
91}
92
93func TestSRGBToLinear(t *testing.T) {
94 if SRGBToLinear(0) != 0 || math.Abs(SRGBToLinear(1)-1) > 1e-12 {
95 t.Fatal("endpoints")
96 }
97 if v := SRGBToLinear(128.0 / 255); math.Abs(v-0.2158605) > 1e-6 {
98 t.Fatalf("SRGBToLinear(128/255) = %v", v)
99 }
100}