draw: switch on the Op compositing operator.

This change only *prepares* the codegen to handle multiple Ops. The
actual generated code still only supports one Op (Src) and not the other
(Over). A follow-up change will add Over.

This Op switch (an eventual x2 multiplier in the amount of code
generated) should be the last of the codegen LoC multipliers. The dst
and src mask options will be implemented in the slow path fallback.

Change-Id: Iecbcc6fad063e2aac36d78d5380c0a0947c709df
Reviewed-on: https://go-review.googlesource.com/8488
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/draw/gen.go b/draw/gen.go
index 6efdafa..3ec18a1 100644
--- a/draw/gen.go
+++ b/draw/gen.go
@@ -71,6 +71,7 @@
 		"420",
 		"440",
 	}
+	ops = []string{"Src"} // TODO: add "Over".
 )
 
 func init() {
@@ -95,17 +96,21 @@
 	sType    string
 	sratio   string
 	receiver string
+	op       string
 }
 
 func gen(w *bytes.Buffer, receiver string, codes ...string) {
 	expn(w, codeRoot, &data{receiver: receiver})
 	for _, code := range codes {
 		for _, t := range dsTypes {
-			expn(w, code, &data{
-				dType:    t.dType,
-				sType:    t.sType,
-				receiver: receiver,
-			})
+			for _, op := range ops {
+				expn(w, code, &data{
+					dType:    t.dType,
+					sType:    t.sType,
+					receiver: receiver,
+					op:       op,
+				})
+			}
 		}
 	}
 }
@@ -118,15 +123,21 @@
 		})
 	}
 	for _, dType := range dTypes {
-		expn(w, codeKernelScaleLeafY, &data{
-			dType: dType,
-		})
+		for _, op := range ops {
+			expn(w, codeKernelScaleLeafY, &data{
+				dType: dType,
+				op:    op,
+			})
+		}
 	}
 	for _, t := range dsTypes {
-		expn(w, codeKernelTransformLeaf, &data{
-			dType: t.dType,
-			sType: t.sType,
-		})
+		for _, op := range ops {
+			expn(w, codeKernelTransformLeaf, &data{
+				dType: t.dType,
+				sType: t.sType,
+				op:    op,
+			})
+		}
 	}
 }
 
@@ -192,13 +203,15 @@
 		return prefix + relName(d.sType) + suffix
 	case "receiver":
 		return prefix + d.receiver + suffix
+	case "op":
+		return prefix + d.op + suffix
 
 	case "switch":
-		return expnSwitch("", true, suffix)
+		return expnSwitch("", "", true, suffix)
 	case "switchD":
-		return expnSwitch("", false, suffix)
+		return expnSwitch("", "", false, suffix)
 	case "switchS":
-		return expnSwitch("anyDType", false, suffix)
+		return expnSwitch("", "anyDType", false, suffix)
 
 	case "preOuter":
 		switch d.dType {
@@ -568,7 +581,19 @@
 	return ""
 }
 
-func expnSwitch(dType string, expandBoth bool, template string) string {
+func expnSwitch(op, dType string, expandBoth bool, template string) string {
+	if op == "" && dType != "anyDType" {
+		lines := []string{"switch opts.op() {"}
+		for _, op = range ops {
+			lines = append(lines,
+				fmt.Sprintf("case %s:", op),
+				expnSwitch(op, dType, expandBoth, template),
+			)
+		}
+		lines = append(lines, "}")
+		return strings.Join(lines, "\n")
+	}
+
 	switchVar := "dst"
 	if dType != "" {
 		switchVar = "src"
@@ -588,14 +613,14 @@
 
 		if dType != "" {
 			if v == "*image.YCbCr" {
-				lines = append(lines, expnSwitchYCbCr(dType, template))
+				lines = append(lines, expnSwitchYCbCr(op, dType, template))
 			} else {
-				lines = append(lines, expnLine(template, &data{dType: dType, sType: v}))
+				lines = append(lines, expnLine(template, &data{dType: dType, sType: v, op: op}))
 			}
 		} else if !expandBoth {
-			lines = append(lines, expnLine(template, &data{dType: v}))
+			lines = append(lines, expnLine(template, &data{dType: v, op: op}))
 		} else {
-			lines = append(lines, expnSwitch(v, false, template))
+			lines = append(lines, expnSwitch(op, v, false, template))
 		}
 	}
 
@@ -603,16 +628,16 @@
 	return strings.Join(lines, "\n")
 }
 
-func expnSwitchYCbCr(dType, template string) string {
+func expnSwitchYCbCr(op, dType, template string) string {
 	lines := []string{
 		"switch src.SubsampleRatio {",
 		"default:",
-		expnLine(template, &data{dType: dType, sType: "image.Image"}),
+		expnLine(template, &data{dType: dType, sType: "image.Image", op: op}),
 	}
 	for _, sratio := range subsampleRatios {
 		lines = append(lines,
 			fmt.Sprintf("case image.YCbCrSubsampleRatio%s:", sratio),
-			expnLine(template, &data{dType: dType, sType: "*image.YCbCr", sratio: sratio}),
+			expnLine(template, &data{dType: dType, sType: "*image.YCbCr", sratio: sratio, op: op}),
 		)
 	}
 	lines = append(lines, "}")
@@ -722,12 +747,16 @@
 			// we cannot use the type-specific fast paths, as they access
 			// the Pix fields directly without bounds checking.
 			if !sr.In(src.Bounds()) {
-				z.scale_Image_Image(dst, dr, adr, src, sr)
+				switch opts.op() {
+				case Over:
+					// TODO: z.scale_Image_Image_Over(dst, dr, adr, src, sr)
+				case Src:
+					z.scale_Image_Image_Src(dst, dr, adr, src, sr)
+				}
 			} else if _, ok := src.(*image.Uniform); ok {
-				// TODO: get the Op from opts.
-				Draw(dst, dr, src, src.Bounds().Min, Src)
+				Draw(dst, dr, src, src.Bounds().Min, opts.op())
 			} else {
-				$switch z.scale_$dTypeRN_$sTypeRN$sratio(dst, dr, adr, src, sr)
+				$switch z.scale_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, src, sr)
 			}
 		}
 
@@ -757,18 +786,22 @@
 			// we cannot use the type-specific fast paths, as they access
 			// the Pix fields directly without bounds checking.
 			if !sr.In(src.Bounds()) {
-				z.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias)
+				switch opts.op() {
+				case Over:
+					// TODO: z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias)
+				case Src:
+					z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+				}
 			} else if u, ok := src.(*image.Uniform); ok {
-				// TODO: get the Op from opts.
-				transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, Src)
+				transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, opts.op())
 			} else {
-				$switch z.transform_$dTypeRN_$sTypeRN$sratio(dst, dr, adr, &d2s, src, sr, bias)
+				$switch z.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias)
 			}
 		}
 	`
 
 	codeNNScaleLeaf = `
-		func (nnInterpolator) scale_$dTypeRN_$sTypeRN$sratio(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle) {
+		func (nnInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle) {
 			dw2 := uint64(dr.Dx()) * 2
 			dh2 := uint64(dr.Dy()) * 2
 			sw := uint64(sr.Dx())
@@ -787,7 +820,7 @@
 	`
 
 	codeNNTransformLeaf = `
-		func (nnInterpolator) transform_$dTypeRN_$sTypeRN$sratio(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point) {
+		func (nnInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point) {
 			$preOuter
 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 				dyf := float64(dr.Min.Y + int(dy)) + 0.5
@@ -807,7 +840,7 @@
 	`
 
 	codeABLScaleLeaf = `
-		func (ablInterpolator) scale_$dTypeRN_$sTypeRN$sratio(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle) {
+		func (ablInterpolator) scale_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, src $sType, sr image.Rectangle) {
 			sw := int32(sr.Dx())
 			sh := int32(sr.Dy())
 			yscale := float64(sh) / float64(dr.Dy())
@@ -861,7 +894,7 @@
 	`
 
 	codeABLTransformLeaf = `
-		func (ablInterpolator) transform_$dTypeRN_$sTypeRN$sratio(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point) {
+		func (ablInterpolator) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point) {
 			$preOuter
 			for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 				dyf := float64(dr.Min.Y + int(dy)) + 0.5
@@ -928,8 +961,7 @@
 			}
 
 			if _, ok := src.(*image.Uniform); ok && sr.In(src.Bounds()) {
-				// TODO: get the Op from opts.
-				Draw(dst, dr, src, src.Bounds().Min, Src)
+				Draw(dst, dr, src, src.Bounds().Min, opts.op())
 				return
 			}
 
@@ -954,7 +986,7 @@
 				$switchS z.scaleX_$sTypeRN$sratio(tmp, src, sr)
 			}
 
-			$switchD z.scaleY_$dTypeRN(dst, dr, adr, tmp)
+			$switchD z.scaleY_$dTypeRN_$op(dst, dr, adr, tmp)
 		}
 
 		func (q *Kernel) Transform(dst Image, s2d *f64.Aff3, src image.Image, sr image.Rectangle, opts *Options) {
@@ -981,8 +1013,7 @@
 			adr = adr.Sub(dr.Min)
 
 			if u, ok := src.(*image.Uniform); ok && sr.In(src.Bounds()) {
-				// TODO: get the Op from opts.
-				transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, Src)
+				transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, opts.op())
 				return
 			}
 
@@ -999,9 +1030,14 @@
 			// we cannot use the type-specific fast paths, as they access
 			// the Pix fields directly without bounds checking.
 			if !sr.In(src.Bounds()) {
-				q.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				switch opts.op() {
+				case Over:
+					// TODO: q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				case Src:
+					q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				}
 			} else {
-				$switch q.transform_$dTypeRN_$sTypeRN$sratio(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				$switch q.transform_$dTypeRN_$sTypeRN$sratio_$op(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
 			}
 		}
 	`
@@ -1029,7 +1065,7 @@
 	`
 
 	codeKernelScaleLeafY = `
-		func (z *kernelScaler) scaleY_$dTypeRN(dst $dType, dr, adr image.Rectangle, tmp [][4]float64) {
+		func (z *kernelScaler) scaleY_$dTypeRN_$op(dst $dType, dr, adr image.Rectangle, tmp [][4]float64) {
 			$preOuter
 			for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
 				$preKernelInner
@@ -1050,7 +1086,7 @@
 	`
 
 	codeKernelTransformLeaf = `
-		func (q *Kernel) transform_$dTypeRN_$sTypeRN$sratio(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+		func (q *Kernel) transform_$dTypeRN_$sTypeRN$sratio_$op(dst $dType, dr, adr image.Rectangle, d2s *f64.Aff3, src $sType, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 			// When shrinking, broaden the effective kernel support so that we still
 			// visit every source pixel.
 			xHalfWidth, xKernelArgScale := q.Support, 1.0
diff --git a/draw/impl.go b/draw/impl.go
index a1de1cd..5f48157 100644
--- a/draw/impl.go
+++ b/draw/impl.go
@@ -20,40 +20,47 @@
 	// we cannot use the type-specific fast paths, as they access
 	// the Pix fields directly without bounds checking.
 	if !sr.In(src.Bounds()) {
-		z.scale_Image_Image(dst, dr, adr, src, sr)
+		switch opts.op() {
+		case Over:
+			// TODO: z.scale_Image_Image_Over(dst, dr, adr, src, sr)
+		case Src:
+			z.scale_Image_Image_Src(dst, dr, adr, src, sr)
+		}
 	} else if _, ok := src.(*image.Uniform); ok {
-		// TODO: get the Op from opts.
-		Draw(dst, dr, src, src.Bounds().Min, Src)
+		Draw(dst, dr, src, src.Bounds().Min, opts.op())
 	} else {
-		switch dst := dst.(type) {
-		case *image.RGBA:
-			switch src := src.(type) {
-			case *image.Gray:
-				z.scale_RGBA_Gray(dst, dr, adr, src, sr)
-			case *image.NRGBA:
-				z.scale_RGBA_NRGBA(dst, dr, adr, src, sr)
+		switch opts.op() {
+		case Src:
+			switch dst := dst.(type) {
 			case *image.RGBA:
-				z.scale_RGBA_RGBA(dst, dr, adr, src, sr)
-			case *image.YCbCr:
-				switch src.SubsampleRatio {
+				switch src := src.(type) {
+				case *image.Gray:
+					z.scale_RGBA_Gray_Src(dst, dr, adr, src, sr)
+				case *image.NRGBA:
+					z.scale_RGBA_NRGBA_Src(dst, dr, adr, src, sr)
+				case *image.RGBA:
+					z.scale_RGBA_RGBA_Src(dst, dr, adr, src, sr)
+				case *image.YCbCr:
+					switch src.SubsampleRatio {
+					default:
+						z.scale_RGBA_Image_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio444:
+						z.scale_RGBA_YCbCr444_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio422:
+						z.scale_RGBA_YCbCr422_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio420:
+						z.scale_RGBA_YCbCr420_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio440:
+						z.scale_RGBA_YCbCr440_Src(dst, dr, adr, src, sr)
+					}
 				default:
-					z.scale_RGBA_Image(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio444:
-					z.scale_RGBA_YCbCr444(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio422:
-					z.scale_RGBA_YCbCr422(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio420:
-					z.scale_RGBA_YCbCr420(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio440:
-					z.scale_RGBA_YCbCr440(dst, dr, adr, src, sr)
+					z.scale_RGBA_Image_Src(dst, dr, adr, src, sr)
 				}
 			default:
-				z.scale_RGBA_Image(dst, dr, adr, src, sr)
-			}
-		default:
-			switch src := src.(type) {
-			default:
-				z.scale_Image_Image(dst, dr, adr, src, sr)
+				switch src := src.(type) {
+				default:
+					z.scale_Image_Image_Src(dst, dr, adr, src, sr)
+				}
 			}
 		}
 	}
@@ -85,46 +92,53 @@
 	// we cannot use the type-specific fast paths, as they access
 	// the Pix fields directly without bounds checking.
 	if !sr.In(src.Bounds()) {
-		z.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias)
+		switch opts.op() {
+		case Over:
+			// TODO: z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias)
+		case Src:
+			z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+		}
 	} else if u, ok := src.(*image.Uniform); ok {
-		// TODO: get the Op from opts.
-		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, Src)
+		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, opts.op())
 	} else {
-		switch dst := dst.(type) {
-		case *image.RGBA:
-			switch src := src.(type) {
-			case *image.Gray:
-				z.transform_RGBA_Gray(dst, dr, adr, &d2s, src, sr, bias)
-			case *image.NRGBA:
-				z.transform_RGBA_NRGBA(dst, dr, adr, &d2s, src, sr, bias)
+		switch opts.op() {
+		case Src:
+			switch dst := dst.(type) {
 			case *image.RGBA:
-				z.transform_RGBA_RGBA(dst, dr, adr, &d2s, src, sr, bias)
-			case *image.YCbCr:
-				switch src.SubsampleRatio {
+				switch src := src.(type) {
+				case *image.Gray:
+					z.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.NRGBA:
+					z.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.RGBA:
+					z.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.YCbCr:
+					switch src.SubsampleRatio {
+					default:
+						z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio444:
+						z.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio422:
+						z.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio420:
+						z.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio440:
+						z.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias)
+					}
 				default:
-					z.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio444:
-					z.transform_RGBA_YCbCr444(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio422:
-					z.transform_RGBA_YCbCr422(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio420:
-					z.transform_RGBA_YCbCr420(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio440:
-					z.transform_RGBA_YCbCr440(dst, dr, adr, &d2s, src, sr, bias)
+					z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
 				}
 			default:
-				z.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias)
-			}
-		default:
-			switch src := src.(type) {
-			default:
-				z.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias)
+				switch src := src.(type) {
+				default:
+					z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+				}
 			}
 		}
 	}
 }
 
-func (nnInterpolator) scale_RGBA_Gray(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -145,7 +159,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_NRGBA(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -168,7 +182,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_RGBA(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -191,7 +205,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_YCbCr444(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -234,7 +248,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_YCbCr422(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -277,7 +291,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_YCbCr420(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -320,7 +334,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_YCbCr440(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -363,7 +377,7 @@
 	}
 }
 
-func (nnInterpolator) scale_RGBA_Image(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
+func (nnInterpolator) scale_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -382,7 +396,7 @@
 	}
 }
 
-func (nnInterpolator) scale_Image_Image(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
+func (nnInterpolator) scale_Image_Image_Src(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
 	dw2 := uint64(dr.Dx()) * 2
 	dh2 := uint64(dr.Dy()) * 2
 	sw := uint64(sr.Dx())
@@ -403,7 +417,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_Gray(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -425,7 +439,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_NRGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -449,7 +463,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_RGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -473,7 +487,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_YCbCr444(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -517,7 +531,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_YCbCr422(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -561,7 +575,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_YCbCr420(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -605,7 +619,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_YCbCr440(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -649,7 +663,7 @@
 	}
 }
 
-func (nnInterpolator) transform_RGBA_Image(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -669,7 +683,7 @@
 	}
 }
 
-func (nnInterpolator) transform_Image_Image(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
+func (nnInterpolator) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
 	dstColorRGBA64 := &color.RGBA64{}
 	dstColor := color.Color(dstColorRGBA64)
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
@@ -701,40 +715,47 @@
 	// we cannot use the type-specific fast paths, as they access
 	// the Pix fields directly without bounds checking.
 	if !sr.In(src.Bounds()) {
-		z.scale_Image_Image(dst, dr, adr, src, sr)
+		switch opts.op() {
+		case Over:
+			// TODO: z.scale_Image_Image_Over(dst, dr, adr, src, sr)
+		case Src:
+			z.scale_Image_Image_Src(dst, dr, adr, src, sr)
+		}
 	} else if _, ok := src.(*image.Uniform); ok {
-		// TODO: get the Op from opts.
-		Draw(dst, dr, src, src.Bounds().Min, Src)
+		Draw(dst, dr, src, src.Bounds().Min, opts.op())
 	} else {
-		switch dst := dst.(type) {
-		case *image.RGBA:
-			switch src := src.(type) {
-			case *image.Gray:
-				z.scale_RGBA_Gray(dst, dr, adr, src, sr)
-			case *image.NRGBA:
-				z.scale_RGBA_NRGBA(dst, dr, adr, src, sr)
+		switch opts.op() {
+		case Src:
+			switch dst := dst.(type) {
 			case *image.RGBA:
-				z.scale_RGBA_RGBA(dst, dr, adr, src, sr)
-			case *image.YCbCr:
-				switch src.SubsampleRatio {
+				switch src := src.(type) {
+				case *image.Gray:
+					z.scale_RGBA_Gray_Src(dst, dr, adr, src, sr)
+				case *image.NRGBA:
+					z.scale_RGBA_NRGBA_Src(dst, dr, adr, src, sr)
+				case *image.RGBA:
+					z.scale_RGBA_RGBA_Src(dst, dr, adr, src, sr)
+				case *image.YCbCr:
+					switch src.SubsampleRatio {
+					default:
+						z.scale_RGBA_Image_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio444:
+						z.scale_RGBA_YCbCr444_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio422:
+						z.scale_RGBA_YCbCr422_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio420:
+						z.scale_RGBA_YCbCr420_Src(dst, dr, adr, src, sr)
+					case image.YCbCrSubsampleRatio440:
+						z.scale_RGBA_YCbCr440_Src(dst, dr, adr, src, sr)
+					}
 				default:
-					z.scale_RGBA_Image(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio444:
-					z.scale_RGBA_YCbCr444(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio422:
-					z.scale_RGBA_YCbCr422(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio420:
-					z.scale_RGBA_YCbCr420(dst, dr, adr, src, sr)
-				case image.YCbCrSubsampleRatio440:
-					z.scale_RGBA_YCbCr440(dst, dr, adr, src, sr)
+					z.scale_RGBA_Image_Src(dst, dr, adr, src, sr)
 				}
 			default:
-				z.scale_RGBA_Image(dst, dr, adr, src, sr)
-			}
-		default:
-			switch src := src.(type) {
-			default:
-				z.scale_Image_Image(dst, dr, adr, src, sr)
+				switch src := src.(type) {
+				default:
+					z.scale_Image_Image_Src(dst, dr, adr, src, sr)
+				}
 			}
 		}
 	}
@@ -766,46 +787,53 @@
 	// we cannot use the type-specific fast paths, as they access
 	// the Pix fields directly without bounds checking.
 	if !sr.In(src.Bounds()) {
-		z.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias)
+		switch opts.op() {
+		case Over:
+			// TODO: z.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias)
+		case Src:
+			z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+		}
 	} else if u, ok := src.(*image.Uniform); ok {
-		// TODO: get the Op from opts.
-		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, Src)
+		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, opts.op())
 	} else {
-		switch dst := dst.(type) {
-		case *image.RGBA:
-			switch src := src.(type) {
-			case *image.Gray:
-				z.transform_RGBA_Gray(dst, dr, adr, &d2s, src, sr, bias)
-			case *image.NRGBA:
-				z.transform_RGBA_NRGBA(dst, dr, adr, &d2s, src, sr, bias)
+		switch opts.op() {
+		case Src:
+			switch dst := dst.(type) {
 			case *image.RGBA:
-				z.transform_RGBA_RGBA(dst, dr, adr, &d2s, src, sr, bias)
-			case *image.YCbCr:
-				switch src.SubsampleRatio {
+				switch src := src.(type) {
+				case *image.Gray:
+					z.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.NRGBA:
+					z.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.RGBA:
+					z.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias)
+				case *image.YCbCr:
+					switch src.SubsampleRatio {
+					default:
+						z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio444:
+						z.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio422:
+						z.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio420:
+						z.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias)
+					case image.YCbCrSubsampleRatio440:
+						z.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias)
+					}
 				default:
-					z.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio444:
-					z.transform_RGBA_YCbCr444(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio422:
-					z.transform_RGBA_YCbCr422(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio420:
-					z.transform_RGBA_YCbCr420(dst, dr, adr, &d2s, src, sr, bias)
-				case image.YCbCrSubsampleRatio440:
-					z.transform_RGBA_YCbCr440(dst, dr, adr, &d2s, src, sr, bias)
+					z.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
 				}
 			default:
-				z.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias)
-			}
-		default:
-			switch src := src.(type) {
-			default:
-				z.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias)
+				switch src := src.(type) {
+				default:
+					z.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias)
+				}
 			}
 		}
 	}
 }
 
-func (ablInterpolator) scale_RGBA_Gray(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.Gray, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -868,7 +896,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_NRGBA(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.NRGBA, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -963,7 +991,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_RGBA(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.RGBA, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1058,7 +1086,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_YCbCr444(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1230,7 +1258,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_YCbCr422(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1402,7 +1430,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_YCbCr420(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1574,7 +1602,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_YCbCr440(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, src *image.YCbCr, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1746,7 +1774,7 @@
 	}
 }
 
-func (ablInterpolator) scale_RGBA_Image(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
+func (ablInterpolator) scale_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1825,7 +1853,7 @@
 	}
 }
 
-func (ablInterpolator) scale_Image_Image(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
+func (ablInterpolator) scale_Image_Image_Src(dst Image, dr, adr image.Rectangle, src image.Image, sr image.Rectangle) {
 	sw := int32(sr.Dx())
 	sh := int32(sr.Dy())
 	yscale := float64(sh) / float64(dr.Dy())
@@ -1906,7 +1934,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_Gray(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -1970,7 +1998,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_NRGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2066,7 +2094,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_RGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2162,7 +2190,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_YCbCr444(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2335,7 +2363,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_YCbCr422(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2508,7 +2536,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_YCbCr420(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2681,7 +2709,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_YCbCr440(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2854,7 +2882,7 @@
 	}
 }
 
-func (ablInterpolator) transform_RGBA_Image(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
 		dyf := float64(dr.Min.Y+int(dy)) + 0.5
 		d := (dr.Min.Y+int(dy)-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+adr.Min.X-dst.Rect.Min.X)*4
@@ -2934,7 +2962,7 @@
 	}
 }
 
-func (ablInterpolator) transform_Image_Image(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
+func (ablInterpolator) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point) {
 	dstColorRGBA64 := &color.RGBA64{}
 	dstColor := color.Color(dstColorRGBA64)
 	for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
@@ -3028,8 +3056,7 @@
 	}
 
 	if _, ok := src.(*image.Uniform); ok && sr.In(src.Bounds()) {
-		// TODO: get the Op from opts.
-		Draw(dst, dr, src, src.Bounds().Min, Src)
+		Draw(dst, dr, src, src.Bounds().Min, opts.op())
 		return
 	}
 
@@ -3076,11 +3103,14 @@
 		}
 	}
 
-	switch dst := dst.(type) {
-	case *image.RGBA:
-		z.scaleY_RGBA(dst, dr, adr, tmp)
-	default:
-		z.scaleY_Image(dst, dr, adr, tmp)
+	switch opts.op() {
+	case Src:
+		switch dst := dst.(type) {
+		case *image.RGBA:
+			z.scaleY_RGBA_Src(dst, dr, adr, tmp)
+		default:
+			z.scaleY_Image_Src(dst, dr, adr, tmp)
+		}
 	}
 }
 
@@ -3108,8 +3138,7 @@
 	adr = adr.Sub(dr.Min)
 
 	if u, ok := src.(*image.Uniform); ok && sr.In(src.Bounds()) {
-		// TODO: get the Op from opts.
-		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, Src)
+		transform_Uniform(dst, dr, adr, &d2s, u, sr, bias, opts.op())
 		return
 	}
 
@@ -3126,37 +3155,45 @@
 	// we cannot use the type-specific fast paths, as they access
 	// the Pix fields directly without bounds checking.
 	if !sr.In(src.Bounds()) {
-		q.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+		switch opts.op() {
+		case Over:
+			// TODO: q.transform_Image_Image_Over(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+		case Src:
+			q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+		}
 	} else {
-		switch dst := dst.(type) {
-		case *image.RGBA:
-			switch src := src.(type) {
-			case *image.Gray:
-				q.transform_RGBA_Gray(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-			case *image.NRGBA:
-				q.transform_RGBA_NRGBA(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+		switch opts.op() {
+		case Src:
+			switch dst := dst.(type) {
 			case *image.RGBA:
-				q.transform_RGBA_RGBA(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-			case *image.YCbCr:
-				switch src.SubsampleRatio {
+				switch src := src.(type) {
+				case *image.Gray:
+					q.transform_RGBA_Gray_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				case *image.NRGBA:
+					q.transform_RGBA_NRGBA_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				case *image.RGBA:
+					q.transform_RGBA_RGBA_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				case *image.YCbCr:
+					switch src.SubsampleRatio {
+					default:
+						q.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					case image.YCbCrSubsampleRatio444:
+						q.transform_RGBA_YCbCr444_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					case image.YCbCrSubsampleRatio422:
+						q.transform_RGBA_YCbCr422_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					case image.YCbCrSubsampleRatio420:
+						q.transform_RGBA_YCbCr420_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					case image.YCbCrSubsampleRatio440:
+						q.transform_RGBA_YCbCr440_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					}
 				default:
-					q.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-				case image.YCbCrSubsampleRatio444:
-					q.transform_RGBA_YCbCr444(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-				case image.YCbCrSubsampleRatio422:
-					q.transform_RGBA_YCbCr422(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-				case image.YCbCrSubsampleRatio420:
-					q.transform_RGBA_YCbCr420(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-				case image.YCbCrSubsampleRatio440:
-					q.transform_RGBA_YCbCr440(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+					q.transform_RGBA_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
 				}
 			default:
-				q.transform_RGBA_Image(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
-			}
-		default:
-			switch src := src.(type) {
-			default:
-				q.transform_Image_Image(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				switch src := src.(type) {
+				default:
+					q.transform_Image_Image_Src(dst, dr, adr, &d2s, src, sr, bias, xscale, yscale)
+				}
 			}
 		}
 	}
@@ -3449,7 +3486,7 @@
 	}
 }
 
-func (z *kernelScaler) scaleY_RGBA(dst *image.RGBA, dr, adr image.Rectangle, tmp [][4]float64) {
+func (z *kernelScaler) scaleY_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, tmp [][4]float64) {
 	for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
 		d := (dr.Min.Y+adr.Min.Y-dst.Rect.Min.Y)*dst.Stride + (dr.Min.X+int(dx)-dst.Rect.Min.X)*4
 		for _, s := range z.vertical.sources[adr.Min.Y:adr.Max.Y] {
@@ -3470,7 +3507,7 @@
 	}
 }
 
-func (z *kernelScaler) scaleY_Image(dst Image, dr, adr image.Rectangle, tmp [][4]float64) {
+func (z *kernelScaler) scaleY_Image_Src(dst Image, dr, adr image.Rectangle, tmp [][4]float64) {
 	dstColorRGBA64 := &color.RGBA64{}
 	dstColor := color.Color(dstColorRGBA64)
 	for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
@@ -3492,7 +3529,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_Gray(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_Gray_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Gray, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -3591,7 +3628,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_NRGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.NRGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -3695,7 +3732,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_RGBA(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.RGBA, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -3799,7 +3836,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_YCbCr444(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_YCbCr444_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -3923,7 +3960,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_YCbCr422(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_YCbCr422_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -4047,7 +4084,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_YCbCr420(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_YCbCr420_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -4171,7 +4208,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_YCbCr440(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_YCbCr440_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.YCbCr, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -4295,7 +4332,7 @@
 	}
 }
 
-func (q *Kernel) transform_RGBA_Image(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
@@ -4395,7 +4432,7 @@
 	}
 }
 
-func (q *Kernel) transform_Image_Image(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
+func (q *Kernel) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src image.Image, sr image.Rectangle, bias image.Point, xscale, yscale float64) {
 	// When shrinking, broaden the effective kernel support so that we still
 	// visit every source pixel.
 	xHalfWidth, xKernelArgScale := q.Support, 1.0
diff --git a/draw/scale.go b/draw/scale.go
index 2e481b1..39520f4 100644
--- a/draw/scale.go
+++ b/draw/scale.go
@@ -19,12 +19,12 @@
 // the part of the destination image defined by dst and the translation of sr
 // so that sr.Min translates to dp.
 func Copy(dst Image, dp image.Point, src image.Image, sr image.Rectangle, opts *Options) {
-	mask, mp, op := image.Image(nil), image.Point{}, Over
+	mask, mp := image.Image(nil), image.Point{}
 	if opts != nil {
-		// TODO: set mask, mp and op.
+		// TODO: set mask and mp.
 	}
 	dr := sr.Add(dp.Sub(sr.Min))
-	DrawMask(dst, dr, src, sr.Min, mask, mp, op)
+	DrawMask(dst, dr, src, sr.Min, mask, mp, opts.op())
 }
 
 // Scaler scales the part of the source image defined by src and sr and writes
@@ -56,10 +56,20 @@
 //
 // A nil *Options means to use the default (zero) values of each field.
 type Options struct {
+	// Op is the compositing operator. The default value is Over.
+	Op Op
+
 	// TODO: add fields a la
 	// https://groups.google.com/forum/#!topic/golang-dev/fgn_xM0aeq4
 }
 
+func (o *Options) op() Op {
+	if o == nil {
+		return Over
+	}
+	return o.Op
+}
+
 // Interpolator is an interpolation algorithm, when dst and src pixels don't
 // have a 1:1 correspondence.
 //
@@ -371,6 +381,7 @@
 }
 
 func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) {
+	// TODO: implement op == Over.
 	switch dst := dst.(type) {
 	case *image.RGBA:
 		pr, pg, pb, pa := src.C.RGBA()
diff --git a/draw/scale_test.go b/draw/scale_test.go
index 9cce5b1..65a536a 100644
--- a/draw/scale_test.go
+++ b/draw/scale_test.go
@@ -59,6 +59,9 @@
 	if err != nil {
 		t.Fatalf("Decode: %v", err)
 	}
+	opts := &Options{
+		Op: Src,
+	}
 	testCases := map[string]Interpolator{
 		"nn": NearestNeighbor,
 		"ab": ApproxBiLinear,
@@ -70,9 +73,9 @@
 
 		got := image.NewRGBA(image.Rect(0, 0, w, h))
 		if direction == "rotate" {
-			q.Transform(got, transformMatrix(40, 10), src, src.Bounds(), nil)
+			q.Transform(got, transformMatrix(40, 10), src, src.Bounds(), opts)
 		} else {
-			q.Scale(got, got.Bounds(), src, src.Bounds(), nil)
+			q.Scale(got, got.Bounds(), src, src.Bounds(), opts)
 		}
 
 		if *genGoldenFiles {
@@ -276,7 +279,7 @@
 		srcGray,
 		srcNRGBA,
 		srcRGBA,
-		srcUniform,
+		srcUnif,
 		srcYCbCr,
 	}
 	var srcs []image.Image
@@ -293,6 +296,9 @@
 		CatmullRom,
 	}
 	blue := image.NewUniform(color.RGBA{0x11, 0x22, 0x44, 0x7f})
+	opts := &Options{
+		Op: Src,
+	}
 
 	for _, dr := range drs {
 		for _, src := range srcs {
@@ -306,11 +312,11 @@
 
 						if transform {
 							m := transformMatrix(2, 1)
-							q.Transform(dst0, m, src, sr, nil)
-							q.Transform(dstWrapper{dst1}, m, srcWrapper{src}, sr, nil)
+							q.Transform(dst0, m, src, sr, opts)
+							q.Transform(dstWrapper{dst1}, m, srcWrapper{src}, sr, opts)
 						} else {
-							q.Scale(dst0, dr, src, sr, nil)
-							q.Scale(dstWrapper{dst1}, dr, srcWrapper{src}, sr, nil)
+							q.Scale(dst0, dr, src, sr, opts)
+							q.Scale(dstWrapper{dst1}, dr, srcWrapper{src}, sr, opts)
 						}
 
 						if !bytes.Equal(dst0.Pix, dst1.Pix) {
@@ -349,7 +355,7 @@
 	return m, nil
 }
 
-func srcUniform(boundsHint image.Rectangle) (image.Image, error) {
+func srcUnif(boundsHint image.Rectangle) (image.Image, error) {
 	return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil
 }
 
@@ -359,7 +365,7 @@
 	return m, nil
 }
 
-func srcYCbCrLarge(boundsHint image.Rectangle) (image.Image, error) {
+func srcLarge(boundsHint image.Rectangle) (image.Image, error) {
 	// 3072 x 2304 is over 7 million pixels at 4:3, comparable to a
 	// 2015 smart-phone camera's output.
 	return srcYCbCr(image.Rect(0, 0, 3072, 2304))
@@ -379,7 +385,7 @@
 	return src, nil
 }
 
-func benchScale(b *testing.B, srcf func(image.Rectangle) (image.Image, error), w int, h int, q Interpolator) {
+func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
 	dst := image.NewRGBA(image.Rect(0, 0, w, h))
 	src, err := srcf(image.Rect(0, 0, 1024, 768))
 	if err != nil {
@@ -392,15 +398,18 @@
 	}); ok {
 		scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy())
 	}
+	opts := &Options{
+		Op: op,
+	}
 
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		scaler.Scale(dst, dr, src, sr, nil)
+		scaler.Scale(dst, dr, src, sr, opts)
 	}
 }
 
-func benchTform(b *testing.B, srcf func(image.Rectangle) (image.Image, error), w int, h int, q Interpolator) {
+func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
 	dst := image.NewRGBA(image.Rect(0, 0, w, h))
 	src, err := srcf(image.Rect(0, 0, 1024, 768))
 	if err != nil {
@@ -408,51 +417,54 @@
 	}
 	sr := src.Bounds()
 	m := transformMatrix(40, 10)
+	opts := &Options{
+		Op: op,
+	}
 
 	b.ReportAllocs()
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		q.Transform(dst, m, src, sr, nil)
+		q.Transform(dst, m, src, sr, opts)
 	}
 }
 
-func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, srcYCbCrLarge, 200, 150, NearestNeighbor) }
-func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, srcYCbCrLarge, 200, 150, ApproxBiLinear) }
-func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, srcYCbCrLarge, 200, 150, BiLinear) }
-func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, srcYCbCrLarge, 200, 150, CatmullRom) }
+func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) }
+func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) }
+func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) }
+func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) }
 
-func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, srcTux, 120, 80, NearestNeighbor) }
-func BenchmarkScaleABDown(b *testing.B) { benchScale(b, srcTux, 120, 80, ApproxBiLinear) }
-func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, srcTux, 120, 80, BiLinear) }
-func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, srcTux, 120, 80, CatmullRom) }
+func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) }
+func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) }
+func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) }
+func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) }
 
-func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, srcTux, 800, 600, NearestNeighbor) }
-func BenchmarkScaleABUp(b *testing.B) { benchScale(b, srcTux, 800, 600, ApproxBiLinear) }
-func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, srcTux, 800, 600, BiLinear) }
-func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, srcTux, 800, 600, CatmullRom) }
+func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) }
+func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) }
+func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) }
+func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) }
 
-func BenchmarkScaleNNSrcRGBA(b *testing.B)    { benchScale(b, srcRGBA, 200, 150, NearestNeighbor) }
-func BenchmarkScaleNNSrcUniform(b *testing.B) { benchScale(b, srcUniform, 200, 150, NearestNeighbor) }
+func BenchmarkScaleNNSrcRGBA(b *testing.B)    { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
+func BenchmarkScaleNNSrcUniform(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) }
 
-func BenchmarkTformNNSrcRGBA(b *testing.B)    { benchTform(b, srcRGBA, 200, 150, NearestNeighbor) }
-func BenchmarkTformNNSrcUniform(b *testing.B) { benchTform(b, srcUniform, 200, 150, NearestNeighbor) }
+func BenchmarkTformNNSrcRGBA(b *testing.B)    { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
+func BenchmarkTformNNSrcUniform(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) }
 
-func BenchmarkScaleABSrcGray(b *testing.B)  { benchScale(b, srcGray, 200, 150, ApproxBiLinear) }
-func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, srcNRGBA, 200, 150, ApproxBiLinear) }
-func BenchmarkScaleABSrcRGBA(b *testing.B)  { benchScale(b, srcRGBA, 200, 150, ApproxBiLinear) }
-func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, srcYCbCr, 200, 150, ApproxBiLinear) }
+func BenchmarkScaleABSrcGray(b *testing.B)  { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) }
+func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
+func BenchmarkScaleABSrcRGBA(b *testing.B)  { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
+func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
 
-func BenchmarkTformABSrcGray(b *testing.B)  { benchTform(b, srcGray, 200, 150, ApproxBiLinear) }
-func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, srcNRGBA, 200, 150, ApproxBiLinear) }
-func BenchmarkTformABSrcRGBA(b *testing.B)  { benchTform(b, srcRGBA, 200, 150, ApproxBiLinear) }
-func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, srcYCbCr, 200, 150, ApproxBiLinear) }
+func BenchmarkTformABSrcGray(b *testing.B)  { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) }
+func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
+func BenchmarkTformABSrcRGBA(b *testing.B)  { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
+func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
 
-func BenchmarkScaleCRSrcGray(b *testing.B)  { benchScale(b, srcGray, 200, 150, CatmullRom) }
-func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, srcNRGBA, 200, 150, CatmullRom) }
-func BenchmarkScaleCRSrcRGBA(b *testing.B)  { benchScale(b, srcRGBA, 200, 150, CatmullRom) }
-func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, srcYCbCr, 200, 150, CatmullRom) }
+func BenchmarkScaleCRSrcGray(b *testing.B)  { benchScale(b, 200, 150, Src, srcGray, CatmullRom) }
+func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) }
+func BenchmarkScaleCRSrcRGBA(b *testing.B)  { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) }
+func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) }
 
-func BenchmarkTformCRSrcGray(b *testing.B)  { benchTform(b, srcGray, 200, 150, CatmullRom) }
-func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, srcNRGBA, 200, 150, CatmullRom) }
-func BenchmarkTformCRSrcRGBA(b *testing.B)  { benchTform(b, srcRGBA, 200, 150, CatmullRom) }
-func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, srcYCbCr, 200, 150, CatmullRom) }
+func BenchmarkTformCRSrcGray(b *testing.B)  { benchTform(b, 200, 150, Src, srcGray, CatmullRom) }
+func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) }
+func BenchmarkTformCRSrcRGBA(b *testing.B)  { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) }
+func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) }