1package gift
2
3import (
4 "image"
5 "image/draw"
6)
7
8type rankMode int
9
10const (
11 rankMedian rankMode = iota
12 rankMin
13 rankMax
14)
15
16type rankFilter struct {
17 ksize int
18 disk bool
19 mode rankMode
20}
21
22func (p *rankFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
23 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
24 return
25}
26
27func (p *rankFilter) Draw(dst draw.Image, src image.Image, options *Options) {
28 if options == nil {
29 options = &defaultOptions
30 }
31
32 srcb := src.Bounds()
33 dstb := dst.Bounds()
34
35 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
36 return
37 }
38
39 ksize := p.ksize
40 if ksize%2 == 0 {
41 ksize--
42 }
43
44 if ksize <= 1 {
45 copyimage(dst, src, options)
46 return
47 }
48 kradius := ksize / 2
49
50 opaque := isOpaque(src)
51
52 var disk []float32
53 if p.disk {
54 disk = genDisk(ksize)
55 }
56
57 pixGetter := newPixelGetter(src)
58 pixSetter := newPixelSetter(dst)
59
60 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
61 pxbuf := []pixel{}
62
63 var rbuf, gbuf, bbuf, abuf []float32
64 if p.mode == rankMedian {
65 rbuf = make([]float32, 0, ksize*ksize)
66 gbuf = make([]float32, 0, ksize*ksize)
67 bbuf = make([]float32, 0, ksize*ksize)
68 if !opaque {
69 abuf = make([]float32, 0, ksize*ksize)
70 }
71 }
72
73 for y := pmin; y < pmax; y++ {
74 // init buffer
75 pxbuf = pxbuf[:0]
76 for i := srcb.Min.X - kradius; i <= srcb.Min.X+kradius; i++ {
77 for j := y - kradius; j <= y+kradius; j++ {
78 kx, ky := i, j
79 if kx < srcb.Min.X {
80 kx = srcb.Min.X
81 } else if kx > srcb.Max.X-1 {
82 kx = srcb.Max.X - 1
83 }
84 if ky < srcb.Min.Y {
85 ky = srcb.Min.Y
86 } else if ky > srcb.Max.Y-1 {
87 ky = srcb.Max.Y - 1
88 }
89 pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky))
90 }
91 }
92
93 for x := srcb.Min.X; x < srcb.Max.X; x++ {
94 var r, g, b, a float32
95 if p.mode == rankMedian {
96 rbuf = rbuf[:0]
97 gbuf = gbuf[:0]
98 bbuf = bbuf[:0]
99 if !opaque {
100 abuf = abuf[:0]
101 }
102 } else if p.mode == rankMin {
103 r, g, b, a = 1, 1, 1, 1
104 } else if p.mode == rankMax {
105 r, g, b, a = 0, 0, 0, 0
106 }
107
108 sz := 0
109 for i := 0; i < ksize; i++ {
110 for j := 0; j < ksize; j++ {
111
112 if p.disk {
113 if disk[i*ksize+j] == 0 {
114 continue
115 }
116 }
117
118 px := pxbuf[i*ksize+j]
119 if p.mode == rankMedian {
120 rbuf = append(rbuf, px.r)
121 gbuf = append(gbuf, px.g)
122 bbuf = append(bbuf, px.b)
123 if !opaque {
124 abuf = append(abuf, px.a)
125 }
126 } else if p.mode == rankMin {
127 r = minf32(r, px.r)
128 g = minf32(g, px.g)
129 b = minf32(b, px.b)
130 if !opaque {
131 a = minf32(a, px.a)
132 }
133 } else if p.mode == rankMax {
134 r = maxf32(r, px.r)
135 g = maxf32(g, px.g)
136 b = maxf32(b, px.b)
137 if !opaque {
138 a = maxf32(a, px.a)
139 }
140 }
141 sz++
142 }
143 }
144
145 if p.mode == rankMedian {
146 sort(rbuf)
147 sort(gbuf)
148 sort(bbuf)
149 if !opaque {
150 sort(abuf)
151 }
152
153 idx := sz / 2
154 r, g, b = rbuf[idx], gbuf[idx], bbuf[idx]
155 if !opaque {
156 a = abuf[idx]
157 }
158 }
159
160 if opaque {
161 a = 1
162 }
163
164 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
165
166 // rotate buffer columns
167 if x < srcb.Max.X-1 {
168 copy(pxbuf[0:], pxbuf[ksize:])
169 pxbuf = pxbuf[0 : ksize*(ksize-1)]
170 kx := x + 1 + kradius
171 if kx > srcb.Max.X-1 {
172 kx = srcb.Max.X - 1
173 }
174 for j := y - kradius; j <= y+kradius; j++ {
175 ky := j
176 if ky < srcb.Min.Y {
177 ky = srcb.Min.Y
178 } else if ky > srcb.Max.Y-1 {
179 ky = srcb.Max.Y - 1
180 }
181 pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky))
182 }
183 }
184 }
185 }
186 })
187}
188
189// Median creates a median image filter.
190// Picks a median value per channel in neighborhood for each pixel.
191// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
192// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
193func Median(ksize int, disk bool) Filter {
194 return &rankFilter{
195 ksize: ksize,
196 disk: disk,
197 mode: rankMedian,
198 }
199}
200
201// Minimum creates a local minimum image filter.
202// Picks a minimum value per channel in neighborhood for each pixel.
203// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
204// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
205func Minimum(ksize int, disk bool) Filter {
206 return &rankFilter{
207 ksize: ksize,
208 disk: disk,
209 mode: rankMin,
210 }
211}
212
213// Maximum creates a local maximum image filter.
214// Picks a maximum value per channel in neighborhood for each pixel.
215// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
216// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
217func Maximum(ksize int, disk bool) Filter {
218 return &rankFilter{
219 ksize: ksize,
220 disk: disk,
221 mode: rankMax,
222 }
223}