Blur input image during downscaling by scaling the filter kernel to prevent moires in the output image
This commit is contained in:
parent
d0b2b9bc39
commit
e548f52385
68
filters.go
68
filters.go
|
@ -42,20 +42,26 @@ func clampToUint16(x float32) (y uint16) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type filterModel struct {
|
type filterModel struct {
|
||||||
src image.Image
|
src image.Image
|
||||||
size int
|
factor [2]float32
|
||||||
kernel func(float32) float32
|
kernel func(float32) float32
|
||||||
tempRow []rgba16
|
tempRow, tempCol []rgba16
|
||||||
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 i := 0; i < len(f.tempCol); i++ {
|
||||||
for j := 0; j < f.size; j++ {
|
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 {
|
func NearestNeighbor(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 2, func(x float32) (y float32) {
|
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 {
|
func Bilinear(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 2, func(x float32) float32 {
|
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 {
|
func Bicubic(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 4, func(x float32) (y float32) {
|
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 {
|
func MitchellNetravali(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 4, func(x float32) (y float32) {
|
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 {
|
func Lanczos2(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 4, lanczosKernel(2), make([]rgba16, 4), make([]rgba16, 4)}
|
return createFilter(img, factor, 4, lanczosKernel(2))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lanczos interpolation (a=3).
|
// Lanczos interpolation (a=3).
|
||||||
func Lanczos3(img image.Image) Filter {
|
func Lanczos3(img image.Image, factor [2]float32) Filter {
|
||||||
return &filterModel{img, 6, lanczosKernel(3), make([]rgba16, 6), make([]rgba16, 6)}
|
return createFilter(img, factor, 6, lanczosKernel(3))
|
||||||
}
|
}
|
||||||
|
|
18
resize.go
18
resize.go
|
@ -46,8 +46,10 @@ type Filter interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// InterpolationFunction return a Filter implementation
|
// InterpolationFunction return a Filter implementation
|
||||||
// that operates on an image
|
// that operates on an image. Two factors
|
||||||
type InterpolationFunction func(image.Image) Filter
|
// 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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user