Browse Source

resize: optimizations & benchmarks

disintegration 8 years ago
parent
commit
e8d65a9608
2 changed files with 96 additions and 54 deletions
  1. 53 50
      resize.go
  2. 43 4
      resize_test.go

+ 53 - 50
resize.go

@@ -7,12 +7,12 @@ import (
 
 type iwpair struct {
 	i int
-	w int32
+	w float64
 }
 
 type pweights struct {
 	iwpairs []iwpair
-	wsum    int32
+	invsum  float64
 }
 
 func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights {
@@ -37,15 +37,15 @@ func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights {
 			endu = srcSize - 1
 		}
 
-		wsum := int32(0)
+		var sum float64
 		for u := startu; u <= endu; u++ {
-			w := int32(0xff * filter.Kernel((float64(u)-fu)/scale))
+			w := filter.Kernel((float64(u) - fu) / scale)
 			if w != 0 {
-				wsum += w
+				sum += w
 				out[v].iwpairs = append(out[v].iwpairs, iwpair{u, w})
 			}
 		}
-		out[v].wsum = wsum
+		out[v].invsum = 1 / sum
 	}
 
 	return out
@@ -127,22 +127,24 @@ func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image
 
 	parallel(dstH, func(partStart, partEnd int) {
 		for dstY := partStart; dstY < partEnd; dstY++ {
+			i0 := dstY * src.Stride
+			j0 := dstY * dst.Stride
 			for dstX := 0; dstX < dstW; dstX++ {
-				var c [4]int64
+				var r, g, b, a float64
 				for _, iw := range weights[dstX].iwpairs {
-					i := dstY*src.Stride + iw.i*4
-					a := int64(src.Pix[i+3]) * int64(iw.w)
-					c[0] += int64(src.Pix[i+0]) * a
-					c[1] += int64(src.Pix[i+1]) * a
-					c[2] += int64(src.Pix[i+2]) * a
-					c[3] += a
+					i := i0 + iw.i*4
+					aw := float64(src.Pix[i+3]) * iw.w
+					r += float64(src.Pix[i+0]) * aw
+					g += float64(src.Pix[i+1]) * aw
+					b += float64(src.Pix[i+2]) * aw
+					a += aw
 				}
-				j := dstY*dst.Stride + dstX*4
-				sum := weights[dstX].wsum
-				dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
-				dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
-				dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
-				dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
+				j := j0 + dstX*4
+				aInv := 1 / a
+				dst.Pix[j+0] = clamp(r * aInv)
+				dst.Pix[j+1] = clamp(g * aInv)
+				dst.Pix[j+2] = clamp(b * aInv)
+				dst.Pix[j+3] = clamp(a * weights[dstX].invsum)
 			}
 		}
 	})
@@ -163,33 +165,31 @@ func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.
 	weights := precomputeWeights(dstH, srcH, filter)
 
 	parallel(dstW, func(partStart, partEnd int) {
-
 		for dstX := partStart; dstX < partEnd; dstX++ {
 			for dstY := 0; dstY < dstH; dstY++ {
-				var c [4]int64
+				var r, g, b, a float64
 				for _, iw := range weights[dstY].iwpairs {
 					i := iw.i*src.Stride + dstX*4
-					a := int64(src.Pix[i+3]) * int64(iw.w)
-					c[0] += int64(src.Pix[i+0]) * a
-					c[1] += int64(src.Pix[i+1]) * a
-					c[2] += int64(src.Pix[i+2]) * a
-					c[3] += a
+					aw := float64(src.Pix[i+3]) * iw.w
+					r += float64(src.Pix[i+0]) * aw
+					g += float64(src.Pix[i+1]) * aw
+					b += float64(src.Pix[i+2]) * aw
+					a += aw
 				}
 				j := dstY*dst.Stride + dstX*4
-				sum := weights[dstY].wsum
-				dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
-				dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
-				dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
-				dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
+				aInv := 1 / a
+				dst.Pix[j+0] = clamp(r * aInv)
+				dst.Pix[j+1] = clamp(g * aInv)
+				dst.Pix[j+2] = clamp(b * aInv)
+				dst.Pix[j+3] = clamp(a * weights[dstY].invsum)
 			}
 		}
-
 	})
 
 	return dst
 }
 
-// fast nearest-neighbor resize, no filtering
+// resizeNearest is a fast nearest-neighbor resize, no filtering.
 func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
 	dstW, dstH := width, height
 
@@ -205,13 +205,16 @@ func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
 	parallel(dstH, func(partStart, partEnd int) {
 
 		for dstY := partStart; dstY < partEnd; dstY++ {
-			fy := (float64(dstY)+0.5)*dy - 0.5
+			srcY := int((float64(dstY) + 0.5) * dy)
+			if srcY > srcH-1 {
+				srcY = srcH - 1
+			}
 
 			for dstX := 0; dstX < dstW; dstX++ {
-				fx := (float64(dstX)+0.5)*dx - 0.5
-
-				srcX := int(math.Min(math.Max(math.Floor(fx+0.5), 0.0), float64(srcW)))
-				srcY := int(math.Min(math.Max(math.Floor(fy+0.5), 0.0), float64(srcH)))
+				srcX := int((float64(dstX) + 0.5) * dx)
+				if srcX > srcW-1 {
+					srcX = srcW - 1
+				}
 
 				srcOff := srcY*src.Stride + srcX*4
 				dstOff := dstY*dst.Stride + dstX*4
@@ -326,7 +329,7 @@ func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image
 	return Fill(img, width, height, Center, filter)
 }
 
-// Resample filter struct. It can be used to make custom filters.
+// ResampleFilter is a resampling filter struct. It can be used to define custom filters.
 //
 // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
 // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
@@ -361,7 +364,7 @@ type ResampleFilter struct {
 	Kernel  func(float64) float64
 }
 
-// Nearest-neighbor filter, no anti-aliasing.
+// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing).
 var NearestNeighbor ResampleFilter
 
 // Box filter (averaging pixels).
@@ -373,37 +376,37 @@ var Linear ResampleFilter
 // Hermite cubic spline filter (BC-spline; B=0; C=0).
 var Hermite ResampleFilter
 
-// Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
+// MitchellNetravali is Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
 var MitchellNetravali ResampleFilter
 
-// Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
+// CatmullRom is a Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
 var CatmullRom ResampleFilter
 
-// Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
+// BSpline is a smooth cubic filter (BC-spline; B=1; C=0).
 var BSpline ResampleFilter
 
-// Gaussian Blurring Filter.
+// Gaussian is a Gaussian blurring Filter.
 var Gaussian ResampleFilter
 
-// Bartlett-windowed sinc filter (3 lobes).
+// Bartlett is a Bartlett-windowed sinc filter (3 lobes).
 var Bartlett ResampleFilter
 
 // Lanczos filter (3 lobes).
 var Lanczos ResampleFilter
 
-// Hann-windowed sinc filter (3 lobes).
+// Hann is a Hann-windowed sinc filter (3 lobes).
 var Hann ResampleFilter
 
-// Hamming-windowed sinc filter (3 lobes).
+// Hamming is a Hamming-windowed sinc filter (3 lobes).
 var Hamming ResampleFilter
 
-// Blackman-windowed sinc filter (3 lobes).
+// Blackman is a Blackman-windowed sinc filter (3 lobes).
 var Blackman ResampleFilter
 
-// Welch-windowed sinc filter (parabolic window, 3 lobes).
+// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes).
 var Welch ResampleFilter
 
-// Cosine-windowed sinc filter (3 lobes).
+// Cosine is a Cosine-windowed sinc filter (3 lobes).
 var Cosine ResampleFilter
 
 func bcspline(x, b, c float64) float64 {

+ 43 - 4
resize_test.go

@@ -2,6 +2,7 @@ package imaging
 
 import (
 	"image"
+	"image/color"
 	"testing"
 )
 
@@ -108,10 +109,10 @@ func TestResize(t *testing.T) {
 				Rect:   image.Rect(0, 0, 4, 4),
 				Stride: 4 * 4,
 				Pix: []uint8{
-					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xff,
-					0x00, 0xff, 0x00, 0x3f, 0x6d, 0x6e, 0x24, 0x6f, 0xb1, 0x13, 0x3a, 0xd0, 0xc0, 0x00, 0x3f, 0xff,
-					0x00, 0xff, 0x00, 0xc0, 0x13, 0xb2, 0x3a, 0xcf, 0x33, 0x32, 0x9a, 0xef, 0x3f, 0x00, 0xc0, 0xff,
-					0x00, 0xff, 0x00, 0xff, 0x00, 0xc0, 0x3f, 0xff, 0x00, 0x3f, 0xc0, 0xff, 0x00, 0x00, 0xff, 0xff,
+					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x40, 0xff, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xff,
+					0x00, 0xff, 0x00, 0x40, 0x6e, 0x6d, 0x25, 0x70, 0xb0, 0x14, 0x3b, 0xcf, 0xbf, 0x00, 0x40, 0xff,
+					0x00, 0xff, 0x00, 0xbf, 0x14, 0xb0, 0x3b, 0xcf, 0x33, 0x33, 0x99, 0xef, 0x40, 0x00, 0xbf, 0xff,
+					0x00, 0xff, 0x00, 0xff, 0x00, 0xbf, 0x40, 0xff, 0x00, 0x40, 0xbf, 0xff, 0x00, 0x00, 0xff, 0xff,
 				},
 			},
 		},
@@ -568,3 +569,41 @@ func TestThumbnail(t *testing.T) {
 		}
 	}
 }
+
+func BenchmarkResizeLanczosUp(b *testing.B) {
+	benchmarkResize(b, 50, 500, Lanczos)
+}
+
+func BenchmarkResizeLinearUp(b *testing.B) {
+	benchmarkResize(b, 50, 500, Linear)
+}
+
+func BenchmarkResizeNearestNeighborUp(b *testing.B) {
+	benchmarkResize(b, 50, 500, NearestNeighbor)
+}
+
+func BenchmarkResizeLanczosDown(b *testing.B) {
+	benchmarkResize(b, 500, 50, Lanczos)
+}
+
+func BenchmarkResizeLinearDown(b *testing.B) {
+	benchmarkResize(b, 500, 50, Linear)
+}
+
+func BenchmarkResizeNearestNeighborDown(b *testing.B) {
+	benchmarkResize(b, 500, 50, NearestNeighbor)
+}
+
+func benchmarkResize(b *testing.B, size1, size2 int, f ResampleFilter) {
+	r := image.Rect(0, 0, size1, size1)
+	img := image.NewRGBA(r)
+	for x := r.Min.X; x < r.Max.X; x++ {
+		for y := r.Min.Y; y < r.Max.Y; y++ {
+			img.SetRGBA(x, y, color.RGBA{uint8(x % 255), uint8(y % 255), uint8((x + y) % 255), 255})
+		}
+	}
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		Resize(img, size2, size2, f)
+	}
+}