mirror of
https://github.com/esimov/caire.git
synced 2025-10-24 17:00:41 +08:00
Implementing image enlargement algorithm
This commit is contained in:
71
carver.go
71
carver.go
@@ -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()
|
||||||
|
|||||||
27
process.go
27
process.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user