diff --git a/.gitignore b/.gitignore index 245da52..7895fde 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ test*.html plan.txt glowgen testapp +testblur videocap .DS_Store .vscode diff --git a/backend/softwarebackend/blur.go b/backend/softwarebackend/blur.go new file mode 100644 index 0000000..2038472 --- /dev/null +++ b/backend/softwarebackend/blur.go @@ -0,0 +1,188 @@ +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 box3(img *image.RGBA, size float64) *image.RGBA { + rsize := int(math.Round(size * 0.75)) // todo properly size + img = box3x(img, rsize) + img = box3x(img, rsize) + img = box3x(img, rsize) + img = box3y(img, rsize) + img = box3y(img, rsize) + img = box3y(img, rsize) + 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 +} diff --git a/backend/softwarebackend/fill.go b/backend/softwarebackend/fill.go index 1ea800a..4ad0d3b 100644 --- a/backend/softwarebackend/fill.go +++ b/backend/softwarebackend/fill.go @@ -20,6 +20,11 @@ func (b *SoftwareBackend) Clear(pts [4][2]float64) { } func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { + if style.Blur > 0 { + b.activateBlurTarget() + defer b.drawBlurred(style.Blur) + } + if lg := style.LinearGradient; lg != nil { lg := lg.(*LinearGradient) from := [2]float64{style.Gradient.X0, style.Gradient.Y0} diff --git a/backend/softwarebackend/software.go b/backend/softwarebackend/software.go index e5c2c21..db88e2a 100644 --- a/backend/softwarebackend/software.go +++ b/backend/softwarebackend/software.go @@ -12,6 +12,8 @@ type SoftwareBackend struct { MSAA int + blurSwap *image.RGBA + clip *image.Alpha mask *image.Alpha w, h int