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