draw: have Scale and Transform recognize straight copies.

This is only for the NearestNeighbor and ApproxBiLinear Interpolators. A
Kernel interpolator will add blur even when the dst and src rectangles
are the same size.

We do not bother recognizing Transforms that are Scales. The performance
difference is minimal, as you still need to do a per-dst-pixel inverse
mapping either way.

benchmark                           old ns/op     new ns/op     delta
BenchmarkSimpleScaleCopy-8          4866297       29586         -99.39%
BenchmarkSimpleTransformCopy-8      4875991       29531         -99.39%
BenchmarkSimpleTransformScale-8     1208147       1223206       +1.25%

Change-Id: If649ad27a4e81bcbb24b18315745c02c9186a5b7
Reviewed-on: https://go-review.googlesource.com/13004
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Nigel Tao 2015-07-31 18:09:14 +10:00
parent 5ec5e003b2
commit 8e3389fa81
3 changed files with 137 additions and 0 deletions

View File

@ -877,6 +877,12 @@ func relName(s string) string {
const ( const (
codeRoot = ` codeRoot = `
func (z $receiver) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z $receiver) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Scale to a Copy.
if dr.Size() == sr.Size() {
Copy(dst, dr.Min, src, sr, op, opts)
return
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts
@ -914,6 +920,16 @@ const (
} }
func (z $receiver) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z $receiver) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Transform to a Copy.
if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 {
dx := int(s2d[2])
dy := int(s2d[5])
if float64(dx) == s2d[2] && float64(dy) == s2d[5] {
Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts)
return
}
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts

View File

@ -11,6 +11,12 @@ import (
) )
func (z nnInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z nnInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Scale to a Copy.
if dr.Size() == sr.Size() {
Copy(dst, dr.Min, src, sr, op, opts)
return
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts
@ -98,6 +104,16 @@ func (z nnInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr
} }
func (z nnInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z nnInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Transform to a Copy.
if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 {
dx := int(s2d[2])
dy := int(s2d[5])
if float64(dx) == s2d[2] && float64(dy) == s2d[5] {
Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts)
return
}
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts
@ -1032,6 +1048,12 @@ func (nnInterpolator) transform_Image_Image_Src(dst Image, dr, adr image.Rectang
} }
func (z ablInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z ablInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Scale to a Copy.
if dr.Size() == sr.Size() {
Copy(dst, dr.Min, src, sr, op, opts)
return
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts
@ -1119,6 +1141,16 @@ func (z ablInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, s
} }
func (z ablInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) { func (z ablInterpolator) Transform(dst Image, s2d f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options) {
// Try to simplify a Transform to a Copy.
if s2d[0] == 1 && s2d[1] == 0 && s2d[3] == 0 && s2d[4] == 1 {
dx := int(s2d[2])
dy := int(s2d[5])
if float64(dx) == s2d[2] && float64(dy) == s2d[5] {
Copy(dst, image.Point{X: sr.Min.X + dx, Y: sr.Min.X + dy}, src, sr, op, opts)
return
}
}
var o Options var o Options
if opts != nil { if opts != nil {
o = *opts o = *opts

View File

@ -121,6 +121,95 @@ func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two",
func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") } func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") }
func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") } func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") }
// TestSimpleTransforms tests Scale and Transform calls that simplify to Copy
// or Scale calls.
func TestSimpleTransforms(t *testing.T) {
f, err := os.Open("../testdata/testpattern.png") // A 100x100 image.
if err != nil {
t.Fatalf("Open: %v", err)
}
defer f.Close()
src, _, err := image.Decode(f)
if err != nil {
t.Fatalf("Decode: %v", err)
}
dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150))
dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150))
for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} {
for _, epsilon := range []float64{0, 1e-50, 1e-1} {
Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil)
Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil)
switch op {
case "scale/copy":
dr := image.Rect(10, 30, 10+100, 30+100)
if epsilon > 1e-10 {
dr.Max.X++
}
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil)
case "tform/copy":
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
ApproxBiLinear.Transform(dst1, f64.Aff3{
1, 0 + epsilon, 10,
0, 1, 30,
}, src, src.Bounds(), Src, nil)
case "tform/scale":
ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil)
ApproxBiLinear.Transform(dst1, f64.Aff3{
0.5, 0.0 + epsilon, 10,
0.0, 0.5, 50,
}, src, src.Bounds(), Src, nil)
}
differ := !bytes.Equal(dst0.Pix, dst1.Pix)
if epsilon > 1e-10 {
if !differ {
t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon)
}
} else {
if differ {
t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon)
}
}
}
}
}
func BenchmarkSimpleScaleCopy(b *testing.B) {
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
b.ResetTimer()
for i := 0; i < b.N; i++ {
ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil)
}
}
func BenchmarkSimpleTransformCopy(b *testing.B) {
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
b.ResetTimer()
for i := 0; i < b.N; i++ {
ApproxBiLinear.Transform(dst, f64.Aff3{
1, 0, 10,
0, 1, 20,
}, src, src.Bounds(), Src, nil)
}
}
func BenchmarkSimpleTransformScale(b *testing.B) {
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
b.ResetTimer()
for i := 0; i < b.N; i++ {
ApproxBiLinear.Transform(dst, f64.Aff3{
0.5, 0.0, 10,
0.0, 0.5, 20,
}, src, src.Bounds(), Src, nil)
}
}
func TestOps(t *testing.T) { func TestOps(t *testing.T) {
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff}) blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
testCases := map[Op]color.RGBA{ testCases := map[Op]color.RGBA{