Skip to content

Commit

Permalink
Change interface of processor to accept image.Image instead of []byte…
Browse files Browse the repository at this point in the history
… and expose decode and encode implementation
  • Loading branch information
albertusdev committed Jul 5, 2019
1 parent 4b3aa78 commit 52d1902
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 88 deletions.
18 changes: 12 additions & 6 deletions pkg/processor/interface.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package processor

import "image"

// Processor interface for performing operations on image bytes
type Processor interface {
// Crop takes an input byte array, width, height and a CropPoint and returns the cropped image bytes or error
Crop(input []byte, width, height int, point CropPoint) ([]byte, error)
// Resize takes an input byte array, width and height and returns the re-sized image bytes or error
Resize(input []byte, width, height int) ([]byte, error)
// Crop takes an image.Image, width, height and a CropPoint and returns the cropped image
Crop(image image.Image, width, height int, point CropPoint) image.Image
// Resize takes an image.Image, width and height and returns the re-sized image
Resize(image image.Image, width, height int) image.Image
// GrayScale takes an input byte array and returns the grayscaled byte array or error
GrayScale(image image.Image) image.Image
// 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)
// GrayScale takes an input byte array and returns the grayscaled byte array or error
GrayScale(input []byte) ([]byte, error)
// 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
Encode(img image.Image, format string) ([]byte, error)
}
35 changes: 11 additions & 24 deletions pkg/processor/native/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,19 @@ type BildProcessor struct {
}

// Crop takes an input byte array, width, height and a CropPoint and returns the cropped image bytes or error
func (bp *BildProcessor) Crop(input []byte, width, height int, point processor.CropPoint) ([]byte, error) {
img, f, err := bp.decode(input)
if err != nil {
return nil, err
}

func (bp *BildProcessor) Crop(img image.Image, width, height int, point processor.CropPoint) image.Image {
w, h := getResizeWidthAndHeightForCrop(width, height, img.Bounds().Dx(), img.Bounds().Dy())

img = transform.Resize(img, w, h, transform.Linear)
x0, y0 := getStartingPointForCrop(w, h, width, height, point)
rect := image.Rect(x0, y0, width+x0, height+y0)
img = (clone.AsRGBA(img)).SubImage(rect)

return bp.encode(img, f)
return img
}

// Resize takes an input byte array, width and height and returns the re-sized image bytes or error
func (bp *BildProcessor) Resize(input []byte, width, height int) ([]byte, error) {
img, f, err := bp.decode(input)
if err != nil {
return nil, err
}
func (bp *BildProcessor) Resize(img image.Image, width, height int) image.Image {

initW := img.Bounds().Dx()
initH := img.Bounds().Dy()
Expand All @@ -60,17 +51,17 @@ func (bp *BildProcessor) Resize(input []byte, width, height int) ([]byte, error)
img = transform.Resize(img, w, h, transform.Linear)
}

return bp.encode(img, f)
return img
}

// Watermark takes an input byte array, overlay byte array and opacity value
// and returns the watermarked image bytes or error
func (bp *BildProcessor) Watermark(base []byte, overlay []byte, opacity uint8) ([]byte, error) {
baseImg, f, err := bp.decode(base)
baseImg, f, err := bp.Decode(base)
if err != nil {
return nil, err
}
overlayImg, _, err := bp.decode(overlay)
overlayImg, _, err := bp.Decode(overlay)
if err != nil {
return nil, err
}
Expand All @@ -94,15 +85,11 @@ func (bp *BildProcessor) Watermark(base []byte, overlay []byte, opacity uint8) (
draw.DrawMask(baseImg.(draw.Image), overlayImg.Bounds().Add(offset), overlayImg, image.ZP, mask, image.ZP, draw.Over)
metrics.Update(metrics.UpdateOption{Name: watermarkDurationKey, Type: metrics.Duration, Duration: time.Since(t)})

return bp.encode(baseImg, f)
return bp.Encode(baseImg, f)
}

// GrayScale takes an input byte array and returns the grayscaled byte array or error
func (bp *BildProcessor) GrayScale(input []byte) ([]byte, error) {
img, f, err := bp.decode(input)
if err != nil {
return nil, err
}
func (bp *BildProcessor) GrayScale(img image.Image) image.Image {
src := clone.AsRGBA(img)
bounds := src.Bounds()
if bounds.Empty() {
Expand All @@ -118,10 +105,10 @@ func (bp *BildProcessor) GrayScale(input []byte) ([]byte, error) {
}
})
}
return bp.encode(src, f)
return src
}

func (bp *BildProcessor) decode(data []byte) (image.Image, string, error) {
func (bp *BildProcessor) Decode(data []byte) (image.Image, string, error) {
t := time.Now()
img, f, err := image.Decode(bytes.NewReader(data))
if err == nil {
Expand All @@ -130,7 +117,7 @@ func (bp *BildProcessor) decode(data []byte) (image.Image, string, error) {
return img, f, err
}

func (bp *BildProcessor) encode(img image.Image, format string) ([]byte, error) {
func (bp *BildProcessor) Encode(img image.Image, format string) ([]byte, error) {
t := time.Now()
if format == pngType && isOpaque(img) {
format = jpgType
Expand Down
43 changes: 15 additions & 28 deletions pkg/processor/native/processor_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package native

import (
"bytes"
"github.com/gojek/darkroom/pkg/processor"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
Expand All @@ -13,48 +12,48 @@ import (
type BildProcessorSuite struct {
suite.Suite
srcData []byte
srcImage image.Image
watermarkData []byte
badData []byte
badImage image.Image
processor processor.Processor
}

func (s *BildProcessorSuite) SetupSuite() {
s.processor = NewBildProcessor()
s.srcData, _ = ioutil.ReadFile("_testdata/test.png")
s.srcImage, _, _ = s.processor.Decode(s.srcData)
s.watermarkData, _ = ioutil.ReadFile("_testdata/overlay.png")
s.badData = []byte("badImage.ext")

}

func TestNewBildProcessor(t *testing.T) {
suite.Run(t, new(BildProcessorSuite))
}

func (s *BildProcessorSuite) TestBildProcessor_Resize() {
output, err := s.processor.Resize(s.srcData, 500, 500)

assert.NotNil(s.T(), output)
assert.Nil(s.T(), err)
out := s.processor.Resize(s.srcImage, 500, 500)

img, _, _ := image.Decode(bytes.NewReader(output))
assert.Equal(s.T(), 500, img.Bounds().Dx())
assert.Equal(s.T(), 375, img.Bounds().Dy())
assert.NotNil(s.T(), out)
assert.Equal(s.T(), 500, out.Bounds().Dx())
assert.Equal(s.T(), 375, out.Bounds().Dy())
}

func (s *BildProcessorSuite) TestBildProcessor_Crop() {
output, err := s.processor.Crop(s.srcData, 500, 500, processor.CropCenter)
out := s.processor.Crop(s.srcImage, 500, 500, processor.CropCenter)

assert.NotNil(s.T(), output)
assert.Nil(s.T(), err)
assert.NotNil(s.T(), out)

img, _, _ := image.Decode(bytes.NewReader(output))
assert.Equal(s.T(), 500, img.Bounds().Dx())
assert.Equal(s.T(), 500, img.Bounds().Dy())
assert.Equal(s.T(), 500, out.Bounds().Dx())
assert.Equal(s.T(), 500, out.Bounds().Dy())
}

func (s *BildProcessorSuite) TestBildProcessor_Grayscale() {
var actual, expected []byte
var err error
actual, err = s.processor.GrayScale(s.srcData)
out := s.processor.GrayScale(s.srcImage)
actual, err = s.processor.Encode(out, "png")
assert.NotNil(s.T(), actual)
assert.Nil(s.T(), err)

Expand All @@ -75,19 +74,7 @@ func (s *BildProcessorSuite) TestBildProcessor_Watermark() {
}

func (s *BildProcessorSuite) TestBildProcessorWithBadInput() {
output, err := s.processor.Crop(s.badData, 0, 0, processor.CropCenter)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), output)

output, err = s.processor.Resize(s.badData, 0, 0)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), output)

output, err = s.processor.GrayScale(s.badData)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), output)

output, err = s.processor.Watermark(s.badData, s.watermarkData, 255)
output, err := s.processor.Watermark(s.badData, s.watermarkData, 255)
assert.NotNil(s.T(), err)
assert.Nil(s.T(), output)

Expand Down
12 changes: 7 additions & 5 deletions pkg/service/manipulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,31 @@ type ProcessSpec struct {
// This manipulator uses bild to do the actual image manipulations
func (m *manipulator) Process(spec ProcessSpec) ([]byte, error) {
params := spec.Params
data := spec.ImageData
//data := spec.ImageData
var err error
data, f, err := m.processor.Decode(spec.ImageData)
if params[fit] == crop {
t := time.Now()
data, err = m.processor.Crop(data, CleanInt(params[width]), CleanInt(params[height]), GetCropPoint(params[crop]))
data = m.processor.Crop(data, CleanInt(params[width]), CleanInt(params[height]), GetCropPoint(params[crop]))
if err == nil {
trackDuration(cropDurationKey, t, spec)
}
} else if len(params[fit]) == 0 && (CleanInt(params[width]) != 0 || CleanInt(params[height]) != 0) {
t := time.Now()
data, err = m.processor.Resize(data, CleanInt(params[width]), CleanInt(params[height]))
data = m.processor.Resize(data, CleanInt(params[width]), CleanInt(params[height]))
if err == nil {
trackDuration(resizeDurationKey, t, spec)
}
}
if params[mono] == blackHexCode {
t := time.Now()
data, err = m.processor.GrayScale(data)
data = m.processor.GrayScale(data)
if err == nil {
trackDuration(grayScaleDurationKey, t, spec)
}
}
return data, err
src, err := m.processor.Encode(data, f)
return src, err
}

// CleanInt takes a string and return an int not greater than 9999
Expand Down
66 changes: 41 additions & 25 deletions pkg/service/manipulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/gojek/darkroom/pkg/processor/native"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"image"
"io/ioutil"
"testing"
"time"
Expand All @@ -21,40 +22,45 @@ func TestManipulator_Process(t *testing.T) {
m := NewManipulator(mp)
params := make(map[string]string)

mp.On("Crop", []byte("toCrop"), 100, 100, processor.CropCenter).Return([]byte("cropped"), nil)
input := []byte("inputData")
decoded := &image.RGBA{Pix: []uint8{1, 2, 3, 4}}

mp.On("Decode", input).Return(decoded, "png", nil)
mp.On("Encode", decoded, "png").Return(input, nil)
mp.On("Crop", decoded, 100, 100, processor.CropCenter).Return(decoded, nil)

params[fit] = crop
params[width] = "100"
params[height] = "100"
data, err := m.Process(ProcessSpec{
ImageData: []byte("toCrop"),
ImageData: input,
Params: params,
})
assert.Nil(t, err)
assert.Equal(t, []byte("cropped"), data)
assert.Equal(t, input, data)

mp.On("Resize", []byte("toResize"), 100, 100).Return([]byte("reSized"), nil)
mp.On("Resize", decoded, 100, 100).Return(decoded, nil)

params = make(map[string]string)
params[width] = "100"
params[height] = "100"
data, err = m.Process(ProcessSpec{
ImageData: []byte("toResize"),
ImageData: input,
Params: params,
})
assert.Nil(t, err)
assert.Equal(t, []byte("reSized"), data)
assert.Equal(t, input, data)

mp.On("GrayScale", []byte("toGrayScale")).Return([]byte("grayScaled"), nil)
mp.On("GrayScale", decoded).Return(decoded, nil)

params = make(map[string]string)
params[mono] = blackHexCode
data, err = m.Process(ProcessSpec{
ImageData: []byte("toGrayScale"),
ImageData: input,
Params: params,
})
assert.Nil(t, err)
assert.Equal(t, []byte("grayScaled"), data)
assert.Equal(t, input, data)
}

func TestGetCropPoint(t *testing.T) {
Expand Down Expand Up @@ -101,31 +107,41 @@ type mockProcessor struct {
mock.Mock
}

func (m *mockProcessor) Crop(input []byte, width, height int, point processor.CropPoint) ([]byte, error) {
args := m.Called(input, width, height, point)
if args.Get(1) == nil {
return args.Get(0).([]byte), nil
}
return args.Get(0).([]byte), args.Get(1).(error)
func (m *mockProcessor) Crop(img image.Image, width, height int, point processor.CropPoint) image.Image {
args := m.Called(img, width, height, point)
return args.Get(0).(image.Image)
}

func (m *mockProcessor) Resize(input []byte, width, height int) ([]byte, error) {
args := m.Called(input, width, height)
if args.Get(1) == nil {
return args.Get(0).([]byte), nil
}
return args.Get(0).([]byte), args.Get(1).(error)
func (m *mockProcessor) Resize(img image.Image, width, height int) image.Image {
args := m.Called(img, width, height)
return args.Get(0).(image.Image)
}

func (m *mockProcessor) Watermark(base []byte, overlay []byte, opacity uint8) ([]byte, error) {
args := m.Called(base, overlay, opacity)
return args.Get(0).([]byte), args.Get(1).(error)
}

func (m *mockProcessor) GrayScale(input []byte) ([]byte, error) {
args := m.Called(input)
func (m *mockProcessor) GrayScale(img image.Image) image.Image {
args := m.Called(img)
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)
ext := args.Get(1).(string)
if args.Get(2) == nil {
return img, ext, nil
}
return img, ext, args.Get(2).(error)
}

func (m *mockProcessor) Encode(img image.Image, format string) ([]byte, error) {
args := m.Called(img, format)
b := args.Get(0).([]byte)
if args.Get(1) == nil {
return args.Get(0).([]byte), nil
return b, nil
}
return args.Get(0).([]byte), args.Get(1).(error)
return b, args.Get(1).(error)
}

0 comments on commit 52d1902

Please sign in to comment.