diff --git a/filters.go b/filters.go index d74d303..08a3588 100644 --- a/filters.go +++ b/filters.go @@ -41,17 +41,24 @@ func clampToUint16(x float32) (y uint16) { return } -func convolution1d(x float32, kernel func(float32) float32, p []rgba16) (c rgba16) { +type filterModel struct { + src image.Image + size int + kernel func(float32) float32 + tempRow []rgba16 + tempCol []rgba16 +} + +func (f *filterModel) convolution1d(x float32, p []rgba16) (c rgba16) { x -= float32(int(x)) - - m := float32(len(p)/2-1) + m := float32(f.size/2 - 1) var k float32 var sum float32 = 0 l := [4]float32{0.0, 0.0, 0.0, 0.0} for j := range p { - k = kernel(x+m-float32(j)) + k = f.kernel(x + m - float32(j)) sum += k for i := range c { l[i] += float32(p[j][i]) * k @@ -63,51 +70,43 @@ func convolution1d(x float32, kernel func(float32) float32, p []rgba16) (c rgba1 return } -func filter(x, y float32, img image.Image, n int, kernel func(x float32) float32) color.RGBA64 { - xf, yf := int(x)-n/2+1, int(y)-n/2+1 +func (f *filterModel) Interpolate(x, y float32) color.RGBA64 { + xf, yf := int(x)-f.size/2+1, int(y)-f.size/2+1 - row := make([]rgba16, n) - col := make([]rgba16, n) - - for i := 0; i < n; i++ { - for j := 0; j < n; j++ { - row[j] = toRGBA(img.At(xf+j, yf+i)) + for i := 0; i < f.size; i++ { + for j := 0; j < f.size; j++ { + f.tempRow[j] = toRGBA(f.src.At(xf+j, yf+i)) } - col[i] = convolution1d(x, kernel, row) + f.tempCol[i] = f.convolution1d(x, f.tempRow) } - c := convolution1d(y, kernel, col) + c := f.convolution1d(y, f.tempCol) return color.RGBA64{c[0], c[1], c[2], c[3]} } // Nearest-neighbor interpolation. // Approximates a value by returning the value of the nearest point. -func NearestNeighbor(x, y float32, img image.Image) color.RGBA64 { - n := 2 - kernel := func(x float32) (y float32) { +func NearestNeighbor(img image.Image) Filter { + return &filterModel{img, 2, func(x float32) (y float32) { if x >= -0.5 && x < 0.5 { y = 1 } else { y = 0 } return - } - return filter(x, y, img, n, kernel) + }, make([]rgba16, 2), make([]rgba16, 2)} } // Bicubic interpolation -func Bilinear(x, y float32, img image.Image) color.RGBA64 { - n := 2 - kernel := func(x float32) float32 { +func Bilinear(img image.Image) Filter { + return &filterModel{img, 2, func(x float32) float32 { return 1 - float32(math.Abs(float64(x))) - } - return filter(x, y, img, n, kernel) + }, make([]rgba16, 2), make([]rgba16, 2)} } // Bicubic interpolation -func Bicubic(x, y float32, img image.Image) color.RGBA64 { - n := 4 - kernel := func(x float32) (y float32) { +func Bicubic(img image.Image) Filter { + return &filterModel{img, 4, func(x float32) (y float32) { absX := float32(math.Abs(float64(x))) if absX <= 1 { y = absX*absX*(1.5*absX-2.5) + 1 @@ -115,24 +114,19 @@ func Bicubic(x, y float32, img image.Image) color.RGBA64 { y = absX*(absX*(2.5-0.5*absX)-4) + 2 } return - } - return filter(x, y, img, n, kernel) + }, make([]rgba16, 4), make([]rgba16, 4)} } // Lanczos interpolation (a=2). -func Lanczos2(x, y float32, img image.Image) color.RGBA64 { - n := 4 - kernel := func(x float32) float32 { +func Lanczos2(img image.Image) Filter { + return &filterModel{img, 4, func(x float32) float32 { return float32(Sinc(float64(x))) * float32(Sinc(float64((x)/float32(2)))) - } - return filter(x, y, img, n, kernel) + }, make([]rgba16, 4), make([]rgba16, 4)} } // Lanczos interpolation (a=3). -func Lanczos3(x, y float32, img image.Image) color.RGBA64 { - n := 6 - kernel := func(x float32) float32 { +func Lanczos3(img image.Image) Filter { + return &filterModel{img, 6, func(x float32) float32 { return float32(Sinc(float64(x))) * float32(Sinc(float64((x)/float32(3)))) - } - return filter(x, y, img, n, kernel) + }, make([]rgba16, 6), make([]rgba16, 6)} } diff --git a/resize.go b/resize.go index c3d3b66..9555350 100644 --- a/resize.go +++ b/resize.go @@ -40,30 +40,14 @@ func (t *Trans2) Eval(x, y float32) (u, v float32) { return } -// Calculate scaling factors using old and new image dimensions. -func calcFactors(width, height uint, oldWidth, oldHeight float32) (scaleX, scaleY float32) { - if width == 0 { - if height == 0 { - scaleX = 1.0 - scaleY = 1.0 - } else { - scaleY = oldHeight / float32(height) - scaleX = scaleY - } - } else { - scaleX = oldWidth / float32(width) - if height == 0 { - scaleY = scaleX - } else { - scaleY = oldHeight / float32(height) - } - } - return +// Filter can interpolate at points (x,y) +type Filter interface { + Interpolate(x, y float32) color.RGBA64 } -// InterpolationFunction return a color for an arbitrary point inside -// an image -type InterpolationFunction func(float32, float32, image.Image) color.RGBA64 +// InterpolationFunction return a Filter implementation +// that operates on an image +type InterpolationFunction func(image.Image) Filter // Resize an image to new width and height using the interpolation function interp. // A new image with the given dimensions will be returned. @@ -85,11 +69,12 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i c := make(chan int, n) for i := 0; i < n; i++ { go func(b image.Rectangle, c chan int) { + filter := interp(img) var u, v float32 for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { u, v = t.Eval(float32(x), float32(y)) - resizedImg.SetRGBA64(x, y, interp(u, v, img)) + resizedImg.SetRGBA64(x, y, filter.Interpolate(u, v)) } } c <- 1 @@ -103,6 +88,27 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i return resizedImg } +// Calculate scaling factors using old and new image dimensions. +func calcFactors(width, height uint, oldWidth, oldHeight float32) (scaleX, scaleY float32) { + if width == 0 { + if height == 0 { + scaleX = 1.0 + scaleY = 1.0 + } else { + scaleY = oldHeight / float32(height) + scaleX = scaleY + } + } else { + scaleX = oldWidth / float32(width) + if height == 0 { + scaleY = scaleX + } else { + scaleY = oldHeight / float32(height) + } + } + return +} + // Set number of parallel jobs // but prevent resize from doing too much work // if #CPUs > width