223 lines
4.9 KiB
Go
223 lines
4.9 KiB
Go
package gift
|
|
|
|
import (
|
|
"image"
|
|
"image/draw"
|
|
)
|
|
|
|
type rankMode int
|
|
|
|
const (
|
|
rankMedian rankMode = iota
|
|
rankMin
|
|
rankMax
|
|
)
|
|
|
|
type rankFilter struct {
|
|
ksize int
|
|
disk bool
|
|
mode rankMode
|
|
}
|
|
|
|
func (p *rankFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
|
|
dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
|
|
return
|
|
}
|
|
|
|
func (p *rankFilter) Draw(dst draw.Image, src image.Image, options *Options) {
|
|
if options == nil {
|
|
options = &defaultOptions
|
|
}
|
|
|
|
srcb := src.Bounds()
|
|
dstb := dst.Bounds()
|
|
|
|
if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
|
|
return
|
|
}
|
|
|
|
ksize := p.ksize
|
|
if ksize%2 == 0 {
|
|
ksize--
|
|
}
|
|
|
|
if ksize <= 1 {
|
|
copyimage(dst, src, options)
|
|
return
|
|
}
|
|
kradius := ksize / 2
|
|
|
|
opaque := isOpaque(src)
|
|
|
|
var disk []float32
|
|
if p.disk {
|
|
disk = genDisk(ksize)
|
|
}
|
|
|
|
pixGetter := newPixelGetter(src)
|
|
pixSetter := newPixelSetter(dst)
|
|
|
|
parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(start, stop int) {
|
|
pxbuf := []pixel{}
|
|
|
|
var rbuf, gbuf, bbuf, abuf []float32
|
|
if p.mode == rankMedian {
|
|
rbuf = make([]float32, 0, ksize*ksize)
|
|
gbuf = make([]float32, 0, ksize*ksize)
|
|
bbuf = make([]float32, 0, ksize*ksize)
|
|
if !opaque {
|
|
abuf = make([]float32, 0, ksize*ksize)
|
|
}
|
|
}
|
|
|
|
for y := start; y < stop; y++ {
|
|
// Init buffer.
|
|
pxbuf = pxbuf[:0]
|
|
for i := srcb.Min.X - kradius; i <= srcb.Min.X+kradius; i++ {
|
|
for j := y - kradius; j <= y+kradius; j++ {
|
|
kx, ky := i, j
|
|
if kx < srcb.Min.X {
|
|
kx = srcb.Min.X
|
|
} else if kx > srcb.Max.X-1 {
|
|
kx = srcb.Max.X - 1
|
|
}
|
|
if ky < srcb.Min.Y {
|
|
ky = srcb.Min.Y
|
|
} else if ky > srcb.Max.Y-1 {
|
|
ky = srcb.Max.Y - 1
|
|
}
|
|
pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky))
|
|
}
|
|
}
|
|
|
|
for x := srcb.Min.X; x < srcb.Max.X; x++ {
|
|
var r, g, b, a float32
|
|
if p.mode == rankMedian {
|
|
rbuf = rbuf[:0]
|
|
gbuf = gbuf[:0]
|
|
bbuf = bbuf[:0]
|
|
if !opaque {
|
|
abuf = abuf[:0]
|
|
}
|
|
} else if p.mode == rankMin {
|
|
r, g, b, a = 1, 1, 1, 1
|
|
} else if p.mode == rankMax {
|
|
r, g, b, a = 0, 0, 0, 0
|
|
}
|
|
|
|
sz := 0
|
|
for i := 0; i < ksize; i++ {
|
|
for j := 0; j < ksize; j++ {
|
|
|
|
if p.disk {
|
|
if disk[i*ksize+j] == 0 {
|
|
continue
|
|
}
|
|
}
|
|
|
|
px := pxbuf[i*ksize+j]
|
|
if p.mode == rankMedian {
|
|
rbuf = append(rbuf, px.r)
|
|
gbuf = append(gbuf, px.g)
|
|
bbuf = append(bbuf, px.b)
|
|
if !opaque {
|
|
abuf = append(abuf, px.a)
|
|
}
|
|
} else if p.mode == rankMin {
|
|
r = minf32(r, px.r)
|
|
g = minf32(g, px.g)
|
|
b = minf32(b, px.b)
|
|
if !opaque {
|
|
a = minf32(a, px.a)
|
|
}
|
|
} else if p.mode == rankMax {
|
|
r = maxf32(r, px.r)
|
|
g = maxf32(g, px.g)
|
|
b = maxf32(b, px.b)
|
|
if !opaque {
|
|
a = maxf32(a, px.a)
|
|
}
|
|
}
|
|
sz++
|
|
}
|
|
}
|
|
|
|
if p.mode == rankMedian {
|
|
sort(rbuf)
|
|
sort(gbuf)
|
|
sort(bbuf)
|
|
if !opaque {
|
|
sort(abuf)
|
|
}
|
|
|
|
idx := sz / 2
|
|
r, g, b = rbuf[idx], gbuf[idx], bbuf[idx]
|
|
if !opaque {
|
|
a = abuf[idx]
|
|
}
|
|
}
|
|
|
|
if opaque {
|
|
a = 1
|
|
}
|
|
|
|
pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
|
|
|
|
// Rotate buffer columns.
|
|
if x < srcb.Max.X-1 {
|
|
copy(pxbuf[0:], pxbuf[ksize:])
|
|
pxbuf = pxbuf[0 : ksize*(ksize-1)]
|
|
kx := x + 1 + kradius
|
|
if kx > srcb.Max.X-1 {
|
|
kx = srcb.Max.X - 1
|
|
}
|
|
for j := y - kradius; j <= y+kradius; j++ {
|
|
ky := j
|
|
if ky < srcb.Min.Y {
|
|
ky = srcb.Min.Y
|
|
} else if ky > srcb.Max.Y-1 {
|
|
ky = srcb.Max.Y - 1
|
|
}
|
|
pxbuf = append(pxbuf, pixGetter.getPixel(kx, ky))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Median creates a median image filter.
|
|
// Picks a median value per channel in neighborhood for each pixel.
|
|
// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
|
|
// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
|
|
func Median(ksize int, disk bool) Filter {
|
|
return &rankFilter{
|
|
ksize: ksize,
|
|
disk: disk,
|
|
mode: rankMedian,
|
|
}
|
|
}
|
|
|
|
// Minimum creates a local minimum image filter.
|
|
// Picks a minimum value per channel in neighborhood for each pixel.
|
|
// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
|
|
// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
|
|
func Minimum(ksize int, disk bool) Filter {
|
|
return &rankFilter{
|
|
ksize: ksize,
|
|
disk: disk,
|
|
mode: rankMin,
|
|
}
|
|
}
|
|
|
|
// Maximum creates a local maximum image filter.
|
|
// Picks a maximum value per channel in neighborhood for each pixel.
|
|
// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
|
|
// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
|
|
func Maximum(ksize int, disk bool) Filter {
|
|
return &rankFilter{
|
|
ksize: ksize,
|
|
disk: disk,
|
|
mode: rankMax,
|
|
}
|
|
}
|