diff --git a/bench_carver_test.go b/bench_carver_test.go index ecdcd86..bfaf907 100644 --- a/bench_carver_test.go +++ b/bench_carver_test.go @@ -29,7 +29,7 @@ func Benchmark_Carver(b *testing.B) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.RemoveSeam(img, seams, p.Debug) } diff --git a/carver.go b/carver.go index 95162fb..c60ec93 100644 --- a/carver.go +++ b/carver.go @@ -13,8 +13,9 @@ import ( const maxFaceDetAttempts = 20 var ( - usedSeams []UsedSeams detAttempts int + sobel *image.NRGBA + energySeams = make([][]Seam, 0) ) // Carver is the main entry struct having as parameters the newly generated image width, height and seam points. @@ -31,17 +32,6 @@ type Seam struct { Y int } -// UsedSeams contains the already generated seams. -type UsedSeams struct { - ActiveSeam []ActiveSeam -} - -// ActiveSeam contains the current seam position and color. -type ActiveSeam struct { - Seam - Pix color.Color -} - // NewCarver returns an initialized Carver structure. func NewCarver(width, height int) *Carver { return &Carver{ @@ -75,7 +65,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error { var srcImg *image.NRGBA width, height := img.Bounds().Dx(), img.Bounds().Dy() - sobel := c.SobelDetector(img, float64(p.SobelThreshold)) + sobel = c.SobelDetector(img, float64(p.SobelThreshold)) if p.FaceDetect && detAttempts < maxFaceDetAttempts { var ratio float64 @@ -132,7 +122,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error { face.Col+face.Scale/2, face.Row+face.Scale/2, ) - draw.Draw(sobel, rect, &image.Uniform{color.White}, image.ZP, draw.Src) + draw.Draw(sobel, rect, &image.Uniform{color.White}, image.Point{}, draw.Src) } } } @@ -189,6 +179,17 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error { } } + // Increase the energy value for each of the selected seam from the seams table + // in order to avoid picking the same seam over and over again. + // We expand the energy level of the selected seams to have a better redistribution. + if len(energySeams) > 0 { + for i := 0; i < len(energySeams); i++ { + for _, seam := range energySeams[i] { + sobel.Set(seam.X, seam.Y, &image.Uniform{color.White}) + } + } + } + if p.BlurRadius > 0 { srcImg = c.StackBlur(sobel, uint32(p.BlurRadius)) } else { @@ -226,7 +227,7 @@ func (c *Carver) ComputeSeams(p *Processor, img *image.NRGBA) error { } // FindLowestEnergySeams find the lowest vertical energy seam. -func (c *Carver) FindLowestEnergySeams() []Seam { +func (c *Carver) FindLowestEnergySeams(p *Processor) []Seam { // Find the lowest cost seam from the energy matrix starting from the last row. var ( min = math.MaxFloat64 @@ -246,8 +247,8 @@ func (c *Carver) FindLowestEnergySeams() []Seam { seams = append(seams, Seam{X: px, Y: c.Height - 1}) var left, middle, right float64 - // Walk up in the matrix table, check the immediate three top pixel seam level - // and add the one which has the lowest cumulative energy. + // Walk up in the matrix table, check the immediate three top pixels seam level + // and add that one which has the lowest cumulative energy. for y := c.Height - 2; y >= 0; y-- { middle = c.get(px, y) // Leftmost seam, no child to the left @@ -275,6 +276,14 @@ func (c *Carver) FindLowestEnergySeams() []Seam { } seams = append(seams, Seam{X: px, Y: y}) } + + // compare against c.Width and NOT c.Height, because the image is rotated. + if p.NewWidth > c.Width || (p.NewHeight > 0 && p.NewHeight > c.Width) { + // Include the currently processed energy seam into the seams table, + // but only when an image enlargement operation is commenced. + // We need to take this approach in order to avoid picking the same seam each time. + energySeams = append(energySeams, seams) + } return seams } @@ -304,10 +313,8 @@ func (c *Carver) RemoveSeam(img *image.NRGBA, seams []Seam, debug bool) *image.N // AddSeam add a new seam. func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGBA { var ( - currentSeam []ActiveSeam - lr, lg, lb uint32 - rr, rg, rb uint32 - py int + lr, lg, lb uint32 + rr, rg, rb uint32 ) bounds := img.Bounds() @@ -320,47 +327,22 @@ func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGB if debug { c.Seams = append(c.Seams, Seam{X: x, Y: y}) } - // Calculate the current seam pixel color by averaging the neighboring pixels color. - if y > 0 { - py = y - 1 - } else { - py = y - } - - if x > 0 { - lr, lg, lb, _ = img.At(x-1, py).RGBA() + if x > 0 && x != bounds.Max.X { + lr, lg, lb, _ = img.At(x-1, y).RGBA() } else { lr, lg, lb, _ = img.At(x, y).RGBA() } - if y < bounds.Max.Y-1 { - py = y + 1 - } else { - py = y - } - if x < bounds.Max.X-1 { - rr, rg, rb, _ = img.At(x+1, py).RGBA() - } else { + rr, rg, rb, _ = img.At(x+1, y).RGBA() + } else if x == bounds.Max.X { rr, rg, rb, _ = img.At(x, y).RGBA() } - avr, avg, avb := (lr+rr)/2, (lg+rg)/2, (lb+rb)/2 - dst.Set(x, y, color.RGBA{uint8(avr >> 8), uint8(avg >> 8), uint8(avb >> 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. - // We will increase the seams weight by duplicating the pixel value. - currentSeam = append(currentSeam, - ActiveSeam{Seam{x + 1, y}, - color.RGBA{ - R: uint8((avr + avr) >> 8), - G: uint8((avg + avg) >> 8), - B: uint8((avb + avb) >> 8), - A: 255, - }, - }) + // calculate the average color of the neighboring pixels + avr, avg, avb := (lr+rr)>>1, (lg+rg)>>1, (lb+rb)>>1 + dst.Set(x, y, color.RGBA{uint8(avr >> 8), uint8(avg >> 8), uint8(avb >> 8), 0xff}) + dst.Set(x+1, y, img.At(x, y)) } else if seam.X < x { dst.Set(x, y, img.At(x-1, y)) dst.Set(x+1, y, img.At(x, y)) @@ -369,7 +351,7 @@ func (c *Carver) AddSeam(img *image.NRGBA, seams []Seam, debug bool) *image.NRGB } } } - usedSeams = append(usedSeams, UsedSeams{currentSeam}) + return dst } diff --git a/carver_test.go b/carver_test.go index 55e1aa4..f9dd25d 100644 --- a/carver_test.go +++ b/carver_test.go @@ -37,7 +37,7 @@ func TestCarver_EnergySeamShouldNotBeDetected(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - les := c.FindLowestEnergySeams() + les := c.FindLowestEnergySeams(p) seams = append(seams, les) } @@ -56,7 +56,7 @@ func TestCarver_DetectHorizontalEnergySeam(t *testing.T) { var totalEnergySeams int img := image.NewNRGBA(image.Rect(0, 0, ImgWidth, ImgHeight)) - draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src) + draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.Point{}, draw.Src) // Replace the pixel colors in a single row from 0xff to 0xdd. 5 is an arbitrary value. // The seam detector should recognize that line as being of low energy density @@ -75,7 +75,7 @@ func TestCarver_DetectHorizontalEnergySeam(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - les := c.FindLowestEnergySeams() + les := c.FindLowestEnergySeams(p) seams = append(seams, les) } @@ -94,7 +94,7 @@ func TestCarver_DetectVerticalEnergySeam(t *testing.T) { var totalEnergySeams int img := image.NewNRGBA(image.Rect(0, 0, ImgWidth, ImgHeight)) - draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src) + draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.Point{}, draw.Src) // Replace the pixel colors in a single column from 0xff to 0xdd. 5 is an arbitrary value. // The seam detector should recognize that line as being of low energy density @@ -114,7 +114,7 @@ func TestCarver_DetectVerticalEnergySeam(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - les := c.FindLowestEnergySeams() + les := c.FindLowestEnergySeams(p) seams = append(seams, les) } img = c.RotateImage270(img) @@ -135,7 +135,7 @@ func TestCarver_RemoveSeam(t *testing.T) { // We choose to fill up the background with an uniform white color // and afterwards we replace the colors in a single row with lower intensity ones. - draw.Draw(img, bounds, &image.Uniform{image.White}, image.ZP, draw.Src) + draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src) origImg := img dx, dy := img.Bounds().Dx(), img.Bounds().Dy() @@ -146,7 +146,7 @@ func TestCarver_RemoveSeam(t *testing.T) { c := NewCarver(dx, dy) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.RemoveSeam(img, seams, false) isEq := true @@ -174,7 +174,7 @@ func TestCarver_AddSeam(t *testing.T) { // We choose to fill up the background with an uniform white color // Afterwards we'll replace the colors in a single row with lower intensity ones. - draw.Draw(img, bounds, &image.Uniform{image.White}, image.ZP, draw.Src) + draw.Draw(img, bounds, &image.Uniform{image.White}, image.Point{}, draw.Src) origImg := img dx, dy := img.Bounds().Dx(), img.Bounds().Dy() @@ -185,7 +185,7 @@ func TestCarver_AddSeam(t *testing.T) { c := NewCarver(dx, dy) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.AddSeam(img, seams, false) dx, dy = img.Bounds().Dx(), img.Bounds().Dy() @@ -212,8 +212,6 @@ func TestCarver_ComputeSeams(t *testing.T) { // We choose to fill up the background with an uniform white color // Afterwards we'll replace the colors in a single row with lower intensity ones. - //draw.Draw(img, img.Bounds(), &image.Uniform{image.White}, image.ZP, draw.Src) - dx, dy := img.Bounds().Dx(), img.Bounds().Dy() // Replace the pixels in row 5 with lower intensity colors. for x := 0; x < dx; x++ { @@ -356,11 +354,11 @@ func TestCarver_ShouldNotRemoveFaceZone(t *testing.T) { face.Col+face.Scale/2, face.Row+face.Scale/2, ) - draw.Draw(sobel, rect, &image.Uniform{image.White}, image.ZP, draw.Src) + draw.Draw(sobel, rect, &image.Uniform{image.White}, image.Point{}, draw.Src) } } c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) for _, seam := range seams { if seam.X >= rect.Min.X && seam.X <= rect.Max.X { diff --git a/process_test.go b/process_test.go index bf0279e..0ce4959 100644 --- a/process_test.go +++ b/process_test.go @@ -17,7 +17,7 @@ func TestResize_ShrinkImageWidth(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.RemoveSeam(img, seams, p.Debug) } imgWidth := img.Bounds().Max.X @@ -40,7 +40,7 @@ func TestResize_ShrinkImageHeight(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.RemoveSeam(img, seams, p.Debug) } img = c.RotateImage270(img) @@ -64,7 +64,7 @@ func TestResize_EnlargeImageWidth(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.AddSeam(img, seams, p.Debug) } imgWidth := img.Bounds().Max.X - origImgWidth @@ -88,7 +88,7 @@ func TestResize_EnlargeImageHeight(t *testing.T) { width, height := img.Bounds().Max.X, img.Bounds().Max.Y c = NewCarver(width, height) c.ComputeSeams(p, img) - seams := c.FindLowestEnergySeams() + seams := c.FindLowestEnergySeams(p) img = c.AddSeam(img, seams, p.Debug) } img = c.RotateImage270(img)