|
|
@@ -22,15 +22,8 @@ import (
|
|
|
"math"
|
|
|
)
|
|
|
|
|
|
-// color.RGBA64 as array
|
|
|
-type rgba16 [4]uint16
|
|
|
-
|
|
|
-// build rgba16 from an arbitrary color
|
|
|
-func toRgba16(c color.Color) rgba16 {
|
|
|
- r, g, b, a := c.RGBA()
|
|
|
- return rgba16{uint16(r), uint16(g), uint16(b), uint16(a)}
|
|
|
-}
|
|
|
-
|
|
|
+// restrict an input float32 to the
|
|
|
+// range of uint16 values
|
|
|
func clampToUint16(x float32) (y uint16) {
|
|
|
y = uint16(x)
|
|
|
if x < 0 {
|
|
|
@@ -42,16 +35,16 @@ func clampToUint16(x float32) (y uint16) {
|
|
|
}
|
|
|
|
|
|
type filterModel struct {
|
|
|
- src image.Image
|
|
|
+ converter
|
|
|
factor [2]float32
|
|
|
kernel func(float32) float32
|
|
|
- tempRow, tempCol []rgba16
|
|
|
+ tempRow, tempCol []colorArray
|
|
|
}
|
|
|
|
|
|
-func (f *filterModel) convolution1d(x float32, p []rgba16, isRow bool) (c rgba16) {
|
|
|
+func (f *filterModel) convolution1d(x float32, p []colorArray, isRow bool) colorArray {
|
|
|
var k float32
|
|
|
var sum float32 = 0
|
|
|
- l := [4]float32{0.0, 0.0, 0.0, 0.0}
|
|
|
+ c := colorArray{0.0, 0.0, 0.0, 0.0}
|
|
|
|
|
|
var index uint
|
|
|
if isRow {
|
|
|
@@ -64,13 +57,15 @@ func (f *filterModel) convolution1d(x float32, p []rgba16, isRow bool) (c rgba16
|
|
|
k = f.kernel((x - float32(j)) / f.factor[index])
|
|
|
sum += k
|
|
|
for i := range c {
|
|
|
- l[i] += float32(p[j][i]) * k
|
|
|
+ c[i] += p[j][i] * k
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // normalize values
|
|
|
for i := range c {
|
|
|
- c[i] = clampToUint16(l[i] / sum)
|
|
|
+ c[i] = c[i] / sum
|
|
|
}
|
|
|
- return
|
|
|
+ return c
|
|
|
}
|
|
|
|
|
|
func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
|
|
|
@@ -80,19 +75,65 @@ func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
|
|
|
|
|
|
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] = f.at(xf+j, yf+i)
|
|
|
}
|
|
|
f.tempCol[i] = f.convolution1d(x, f.tempRow, true)
|
|
|
}
|
|
|
|
|
|
c := f.convolution1d(y, f.tempCol, false)
|
|
|
- return color.RGBA64{c[0], c[1], c[2], c[3]}
|
|
|
+ return color.RGBA64{
|
|
|
+ clampToUint16(c[0]),
|
|
|
+ clampToUint16(c[1]),
|
|
|
+ clampToUint16(c[2]),
|
|
|
+ clampToUint16(c[3]),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) Filter {
|
|
|
+// createFilter tries to find an optimized converter for the given input image
|
|
|
+// and initializes all filterModel members to their defaults
|
|
|
+func createFilter(img image.Image, factor [2]float32, size int, kernel func(float32) float32) (f 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)}
|
|
|
+
|
|
|
+ switch img.(type) {
|
|
|
+ default:
|
|
|
+ f = &filterModel{
|
|
|
+ &genericConverter{img},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ case *image.RGBA:
|
|
|
+ f = &filterModel{
|
|
|
+ &rgbaConverter{img.(*image.RGBA)},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ case *image.RGBA64:
|
|
|
+ f = &filterModel{
|
|
|
+ &rgba64Converter{img.(*image.RGBA64)},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ case *image.Gray:
|
|
|
+ f = &filterModel{
|
|
|
+ &grayConverter{img.(*image.Gray)},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ case *image.Gray16:
|
|
|
+ f = &filterModel{
|
|
|
+ &gray16Converter{img.(*image.Gray16)},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ case *image.YCbCr:
|
|
|
+ f = &filterModel{
|
|
|
+ &ycbcrConverter{img.(*image.YCbCr)},
|
|
|
+ factor, kernel,
|
|
|
+ make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
}
|
|
|
|
|
|
// Nearest-neighbor interpolation
|
|
|
@@ -127,6 +168,7 @@ func Bicubic(img image.Image, factor [2]float32) Filter {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+// Mitchell-Netravali interpolation
|
|
|
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)))
|
|
|
@@ -145,12 +187,12 @@ func lanczosKernel(a uint) func(float32) float32 {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// Lanczos interpolation (a=2).
|
|
|
+// Lanczos interpolation (a=2)
|
|
|
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, factor [2]float32) Filter {
|
|
|
return createFilter(img, factor, 6, lanczosKernel(3))
|
|
|
}
|