feat: Add optimal seam finding

This commit is contained in:
Myzel394 2024-12-08 14:52:37 +01:00
parent fbc1acbeee
commit b71c386950
No known key found for this signature in database
GPG Key ID: ED20A1D1D423AF3F
3 changed files with 178 additions and 25 deletions

142
imageutils/seam.go Normal file
View File

@ -0,0 +1,142 @@
package imageutils
import (
"fmt"
"image"
"image/color"
)
type SeamNode struct {
// if null = root node
PreviosNode *SeamNode
X int
Y int
ThisCost uint
AccumulatedCosts uint
}
func (s SeamNode) String() string {
if s.PreviosNode == nil {
return fmt.Sprintf("(%d, %d; %d)", s.X, s.Y, s.AccumulatedCosts)
} else {
return fmt.Sprintf("(%d, %d; %d) -> %v", s.X, s.Y, s.AccumulatedCosts, s.PreviosNode)
}
}
func (s SeamNode) WriteSeamChainToImage(img *image.RGBA) {
img.Set(
s.X,
s.Y,
color.RGBA{255, 0, 0, 1},
)
if s.PreviosNode != nil {
s.PreviosNode.WriteSeamChainToImage(img)
}
}
type Position struct {
X int
Y int
}
type ImageSeams struct {
// map of <y row> -> SeamNodes
Seams [][]*SeamNode
}
func NewImageSeams() ImageSeams {
return ImageSeams{
Seams: make([][]*SeamNode, 0),
}
}
func (i *ImageSeams) CreateSeamsFromRectangle(rect image.Rectangle) {
for y := range rect.Max.Y {
rowSeams := make([]*SeamNode, 0)
for x := range rect.Max.X {
rowSeams = append(rowSeams, &SeamNode{
X: x,
Y: y,
})
}
i.Seams = append(i.Seams, rowSeams)
}
}
// Get the nodes above a given x, y coordinate.
// Get the left and right neighbors, if there are any.
func (i *ImageSeams) GetNodesAbove(x int, y int) []*SeamNode {
if y == 0 {
return nil
}
rowSeams := i.Seams[y-1]
seams := make([]*SeamNode, 0, 3)
// Left node
if x != 0 {
seams = append(seams, rowSeams[x-1])
}
// Middle node
seams = append(seams, rowSeams[x])
// Right node
if x < (len(rowSeams) - 1) {
seams = append(seams, rowSeams[x+1])
}
return seams
}
// Find the best node that's above the current one at `(x, y)`
func (i *ImageSeams) FindBestNodeAbove(x int, y int) *SeamNode {
nodes := i.GetNodesAbove(x, y)
lowestCostNode := nodes[0]
for _, node := range nodes {
if node.AccumulatedCosts < lowestCostNode.AccumulatedCosts {
lowestCostNode = node
}
}
return lowestCostNode
}
func (i *ImageSeams) CreateOptimizedRoutesForRow(y int) {
row := i.Seams[y]
for x, node := range row {
bestNode := i.FindBestNodeAbove(x, y)
node.PreviosNode = bestNode
node.AccumulatedCosts = bestNode.AccumulatedCosts + node.ThisCost
}
}
func (i *ImageSeams) CreateOptimizedRoutes() {
for y := 1; y < len(i.Seams); y++ {
i.CreateOptimizedRoutesForRow(y)
}
}
func (i *ImageSeams) GetLowestSeam() *SeamNode {
lastRow := i.Seams[len(i.Seams)-1]
lowestNode := lastRow[0]
for _, node := range lastRow {
if node.AccumulatedCosts < lowestNode.AccumulatedCosts {
lowestNode = node
}
}
return lowestNode
}
func (i *ImageSeams) SetCostForNode(x int, y int, cost uint) {
node := i.Seams[y][x]
node.ThisCost = cost
node.AccumulatedCosts = cost
}

17
main.go
View File

@ -6,12 +6,13 @@ import (
"image/png" "image/png"
_ "image/png" _ "image/png"
"os" "os"
"myzel394.app/image-stuff/imageutils" "myzel394.app/image-stuff/imageutils"
) )
func main() { func main() {
// Read image // Read image
reader, _ := os.Open("./assets/water2.png") reader, _ := os.Open("./assets/surfer.png")
rawImage, _, _ := image.Decode(reader) rawImage, _, _ := image.Decode(reader)
readImage := imageutils.ImageAnalyzer{Image: rawImage} readImage := imageutils.ImageAnalyzer{Image: rawImage}
@ -20,16 +21,26 @@ func main() {
height := bounds.Max.Y height := bounds.Max.Y
writeImage := image.NewRGBA(bounds) writeImage := image.NewRGBA(bounds)
seams := imageutils.NewImageSeams()
seams.CreateSeamsFromRectangle(bounds)
// Main action // Main action
for x := range width { for x := range width {
for y := range height { for y := range height {
energy := uint8(readImage.CalculateEnergyAt(x, y)) energy := uint(readImage.CalculateEnergyAt(x, y))
hexColor := uint8(energy)
color := color.RGBA{energy, energy, energy, 255} color := color.RGBA{hexColor, hexColor, hexColor, 255}
writeImage.Set(x, y, color) writeImage.Set(x, y, color)
seams.SetCostForNode(x, y, energy)
} }
} }
seams.CreateOptimizedRoutes()
lowestSeam := seams.GetLowestSeam()
lowestSeam.WriteSeamChainToImage(writeImage)
// Out image // Out image
writer, _ := os.Create("image.png") writer, _ := os.Create("image.png")
png.Encode(writer, writeImage) png.Encode(writer, writeImage)