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:
parent
5ec5e003b2
commit
8e3389fa81
16
draw/gen.go
16
draw/gen.go
|
@ -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
|
||||||
|
|
32
draw/impl.go
32
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) {
|
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
|
||||||
|
|
|
@ -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{
|
||||||
|
|
Loading…
Reference in New Issue
Block a user