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.Point{}, test.mask, image.Point{}, 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.Point{}, test.mask, image.Point{}, 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.Point{}, 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.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, Src)
621 check("whole")
622 }
623 }
624
625 func TestDrawSrcNonpremultiplied(t *testing.T) {
626 var (
627 opaqueGray = color.NRGBA{0x99, 0x99, 0x99, 0xff}
628 transparentBlue = color.NRGBA{0x00, 0x00, 0xff, 0x00}
629 transparentGreen = color.NRGBA{0x00, 0xff, 0x00, 0x00}
630 transparentRed = color.NRGBA{0xff, 0x00, 0x00, 0x00}
631
632 opaqueGray64 = color.NRGBA64{0x9999, 0x9999, 0x9999, 0xffff}
633 transparentPurple64 = color.NRGBA64{0xfedc, 0x0000, 0x7654, 0x0000}
634 )
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654 {
655 dst := image.NewNRGBA(image.Rect(0, 10, 3, 11))
656 dst.SetNRGBA(0, 10, opaqueGray)
657 src := image.NewNRGBA(image.Rect(1, 20, 4, 21))
658 src.SetNRGBA(1, 20, transparentBlue)
659 src.SetNRGBA(2, 20, transparentGreen)
660 src.SetNRGBA(3, 20, transparentRed)
661
662 dr := image.Rect(1, 10, 3, 11)
663 Draw(dst, dr, src, image.Point{1, 20}, Src)
664
665 if got, want := dst.At(0, 10), opaqueGray; got != want {
666 t.Errorf("At(0, 10):\ngot %#v\nwant %#v", got, want)
667 }
668 if got, want := dst.At(1, 10), transparentBlue; got != want {
669 t.Errorf("At(1, 10):\ngot %#v\nwant %#v", got, want)
670 }
671 if got, want := dst.At(2, 10), transparentGreen; got != want {
672 t.Errorf("At(2, 10):\ngot %#v\nwant %#v", got, want)
673 }
674 }
675
676
677 {
678 dst := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
679 dst.SetNRGBA64(0, 0, opaqueGray64)
680 src := image.NewNRGBA64(image.Rect(0, 0, 1, 1))
681 src.SetNRGBA64(0, 0, transparentPurple64)
682 Draw(dst, dst.Bounds(), src, image.Point{0, 0}, Src)
683 if got, want := dst.At(0, 0), transparentPurple64; got != want {
684 t.Errorf("At(0, 0):\ngot %#v\nwant %#v", got, want)
685 }
686 }
687 }
688
689
690
691
692 func TestFloydSteinbergCheckerboard(t *testing.T) {
693 b := image.Rect(0, 0, 640, 480)
694
695 src := &image.Uniform{color.Gray16{0x7fff}}
696 dst := image.NewPaletted(b, color.Palette{color.Black, color.White})
697 FloydSteinberg.Draw(dst, b, src, image.Point{})
698 nErr := 0
699 for y := b.Min.Y; y < b.Max.Y; y++ {
700 for x := b.Min.X; x < b.Max.X; x++ {
701 got := dst.Pix[dst.PixOffset(x, y)]
702 want := uint8(x+y) % 2
703 if got != want {
704 t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want)
705 if nErr++; nErr == 10 {
706 t.Fatal("there may be more errors")
707 }
708 }
709 }
710 }
711 }
712
713
714
715 type embeddedPaletted struct {
716 *image.Paletted
717 }
718
719
720
721 func TestPaletted(t *testing.T) {
722 f, err := os.Open("../testdata/video-001.png")
723 if err != nil {
724 t.Fatalf("open: %v", err)
725 }
726 defer f.Close()
727 video001, err := png.Decode(f)
728 if err != nil {
729 t.Fatalf("decode: %v", err)
730 }
731 b := video001.Bounds()
732
733 cgaPalette := color.Palette{
734 color.RGBA{0x00, 0x00, 0x00, 0xff},
735 color.RGBA{0x55, 0xff, 0xff, 0xff},
736 color.RGBA{0xff, 0x55, 0xff, 0xff},
737 color.RGBA{0xff, 0xff, 0xff, 0xff},
738 }
739 drawers := map[string]Drawer{
740 "src": Src,
741 "floyd-steinberg": FloydSteinberg,
742 }
743 sources := map[string]image.Image{
744 "uniform": &image.Uniform{color.RGBA{0xff, 0x7f, 0xff, 0xff}},
745 "video001": video001,
746 }
747
748 for dName, d := range drawers {
749 loop:
750 for sName, src := range sources {
751 dst0 := image.NewPaletted(b, cgaPalette)
752 dst1 := image.NewPaletted(b, cgaPalette)
753 d.Draw(dst0, b, src, image.Point{})
754 d.Draw(embeddedPaletted{dst1}, b, src, image.Point{})
755 for y := b.Min.Y; y < b.Max.Y; y++ {
756 for x := b.Min.X; x < b.Max.X; x++ {
757 if !eq(dst0.At(x, y), dst1.At(x, y)) {
758 t.Errorf("%s / %s: at (%d, %d), %v versus %v",
759 dName, sName, x, y, dst0.At(x, y), dst1.At(x, y))
760 continue loop
761 }
762 }
763 }
764 }
765 }
766 }
767
768 func TestSqDiff(t *testing.T) {
769
770
771
772
773
774 orig := func(x, y int32) uint32 {
775 var d uint32
776 if x > y {
777 d = uint32(x - y)
778 } else {
779 d = uint32(y - x)
780 }
781 return (d * d) >> 2
782 }
783 testCases := []int32{
784 0,
785 1,
786 2,
787 0x0fffd,
788 0x0fffe,
789 0x0ffff,
790 0x10000,
791 0x10001,
792 0x10002,
793 0x7ffffffd,
794 0x7ffffffe,
795 0x7fffffff,
796 -0x7ffffffd,
797 -0x7ffffffe,
798 -0x80000000,
799 }
800 for _, x := range testCases {
801 for _, y := range testCases {
802 if got, want := sqDiff(x, y), orig(x, y); got != want {
803 t.Fatalf("sqDiff(%#x, %#x): got %d, want %d", x, y, got, want)
804 }
805 }
806 }
807 if err := quick.CheckEqual(orig, sqDiff, &quick.Config{MaxCountScale: 10}); err != nil {
808 t.Fatal(err)
809 }
810 }
811
View as plain text