1
2
3
4
5 package moreland
6
7 import (
8 "image/color"
9 "math"
10 "strings"
11 "testing"
12
13 "golang.org/x/exp/rand"
14
15 "gonum.org/v1/plot/palette"
16 )
17
18
19
20
21
22 const bitTolerance = 1.0 / 256.0 * 65535.0
23
24 func TestInterpolateMSHDiverging(t *testing.T) {
25 type test struct {
26 start, end msh
27 convergeM, scalar, convergePoint float64
28 result msh
29 }
30 tests := []test{
31 {
32 start: msh{M: 80, S: 1.08, H: -1.1},
33 end: msh{M: 80, S: 1.08, H: 0.5},
34 convergeM: 88,
35 convergePoint: 0.5,
36 scalar: 0.125,
37 result: msh{M: 82, S: 0.81, H: -1.2402896406131008},
38 },
39 {
40 start: msh{M: 80, S: 1.08, H: -1.1},
41 end: msh{M: 80, S: 1.08, H: 0.5},
42 convergeM: 88,
43 convergePoint: 0.5,
44 scalar: 0.5,
45 result: msh{M: 88, S: 0, H: 0},
46 },
47 {
48 start: msh{M: 80, S: 1.08, H: -1.1},
49 end: msh{M: 80, S: 1.08, H: 0.5},
50 convergeM: 88,
51 convergePoint: 0.5,
52 scalar: 0.75,
53 result: msh{M: 84, S: 0.54, H: 0.7805792812262012},
54 },
55 {
56 start: msh{M: 80, S: 1.08, H: -1.1},
57 end: msh{M: 80, S: 1.08, H: 0.5},
58 convergeM: 88,
59 convergePoint: 0.75,
60 scalar: 0.7499999999999999,
61 result: msh{M: 88, S: 1.1990408665951691e-16, H: -1.6611585624524023},
62 },
63 {
64 start: msh{M: 80, S: 1.08, H: -1.1},
65 end: msh{M: 80, S: 1.08, H: 0.5},
66 convergeM: 88,
67 convergePoint: 0.75,
68 scalar: 0.75,
69 result: msh{M: 88, S: 0, H: 0},
70 },
71 {
72 start: msh{M: 80, S: 1.08, H: -1.1},
73 end: msh{M: 80, S: 1.08, H: 0.5},
74 convergeM: 88,
75 convergePoint: 0.75,
76 scalar: 0.7500000000000001,
77 result: msh{M: 88, S: 2.3980817331903383e-16, H: 1.0611585624524023},
78 },
79 }
80 for i, test := range tests {
81 p := newSmoothDiverging(test.start, test.end, test.convergeM)
82 p.SetMin(0)
83 p.SetMax(1)
84 result := p.(*smoothDiverging).interpolateMSHDiverging(test.scalar, test.convergePoint)
85 if result != test.result {
86 t.Errorf("test %d: expected %v; got %v", i, test.result, result)
87 }
88 }
89 }
90
91 func TestSmoothBlueRed(t *testing.T) {
92 p := SmoothBlueRed()
93 wantP := []color.NRGBA{
94 {59, 76, 192, 255},
95 {68, 90, 204, 255},
96 {77, 104, 215, 255},
97 {87, 117, 225, 255},
98 {98, 130, 234, 255},
99 {108, 142, 241, 255},
100 {119, 154, 247, 255},
101 {130, 165, 251, 255},
102 {141, 176, 254, 255},
103 {152, 185, 255, 255},
104 {163, 194, 255, 255},
105 {174, 201, 253, 255},
106 {184, 208, 249, 255},
107 {194, 213, 244, 255},
108 {204, 217, 238, 255},
109 {213, 219, 230, 255},
110 {221, 221, 221, 255},
111 {229, 216, 209, 255},
112 {236, 211, 197, 255},
113 {241, 204, 185, 255},
114 {245, 196, 173, 255},
115 {247, 187, 160, 255},
116 {247, 177, 148, 255},
117 {247, 166, 135, 255},
118 {244, 154, 123, 255},
119 {241, 141, 111, 255},
120 {236, 127, 99, 255},
121 {229, 112, 88, 255},
122 {222, 96, 77, 255},
123 {213, 80, 66, 255},
124 {203, 62, 56, 255},
125 {192, 40, 47, 255},
126 {180, 4, 38, 255},
127 }
128 c := p.Palette(33)
129 if len(c.Colors()) != len(wantP) {
130 t.Errorf("length doesn't match: %d != %d", len(c.Colors()), len(wantP))
131 }
132 for i, c := range c.Colors() {
133 w := wantP[i]
134 if !similar(w, c, bitTolerance) {
135 t.Errorf("%d: want %+v but have %+v", i, w, c)
136 }
137 }
138 }
139
140 func TestSmoothCoolWarm(t *testing.T) {
141 type test struct {
142 start [3]float64
143 f func(int) palette.Palette
144 end [3]float64
145 }
146 tests := []test{
147 {[3]float64{0.230, 0.299, 0.754}, SmoothBlueRed().Palette, [3]float64{0.706, 0.016, 0.150}},
148 {[3]float64{0.436, 0.308, 0.631}, SmoothPurpleOrange().Palette, [3]float64{0.759, 0.334, 0.046}},
149 {[3]float64{0.085, 0.532, 0.201}, SmoothGreenPurple().Palette, [3]float64{0.436, 0.308, 0.631}},
150 {[3]float64{0.217, 0.525, 0.910}, SmoothBlueTan().Palette, [3]float64{0.677, 0.492, 0.093}},
151 {[3]float64{0.085, 0.532, 0.201}, SmoothGreenRed().Palette, [3]float64{0.758, 0.214, 0.233}},
152 }
153 midPoint := [3]float64{0.865, 0.865, 0.865}
154
155 for i, test := range tests {
156 c := test.f(3).Colors()
157 testRGB(t, i, "start", c[0], test.start)
158 testRGB(t, i, "mid", c[1], midPoint)
159 testRGB(t, i, "end", c[2], test.end)
160 }
161 }
162
163 func fracToByte(v float64) uint8 {
164 return uint8(v*255 + 0.5)
165 }
166
167 func testRGB(t *testing.T, i int, label string, c1 color.Color, c2 [3]float64) {
168 c3 := color.NRGBA{
169 R: fracToByte(c2[0]),
170 G: fracToByte(c2[1]),
171 B: fracToByte(c2[2]),
172 A: 255,
173 }
174 if !similar(c1, c3, bitTolerance) {
175 t.Errorf("%d %s: want %+v but have %+v", i, label, c1, c3)
176 }
177 }
178
179 func TestSmoothDiverging_At(t *testing.T) {
180
181 start := msh{M: 80, S: 1.08, H: -1.1}
182 end := msh{M: 80, S: 1.08, H: 0.5}
183 p := newSmoothDiverging(start, end, 88)
184 p.SetMax(2)
185 p.SetMin(-1)
186 scalar := -1 + 3*0.125
187 rgb, err := p.At(scalar)
188 if err != nil {
189 t.Error(err)
190 }
191
192
193 want := color.NRGBA{R: 98, G: 130, B: 234, A: 255}
194 if !similar(want, rgb, bitTolerance) {
195 t.Errorf("have %+v, want %+v", rgb, want)
196 }
197 }
198
199 func BenchmarkSmoothDiverging_At(b *testing.B) {
200 p := SmoothBlueRed()
201 p.SetMax(1.0000000001)
202 rand.Seed(1)
203 for i := 0; i < b.N; i++ {
204 if _, err := p.At(rand.Float64()); err != nil {
205 b.Fatal(err)
206 }
207 }
208 }
209
210 func TestSmoothDiverging_Range(t *testing.T) {
211 p := SmoothBlueRed()
212 if _, err := p.At(0); !strings.Contains(err.Error(), "max == min") {
213 t.Errorf("should have 'max == min' error")
214 }
215 p.SetMax(1)
216 vals := []float64{-1, 0, 1, 2, math.Inf(1), math.Inf(-1), math.NaN()}
217 errs := []error{palette.ErrUnderflow, nil, nil, palette.ErrOverflow,
218 palette.ErrOverflow, palette.ErrUnderflow, palette.ErrNaN}
219 for i, v := range vals {
220 _, err := p.At(v)
221 wantErr := errs[i]
222 if wantErr == nil && err != nil {
223 t.Errorf("val %g: want no error but have %v", v, err)
224 } else if wantErr != nil && err == nil {
225 t.Errorf("val %g: want error but have no error", v)
226 } else if wantErr != err {
227 t.Errorf("val %g: want error %v but have %v", v, wantErr, err)
228 }
229 }
230 p.SetMin(2)
231 if _, err := p.At(0); !strings.Contains(err.Error(), "< min") {
232 t.Errorf("should have 'max < min' error")
233 }
234 }
235
236
237
238 func similar(a, b color.Color, tolerance float64) bool {
239 aR, aG, aB, aA := a.RGBA()
240 bR, bG, bB, bA := b.RGBA()
241 if math.Abs(float64(bR)-float64(aR)) > tolerance ||
242 math.Abs(float64(bG)-float64(aG)) > tolerance ||
243 math.Abs(float64(bB)-float64(aB)) > tolerance ||
244 math.Abs(float64(bA)-float64(aA)) > tolerance {
245 return false
246 }
247 return true
248 }
249
View as plain text