github.com/ccccaoqing/test@v0.0.0-20220510085219-3985d23445c0/src/image/draw/draw_test.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package draw 6 7 import ( 8 "image" 9 "image/color" 10 "image/png" 11 "os" 12 "testing" 13 ) 14 15 func eq(c0, c1 color.Color) bool { 16 r0, g0, b0, a0 := c0.RGBA() 17 r1, g1, b1, a1 := c1.RGBA() 18 return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 19 } 20 21 func fillBlue(alpha int) image.Image { 22 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)}) 23 } 24 25 func fillAlpha(alpha int) image.Image { 26 return image.NewUniform(color.Alpha{uint8(alpha)}) 27 } 28 29 func vgradGreen(alpha int) image.Image { 30 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 31 for y := 0; y < 16; y++ { 32 for x := 0; x < 16; x++ { 33 m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)}) 34 } 35 } 36 return m 37 } 38 39 func vgradAlpha(alpha int) image.Image { 40 m := image.NewAlpha(image.Rect(0, 0, 16, 16)) 41 for y := 0; y < 16; y++ { 42 for x := 0; x < 16; x++ { 43 m.Set(x, y, color.Alpha{uint8(y * alpha / 15)}) 44 } 45 } 46 return m 47 } 48 49 func vgradGreenNRGBA(alpha int) image.Image { 50 m := image.NewNRGBA(image.Rect(0, 0, 16, 16)) 51 for y := 0; y < 16; y++ { 52 for x := 0; x < 16; x++ { 53 m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)}) 54 } 55 } 56 return m 57 } 58 59 func vgradCr() image.Image { 60 m := &image.YCbCr{ 61 Y: make([]byte, 16*16), 62 Cb: make([]byte, 16*16), 63 Cr: make([]byte, 16*16), 64 YStride: 16, 65 CStride: 16, 66 SubsampleRatio: image.YCbCrSubsampleRatio444, 67 Rect: image.Rect(0, 0, 16, 16), 68 } 69 for y := 0; y < 16; y++ { 70 for x := 0; x < 16; x++ { 71 m.Cr[y*m.CStride+x] = uint8(y * 0x11) 72 } 73 } 74 return m 75 } 76 77 func hgradRed(alpha int) Image { 78 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 79 for y := 0; y < 16; y++ { 80 for x := 0; x < 16; x++ { 81 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)}) 82 } 83 } 84 return m 85 } 86 87 func gradYellow(alpha int) Image { 88 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 89 for y := 0; y < 16; y++ { 90 for x := 0; x < 16; x++ { 91 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)}) 92 } 93 } 94 return m 95 } 96 97 type drawTest struct { 98 desc string 99 src image.Image 100 mask image.Image 101 op Op 102 expected color.Color 103 } 104 105 var drawTests = []drawTest{ 106 // Uniform mask (0% opaque). 107 {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}}, 108 {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}}, 109 // Uniform mask (100%, 75%, nil) and uniform source. 110 // At (x, y) == (8, 8): 111 // The destination pixel is {136, 0, 0, 255}. 112 // The source pixel is {0, 0, 90, 90}. 113 {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}}, 114 {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}}, 115 {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}}, 116 {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}}, 117 {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}}, 118 {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}}, 119 // Uniform mask (100%, 75%, nil) and variable source. 120 // At (x, y) == (8, 8): 121 // The destination pixel is {136, 0, 0, 255}. 122 // The source pixel is {0, 48, 0, 90}. 123 {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}}, 124 {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}}, 125 {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}}, 126 {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}}, 127 {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}}, 128 {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}}, 129 // Uniform mask (100%, 75%, nil) and variable NRGBA source. 130 // At (x, y) == (8, 8): 131 // The destination pixel is {136, 0, 0, 255}. 132 // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. 133 // The result pixel is different than in the "copy*" test cases because of rounding errors. 134 {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}}, 135 {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}}, 136 {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}}, 137 {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}}, 138 {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}}, 139 {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}}, 140 // Uniform mask (100%, 75%, nil) and variable YCbCr source. 141 // At (x, y) == (8, 8): 142 // The destination pixel is {136, 0, 0, 255}. 143 // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. 144 {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}}, 145 {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}}, 146 {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}}, 147 {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}}, 148 {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}}, 149 {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}}, 150 // Variable mask and variable source. 151 // At (x, y) == (8, 8): 152 // The destination pixel is {136, 0, 0, 255}. 153 // The source pixel is {0, 0, 255, 255}. 154 // The mask pixel's alpha is 102, or 40%. 155 {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}}, 156 {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}}, 157 } 158 159 func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image { 160 // Since golden is a newly allocated image, we don't have to check if the 161 // input source and mask images and the output golden image overlap. 162 b := dst.Bounds() 163 sb := src.Bounds() 164 mb := image.Rect(-1e9, -1e9, 1e9, 1e9) 165 if mask != nil { 166 mb = mask.Bounds() 167 } 168 golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y)) 169 for y := r.Min.Y; y < r.Max.Y; y++ { 170 sy := y + sp.Y - r.Min.Y 171 my := y + mp.Y - r.Min.Y 172 for x := r.Min.X; x < r.Max.X; x++ { 173 if !(image.Pt(x, y).In(b)) { 174 continue 175 } 176 sx := x + sp.X - r.Min.X 177 if !(image.Pt(sx, sy).In(sb)) { 178 continue 179 } 180 mx := x + mp.X - r.Min.X 181 if !(image.Pt(mx, my).In(mb)) { 182 continue 183 } 184 185 const M = 1<<16 - 1 186 var dr, dg, db, da uint32 187 if op == Over { 188 dr, dg, db, da = dst.At(x, y).RGBA() 189 } 190 sr, sg, sb, sa := src.At(sx, sy).RGBA() 191 ma := uint32(M) 192 if mask != nil { 193 _, _, _, ma = mask.At(mx, my).RGBA() 194 } 195 a := M - (sa * ma / M) 196 golden.Set(x, y, color.RGBA64{ 197 uint16((dr*a + sr*ma) / M), 198 uint16((dg*a + sg*ma) / M), 199 uint16((db*a + sb*ma) / M), 200 uint16((da*a + sa*ma) / M), 201 }) 202 } 203 } 204 return golden.SubImage(b) 205 } 206 207 func TestDraw(t *testing.T) { 208 rr := []image.Rectangle{ 209 image.Rect(0, 0, 0, 0), 210 image.Rect(0, 0, 16, 16), 211 image.Rect(3, 5, 12, 10), 212 image.Rect(0, 0, 9, 9), 213 image.Rect(8, 8, 16, 16), 214 image.Rect(8, 0, 9, 16), 215 image.Rect(0, 8, 16, 9), 216 image.Rect(8, 8, 9, 9), 217 image.Rect(8, 8, 8, 8), 218 } 219 for _, r := range rr { 220 loop: 221 for _, test := range drawTests { 222 dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image) 223 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 224 golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 225 b := dst.Bounds() 226 if !b.Eq(golden.Bounds()) { 227 t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds()) 228 continue 229 } 230 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 231 DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 232 if image.Pt(8, 8).In(r) { 233 // Check that the resultant pixel at (8, 8) matches what we expect 234 // (the expected value can be verified by hand). 235 if !eq(dst.At(8, 8), test.expected) { 236 t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected) 237 continue 238 } 239 } 240 // Check that the resultant dst image matches the golden output. 241 for y := b.Min.Y; y < b.Max.Y; y++ { 242 for x := b.Min.X; x < b.Max.X; x++ { 243 if !eq(dst.At(x, y), golden.At(x, y)) { 244 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)) 245 continue loop 246 } 247 } 248 } 249 } 250 } 251 } 252 253 func TestDrawOverlap(t *testing.T) { 254 for _, op := range []Op{Over, Src} { 255 for yoff := -2; yoff <= 2; yoff++ { 256 loop: 257 for xoff := -2; xoff <= 2; xoff++ { 258 m := gradYellow(127).(*image.RGBA) 259 dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA) 260 src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA) 261 b := dst.Bounds() 262 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 263 golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 264 if !b.Eq(golden.Bounds()) { 265 t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds()) 266 continue 267 } 268 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 269 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 270 // Check that the resultant dst image matches the golden output. 271 for y := b.Min.Y; y < b.Max.Y; y++ { 272 for x := b.Min.X; x < b.Max.X; x++ { 273 if !eq(dst.At(x, y), golden.At(x, y)) { 274 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)) 275 continue loop 276 } 277 } 278 } 279 } 280 } 281 } 282 } 283 284 // TestNonZeroSrcPt checks drawing with a non-zero src point parameter. 285 func TestNonZeroSrcPt(t *testing.T) { 286 a := image.NewRGBA(image.Rect(0, 0, 1, 1)) 287 b := image.NewRGBA(image.Rect(0, 0, 2, 2)) 288 b.Set(0, 0, color.RGBA{0, 0, 0, 5}) 289 b.Set(1, 0, color.RGBA{0, 0, 5, 5}) 290 b.Set(0, 1, color.RGBA{0, 5, 0, 5}) 291 b.Set(1, 1, color.RGBA{5, 0, 0, 5}) 292 Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over) 293 if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) { 294 t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0)) 295 } 296 } 297 298 func TestFill(t *testing.T) { 299 rr := []image.Rectangle{ 300 image.Rect(0, 0, 0, 0), 301 image.Rect(0, 0, 40, 30), 302 image.Rect(10, 0, 40, 30), 303 image.Rect(0, 20, 40, 30), 304 image.Rect(10, 20, 40, 30), 305 image.Rect(10, 20, 15, 25), 306 image.Rect(10, 0, 35, 30), 307 image.Rect(0, 15, 40, 16), 308 image.Rect(24, 24, 25, 25), 309 image.Rect(23, 23, 26, 26), 310 image.Rect(22, 22, 27, 27), 311 image.Rect(21, 21, 28, 28), 312 image.Rect(20, 20, 29, 29), 313 } 314 for _, r := range rr { 315 m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA) 316 b := m.Bounds() 317 c := color.RGBA{11, 0, 0, 255} 318 src := &image.Uniform{C: c} 319 check := func(desc string) { 320 for y := b.Min.Y; y < b.Max.Y; y++ { 321 for x := b.Min.X; x < b.Max.X; x++ { 322 if !eq(c, m.At(x, y)) { 323 t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y)) 324 return 325 } 326 } 327 } 328 } 329 // Draw 1 pixel at a time. 330 for y := b.Min.Y; y < b.Max.Y; y++ { 331 for x := b.Min.X; x < b.Max.X; x++ { 332 DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src) 333 } 334 } 335 check("pixel") 336 // Draw 1 row at a time. 337 c = color.RGBA{0, 22, 0, 255} 338 src = &image.Uniform{C: c} 339 for y := b.Min.Y; y < b.Max.Y; y++ { 340 DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src) 341 } 342 check("row") 343 // Draw 1 column at a time. 344 c = color.RGBA{0, 0, 33, 255} 345 src = &image.Uniform{C: c} 346 for x := b.Min.X; x < b.Max.X; x++ { 347 DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src) 348 } 349 check("column") 350 // Draw the whole image at once. 351 c = color.RGBA{44, 55, 66, 77} 352 src = &image.Uniform{C: c} 353 DrawMask(m, b, src, image.ZP, nil, image.ZP, Src) 354 check("whole") 355 } 356 } 357 358 // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg 359 // error diffusion of a uniform 50% gray source image with a black-and-white 360 // palette is a checkerboard pattern. 361 func TestFloydSteinbergCheckerboard(t *testing.T) { 362 b := image.Rect(0, 0, 640, 480) 363 // We can't represent 50% exactly, but 0x7fff / 0xffff is close enough. 364 src := &image.Uniform{color.Gray16{0x7fff}} 365 dst := image.NewPaletted(b, color.Palette{color.Black, color.White}) 366 FloydSteinberg.Draw(dst, b, src, image.Point{}) 367 nErr := 0 368 for y := b.Min.Y; y < b.Max.Y; y++ { 369 for x := b.Min.X; x < b.Max.X; x++ { 370 got := dst.Pix[dst.PixOffset(x, y)] 371 want := uint8(x+y) % 2 372 if got != want { 373 t.Errorf("at (%d, %d): got %d, want %d", x, y, got, want) 374 if nErr++; nErr == 10 { 375 t.Fatal("there may be more errors") 376 } 377 } 378 } 379 } 380 } 381 382 // embeddedPaletted is an Image that behaves like an *image.Paletted but whose 383 // type is not *image.Paletted. 384 type embeddedPaletted struct { 385 *image.Paletted 386 } 387 388 // TestPaletted tests that the drawPaletted function behaves the same 389 // regardless of whether dst is an *image.Paletted. 390 func TestPaletted(t *testing.T) { 391 f, err := os.Open("../testdata/video-001.png") 392 if err != nil { 393 t.Fatalf("open: %v", err) 394 } 395 defer f.Close() 396 src, err := png.Decode(f) 397 if err != nil { 398 t.Fatalf("decode: %v", err) 399 } 400 b := src.Bounds() 401 402 cgaPalette := color.Palette{ 403 color.RGBA{0x00, 0x00, 0x00, 0xff}, 404 color.RGBA{0x55, 0xff, 0xff, 0xff}, 405 color.RGBA{0xff, 0x55, 0xff, 0xff}, 406 color.RGBA{0xff, 0xff, 0xff, 0xff}, 407 } 408 drawers := map[string]Drawer{ 409 "src": Src, 410 "floyd-steinberg": FloydSteinberg, 411 } 412 413 loop: 414 for dName, d := range drawers { 415 dst0 := image.NewPaletted(b, cgaPalette) 416 dst1 := image.NewPaletted(b, cgaPalette) 417 d.Draw(dst0, b, src, image.Point{}) 418 d.Draw(embeddedPaletted{dst1}, b, src, image.Point{}) 419 for y := b.Min.Y; y < b.Max.Y; y++ { 420 for x := b.Min.X; x < b.Max.X; x++ { 421 if !eq(dst0.At(x, y), dst1.At(x, y)) { 422 t.Errorf("%s: at (%d, %d), %v versus %v", 423 dName, x, y, dst0.At(x, y), dst1.At(x, y)) 424 continue loop 425 } 426 } 427 } 428 } 429 }