فهرست منبع

Blur input image during downscaling by scaling the filter kernel to prevent moires in the output image

jst 14 سال پیش
والد
کامیت
e548f52385
3فایلهای تغییر یافته به همراه65 افزوده شده و 31 حذف شده
  1. 40 28
      filters.go
  2. 15 3
      resize.go
  3. 10 0
      resize_test.go

+ 40 - 28
filters.go

@@ -42,20 +42,26 @@ func clampToUint16(x float32) (y uint16) {
 }
 }
 
 
 type filterModel struct {
 type filterModel struct {
-	src     image.Image
-	size    int
-	kernel  func(float32) float32
-	tempRow []rgba16
-	tempCol []rgba16
+	src              image.Image
+	factor           [2]float32
+	kernel           func(float32) float32
+	tempRow, tempCol []rgba16
 }
 }
 
 
-func (f *filterModel) convolution1d(x float32, p []rgba16) (c rgba16) {
+func (f *filterModel) convolution1d(x float32, p []rgba16, isRow bool) (c rgba16) {
 	var k float32
 	var k float32
 	var sum float32 = 0
 	var sum float32 = 0
 	l := [4]float32{0.0, 0.0, 0.0, 0.0}
 	l := [4]float32{0.0, 0.0, 0.0, 0.0}
 
 
+	var index uint
+	if isRow {
+		index = 0
+	} else {
+		index = 1
+	}
+
 	for j := range p {
 	for j := range p {
-		k = f.kernel(x - float32(j))
+		k = f.kernel((x - float32(j)) / f.factor[index])
 		sum += k
 		sum += k
 		for i := range c {
 		for i := range c {
 			l[i] += float32(p[j][i]) * k
 			l[i] += float32(p[j][i]) * k
@@ -68,43 +74,49 @@ func (f *filterModel) convolution1d(x float32, p []rgba16) (c rgba16) {
 }
 }
 
 
 func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
 func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
-	xf, yf := int(x)-f.size/2+1, int(y)-f.size/2+1
+	xf, yf := int(x)-len(f.tempRow)/2+1, int(y)-len(f.tempCol)/2+1
 	x -= float32(xf)
 	x -= float32(xf)
 	y -= float32(yf)
 	y -= float32(yf)
 
 
-	for i := 0; i < f.size; i++ {
-		for j := 0; j < f.size; j++ {
+	for i := 0; i < len(f.tempCol); i++ {
+		for j := 0; j < len(f.tempRow); j++ {
 			f.tempRow[j] = toRgba16(f.src.At(xf+j, yf+i))
 			f.tempRow[j] = toRgba16(f.src.At(xf+j, yf+i))
 		}
 		}
-		f.tempCol[i] = f.convolution1d(x, f.tempRow)
+		f.tempCol[i] = f.convolution1d(x, f.tempRow, true)
 	}
 	}
 
 
-	c := f.convolution1d(y, f.tempCol)
+	c := f.convolution1d(y, f.tempCol, false)
 	return color.RGBA64{c[0], c[1], c[2], c[3]}
 	return color.RGBA64{c[0], c[1], c[2], c[3]}
 }
 }
 
 
+func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) Filter {
+	sizeX := size * (int(math.Ceil(float64(factor[0]))))
+	sizeY := size * (int(math.Ceil(float64(factor[1]))))
+	return &filterModel{img, factor, kernel, make([]rgba16, sizeX), make([]rgba16, sizeY)}
+}
+
 // Nearest-neighbor interpolation
 // Nearest-neighbor interpolation
-func NearestNeighbor(img image.Image) Filter {
-	return &filterModel{img, 2, func(x float32) (y float32) {
+func NearestNeighbor(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 2, func(x float32) (y float32) {
 		if x >= -0.5 && x < 0.5 {
 		if x >= -0.5 && x < 0.5 {
 			y = 1
 			y = 1
 		} else {
 		} else {
 			y = 0
 			y = 0
 		}
 		}
 		return
 		return
-	}, make([]rgba16, 2), make([]rgba16, 2)}
+	})
 }
 }
 
 
 // Bilinear interpolation
 // Bilinear interpolation
-func Bilinear(img image.Image) Filter {
-	return &filterModel{img, 2, func(x float32) float32 {
+func Bilinear(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 2, func(x float32) float32 {
 		return 1 - float32(math.Abs(float64(x)))
 		return 1 - float32(math.Abs(float64(x)))
-	}, make([]rgba16, 2), make([]rgba16, 2)}
+	})
 }
 }
 
 
 // Bicubic interpolation (with cubic hermite spline)
 // Bicubic interpolation (with cubic hermite spline)
-func Bicubic(img image.Image) Filter {
-	return &filterModel{img, 4, func(x float32) (y float32) {
+func Bicubic(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 4, func(x float32) (y float32) {
 		absX := float32(math.Abs(float64(x)))
 		absX := float32(math.Abs(float64(x)))
 		if absX <= 1 {
 		if absX <= 1 {
 			y = absX*absX*(1.5*absX-2.5) + 1
 			y = absX*absX*(1.5*absX-2.5) + 1
@@ -112,11 +124,11 @@ func Bicubic(img image.Image) Filter {
 			y = absX*(absX*(2.5-0.5*absX)-4) + 2
 			y = absX*(absX*(2.5-0.5*absX)-4) + 2
 		}
 		}
 		return
 		return
-	}, make([]rgba16, 4), make([]rgba16, 4)}
+	})
 }
 }
 
 
-func MitchellNetravali(img image.Image) Filter {
-	return &filterModel{img, 4, func(x float32) (y float32) {
+func MitchellNetravali(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 4, func(x float32) (y float32) {
 		absX := float32(math.Abs(float64(x)))
 		absX := float32(math.Abs(float64(x)))
 		if absX <= 1 {
 		if absX <= 1 {
 			y = absX*absX*(7*absX-12) + 16.0/3
 			y = absX*absX*(7*absX-12) + 16.0/3
@@ -124,7 +136,7 @@ func MitchellNetravali(img image.Image) Filter {
 			y = -(absX - 2) * (absX - 2) / 3 * (7*absX - 8)
 			y = -(absX - 2) * (absX - 2) / 3 * (7*absX - 8)
 		}
 		}
 		return
 		return
-	}, make([]rgba16, 4), make([]rgba16, 4)}
+	})
 }
 }
 
 
 func lanczosKernel(a uint) func(float32) float32 {
 func lanczosKernel(a uint) func(float32) float32 {
@@ -134,11 +146,11 @@ func lanczosKernel(a uint) func(float32) float32 {
 }
 }
 
 
 // Lanczos interpolation (a=2).
 // Lanczos interpolation (a=2).
-func Lanczos2(img image.Image) Filter {
-	return &filterModel{img, 4, lanczosKernel(2), make([]rgba16, 4), make([]rgba16, 4)}
+func Lanczos2(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 4, lanczosKernel(2))
 }
 }
 
 
 // Lanczos interpolation (a=3).
 // Lanczos interpolation (a=3).
-func Lanczos3(img image.Image) Filter {
-	return &filterModel{img, 6, lanczosKernel(3), make([]rgba16, 6), make([]rgba16, 6)}
+func Lanczos3(img image.Image, factor [2]float32) Filter {
+	return createFilter(img, factor, 6, lanczosKernel(3))
 }
 }

+ 15 - 3
resize.go

@@ -46,8 +46,10 @@ type Filter interface {
 }
 }
 
 
 // InterpolationFunction return a Filter implementation
 // InterpolationFunction return a Filter implementation
-// that operates on an image
-type InterpolationFunction func(image.Image) Filter
+// that operates on an image. Two factors
+// allow to scale the filter kernels in x- and y-direction
+// to prevent moire patterns.
+type InterpolationFunction func(image.Image, [2]float32) Filter
 
 
 // Resize an image to new width and height using the interpolation function interp.
 // Resize an image to new width and height using the interpolation function interp.
 // A new image with the given dimensions will be returned.
 // A new image with the given dimensions will be returned.
@@ -69,7 +71,7 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i
 	c := make(chan int, n)
 	c := make(chan int, n)
 	for i := 0; i < n; i++ {
 	for i := 0; i < n; i++ {
 		go func(b image.Rectangle, c chan int) {
 		go func(b image.Rectangle, c chan int) {
-			filter := interp(img)
+			filter := interp(img, [2]float32{clampFactor(scaleX), clampFactor(scaleY)})
 			var u, v float32
 			var u, v float32
 			for y := b.Min.Y; y < b.Max.Y; y++ {
 			for y := b.Min.Y; y < b.Max.Y; y++ {
 				for x := b.Min.X; x < b.Max.X; x++ {
 				for x := b.Min.X; x < b.Max.X; x++ {
@@ -109,6 +111,16 @@ func calcFactors(width, height uint, oldWidth, oldHeight float32) (scaleX, scale
 	return
 	return
 }
 }
 
 
+// Set filter scaling factor to avoid moire patterns.
+// This is only useful in case of downscaling (factor>1).
+func clampFactor(factor float32) (r float32) {
+	r = factor
+	if r < 1 {
+		r = 1
+	}
+	return
+}
+
 // Set number of parallel jobs
 // Set number of parallel jobs
 // but prevent resize from doing too much work
 // but prevent resize from doing too much work
 // if #CPUs > width
 // if #CPUs > width

+ 10 - 0
resize_test.go

@@ -51,3 +51,13 @@ func Benchmark_BigResize(b *testing.B) {
 	}
 	}
 	m.At(0, 0)
 	m.At(0, 0)
 }
 }
+
+func Benchmark_Reduction(b *testing.B) {
+	largeImg := image.NewRGBA(image.Rect(0, 0, 1000, 1000))
+
+	var m image.Image
+	for i := 0; i < b.N; i++ {
+		m = Resize(300, 300, largeImg, Lanczos3)
+	}
+	m.At(0, 0)
+}