github.com/AESNooper/go/src@v0.0.0-20220218095104-b56a4ab1bbbb/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 "testing/quick" 14 ) 15 16 // slowestRGBA is a draw.Image like image.RGBA but it is a different type and 17 // therefore does not trigger the draw.go fastest code paths. 18 // 19 // Unlike slowerRGBA, it does not implement the draw.RGBA64Image interface. 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] // Small cap improves performance, see https://golang.org/issue/27857 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] // Small cap improves performance, see https://golang.org/issue/27857 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 interface{} = (*slowestRGBA)(nil) 88 if _, ok := p.(RGBA64Image); ok { 89 panic("slowestRGBA should not be an RGBA64Image") 90 } 91 } 92 93 // slowerRGBA is a draw.Image like image.RGBA but it is a different type and 94 // therefore does not trigger the draw.go fastest code paths. 95 // 96 // Unlike slowestRGBA, it still implements the draw.RGBA64Image interface. 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] // Small cap improves performance, see https://golang.org/issue/27857 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] // Small cap improves performance, see https://golang.org/issue/27857 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] // Small cap improves performance, see https://golang.org/issue/27857 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 interface{} = (*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 // Uniform mask (0% opaque). 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 // Uniform mask (100%, 75%, nil) and uniform source. 297 // At (x, y) == (8, 8): 298 // The destination pixel is {136, 0, 0, 255}. 299 // The source pixel is {0, 0, 90, 90}. 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 // Uniform mask (100%, 75%, nil) and variable source. 307 // At (x, y) == (8, 8): 308 // The destination pixel is {136, 0, 0, 255}. 309 // The source pixel is {0, 48, 0, 90}. 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 // Uniform mask (100%, 75%, nil) and variable NRGBA source. 317 // At (x, y) == (8, 8): 318 // The destination pixel is {136, 0, 0, 255}. 319 // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. 320 // The result pixel is different than in the "copy*" test cases because of rounding errors. 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 // Uniform mask (100%, 75%, nil) and variable YCbCr source. 328 // At (x, y) == (8, 8): 329 // The destination pixel is {136, 0, 0, 255}. 330 // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. 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 // Uniform mask (100%, 75%, nil) and variable Gray source. 338 // At (x, y) == (8, 8): 339 // The destination pixel is {136, 0, 0, 255}. 340 // The source pixel is {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space. 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 // Same again, but with a slowerRGBA source. 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 // Same again, but with a slowestRGBA source. 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 // Uniform mask (100%, 75%, nil) and variable CMYK source. 374 // At (x, y) == (8, 8): 375 // The destination pixel is {136, 0, 0, 255}. 376 // The source pixel is {0, 136, 0, 63} in CMYK-space, which is {192, 89, 192} in RGB-space. 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 // Variable mask and uniform source. 384 // At (x, y) == (8, 8): 385 // The destination pixel is {136, 0, 0, 255}. 386 // The source pixel is {0, 0, 255, 255}. 387 // The mask pixel's alpha is 102, or 40%. 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 // Same again, but with a slowerRGBA mask. 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 // Same again, but with a slowestRGBA mask. 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 // Variable mask and variable source. 401 // At (x, y) == (8, 8): 402 // The destination pixel is {136, 0, 0, 255}. 403 // The source pixel is: 404 // - {0, 48, 0, 90}. 405 // - {136} in Gray-space, which is {136, 136, 136, 255} in RGBA-space. 406 // The mask pixel's alpha is 102, or 40%. 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 // Since golden is a newly allocated image, we don't have to check if the 413 // input source and mask images and the output golden image overlap. 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 // For i != 0, substitute a different-typed dst that will take 477 // us off the fastest code paths. We should still get the same 478 // result, in terms of final pixel RGBA values. 479 switch i { 480 case 1: 481 dst = convertToSlowerRGBA(dst) 482 case 2: 483 dst = convertToSlowestRGBA(dst) 484 } 485 486 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 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 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 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 // Check that the resultant pixel at (8, 8) matches what we expect 498 // (the expected value can be verified by hand). 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 // Check that the resultant dst image matches the golden output. 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 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 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 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 536 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 537 // Check that the resultant dst image matches the golden output. 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 // TestNonZeroSrcPt checks drawing with a non-zero src point parameter. 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 // Draw 1 pixel at a time. 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 // Draw 1 row at a time. 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 // Draw 1 column at a time. 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 // Draw the whole image at once. 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 // TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg 626 // error diffusion of a uniform 50% gray source image with a black-and-white 627 // palette is a checkerboard pattern. 628 func TestFloydSteinbergCheckerboard(t *testing.T) { 629 b := image.Rect(0, 0, 640, 480) 630 // We can't represent 50% exactly, but 0x7fff / 0xffff is close enough. 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 // embeddedPaletted is an Image that behaves like an *image.Paletted but whose 650 // type is not *image.Paletted. 651 type embeddedPaletted struct { 652 *image.Paletted 653 } 654 655 // TestPaletted tests that the drawPaletted function behaves the same 656 // regardless of whether dst is an *image.Paletted. 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 // This test is similar to the one from the image/color package, but 706 // sqDiff in this package accepts int32 instead of uint32, so test it 707 // for appropriate input. 708 709 // canonical sqDiff implementation 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 }