Implementing image enlargement algorithm

This commit is contained in:
esimov
2018-01-26 16:33:55 +02:00
parent 38e25b8abf
commit 8aaa570ee3
2 changed files with 81 additions and 17 deletions

View File

@@ -3,16 +3,30 @@ package caire
import ( import (
"image" "image"
"image/color" "image/color"
"image/draw"
_ "image/png" _ "image/png"
"math" "math"
) )
var usedSeams []UsedSeams
type Carver struct { type Carver struct {
Width int Width int
Height int Height int
Points []float64 Points []float64
} }
// Struct containing the generated seams.
type UsedSeams struct {
ActiveSeam []ActiveSeam
}
// Struct containing the current seam color and position.
type ActiveSeam struct {
Seam
Pix color.Color
}
// Seam struct containing the pixel coordinate values. // Seam struct containing the pixel coordinate values.
type Seam struct { type Seam struct {
X int X int
@@ -51,18 +65,27 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) []float64 {
var src *image.NRGBA var src *image.NRGBA
bounds := img.Bounds() bounds := img.Bounds()
iw, ih := bounds.Dx(), bounds.Dy() iw, ih := bounds.Dx(), bounds.Dy()
sobel := SobelFilter(Grayscale(img), float64(p.SobelThreshold))
newImg := image.NewNRGBA(image.Rect(0, 0, bounds.Dx(), bounds.Dy()))
draw.Draw(newImg, newImg.Bounds(), img, image.ZP, draw.Src)
// Replace the energy map seam values with the pixel values already stored on a slice each time we add a new seam.
for _, seam := range usedSeams {
for _, as := range seam.ActiveSeam {
newImg.Set(as.X, as.Y, as.Pix)
}
}
sobel := SobelFilter(Grayscale(newImg), float64(p.SobelThreshold))
if p.BlurRadius > 0 { if p.BlurRadius > 0 {
src = Stackblur(sobel, uint32(iw), uint32(ih), uint32(p.BlurRadius)) src = Stackblur(sobel, uint32(iw), uint32(ih), uint32(p.BlurRadius))
} else { } else {
src = sobel src = sobel
} }
for x := 0; x < c.Width; x++ { for x := 0; x < c.Width; x++ {
for y := 0; y < c.Height; y++ { for y := 0; y < c.Height; y++ {
r, _, _, _ := src.At(x, y).RGBA() r, _, _, a := src.At(x, y).RGBA()
c.set(x, y, float64(r)) c.set(x, y, float64(r)/ float64(a))
} }
} }
@@ -77,7 +100,6 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) []float64 {
middle = c.get(x, y-1) middle = c.get(x, y-1)
right = c.get(x+1, y-1) right = c.get(x+1, y-1)
min := math.Min(math.Min(left, middle), right) min := math.Min(math.Min(left, middle), right)
// Set the minimum energy level. // Set the minimum energy level.
c.set(x, y, c.get(x, y)+min) c.set(x, y, c.get(x, y)+min)
} }
@@ -87,7 +109,6 @@ func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) []float64 {
right := c.get(0, y) + math.Min(c.get(c.Width-1, y-1), c.get(c.Width-2, y-1)) right := c.get(0, y) + math.Min(c.get(c.Width-1, y-1), c.get(c.Width-2, y-1))
c.set(c.Width-1, y, right) c.set(c.Width-1, y, right)
} }
return c.Points return c.Points
} }
@@ -106,6 +127,7 @@ func (c *Carver) FindLowestEnergySeams() []Seam {
px = x px = x
} }
} }
seams = append(seams, Seam{X: px, Y: c.Height - 1}) seams = append(seams, Seam{X: px, Y: c.Height - 1})
var left, middle, right float64 var left, middle, right float64
@@ -169,6 +191,43 @@ func (c *Carver) RemoveSeam(img *image.NRGBA, seams []Seam, debug bool) *image.N
return dst return dst
} }
// Add new seam.
func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGBA {
var currentSeam []ActiveSeam
bounds := img.Bounds()
dst := image.NewNRGBA(image.Rect(0, 0, bounds.Dx()+1, bounds.Dy()))
for _, seam := range seams {
y := seam.Y
for x := 0; x < bounds.Max.X; x++ {
if seam.X == x {
if debug == true {
dst.Set(x, y, color.RGBA{255, 0, 0, 255})
continue
}
// Calculate the current seam pixel color by averaging the neighboring pixels color.
lr, lg, lb, _ := img.At(x-1, y).RGBA()
rr, rg, rb, _ := img.At(x+1, y).RGBA()
alr, alg, alb := (lr + rr) / 2, (lg + rg) / 2, (lb + rb) / 2
dst.Set(x, y, color.RGBA{uint8(alr>>8),uint8(alg>>8),uint8(alb>>8),255})
// Append the current seam position and color to the existing seams.
// To avoid picking the same optimal seam over and over again,
// each time we detect an optimal seam we assign a large positive value
// to the corresponding pixels in the energy map.
currentSeam = append(currentSeam, ActiveSeam{Seam{x+1, y}, color.RGBA{R:255, G:255, B:255, A:255}})
} else if seam.X < x {
dst.Set(x, y, img.At(x-1, y))
dst.Set(x+1, y, img.At(x, y))
} else {
dst.Set(x, y, img.At(x, y))
}
}
}
usedSeams = append(usedSeams, UsedSeams{currentSeam})
return dst
}
// Rotate image by 90 degree counter clockwise // Rotate image by 90 degree counter clockwise
func (c *Carver) RotateImage90(src *image.NRGBA) *image.NRGBA { func (c *Carver) RotateImage90(src *image.NRGBA) *image.NRGBA {
b := src.Bounds() b := src.Bounds()

View File

@@ -40,19 +40,27 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
width, height := img.Bounds().Dx(), img.Bounds().Dy() width, height := img.Bounds().Dx(), img.Bounds().Dy()
newWidth := width - (width - (width - p.NewWidth)) newWidth := width - (width - (width - p.NewWidth))
newHeight := height - (height - (height - p.NewHeight)) newHeight := height - (height - (height - p.NewHeight))
if p.NewWidth == 0 { if p.NewWidth == 0 {
newWidth = p.NewWidth newWidth = p.NewWidth
} }
if p.NewHeight == 0 { if p.NewHeight == 0 {
newHeight = p.NewHeight newHeight = p.NewHeight
} }
resize := func() { reduce := func() {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height) c = NewCarver(width, height)
c.ComputeSeams(img, p) c.ComputeSeams(img, p)
seams := c.FindLowestEnergySeams() seams := c.FindLowestEnergySeams()
img = c.RemoveSeam(img, seams, p.Debug) img = c.RemoveSeam(img, seams, p.Debug)
} }
enlarge := func() {
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
c = NewCarver(width, height)
c.ComputeSeams(img, p)
seams := c.FindLowestEnergySeams()
img = c.AddSeam(img, seams, p.Debug)
}
if p.Percentage { if p.Percentage {
// Calculate new sizes based on provided percentage. // Calculate new sizes based on provided percentage.
@@ -64,23 +72,20 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
} }
// Resize image horizontally // Resize image horizontally
for x := 0; x < pw; x++ { for x := 0; x < pw; x++ {
resize() reduce()
} }
// Resize image vertically // Resize image vertically
img = c.RotateImage90(img) img = c.RotateImage90(img)
for y := 0; y < ph; y++ { for y := 0; y < ph; y++ {
resize() reduce()
} }
img = c.RotateImage270(img) img = c.RotateImage270(img)
} else if newWidth > 0 || newHeight > 0 { } else if newWidth > 0 || newHeight > 0 {
if newWidth > 0 { if newWidth > 0 {
if newWidth > c.Width {
err := errors.New("the generated image width should be less than original image width.") for x := 0; x < 80; x++ {
return nil, err enlarge()
} }
for x := 0; x < newWidth; x++ {
resize()
}
} }
if newHeight > 0 { if newHeight > 0 {
@@ -90,7 +95,7 @@ func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) {
} }
img = c.RotateImage90(img) img = c.RotateImage90(img)
for y := 0; y < newHeight; y++ { for y := 0; y < newHeight; y++ {
resize() reduce()
} }
img = c.RotateImage270(img) img = c.RotateImage270(img)
} }