canvas/backend/softwarebackend/blur.go
2019-05-13 11:56:36 +02:00

316 lines
6.3 KiB
Go

package softwarebackend
import (
"image"
"image/color"
"image/draw"
"math"
)
func (b *SoftwareBackend) activateBlurTarget() {
b.blurSwap = b.Image
b.Image = image.NewRGBA(b.Image.Rect)
}
func (b *SoftwareBackend) drawBlurred(size float64) {
blurred := box3(b.Image, size)
b.Image = b.blurSwap
draw.Draw(b.Image, b.Image.Rect, blurred, image.ZP, draw.Over)
}
/*
func gauss(img *image.RGBA, size float64) *image.RGBA {
const maxDist = 128
var buf [2*maxDist + 1]float64
kernel := buf[:]
gaussianKernel(size, kernel)
dist := maxDist
for x := 0; x < maxDist; x++ {
if kernel[x] < 0.002 {
continue
}
dist = maxDist - x
kernel = kernel[x : len(kernel)-x]
break
}
fmt.Println(dist)
img = gaussx(img, kernel, dist)
return gaussy(img, kernel, dist)
}
func gaussx(img *image.RGBA, kernel []float64, maxDist int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
imgFrom, imgTo := x-maxDist, x+maxDist
kernelFrom := 0
if imgFrom < 0 {
kernelFrom -= imgFrom
imgFrom = 0
}
if imgTo >= w {
imgTo = w - 1
}
kernelSum := 0.0
var r, g, b, a float64
for xx, j := imgFrom, kernelFrom; xx <= imgTo; xx++ {
k := kernel[j]
kernelSum += k
col := img.RGBAAt(xx, y)
r += float64(col.R) * k
g += float64(col.G) * k
b += float64(col.B) * k
a += float64(col.A) * k
j++
}
col := color.RGBA{
R: uint8(math.Round(r / kernelSum)),
G: uint8(math.Round(g / kernelSum)),
B: uint8(math.Round(b / kernelSum)),
A: uint8(math.Round(a / kernelSum)),
}
result.SetRGBA(x, y, col)
}
}
return result
}
func gaussy(img *image.RGBA, kernel []float64, maxDist int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for y := 0; y < h; y++ {
imgFrom, imgTo := y-maxDist, y+maxDist
kernelFrom := 0
if imgFrom < 0 {
kernelFrom -= imgFrom
imgFrom = 0
}
if imgTo >= h {
imgTo = h - 1
}
for x := 0; x < w; x++ {
kernelSum := 0.0
var r, g, b, a float64
for yy, j := imgFrom, kernelFrom; yy <= imgTo; yy++ {
k := kernel[j]
kernelSum += k
col := img.RGBAAt(x, yy)
r += float64(col.R) * k
g += float64(col.G) * k
b += float64(col.B) * k
a += float64(col.A) * k
j++
}
col := color.RGBA{
R: uint8(math.Round(r / kernelSum)),
G: uint8(math.Round(g / kernelSum)),
B: uint8(math.Round(b / kernelSum)),
A: uint8(math.Round(a / kernelSum)),
}
result.SetRGBA(x, y, col)
}
}
return result
}
func gaussianKernel(stddev float64, target []float64) {
stddevSqr := stddev * stddev
center := float64(len(target) / 2)
factor := 1.0 / math.Sqrt(2*math.Pi*stddevSqr)
for i := range target {
x := float64(i) - center
target[i] = factor * math.Pow(math.E, -x*x/(2*stddevSqr))
}
}
*/
func box3(img *image.RGBA, size float64) *image.RGBA {
size *= 1 - 1/(size+1) // this just seems to improve the accuracy
fsize := math.Floor(size)
sizea := int(fsize)
sizeb := sizea
sizec := sizea
if size-fsize > 0.333333333 {
sizeb++
}
if size-fsize > 0.666666666 {
sizec++
}
img = box3x(img, sizea)
img = box3x(img, sizeb)
img = box3x(img, sizec)
img = box3y(img, sizea)
img = box3y(img, sizeb)
img = box3y(img, sizec)
return img
}
func box3x(img *image.RGBA, size int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for y := 0; y < h; y++ {
if size >= w {
var r, g, b, a float64
for x := 0; x < w; x++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
factor := 1.0 / float64(w)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
for x := 0; x < w; x++ {
result.SetRGBA(x, y, col)
}
continue
}
var r, g, b, a float64
for x := 0; x <= size; x++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
samples := size + 1
x := 0
for {
factor := 1.0 / float64(samples)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
result.SetRGBA(x, y, col)
if x >= w-1 {
break
}
if left := x - size; left >= 0 {
col = img.RGBAAt(left, y)
r -= float64(col.R)
g -= float64(col.G)
b -= float64(col.B)
a -= float64(col.A)
samples--
}
x++
if right := x + size; right < w {
col = img.RGBAAt(right, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
samples++
}
}
}
return result
}
func box3y(img *image.RGBA, size int) *image.RGBA {
bounds := img.Bounds()
result := image.NewRGBA(bounds)
w, h := bounds.Dx(), bounds.Dy()
for x := 0; x < w; x++ {
if size >= h {
var r, g, b, a float64
for y := 0; y < h; y++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
factor := 1.0 / float64(h)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
for y := 0; y < h; y++ {
result.SetRGBA(x, y, col)
}
continue
}
var r, g, b, a float64
for y := 0; y <= size; y++ {
col := img.RGBAAt(x, y)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
}
samples := size + 1
y := 0
for {
factor := 1.0 / float64(samples)
col := color.RGBA{
R: uint8(math.Round(r * factor)),
G: uint8(math.Round(g * factor)),
B: uint8(math.Round(b * factor)),
A: uint8(math.Round(a * factor)),
}
result.SetRGBA(x, y, col)
if y >= h-1 {
break
}
if top := y - size; top >= 0 {
col = img.RGBAAt(x, top)
r -= float64(col.R)
g -= float64(col.G)
b -= float64(col.B)
a -= float64(col.A)
samples--
}
y++
if bottom := y + size; bottom < h {
col = img.RGBAAt(x, bottom)
r += float64(col.R)
g += float64(col.G)
b += float64(col.B)
a += float64(col.A)
samples++
}
}
}
return result
}