diff --git a/filters.go b/filters.go index 8d2eb3b..057371a 100644 --- a/filters.go +++ b/filters.go @@ -19,6 +19,7 @@ package resize import ( "image" "image/color" + "math" ) // color.RGBA64 as array @@ -34,81 +35,24 @@ func clampToUint16(x float32) (y uint16) { y = uint16(x) if x < 0 { y = 0 - } else if x > float32(0xffff) { + } else if x > float32(0xfffe) { y = 0xffff } return } -// Nearest-neighbor interpolation. -// Approximates a value by returning the value of the nearest point. -func NearestNeighbor(x, y float32, img image.Image) color.RGBA64 { - xn, yn := int(float32(int(x))+0.5), int(float32(int(y))+0.5) - c := toRGBA(img.At(xn, yn)) - return color.RGBA64{c[0], c[1], c[2], c[3]} -} - -// Linear interpolation. -func linearInterp(x float32, p *[2]RGBA) (c RGBA) { - x -= float32(int(x)) - for i := range c { - c[i] = clampToUint16(float32(p[0][i])*(1.0-x) + x*float32(p[1][i])) - } - return -} - -// Bilinear interpolation. -func Bilinear(x, y float32, img image.Image) color.RGBA64 { - xf, yf := int(x), int(y) - - var row [2]RGBA - var col [2]RGBA - for i := 0; i < 2; i++ { - row = [2]RGBA{toRGBA(img.At(xf, yf+i)), toRGBA(img.At(xf+1, yf+i))} - col[i] = linearInterp(x, &row) - } - - c := linearInterp(y, &col) - return color.RGBA64{c[0], c[1], c[2], c[3]} -} - -// cubic interpolation -func cubicInterp(x float32, p *[4]RGBA) (c RGBA) { - x -= float32(int(x)) - for i := range c { - c[i] = clampToUint16(float32(p[1][i]) + 0.5*x*(float32(p[2][i])-float32(p[0][i])+x*(2.0*float32(p[0][i])-5.0*float32(p[1][i])+4.0*float32(p[2][i])-float32(p[3][i])+x*(3.0*(float32(p[1][i])-float32(p[2][i]))+float32(p[3][i])-float32(p[0][i]))))) - } - return -} - -// Bicubic interpolation. -func Bicubic(x, y float32, img image.Image) color.RGBA64 { - xf, yf := int(x), int(y) - - var row [4]RGBA - var col [4]RGBA - for i := 0; i < 4; i++ { - row = [4]RGBA{toRGBA(img.At(xf-1, yf+i-1)), toRGBA(img.At(xf, yf+i-1)), toRGBA(img.At(xf+1, yf+i-1)), toRGBA(img.At(xf+2, yf+i-1))} - col[i] = cubicInterp(x, &row) - } - - c := cubicInterp(y, &col) - return color.RGBA64{c[0], c[1], c[2], c[3]} -} - -// 1-d convolution with windowed sinc for a=2. -func lanczos2_x(x float32, p *[4]RGBA) (c RGBA) { +func convolution1d(x float32, kernel func(float32, int) float32, p []RGBA) (c RGBA) { x -= float32(int(x)) - var kernel float32 - var sum float32 = 0 // for kernel normalization + var k float32 + var sum float32 = 0 l := [4]float32{0.0, 0.0, 0.0, 0.0} for j := range p { - kernel = float32(Sinc(float64(x-float32(j-1)))) * float32(Sinc(float64((x-float32(j-1))/2.0))) - sum += kernel + k = kernel(x, j) + sum += k for i := range c { - l[i] += float32(p[j][i]) * kernel + l[i] += float32(p[j][i]) * k } } for i := range c { @@ -117,53 +61,77 @@ func lanczos2_x(x float32, p *[4]RGBA) (c RGBA) { return } +func filter(x, y float32, img image.Image, n int, kernel func(x float32, j int) float32) color.RGBA64 { + xf, yf := int(x)-n/2+1, int(y)-n/2+1 + + row := make([]RGBA, n) + col := make([]RGBA, n) + + for i := 0; i < n; i++ { + for j := 0; j < n; j++ { + row[j] = toRGBA(img.At(xf+j, yf+i)) + } + col[i] = convolution1d(x, kernel, row) + } + + c := convolution1d(y, kernel, col) + 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, j int) (y float32) { + if x+0.5 >= float32(j) && x+0.5 < float32(j)+1 { + y = 1 + } else { + y = 0 + } + return + } + return filter(x, y, img, n, kernel) +} + +// Bicubic interpolation +func Bilinear(x, y float32, img image.Image) color.RGBA64 { + n := 2 + kernel := func(x float32, j int) float32 { + xa := float32(math.Abs(float64(x - float32(j)))) + return 1 - xa + } + return filter(x, y, img, n, kernel) +} + +// Bicubic interpolation +func Bicubic(x, y float32, img image.Image) color.RGBA64 { + n := 4 + kernel := func(x float32, j int) (y float32) { + xa := float32(math.Abs(float64(x - float32(j-1)))) + if xa <= 1 { + y = 1.5*xa*xa*xa - 2.5*xa*xa + 1 + } else { + y = -0.5*xa*xa*xa + 2.5*xa*xa - 4*xa + 2 + } + return + } + return filter(x, y, img, n, kernel) +} + // Lanczos interpolation (a=2). func Lanczos2(x, y float32, img image.Image) color.RGBA64 { - xf, yf := int(x), int(y) - - var row [4]RGBA - var col [4]RGBA - for i := range row { - row = [4]RGBA{toRGBA(img.At(xf-1, yf+i-1)), toRGBA(img.At(xf, yf+i-1)), toRGBA(img.At(xf+1, yf+i-1)), toRGBA(img.At(xf+2, yf+i-1))} - col[i] = lanczos2_x(x, &row) + n := 4 + kernel := func(x float32, j int) float32 { + return float32(Sinc(float64(x-float32(j-1)))) * float32(Sinc(float64((x-float32(j-1))/float32(2)))) } - - c := lanczos2_x(y, &col) - return color.RGBA64{c[0], c[1], c[2], c[3]} -} - -// 1-d convolution with windowed sinc for a=3. -func lanczos3_x(x float32, p *[6]RGBA) (c RGBA) { - x -= float32(int(x)) - - var kernel float32 - var sum float32 = 0 // for kernel normalization - l := [4]float32{0.0, 0.0, 0.0, 0.0} - - for j := range p { - kernel = float32(Sinc(float64(x-float32(j-2)))) * float32(Sinc(float64((x-float32(j-2))/3.0))) - sum += kernel - for i := range c { - l[i] += float32(p[j][i]) * kernel - } - } - for i := range c { - c[i] = clampToUint16(l[i] / sum) - } - return + return filter(x, y, img, n, kernel) } // Lanczos interpolation (a=3). func Lanczos3(x, y float32, img image.Image) color.RGBA64 { - xf, yf := int(x), int(y) - - var row [6]RGBA - var col [6]RGBA - for i := range row { - row = [6]RGBA{toRGBA(img.At(xf-2, yf+i-2)), toRGBA(img.At(xf-1, yf+i-2)), toRGBA(img.At(xf, yf+i-2)), toRGBA(img.At(xf+1, yf+i-2)), toRGBA(img.At(xf+2, yf+i-2)), toRGBA(img.At(xf+3, yf+i-2))} - col[i] = lanczos3_x(x, &row) + n := 6 + kernel := func(x float32, j int) float32 { + return float32(Sinc(float64(x-float32(j-2)))) * float32(Sinc(float64((x-float32(j-2))/float32(3)))) } - - c := lanczos3_x(y, &col) - return color.RGBA64{c[0], c[1], c[2], c[3]} + return filter(x, y, img, n, kernel) } diff --git a/resize.go b/resize.go index aee19c3..c3d3b66 100644 --- a/resize.go +++ b/resize.go @@ -30,11 +30,6 @@ import ( "runtime" ) -var ( - // NCPU holds the number of available CPUs at runtime. - NCPU = runtime.NumCPU() -) - // Trans2 is a 2-dimensional linear transformation. type Trans2 [6]float32 @@ -86,15 +81,7 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i resizedImg := image.NewRGBA64(image.Rect(0, 0, int(oldWidth/scaleX), int(oldHeight/scaleY))) b := resizedImg.Bounds() - // prevent resize from doing too much work - // if #CPUs > width - n := 1 - if NCPU < b.Dy() { - n = NCPU - } else { - n = b.Dy() - } - + n := numJobs(b.Dy()) c := make(chan int, n) for i := 0; i < n; i++ { go func(b image.Rectangle, c chan int) { @@ -115,3 +102,14 @@ func Resize(width, height uint, img image.Image, interp InterpolationFunction) i return resizedImg } + +// Set number of parallel jobs +// but prevent resize from doing too much work +// if #CPUs > width +func numJobs(d int) (n int) { + n = runtime.NumCPU() + if n > d { + n = d + } + return +}