git clone https://git.lucas.co/go_mono.git
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 <[email protected]>
draw/gen.go | 16 ++++++++++
draw/impl.go | 32 ++++++++++++++++++++
draw/scale_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 137 insertions(+)
diff --git a/draw/gen.go b/draw/gen.go
index f1e80af..0fed474 100644
--- a/draw/gen.go
+++ b/draw/gen.go
@@ -877,6 +877,12 @@ func relName(s string) string {
const (
codeRoot = `
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
if opts != nil {
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) {
+ // 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
if opts != nil {
o = *opts
diff --git a/draw/impl.go b/draw/impl.go
index 967e4e6..d6484d7 100644
--- a/draw/impl.go
+++ b/draw/impl.go
@@ -11,6 +11,12 @@ import (
)
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
if opts != nil {
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) {
+ // 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
if opts != nil {
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) {
+ // 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
if opts != nil {
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) {
+ // 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
if opts != nil {
o = *opts
diff --git a/draw/scale_test.go b/draw/scale_test.go
index 5a2b47a..5e184c2 100644
--- a/draw/scale_test.go
+++ b/draw/scale_test.go
@@ -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 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) {
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
testCases := map[Op]color.RGBA{