Skip to content

Commit 15ceb4d

Browse files
author
Angelos Katharopoulos
committed
Make gaussian blur using separable convolution
1 parent 244b621 commit 15ceb4d

File tree

3 files changed

+32
-19
lines changed

3 files changed

+32
-19
lines changed

blur/blur.go

+13-14
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,18 @@ func Gaussian(src image.Image, radius float64) *image.RGBA {
3535
return clone.AsRGBA(src)
3636
}
3737

38+
// Create the 1-d gaussian kernel
3839
length := int(math.Ceil(2*radius + 1))
39-
k := convolution.NewKernel(length, length)
40-
41-
gaussianFn := func(x, y, sigma float64) float64 {
42-
return math.Exp(-(x*x/sigma + y*y/sigma))
43-
}
44-
45-
for x := 0; x < length; x++ {
46-
for y := 0; y < length; y++ {
47-
k.Matrix[y*length+x] = gaussianFn(float64(x)-radius, float64(y)-radius, 4*radius)
48-
49-
}
50-
}
51-
52-
return convolution.Convolve(src, k.Normalized(), &convolution.Options{Bias: 0, Wrap: false, KeepAlpha: false})
40+
k := convolution.NewKernel(length, 1)
41+
for i, x := 0, -radius; i < length; i, x = i+1, x+1 {
42+
k.Matrix[i] = math.Exp(-(x*x/4/radius))
43+
}
44+
normK := k.Normalized()
45+
46+
// Perform separable convolution
47+
options := convolution.Options{Bias: 0, Wrap: false, KeepAlpha: false}
48+
result := convolution.Convolve(src, normK, &options)
49+
result = convolution.Convolve(result, normK.Transpose(), &options)
50+
51+
return result
5352
}

blur/blur_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,9 @@ func TestGaussianBlur(t *testing.T) {
128128
Rect: image.Rect(0, 0, 3, 3),
129129
Stride: 3 * 4,
130130
Pix: []uint8{
131-
0xb1, 0xb1, 0xb1, 0xff, 0xbd, 0xbd, 0xbd, 0xff, 0xcc, 0xcc, 0xcc, 0xff,
132-
0x4d, 0x4d, 0x4d, 0xff, 0x68, 0x68, 0x68, 0xff, 0x8b, 0x8b, 0x8b, 0xff,
133-
0x0, 0x0, 0x0, 0xff, 0x26, 0x26, 0x26, 0xff, 0x59, 0x59, 0x59, 0xff,
131+
0xb1, 0xb1, 0xb1, 0xff, 0xbc, 0xbc, 0xbc, 0xff, 0xcc, 0xcc, 0xcc, 0xff,
132+
0x4d, 0x4d, 0x4d, 0xff, 0x68, 0x68, 0x68, 0xff, 0x8b, 0x8b, 0x8b, 0xff,
133+
0x0, 0x0, 0x0, 0xff, 0x25, 0x25, 0x25, 0xff, 0x58, 0x58, 0x58, 0xff,
134134
},
135135
},
136136
},
@@ -157,10 +157,10 @@ func TestGaussianBlur(t *testing.T) {
157157
},
158158
}
159159

160-
for _, c := range cases {
160+
for i, c := range cases {
161161
actual := Gaussian(c.value, c.radius)
162162
if !util.RGBAImageEqual(actual, c.expected) {
163-
t.Errorf("%s: expected: %#v, actual: %#v", "GaussianBlur", c.expected, actual)
163+
t.Errorf("%s %d: expected: %#v, actual: %#v", "GaussianBlur", i, c.expected, actual)
164164
}
165165
}
166166
}

convolution/kernel.go

+14
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type Matrix interface {
1515
Normalized() Matrix
1616
MaxX() int
1717
MaxY() int
18+
Transpose() Matrix
1819
}
1920

2021
// NewKernel returns a kernel of the provided length.
@@ -63,6 +64,19 @@ func (k *Kernel) At(x, y int) float64 {
6364
return k.Matrix[y*k.Width+x]
6465
}
6566

67+
// Transpose returns a new Kernel that is transposed
68+
func (k *Kernel) Transpose() Matrix {
69+
w := k.Width;
70+
h := k.Height;
71+
nk := NewKernel(h, w)
72+
73+
for i := 0; i < w*h; i++ {
74+
nk.Matrix[i] = k.Matrix[i];
75+
}
76+
77+
return nk
78+
}
79+
6680
// String returns the string representation of the matrix.
6781
func (k *Kernel) String() string {
6882
result := ""

0 commit comments

Comments
 (0)