Skip to content

Commit

Permalink
Merge pull request #9 from anubhavp28/new-image-op
Browse files Browse the repository at this point in the history
Adds new image operations - flip and rotate
  • Loading branch information
baskarap authored Jul 24, 2019
2 parents 59c1bdb + c9e9aba commit 5630aa7
Show file tree
Hide file tree
Showing 14 changed files with 179 additions and 13 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type Processor interface {
GrayScale(img image.Image) (image.Image, error)
Resize(img image.Image, width, height int) (image.Image, error)
Watermark(base []byte, overlay []byte, opacity uint8) ([]byte, error)
Flip(image image.Image, mode string) (image.Image, error)
Rotate(image image.Image, angle float64) (image.Image, error)
}
```
```go
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/kr/pty v1.1.8 // indirect
github.com/magiconair/properties v1.8.1 // indirect
github.com/mattn/goveralls v0.0.2 // indirect
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/pkg/errors v0.8.1
github.com/prometheus/common v0.6.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/goveralls v0.0.2 h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
Expand Down Expand Up @@ -217,6 +219,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190711165009-e47acb2ca7f9/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
Expand Down Expand Up @@ -291,6 +294,7 @@ golang.org/x/tools v0.0.0-20190619215442-4adf7a708c2d h1:LQ06Vbju+Kwbcd94hb+6CgD
golang.org/x/tools v0.0.0-20190619215442-4adf7a708c2d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190710184609-286818132824/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8 h1:VZick+NwcqlXXVsD1iFr4Wo6F1FgBbnM4AOMzhwKQ7w=
golang.org/x/tools v0.0.0-20190711191110-9a621aea19f8/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
Expand Down
4 changes: 3 additions & 1 deletion pkg/processor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type Processor interface {
Watermark(base []byte, overlay []byte, opacity uint8) ([]byte, error)
Decode(data []byte) (image.Image, string, error)
Encode(img image.Image, format string) ([]byte, error)
Flip(image image.Image, mode string) (image.Image, error)
Rotate(image image.Image, angle float64) (image.Image, error)
}
```
Any `struct` implementing the above interface can be used with Darkroom.
Expand All @@ -22,7 +24,7 @@ Any `struct` implementing the above interface can be used with Darkroom.
```go
bp := NewBildProcessor()
srcImgData, _ := ioutil.ReadFile("test.png")
srcImg, _, _ := bp.Decode(srcImgData)
srcImg, _, _ := bp.Decode(srcImgData)
outputImg, err := bp.Resize(srcImg, 500, 500)
outputImgData, _ := bp.Encode(outputImg, "png")
_ = ioutil.WriteFile("output.png", outputImgData, 0644)
Expand Down
7 changes: 7 additions & 0 deletions pkg/processor/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ type Processor interface {
// Watermark takes an input byte array, overlay byte array and opacity value
// and returns the watermarked image bytes or error
Watermark(base []byte, overlay []byte, opacity uint8) ([]byte, error)
// Flip takes an input image and returns the image flipped. The direction of flip
// is determined by the specified mode - 'v' for a vertical flip, 'h' for a horizontal flip and
// 'vh'(or 'hv') for both.
Flip(image image.Image, mode string) image.Image
// Rotate takes an input image and returns a image rotated by the specified degrees.
// The rotation is applied clockwise, and fractional angles are supported.
Rotate(image image.Image, angle float64) image.Image
// Decode takes a byte array and returns the image, extension, and error
Decode(data []byte) (image.Image, string, error)
// Encode takes an image and extension and return the encoded byte array or error
Expand Down
Binary file added pkg/processor/native/_testdata/test_flipedH.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added pkg/processor/native/_testdata/test_flipedVH.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added pkg/processor/native/_testdata/test_rotated90.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 27 additions & 3 deletions pkg/processor/native/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package native

import (
"bytes"
"image"
"image/color"
"image/draw"
"strings"

"github.com/anthonynsimon/bild/clone"
"github.com/anthonynsimon/bild/effect"
"github.com/anthonynsimon/bild/transform"
"github.com/gojek/darkroom/pkg/processor"
"image"
"image/color"
"image/draw"
)

// BildProcessor uses bild library to process images using native Golang image.Image interface
Expand Down Expand Up @@ -80,6 +82,28 @@ func (bp *BildProcessor) GrayScale(img image.Image) image.Image {
return effect.GrayscaleWithWeights(img, 0.299, 0.587, 0.114)
}

// Flip takes an input image and returns the image flipped. The direction of flip
// is determined by the specified mode - 'v' for a vertical flip, 'h' for a
// horizontal flip and 'vh'(or 'hv') for both.
func (bp *BildProcessor) Flip(img image.Image, mode string) image.Image {
mode = strings.ToLower(mode)
for _, op := range mode {
switch op {
case 'v':
img = transform.FlipV(img)
case 'h':
img = transform.FlipH(img)
}
}
return img
}

// Rotate takes an input image and returns a image rotated by the specified degrees.
// The rotation is applied clockwise, and fractional angles are also supported.
func (bp *BildProcessor) Rotate(img image.Image, angle float64) image.Image {
return transform.Rotate(img, angle, nil)
}

// Decode takes a byte array and returns the decoded image, format, or the error
func (bp *BildProcessor) Decode(data []byte) (image.Image, string, error) {
img, f, err := image.Decode(bytes.NewReader(data))
Expand Down
73 changes: 70 additions & 3 deletions pkg/processor/native/processor_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package native

import (
"github.com/gojek/darkroom/pkg/processor"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"image"
"io/ioutil"
"testing"

"github.com/gojek/darkroom/pkg/processor"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type BildProcessorSuite struct {
Expand Down Expand Up @@ -62,6 +63,72 @@ func (s *BildProcessorSuite) TestBildProcessor_Grayscale() {
assert.EqualValues(s.T(), actual, expected)
}

func (s *BildProcessorSuite) TestBildProcessor_Flip() {
var actual, expected []byte
var err error
cases := []struct {
flipMode string
testFile string
}{
{
flipMode: "v",
testFile: "_testdata/test_flipedV.jpg",
},
{
flipMode: "h",
testFile: "_testdata/test_flipedH.jpg",
},
{
flipMode: "vh",
testFile: "_testdata/test_flipedVH.jpg",
},
}

for _, c := range cases {
out := s.processor.Flip(s.srcImage, c.flipMode)
actual, err = s.processor.Encode(out, "jpeg")
assert.NotNil(s.T(), actual)
assert.Nil(s.T(), err)
expected, err = ioutil.ReadFile(c.testFile)
assert.NotNil(s.T(), expected)
assert.Nil(s.T(), err)
assert.EqualValues(s.T(), actual, expected)
}
}

func (s *BildProcessorSuite) TestBildProcessor_Rotate() {
var actual, expected []byte
var err error
cases := []struct {
angle float64
testFile string
}{
{
angle: 90.0,
testFile: "_testdata/test_rotated90.jpg",
},
{
angle: 175,
testFile: "_testdata/test_rotated175.jpg",
},
{
angle: 450.0,
testFile: "_testdata/test_rotated90.jpg",
},
}

for _, c := range cases {
out := s.processor.Rotate(s.srcImage, c.angle)
actual, err = s.processor.Encode(out, "jpeg")
assert.NotNil(s.T(), actual)
assert.Nil(s.T(), err)
expected, err = ioutil.ReadFile(c.testFile)
assert.NotNil(s.T(), expected)
assert.Nil(s.T(), err)
assert.EqualValues(s.T(), actual, expected)
}
}

func (s *BildProcessorSuite) TestBildProcessor_Watermark() {
output, err := s.processor.Watermark(s.srcData, s.watermarkData, 200)

Expand Down
29 changes: 27 additions & 2 deletions pkg/service/manipulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package service

import (
"fmt"
"github.com/gojek/darkroom/pkg/metrics"
"github.com/gojek/darkroom/pkg/processor"
"math"
"net/http"
"strconv"
"strings"
"time"

"github.com/gojek/darkroom/pkg/metrics"
"github.com/gojek/darkroom/pkg/processor"
)

const (
Expand All @@ -17,12 +19,16 @@ const (
crop = "crop"
mono = "mono"
blackHexCode = "000000"
flip = "flip"
rotate = "rot"

cropDurationKey = "cropDuration"
decodeDurationKey = "decodeDuration"
encodeDurationKey = "encodeDuration"
grayScaleDurationKey = "grayScaleDuration"
resizeDurationKey = "resizeDuration"
flipDurationKey = "flipDuration"
rotateDurationKey = "rotateDuration"
)

// Manipulator interface sets the contract on the implementation for common processing support in darkroom
Expand Down Expand Up @@ -70,6 +76,16 @@ func (m *manipulator) Process(spec ProcessSpec) ([]byte, error) {
data = m.processor.GrayScale(data)
trackDuration(grayScaleDurationKey, t, spec)
}
if len(params[flip]) != 0 {
t = time.Now()
data = m.processor.Flip(data, params[flip])
trackDuration(flipDurationKey, t, spec)
}
if CleanAngle(params[rotate]) > 0 {
t = time.Now()
data = m.processor.Rotate(data, CleanAngle(params[rotate]))
trackDuration(rotateDurationKey, t, spec)
}
t = time.Now()
src, err := m.processor.Encode(data, f)
if err == nil {
Expand All @@ -87,6 +103,15 @@ func CleanInt(input string) int {
return val % 10000 // Never return value greater than 9999
}

// CleanAngle takes a string and return a float64 not greater than 360
func CleanAngle(input string) float64 {
val, _ := strconv.ParseFloat(input, 64)
if val <= 0 {
return 0
}
return math.Mod(val, 360) // Never return value greater than 360
}

// GetCropPoint takes a string and returns the type CropPoint
func GetCropPoint(input string) processor.CropPoint {
switch input {
Expand Down
42 changes: 38 additions & 4 deletions pkg/service/manipulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package service

import (
"fmt"
"github.com/gojek/darkroom/pkg/processor"
"github.com/gojek/darkroom/pkg/processor/native"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"image"
"io/ioutil"
"testing"
"time"

"github.com/gojek/darkroom/pkg/processor"
"github.com/gojek/darkroom/pkg/processor/native"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestNewManipulator(t *testing.T) {
Expand Down Expand Up @@ -61,6 +62,29 @@ func TestManipulator_Process(t *testing.T) {
})
assert.Nil(t, err)
assert.Equal(t, input, data)

mp.On("Flip", decoded, "v").Return(decoded, nil)

params = make(map[string]string)
params[flip] = "v"
data, err = m.Process(ProcessSpec{
ImageData: input,
Params: params,
})
assert.Nil(t, err)
assert.Equal(t, input, data)

mp.On("Rotate", decoded, 90.5).Return(decoded, nil)

params = make(map[string]string)
params[rotate] = "90.5"
data, err = m.Process(ProcessSpec{
ImageData: input,
Params: params,
})
assert.Nil(t, err)
assert.Equal(t, input, data)

}

func TestGetCropPoint(t *testing.T) {
Expand Down Expand Up @@ -127,6 +151,16 @@ func (m *mockProcessor) GrayScale(img image.Image) image.Image {
return args.Get(0).(image.Image)
}

func (m *mockProcessor) Flip(img image.Image, mode string) image.Image {
args := m.Called(img, mode)
return args.Get(0).(image.Image)
}

func (m *mockProcessor) Rotate(img image.Image, angle float64) image.Image {
args := m.Called(img, angle)
return args.Get(0).(image.Image)
}

func (m *mockProcessor) Decode(data []byte) (image.Image, string, error) {
args := m.Called(data)
img := args.Get(0).(image.Image)
Expand Down

0 comments on commit 5630aa7

Please sign in to comment.