rank.go

  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}