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
17
18
19
20 type slowestRGBA struct {
21 Pix []uint8
22 Stride int
23 Rect image.Rectangle
24 }
25
26 func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel }
27
28 func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect }
29
30 func (p *slowestRGBA) At(x, y int) color.Color {
31 return p.RGBA64At(x, y)
32 }
33
34 func (p *slowestRGBA) RGBA64At(x, y int) color.RGBA64 {
35 if !(image.Point{x, y}.In(p.Rect)) {
36 return color.RGBA64{}
37 }
38 i := p.PixOffset(x, y)
39 s := p.Pix[i : i+4 : i+4]
40 r := uint16(s[0])
41 g := uint16(s[1])
42 b := uint16(s[2])
43 a := uint16(s[3])
44 return color.RGBA64{
45 (r << 8) | r,
46 (g << 8) | g,
47 (b << 8) | b,
48 (a << 8) | a,
49 }
50 }
51
52 func (p *slowestRGBA) Set(x, y int, c color.Color) {
53 if !(image.Point{x, y}.In(p.Rect)) {
54 return
55 }
56 i := p.PixOffset(x, y)
57 c1 := color.RGBAModel.Convert(c).(color.RGBA)
58 s := p.Pix[i : i+4 : i+4]
59 s[0] = c1.R
60 s[1] = c1.G
61 s[2] = c1.B
62 s[3] = c1.A
63 }
64
65 func (p *slowestRGBA) PixOffset(x, y int) int {
66 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
67 }
68
69 func convertToSlowestRGBA(m image.Image) *slowestRGBA {
70 if rgba, ok := m.(*image.RGBA); ok {
71 return &slowestRGBA{
72 Pix: append([]byte(nil), rgba.Pix...),
73 Stride: rgba.Stride,
74 Rect: rgba.Rect,
75 }
76 }
77 rgba := image.NewRGBA(m.Bounds())
78 Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
79 return &slowestRGBA{
80 Pix: rgba.Pix,
81 Stride: rgba.Stride,
82 Rect: rgba.Rect,
83 }
84 }
85
86 func init() {
87 var p any = (*slowestRGBA)(nil)
88 if _, ok := p.(RGBA64Image); ok {
89 panic("slowestRGBA should not be an RGBA64Image")
90 }
91 }
92
93
94
95
96
97 type slowerRGBA struct {
98 Pix []uint8
99 Stride int
100 Rect image.Rectangle
101 }
102
103 func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel }
104
105 func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect }
106
107 func (p *slowerRGBA) At(x, y int) color.Color {
108 return p.RGBA64At(x, y)
109 }
110
111 func (p *slowerRGBA) RGBA64At(x, y int) color.RGBA64 {
112 if !(image.Point{x, y}.In(p.Rect)) {
113 return color.RGBA64{}
114 }
115 i := p.PixOffset(x, y)
116 s := p.Pix[i : i+4 : i+4]
117 r := uint16(s[0])
118 g := uint16(s[1])
119 b := uint16(s[2])
120 a := uint16(s[3])
121 return color.RGBA64{
122 (r << 8) | r,
123 (g << 8) | g,
124 (b << 8) | b,
125 (a << 8) | a,
126 }
127 }
128
129 func (p *slowerRGBA) Set(x, y int, c color.Color) {
130 if !(image.Point{x, y}.In(p.Rect)) {
131 return
132 }
133 i := p.PixOffset(x, y)
134 c1 := color.RGBAModel.Convert(c).(color.RGBA)
135 s := p.Pix[i : i+4 : i+4]
136 s[0] = c1.R
137 s[1] = c1.G
138 s[2] = c1.B
139 s[3] = c1.A
140 }
141
142 func (p *slowerRGBA) SetRGBA64(x, y int, c color.RGBA64) {
143 if !(image.Point{x, y}.In(p.Rect)) {
144 return
145 }
146 i := p.PixOffset(x, y)
147 s := p.Pix[i : i+4 : i+4]
148 s[0] = uint8(c.R >> 8)
149 s[1] = uint8(c.G >> 8)
150 s[2] = uint8(c.B >> 8)
151 s[3] = uint8(c.A >> 8)
152 }
153
154 func (p *slowerRGBA) PixOffset(x, y int) int {
155 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4
156 }
157
158 func convertToSlowerRGBA(m image.Image) *slowerRGBA {
159 if rgba, ok := m.(*image.RGBA); ok {
160 return &slowerRGBA{
161 Pix: append([]byte(nil), rgba.Pix...),
162 Stride: rgba.Stride,
163 Rect: rgba.Rect,
164 }
165 }
166 rgba := image.NewRGBA(m.Bounds())
167 Draw(rgba, rgba.Bounds(), m, m.Bounds().Min, Src)
168 return &slowerRGBA{
169 Pix: rgba.Pix,
170 Stride: rgba.Stride,
171 Rect: rgba.Rect,
172 }
173 }
174
175 func init() {
176 var p any = (*slowerRGBA)(nil)
177 if _, ok := p.(RGBA64Image); !ok {
178 panic("slowerRGBA should be an RGBA64Image")
179 }
180 }
181
182 func eq(c0, c1 color.Color) bool {
183 r0, g0, b0, a0 := c0.RGBA()
184 r1, g1, b1, a1 := c1.RGBA()
185 return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1
186 }
187
188 func fillBlue(alpha int) image.Image {
189 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)})
190 }
191
192 func fillAlpha(alpha int) image.Image {
193 return image.NewUniform(color.Alpha{uint8(alpha)})
194 }
195
196 func vgradGreen(alpha int) image.Image {
197 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
198 for y := 0; y < 16; y++ {
199 for x := 0; x < 16; x++ {
200 m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)})
201 }
202 }
203 return m
204 }
205
206 func vgradAlpha(alpha int) image.Image {
207 m := image.NewAlpha(image.Rect(0, 0, 16, 16))
208 for y := 0; y < 16; y++ {
209 for x := 0; x < 16; x++ {
210 m.Set(x, y, color.Alpha{uint8(y * alpha / 15)})
211 }
212 }
213 return m
214 }
215
216 func vgradGreenNRGBA(alpha int) image.Image {
217 m := image.NewNRGBA(image.Rect(0, 0, 16, 16))
218 for y := 0; y < 16; y++ {
219 for x := 0; x < 16; x++ {
220 m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)})
221 }
222 }
223 return m
224 }
225
226 func vgradCr() image.Image {
227 m := &image.YCbCr{
228 Y: make([]byte, 16*16),
229 Cb: make([]byte, 16*16),
230 Cr: make([]byte, 16*16),
231 YStride: 16,
232 CStride: 16,
233 SubsampleRatio: image.YCbCrSubsampleRatio444,
234 Rect: image.Rect(0, 0, 16, 16),
235 }
236 for y := 0; y < 16; y++ {
237 for x := 0; x < 16; x++ {
238 m.Cr[y*m.CStride+x] = uint8(y * 0x11)
239 }
240 }
241 return m
242 }
243
244 func vgradGray() image.Image {
245 m := image.NewGray(image.Rect(0, 0, 16, 16))
246 for y := 0; y < 16; y++ {
247 for x := 0; x < 16; x++ {
248 m.Set(x, y, color.Gray{uint8(y * 0x11)})
249 }
250 }
251 return m
252 }
253
254 func vgradMagenta() image.Image {
255 m := image.NewCMYK(image.Rect(0, 0, 16, 16))
256 for y := 0; y < 16; y++ {
257 for x := 0; x < 16; x++ {
258 m.Set(x, y, color.CMYK{0, uint8(y * 0x11), 0, 0x3f})
259 }
260 }
261 return m
262 }
263
264 func hgradRed(alpha int) Image {
265 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
266 for y := 0; y < 16; y++ {
267 for x := 0; x < 16; x++ {
268 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)})
269 }
270 }
271 return m
272 }
273
274 func gradYellow(alpha int) Image {
275 m := image.NewRGBA(image.Rect(0, 0, 16, 16))
276 for y := 0; y < 16; y++ {
277 for x := 0; x < 16; x++ {
278 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)})
279 }
280 }
281 return m
282 }
283
284 type drawTest struct {
285 desc string
286 src image.Image
287 mask image.Image
288 op Op
289 expected color.Color
290 }
291
292 var drawTests = []drawTest{
293
294 {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}},
295 {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}},
296
297
298
299
300 {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}},
301 {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}},
302 {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}},
303 {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}},
304 {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}},
305 {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}},
306
307
308
309
310 {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}},
311 {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}},
312 {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}},
313 {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}},
314 {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}},
315 {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}},
316
317
318
319
320
321 {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}},
322 {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}},
323 {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}},
324 {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}},
325 {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}},
326 {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}},
327
328
329
330
331 {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}},
332 {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}},
333 {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}},
334 {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}},
335 {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}},
336 {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}},
337
338
339
340
341 {"gray", vgradGray(), fillAlpha(255), Over, color.RGBA{136, 136, 136, 255}},
342 {"graySrc", vgradGray(), fillAlpha(255), Src, color.RGBA{136, 136, 136, 255}},
343 {"grayAlpha", vgradGray(), fillAlpha(192), Over, color.RGBA{136, 102, 102, 255}},
344 {"grayAlphaSrc", vgradGray(), fillAlpha(192), Src, color.RGBA{102, 102, 102, 192}},
345 {"grayNil", vgradGray(), nil, Over, color.RGBA{136, 136, 136, 255}},
346 {"grayNilSrc", vgradGray(), nil, Src, color.RGBA{136, 136, 136, 255}},
347
348 {"graySlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
349 Over, color.RGBA{136, 136, 136, 255}},
350 {"graySrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(255),
351 Src, color.RGBA{136, 136, 136, 255}},
352 {"grayAlphaSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
353 Over, color.RGBA{136, 102, 102, 255}},
354 {"grayAlphaSrcSlower", convertToSlowerRGBA(vgradGray()), fillAlpha(192),
355 Src, color.RGBA{102, 102, 102, 192}},
356 {"grayNilSlower", convertToSlowerRGBA(vgradGray()), nil,
357 Over, color.RGBA{136, 136, 136, 255}},
358 {"grayNilSrcSlower", convertToSlowerRGBA(vgradGray()), nil,
359 Src, color.RGBA{136, 136, 136, 255}},
360
361 {"graySlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
362 Over, color.RGBA{136, 136, 136, 255}},
363 {"graySrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(255),
364 Src, color.RGBA{136, 136, 136, 255}},
365 {"grayAlphaSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
366 Over, color.RGBA{136, 102, 102, 255}},
367 {"grayAlphaSrcSlowest", convertToSlowestRGBA(vgradGray()), fillAlpha(192),
368 Src, color.RGBA{102, 102, 102, 192}},
369 {"grayNilSlowest", convertToSlowestRGBA(vgradGray()), nil,
370 Over, color.RGBA{136, 136, 136, 255}},
371 {"grayNilSrcSlowest", convertToSlowestRGBA(vgradGray()), nil,
372 Src, color.RGBA{136, 136, 136, 255}},
373
374
375
376
377 {"cmyk", vgradMagenta(), fillAlpha(255), Over, color.RGBA{192, 89, 192, 255}},
378 {"cmykSrc", vgradMagenta(), fillAlpha(255), Src, color.RGBA{192, 89, 192, 255}},
379 {"cmykAlpha", vgradMagenta(), fillAlpha(192), Over, color.RGBA{178, 67, 145, 255}},
380 {"cmykAlphaSrc", vgradMagenta(), fillAlpha(192), Src, color.RGBA{145, 67, 145, 192}},
381 {"cmykNil", vgradMagenta(), nil, Over, color.RGBA{192, 89, 192, 255}},
382 {"cmykNilSrc", vgradMagenta(), nil, Src, color.RGBA{192, 89, 192, 255}},
383
384
385
386
387
388 {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}},
389 {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}},
390
391 {"genericSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
392 Over, color.RGBA{81, 0, 102, 255}},
393 {"genericSrcSlower", fillBlue(255), convertToSlowerRGBA(vgradAlpha(192)),
394 Src, color.RGBA{0, 0, 102, 102}},
395
396 {"genericSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
397 Over, color.RGBA{81, 0, 102, 255}},
398 {"genericSrcSlowest", fillBlue(255), convertToSlowestRGBA(vgradAlpha(192)),
399 Src, color.RGBA{0, 0, 102, 102}},
400
401
402
403
404
405
406
407 {"rgbaVariableMaskOver", vgradGreen(90), vgradAlpha(192), Over, color.RGBA{117, 19, 0, 255}},
408 {"grayVariableMaskOver", vgradGray(), vgradAlpha(192), Over, color.RGBA{136, 54, 54, 255}},
409 }
410
411 func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image {
412
413
414 b := dst.Bounds()
415 sb := src.Bounds()
416 mb := image.Rect(-1e9, -1e9, 1e9, 1e9)
417 if mask != nil {
418 mb = mask.Bounds()
419 }
420 golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y))
421 for y := r.Min.Y; y < r.Max.Y; y++ {
422 sy := y + sp.Y - r.Min.Y
423 my := y + mp.Y - r.Min.Y
424 for x := r.Min.X; x < r.Max.X; x++ {
425 if !(image.Pt(x, y).In(b)) {
426 continue
427 }
428 sx := x + sp.X - r.Min.X
429 if !(image.Pt(sx, sy).In(sb)) {
430 continue
431 }
432 mx := x + mp.X - r.Min.X
433 if !(image.Pt(mx, my).In(mb)) {
434 continue
435 }
436
437 const M = 1<<16 - 1
438 var dr, dg, db, da uint32
439 if op == Over {
440 dr, dg, db, da = dst.At(x, y).RGBA()
441 }
442 sr, sg, sb, sa := src.At(sx, sy).RGBA()
443 ma := uint32(M)
444 if mask != nil {
445 _, _, _, ma = mask.At(mx, my).RGBA()
446 }
447 a := M - (sa * ma / M)
448 golden.Set(x, y, color.RGBA64{
449 uint16((dr*a + sr*ma) / M),
450 uint16((dg*a + sg*ma) / M),
451 uint16((db*a + sb*ma) / M),
452 uint16((da*a + sa*ma) / M),
453 })
454 }
455 }
456 return golden.SubImage(b)
457 }
458
459 func TestDraw(t *testing.T) {
460 rr := []image.Rectangle{
461 image.Rect(0, 0, 0, 0),
462 image.Rect(0, 0, 16, 16),
463 image.Rect(3, 5, 12, 10),
464 image.Rect(0, 0, 9, 9),
465 image.Rect(8, 8, 16, 16),
466 image.Rect(8, 0, 9, 16),
467 image.Rect(0, 8, 16, 9),
468 image.Rect(8, 8, 9, 9),
469 image.Rect(8, 8, 8, 8),
470 }
471 for _, r := range rr {
472 loop:
473 for _, test := range drawTests {
474 for i := 0; i < 3; i++ {
475 dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image)
476
477
478
479 switch i {
480 case 1:
481 dst = convertToSlowerRGBA(dst)
482 case 2:
483 dst = convertToSlowestRGBA(dst)
484 }
485
486
487 golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
488 b := dst.Bounds()
489 if !b.Eq(golden.Bounds()) {
490 t.Errorf("draw %v %s on %T: bounds %v versus %v",
491 r, test.desc, dst, dst.Bounds(), golden.Bounds())
492 continue
493 }
494
495 DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op)
496 if image.Pt(8, 8).In(r) {
497
498
499 if !eq(dst.At(8, 8), test.expected) {
500 t.Errorf("draw %v %s on %T: at (8, 8) %v versus %v",
501 r, test.desc, dst, dst.At(8, 8), test.expected)
502 continue
503 }
504 }
505
506 for y := b.Min.Y; y < b.Max.Y; y++ {
507 for x := b.Min.X; x < b.Max.X; x++ {
508 if !eq(dst.At(x, y), golden.At(x, y)) {
509 t.Errorf("draw %v %s on %T: at (%d, %d), %v versus golden %v",
510 r, test.desc, dst, x, y, dst.At(x, y), golden.At(x, y))
511 continue loop
512 }
513 }
514 }
515 }
516 }
517 }
518 }
519
520 func TestDrawOverlap(t *testing.T) {
521 for _, op := range []Op{Over, Src} {
522 for yoff := -2; yoff <= 2; yoff++ {
523 loop:
524 for xoff := -2; xoff <= 2; xoff++ {
525 m := gradYellow(127).(*image.RGBA)
526 dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA)
527 src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA)
528 b := dst.Bounds()
529
530 golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
531 if !b.Eq(golden.Bounds()) {
532 t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds())
533 continue
534 }
535
536 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op)
537
538 for y := b.Min.Y; y < b.Max.Y; y++ {
539 for x := b.Min.X; x < b.Max.X; x++ {
540 if !eq(dst.At(x, y), golden.At(x, y)) {
541 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))
542 continue loop
543 }
544 }
545 }
546 }
547 }
548 }
549 }
550
551
552 func TestNonZeroSrcPt(t *testing.T) {
553 a := image.NewRGBA(image.Rect(0, 0, 1, 1))
554 b := image.NewRGBA(image.Rect(0, 0, 2, 2))
555 b.Set(0, 0, color.RGBA{0, 0, 0, 5})
556 b.Set(1, 0, color.RGBA{0, 0, 5, 5})
557 b.Set(0, 1, color.RGBA{0, 5, 0, 5})
558 b.Set(1, 1, color.RGBA{5, 0, 0, 5})
559 Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over)
560 if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) {
561 t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0))
562 }
563 }
564
565 func TestFill(t *testing.T) {
566 rr := []image.Rectangle{
567 image.Rect(0, 0, 0, 0),
568 image.Rect(0, 0, 40, 30),
569 image.Rect(10, 0, 40, 30),
570 image.Rect(0, 20, 40, 30),
571 image.Rect(10, 20, 40, 30),
572 image.Rect(10, 20, 15, 25),
573 image.Rect(10, 0, 35, 30),
574 image.Rect(0, 15, 40, 16),
575 image.Rect(24, 24, 25, 25),
576 image.Rect(23, 23, 26, 26),
577 image.Rect(22, 22, 27, 27),
578 image.Rect(21, 21, 28, 28),
579 image.Rect(20, 20, 29, 29),
580 }
581 for _, r := range rr {
582 m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA)
583 b := m.Bounds()
584 c := color.RGBA{11, 0, 0, 255}
585 src := &image.Uniform{C: c}
586 check := func(desc string) {
587 for y := b.Min.Y; y < b.Max.Y; y++ {
588 for x := b.Min.X; x < b.Max.X; x++ {
589 if !eq(c, m.At(x, y)) {
590 t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y))
591 return
592 }
593 }
594 }
595 }
596
597 for y := b.Min.Y; y < b.Max.Y; y++ {
598 for x := b.Min.X; x < b.Max.X; x++ {
599 DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src)
600 }
601 }
602 check("pixel")
603
604 c = color.RGBA{0, 22, 0, 255}
605 src = &image.Uniform{C: c}
606 for y := b.Min.Y; y < b.Max.Y; y++ {
607 DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src)
608 }
609 check("row")
610
611 c = color.RGBA{0, 0, 33, 255}
612 src = &image.Uniform{C: c}
613 for x := b.Min.X; x < b.Max.X; x++ {
614 DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src)
615 }
616 check("column")
617
618 c = color.RGBA{44, 55, 66, 77}
619 src = &image.Uniform{C: c}
620 DrawMask(m, b, src, image.ZP, nil, image.ZP, Src)
621 check("whole")
622 }
623 }
624
625
626
627
628 func TestFloydSteinbergCheckerboard(t *testing.T) {
629 b := image.Rect(0, 0, 640, 480)
630
631 src := &image.Uniform{color.Gray16{0x7fff}}
632 dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
633 FloydSteinberg.Draw(dst, b, src, image.Point{})
634 nErr := 0
635 for y := b.Min.Y; y < b.Max.Y; y++ {
636 for x := b.Min.X; x < b.Max.X; x++ {
637 got := dst.Pix[dst.PixOffset(x, y)]
638 want := uint8(x+y) % 2
639 if got != want {
640 t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
641 if nErr++; nErr == 10 {
642 t.Fatal("there may be more errors")
643 }
644 }
645 }
646 }
647 }
648
649
650
651 type embeddedPaletted struct {
652 *image.Paletted
653 }
654
655
656
657 func TestPaletted(t *testing.T) {
658 f, err := os.Open("../testdata/video-001.png")
659 if err != nil {
660 t.Fatalf("open: %v", err)
661 }
662 defer f.Close()
663 video001, err := png.Decode(f)
664 if err != nil {
665 t.Fatalf("decode: %v", err)
666 }
667 b := video001.Bounds()
668
669 cgaPalette := color.Palette{
670 color.RGBA{0x00, 0x00, 0x00, 0xff},
671 color.RGBA{0x55, 0xff, 0xff, 0xff},
672 color.RGBA{0xff, 0x55, 0xff, 0xff},
673 color.RGBA{0xff, 0xff, 0xff, 0xff},
674 }
675 drawers := map[string]Drawer{
676 "src": Src,
677 "floyd-steinberg": FloydSteinberg,
678 }
679 sources := map[string]image.Image{
680 "uniform": &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
681 "video001": video001,
682 }
683
684 for dName, d := range drawers {
685 loop:
686 for sName, src := range sources {
687 dst0 := image.NewPaletted(b, cgaPalette)
688 dst1 := image.NewPaletted(b, cgaPalette)
689 d.Draw(dst0, b, src, image.Point{})
690 d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
691 for y := b.Min.Y; y < b.Max.Y; y++ {
692 for x := b.Min.X; x < b.Max.X; x++ {
693 if !eq(dst0.At(x, y), dst1.At(x, y)) {
694 t.Errorf("%s / %s: at (%d, %d), %v versus %v",
695 dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
696 continue loop
697 }
698 }
699 }
700 }
701 }
702 }
703
704 func TestSqDiff(t *testing.T) {
705
706
707
708
709
710 orig := func(x, y int32) uint32 {
711 var d uint32
712 if x > y {
713 d = uint32(x - y)
714 } else {
715 d = uint32(y - x)
716 }
717 return (d * d) >> 2
718 }
719 testCases := []int32{
720 0,
721 1,
722 2,
723 0x0fffd,
724 0x0fffe,
725 0x0ffff,
726 0x10000,
727 0x10001,
728 0x10002,
729 0x7ffffffd,
730 0x7ffffffe,
731 0x7fffffff,
732 -0x7ffffffd,
733 -0x7ffffffe,
734 -0x80000000,
735 }
736 for _, x := range testCases {
737 for _, y := range testCases {
738 if got, want := sqDiff(x, y), orig(x, y); got != want {
739 t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
740 }
741 }
742 }
743 if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
744 t.Fatal(err)
745 }
746 }
747
View as plain text