github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/image/draw/scale_test.go (about) 1 // Copyright 2015 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 "bytes" 9 "flag" 10 "fmt" 11 "image" 12 "image/color" 13 "image/png" 14 "math/rand" 15 "os" 16 "reflect" 17 "testing" 18 19 "golang.org/x/image/math/f64" 20 21 _ "image/jpeg" 22 ) 23 24 var genGoldenFiles = flag.Bool("gen_golden_files", false, "whether to generate the TestXxx golden files.") 25 26 var transformMatrix = func(scale, tx, ty float64) f64.Aff3 { 27 const cos30, sin30 = 0.866025404, 0.5 28 return f64.Aff3{ 29 +scale * cos30, -scale * sin30, tx, 30 +scale * sin30, +scale * cos30, ty, 31 } 32 } 33 34 func encode(filename string, m image.Image) error { 35 f, err := os.Create(filename) 36 if err != nil { 37 return fmt.Errorf("Create: %v", err) 38 } 39 defer f.Close() 40 if err := png.Encode(f, m); err != nil { 41 return fmt.Errorf("Encode: %v", err) 42 } 43 return nil 44 } 45 46 // testInterp tests that interpolating the source image gives the exact 47 // destination image. This is to ensure that any refactoring or optimization of 48 // the interpolation code doesn't change the behavior. Changing the actual 49 // algorithm or kernel used by any particular quality setting will obviously 50 // change the resultant pixels. In such a case, use the gen_golden_files flag 51 // to regenerate the golden files. 52 func testInterp(t *testing.T, w int, h int, direction, prefix, suffix string) { 53 f, err := os.Open("../testdata/" + prefix + suffix) 54 if err != nil { 55 t.Fatalf("Open: %v", err) 56 } 57 defer f.Close() 58 src, _, err := image.Decode(f) 59 if err != nil { 60 t.Fatalf("Decode: %v", err) 61 } 62 63 op, scale := Src, 3.75 64 if prefix == "tux" { 65 op, scale = Over, 0.125 66 } 67 green := image.NewUniform(color.RGBA{0x00, 0x22, 0x11, 0xff}) 68 69 testCases := map[string]Interpolator{ 70 "nn": NearestNeighbor, 71 "ab": ApproxBiLinear, 72 "bl": BiLinear, 73 "cr": CatmullRom, 74 } 75 for name, q := range testCases { 76 goldenFilename := fmt.Sprintf("../testdata/%s-%s-%s.png", prefix, direction, name) 77 78 got := image.NewRGBA(image.Rect(0, 0, w, h)) 79 Copy(got, image.Point{}, green, got.Bounds(), Src, nil) 80 if direction == "rotate" { 81 q.Transform(got, transformMatrix(scale, 40, 10), src, src.Bounds(), op, nil) 82 } else { 83 q.Scale(got, got.Bounds(), src, src.Bounds(), op, nil) 84 } 85 86 if *genGoldenFiles { 87 if err := encode(goldenFilename, got); err != nil { 88 t.Error(err) 89 } 90 continue 91 } 92 93 g, err := os.Open(goldenFilename) 94 if err != nil { 95 t.Errorf("Open: %v", err) 96 continue 97 } 98 defer g.Close() 99 wantRaw, err := png.Decode(g) 100 if err != nil { 101 t.Errorf("Decode: %v", err) 102 continue 103 } 104 // convert wantRaw to RGBA. 105 want, ok := wantRaw.(*image.RGBA) 106 if !ok { 107 b := wantRaw.Bounds() 108 want = image.NewRGBA(b) 109 Draw(want, b, wantRaw, b.Min, Src) 110 } 111 112 if !reflect.DeepEqual(got, want) { 113 t.Errorf("%s: actual image differs from golden image", goldenFilename) 114 continue 115 } 116 } 117 } 118 119 func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "go-turns-two", "-280x360.jpeg") } 120 func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two", "-14x18.png") } 121 func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") } 122 func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") } 123 124 // TestSimpleTransforms tests Scale and Transform calls that simplify to Copy 125 // or Scale calls. 126 func TestSimpleTransforms(t *testing.T) { 127 f, err := os.Open("../testdata/testpattern.png") // A 100x100 image. 128 if err != nil { 129 t.Fatalf("Open: %v", err) 130 } 131 defer f.Close() 132 src, _, err := image.Decode(f) 133 if err != nil { 134 t.Fatalf("Decode: %v", err) 135 } 136 137 dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150)) 138 dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150)) 139 for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} { 140 for _, epsilon := range []float64{0, 1e-50, 1e-1} { 141 Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil) 142 Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil) 143 144 switch op { 145 case "scale/copy": 146 dr := image.Rect(10, 30, 10+100, 30+100) 147 if epsilon > 1e-10 { 148 dr.Max.X++ 149 } 150 Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil) 151 ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil) 152 case "tform/copy": 153 Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil) 154 ApproxBiLinear.Transform(dst1, f64.Aff3{ 155 1, 0 + epsilon, 10, 156 0, 1, 30, 157 }, src, src.Bounds(), Src, nil) 158 case "tform/scale": 159 ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil) 160 ApproxBiLinear.Transform(dst1, f64.Aff3{ 161 0.5, 0.0 + epsilon, 10, 162 0.0, 0.5, 50, 163 }, src, src.Bounds(), Src, nil) 164 } 165 166 differ := !bytes.Equal(dst0.Pix, dst1.Pix) 167 if epsilon > 1e-10 { 168 if !differ { 169 t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon) 170 } 171 } else { 172 if differ { 173 t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon) 174 } 175 } 176 } 177 } 178 } 179 180 func BenchmarkSimpleScaleCopy(b *testing.B) { 181 dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) 182 src := image.NewRGBA(image.Rect(0, 0, 400, 300)) 183 b.ResetTimer() 184 for i := 0; i < b.N; i++ { 185 ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil) 186 } 187 } 188 189 func BenchmarkSimpleTransformCopy(b *testing.B) { 190 dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) 191 src := image.NewRGBA(image.Rect(0, 0, 400, 300)) 192 b.ResetTimer() 193 for i := 0; i < b.N; i++ { 194 ApproxBiLinear.Transform(dst, f64.Aff3{ 195 1, 0, 10, 196 0, 1, 20, 197 }, src, src.Bounds(), Src, nil) 198 } 199 } 200 201 func BenchmarkSimpleTransformScale(b *testing.B) { 202 dst := image.NewRGBA(image.Rect(0, 0, 640, 480)) 203 src := image.NewRGBA(image.Rect(0, 0, 400, 300)) 204 b.ResetTimer() 205 for i := 0; i < b.N; i++ { 206 ApproxBiLinear.Transform(dst, f64.Aff3{ 207 0.5, 0.0, 10, 208 0.0, 0.5, 20, 209 }, src, src.Bounds(), Src, nil) 210 } 211 } 212 213 func TestOps(t *testing.T) { 214 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) 215 testCases := map[Op]color.RGBA{ 216 Over: color.RGBA{0x7f, 0x00, 0x80, 0xff}, 217 Src: color.RGBA{0x7f, 0x00, 0x00, 0x7f}, 218 } 219 for op, want := range testCases { 220 dst := image.NewRGBA(image.Rect(0, 0, 2, 2)) 221 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) 222 223 src := image.NewRGBA(image.Rect(0, 0, 1, 1)) 224 src.SetRGBA(0, 0, color.RGBA{0x7f, 0x00, 0x00, 0x7f}) 225 226 NearestNeighbor.Scale(dst, dst.Bounds(), src, src.Bounds(), op, nil) 227 228 if got := dst.RGBAAt(0, 0); got != want { 229 t.Errorf("op=%v: got %v, want %v", op, got, want) 230 } 231 } 232 } 233 234 // TestNegativeWeights tests that scaling by a kernel that produces negative 235 // weights, such as the Catmull-Rom kernel, doesn't produce an invalid color 236 // according to Go's alpha-premultiplied model. 237 func TestNegativeWeights(t *testing.T) { 238 check := func(m *image.RGBA) error { 239 b := m.Bounds() 240 for y := b.Min.Y; y < b.Max.Y; y++ { 241 for x := b.Min.X; x < b.Max.X; x++ { 242 if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A { 243 return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c) 244 } 245 } 246 } 247 return nil 248 } 249 250 src := image.NewRGBA(image.Rect(0, 0, 16, 16)) 251 for y := 0; y < 16; y++ { 252 for x := 0; x < 16; x++ { 253 a := y * 0x11 254 src.Set(x, y, color.RGBA{ 255 R: uint8(x * 0x11 * a / 0xff), 256 A: uint8(a), 257 }) 258 } 259 } 260 if err := check(src); err != nil { 261 t.Fatalf("src image: %v", err) 262 } 263 264 dst := image.NewRGBA(image.Rect(0, 0, 32, 32)) 265 CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil) 266 if err := check(dst); err != nil { 267 t.Fatalf("dst image: %v", err) 268 } 269 } 270 271 func fillPix(r *rand.Rand, pixs ...[]byte) { 272 for _, pix := range pixs { 273 for i := range pix { 274 pix[i] = uint8(r.Intn(256)) 275 } 276 } 277 } 278 279 func TestInterpClipCommute(t *testing.T) { 280 src := image.NewNRGBA(image.Rect(0, 0, 20, 20)) 281 fillPix(rand.New(rand.NewSource(0)), src.Pix) 282 283 outer := image.Rect(1, 1, 8, 5) 284 inner := image.Rect(2, 3, 6, 5) 285 qs := []Interpolator{ 286 NearestNeighbor, 287 ApproxBiLinear, 288 CatmullRom, 289 } 290 for _, transform := range []bool{false, true} { 291 for _, q := range qs { 292 dst0 := image.NewRGBA(image.Rect(1, 1, 10, 10)) 293 dst1 := image.NewRGBA(image.Rect(1, 1, 10, 10)) 294 for i := range dst0.Pix { 295 dst0.Pix[i] = uint8(i / 4) 296 dst1.Pix[i] = uint8(i / 4) 297 } 298 299 var interp func(dst *image.RGBA) 300 if transform { 301 interp = func(dst *image.RGBA) { 302 q.Transform(dst, transformMatrix(3.75, 2, 1), src, src.Bounds(), Over, nil) 303 } 304 } else { 305 interp = func(dst *image.RGBA) { 306 q.Scale(dst, outer, src, src.Bounds(), Over, nil) 307 } 308 } 309 310 // Interpolate then clip. 311 interp(dst0) 312 dst0 = dst0.SubImage(inner).(*image.RGBA) 313 314 // Clip then interpolate. 315 dst1 = dst1.SubImage(inner).(*image.RGBA) 316 interp(dst1) 317 318 loop: 319 for y := inner.Min.Y; y < inner.Max.Y; y++ { 320 for x := inner.Min.X; x < inner.Max.X; x++ { 321 if c0, c1 := dst0.RGBAAt(x, y), dst1.RGBAAt(x, y); c0 != c1 { 322 t.Errorf("q=%T: at (%d, %d): c0=%v, c1=%v", q, x, y, c0, c1) 323 break loop 324 } 325 } 326 } 327 } 328 } 329 } 330 331 // translatedImage is an image m translated by t. 332 type translatedImage struct { 333 m image.Image 334 t image.Point 335 } 336 337 func (t *translatedImage) At(x, y int) color.Color { return t.m.At(x-t.t.X, y-t.t.Y) } 338 func (t *translatedImage) Bounds() image.Rectangle { return t.m.Bounds().Add(t.t) } 339 func (t *translatedImage) ColorModel() color.Model { return t.m.ColorModel() } 340 341 // TestSrcTranslationInvariance tests that Scale and Transform are invariant 342 // under src translations. Specifically, when some source pixels are not in the 343 // bottom-right quadrant of src coordinate space, we consistently round down, 344 // not round towards zero. 345 func TestSrcTranslationInvariance(t *testing.T) { 346 f, err := os.Open("../testdata/testpattern.png") 347 if err != nil { 348 t.Fatalf("Open: %v", err) 349 } 350 defer f.Close() 351 src, _, err := image.Decode(f) 352 if err != nil { 353 t.Fatalf("Decode: %v", err) 354 } 355 sr := image.Rect(2, 3, 16, 12) 356 if !sr.In(src.Bounds()) { 357 t.Fatalf("src bounds too small: got %v", src.Bounds()) 358 } 359 qs := []Interpolator{ 360 NearestNeighbor, 361 ApproxBiLinear, 362 CatmullRom, 363 } 364 deltas := []image.Point{ 365 {+0, +0}, 366 {+0, +5}, 367 {+0, -5}, 368 {+5, +0}, 369 {-5, +0}, 370 {+8, +8}, 371 {+8, -8}, 372 {-8, +8}, 373 {-8, -8}, 374 } 375 m00 := transformMatrix(3.75, 0, 0) 376 377 for _, transform := range []bool{false, true} { 378 for _, q := range qs { 379 want := image.NewRGBA(image.Rect(0, 0, 20, 20)) 380 if transform { 381 q.Transform(want, m00, src, sr, Over, nil) 382 } else { 383 q.Scale(want, want.Bounds(), src, sr, Over, nil) 384 } 385 for _, delta := range deltas { 386 tsrc := &translatedImage{src, delta} 387 got := image.NewRGBA(image.Rect(0, 0, 20, 20)) 388 if transform { 389 m := matMul(&m00, &f64.Aff3{ 390 1, 0, -float64(delta.X), 391 0, 1, -float64(delta.Y), 392 }) 393 q.Transform(got, m, tsrc, sr.Add(delta), Over, nil) 394 } else { 395 q.Scale(got, got.Bounds(), tsrc, sr.Add(delta), Over, nil) 396 } 397 if !bytes.Equal(got.Pix, want.Pix) { 398 t.Errorf("pix differ for delta=%v, transform=%t, q=%T", delta, transform, q) 399 } 400 } 401 } 402 } 403 } 404 405 func TestSrcMask(t *testing.T) { 406 srcMask := image.NewRGBA(image.Rect(0, 0, 23, 1)) 407 srcMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f}) 408 srcMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff}) 409 srcMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f}) 410 srcMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00}) 411 red := image.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff}) 412 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) 413 dst := image.NewRGBA(image.Rect(0, 0, 6, 1)) 414 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) 415 NearestNeighbor.Scale(dst, dst.Bounds(), red, image.Rect(0, 0, 3, 1), Over, &Options{ 416 SrcMask: srcMask, 417 SrcMaskP: image.Point{20, 0}, 418 }) 419 got := [6]color.RGBA{ 420 dst.RGBAAt(0, 0), 421 dst.RGBAAt(1, 0), 422 dst.RGBAAt(2, 0), 423 dst.RGBAAt(3, 0), 424 dst.RGBAAt(4, 0), 425 dst.RGBAAt(5, 0), 426 } 427 want := [6]color.RGBA{ 428 {0xff, 0x00, 0x00, 0xff}, 429 {0xff, 0x00, 0x00, 0xff}, 430 {0x3f, 0x00, 0xc0, 0xff}, 431 {0x3f, 0x00, 0xc0, 0xff}, 432 {0x00, 0x00, 0xff, 0xff}, 433 {0x00, 0x00, 0xff, 0xff}, 434 } 435 if got != want { 436 t.Errorf("\ngot %v\nwant %v", got, want) 437 } 438 } 439 440 func TestDstMask(t *testing.T) { 441 dstMask := image.NewRGBA(image.Rect(0, 0, 23, 1)) 442 dstMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f}) 443 dstMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff}) 444 dstMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f}) 445 dstMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00}) 446 red := image.NewRGBA(image.Rect(0, 0, 1, 1)) 447 red.SetRGBA(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff}) 448 blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) 449 qs := []Interpolator{ 450 NearestNeighbor, 451 ApproxBiLinear, 452 CatmullRom, 453 } 454 for _, q := range qs { 455 dst := image.NewRGBA(image.Rect(0, 0, 3, 1)) 456 Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil) 457 q.Scale(dst, dst.Bounds(), red, red.Bounds(), Over, &Options{ 458 DstMask: dstMask, 459 DstMaskP: image.Point{20, 0}, 460 }) 461 got := [3]color.RGBA{ 462 dst.RGBAAt(0, 0), 463 dst.RGBAAt(1, 0), 464 dst.RGBAAt(2, 0), 465 } 466 want := [3]color.RGBA{ 467 {0xff, 0x00, 0x00, 0xff}, 468 {0x3f, 0x00, 0xc0, 0xff}, 469 {0x00, 0x00, 0xff, 0xff}, 470 } 471 if got != want { 472 t.Errorf("q=%T:\ngot %v\nwant %v", q, got, want) 473 } 474 } 475 } 476 477 func TestRectDstMask(t *testing.T) { 478 f, err := os.Open("../testdata/testpattern.png") 479 if err != nil { 480 t.Fatalf("Open: %v", err) 481 } 482 defer f.Close() 483 src, _, err := image.Decode(f) 484 if err != nil { 485 t.Fatalf("Decode: %v", err) 486 } 487 m00 := transformMatrix(1, 0, 0) 488 489 bounds := image.Rect(0, 0, 50, 50) 490 dstOutside := image.NewRGBA(bounds) 491 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 492 for x := bounds.Min.X; x < bounds.Max.X; x++ { 493 dstOutside.SetRGBA(x, y, color.RGBA{uint8(5 * x), uint8(5 * y), 0x00, 0xff}) 494 } 495 } 496 497 mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA { 498 m := image.NewRGBA(bounds) 499 Copy(m, bounds.Min, dstOutside, bounds, Src, nil) 500 q.Transform(m, m00, src, src.Bounds(), Over, &Options{ 501 DstMask: dstMask, 502 DstMaskP: dstMaskP, 503 }) 504 return m 505 } 506 507 qs := []Interpolator{ 508 NearestNeighbor, 509 ApproxBiLinear, 510 CatmullRom, 511 } 512 dstMaskPs := []image.Point{ 513 {0, 0}, 514 {5, 7}, 515 {-3, 0}, 516 } 517 rect := image.Rect(10, 10, 30, 40) 518 for _, q := range qs { 519 for _, dstMaskP := range dstMaskPs { 520 dstInside := mk(q, nil, image.Point{}) 521 for _, wrap := range []bool{false, true} { 522 // TODO: replace "rectImage(rect)" with "rect" once Go 1.5 is 523 // released, where an image.Rectangle implements image.Image. 524 dstMask := image.Image(rectImage(rect)) 525 if wrap { 526 dstMask = srcWrapper{dstMask} 527 } 528 dst := mk(q, dstMask, dstMaskP) 529 530 nError := 0 531 loop: 532 for y := bounds.Min.Y; y < bounds.Max.Y; y++ { 533 for x := bounds.Min.X; x < bounds.Max.X; x++ { 534 which := dstOutside 535 if (image.Point{x, y}).Add(dstMaskP).In(rect) { 536 which = dstInside 537 } 538 if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want { 539 if nError == 10 { 540 t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap) 541 break loop 542 } 543 nError++ 544 t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v", 545 q, dstMaskP, wrap, x, y, got, want) 546 } 547 } 548 } 549 } 550 } 551 } 552 } 553 554 // TODO: delete this wrapper type once Go 1.5 is released, where an 555 // image.Rectangle implements image.Image. 556 type rectImage image.Rectangle 557 558 func (r rectImage) ColorModel() color.Model { return color.Alpha16Model } 559 func (r rectImage) Bounds() image.Rectangle { return image.Rectangle(r) } 560 func (r rectImage) At(x, y int) color.Color { 561 if (image.Point{x, y}).In(image.Rectangle(r)) { 562 return color.Opaque 563 } 564 return color.Transparent 565 } 566 567 // The fooWrapper types wrap the dst or src image to avoid triggering the 568 // type-specific fast path implementations. 569 type ( 570 dstWrapper struct{ Image } 571 srcWrapper struct{ image.Image } 572 ) 573 574 func srcGray(boundsHint image.Rectangle) (image.Image, error) { 575 m := image.NewGray(boundsHint) 576 fillPix(rand.New(rand.NewSource(0)), m.Pix) 577 return m, nil 578 } 579 580 func srcNRGBA(boundsHint image.Rectangle) (image.Image, error) { 581 m := image.NewNRGBA(boundsHint) 582 fillPix(rand.New(rand.NewSource(1)), m.Pix) 583 return m, nil 584 } 585 586 func srcRGBA(boundsHint image.Rectangle) (image.Image, error) { 587 m := image.NewRGBA(boundsHint) 588 fillPix(rand.New(rand.NewSource(2)), m.Pix) 589 // RGBA is alpha-premultiplied, so the R, G and B values should 590 // be <= the A values. 591 for i := 0; i < len(m.Pix); i += 4 { 592 m.Pix[i+0] = uint8(uint32(m.Pix[i+0]) * uint32(m.Pix[i+3]) / 0xff) 593 m.Pix[i+1] = uint8(uint32(m.Pix[i+1]) * uint32(m.Pix[i+3]) / 0xff) 594 m.Pix[i+2] = uint8(uint32(m.Pix[i+2]) * uint32(m.Pix[i+3]) / 0xff) 595 } 596 return m, nil 597 } 598 599 func srcUnif(boundsHint image.Rectangle) (image.Image, error) { 600 return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil 601 } 602 603 func srcYCbCr(boundsHint image.Rectangle) (image.Image, error) { 604 m := image.NewYCbCr(boundsHint, image.YCbCrSubsampleRatio420) 605 fillPix(rand.New(rand.NewSource(3)), m.Y, m.Cb, m.Cr) 606 return m, nil 607 } 608 609 func srcLarge(boundsHint image.Rectangle) (image.Image, error) { 610 // 3072 x 2304 is over 7 million pixels at 4:3, comparable to a 611 // 2015 smart-phone camera's output. 612 return srcYCbCr(image.Rect(0, 0, 3072, 2304)) 613 } 614 615 func srcTux(boundsHint image.Rectangle) (image.Image, error) { 616 // tux.png is a 386 x 395 image. 617 f, err := os.Open("../testdata/tux.png") 618 if err != nil { 619 return nil, fmt.Errorf("Open: %v", err) 620 } 621 defer f.Close() 622 src, err := png.Decode(f) 623 if err != nil { 624 return nil, fmt.Errorf("Decode: %v", err) 625 } 626 return src, nil 627 } 628 629 func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) { 630 dst := image.NewRGBA(image.Rect(0, 0, w, h)) 631 src, err := srcf(image.Rect(0, 0, 1024, 768)) 632 if err != nil { 633 b.Fatal(err) 634 } 635 dr, sr := dst.Bounds(), src.Bounds() 636 scaler := Scaler(q) 637 if n, ok := q.(interface { 638 NewScaler(int, int, int, int) Scaler 639 }); ok { 640 scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy()) 641 } 642 643 b.ReportAllocs() 644 b.ResetTimer() 645 for i := 0; i < b.N; i++ { 646 scaler.Scale(dst, dr, src, sr, op, nil) 647 } 648 } 649 650 func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) { 651 dst := image.NewRGBA(image.Rect(0, 0, w, h)) 652 src, err := srcf(image.Rect(0, 0, 1024, 768)) 653 if err != nil { 654 b.Fatal(err) 655 } 656 sr := src.Bounds() 657 m := transformMatrix(3.75, 40, 10) 658 659 b.ReportAllocs() 660 b.ResetTimer() 661 for i := 0; i < b.N; i++ { 662 q.Transform(dst, m, src, sr, op, nil) 663 } 664 } 665 666 func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) } 667 func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) } 668 func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) } 669 func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) } 670 671 func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) } 672 func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) } 673 func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) } 674 func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) } 675 676 func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) } 677 func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) } 678 func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) } 679 func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) } 680 681 func BenchmarkScaleNNSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) } 682 func BenchmarkScaleNNSrcUnif(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) } 683 684 func BenchmarkScaleNNOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, NearestNeighbor) } 685 func BenchmarkScaleNNOverUnif(b *testing.B) { benchScale(b, 200, 150, Over, srcUnif, NearestNeighbor) } 686 687 func BenchmarkTformNNSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) } 688 func BenchmarkTformNNSrcUnif(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) } 689 690 func BenchmarkTformNNOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, NearestNeighbor) } 691 func BenchmarkTformNNOverUnif(b *testing.B) { benchTform(b, 200, 150, Over, srcUnif, NearestNeighbor) } 692 693 func BenchmarkScaleABSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) } 694 func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) } 695 func BenchmarkScaleABSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) } 696 func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) } 697 698 func BenchmarkScaleABOverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, ApproxBiLinear) } 699 func BenchmarkScaleABOverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) } 700 func BenchmarkScaleABOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, ApproxBiLinear) } 701 func BenchmarkScaleABOverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) } 702 703 func BenchmarkTformABSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) } 704 func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) } 705 func BenchmarkTformABSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) } 706 func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) } 707 708 func BenchmarkTformABOverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, ApproxBiLinear) } 709 func BenchmarkTformABOverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) } 710 func BenchmarkTformABOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, ApproxBiLinear) } 711 func BenchmarkTformABOverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) } 712 713 func BenchmarkScaleCRSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, CatmullRom) } 714 func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) } 715 func BenchmarkScaleCRSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) } 716 func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) } 717 718 func BenchmarkScaleCROverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, CatmullRom) } 719 func BenchmarkScaleCROverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, CatmullRom) } 720 func BenchmarkScaleCROverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, CatmullRom) } 721 func BenchmarkScaleCROverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, CatmullRom) } 722 723 func BenchmarkTformCRSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, CatmullRom) } 724 func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) } 725 func BenchmarkTformCRSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) } 726 func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) } 727 728 func BenchmarkTformCROverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, CatmullRom) } 729 func BenchmarkTformCROverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, CatmullRom) } 730 func BenchmarkTformCROverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, CatmullRom) } 731 func BenchmarkTformCROverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, CatmullRom) }