Last active
February 1, 2024 10:52
-
-
Save wjkoh/660c97cfd3133bae936a431d20ab983e to your computer and use it in GitHub Desktop.
Go: Box blur
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It requires 32 milliseconds to blur a 1024x1024 RGBA image with a radius of 8 pixels and two passes.