From 24b0de15f1ec0a9d5e64d58b91b166bd45cfe4bc Mon Sep 17 00:00:00 2001 From: Nigel Tao Date: Wed, 22 Apr 2015 15:24:07 +1000 Subject: [PATCH] draw: add a fast path for an image.Rectangle DstMask. Change-Id: Id5227b9d217b56a342bc1ffc735dababa8a9e3e9 Reviewed-on: https://go-review.googlesource.com/9233 Reviewed-by: Rob Pike --- draw/gen.go | 20 +++++++++++-------- draw/impl.go | 30 ++++++++++++++++------------ draw/scale.go | 11 +++++++++++ draw/scale_test.go | 49 ++++++++++++++++++++++++++++++++++------------ 4 files changed, 78 insertions(+), 32 deletions(-) diff --git a/draw/gen.go b/draw/gen.go index b02368a..33830ac 100644 --- a/draw/gen.go +++ b/draw/gen.go @@ -854,12 +854,14 @@ const ( o = *opts } - // adr is the affected destination pixels, relative to dr.Min. - adr := dst.Bounds().Intersect(dr).Sub(dr.Min) - // TODO: clip adr to o.DstMask.Bounds(). + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) if o.Op == Over && o.SrcMask == nil && opaque(src) { o.Op = Src } @@ -892,7 +894,7 @@ const ( dr := transformRect(s2d, &sr) // adr is the affected destination pixels. adr := dst.Bounds().Intersect(dr) - // TODO: clip adr to o.DstMask.Bounds(). + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } @@ -1097,12 +1099,14 @@ const ( o = *opts } - // adr is the affected destination pixels, relative to dr.Min. - adr := dst.Bounds().Intersect(dr).Sub(dr.Min) - // TODO: clip adr to o.DstMask.Bounds(). + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) if o.Op == Over && o.SrcMask == nil && opaque(src) { o.Op = Src } @@ -1156,7 +1160,7 @@ const ( dr := transformRect(s2d, &sr) // adr is the affected destination pixels. adr := dst.Bounds().Intersect(dr) - // TODO: clip adr to o.DstMask.Bounds(). + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } diff --git a/draw/impl.go b/draw/impl.go index d64cb77..69ba309 100644 --- a/draw/impl.go +++ b/draw/impl.go @@ -16,12 +16,14 @@ func (z nnInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, sr o = *opts } - // adr is the affected destination pixels, relative to dr.Min. - adr := dst.Bounds().Intersect(dr).Sub(dr.Min) - // TODO: clip adr to o.DstMask.Bounds(). + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) if o.Op == Over && o.SrcMask == nil && opaque(src) { o.Op = Src } @@ -104,7 +106,7 @@ func (z nnInterpolator) Transform(dst Image, s2d *f64.Aff3, src image.Image, sr dr := transformRect(s2d, &sr) // adr is the affected destination pixels. adr := dst.Bounds().Intersect(dr) - // TODO: clip adr to o.DstMask.Bounds(). + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } @@ -1003,12 +1005,14 @@ func (z ablInterpolator) Scale(dst Image, dr image.Rectangle, src image.Image, s o = *opts } - // adr is the affected destination pixels, relative to dr.Min. - adr := dst.Bounds().Intersect(dr).Sub(dr.Min) - // TODO: clip adr to o.DstMask.Bounds(). + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) if o.Op == Over && o.SrcMask == nil && opaque(src) { o.Op = Src } @@ -1091,7 +1095,7 @@ func (z ablInterpolator) Transform(dst Image, s2d *f64.Aff3, src image.Image, sr dr := transformRect(s2d, &sr) // adr is the affected destination pixels. adr := dst.Bounds().Intersect(dr) - // TODO: clip adr to o.DstMask.Bounds(). + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } @@ -4257,12 +4261,14 @@ func (z *kernelScaler) Scale(dst Image, dr image.Rectangle, src image.Image, sr o = *opts } - // adr is the affected destination pixels, relative to dr.Min. - adr := dst.Bounds().Intersect(dr).Sub(dr.Min) - // TODO: clip adr to o.DstMask.Bounds(). + // adr is the affected destination pixels. + adr := dst.Bounds().Intersect(dr) + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } + // Make adr relative to dr.Min. + adr = adr.Sub(dr.Min) if o.Op == Over && o.SrcMask == nil && opaque(src) { o.Op = Src } @@ -4353,7 +4359,7 @@ func (q *Kernel) Transform(dst Image, s2d *f64.Aff3, src image.Image, sr image.R dr := transformRect(s2d, &sr) // adr is the affected destination pixels. adr := dst.Bounds().Intersect(dr) - // TODO: clip adr to o.DstMask.Bounds(). + adr, o.DstMask = clipAffectedDestRect(adr, o.DstMask, o.DstMaskP) if adr.Empty() || sr.Empty() { return } diff --git a/draw/scale.go b/draw/scale.go index 248c083..db67f88 100644 --- a/draw/scale.go +++ b/draw/scale.go @@ -408,6 +408,17 @@ func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) { return dr } +func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) { + if dstMask == nil { + return adr, nil + } + if r, ok := dstMask.(image.Rectangle); ok { + return adr.Intersect(r.Sub(dstMaskP)), nil + } + // TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha? + return adr, dstMask +} + func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) { switch op { case Over: diff --git a/draw/scale_test.go b/draw/scale_test.go index 4a79339..233c70e 100644 --- a/draw/scale_test.go +++ b/draw/scale_test.go @@ -373,30 +373,55 @@ func TestRectDstMask(t *testing.T) { } } - mk := func(q Transformer, dstMask image.Image) *image.RGBA { + mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA { m := image.NewRGBA(bounds) Copy(m, bounds.Min, dstOutside, bounds, nil) - q.Transform(m, m00, src, src.Bounds(), &Options{DstMask: dstMask}) + q.Transform(m, m00, src, src.Bounds(), &Options{ + DstMask: dstMask, + DstMaskP: dstMaskP, + }) return m } - rect := image.Rect(20, 10, 30, 40) qs := []Interpolator{ NearestNeighbor, ApproxBiLinear, CatmullRom, } + dstMaskPs := []image.Point{ + {0, 0}, + {5, 7}, + {-3, 0}, + } + rect := image.Rect(10, 10, 30, 40) for _, q := range qs { - dstInside := mk(q, nil) - dst := mk(q, rect) - for y := bounds.Min.Y; y < bounds.Max.Y; y++ { - for x := bounds.Min.X; x < bounds.Max.X; x++ { - which := dstOutside - if (image.Point{x, y}).In(rect) { - which = dstInside + for _, dstMaskP := range dstMaskPs { + dstInside := mk(q, nil, image.Point{}) + for _, wrap := range []bool{false, true} { + dstMask := image.Image(rect) + if wrap { + dstMask = srcWrapper{dstMask} } - if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want { - t.Errorf("x=%3d y=%3d: got %v, want %v", x, y, got, want) + dst := mk(q, dstMask, dstMaskP) + + nError := 0 + loop: + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + which := dstOutside + if (image.Point{x, y}).Add(dstMaskP).In(rect) { + which = dstInside + } + if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want { + if nError == 10 { + t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap) + break loop + } + nError++ + t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v", + q, dstMaskP, wrap, x, y, got, want) + } + } } } }