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{