Skip to content

Commit

Permalink
Merge pull request #120 from thoas/feat-op-flat-stick-position
Browse files Browse the repository at this point in the history
feat(engine): goimage flat: add stick position
  • Loading branch information
thoas authored Jul 16, 2019
2 parents e6ba038 + 28d8dc5 commit 053a6f5
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 44 deletions.
14 changes: 14 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,17 @@ var (
// Compiler is the compiler used during build
Compiler string
)

const (
TopRight = "top-right"
TopLeft = "top-left"
BottomRight = "bottom-right"
BottomLeft = "bottom-left"
)

var StickPositions = []string{
TopRight,
TopLeft,
BottomRight,
BottomLeft,
}
1 change: 1 addition & 0 deletions engine/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Options struct {
Width int
Height int
Position string
Stick string
Color string
Degree int
Images []image.ImageFile
Expand Down
110 changes: 95 additions & 15 deletions engine/backend/goimage_flat.go
Original file line number Diff line number Diff line change
@@ -1,48 +1,118 @@
package backend

import (
"bytes"
"fmt"
"image"
"image/draw"
"image/gif"
"strconv"
"strings"

"github.com/disintegration/imaging"
colorful "github.com/lucasb-eyer/go-colorful"

"github.com/thoas/picfit/constants"
imagefile "github.com/thoas/picfit/image"
)

func (e *GoImage) Flat(backgroundFile *imagefile.ImageFile, options *Options) ([]byte, error) {
var err error
images := make([]image.Image, len(options.Images))
for i := range options.Images {
images[i], err = e.Source(&options.Images[i])
if err != nil {
return nil, err
}
}

if options.Format == imaging.GIF {
return e.TransformGIF(backgroundFile, options, imaging.Resize)
g, err := gif.DecodeAll(bytes.NewReader(backgroundFile.Source))
if err != nil {
return nil, err
}

for i := range g.Image {
if options.Stick != "" {
drawStickForeground(g.Image[i], images, options)
} else {
drawPosForeground(g.Image[i], images, options)
}
}
buf := bytes.Buffer{}

err = gif.EncodeAll(&buf, g)
if err != nil {
return nil, err
}

return buf.Bytes(), nil
}

background, err := e.Source(backgroundFile)
if err != nil {
return nil, err
}

images := make([]image.Image, len(options.Images))
for i := range options.Images {
images[i], err = e.Source(&options.Images[i])
if err != nil {
return nil, err
}
bg, ok := background.(draw.Image)
if !ok {
bg = image.NewRGBA(image.Rectangle{image.Point{}, background.Bounds().Size()})
draw.Draw(bg, background.Bounds(), background, image.Point{}, draw.Src)
}

if options.Stick != "" {
drawStickForeground(bg, images, options)
} else {
drawPosForeground(bg, images, options)
}

bg := image.NewRGBA(image.Rectangle{image.Point{}, background.Bounds().Size()})
draw.Draw(bg, background.Bounds(), background, image.Point{}, draw.Src)
return e.ToBytes(bg, options.Format, options.Quality)
}

func drawStickForeground(bg draw.Image, images []image.Image, options *Options) {
for i := range images {
opts := &Options{
Upscale: true,
Width: options.Width,
Height: options.Height,
}

images[i] = scale(images[i], opts, imaging.Resize)

bounds := images[i].Bounds()
var position image.Point
switch options.Stick {
case constants.TopLeft:
position = bounds.Min
case constants.TopRight:
position = image.Point{X: bg.Bounds().Dx() - bounds.Dx(), Y: 0}
case constants.BottomLeft:
position = image.Point{X: 0, Y: bg.Bounds().Dy() - bounds.Dy()}
case constants.BottomRight:
position = image.Point{
X: bg.Bounds().Dx() - bounds.Dx(),
Y: bg.Bounds().Dy() - bounds.Dy(),
}
}

draw.Draw(bg, image.Rectangle{
position,
position.Add(bounds.Size()),
}, images[i], bounds.Min, draw.Over)
}
}

// drawPosForeground draw the given images on the given background inside the
// section delimited by the options position.
func drawPosForeground(bg draw.Image, images []image.Image, options *Options) {
dst := positionForeground(bg, options.Position)
fg := foregroundImage(dst, options.Color)
fg = drawForeground(fg, images, options)

draw.Draw(bg, dst, fg, fg.Bounds().Min, draw.Over)

return e.ToBytes(bg, options.Format, options.Quality)
}

// positionForeground creates a mask with the given position.
func positionForeground(bg image.Image, pos string) image.Rectangle {
ratios := []int{100, 100, 100, 100}
val := strings.Split(pos, ".")
Expand All @@ -59,7 +129,8 @@ func positionForeground(bg image.Image, pos string) image.Rectangle {
}
}

func foregroundImage(rec image.Rectangle, c string) *image.RGBA {
// foregroundImage creates an Image with the given mask and the given color.
func foregroundImage(rec image.Rectangle, c string) draw.Image {
fg := image.NewRGBA(image.Rectangle{image.ZP, rec.Size()})
if c == "" {
return fg
Expand All @@ -74,7 +145,10 @@ func foregroundImage(rec image.Rectangle, c string) *image.RGBA {
return fg
}

func drawForeground(fg *image.RGBA, images []image.Image, options *Options) *image.RGBA {
// drawForeground draw the given images inside the destination foreground.
// if the foreground image has a height superior to its width, the images
// are vertically aligned, else they are horizontally aligned.
func drawForeground(fg draw.Image, images []image.Image, options *Options) draw.Image {
n := len(images)
if n == 0 {
return fg
Expand Down Expand Up @@ -103,7 +177,10 @@ func drawForeground(fg *image.RGBA, images []image.Image, options *Options) *ima
}
}

func foregroundHorizontal(fg *image.RGBA, images []image.Image, options *Options) *image.RGBA {
// foregroundHorizontal splits the fg according to the number of images in
// equal parts horizontally aligned and draw each images in the given order in
// the center of each of theses parts.
func foregroundHorizontal(fg draw.Image, images []image.Image, options *Options) draw.Image {
position := fg.Bounds().Min
totalHeight := fg.Bounds().Dy()
cellWidth := fg.Bounds().Dx() / len(images)
Expand All @@ -120,7 +197,10 @@ func foregroundHorizontal(fg *image.RGBA, images []image.Image, options *Options
return fg
}

func foregroundVertical(fg *image.RGBA, images []image.Image, options *Options) *image.RGBA {
// foregroundVertical splits the fg according to the number of images in
// equal parts vertically aligned and draw each images in the given order in
// the center of each of theses parts.
func foregroundVertical(fg draw.Image, images []image.Image, options *Options) draw.Image {
position := fg.Bounds().Min
cellHeight := fg.Bounds().Dy() / len(images)
totalWidth := fg.Bounds().Dx()
Expand Down
1 change: 1 addition & 0 deletions engine/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type Backends struct {
}

type Backend struct {
Weight int
Mimetypes []string
}

Expand Down
7 changes: 7 additions & 0 deletions engine/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ var ContentTypes = map[string]string{
"bmp": "image/bmp",
"gif": "image/gif",
}

var MimeTypes = []string{
"image/jpeg",
"image/png",
"image/bmp",
"image/gif",
}
69 changes: 40 additions & 29 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package engine

import (
"fmt"
"sort"
"strings"

"github.com/thoas/picfit/engine/backend"
Expand All @@ -14,41 +15,46 @@ type Engine struct {
Format string
DefaultQuality int

backends []backend.Backend
mimetypes map[string]backend.Backend
backends []Backend
}

type Backend struct {
backend.Backend
weight int
mimetypes []string
}

// New initializes an Engine
func New(cfg config.Config) *Engine {
var (
b []backend.Backend
mimetypes = map[string]backend.Backend{}
)
var b []Backend

if cfg.Backends == nil {
b = append(b, &backend.GoImage{})
b = append(b, Backend{
Backend: &backend.GoImage{},
mimetypes: MimeTypes,
})
} else {
if cfg.Backends.Lilliput != nil {
back := backend.NewLilliput(cfg)

b = append(b, back)

for _, mimetype := range cfg.Backends.Lilliput.Mimetypes {
mimetypes[mimetype] = back
}
b = append(b, Backend{
Backend: backend.NewLilliput(cfg),
mimetypes: cfg.Backends.Lilliput.Mimetypes,
weight: cfg.Backends.Lilliput.Weight,
})
}

if cfg.Backends.GoImage != nil {
back := &backend.GoImage{}

b = append(b, back)

for _, mimetype := range cfg.Backends.GoImage.Mimetypes {
mimetypes[mimetype] = back
}
b = append(b, Backend{
Backend: &backend.GoImage{},
mimetypes: cfg.Backends.GoImage.Mimetypes,
weight: cfg.Backends.GoImage.Weight,
})
}
}

sort.Slice(b, func(i, j int) bool {
return b[i].weight < b[j].weight
})

quality := config.DefaultQuality
if cfg.Quality != 0 {
quality = cfg.Quality
Expand All @@ -59,7 +65,6 @@ func New(cfg config.Config) *Engine {
Format: cfg.Format,
DefaultQuality: quality,
backends: b,
mimetypes: mimetypes,
}
}

Expand All @@ -79,16 +84,22 @@ func (e Engine) Transform(output *image.ImageFile, operations []EngineOperation)
source = output.Source
)

ct := output.ContentType()
for i := range operations {
backends := e.backends
for j := range e.backends {
var processing bool
for k := range e.backends[j].mimetypes {
if ct == e.backends[j].mimetypes[k] {
processing = true
break
}
}

back, ok := e.mimetypes[output.ContentType()]
if ok {
backends = []backend.Backend{back}
}
if !processing {
continue
}

for j := range backends {
processed, err = operate(backends[j], output, operations[i].Operation, operations[i].Options)
processed, err = operate(e.backends[j], output, operations[i].Operation, operations[i].Options)
if err == nil {
output.Source = processed
break
Expand Down
16 changes: 16 additions & 0 deletions parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/disintegration/imaging"
"github.com/pkg/errors"

"github.com/thoas/picfit/constants"
"github.com/thoas/picfit/engine"
"github.com/thoas/picfit/engine/backend"
"github.com/thoas/picfit/failure"
Expand Down Expand Up @@ -194,6 +195,20 @@ func (p Processor) newBackendOptionsFromParameters(operation engine.Operation, q
return nil, fmt.Errorf("Parameter \"pos\" not found in query string")
}

stick, _ := qs["stick"].(string)
if stick != "" {
var exists bool
for i := range constants.StickPositions {
if stick == constants.StickPositions[i] {
exists = true
break
}
}
if !exists {
return nil, fmt.Errorf("Parameter \"stick\" has wrong value. Available values are: %v", constants.StickPositions)
}
}

color, _ := qs["color"].(string)

if deg, ok := qs["deg"].(string); ok {
Expand Down Expand Up @@ -229,6 +244,7 @@ func (p Processor) newBackendOptionsFromParameters(operation engine.Operation, q
Height: height,
Upscale: upscale,
Position: position,
Stick: stick,
Quality: quality,
Degree: degree,
Color: color,
Expand Down

0 comments on commit 053a6f5

Please sign in to comment.