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 <r@golang.org>
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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)
+ }
+ }
}
}
}