2012-08-02 21:59:40 +02:00
|
|
|
/*
|
|
|
|
Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
|
|
|
|
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
|
|
with or without fee is hereby granted, provided that the above copyright notice
|
|
|
|
and this permission notice appear in all copies.
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
|
|
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
|
|
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
|
|
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
|
|
THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package resize
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
|
|
|
"image/color"
|
2012-09-14 23:12:05 +02:00
|
|
|
"math"
|
2012-08-02 21:59:40 +02:00
|
|
|
)
|
|
|
|
|
2012-12-11 20:18:23 +01:00
|
|
|
// restrict an input float32 to the range of uint16 values
|
2012-08-02 21:59:40 +02:00
|
|
|
func clampToUint16(x float32) (y uint16) {
|
|
|
|
y = uint16(x)
|
|
|
|
if x < 0 {
|
|
|
|
y = 0
|
2012-09-14 23:12:05 +02:00
|
|
|
} else if x > float32(0xfffe) {
|
2012-12-10 18:56:53 +01:00
|
|
|
// "else if x > float32(0xffff)" will cause overflows!
|
2012-08-02 21:59:40 +02:00
|
|
|
y = 0xffff
|
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-08-02 21:59:40 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2012-12-11 20:18:23 +01:00
|
|
|
// describe a resampling filter
|
2012-09-15 20:24:14 +02:00
|
|
|
type filterModel struct {
|
2012-12-11 20:24:30 +01:00
|
|
|
// resampling is done by convolution with a (scaled) kernel
|
|
|
|
kernel func(float32) float32
|
2012-12-11 20:18:23 +01:00
|
|
|
|
|
|
|
// instead of blurring an image before downscaling to avoid aliasing,
|
2012-12-13 22:13:37 +01:00
|
|
|
// the filter is scaled by a factor which leads to a similar effect
|
2012-12-11 20:18:23 +01:00
|
|
|
factor [2]float32
|
|
|
|
|
2012-12-11 20:24:30 +01:00
|
|
|
// for optimized access to image points
|
|
|
|
converter
|
2012-12-11 20:18:23 +01:00
|
|
|
|
|
|
|
// temporaries used by Interpolate
|
2012-09-21 20:02:25 +02:00
|
|
|
tempRow, tempCol []colorArray
|
2012-09-15 20:24:14 +02:00
|
|
|
}
|
|
|
|
|
2012-12-13 22:13:37 +01:00
|
|
|
func (f *filterModel) convolution1d(x float32, p []colorArray, factor float32) colorArray {
|
2012-09-14 23:12:05 +02:00
|
|
|
var k float32
|
|
|
|
var sum float32 = 0
|
2012-09-21 20:02:25 +02:00
|
|
|
c := colorArray{0.0, 0.0, 0.0, 0.0}
|
2012-09-04 18:49:04 +02:00
|
|
|
|
|
|
|
for j := range p {
|
2012-12-13 22:13:37 +01:00
|
|
|
k = f.kernel((x - float32(j)) / factor)
|
2012-09-14 23:12:05 +02:00
|
|
|
sum += k
|
2012-09-04 18:49:04 +02:00
|
|
|
for i := range c {
|
2012-09-21 20:02:25 +02:00
|
|
|
c[i] += p[j][i] * k
|
2012-09-04 18:49:04 +02:00
|
|
|
}
|
|
|
|
}
|
2012-09-21 20:02:25 +02:00
|
|
|
|
|
|
|
// normalize values
|
2012-09-04 18:49:04 +02:00
|
|
|
for i := range c {
|
2012-09-21 20:02:25 +02:00
|
|
|
c[i] = c[i] / sum
|
2012-09-04 18:49:04 +02:00
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
return c
|
2012-09-04 18:49:04 +02:00
|
|
|
}
|
|
|
|
|
2012-09-15 20:24:14 +02:00
|
|
|
func (f *filterModel) Interpolate(x, y float32) color.RGBA64 {
|
2012-09-19 21:03:56 +02:00
|
|
|
xf, yf := int(x)-len(f.tempRow)/2+1, int(y)-len(f.tempCol)/2+1
|
2012-09-16 09:20:11 +02:00
|
|
|
x -= float32(xf)
|
|
|
|
y -= float32(yf)
|
2012-09-04 18:49:04 +02:00
|
|
|
|
2012-12-13 21:55:44 +01:00
|
|
|
for i := range f.tempCol {
|
|
|
|
for j := range f.tempRow {
|
2012-09-21 20:02:25 +02:00
|
|
|
f.tempRow[j] = f.at(xf+j, yf+i)
|
2012-09-14 23:12:05 +02:00
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-12-13 22:13:37 +01:00
|
|
|
f.tempCol[i] = f.convolution1d(x, f.tempRow, f.factor[0])
|
2012-09-04 18:49:04 +02:00
|
|
|
}
|
|
|
|
|
2012-12-13 22:13:37 +01:00
|
|
|
c := f.convolution1d(y, f.tempCol, f.factor[1])
|
2012-09-21 20:02:25 +02:00
|
|
|
return color.RGBA64{
|
|
|
|
clampToUint16(c[0]),
|
|
|
|
clampToUint16(c[1]),
|
|
|
|
clampToUint16(c[2]),
|
|
|
|
clampToUint16(c[3]),
|
|
|
|
}
|
2012-09-04 18:49:04 +02:00
|
|
|
}
|
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
// 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) {
|
2012-09-19 21:03:56 +02:00
|
|
|
sizeX := size * (int(math.Ceil(float64(factor[0]))))
|
|
|
|
sizeY := size * (int(math.Ceil(float64(factor[1]))))
|
2012-09-21 20:02:25 +02:00
|
|
|
|
|
|
|
switch img.(type) {
|
|
|
|
default:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&genericConverter{img},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
case *image.RGBA:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&rgbaConverter{img.(*image.RGBA)},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
case *image.RGBA64:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&rgba64Converter{img.(*image.RGBA64)},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
case *image.Gray:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&grayConverter{img.(*image.Gray)},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
case *image.Gray16:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&gray16Converter{img.(*image.Gray16)},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
case *image.YCbCr:
|
|
|
|
f = &filterModel{
|
2012-12-11 20:24:30 +01:00
|
|
|
kernel, factor,
|
2012-09-21 20:02:25 +02:00
|
|
|
&ycbcrConverter{img.(*image.YCbCr)},
|
|
|
|
make([]colorArray, sizeX), make([]colorArray, sizeY),
|
|
|
|
}
|
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
return
|
2012-09-19 21:03:56 +02:00
|
|
|
}
|
|
|
|
|
2012-09-16 09:20:11 +02:00
|
|
|
// Nearest-neighbor interpolation
|
2012-09-19 21:03:56 +02:00
|
|
|
func NearestNeighbor(img image.Image, factor [2]float32) Filter {
|
|
|
|
return createFilter(img, factor, 2, func(x float32) (y float32) {
|
2012-09-15 19:30:32 +02:00
|
|
|
if x >= -0.5 && x < 0.5 {
|
2012-09-14 23:12:05 +02:00
|
|
|
y = 1
|
|
|
|
} else {
|
|
|
|
y = 0
|
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-09-14 23:12:05 +02:00
|
|
|
return
|
2012-09-19 21:03:56 +02:00
|
|
|
})
|
2012-09-14 23:12:05 +02:00
|
|
|
}
|
2012-09-04 18:49:04 +02:00
|
|
|
|
2012-09-16 09:20:11 +02:00
|
|
|
// Bilinear interpolation
|
2012-09-19 21:03:56 +02:00
|
|
|
func Bilinear(img image.Image, factor [2]float32) Filter {
|
2012-12-10 18:56:53 +01:00
|
|
|
return createFilter(img, factor, 2, func(x float32) (y float32) {
|
|
|
|
absX := float32(math.Abs(float64(x)))
|
|
|
|
if absX <= 1 {
|
|
|
|
y = 1 - absX
|
|
|
|
} else {
|
|
|
|
y = 0
|
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-12-10 18:56:53 +01:00
|
|
|
return
|
2012-09-19 21:03:56 +02:00
|
|
|
})
|
2012-09-14 23:12:05 +02:00
|
|
|
}
|
2012-09-04 18:49:04 +02:00
|
|
|
|
2013-04-04 22:32:33 +02:00
|
|
|
func splineKernel(B, C float32) func(float32) float32 {
|
2013-04-09 21:31:31 +02:00
|
|
|
factorA := 2.0-1.5*B-C
|
|
|
|
factorB := -3.0+2.0*B+C
|
|
|
|
factorC := 1.0-1.0/3.0*B
|
|
|
|
factorD := -B/6.0-C
|
|
|
|
factorE := B+5.0*C
|
|
|
|
factorF := -2.0*B-8.0*C
|
|
|
|
factorG := 4.0/3.0*B + 4.0*C
|
2013-04-04 22:32:33 +02:00
|
|
|
return func(x float32) (y float32) {
|
2012-09-15 19:30:32 +02:00
|
|
|
absX := float32(math.Abs(float64(x)))
|
|
|
|
if absX <= 1 {
|
2013-04-09 21:31:31 +02:00
|
|
|
y = absX*absX*(factorA*absX+factorB) +factorC
|
2012-12-10 18:56:53 +01:00
|
|
|
} else if absX <= 2 {
|
2013-04-09 21:31:31 +02:00
|
|
|
y = absX*(absX*(absX*factorD+factorE)+factorF) + factorG
|
2012-12-10 18:56:53 +01:00
|
|
|
} else {
|
|
|
|
y = 0
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-09-14 23:12:05 +02:00
|
|
|
return
|
2013-04-04 22:32:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Bicubic interpolation (with cubic hermite spline)
|
|
|
|
func Bicubic(img image.Image, factor [2]float32) Filter {
|
|
|
|
return createFilter(img, factor, 4, splineKernel(0, 0.5))
|
2012-09-14 23:12:05 +02:00
|
|
|
}
|
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
// Mitchell-Netravali interpolation
|
2012-09-19 21:03:56 +02:00
|
|
|
func MitchellNetravali(img image.Image, factor [2]float32) Filter {
|
2013-04-04 22:32:33 +02:00
|
|
|
return createFilter(img, factor, 4, splineKernel(1.0/3.0, 1.0/3.0))
|
2012-09-19 19:32:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func lanczosKernel(a uint) func(float32) float32 {
|
2012-12-10 18:56:53 +01:00
|
|
|
return func(x float32) (y float32) {
|
|
|
|
if x > -float32(a) && x < float32(a) {
|
|
|
|
y = float32(Sinc(float64(x))) * float32(Sinc(float64(x/float32(a))))
|
|
|
|
} else {
|
|
|
|
y = 0
|
|
|
|
}
|
2012-12-13 21:55:44 +01:00
|
|
|
|
2012-12-10 18:56:53 +01:00
|
|
|
return
|
2012-09-19 19:32:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
// Lanczos interpolation (a=2)
|
2012-09-19 21:03:56 +02:00
|
|
|
func Lanczos2(img image.Image, factor [2]float32) Filter {
|
|
|
|
return createFilter(img, factor, 4, lanczosKernel(2))
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|
|
|
|
|
2012-09-21 20:02:25 +02:00
|
|
|
// Lanczos interpolation (a=3)
|
2012-09-19 21:03:56 +02:00
|
|
|
func Lanczos3(img image.Image, factor [2]float32) Filter {
|
|
|
|
return createFilter(img, factor, 6, lanczosKernel(3))
|
2012-08-02 21:59:40 +02:00
|
|
|
}
|