Skip to content

Instantly share code, notes, and snippets.

@wjkoh
Last active February 1, 2024 10:52
Show Gist options
  • Save wjkoh/660c97cfd3133bae936a431d20ab983e to your computer and use it in GitHub Desktop.
Save wjkoh/660c97cfd3133bae936a431d20ab983e to your computer and use it in GitHub Desktop.
Go: Box blur
package bake
import (
"errors"
"image"
"image/draw"
)
// Try radius: 8 and numPasses: 2 with
// https://cdn.pixabay.com/photo/2015/09/21/14/24/zombie-949916_960_720.jpg
func Blur(im image.Image, radius int, numPasses int) (*image.RGBA, error) {
if radius < 1 {
return nil, errors.New("radius must be greater than 0")
}
if numPasses < 1 {
return nil, errors.New("numPasses must be greater than 0")
}
windowSize := 2*radius + 1
if im.Bounds().Dx() < windowSize || im.Bounds().Dy() < windowSize {
return nil, errors.New("image is smaller than 2*radius + 1")
}
// Use RGBA, not NRGBA. We need alpha-premultiplied RGB channels.
src := image.NewRGBA(image.Rect(0, 0, im.Bounds().Dx(), im.Bounds().Dy()))
draw.Draw(src, src.Bounds(), im, im.Bounds().Min, draw.Src)
dst := image.NewRGBA(image.Rect(0, 0, im.Bounds().Dx(), im.Bounds().Dy()))
var (
mean [4]float64
oneOverWindowSize = 1.0 / float64(windowSize)
offset int
)
for i := 0; i < numPasses; i++ {
for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ {
mean = [4]float64{0.0, 0.0, 0.0, 0.0}
for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ {
if x <= radius || x >= src.Bounds().Max.X-radius {
mean = [4]float64{0.0, 0.0, 0.0, 0.0}
for dx := -radius; dx < radius+1; dx++ {
mirrored := x + dx
if mirrored < 0 {
mirrored = -mirrored
} else if mirrored >= src.Bounds().Max.X {
mirrored = src.Bounds().Max.X - (mirrored - src.Bounds().Max.X) - 2
}
offset = src.PixOffset(mirrored, y)
add(mean[:], src.Pix[offset:offset+4])
}
multiply(mean[:], oneOverWindowSize)
offset = dst.PixOffset(x, y)
assign(dst.Pix[offset:offset+4], mean[:])
continue
}
offset = src.PixOffset(x-radius-1, y)
addCoeff(mean[:], src.Pix[offset:offset+4], -oneOverWindowSize)
offset = src.PixOffset(x+radius, y)
addCoeff(mean[:], src.Pix[offset:offset+4], oneOverWindowSize)
offset = dst.PixOffset(x, y)
assign(dst.Pix[offset:offset+4], mean[:])
}
}
src, dst = dst, src
for x := src.Bounds().Min.X; x < src.Bounds().Max.X; x++ {
mean = [4]float64{0.0, 0.0, 0.0, 0.0}
for y := src.Bounds().Min.Y; y < src.Bounds().Max.Y; y++ {
if y <= radius || y >= src.Bounds().Max.Y-radius {
mean = [4]float64{0.0, 0.0, 0.0, 0.0}
for dy := -radius; dy < radius+1; dy++ {
mirrored := y + dy
if mirrored < 0 {
mirrored = -mirrored
} else if mirrored >= src.Bounds().Max.Y {
mirrored = src.Bounds().Max.Y - (mirrored - src.Bounds().Max.Y) - 2
}
offset = src.PixOffset(x, mirrored)
add(mean[:], src.Pix[offset:offset+4])
}
multiply(mean[:], oneOverWindowSize)
offset = dst.PixOffset(x, y)
assign(dst.Pix[offset:offset+4], mean[:])
continue
}
offset = src.PixOffset(x, y-radius-1)
addCoeff(mean[:], src.Pix[offset:offset+4], -oneOverWindowSize)
offset = src.PixOffset(x, y+radius)
addCoeff(mean[:], src.Pix[offset:offset+4], oneOverWindowSize)
offset = dst.PixOffset(x, y)
assign(dst.Pix[offset:offset+4], mean[:])
}
}
src, dst = dst, src
}
return src, nil
}
func assign[T uint8 | float64](a []uint8, b []T) {
a[0] = uint8(b[0])
a[1] = uint8(b[1])
a[2] = uint8(b[2])
a[3] = uint8(b[3])
}
func add(a []float64, b []uint8) {
a[0] = a[0] + float64(b[0])
a[1] = a[1] + float64(b[1])
a[2] = a[2] + float64(b[2])
a[3] = a[3] + float64(b[3])
}
func addCoeff(a []float64, b []uint8, c float64) {
a[0] = a[0] + float64(b[0])*c
a[1] = a[1] + float64(b[1])*c
a[2] = a[2] + float64(b[2])*c
a[3] = a[3] + float64(b[3])*c
}
func multiply(a []float64, b float64) {
a[0] = a[0] * b
a[1] = a[1] * b
a[2] = a[2] * b
a[3] = a[3] * b
}
@wjkoh
Copy link
Author

wjkoh commented Nov 3, 2023

source
blurred

@wjkoh
Copy link
Author

wjkoh commented Nov 4, 2023

It requires 32 milliseconds to blur a 1024x1024 RGBA image with a radius of 8 pixels and two passes.

wjkoh@Woojongs-MacBook-Pro ~/bake (main)> go test -bench=. -run=BenchmarkBlur                                                                                                                     (base) 
goos: darwin
goarch: arm64
pkg: github.com/cowork-ai/bake
BenchmarkBlur64-10      	  11364	   104743 ns/op	  32896 B/op	      4 allocs/op
BenchmarkBlur128-10     	   2781	   422655 ns/op	 131200 B/op	      4 allocs/op
BenchmarkBlur256-10     	    679	  1774799 ns/op	 524416 B/op	      4 allocs/op
BenchmarkBlur512-10     	    163	  7246958 ns/op	2097280 B/op	      4 allocs/op
BenchmarkBlur1024-10    	     34	 32612907 ns/op	8388738 B/op	      4 allocs/op
PASS
ok  	github.com/cowork-ai/bake	8.195s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment