1
2
3
4
5 package vector
6
7
8
9 import (
10 "fmt"
11 "image"
12 "image/color"
13 "image/draw"
14 "image/png"
15 "math"
16 "math/rand"
17 "os"
18 "path/filepath"
19 "testing"
20 )
21
22
23 func encodePNG(dstFilename string, src image.Image) error {
24 f, err := os.Create(dstFilename)
25 if err != nil {
26 return err
27 }
28 encErr := png.Encode(f, src)
29 closeErr := f.Close()
30 if encErr != nil {
31 return encErr
32 }
33 return closeErr
34 }
35
36 func pointOnCircle(center, radius, index, number int) (x, y float32) {
37 c := float64(center)
38 r := float64(radius)
39 i := float64(index)
40 n := float64(number)
41 return float32(c + r*(math.Cos(2*math.Pi*i/n))),
42 float32(c + r*(math.Sin(2*math.Pi*i/n)))
43 }
44
45 func TestRasterizeOutOfBounds(t *testing.T) {
46
47
48
49
50
51 const tmpDir = ""
52
53 const center, radius, n = 16, 20, 16
54 var z Rasterizer
55 for i := 0; i < n; i++ {
56 for j := 1; j < n/2; j++ {
57 z.Reset(2*center, 2*center)
58 z.MoveTo(1*center, 1*center)
59 z.LineTo(pointOnCircle(center, radius, i+0, n))
60 z.LineTo(pointOnCircle(center, radius, i+j, n))
61 z.ClosePath()
62
63 z.MoveTo(0*center, 0*center)
64 z.LineTo(0*center, 2*center)
65 z.LineTo(2*center, 2*center)
66 z.LineTo(2*center, 0*center)
67 z.ClosePath()
68
69 dst := image.NewAlpha(z.Bounds())
70 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
71
72 if tmpDir == "" {
73 continue
74 }
75
76 filename := filepath.Join(tmpDir, fmt.Sprintf("out-%02d-%02d.png", i, j))
77 if err := encodePNG(filename, dst); err != nil {
78 t.Error(err)
79 }
80 t.Logf("wrote %s", filename)
81 }
82 }
83 }
84
85 func TestRasterizePolygon(t *testing.T) {
86 var z Rasterizer
87 for radius := 4; radius <= 256; radius *= 2 {
88 for n := 3; n <= 19; n += 4 {
89 z.Reset(2*radius, 2*radius)
90 z.MoveTo(float32(2*radius), float32(1*radius))
91 for i := 1; i < n; i++ {
92 z.LineTo(pointOnCircle(radius, radius, i, n))
93 }
94 z.ClosePath()
95
96 dst := image.NewAlpha(z.Bounds())
97 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
98
99 if err := checkCornersCenter(dst); err != nil {
100 t.Errorf("radius=%d, n=%d: %v", radius, n, err)
101 }
102 }
103 }
104 }
105
106 func TestRasterizeAlmostAxisAligned(t *testing.T) {
107 z := NewRasterizer(8, 8)
108 z.MoveTo(2, 2)
109 z.LineTo(6, math.Nextafter32(2, 0))
110 z.LineTo(6, 6)
111 z.LineTo(math.Nextafter32(2, 0), 6)
112 z.ClosePath()
113
114 dst := image.NewAlpha(z.Bounds())
115 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
116
117 if err := checkCornersCenter(dst); err != nil {
118 t.Error(err)
119 }
120 }
121
122 func TestRasterizeWideAlmostHorizontalLines(t *testing.T) {
123 var z Rasterizer
124 for i := uint(3); i < 16; i++ {
125 x := float32(int(1 << i))
126
127 z.Reset(8, 8)
128 z.MoveTo(-x, 3)
129 z.LineTo(+x, 4)
130 z.LineTo(+x, 6)
131 z.LineTo(-x, 6)
132 z.ClosePath()
133
134 dst := image.NewAlpha(z.Bounds())
135 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
136
137 if err := checkCornersCenter(dst); err != nil {
138 t.Errorf("i=%d: %v", i, err)
139 }
140 }
141 }
142
143 func TestRasterize30Degrees(t *testing.T) {
144 z := NewRasterizer(8, 8)
145 z.MoveTo(4, 4)
146 z.LineTo(8, 4)
147 z.LineTo(4, 6)
148 z.ClosePath()
149
150 dst := image.NewAlpha(z.Bounds())
151 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
152
153 if err := checkCornersCenter(dst); err != nil {
154 t.Error(err)
155 }
156 }
157
158 func TestRasterizeRandomLineTos(t *testing.T) {
159 var z Rasterizer
160 for i := 5; i < 50; i++ {
161 n, rng := 0, rand.New(rand.NewSource(int64(i)))
162
163 z.Reset(i+2, i+2)
164 z.MoveTo(float32(i/2), float32(i/2))
165 for ; rng.Intn(16) != 0; n++ {
166 x := 1 + rng.Intn(i)
167 y := 1 + rng.Intn(i)
168 z.LineTo(float32(x), float32(y))
169 }
170 z.ClosePath()
171
172 dst := image.NewAlpha(z.Bounds())
173 z.Draw(dst, dst.Bounds(), image.Opaque, image.Point{})
174
175 if err := checkCorners(dst); err != nil {
176 t.Errorf("i=%d (%d nodes): %v", i, n, err)
177 }
178 }
179 }
180
181
182
183 func checkCornersCenter(m *image.Alpha) error {
184 if err := checkCorners(m); err != nil {
185 return err
186 }
187 size := m.Bounds().Size()
188 center := m.Pix[(size.Y/2)*m.Stride+(size.X/2)]
189 if center != 0xff {
190 return fmt.Errorf("center: got %#02x, want 0xff", center)
191 }
192 return nil
193 }
194
195
196 func checkCorners(m *image.Alpha) error {
197 size := m.Bounds().Size()
198 corners := [4]uint8{
199 m.Pix[(0*size.Y+0)*m.Stride+(0*size.X+0)],
200 m.Pix[(0*size.Y+0)*m.Stride+(1*size.X-1)],
201 m.Pix[(1*size.Y-1)*m.Stride+(0*size.X+0)],
202 m.Pix[(1*size.Y-1)*m.Stride+(1*size.X-1)],
203 }
204 if corners != [4]uint8{} {
205 return fmt.Errorf("corners were not all zero: %v", corners)
206 }
207 return nil
208 }
209
210 var basicMask = []byte{
211 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
212 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
213 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xaa, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00,
214 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x5f, 0x00, 0x00, 0x00, 0x00,
215 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x24, 0x00, 0x00, 0x00,
216 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x00, 0x00, 0x00,
217 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x14, 0x00, 0x00,
218 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x4a, 0x00, 0x00,
219 0x00, 0x00, 0xcc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x81, 0x00, 0x00,
220 0x00, 0x00, 0x66, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe4, 0xff, 0xff, 0xff, 0xb6, 0x00, 0x00,
221 0x00, 0x00, 0x0c, 0xf2, 0xff, 0xff, 0xfe, 0x9e, 0x15, 0x00, 0x15, 0x96, 0xff, 0xce, 0x00, 0x00,
222 0x00, 0x00, 0x00, 0x88, 0xfc, 0xe3, 0x43, 0x00, 0x00, 0x00, 0x00, 0x06, 0xcd, 0xdc, 0x00, 0x00,
223 0x00, 0x00, 0x00, 0x00, 0x10, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0xde, 0x00, 0x00,
224 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00,
225 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
226 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
227 }
228
229 func testBasicPath(t *testing.T, prefix string, dst draw.Image, src image.Image, op draw.Op, want []byte) {
230 z := NewRasterizer(16, 16)
231 z.MoveTo(2, 2)
232 z.LineTo(8, 2)
233 z.QuadTo(14, 2, 14, 14)
234 z.CubeTo(8, 2, 5, 20, 2, 8)
235 z.ClosePath()
236
237 z.DrawOp = op
238 z.Draw(dst, z.Bounds(), src, image.Point{})
239
240 var got []byte
241 switch dst := dst.(type) {
242 case *image.Alpha:
243 got = dst.Pix
244 case *image.RGBA:
245 got = dst.Pix
246 default:
247 t.Errorf("%s: unrecognized dst image type %T", prefix, dst)
248 }
249
250 if len(got) != len(want) {
251 t.Errorf("%s: len(got)=%d and len(want)=%d differ", prefix, len(got), len(want))
252 return
253 }
254 for i := range got {
255 delta := int(got[i]) - int(want[i])
256
257
258 if delta < -2 || +2 < delta {
259 t.Errorf("%s: i=%d: got %#02x, want %#02x", prefix, i, got[i], want[i])
260 return
261 }
262 }
263 }
264
265 func TestBasicPathDstAlpha(t *testing.T) {
266 for _, background := range []uint8{0x00, 0x80} {
267 for _, op := range []draw.Op{draw.Over, draw.Src} {
268 for _, xPadding := range []int{0, 7} {
269 bounds := image.Rect(0, 0, 16+xPadding, 16)
270 dst := image.NewAlpha(bounds)
271 for i := range dst.Pix {
272 dst.Pix[i] = background
273 }
274
275 want := make([]byte, len(dst.Pix))
276 copy(want, dst.Pix)
277
278 if op == draw.Over && background == 0x80 {
279 for y := 0; y < 16; y++ {
280 for x := 0; x < 16; x++ {
281 ma := basicMask[16*y+x]
282 i := dst.PixOffset(x, y)
283 want[i] = 0xff - (0xff-ma)/2
284 }
285 }
286 } else {
287 for y := 0; y < 16; y++ {
288 for x := 0; x < 16; x++ {
289 ma := basicMask[16*y+x]
290 i := dst.PixOffset(x, y)
291 want[i] = ma
292 }
293 }
294 }
295
296 prefix := fmt.Sprintf("background=%#02x, op=%v, xPadding=%d", background, op, xPadding)
297 testBasicPath(t, prefix, dst, image.Opaque, op, want)
298 }
299 }
300 }
301 }
302
303 func TestBasicPathDstRGBA(t *testing.T) {
304 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
305
306 for _, op := range []draw.Op{draw.Over, draw.Src} {
307 for _, xPadding := range []int{0, 7} {
308 bounds := image.Rect(0, 0, 16+xPadding, 16)
309 dst := image.NewRGBA(bounds)
310 for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
311 for x := bounds.Min.X; x < bounds.Max.X; x++ {
312 dst.SetRGBA(x, y, color.RGBA{
313 R: uint8(y * 0x07),
314 G: uint8(x * 0x05),
315 B: 0x00,
316 A: 0x80,
317 })
318 }
319 }
320
321 want := make([]byte, len(dst.Pix))
322 copy(want, dst.Pix)
323
324 if op == draw.Over {
325 for y := 0; y < 16; y++ {
326 for x := 0; x < 16; x++ {
327 ma := basicMask[16*y+x]
328 i := dst.PixOffset(x, y)
329 want[i+0] = uint8((uint32(0xff-ma) * uint32(y*0x07)) / 0xff)
330 want[i+1] = uint8((uint32(0xff-ma) * uint32(x*0x05)) / 0xff)
331 want[i+2] = ma
332 want[i+3] = ma/2 + 0x80
333 }
334 }
335 } else {
336 for y := 0; y < 16; y++ {
337 for x := 0; x < 16; x++ {
338 ma := basicMask[16*y+x]
339 i := dst.PixOffset(x, y)
340 want[i+0] = 0x00
341 want[i+1] = 0x00
342 want[i+2] = ma
343 want[i+3] = ma
344 }
345 }
346 }
347
348 prefix := fmt.Sprintf("op=%v, xPadding=%d", op, xPadding)
349 testBasicPath(t, prefix, dst, blue, op, want)
350 }
351 }
352 }
353
354 const (
355 benchmarkGlyphWidth = 893
356 benchmarkGlyphHeight = 1122
357 )
358
359 type benchmarkGlyphDatum struct {
360
361 n uint32
362 px float32
363 py float32
364 qx float32
365 qy float32
366 }
367
368
369
370 var benchmarkGlyphData = []benchmarkGlyphDatum{
371 {0, 699, 1102, 0, 0},
372 {2, 683, 1070, 673, 988},
373 {2, 544, 1122, 365, 1122},
374 {2, 205, 1122, 102.5, 1031.5},
375 {2, 0, 941, 0, 802},
376 {2, 0, 633, 128.5, 539.5},
377 {2, 257, 446, 490, 446},
378 {1, 670, 446, 0, 0},
379 {1, 670, 361, 0, 0},
380 {2, 670, 264, 612, 206.5},
381 {2, 554, 149, 441, 149},
382 {2, 342, 149, 275, 199},
383 {2, 208, 249, 208, 320},
384 {1, 22, 320, 0, 0},
385 {2, 22, 239, 79.5, 163.5},
386 {2, 137, 88, 235.5, 44},
387 {2, 334, 0, 452, 0},
388 {2, 639, 0, 745, 93.5},
389 {2, 851, 187, 855, 351},
390 {1, 855, 849, 0, 0},
391 {2, 855, 998, 893, 1086},
392 {1, 893, 1102, 0, 0},
393 {1, 699, 1102, 0, 0},
394 {0, 392, 961, 0, 0},
395 {2, 479, 961, 557, 916},
396 {2, 635, 871, 670, 799},
397 {1, 670, 577, 0, 0},
398 {1, 525, 577, 0, 0},
399 {2, 185, 577, 185, 776},
400 {2, 185, 863, 243, 912},
401 {2, 301, 961, 392, 961},
402 }
403
404 func scaledBenchmarkGlyphData(height int) (width int, data []benchmarkGlyphDatum) {
405 scale := float32(height) / benchmarkGlyphHeight
406
407
408 data = append(data, benchmarkGlyphData...)
409 for i := range data {
410 data[i].px *= scale
411 data[i].py *= scale
412 data[i].qx *= scale
413 data[i].qy *= scale
414 }
415
416 return int(math.Ceil(float64(benchmarkGlyphWidth * scale))), data
417 }
418
419
420
421
422
423
424 func benchGlyph(b *testing.B, colorModel byte, loose bool, height int, op draw.Op) {
425 width, data := scaledBenchmarkGlyphData(height)
426 z := NewRasterizer(width, height)
427
428 bounds := z.Bounds()
429 if loose {
430 bounds.Max.X++
431 }
432 dst, src := draw.Image(nil), image.Image(nil)
433 switch colorModel {
434 case 'A':
435 dst = image.NewAlpha(bounds)
436 src = image.Opaque
437 case 'N':
438 dst = image.NewNRGBA(bounds)
439 src = image.NewUniform(color.NRGBA{0x40, 0x80, 0xc0, 0xff})
440 case 'R':
441 dst = image.NewRGBA(bounds)
442 src = image.NewUniform(color.RGBA{0x40, 0x80, 0xc0, 0xff})
443 default:
444 b.Fatal("unsupported color model")
445 }
446 bounds = z.Bounds()
447
448 b.ResetTimer()
449 for i := 0; i < b.N; i++ {
450 z.Reset(width, height)
451 z.DrawOp = op
452 for _, d := range data {
453 switch d.n {
454 case 0:
455 z.MoveTo(d.px, d.py)
456 case 1:
457 z.LineTo(d.px, d.py)
458 case 2:
459 z.QuadTo(d.px, d.py, d.qx, d.qy)
460 }
461 }
462 z.Draw(dst, bounds, src, image.Point{})
463 }
464 }
465
466
467
468
469 func BenchmarkGlyphAlpha16Over(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Over) }
470 func BenchmarkGlyphAlpha16Src(b *testing.B) { benchGlyph(b, 'A', false, 16, draw.Src) }
471 func BenchmarkGlyphAlpha32Over(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Over) }
472 func BenchmarkGlyphAlpha32Src(b *testing.B) { benchGlyph(b, 'A', false, 32, draw.Src) }
473 func BenchmarkGlyphAlpha64Over(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Over) }
474 func BenchmarkGlyphAlpha64Src(b *testing.B) { benchGlyph(b, 'A', false, 64, draw.Src) }
475 func BenchmarkGlyphAlpha128Over(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Over) }
476 func BenchmarkGlyphAlpha128Src(b *testing.B) { benchGlyph(b, 'A', false, 128, draw.Src) }
477 func BenchmarkGlyphAlpha256Over(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Over) }
478 func BenchmarkGlyphAlpha256Src(b *testing.B) { benchGlyph(b, 'A', false, 256, draw.Src) }
479 func BenchmarkGlyphAlpha1024Over(b *testing.B) { benchGlyph(b, 'A', false, 1024, draw.Over) }
480 func BenchmarkGlyphAlpha1024Src(b *testing.B) { benchGlyph(b, 'A', false, 1024, draw.Src) }
481
482 func BenchmarkGlyphAlphaLoose16Over(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Over) }
483 func BenchmarkGlyphAlphaLoose16Src(b *testing.B) { benchGlyph(b, 'A', true, 16, draw.Src) }
484 func BenchmarkGlyphAlphaLoose32Over(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Over) }
485 func BenchmarkGlyphAlphaLoose32Src(b *testing.B) { benchGlyph(b, 'A', true, 32, draw.Src) }
486 func BenchmarkGlyphAlphaLoose64Over(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Over) }
487 func BenchmarkGlyphAlphaLoose64Src(b *testing.B) { benchGlyph(b, 'A', true, 64, draw.Src) }
488 func BenchmarkGlyphAlphaLoose128Over(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Over) }
489 func BenchmarkGlyphAlphaLoose128Src(b *testing.B) { benchGlyph(b, 'A', true, 128, draw.Src) }
490 func BenchmarkGlyphAlphaLoose256Over(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Over) }
491 func BenchmarkGlyphAlphaLoose256Src(b *testing.B) { benchGlyph(b, 'A', true, 256, draw.Src) }
492 func BenchmarkGlyphAlphaLoose1024Over(b *testing.B) { benchGlyph(b, 'A', true, 1024, draw.Over) }
493 func BenchmarkGlyphAlphaLoose1024Src(b *testing.B) { benchGlyph(b, 'A', true, 1024, draw.Src) }
494
495 func BenchmarkGlyphRGBA16Over(b *testing.B) { benchGlyph(b, 'R', false, 16, draw.Over) }
496 func BenchmarkGlyphRGBA16Src(b *testing.B) { benchGlyph(b, 'R', false, 16, draw.Src) }
497 func BenchmarkGlyphRGBA32Over(b *testing.B) { benchGlyph(b, 'R', false, 32, draw.Over) }
498 func BenchmarkGlyphRGBA32Src(b *testing.B) { benchGlyph(b, 'R', false, 32, draw.Src) }
499 func BenchmarkGlyphRGBA64Over(b *testing.B) { benchGlyph(b, 'R', false, 64, draw.Over) }
500 func BenchmarkGlyphRGBA64Src(b *testing.B) { benchGlyph(b, 'R', false, 64, draw.Src) }
501 func BenchmarkGlyphRGBA128Over(b *testing.B) { benchGlyph(b, 'R', false, 128, draw.Over) }
502 func BenchmarkGlyphRGBA128Src(b *testing.B) { benchGlyph(b, 'R', false, 128, draw.Src) }
503 func BenchmarkGlyphRGBA256Over(b *testing.B) { benchGlyph(b, 'R', false, 256, draw.Over) }
504 func BenchmarkGlyphRGBA256Src(b *testing.B) { benchGlyph(b, 'R', false, 256, draw.Src) }
505 func BenchmarkGlyphRGBA1024Over(b *testing.B) { benchGlyph(b, 'R', false, 1024, draw.Over) }
506 func BenchmarkGlyphRGBA1024Src(b *testing.B) { benchGlyph(b, 'R', false, 1024, draw.Src) }
507
508 func BenchmarkGlyphNRGBA16Over(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Over) }
509 func BenchmarkGlyphNRGBA16Src(b *testing.B) { benchGlyph(b, 'N', false, 16, draw.Src) }
510 func BenchmarkGlyphNRGBA32Over(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Over) }
511 func BenchmarkGlyphNRGBA32Src(b *testing.B) { benchGlyph(b, 'N', false, 32, draw.Src) }
512 func BenchmarkGlyphNRGBA64Over(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Over) }
513 func BenchmarkGlyphNRGBA64Src(b *testing.B) { benchGlyph(b, 'N', false, 64, draw.Src) }
514 func BenchmarkGlyphNRGBA128Over(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Over) }
515 func BenchmarkGlyphNRGBA128Src(b *testing.B) { benchGlyph(b, 'N', false, 128, draw.Src) }
516 func BenchmarkGlyphNRGBA256Over(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Over) }
517 func BenchmarkGlyphNRGBA256Src(b *testing.B) { benchGlyph(b, 'N', false, 256, draw.Src) }
518 func BenchmarkGlyphNRGBA1024Over(b *testing.B) { benchGlyph(b, 'N', false, 1024, draw.Over) }
519 func BenchmarkGlyphNRGBA1024Src(b *testing.B) { benchGlyph(b, 'N', false, 1024, draw.Src) }
520
View as plain text