1
2
3
4
5 package vggio
6
7 import (
8 "fmt"
9 "image"
10 "image/color"
11 "image/png"
12 "math"
13 "os"
14 "runtime"
15 "testing"
16
17 "gioui.org/layout"
18 "gioui.org/op"
19 "gonum.org/v1/plot"
20 "gonum.org/v1/plot/cmpimg"
21 "gonum.org/v1/plot/plotter"
22 "gonum.org/v1/plot/vg"
23 "gonum.org/v1/plot/vg/draw"
24 )
25
26 const deltaGio = 0.05
27
28
29
30
31
32
33
34 func init() {
35 if runtime.GOOS != "linux" {
36 return
37 }
38
39 const (
40 w = 20 * vg.Centimeter
41 h = 15 * vg.Centimeter
42 dpi = 96
43 )
44 gtx := layout.Context{
45 Ops: new(op.Ops),
46 Constraints: layout.Exact(image.Pt(
47 int(w.Dots(dpi)),
48 int(h.Dots(dpi)),
49 )),
50 }
51
52 var err error
53 for try := 0; try < 2; try++ {
54 _, err = New(gtx, w, h, UseDPI(dpi)).Screenshot()
55 if err == nil {
56 return
57 }
58 }
59
60 panic(fmt.Errorf("vg/vggio_test: could not setup headless display: %+v", err))
61 }
62
63 func TestCanvas(t *testing.T) {
64 if runtime.GOOS == "darwin" {
65 t.Skip("TODO: github actions for darwin with headless setup.")
66 }
67
68 const fname = "testdata/func.png"
69
70 const (
71 w = 20 * vg.Centimeter
72 h = 15 * vg.Centimeter
73 dpi = 96
74 )
75
76 cmpimg.CheckPlotApprox(func() {
77 p := plot.New()
78 p.Title.Text = "My title"
79 p.X.Label.Text = "X"
80 p.Y.Label.Text = "Y"
81
82 quad := plotter.NewFunction(func(x float64) float64 { return x * x })
83 quad.Color = color.RGBA{B: 255, A: 255}
84
85 exp := plotter.NewFunction(func(x float64) float64 { return math.Pow(2, x) })
86 exp.Dashes = []vg.Length{vg.Points(2), vg.Points(2)}
87 exp.Width = vg.Points(2)
88 exp.Color = color.RGBA{G: 255, A: 255}
89
90 sin := plotter.NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 })
91 sin.Dashes = []vg.Length{vg.Points(4), vg.Points(5)}
92 sin.Width = vg.Points(4)
93 sin.Color = color.RGBA{R: 255, A: 255}
94
95 p.Add(quad, exp, sin)
96 p.Legend.Add("x^2", quad)
97 p.Legend.Add("2^x", exp)
98 p.Legend.Add("10*sin(x)+50", sin)
99 p.Legend.ThumbnailWidth = 0.5 * vg.Inch
100
101 p.X.Min = 0
102 p.X.Max = 10
103 p.Y.Min = 0
104 p.Y.Max = 100
105
106 p.Add(plotter.NewGrid())
107
108 gtx := layout.Context{
109 Ops: new(op.Ops),
110 Constraints: layout.Exact(image.Pt(
111 int(w.Dots(dpi)),
112 int(h.Dots(dpi)),
113 )),
114 }
115 cnv := New(gtx, w, h, UseDPI(dpi))
116 p.Draw(draw.New(cnv))
117
118 img, err := cnv.Screenshot()
119 if err != nil {
120 t.Fatalf("could not create screenshot: %+v", err)
121 }
122 f, err := os.Create(fname)
123 if err != nil {
124 t.Fatalf("could not create output file: %+v", err)
125 }
126 defer f.Close()
127
128 err = png.Encode(f, img)
129 if err != nil {
130 t.Fatalf("could not encode screenshot: %+v", err)
131 }
132
133 err = f.Close()
134 if err != nil {
135 t.Fatalf("could not save screenshot: %+v", err)
136 }
137 }, t, deltaGio, "func.png",
138 )
139 }
140
141 func TestCollectionName(t *testing.T) {
142 for _, tc := range []struct {
143 name string
144 want string
145 }{
146 {"Liberation", "Liberation"},
147 {"LiberationSerif-Bold", "LiberationSerif"},
148 {"LiberationSerif-BoldItalic", "LiberationSerif"},
149 {"LiberationSerif-BoldItalic-Extra", "LiberationSerif"},
150
151 {"LiberationMono", "LiberationMono"},
152 {"LiberationMono-Regular", "LiberationMono"},
153
154 {"Times-Roman", "Times"},
155 {"Times-Bold", "Times"},
156 } {
157 got := collectionName(tc.name)
158 if got != tc.want {
159 t.Errorf(
160 "%s: invalid collection name: got=%q, want=%q",
161 tc.name, got, tc.want,
162 )
163 }
164 }
165 }
166
167 func TestLabels(t *testing.T) {
168 if runtime.GOOS == "darwin" {
169 t.Skip("TODO: github actions for darwin with headless setup.")
170 }
171
172 const fname = "testdata/labels.png"
173
174 const (
175 w = 20 * vg.Centimeter
176 h = 15 * vg.Centimeter
177 dpi = 96
178 )
179
180 cmpimg.CheckPlotApprox(func() {
181 p := plot.New()
182 p.Title.Text = "Labels"
183 p.X.Min = -1
184 p.X.Max = +1
185 p.Y.Min = -1
186 p.Y.Max = +1
187
188 const (
189 left = 0.00
190 middle = 0.02
191 right = 0.04
192 )
193
194 labels, err := plotter.NewLabels(plotter.XYLabels{
195 XYs: []plotter.XY{
196 {X: -0.8 + left, Y: -0.5},
197 {X: -0.6 + middle, Y: -0.5},
198 {X: -0.4 + right, Y: -0.5},
199
200 {X: -0.8 + left, Y: +0.5},
201 {X: -0.6 + middle, Y: +0.5},
202 {X: -0.4 + right, Y: +0.5},
203
204 {X: +0.0 + left, Y: +0},
205 {X: +0.2 + middle, Y: +0},
206 {X: +0.4 + right, Y: +0},
207 },
208 Labels: []string{
209 "Aq", "Aq", "Aq",
210 "Aq\nAq", "Aq\nAq", "Aq\nAq",
211
212 "Bg\nBg\nBg",
213 "Bg\nBg\nBg",
214 "Bg\nBg\nBg",
215 },
216 })
217 if err != nil {
218 t.Fatalf("could not creates labels plotter: %+v", err)
219 }
220 for i := range labels.TextStyle {
221 sty := &labels.TextStyle[i]
222 sty.Font.Size = vg.Length(34)
223 }
224 labels.TextStyle[0].YAlign = draw.YBottom
225 labels.TextStyle[1].YAlign = draw.YCenter
226 labels.TextStyle[2].YAlign = draw.YTop
227
228 labels.TextStyle[3].YAlign = draw.YBottom
229 labels.TextStyle[4].YAlign = draw.YCenter
230 labels.TextStyle[5].YAlign = draw.YTop
231
232 labels.TextStyle[6].YAlign = draw.YBottom
233 labels.TextStyle[7].YAlign = draw.YCenter
234 labels.TextStyle[8].YAlign = draw.YTop
235
236 lred, err := plotter.NewLabels(plotter.XYLabels{
237 XYs: []plotter.XY{
238 {X: -0.8 + left, Y: +0.5},
239 {X: +0.0 + left, Y: +0},
240 },
241 Labels: []string{
242 "Aq", "Bg",
243 },
244 })
245 if err != nil {
246 t.Fatalf("could not creates labels plotter: %+v", err)
247 }
248 for i := range lred.TextStyle {
249 sty := &lred.TextStyle[i]
250 sty.Font.Size = vg.Length(34)
251 sty.Color = color.RGBA{R: 255, A: 255}
252 sty.YAlign = draw.YBottom
253 }
254
255 m5 := plotter.NewFunction(func(float64) float64 { return -0.5 })
256 m5.LineStyle.Color = color.RGBA{R: 255, A: 255}
257
258 l0 := plotter.NewFunction(func(float64) float64 { return 0 })
259 l0.LineStyle.Color = color.RGBA{G: 255, A: 255}
260
261 p5 := plotter.NewFunction(func(float64) float64 { return +0.5 })
262 p5.LineStyle.Color = color.RGBA{B: 255, A: 255}
263
264 p.Add(labels, lred, m5, l0, p5)
265 p.Add(plotter.NewGrid())
266 p.Add(plotter.NewGlyphBoxes())
267
268 gtx := layout.Context{
269 Ops: new(op.Ops),
270 Constraints: layout.Exact(image.Pt(
271 int(w.Dots(dpi)),
272 int(h.Dots(dpi)),
273 )),
274 }
275 cnv := New(gtx, w, h, UseDPI(dpi))
276 p.Draw(draw.New(cnv))
277
278 img, err := cnv.Screenshot()
279 if err != nil {
280 t.Fatalf("could not create screenshot: %+v", err)
281 }
282 f, err := os.Create(fname)
283 if err != nil {
284 t.Fatalf("could not create output file: %+v", err)
285 }
286 defer f.Close()
287
288 err = png.Encode(f, img)
289 if err != nil {
290 t.Fatalf("could not encode screenshot: %+v", err)
291 }
292
293 err = f.Close()
294 if err != nil {
295 t.Fatalf("could not save screenshot: %+v", err)
296 }
297 }, t, deltaGio, "labels.png",
298 )
299 }
300
301 func TestPaths(t *testing.T) {
302 if runtime.GOOS == "darwin" {
303 t.Skip("TODO: github actions for darwin with headless setup.")
304 }
305
306 const fname = "testdata/paths.png"
307
308 const (
309 w = 20 * vg.Centimeter
310 h = 15 * vg.Centimeter
311 dpi = 96
312 )
313
314 cmpimg.CheckPlotApprox(func() {
315 p := plot.New()
316 p.Title.Text = "Paths"
317 p.X.Min = -1
318 p.X.Max = +1
319 p.Y.Min = -1
320 p.Y.Max = +1
321
322 newScatter := func(c color.Color, sty draw.GlyphDrawer, x, y float64) *plotter.Scatter {
323 t.Helper()
324
325 pts := make(plotter.XYs, 1)
326 pts[0].X = x
327 pts[0].Y = y
328
329 plt, err := plotter.NewScatter(pts)
330 if err != nil {
331 t.Fatal(err)
332 }
333 plt.GlyphStyle.Color = c
334 plt.GlyphStyle.Radius = vg.Points(10)
335 plt.GlyphStyle.Shape = sty
336 return plt
337 }
338
339 p.Add(
340 newScatter(
341 color.RGBA{R: 255, A: 255},
342 draw.CircleGlyph{},
343 -0.8, -0.8,
344 ),
345 newScatter(
346 color.RGBA{B: 255, A: 255},
347 draw.RingGlyph{},
348 -0.6, -0.6,
349 ),
350 newScatter(
351 color.RGBA{R: 255, A: 255},
352 draw.SquareGlyph{},
353 -0.4, -0.4,
354 ),
355 newScatter(
356 color.RGBA{B: 255, A: 255},
357 draw.BoxGlyph{},
358 -0.2, -0.2,
359 ),
360 newScatter(
361 color.RGBA{R: 255, A: 255},
362 draw.TriangleGlyph{},
363 0, 0,
364 ),
365 newScatter(
366 color.RGBA{B: 255, A: 255},
367 draw.PyramidGlyph{},
368 0.2, 0.2,
369 ),
370 newScatter(
371 color.RGBA{R: 255, A: 255},
372 draw.PlusGlyph{},
373 0.4, 0.4,
374 ),
375 newScatter(
376 color.RGBA{B: 255, A: 255},
377 draw.CrossGlyph{},
378 0.6, 0.6,
379 ),
380 )
381
382 p.Add(plotter.NewGrid())
383 p.Add(plotter.NewGlyphBoxes())
384
385 gtx := layout.Context{
386 Ops: new(op.Ops),
387 Constraints: layout.Exact(image.Pt(
388 int(w.Dots(dpi)),
389 int(h.Dots(dpi)),
390 )),
391 }
392 cnv := New(gtx, w, h, UseDPI(dpi), UseBackgroundColor(color.Transparent))
393 p.Draw(draw.New(cnv))
394
395 img, err := cnv.Screenshot()
396 if err != nil {
397 t.Fatalf("could not create screenshot: %+v", err)
398 }
399 f, err := os.Create(fname)
400 if err != nil {
401 t.Fatalf("could not create output file: %+v", err)
402 }
403 defer f.Close()
404
405 err = png.Encode(f, img)
406 if err != nil {
407 t.Fatalf("could not encode screenshot: %+v", err)
408 }
409
410 err = f.Close()
411 if err != nil {
412 t.Fatalf("could not save screenshot: %+v", err)
413 }
414 }, t, deltaGio, "paths.png",
415 )
416 }
417
418
419 func TestImage(t *testing.T) {
420 if runtime.GOOS == "darwin" {
421 t.Skip("TODO: github actions for darwin with headless setup.")
422 }
423
424 const fname = "testdata/image.png"
425
426 const (
427 w = 20 * vg.Centimeter
428 h = 15 * vg.Centimeter
429 dpi = 96
430 )
431
432 cmpimg.CheckPlotApprox(func() {
433 p := plot.New()
434 p.Title.Text = "A Logo"
435
436
437 src, err := os.Open("../../plotter/testdata/gopher.png")
438 if err != nil {
439 t.Fatalf("error opening image file: %v\n", err)
440 }
441 defer src.Close()
442
443 img, err := png.Decode(src)
444 if err != nil {
445 t.Fatalf("error decoding image file: %v\n", err)
446 }
447
448 p.Add(plotter.NewImage(img, 100, 100, 200, 200))
449
450 gtx := layout.Context{
451 Ops: new(op.Ops),
452 Constraints: layout.Exact(image.Pt(
453 int(w.Dots(dpi)),
454 int(h.Dots(dpi)),
455 )),
456 }
457 cnv := New(gtx, w, h, UseDPI(dpi), UseBackgroundColor(color.Transparent))
458 p.Draw(draw.New(cnv))
459
460 scr, err := cnv.Screenshot()
461 if err != nil {
462 t.Fatalf("could not create screenshot: %+v", err)
463 }
464
465 out, err := os.Create(fname)
466 if err != nil {
467 t.Fatalf("could not create output file: %+v", err)
468 }
469 defer out.Close()
470
471 err = png.Encode(out, scr)
472 if err != nil {
473 t.Fatalf("could not encode screenshot: %+v", err)
474 }
475
476 err = out.Close()
477 if err != nil {
478 t.Fatalf("could not save screenshot: %+v", err)
479 }
480 }, t, deltaGio, "image.png",
481 )
482 }
483
View as plain text