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

View File

@ -16,25 +16,25 @@ func getEnergy(
secondPixel color.Color, secondPixel color.Color,
) float64 { ) float64 {
firstRed, firstGreen, firstBlue, _ := firstPixel.RGBA() firstRed, firstGreen, firstBlue, _ := firstPixel.RGBA()
firstRed, firstGreen, firstBlue = firstRed >> 8, firstGreen >> 8, firstBlue >> 8 firstRed, firstGreen, firstBlue = firstRed>>8, firstGreen>>8, firstBlue>>8
secondRed, secondGreen, secondBlue, _ := secondPixel.RGBA() secondRed, secondGreen, secondBlue, _ := secondPixel.RGBA()
secondRed, secondGreen, secondBlue = secondRed >> 8, secondGreen >> 8, secondBlue >> 8 secondRed, secondGreen, secondBlue = secondRed>>8, secondGreen>>8, secondBlue>>8
return float64( return float64(
math.Abs(float64(firstRed - secondRed)) + math.Abs(float64(firstRed-secondRed)) +
math.Abs(float64(firstGreen - secondGreen)) + math.Abs(float64(firstGreen-secondGreen)) +
math.Abs(float64(firstBlue - secondBlue)), math.Abs(float64(firstBlue-secondBlue)),
) )
} }
// Calculate a 0-255 grayscale value from a color using the NTSC formula // Calculate a 0-255 grayscale value from a color using the NTSC formula
func colorToGray(color color.Color) float64 { func colorToGray(color color.Color) float64 {
red, green, blue, _ := color.RGBA() red, green, blue, _ := color.RGBA()
red, green, blue = red >> 8, green >> 8, blue >> 8 red, green, blue = red>>8, green>>8, blue>>8
return 0.299 * float64(red) + return 0.299*float64(red) +
0.587 * float64(green) + 0.587*float64(green) +
0.114 * float64(blue) 0.114*float64(blue)
} }
func sumSlice(slice []float64) float64 { func sumSlice(slice []float64) float64 {
@ -48,35 +48,35 @@ func sumSlice(slice []float64) float64 {
func (image *ImageAnalyzer) CalculateEnergyAt(x int, y int) float64 { func (image *ImageAnalyzer) CalculateEnergyAt(x int, y int) float64 {
northPixel := image.At( northPixel := image.At(
x, x,
max(0, y - 1), max(0, y-1),
) )
northEastPixel := image.At( northEastPixel := image.At(
min(image.Bounds().Max.X, x + 1), min(image.Bounds().Max.X, x+1),
max(0, y - 1), max(0, y-1),
) )
eastPixel := image.At( eastPixel := image.At(
min(image.Bounds().Max.X, x + 1), min(image.Bounds().Max.X, x+1),
y, y,
) )
southEastPixel := image.At( southEastPixel := image.At(
min(image.Bounds().Max.X, x + 1), min(image.Bounds().Max.X, x+1),
min(image.Bounds().Max.Y, y + 1), min(image.Bounds().Max.Y, y+1),
) )
southPixel := image.At( southPixel := image.At(
x, x,
min(image.Bounds().Max.Y, y + 1), min(image.Bounds().Max.Y, y+1),
) )
southWestPixel := image.At( southWestPixel := image.At(
max(0, x - 1), max(0, x-1),
min(image.Bounds().Max.Y, y + 1), min(image.Bounds().Max.Y, y+1),
) )
westPixel := image.At( westPixel := image.At(
max(0, x - 1), max(0, x-1),
y, y,
) )
northWestPixel := image.At( northWestPixel := image.At(
max(0, x - 1), max(0, x-1),
max(0, y - 1), max(0, y-1),
) )
thisPixel := image.At( thisPixel := image.At(

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)