Skip to content

Commit

Permalink
Merge pull request #98 from thoas/refac-mutiple-operations
Browse files Browse the repository at this point in the history
Refac mutiple operations
  • Loading branch information
thoas authored Dec 11, 2018
2 parents 9e79789 + 4f4ccce commit 30ea8f9
Show file tree
Hide file tree
Showing 12 changed files with 421 additions and 166 deletions.
21 changes: 19 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ storage to your configuration file.
"enable_upload": true
}
}
To work properly, the input field must be named "data"

Test it with the excellent httpie_:
Expand All @@ -522,6 +522,23 @@ Test it with the excellent httpie_:

You will retrieve the uploaded image information in ``JSON`` format.

Multiple operations
===================

Multiple operations can be done on the same image following a given order.

First operation must be described as above then other operation are described in parameters ``op``.
The order of ``op`` parameters is the order used.

Each options of the operation must be described with subparameters separed by
``:`` with the operation name as argument to ``op``.

Example of a resize followed by a rotation:

::

<img src="http://localhost:3001/display?w=100&h=100&path=path/to/file.png&op=resize&op=op:rotate+deg:180"

Security
========

Expand Down Expand Up @@ -706,7 +723,7 @@ Stats

The stats middleware is disabled by default, you can enable it your config.

It will store various information about your web application (response time, status code count, etc.)
It will store various information about your web application (response time, status code count, etc.)

``config.json``

Expand Down
13 changes: 9 additions & 4 deletions application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ func processImage(c *gin.Context, l logger.Logger, storeKey string, async bool)
}

var filepath string
parameters := c.MustGet("parameters").(map[string]string)
qs := c.MustGet("parameters").(map[string]interface{})

var err error
u, exists := c.Get("url")
Expand All @@ -263,7 +263,7 @@ func processImage(c *gin.Context, l logger.Logger, storeKey string, async bool)
// URL provided we use http protocol to retrieve it
s := storage.SourceFromContext(c)

filepath = parameters["path"]
filepath = qs["path"].(string)
if !s.Exists(filepath) {
return nil, errs.ErrFileNotExists
}
Expand All @@ -274,8 +274,13 @@ func processImage(c *gin.Context, l logger.Logger, storeKey string, async bool)
return nil, err
}

op := c.MustGet("op").(engine.Operation)
file, err = engine.FromContext(c).Transform(file, op, parameters)
e := engine.FromContext(c)
parameters, err := NewParameters(e, file, qs)
if err != nil {
return nil, err
}

file, err = e.Transform(parameters.Output, parameters.Operations)
if err != nil {
return nil, err
}
Expand Down
14 changes: 14 additions & 0 deletions application/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,20 @@ func TestDummyApplication(t *testing.T) {
},
ContentType: "image/jpeg",
},
&TestRequest{
URL: fmt.Sprintf("http://example.com/display?url=%s&op=op:resize+w:100+h:50&op=op:rotate+deg:90", u.String()),
Dimensions: &Dimension{
Width: 50,
Height: 100,
},
},
&TestRequest{
URL: fmt.Sprintf("http://example.com/display?url=%s&op=resize&w=100&h=50&op=op:rotate+deg:90", u.String()),
Dimensions: &Dimension{
Width: 50,
Height: 100,
},
},
}

for _, test := range tests {
Expand Down
214 changes: 214 additions & 0 deletions application/parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package application

import (
"fmt"
"strconv"
"strings"

"github.com/disintegration/imaging"
"github.com/thoas/picfit/engine"
"github.com/thoas/picfit/engine/backend"
"github.com/thoas/picfit/image"
)

const (
defaultUpscale = true
defaultWidth = 0
defaultHeight = 0
defaultDegree = 90
)

var formats = map[string]imaging.Format{
"jpeg": imaging.JPEG,
"jpg": imaging.JPEG,
"png": imaging.PNG,
"gif": imaging.GIF,
"bmp": imaging.BMP,
}

type Parameters struct {
Output *image.ImageFile
Operations []engine.EngineOperation
}

func NewParameters(e *engine.Engine, input *image.ImageFile, qs map[string]interface{}) (*Parameters, error) {
format, ok := qs["fmt"].(string)
filepath := input.Filepath

if ok {
if _, ok := engine.ContentTypes[format]; !ok {
return nil, fmt.Errorf("Unknown format %s", format)
}

}

if format == "" && e.Format != "" {
format = e.Format
}

if format == "" {
format = input.Format()
}

if format == "" {
format = e.DefaultFormat
}

if format != input.Format() {
index := len(filepath) - len(input.Format())

filepath = filepath[:index] + format

if contentType, ok := engine.ContentTypes[format]; ok {
input.Headers["Content-Type"] = contentType
}
}

output := &image.ImageFile{
Source: input.Source,
Key: input.Key,
Headers: input.Headers,
Filepath: filepath,
}

var operations []engine.EngineOperation

op, ok := qs["op"].(string)
if ok {
operation := engine.Operation(op)
opts, err := newBackendOptionsFromParameters(e, operation, qs)
if err != nil {
return nil, err
}

opts.Format = formats[format]
operations = append(operations, engine.EngineOperation{
Options: opts,
Operation: operation,
})
}

ops, ok := qs["op"].([]string)
if ok {
for i := range ops {
var err error
engineOperation := &engine.EngineOperation{}
operation, k := engine.Operations[ops[i]]
if k {
engineOperation.Operation = operation
engineOperation.Options, err = newBackendOptionsFromParameters(e, operation, qs)
if err != nil {
return nil, err
}
} else {
engineOperation, err = newEngineOperationFromQuery(e, ops[i])
if err != nil {
return nil, err
}
}

if engineOperation != nil {
engineOperation.Options.Format = formats[format]
operations = append(operations, *engineOperation)
}
}
}

return &Parameters{
Output: output,
Operations: operations,
}, nil
}

func newEngineOperationFromQuery(e *engine.Engine, op string) (*engine.EngineOperation, error) {
params := make(map[string]interface{})
for _, p := range strings.Split(op, " ") {
l := strings.Split(p, ":")
if len(l) > 1 {
params[l[0]] = l[1]
}
}

op, ok := params["op"].(string)
if !ok {
return nil, nil
}

operation := engine.Operation(op)
opts, err := newBackendOptionsFromParameters(e, operation, params)
if err != nil {
return nil, err
}

return &engine.EngineOperation{
Options: opts,
Operation: operation,
}, nil
}

func newBackendOptionsFromParameters(e *engine.Engine, operation engine.Operation, qs map[string]interface{}) (*backend.Options, error) {
var (
err error
quality int
upscale = defaultUpscale
height = defaultHeight
width = defaultWidth
degree = defaultDegree
)

q, ok := qs["q"].(string)
if ok {
quality, err = strconv.Atoi(q)
if err != nil {
return nil, err
}

if quality > 100 {
return nil, fmt.Errorf("Quality should be <= 100")
}
} else {
quality = e.DefaultQuality
}

position, ok := qs["pos"].(string)
if !ok && operation == engine.Flip {
return nil, fmt.Errorf("Parameter \"pos\" not found in query string")
}

if deg, ok := qs["deg"].(string); ok {
degree, err = strconv.Atoi(deg)
if err != nil {
return nil, err
}
}

if up, ok := qs["upscale"].(string); ok {
upscale, err = strconv.ParseBool(up)
if err != nil {
return nil, err
}
}

if w, ok := qs["w"].(string); ok {
width, err = strconv.Atoi(w)
if err != nil {
return nil, err
}
}

if h, ok := qs["h"].(string); ok {
height, err = strconv.Atoi(h)
if err != nil {
return nil, err
}
}

return &backend.Options{
Width: width,
Height: height,
Upscale: upscale,
Position: position,
Quality: quality,
Degree: degree,
}, nil
}
22 changes: 22 additions & 0 deletions application/parameters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package application

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/thoas/picfit/engine"
)

func TestEngineOperationFromQuery(t *testing.T) {
op := "op:resize w:123 h:321 upscale:true pos:top q:99"
operation, err := newEngineOperationFromQuery(&engine.Engine{}, op)
assert.Nil(t, err)

assert.Equal(t, operation.Operation.String(), "resize")
assert.Equal(t, operation.Options.Height, 321)
assert.Equal(t, operation.Options.Width, 123)
assert.Equal(t, operation.Options.Position, "top")
assert.Equal(t, operation.Options.Quality, 99)
assert.True(t, operation.Options.Upscale)
}
Loading

0 comments on commit 30ea8f9

Please sign in to comment.