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)
+						}
+					}
 				}
 			}
 		}