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 5package draw 6 7import ( 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. 20type slowestRGBA struct { 21 Pix []uint8 22 Stride int 23 Rect image.Rectangle 24} 25 26func (p *slowestRGBA) ColorModel() color.Model { return color.RGBAModel } 27 28func (p *slowestRGBA) Bounds() image.Rectangle { return p.Rect } 29 30func (p *slowestRGBA) At(x, y int) color.Color { 31 return p.RGBA64At(x, y) 32} 33 34func (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 52func (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 65func (p *slowestRGBA) PixOffset(x, y int) int { 66 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 67} 68 69func 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 86func 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// 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. 97type slowerRGBA struct { 98 Pix []uint8 99 Stride int 100 Rect image.Rectangle 101} 102 103func (p *slowerRGBA) ColorModel() color.Model { return color.RGBAModel } 104 105func (p *slowerRGBA) Bounds() image.Rectangle { return p.Rect } 106 107func (p *slowerRGBA) At(x, y int) color.Color { 108 return p.RGBA64At(x, y) 109} 110 111func (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 129func (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 142func (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 154func (p *slowerRGBA) PixOffset(x, y int) int { 155 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 156} 157 158func 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 175func init() { 176 var p any = (*slowerRGBA)(nil) 177 if _, ok := p.(RGBA64Image); !ok { 178 panic("slowerRGBA should be an RGBA64Image") 179 } 180} 181 182func 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 188func fillBlue(alpha int) image.Image { 189 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)}) 190} 191 192func fillAlpha(alpha int) image.Image { 193 return image.NewUniform(color.Alpha{uint8(alpha)}) 194} 195 196func 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 206func 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 216func 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 226func 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 244func 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 254func 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 264func 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 274func 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 284type drawTest struct { 285 desc string 286 src image.Image 287 mask image.Image 288 op Op 289 expected color.Color 290} 291 292var 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 411func 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 459func 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.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 // 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.Point{}, test.mask, image.Point{}, 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 520func 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.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 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 536 DrawMask(dst, b, src, src.Bounds().Min, nil, image.Point{}, 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. 552func 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 565func 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, 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.Point{}, nil, image.Point{}, Src) 621 check("whole") 622 } 623} 624 625func 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 // dst and src are 1x3 images but the dr rectangle (and hence the overlap) 637 // is only 1x2. The Draw call should affect dst's pixels at (1, 10) and (2, 638 // 10) but the pixel at (0, 10) should be untouched. 639 // 640 // The src image is entirely transparent (and the Draw operator is Src) so 641 // the two touched pixels should be set to transparent colors. 642 // 643 // In general, Go's color.Color type (and specifically the Color.RGBA 644 // method) works in premultiplied alpha, where there's no difference 645 // between "transparent blue" and "transparent red". It's all "just 646 // transparent" and canonically "transparent black" (all zeroes). 647 // 648 // However, since the operator is Src (so the pixels are 'copied', not 649 // 'blended') and both dst and src images are *image.NRGBA (N stands for 650 // Non-premultiplied alpha which *does* distinguish "transparent blue" and 651 // "transparent red"), we prefer that this distinction carries through and 652 // dst's touched pixels should be transparent blue and transparent green, 653 // not just transparent black. 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 // Check image.NRGBA64 (not image.NRGBA) similarly. 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// TestFloydSteinbergCheckerboard tests that the result of Floyd-Steinberg 690// error diffusion of a uniform 50% gray source image with a black-and-white 691// palette is a checkerboard pattern. 692func TestFloydSteinbergCheckerboard(t *testing.T) { 693 b := image.Rect(0, 0, 640, 480) 694 // We can't represent 50% exactly, but 0x7fff / 0xffff is close enough. 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// embeddedPaletted is an Image that behaves like an *image.Paletted but whose 714// type is not *image.Paletted. 715type embeddedPaletted struct { 716 *image.Paletted 717} 718 719// TestPaletted tests that the drawPaletted function behaves the same 720// regardless of whether dst is an *image.Paletted. 721func 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 768func TestSqDiff(t *testing.T) { 769 // This test is similar to the one from the image/color package, but 770 // sqDiff in this package accepts int32 instead of uint32, so test it 771 // for appropriate input. 772 773 // canonical sqDiff implementation 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