From 8e3389fa81abf8e318abefa17054df807e5c5692 Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Fri, 31 Jul 2015 18:09:14 +1000 Subject: [PATCH] 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 --- 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{