github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/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 "testing" 11 ) 12 13 func eq(c0, c1 color.Color) bool { 14 r0, g0, b0, a0 := c0.RGBA() 15 r1, g1, b1, a1 := c1.RGBA() 16 return r0 == r1 && g0 == g1 && b0 == b1 && a0 == a1 17 } 18 19 func fillBlue(alpha int) image.Image { 20 return image.NewUniform(color.RGBA{0, 0, uint8(alpha), uint8(alpha)}) 21 } 22 23 func fillAlpha(alpha int) image.Image { 24 return image.NewUniform(color.Alpha{uint8(alpha)}) 25 } 26 27 func vgradGreen(alpha int) image.Image { 28 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 29 for y := 0; y < 16; y++ { 30 for x := 0; x < 16; x++ { 31 m.Set(x, y, color.RGBA{0, uint8(y * alpha / 15), 0, uint8(alpha)}) 32 } 33 } 34 return m 35 } 36 37 func vgradAlpha(alpha int) image.Image { 38 m := image.NewAlpha(image.Rect(0, 0, 16, 16)) 39 for y := 0; y < 16; y++ { 40 for x := 0; x < 16; x++ { 41 m.Set(x, y, color.Alpha{uint8(y * alpha / 15)}) 42 } 43 } 44 return m 45 } 46 47 func vgradGreenNRGBA(alpha int) image.Image { 48 m := image.NewNRGBA(image.Rect(0, 0, 16, 16)) 49 for y := 0; y < 16; y++ { 50 for x := 0; x < 16; x++ { 51 m.Set(x, y, color.RGBA{0, uint8(y * 0x11), 0, uint8(alpha)}) 52 } 53 } 54 return m 55 } 56 57 func vgradCr() image.Image { 58 m := &image.YCbCr{ 59 Y: make([]byte, 16*16), 60 Cb: make([]byte, 16*16), 61 Cr: make([]byte, 16*16), 62 YStride: 16, 63 CStride: 16, 64 SubsampleRatio: image.YCbCrSubsampleRatio444, 65 Rect: image.Rect(0, 0, 16, 16), 66 } 67 for y := 0; y < 16; y++ { 68 for x := 0; x < 16; x++ { 69 m.Cr[y*m.CStride+x] = uint8(y * 0x11) 70 } 71 } 72 return m 73 } 74 75 func hgradRed(alpha int) Image { 76 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 77 for y := 0; y < 16; y++ { 78 for x := 0; x < 16; x++ { 79 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), 0, 0, uint8(alpha)}) 80 } 81 } 82 return m 83 } 84 85 func gradYellow(alpha int) Image { 86 m := image.NewRGBA(image.Rect(0, 0, 16, 16)) 87 for y := 0; y < 16; y++ { 88 for x := 0; x < 16; x++ { 89 m.Set(x, y, color.RGBA{uint8(x * alpha / 15), uint8(y * alpha / 15), 0, uint8(alpha)}) 90 } 91 } 92 return m 93 } 94 95 type drawTest struct { 96 desc string 97 src image.Image 98 mask image.Image 99 op Op 100 expected color.Color 101 } 102 103 var drawTests = []drawTest{ 104 // Uniform mask (0% opaque). 105 {"nop", vgradGreen(255), fillAlpha(0), Over, color.RGBA{136, 0, 0, 255}}, 106 {"clear", vgradGreen(255), fillAlpha(0), Src, color.RGBA{0, 0, 0, 0}}, 107 // Uniform mask (100%, 75%, nil) and uniform source. 108 // At (x, y) == (8, 8): 109 // The destination pixel is {136, 0, 0, 255}. 110 // The source pixel is {0, 0, 90, 90}. 111 {"fill", fillBlue(90), fillAlpha(255), Over, color.RGBA{88, 0, 90, 255}}, 112 {"fillSrc", fillBlue(90), fillAlpha(255), Src, color.RGBA{0, 0, 90, 90}}, 113 {"fillAlpha", fillBlue(90), fillAlpha(192), Over, color.RGBA{100, 0, 68, 255}}, 114 {"fillAlphaSrc", fillBlue(90), fillAlpha(192), Src, color.RGBA{0, 0, 68, 68}}, 115 {"fillNil", fillBlue(90), nil, Over, color.RGBA{88, 0, 90, 255}}, 116 {"fillNilSrc", fillBlue(90), nil, Src, color.RGBA{0, 0, 90, 90}}, 117 // Uniform mask (100%, 75%, nil) and variable source. 118 // At (x, y) == (8, 8): 119 // The destination pixel is {136, 0, 0, 255}. 120 // The source pixel is {0, 48, 0, 90}. 121 {"copy", vgradGreen(90), fillAlpha(255), Over, color.RGBA{88, 48, 0, 255}}, 122 {"copySrc", vgradGreen(90), fillAlpha(255), Src, color.RGBA{0, 48, 0, 90}}, 123 {"copyAlpha", vgradGreen(90), fillAlpha(192), Over, color.RGBA{100, 36, 0, 255}}, 124 {"copyAlphaSrc", vgradGreen(90), fillAlpha(192), Src, color.RGBA{0, 36, 0, 68}}, 125 {"copyNil", vgradGreen(90), nil, Over, color.RGBA{88, 48, 0, 255}}, 126 {"copyNilSrc", vgradGreen(90), nil, Src, color.RGBA{0, 48, 0, 90}}, 127 // Uniform mask (100%, 75%, nil) and variable NRGBA source. 128 // At (x, y) == (8, 8): 129 // The destination pixel is {136, 0, 0, 255}. 130 // The source pixel is {0, 136, 0, 90} in NRGBA-space, which is {0, 48, 0, 90} in RGBA-space. 131 // The result pixel is different than in the "copy*" test cases because of rounding errors. 132 {"nrgba", vgradGreenNRGBA(90), fillAlpha(255), Over, color.RGBA{88, 46, 0, 255}}, 133 {"nrgbaSrc", vgradGreenNRGBA(90), fillAlpha(255), Src, color.RGBA{0, 46, 0, 90}}, 134 {"nrgbaAlpha", vgradGreenNRGBA(90), fillAlpha(192), Over, color.RGBA{100, 34, 0, 255}}, 135 {"nrgbaAlphaSrc", vgradGreenNRGBA(90), fillAlpha(192), Src, color.RGBA{0, 34, 0, 68}}, 136 {"nrgbaNil", vgradGreenNRGBA(90), nil, Over, color.RGBA{88, 46, 0, 255}}, 137 {"nrgbaNilSrc", vgradGreenNRGBA(90), nil, Src, color.RGBA{0, 46, 0, 90}}, 138 // Uniform mask (100%, 75%, nil) and variable YCbCr source. 139 // At (x, y) == (8, 8): 140 // The destination pixel is {136, 0, 0, 255}. 141 // The source pixel is {0, 0, 136} in YCbCr-space, which is {11, 38, 0, 255} in RGB-space. 142 {"ycbcr", vgradCr(), fillAlpha(255), Over, color.RGBA{11, 38, 0, 255}}, 143 {"ycbcrSrc", vgradCr(), fillAlpha(255), Src, color.RGBA{11, 38, 0, 255}}, 144 {"ycbcrAlpha", vgradCr(), fillAlpha(192), Over, color.RGBA{42, 28, 0, 255}}, 145 {"ycbcrAlphaSrc", vgradCr(), fillAlpha(192), Src, color.RGBA{8, 28, 0, 192}}, 146 {"ycbcrNil", vgradCr(), nil, Over, color.RGBA{11, 38, 0, 255}}, 147 {"ycbcrNilSrc", vgradCr(), nil, Src, color.RGBA{11, 38, 0, 255}}, 148 // Variable mask and variable source. 149 // At (x, y) == (8, 8): 150 // The destination pixel is {136, 0, 0, 255}. 151 // The source pixel is {0, 0, 255, 255}. 152 // The mask pixel's alpha is 102, or 40%. 153 {"generic", fillBlue(255), vgradAlpha(192), Over, color.RGBA{81, 0, 102, 255}}, 154 {"genericSrc", fillBlue(255), vgradAlpha(192), Src, color.RGBA{0, 0, 102, 102}}, 155 } 156 157 func makeGolden(dst image.Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) image.Image { 158 // Since golden is a newly allocated image, we don't have to check if the 159 // input source and mask images and the output golden image overlap. 160 b := dst.Bounds() 161 sb := src.Bounds() 162 mb := image.Rect(-1e9, -1e9, 1e9, 1e9) 163 if mask != nil { 164 mb = mask.Bounds() 165 } 166 golden := image.NewRGBA(image.Rect(0, 0, b.Max.X, b.Max.Y)) 167 for y := r.Min.Y; y < r.Max.Y; y++ { 168 sy := y + sp.Y - r.Min.Y 169 my := y + mp.Y - r.Min.Y 170 for x := r.Min.X; x < r.Max.X; x++ { 171 if !(image.Pt(x, y).In(b)) { 172 continue 173 } 174 sx := x + sp.X - r.Min.X 175 if !(image.Pt(sx, sy).In(sb)) { 176 continue 177 } 178 mx := x + mp.X - r.Min.X 179 if !(image.Pt(mx, my).In(mb)) { 180 continue 181 } 182 183 const M = 1<<16 - 1 184 var dr, dg, db, da uint32 185 if op == Over { 186 dr, dg, db, da = dst.At(x, y).RGBA() 187 } 188 sr, sg, sb, sa := src.At(sx, sy).RGBA() 189 ma := uint32(M) 190 if mask != nil { 191 _, _, _, ma = mask.At(mx, my).RGBA() 192 } 193 a := M - (sa * ma / M) 194 golden.Set(x, y, color.RGBA64{ 195 uint16((dr*a + sr*ma) / M), 196 uint16((dg*a + sg*ma) / M), 197 uint16((db*a + sb*ma) / M), 198 uint16((da*a + sa*ma) / M), 199 }) 200 } 201 } 202 return golden.SubImage(b) 203 } 204 205 func TestDraw(t *testing.T) { 206 rr := []image.Rectangle{ 207 image.Rect(0, 0, 0, 0), 208 image.Rect(0, 0, 16, 16), 209 image.Rect(3, 5, 12, 10), 210 image.Rect(0, 0, 9, 9), 211 image.Rect(8, 8, 16, 16), 212 image.Rect(8, 0, 9, 16), 213 image.Rect(0, 8, 16, 9), 214 image.Rect(8, 8, 9, 9), 215 image.Rect(8, 8, 8, 8), 216 } 217 for _, r := range rr { 218 loop: 219 for _, test := range drawTests { 220 dst := hgradRed(255).(*image.RGBA).SubImage(r).(Image) 221 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 222 golden := makeGolden(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 223 b := dst.Bounds() 224 if !b.Eq(golden.Bounds()) { 225 t.Errorf("draw %v %s: bounds %v versus %v", r, test.desc, dst.Bounds(), golden.Bounds()) 226 continue 227 } 228 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 229 DrawMask(dst, image.Rect(0, 0, 16, 16), test.src, image.ZP, test.mask, image.ZP, test.op) 230 if image.Pt(8, 8).In(r) { 231 // Check that the resultant pixel at (8, 8) matches what we expect 232 // (the expected value can be verified by hand). 233 if !eq(dst.At(8, 8), test.expected) { 234 t.Errorf("draw %v %s: at (8, 8) %v versus %v", r, test.desc, dst.At(8, 8), test.expected) 235 continue 236 } 237 } 238 // Check that the resultant dst image matches the golden output. 239 for y := b.Min.Y; y < b.Max.Y; y++ { 240 for x := b.Min.X; x < b.Max.X; x++ { 241 if !eq(dst.At(x, y), golden.At(x, y)) { 242 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)) 243 continue loop 244 } 245 } 246 } 247 } 248 } 249 } 250 251 func TestDrawOverlap(t *testing.T) { 252 for _, op := range []Op{Over, Src} { 253 for yoff := -2; yoff <= 2; yoff++ { 254 loop: 255 for xoff := -2; xoff <= 2; xoff++ { 256 m := gradYellow(127).(*image.RGBA) 257 dst := m.SubImage(image.Rect(5, 5, 10, 10)).(*image.RGBA) 258 src := m.SubImage(image.Rect(5+xoff, 5+yoff, 10+xoff, 10+yoff)).(*image.RGBA) 259 b := dst.Bounds() 260 // Draw the (src, mask, op) onto a copy of dst using a slow but obviously correct implementation. 261 golden := makeGolden(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 262 if !b.Eq(golden.Bounds()) { 263 t.Errorf("drawOverlap xoff=%d,yoff=%d: bounds %v versus %v", xoff, yoff, dst.Bounds(), golden.Bounds()) 264 continue 265 } 266 // Draw the same combination onto the actual dst using the optimized DrawMask implementation. 267 DrawMask(dst, b, src, src.Bounds().Min, nil, image.ZP, op) 268 // Check that the resultant dst image matches the golden output. 269 for y := b.Min.Y; y < b.Max.Y; y++ { 270 for x := b.Min.X; x < b.Max.X; x++ { 271 if !eq(dst.At(x, y), golden.At(x, y)) { 272 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)) 273 continue loop 274 } 275 } 276 } 277 } 278 } 279 } 280 } 281 282 // TestNonZeroSrcPt checks drawing with a non-zero src point parameter. 283 func TestNonZeroSrcPt(t *testing.T) { 284 a := image.NewRGBA(image.Rect(0, 0, 1, 1)) 285 b := image.NewRGBA(image.Rect(0, 0, 2, 2)) 286 b.Set(0, 0, color.RGBA{0, 0, 0, 5}) 287 b.Set(1, 0, color.RGBA{0, 0, 5, 5}) 288 b.Set(0, 1, color.RGBA{0, 5, 0, 5}) 289 b.Set(1, 1, color.RGBA{5, 0, 0, 5}) 290 Draw(a, image.Rect(0, 0, 1, 1), b, image.Pt(1, 1), Over) 291 if !eq(color.RGBA{5, 0, 0, 5}, a.At(0, 0)) { 292 t.Errorf("non-zero src pt: want %v got %v", color.RGBA{5, 0, 0, 5}, a.At(0, 0)) 293 } 294 } 295 296 func TestFill(t *testing.T) { 297 rr := []image.Rectangle{ 298 image.Rect(0, 0, 0, 0), 299 image.Rect(0, 0, 40, 30), 300 image.Rect(10, 0, 40, 30), 301 image.Rect(0, 20, 40, 30), 302 image.Rect(10, 20, 40, 30), 303 image.Rect(10, 20, 15, 25), 304 image.Rect(10, 0, 35, 30), 305 image.Rect(0, 15, 40, 16), 306 image.Rect(24, 24, 25, 25), 307 image.Rect(23, 23, 26, 26), 308 image.Rect(22, 22, 27, 27), 309 image.Rect(21, 21, 28, 28), 310 image.Rect(20, 20, 29, 29), 311 } 312 for _, r := range rr { 313 m := image.NewRGBA(image.Rect(0, 0, 40, 30)).SubImage(r).(*image.RGBA) 314 b := m.Bounds() 315 c := color.RGBA{11, 0, 0, 255} 316 src := &image.Uniform{C: c} 317 check := func(desc string) { 318 for y := b.Min.Y; y < b.Max.Y; y++ { 319 for x := b.Min.X; x < b.Max.X; x++ { 320 if !eq(c, m.At(x, y)) { 321 t.Errorf("%s fill: at (%d, %d), sub-image bounds=%v: want %v got %v", desc, x, y, r, c, m.At(x, y)) 322 return 323 } 324 } 325 } 326 } 327 // Draw 1 pixel at a time. 328 for y := b.Min.Y; y < b.Max.Y; y++ { 329 for x := b.Min.X; x < b.Max.X; x++ { 330 DrawMask(m, image.Rect(x, y, x+1, y+1), src, image.ZP, nil, image.ZP, Src) 331 } 332 } 333 check("pixel") 334 // Draw 1 row at a time. 335 c = color.RGBA{0, 22, 0, 255} 336 src = &image.Uniform{C: c} 337 for y := b.Min.Y; y < b.Max.Y; y++ { 338 DrawMask(m, image.Rect(b.Min.X, y, b.Max.X, y+1), src, image.ZP, nil, image.ZP, Src) 339 } 340 check("row") 341 // Draw 1 column at a time. 342 c = color.RGBA{0, 0, 33, 255} 343 src = &image.Uniform{C: c} 344 for x := b.Min.X; x < b.Max.X; x++ { 345 DrawMask(m, image.Rect(x, b.Min.Y, x+1, b.Max.Y), src, image.ZP, nil, image.ZP, Src) 346 } 347 check("column") 348 // Draw the whole image at once. 349 c = color.RGBA{44, 55, 66, 77} 350 src = &image.Uniform{C: c} 351 DrawMask(m, b, src, image.ZP, nil, image.ZP, Src) 352 check("whole") 353 } 354 }