Skip to content
This repository has been archived by the owner on Jun 21, 2024. It is now read-only.

Commit

Permalink
floodfill
Browse files Browse the repository at this point in the history
  • Loading branch information
bit101 committed Mar 30, 2020
1 parent d7c88bd commit 4ee8ac7
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 1 deletion.
11 changes: 11 additions & 0 deletions drawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"math/rand"

"github.com/bit101/blgo/blmath"
"github.com/bit101/blgo/floodfill"
"github.com/bit101/blgo/geom"
)

// Plot draws a single pixel.
func (s *Surface) Plot(p *geom.Point) {
s.Save()
s.Translate(p.X, p.Y)
Expand Down Expand Up @@ -473,6 +475,15 @@ func (s *Surface) StrokeMultiLoop(points []*geom.Point) {
s.Stroke()
}

////////////////////////////////////////
// FloodFill
////////////////////////////////////////

// FloodFill fills adjacent pixels with a specified color
func (s *Surface) FloodFill(x, y, r, g, b, threshold float64) {
floodfill.FloodFill(s, x, y, r, g, b, threshold)
}

////////////////////////////////////////
// Grid
////////////////////////////////////////
Expand Down
13 changes: 13 additions & 0 deletions floodfill/coord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package floodfill

type coord struct {
index int
x int
y int
}

func newCoord(x, y, w int) coord {
// we get width passed so we can precompute the data index
index := (y*w + x) * 4
return coord{index: index, x: x, y: y}
}
68 changes: 68 additions & 0 deletions floodfill/floodfill.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package floodfill

// Surface is an interface for a surface used for performing flood fills
// Used to avoid import cycles
type Surface interface {
GetHeight() int
GetWidth() int
SetData([]byte)
GetData() []byte
}

// FloodFill fills a surface from the specified point with the specified color
func FloodFill(surface Surface, x, y, r, g, b float64, threshold float64) {
// surface uses float values, but pixel data is all int values. convert.
xi, yi := int(x), int(y)
w, h := surface.GetWidth(), surface.GetHeight()

// if pixel outside of image, return
if xi < 0 || xi >= w || yi < 0 || yi >= h {
return
}

// more int conversions
ri, gi, bi, th := int(r*255), int(g*255), int(b*255), int(threshold*255)

data := surface.GetData()
nextCoord := newCoord(xi, yi, w)

// color of target color and replacement color for fill
targetRGB := getRGB(data, nextCoord)
replacementRGB := newRGB(ri, gi, bi)

// target color is already replacement color, return
if targetRGB.isEqual(replacementRGB, th) {
return
}

// change color of first pixel to replacement color, add coord to queue
setRGB(data, nextCoord, replacementRGB)
var queue []coord
queue = append(queue, nextCoord)

// helper func. if coord is in bounds and has target color, change to replacement color and add to queue
checkNeighbor := func(x, y int) {
neighbor := newCoord(x, y, w)
if neighbor.x < w-1 && neighbor.x > 0 && neighbor.y < h-1 && neighbor.y > 0 &&
getRGB(data, neighbor).isEqual(targetRGB, th) {

setRGB(data, neighbor, replacementRGB)
queue = append(queue, neighbor)
}
}

// while there are items in the queue
for len(queue) > 0 {
// shift the first one off (it's already been colored
nextCoord, queue = queue[0], queue[1:]

// check four surrounding coords
checkNeighbor(nextCoord.x+1, nextCoord.y)
checkNeighbor(nextCoord.x-1, nextCoord.y)
checkNeighbor(nextCoord.x, nextCoord.y+1)
checkNeighbor(nextCoord.x, nextCoord.y-1)
}

// queue is empty. rewrite data
surface.SetData(data)
}
39 changes: 39 additions & 0 deletions floodfill/rgb.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package floodfill

type rgb struct {
r int
g int
b int
}

func newRGB(r, g, b int) rgb {
return rgb{
r: r,
g: g,
b: b,
}
}

func (p rgb) isEqual(q rgb, max int) bool {
return abs(p.r, q.r) <= max && abs(p.g, q.g) <= max && abs(p.b, q.b) <= max
}

func abs(a, b int) int {
diff := a - b
if diff < 0 {
return -diff
}
return diff
}

func getRGB(data []byte, c coord) rgb {
// stored as b, g, r, a
return newRGB(int(data[c.index+2]), int(data[c.index+1]), int(data[c.index]))
}

func setRGB(data []byte, c coord, replacementRGB rgb) {
// stored as b, g, r, a
data[c.index+2] = byte(replacementRGB.r)
data[c.index+1] = byte(replacementRGB.g)
data[c.index] = byte(replacementRGB.b)
}
2 changes: 1 addition & 1 deletion surface.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func (s *Surface) SetSourceColor(color color.Color) {
s.SetSourceRGBA(color.R, color.G, color.B, color.A)
}

// GetPixel returns the r, g, b, a value at a given x, y location.
// GetPixel returns the b, g, r, a value at a given x, y location.
func (s *Surface) GetPixel(x int, y int) (byte, byte, byte, byte) {
data := s.GetData()
index := (y*s.GetWidth() + x) * 4
Expand Down

0 comments on commit 4ee8ac7

Please sign in to comment.