From af88c26f90d628c64e16d184b7b8e9b563bd4903 Mon Sep 17 00:00:00 2001 From: esimov Date: Wed, 17 Jan 2018 10:52:56 +0200 Subject: [PATCH] Code refactoring --- carver.go | 100 ++++++++-------------------------------------- cmd/caire/main.go | 2 +- process.go | 80 ++++++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 90 deletions(-) diff --git a/carver.go b/carver.go index 4b19165..4b0cb23 100644 --- a/carver.go +++ b/carver.go @@ -5,14 +5,12 @@ import ( _ "image/png" "math" "image/color" - "github.com/pkg/errors" ) -// SeamCarver is an interface that Carver uses to implement the Resize function. -// It takes an image and the output as parameters and returns the resized image -// and the error, if exists. -type SeamCarver interface { - Resize(*image.NRGBA) (image.Image, error) +type Carver struct { + Width int + Height int + Points []float64 } // Seam struct containing the pixel coordinate values. @@ -22,15 +20,11 @@ type Seam struct { } // NewCarver returns an initialized Carver structure. -func NewCarver(width, height, threshold, blur, rw, rh int, perc bool) *Carver { +func NewCarver(width, height int) *Carver { return &Carver{ width, height, make([]float64, width*height), - threshold, - blur, - rw, rh, - perc, } } @@ -53,21 +47,22 @@ func (c *Carver) set(x, y int, px float64) { // // - the minimum energy level is calculated by summing up the current pixel value // with the minimum pixel value of the neighboring pixels from the previous row. -func (c *Carver) computeSeams(img *image.NRGBA) []float64 { +func (c *Carver) ComputeSeams(img *image.NRGBA, p *Processor) []float64 { var src *image.NRGBA bounds := img.Bounds() iw, ih := bounds.Dx(), bounds.Dy() - sobel := SobelFilter(Grayscale(img), float64(c.SobelThreshold)) + sobel := SobelFilter(Grayscale(img), float64(p.SobelThreshold)) - if c.BlurRadius > 0 { - src = Stackblur(sobel, uint32(iw), uint32(ih), uint32(c.BlurRadius)) + if p.BlurRadius > 0 { + src = Stackblur(sobel, uint32(iw), uint32(ih), uint32(p.BlurRadius)) } else { src = sobel } + for x := 0; x < c.Width; x++ { for y := 0; y < c.Height; y++ { - r, _, _, a := src.At(x, y).RGBA() - c.set(x, y, float64(r)/float64(a)) + r, _, _, _ := src.At(x, y).RGBA() + c.set(x, y, float64(r)) } } @@ -95,7 +90,7 @@ func (c *Carver) computeSeams(img *image.NRGBA) []float64 { } // Find the lowest vertical energy seam. -func (c *Carver) findLowestEnergySeams() []Seam { +func (c *Carver) FindLowestEnergySeams() []Seam { // Find the lowest cost seam from the energy matrix starting from the last row. var min float64 = math.MaxFloat64 var px int @@ -104,7 +99,7 @@ func (c *Carver) findLowestEnergySeams() []Seam { // Find the pixel on the last row with the minimum cumulative energy and use this as the starting pixel for x := 0; x < c.Width; x++ { seam := c.get(x, c.Height-1) - if seam < min && seam > 0 { + if seam < min { min = seam px = x } @@ -150,7 +145,7 @@ func (c *Carver) findLowestEnergySeams() []Seam { } // Remove image pixels based on energy seams level. -func (c *Carver) removeSeam(img *image.NRGBA, seams []Seam) *image.NRGBA { +func (c *Carver) RemoveSeam(img *image.NRGBA, seams []Seam) *image.NRGBA { bounds := img.Bounds() dst := image.NewNRGBA(image.Rect(0, 0, bounds.Dx()-1, bounds.Dy())) @@ -171,7 +166,7 @@ func (c *Carver) removeSeam(img *image.NRGBA, seams []Seam) *image.NRGBA { } // 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() dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X)) for dstY := 0; dstY < b.Max.X; dstY++ { @@ -188,7 +183,7 @@ func (c *Carver) rotateImage90(src *image.NRGBA) *image.NRGBA { } // Rotate image by 270 degree counter clockwise -func (c *Carver) rotateImage270(src *image.NRGBA) *image.NRGBA { +func (c *Carver) RotateImage270(src *image.NRGBA) *image.NRGBA { b := src.Bounds() dst := image.NewNRGBA(image.Rect(0, 0, b.Max.Y, b.Max.X)) for dstY := 0; dstY < b.Max.X; dstY++ { @@ -202,65 +197,4 @@ func (c *Carver) rotateImage270(src *image.NRGBA) *image.NRGBA { } } return dst -} - -// Resize is the main function taking the source image and encoding the rescaled image into the output file. -func (c *Carver) Resize(img *image.NRGBA) (image.Image, error) { - width, height := img.Bounds().Max.X, img.Bounds().Max.Y - carver := NewCarver(width, height, c.SobelThreshold, c.BlurRadius, c.NewWidth, c.NewHeight, c.Percentage) - - resize := func() { - carver.computeSeams(img) - seams := carver.findLowestEnergySeams() - img = carver.removeSeam(img, seams) - } - - if carver.Percentage { - // Calculate new sizes based on provided percentage. - nw := carver.Width - int(float64(carver.Width) - (float64(carver.NewWidth)/100 * float64(carver.Width))) - nh := carver.Height - int(float64(carver.Height) - (float64(carver.NewHeight)/100 * float64(carver.Height))) - - // Resize image horizontally - for x := 0; x < nw; x++ { - resize() - } - // Resize image vertically - img = c.rotateImage90(img) - - // Needs to update the slice width & height because of image rotation. - carver.Width = img.Bounds().Dx() - carver.Height = img.Bounds().Dy() - for y := 0; y < nh; y++ { - resize() - } - img = c.rotateImage270(img) - } else if carver.NewWidth > 0 || carver.NewHeight > 0 { - if carver.NewWidth > 0 { - if carver.NewWidth > carver.Width { - err := errors.New("new width should be less than image width.") - return nil, err - } - for x := 0; x < carver.NewWidth; x++ { - resize() - } - } - - if carver.NewHeight > 0 { - if carver.NewHeight > carver.Height { - err := errors.New("new height should be less than image height.") - return nil, err - } - img = c.rotateImage90(img) - - // Needs to update the slice width & height because of image rotation - // otherwise the new image will be cut off. - carver.Width = img.Bounds().Dx() - carver.Height = img.Bounds().Dy() - for y := 0; y < carver.NewHeight; y++ { - resize() - } - img = c.rotateImage270(img) - } - } - return img, nil } \ No newline at end of file diff --git a/cmd/caire/main.go b/cmd/caire/main.go index c2f6ebb..751b1d6 100644 --- a/cmd/caire/main.go +++ b/cmd/caire/main.go @@ -40,7 +40,7 @@ func main() { toProcess := make(map[string]string) - p := &caire.Carver{ + p := &caire.Processor{ BlurRadius: *blurRadius, SobelThreshold: *sobelThreshold, NewWidth: *newWidth, diff --git a/process.go b/process.go index f7d5d3b..13ad90e 100644 --- a/process.go +++ b/process.go @@ -8,13 +8,17 @@ import ( "io" "os" "image/jpeg" + "github.com/pkg/errors" ) +// SeamCarver is an interface that Carver uses to implement the Resize function. +// It takes an image and the output as parameters and returns the resized image. +type SeamCarver interface { + Resize(*image.NRGBA) (image.Image, error) +} + // Processor options -type Carver struct { - Width int - Height int - Points []float64 +type Processor struct { SobelThreshold int BlurRadius int NewWidth int @@ -22,20 +26,84 @@ type Carver struct { Percentage bool } + // Implement the Resize method of the Carver interface. func Resize(s SeamCarver, img *image.NRGBA) (image.Image, error) { return s.Resize(img) } +// This is the main entry point which takes the source image +// and encodes the new, rescaled image into the output file. +func (p *Processor) Resize(img *image.NRGBA) (image.Image, error) { + var c *Carver = NewCarver(img.Bounds().Dx(), img.Bounds().Dy()) + + resize := func() { + width, height := img.Bounds().Max.X, img.Bounds().Max.Y + c = NewCarver(width, height) + c.ComputeSeams(img, p) + seams := c.FindLowestEnergySeams() + img = c.RemoveSeam(img, seams) + } + + if p.Percentage { + // Calculate new sizes based on provided percentage. + nw := c.Width - int(float64(c.Width) - (float64(p.NewWidth)/100 * float64(c.Width))) + nh := c.Height - int(float64(c.Height) - (float64(p.NewHeight)/100 * float64(c.Height))) + // Resize image horizontally + for x := 0; x < nw; x++ { + resize() + } + // Resize image vertically + img = c.RotateImage90(img) + + // Needs to update the slice width & height because of image rotation. + c.Width = img.Bounds().Dx() + c.Height = img.Bounds().Dy() + for y := 0; y < nh; y++ { + resize() + } + img = c.RotateImage270(img) + } else if p.NewWidth > 0 || p.NewHeight > 0 { + if p.NewWidth > 0 { + if p.NewWidth > c.Width { + err := errors.New("new width should be less than image width.") + return nil, err + } + for x := 0; x < p.NewWidth; x++ { + resize() + } + } + + if p.NewHeight > 0 { + if p.NewHeight > c.Height { + err := errors.New("new height should be less than image height.") + return nil, err + } + img = c.RotateImage90(img) + + // Needs to update the slice width & height because of image rotation + // otherwise the new image will be cut off. + c.Width = img.Bounds().Dx() + c.Height = img.Bounds().Dy() + for y := 0; y < p.NewHeight; y++ { + resize() + } + img = c.RotateImage270(img) + } + } + return img, nil +} + // Process is the main entry point for the image resize operation. -func (c *Carver) Process(file io.Reader, output string) (*os.File, error) { +func (p *Processor) Process(file io.Reader, output string) (*os.File, error) { src, _, err := image.Decode(file) if err != nil { return nil, err } img := imgToNRGBA(src) - res, err := Resize(c, img) + //sobel := SobelFilter(Grayscale(img), float64(p.SobelThreshold)) + res, err := Resize(p, img) if err != nil { return nil, err }