Source file
src/image/draw/draw_test.go
1
2
3
4
5 package draw
6
7 import (
8 "image"
9 "image/color"
10 "image/png"
11 "os"
12 "testing"
13 "testing/quick"
14 )
15
16 func eq(c0, c1 color.Color) bool {
17 r0, g0, b0, a0 := c0.RGBA()
18 r1, g1, b1, a1 := c1.RGBA()
19 return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
20 }
21
22 func fillBlue(alpha int) image.Image {
23 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
24 }
25
26 func fillAlpha(alpha int) image.Image {
27 return image.NewUniform(color.Alpha{uint8(alpha)})
28 }
29
30 func vgradGreen(alpha int) image.Image {
31 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
32 for y := 0; y < 16; y++ {
33 for x := 0; x < 16; x++ {
34 m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
35 }
36 }
37 return m
38 }
39
40 func vgradAlpha(alpha int) image.Image {
41 m := image.NewAlpha(image.Rect(0, 0, 16, 16))
42 for y := 0; y < 16; y++ {
43 for x := 0; x < 16; x++ {
44 m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
45 }
46 }
47 return m
48 }
49
50 func vgradGreenNRGBA(alpha int) image.Image {
51 m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
52 for y := 0; y < 16; y++ {
53 for x := 0; x < 16; x++ {
54 m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
55 }
56 }
57 return m
58 }
59
60 func vgradCr() image.Image {
61 m := &image.YCbCr{
62 Y: make([]byte, 16*16),
63 Cb: make([]byte, 16*16),
64 Cr: make([]byte, 16*16),
65 YStride: 16,
66 CStride: 16,
67 SubsampleRatio: image.YCbCrSubsampleRatio444,
68 Rect: image.Rect(0, 0, 16, 16),
69 }
70 for y := 0; y < 16; y++ {
71 for x := 0; x < 16; x++ {
72 m.Cr[y*m.CStride+x] = uint8(y * 0x11)
73 }
74 }
75 return m
76 }
77
78 func vgradGray() image.Image {
79 m := image.NewGray(image.Rect(0, 0, 16, 16))
80 for y := 0; y < 16; y++ {
81 for x := 0; x < 16; x++ {
82 m.Set(x, y, color.Gray{uint8(y * 0x11)})
83 }
84 }
85 return m
86 }
87
88 func vgradMagenta() image.Image {
89 m := image.NewCMYK(image.Rect(0, 0, 16, 16))
90 for y := 0; y < 16; y++ {
91 for x := 0; x < 16; x++ {
92 m.Set(x, y, color.CMYK{0, uint8(y * 0x11), 0, 0x3f})
93 }
94 }
95 return m
96 }
97
98 func hgradRed(alpha int) Image {
99 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
100 for y := 0; y < 16; y++ {
101 for x := 0; x < 16; x++ {
102 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
103 }
104 }
105 return m
106 }
107
108 func gradYellow(alpha int) Image {
109 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
110 for y := 0; y < 16; y++ {
111 for x := 0; x < 16; x++ {
112 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
113 }
114 }
115 return m
116 }
117
118 type drawTest struct {
119 desc string
120 src image.Image
121 mask image.Image
122 op Op
123 expected color.Color
124 }
125
126 var drawTests = []drawTest{
127
128 {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
129 {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
130
131
132
133
134 {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
135 {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
136 {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
137 {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
138 {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
139 {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
140
141
142
143
144 {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
145 {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
146 {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
147 {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
148 {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
149 {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
150
151
152
153
154
155 {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
156 {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
157 {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
158 {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
159 {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
160 {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
161
162
163
164
165 {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
166 {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
167 {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
168 {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
169 {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
170 {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
171
172
173
174
175 {"gray", vgradGray(), fillAlpha(255), Over, color.RGBA{136, 136, 136, 255}},
176 {"graySrc", vgradGray(), fillAlpha(255), Src, color.RGBA{136, 136, 136, 255}},
177 {"grayAlpha", vgradGray(), fillAlpha(192), Over, color.RGBA{136, 102, 102, 255}},
178 {"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
179 {"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
180 {"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
181
182
183
184
185 {"cmyk", vgradMagenta(), fillAlpha(255), Over, color.RGBA{192, 89, 192, 255}},
186 {"cmykSrc", vgradMagenta(), fillAlpha(255), Src, color.RGBA{192, 89, 192, 255}},
187 {"cmykAlpha", vgradMagenta(), fillAlpha(192), Over, color.RGBA{178, 67, 145, 255}},
188 {"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}},
189 {"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}},
190 {"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}},
191
192
193
194
195
196 {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
197 {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
198 }
199
200 func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
201
202
203 b := dst.Bounds()
204 sb := src.Bounds()
205 mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
206 if mask != nil {
207 mb = mask.Bounds()
208 }
209 golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
210 for y := r.Min.Y; y < r.Max.Y; y++ {
211 sy := y + sp.Y - r.Min.Y
212 my := y + mp.Y - r.Min.Y
213 for x := r.Min.X; x < r.Max.X; x++ {
214 if !(image.Pt(x, y).In(b)) {
215 continue
216 }
217 sx := x + sp.X - r.Min.X
218 if !(image.Pt(sx, sy).In(sb)) {
219 continue
220 }
221 mx := x + mp.X - r.Min.X
222 if !(image.Pt(mx, my).In(mb)) {
223 continue
224 }
225
226 const M = 1<<16 - 1
227 var dr, dg, db, da uint32
228 if op == Over {
229 dr, dg, db, da = dst.At(x, y).RGBA()
230 }
231 sr, sg, sb, sa := src.At(sx, sy).RGBA()
232 ma := uint32(M)
233 if mask != nil {
234 _, _, _, ma = mask.At(mx, my).RGBA()
235 }
236 a := M - (sa * ma / M)
237 golden.Set(x, y, color.RGBA64{
238 uint16((dr*a + sr*ma) / M),
239 uint16((dg*a + sg*ma) / M),
240 uint16((db*a + sb*ma) / M),
241 uint16((da*a + sa*ma) / M),
242 })
243 }
244 }
245 return golden.SubImage(b)
246 }
247
248 func TestDraw(t *testing.T) {
249 rr := []image.Rectangle{
250 image.Rect(0, 0, 0, 0),
251 image.Rect(0, 0, 16, 16),
252 image.Rect(3, 5, 12, 10),
253 image.Rect(0, 0, 9, 9),
254 image.Rect(8, 8, 16, 16),
255 image.Rect(8, 0, 9, 16),
256 image.Rect(0, 8, 16, 9),
257 image.Rect(8, 8, 9, 9),
258 image.Rect(8, 8, 8, 8),
259 }
260 for _, r := range rr {
261 loop:
262 for _, test := range drawTests {
263 dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
264
265 golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
266 b := dst.Bounds()
267 if !b.Eq(golden.Bounds()) {
268 t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds())
269 continue
270 }
271
272 DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
273 if image.Pt(8, 8).In(r) {
274
275
276 if !eq(dst.At(8, 8), test.expected) {
277 t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected)
278 continue
279 }
280 }
281
282 for y := b.Min.Y; y < b.Max.Y; y++ {
283 for x := b.Min.X; x < b.Max.X; x++ {
284 if !eq(dst.At(x, y), golden.At(x, y)) {
285 t.Errorf("draw %v %s: at (%d, %d), %v versus golden %v", r, test.desc, x, y, dst.At(x, y), golden.At(x, y))
286 continue loop
287 }
288 }
289 }
290 }
291 }
292 }
293
294 func TestDrawOverlap(t *testing.T) {
295 for _, op := range []Op{Over, Src} {
296 for yoff := -2; yoff <= 2; yoff++ {
297 loop:
298 for xoff := -2; xoff <= 2; xoff++ {
299 m := gradYellow(127).(*image.RGBA)
300 dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
301 src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
302 b := dst.Bounds()
303
304 golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
305 if !b.Eq(golden.Bounds()) {
306 t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
307 continue
308 }
309
310 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
311
312 for y := b.Min.Y; y < b.Max.Y; y++ {
313 for x := b.Min.X; x < b.Max.X; x++ {
314 if !eq(dst.At(x, y), golden.At(x, y)) {
315 t.Errorf("drawOverlap xoff=%d,yoff=%d: at (%d, %d), %v versus golden %v", xoff, yoff, x, y, dst.At(x, y), golden.At(x, y))
316 continue loop
317 }
318 }
319 }
320 }
321 }
322 }
323 }
324
325
326 func TestNonZeroSrcPt(t *testing.T) {
327 a := image.NewRGBA(image.Rect(0, 0, 1, 1))
328 b := image.NewRGBA(image.Rect(0, 0, 2, 2))
329 b.Set(0, 0, color.RGBA{0, 0, 0, 5})
330 b.Set(1, 0, color.RGBA{0, 0, 5, 5})
331 b.Set(0, 1, color.RGBA{0, 5, 0, 5})
332 b.Set(1, 1, color.RGBA{5, 0, 0, 5})
333 Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
334 if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
335 t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
336 }
337 }
338
339 func TestFill(t *testing.T) {
340 rr := []image.Rectangle{
341 image.Rect(0, 0, 0, 0),
342 image.Rect(0, 0, 40, 30),
343 image.Rect(10, 0, 40, 30),
344 image.Rect(0, 20, 40, 30),
345 image.Rect(10, 20, 40, 30),
346 image.Rect(10, 20, 15, 25),
347 image.Rect(10, 0, 35, 30),
348 image.Rect(0, 15, 40, 16),
349 image.Rect(24, 24, 25, 25),
350 image.Rect(23, 23, 26, 26),
351 image.Rect(22, 22, 27, 27),
352 image.Rect(21, 21, 28, 28),
353 image.Rect(20, 20, 29, 29),
354 }
355 for _, r := range rr {
356 m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
357 b := m.Bounds()
358 c := color.RGBA{11, 0, 0, 255}
359 src := &image.Uniform{C: c}
360 check := func(desc string) {
361 for y := b.Min.Y; y < b.Max.Y; y++ {
362 for x := b.Min.X; x < b.Max.X; x++ {
363 if !eq(c, m.At(x, y)) {
364 t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
365 return
366 }
367 }
368 }
369 }
370
371 for y := b.Min.Y; y < b.Max.Y; y++ {
372 for x := b.Min.X; x < b.Max.X; x++ {
373 DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
374 }
375 }
376 check("pixel")
377
378 c = color.RGBA{0, 22, 0, 255}
379 src = &image.Uniform{C: c}
380 for y := b.Min.Y; y < b.Max.Y; y++ {
381 DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
382 }
383 check("row")
384
385 c = color.RGBA{0, 0, 33, 255}
386 src = &image.Uniform{C: c}
387 for x := b.Min.X; x < b.Max.X; x++ {
388 DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
389 }
390 check("column")
391
392 c = color.RGBA{44, 55, 66, 77}
393 src = &image.Uniform{C: c}
394 DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
395 check("whole")
396 }
397 }
398
399
400
401
402 func TestFloydSteinbergCheckerboard(t *testing.T) {
403 b := image.Rect(0, 0, 640, 480)
404
405 src := &image.Uniform{color.Gray16{0x7fff}}
406 dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
407 FloydSteinberg.Draw(dst, b, src, image.Point{})
408 nErr := 0
409 for y := b.Min.Y; y < b.Max.Y; y++ {
410 for x := b.Min.X; x < b.Max.X; x++ {
411 got := dst.Pix[dst.PixOffset(x, y)]
412 want := uint8(x+y) % 2
413 if got != want {
414 t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
415 if nErr++; nErr == 10 {
416 t.Fatal("there may be more errors")
417 }
418 }
419 }
420 }
421 }
422
423
424
425 type embeddedPaletted struct {
426 *image.Paletted
427 }
428
429
430
431 func TestPaletted(t *testing.T) {
432 f, err := os.Open("../testdata/video-001.png")
433 if err != nil {
434 t.Fatalf("open: %v", err)
435 }
436 defer f.Close()
437 video001, err := png.Decode(f)
438 if err != nil {
439 t.Fatalf("decode: %v", err)
440 }
441 b := video001.Bounds()
442
443 cgaPalette := color.Palette{
444 color.RGBA{0x00, 0x00, 0x00, 0xff},
445 color.RGBA{0x55, 0xff, 0xff, 0xff},
446 color.RGBA{0xff, 0x55, 0xff, 0xff},
447 color.RGBA{0xff, 0xff, 0xff, 0xff},
448 }
449 drawers := map[string]Drawer{
450 "src": Src,
451 "floyd-steinberg": FloydSteinberg,
452 }
453 sources := map[string]image.Image{
454 "uniform": &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
455 "video001": video001,
456 }
457
458 for dName, d := range drawers {
459 loop:
460 for sName, src := range sources {
461 dst0 := image.NewPaletted(b, cgaPalette)
462 dst1 := image.NewPaletted(b, cgaPalette)
463 d.Draw(dst0, b, src, image.Point{})
464 d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
465 for y := b.Min.Y; y < b.Max.Y; y++ {
466 for x := b.Min.X; x < b.Max.X; x++ {
467 if !eq(dst0.At(x, y), dst1.At(x, y)) {
468 t.Errorf("%s / %s: at (%d, %d), %v versus %v",
469 dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
470 continue loop
471 }
472 }
473 }
474 }
475 }
476 }
477
478 func TestSqDiff(t *testing.T) {
479
480
481
482
483
484 orig := func(x, y int32) uint32 {
485 var d uint32
486 if x > y {
487 d = uint32(x - y)
488 } else {
489 d = uint32(y - x)
490 }
491 return (d * d) >> 2
492 }
493 testCases := []int32{
494 0,
495 1,
496 2,
497 0x0fffd,
498 0x0fffe,
499 0x0ffff,
500 0x10000,
501 0x10001,
502 0x10002,
503 0x7ffffffd,
504 0x7ffffffe,
505 0x7fffffff,
506 -0x7ffffffd,
507 -0x7ffffffe,
508 -0x80000000,
509 }
510 for _, x := range testCases {
511 for _, y := range testCases {
512 if got, want := sqDiff(x, y), orig(x, y); got != want {
513 t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
514 }
515 }
516 }
517 if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
518 t.Fatal(err)
519 }
520 }
521
View as plain text