96 lines
2.6 KiB
Go
96 lines
2.6 KiB
Go
|
package goeffects
|
||
|
|
||
|
// Package copied from https://github.com/markdaws/go-effects
|
||
|
|
||
|
import "image"
|
||
|
import "runtime"
|
||
|
|
||
|
|
||
|
type oilPainting struct {
|
||
|
filterSize int
|
||
|
levels int
|
||
|
}
|
||
|
|
||
|
// NewOilPainting renders the input image as if it was painted like an oil painting. numRoutines specifies how many
|
||
|
// goroutines should be used to process the image in parallel, use 0 to let the library decide. filterSize specifies
|
||
|
// how bold the image should look, larger numbers equate to larger strokes, levels specifies how many buckets colors
|
||
|
// will be grouped in to, start with values 5,30 to see how that works.
|
||
|
|
||
|
func NewOilPainting(filterSize, levels int) Effect {
|
||
|
return &oilPainting{filterSize: filterSize, levels: levels}
|
||
|
}
|
||
|
|
||
|
func (op *oilPainting) Apply(img *Image, numRoutines int) (*Image, error) {
|
||
|
levels := op.levels - 1
|
||
|
filterOffset := (op.filterSize - 1) / 2
|
||
|
|
||
|
if numRoutines == 0 {
|
||
|
numRoutines = runtime.GOMAXPROCS(0)
|
||
|
}
|
||
|
|
||
|
var iBin, rBin, gBin, bBin [][]int
|
||
|
iBin = make([][]int, numRoutines)
|
||
|
rBin = make([][]int, numRoutines)
|
||
|
gBin = make([][]int, numRoutines)
|
||
|
bBin = make([][]int, numRoutines)
|
||
|
for ri := 0; ri < numRoutines; ri++ {
|
||
|
iBin[ri] = make([]int, levels+1)
|
||
|
rBin[ri] = make([]int, levels+1)
|
||
|
gBin[ri] = make([]int, levels+1)
|
||
|
bBin[ri] = make([]int, levels+1)
|
||
|
}
|
||
|
|
||
|
pf := func(ri, x, y, offset, inStride int, inPix, outPix []uint8) {
|
||
|
reset(iBin[ri])
|
||
|
reset(rBin[ri])
|
||
|
reset(gBin[ri])
|
||
|
reset(bBin[ri])
|
||
|
|
||
|
var maxIntensity int
|
||
|
var maxIndex int
|
||
|
|
||
|
for fy := -filterOffset; fy <= filterOffset; fy++ {
|
||
|
for fx := -filterOffset; fx <= filterOffset; fx++ {
|
||
|
fOffset := offset + (fx*4 + fy*inStride)
|
||
|
r := inPix[fOffset]
|
||
|
g := inPix[fOffset+1]
|
||
|
b := inPix[fOffset+2]
|
||
|
ci := int(roundToInt32((float64(r+g+b) / 3.0 * float64(levels)) / 255.0))
|
||
|
iBin[ri][ci]++
|
||
|
rBin[ri][ci] += int(r)
|
||
|
gBin[ri][ci] += int(g)
|
||
|
bBin[ri][ci] += int(b)
|
||
|
|
||
|
if iBin[ri][ci] > maxIntensity {
|
||
|
maxIntensity = iBin[ri][ci]
|
||
|
maxIndex = ci
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
outPix[offset] = uint8(rBin[ri][maxIndex] / maxIntensity)
|
||
|
outPix[offset+1] = uint8(gBin[ri][maxIndex] / maxIntensity)
|
||
|
outPix[offset+2] = uint8(bBin[ri][maxIndex] / maxIntensity)
|
||
|
outPix[offset+3] = 255
|
||
|
}
|
||
|
|
||
|
out := &Image{
|
||
|
img: image.NewRGBA(image.Rectangle{
|
||
|
Min: image.Point{X: 0, Y: 0},
|
||
|
Max: image.Point{X: img.Width, Y: img.Height},
|
||
|
}),
|
||
|
Width: img.Width,
|
||
|
Height: img.Height,
|
||
|
Bounds: Rect{
|
||
|
X: img.Bounds.X + filterOffset,
|
||
|
Y: img.Bounds.Y + filterOffset,
|
||
|
Width: img.Bounds.Width - 2*filterOffset,
|
||
|
Height: img.Bounds.Height - 2*filterOffset,
|
||
|
},
|
||
|
}
|
||
|
runParallel(numRoutines, img, out.Bounds, out, pf, 0)
|
||
|
return out, nil
|
||
|
}
|
||
|
|
||
|
|