1package imaging
2
3import (
4 "image"
5 "math"
6)
7
8func gaussianBlurKernel(x, sigma float64) float64 {
9 return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
10}
11
12// Blur produces a blurred version of the image using a Gaussian function.
13// Sigma parameter must be positive and indicates how much the image will be blurred.
14//
15// Example:
16//
17// dstImage := imaging.Blur(srcImage, 3.5)
18//
19func Blur(img image.Image, sigma float64) *image.NRGBA {
20 if sigma <= 0 {
21 return Clone(img)
22 }
23
24 radius := int(math.Ceil(sigma * 3.0))
25 kernel := make([]float64, radius+1)
26
27 for i := 0; i <= radius; i++ {
28 kernel[i] = gaussianBlurKernel(float64(i), sigma)
29 }
30
31 return blurVertical(blurHorizontal(img, kernel), kernel)
32}
33
34func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
35 src := newScanner(img)
36 dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
37 radius := len(kernel) - 1
38
39 parallel(0, src.h, func(ys <-chan int) {
40 scanLine := make([]uint8, src.w*4)
41 scanLineF := make([]float64, len(scanLine))
42 for y := range ys {
43 src.scan(0, y, src.w, y+1, scanLine)
44 for i, v := range scanLine {
45 scanLineF[i] = float64(v)
46 }
47 for x := 0; x < src.w; x++ {
48 min := x - radius
49 if min < 0 {
50 min = 0
51 }
52 max := x + radius
53 if max > src.w-1 {
54 max = src.w - 1
55 }
56 var r, g, b, a, wsum float64
57 for ix := min; ix <= max; ix++ {
58 i := ix * 4
59 weight := kernel[absint(x-ix)]
60 wsum += weight
61 s := scanLineF[i : i+4 : i+4]
62 wa := s[3] * weight
63 r += s[0] * wa
64 g += s[1] * wa
65 b += s[2] * wa
66 a += wa
67 }
68 if a != 0 {
69 aInv := 1 / a
70 j := y*dst.Stride + x*4
71 d := dst.Pix[j : j+4 : j+4]
72 d[0] = clamp(r * aInv)
73 d[1] = clamp(g * aInv)
74 d[2] = clamp(b * aInv)
75 d[3] = clamp(a / wsum)
76 }
77 }
78 }
79 })
80
81 return dst
82}
83
84func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
85 src := newScanner(img)
86 dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
87 radius := len(kernel) - 1
88
89 parallel(0, src.w, func(xs <-chan int) {
90 scanLine := make([]uint8, src.h*4)
91 scanLineF := make([]float64, len(scanLine))
92 for x := range xs {
93 src.scan(x, 0, x+1, src.h, scanLine)
94 for i, v := range scanLine {
95 scanLineF[i] = float64(v)
96 }
97 for y := 0; y < src.h; y++ {
98 min := y - radius
99 if min < 0 {
100 min = 0
101 }
102 max := y + radius
103 if max > src.h-1 {
104 max = src.h - 1
105 }
106 var r, g, b, a, wsum float64
107 for iy := min; iy <= max; iy++ {
108 i := iy * 4
109 weight := kernel[absint(y-iy)]
110 wsum += weight
111 s := scanLineF[i : i+4 : i+4]
112 wa := s[3] * weight
113 r += s[0] * wa
114 g += s[1] * wa
115 b += s[2] * wa
116 a += wa
117 }
118 if a != 0 {
119 aInv := 1 / a
120 j := y*dst.Stride + x*4
121 d := dst.Pix[j : j+4 : j+4]
122 d[0] = clamp(r * aInv)
123 d[1] = clamp(g * aInv)
124 d[2] = clamp(b * aInv)
125 d[3] = clamp(a / wsum)
126 }
127 }
128 }
129 })
130
131 return dst
132}
133
134// Sharpen produces a sharpened version of the image.
135// Sigma parameter must be positive and indicates how much the image will be sharpened.
136//
137// Example:
138//
139// dstImage := imaging.Sharpen(srcImage, 3.5)
140//
141func Sharpen(img image.Image, sigma float64) *image.NRGBA {
142 if sigma <= 0 {
143 return Clone(img)
144 }
145
146 src := newScanner(img)
147 dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
148 blurred := Blur(img, sigma)
149
150 parallel(0, src.h, func(ys <-chan int) {
151 scanLine := make([]uint8, src.w*4)
152 for y := range ys {
153 src.scan(0, y, src.w, y+1, scanLine)
154 j := y * dst.Stride
155 for i := 0; i < src.w*4; i++ {
156 val := int(scanLine[i])<<1 - int(blurred.Pix[j])
157 if val < 0 {
158 val = 0
159 } else if val > 0xff {
160 val = 0xff
161 }
162 dst.Pix[j] = uint8(val)
163 j++
164 }
165 }
166 })
167
168 return dst
169}