Browse Source

initial commit

nfnt 12 years ago
commit
428642c9f1
6 changed files with 355 additions and 0 deletions
  1. 0
    0
      README
  2. 142
    0
      filters.go
  3. 108
    0
      resize.go
  4. 18
    0
      resize_test.go
  5. 49
    0
      sinc.go
  6. 38
    0
      sinc_test.go

+ 0
- 0
README View File


+ 142
- 0
filters.go View File

@@ -0,0 +1,142 @@
1
+/*
2
+Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
17
+package resize
18
+
19
+import (
20
+	"image"
21
+	"image/color"
22
+	"math"
23
+)
24
+
25
+// color.RGBA64 as array
26
+type RGBA [4]uint16
27
+
28
+// build RGBA from an arbitrary color
29
+func toRGBA(c color.Color) RGBA {
30
+	n := color.RGBA64Model.Convert(c).(color.RGBA64)
31
+	return RGBA{n.R, n.G, n.B, n.A}
32
+}
33
+
34
+func clampToUint16(x float32) (y uint16) {
35
+	y = uint16(x)
36
+	if x < 0 {
37
+		y = 0
38
+	} else if x > float32(0xffff) {
39
+		y = 0xffff
40
+	}
41
+	return
42
+}
43
+
44
+// Nearest-neighbor interpolation.
45
+// Approximates a value by returning the value of the nearest point.
46
+func NearestNeighbor(x, y float32, img image.Image) color.RGBA64 {
47
+	xn, yn := int(x), int(y)
48
+	c := toRGBA(img.At(xn, yn))
49
+	return color.RGBA64{c[0], c[1], c[2], c[3]}
50
+}
51
+
52
+// Linear interpolation.
53
+func linearInterp(x float32, p *[2]RGBA) (c RGBA) {
54
+	x -= float32(math.Floor(float64(x)))
55
+	for i := range c {
56
+		c[i] = clampToUint16(float32(p[0][i])*(1.0-x) + x*float32(p[1][i]))
57
+	}
58
+	return
59
+}
60
+
61
+// Bilinear interpolation.
62
+func Bilinear(x, y float32, img image.Image) color.RGBA64 {
63
+	xf, yf := int(math.Floor(float64(x))), int(math.Floor(float64(y)))
64
+
65
+	var row [2]RGBA
66
+	var col [2]RGBA
67
+	row = [2]RGBA{toRGBA(img.At(xf, yf)), toRGBA(img.At(xf+1, yf))}
68
+	col[0] = linearInterp(x, &row)
69
+	row = [2]RGBA{toRGBA(img.At(xf, yf+1)), toRGBA(img.At(xf+1, yf+1))}
70
+	col[1] = linearInterp(x, &row)
71
+
72
+	c := linearInterp(y, &col)
73
+	return color.RGBA64{c[0], c[1], c[2], c[3]}
74
+}
75
+
76
+// cubic interpolation
77
+func cubicInterp(x float32, p *[4]RGBA) (c RGBA) {
78
+	x -= float32(math.Floor(float64(x)))
79
+	for i := range c {
80
+		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])))))
81
+	}
82
+	return
83
+}
84
+
85
+// Bicubic interpolation.
86
+func Bicubic(x, y float32, img image.Image) color.RGBA64 {
87
+	xf, yf := int(math.Floor(float64(x))), int(math.Floor(float64(y)))
88
+
89
+	var row [4]RGBA
90
+	var col [4]RGBA
91
+	row = [4]RGBA{toRGBA(img.At(xf-1, yf-1)), toRGBA(img.At(xf, yf-1)), toRGBA(img.At(xf+1, yf-1)), toRGBA(img.At(xf+2, yf-1))}
92
+	col[0] = cubicInterp(x, &row)
93
+	row = [4]RGBA{toRGBA(img.At(xf-1, yf)), toRGBA(img.At(xf, yf)), toRGBA(img.At(xf+1, yf)), toRGBA(img.At(xf+2, yf))}
94
+	col[1] = cubicInterp(x, &row)
95
+	row = [4]RGBA{toRGBA(img.At(xf-1, yf+1)), toRGBA(img.At(xf, yf+1)), toRGBA(img.At(xf+1, yf+1)), toRGBA(img.At(xf+2, yf+1))}
96
+	col[2] = cubicInterp(x, &row)
97
+	row = [4]RGBA{toRGBA(img.At(xf-1, yf+2)), toRGBA(img.At(xf, yf+2)), toRGBA(img.At(xf+1, yf+2)), toRGBA(img.At(xf+2, yf+2))}
98
+	col[3] = cubicInterp(x, &row)
99
+
100
+	c := cubicInterp(y, &col)
101
+	return color.RGBA64{c[0], c[1], c[2], c[3]}
102
+}
103
+
104
+// 1-d convolution with windowed sinc for a=3.
105
+func lanczos_x(x float32, p *[6]RGBA) (c RGBA) {
106
+	x -= float32(math.Floor(float64(x)))
107
+	var v float32
108
+	l := [4]float32{0.0, 0.0, 0.0, 0.0}
109
+	for j := range p {
110
+		v = float32(Sinc(float64(x-float32(j-2)))) * float32(Sinc(float64((x-float32(j-2))/3.0)))
111
+		for i := range c {
112
+			l[i] += float32(p[j][i]) * v
113
+		}
114
+	}
115
+	for i := range c {
116
+		c[i] = clampToUint16(l[i])
117
+	}
118
+	return
119
+}
120
+
121
+// Lanczos interpolation (a=3).
122
+func Lanczos3(x, y float32, img image.Image) color.RGBA64 {
123
+	xf, yf := int(math.Floor(float64(x))), int(math.Floor(float64(y)))
124
+
125
+	var row [6]RGBA
126
+	var col [6]RGBA
127
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf-2)), toRGBA(img.At(xf-1, yf-2)), toRGBA(img.At(xf, yf-2)), toRGBA(img.At(xf+1, yf-2)), toRGBA(img.At(xf+2, yf-2)), toRGBA(img.At(xf+3, yf-2))}
128
+	col[0] = lanczos_x(x, &row)
129
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf-1)), toRGBA(img.At(xf-1, yf-1)), toRGBA(img.At(xf, yf-1)), toRGBA(img.At(xf+1, yf-1)), toRGBA(img.At(xf+2, yf-1)), toRGBA(img.At(xf+3, yf-1))}
130
+	col[1] = lanczos_x(x, &row)
131
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf)), toRGBA(img.At(xf-1, yf)), toRGBA(img.At(xf, yf)), toRGBA(img.At(xf+1, yf)), toRGBA(img.At(xf+2, yf)), toRGBA(img.At(xf+3, yf))}
132
+	col[2] = lanczos_x(x, &row)
133
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf+1)), toRGBA(img.At(xf-1, yf+1)), toRGBA(img.At(xf, yf+1)), toRGBA(img.At(xf+1, yf+1)), toRGBA(img.At(xf+2, yf+1)), toRGBA(img.At(xf+3, yf+1))}
134
+	col[3] = lanczos_x(x, &row)
135
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf+2)), toRGBA(img.At(xf-1, yf+2)), toRGBA(img.At(xf, yf+2)), toRGBA(img.At(xf+1, yf+2)), toRGBA(img.At(xf+2, yf+2)), toRGBA(img.At(xf+3, yf+2))}
136
+	col[4] = lanczos_x(x, &row)
137
+	row = [6]RGBA{toRGBA(img.At(xf-2, yf+3)), toRGBA(img.At(xf-1, yf+3)), toRGBA(img.At(xf, yf+3)), toRGBA(img.At(xf+1, yf+3)), toRGBA(img.At(xf+2, yf+3)), toRGBA(img.At(xf+3, yf+3))}
138
+	col[5] = lanczos_x(x, &row)
139
+
140
+	c := lanczos_x(y, &col)
141
+	return color.RGBA64{c[0], c[1], c[2], c[3]}
142
+}

+ 108
- 0
resize.go View File

@@ -0,0 +1,108 @@
1
+/*
2
+Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
17
+// Package resize implements various image resizing methods.
18
+//
19
+// The package works with the Image interface described in the image package.
20
+// Various interpolation methods are provided and multiple processors may be
21
+// utilized in the computations.
22
+//
23
+// Example:
24
+//     imgResized := resize.Resize(1000, -1, imgOld, Lanczos3)
25
+package resize
26
+
27
+import (
28
+	"image"
29
+	"image/color"
30
+	"runtime"
31
+)
32
+
33
+var (
34
+	// NCPU holds the number of available CPUs at runtime.
35
+	NCPU = runtime.NumCPU()
36
+)
37
+
38
+// Trans2 is a 2-dimensional linear transformation.
39
+type Trans2 [6]float32
40
+
41
+// Apply the transformation to a point (x,y).
42
+func (t *Trans2) Eval(x, y float32) (u, v float32) {
43
+	u = t[0]*x + t[1]*y + t[2]
44
+	v = t[3]*x + t[4]*y + t[5]
45
+	return
46
+}
47
+
48
+// Calculate scaling factors using old and new image dimensions.
49
+func calcFactors(w, h int, wo, ho float32) (sx, sy float32) {
50
+	if w == -1 {
51
+		if h == -1 {
52
+			sx = 1.0
53
+			sy = 1.0
54
+		} else {
55
+			sy = ho / float32(h)
56
+			sx = sy
57
+		}
58
+	} else {
59
+		sx = wo / float32(w)
60
+		if h == -1 {
61
+			sy = sx
62
+		} else {
63
+			sy = ho / float32(h)
64
+		}
65
+	}
66
+	return
67
+}
68
+
69
+// InterpolationFunction return a color for an arbitrary point inside
70
+// an image
71
+type InterpolationFunction func(float32, float32, image.Image) color.RGBA64
72
+
73
+// Resize an image to new width w and height h using the interpolation function interp.
74
+// A new image with the given dimensions will be returned.
75
+// If one of the parameters w or h is set to -1, its size will be calculated so that
76
+// the aspect ratio is that of the originating image.
77
+// The resizing algorithm uses slices for parallel computation.
78
+func Resize(w int, h int, img image.Image, interp InterpolationFunction) image.Image {
79
+	b_old := img.Bounds()
80
+	w_old := float32(b_old.Dx())
81
+	h_old := float32(b_old.Dy())
82
+
83
+	scaleX, scaleY := calcFactors(w, h, w_old, h_old)
84
+	t := Trans2{scaleX, 0, float32(b_old.Min.X), 0, scaleY, float32(b_old.Min.Y)}
85
+
86
+	m := image.NewRGBA64(image.Rect(0, 0, int(w_old/scaleX), int(h_old/scaleY)))
87
+	b := m.Bounds()
88
+
89
+	c := make(chan int, NCPU)
90
+	for i := 0; i < NCPU; i++ {
91
+		go func(b image.Rectangle, c chan int) {
92
+			var u, v float32
93
+			for y := b.Min.Y; y < b.Max.Y; y++ {
94
+				for x := b.Min.X; x < b.Max.X; x++ {
95
+					u, v = t.Eval(float32(x), float32(y))
96
+					m.SetRGBA64(x, y, interp(u, v, img))
97
+				}
98
+			}
99
+			c <- 1
100
+		}(image.Rect(b.Min.X, b.Min.Y+i*(b.Dy())/4, b.Max.X, b.Min.Y+(i+1)*(b.Dy())/4), c)
101
+	}
102
+
103
+	for i := 0; i < NCPU; i++ {
104
+		<-c
105
+	}
106
+
107
+	return m
108
+}

+ 18
- 0
resize_test.go View File

@@ -0,0 +1,18 @@
1
+package resize
2
+
3
+import (
4
+	"image"
5
+	"image/color"
6
+	"testing"
7
+)
8
+
9
+func Test_Nearest(t *testing.T) {
10
+	img := image.NewGray16(image.Rect(0,0, 3,3))
11
+	img.Set(1,1, color.White)
12
+	
13
+	m := Resize(6,-1, img, NearestNeighbor)
14
+	
15
+	if m.At(2,2) != m.At(3,3) {
16
+		t.Fail()
17
+	}
18
+}

+ 49
- 0
sinc.go View File

@@ -0,0 +1,49 @@
1
+/*
2
+Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
3
+
4
+Permission to use, copy, modify, and/or distribute this software for any purpose
5
+with or without fee is hereby granted, provided that the above copyright notice
6
+and this permission notice appear in all copies.
7
+
8
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
10
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
12
+OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
13
+TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
14
+THIS SOFTWARE.
15
+*/
16
+
17
+package resize
18
+
19
+import (
20
+	"math"
21
+)
22
+
23
+var (
24
+	epsilon      = math.Nextafter(1.0, 2.0) - 1.0 // machine epsilon
25
+	taylor2bound = math.Sqrt(epsilon)
26
+	taylorNbound = math.Sqrt(taylor2bound)
27
+)
28
+
29
+// unnormalized sinc function
30
+func Sinc1(x float64) (y float64) {
31
+	if math.Abs(x) >= taylorNbound {
32
+		y = math.Sin(x) / x
33
+	} else {
34
+		y = 1.0
35
+		if math.Abs(x) >= epsilon {
36
+			x2 := x * x
37
+			y -= x2 / 6.0
38
+			if math.Abs(x) >= taylor2bound {
39
+				y += (x2 * x2) / 120.0
40
+			}
41
+		}
42
+	}
43
+	return
44
+}
45
+
46
+// normalized sinc function
47
+func Sinc(x float64) float64 {
48
+	return Sinc1(x * math.Pi)
49
+}

+ 38
- 0
sinc_test.go View File

@@ -0,0 +1,38 @@
1
+package resize
2
+
3
+import (
4
+	"fmt"
5
+	"math"
6
+	"testing"
7
+)
8
+
9
+const limit = 1e-12
10
+
11
+func Test_SincOne(t *testing.T) {
12
+	zero := Sinc(1)
13
+	if zero >= limit {
14
+		t.Error("Sinc(1) != 0")
15
+	}
16
+}
17
+
18
+func Test_SincZero(t *testing.T) {
19
+	one := Sinc(0)
20
+	if math.Abs(one-1) >= limit {
21
+		t.Error("Sinc(0) != 1")
22
+	}
23
+}
24
+
25
+func Test_SincDotOne(t *testing.T) {
26
+	res := Sinc(0.1)
27
+	if math.Abs(res-0.983631643083466) >= limit {
28
+		t.Error("Sinc(0.1) wrong")
29
+	}
30
+}
31
+
32
+func Test_SincNearZero(t *testing.T) {
33
+	res := Sinc(0.000001)
34
+	if math.Abs(res-0.9999999999983551) >= limit {
35
+		fmt.Println(res)
36
+		t.Error("Sinc near zero not stable")
37
+	}
38
+}