mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Improve handling of non-square avatars (#7025)
* Crop avatar before resizing (#1268) Signed-off-by: Rob Watson <rfwatson@users.noreply.github.com> * Fix spelling error Signed-off-by: Rob Watson <rfwatson@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							@@ -90,6 +90,7 @@ require (
 | 
				
			|||||||
	github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect
 | 
						github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae // indirect
 | 
				
			||||||
	github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc
 | 
						github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc
 | 
				
			||||||
	github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
 | 
						github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
 | 
				
			||||||
 | 
						github.com/oliamb/cutter v0.2.2
 | 
				
			||||||
	github.com/philhofer/fwd v1.0.0 // indirect
 | 
						github.com/philhofer/fwd v1.0.0 // indirect
 | 
				
			||||||
	github.com/pkg/errors v0.8.1 // indirect
 | 
						github.com/pkg/errors v0.8.1 // indirect
 | 
				
			||||||
	github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e
 | 
						github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							@@ -244,6 +244,8 @@ github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc h1:z1PgdCCmYYVL0BoJT
 | 
				
			|||||||
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
 | 
					github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
 | 
				
			||||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
 | 
					github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=
 | 
				
			||||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
					github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
				
			||||||
 | 
					github.com/oliamb/cutter v0.2.2 h1:Lfwkya0HHNU1YLnGv2hTkzHfasrSMkgv4Dn+5rmlk3k=
 | 
				
			||||||
 | 
					github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU=
 | 
				
			||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
					github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
				
			||||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 | 
					github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
 | 
				
			||||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
					github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
					 | 
				
			||||||
	"container/list"
 | 
						"container/list"
 | 
				
			||||||
	"crypto/md5"
 | 
						"crypto/md5"
 | 
				
			||||||
	"crypto/sha256"
 | 
						"crypto/sha256"
 | 
				
			||||||
@@ -14,7 +13,6 @@ import (
 | 
				
			|||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"image"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Needed for jpeg support
 | 
						// Needed for jpeg support
 | 
				
			||||||
	_ "image/jpeg"
 | 
						_ "image/jpeg"
 | 
				
			||||||
@@ -39,7 +37,6 @@ import (
 | 
				
			|||||||
	"github.com/go-xorm/builder"
 | 
						"github.com/go-xorm/builder"
 | 
				
			||||||
	"github.com/go-xorm/core"
 | 
						"github.com/go-xorm/core"
 | 
				
			||||||
	"github.com/go-xorm/xorm"
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
	"github.com/nfnt/resize"
 | 
					 | 
				
			||||||
	"golang.org/x/crypto/pbkdf2"
 | 
						"golang.org/x/crypto/pbkdf2"
 | 
				
			||||||
	"golang.org/x/crypto/ssh"
 | 
						"golang.org/x/crypto/ssh"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -457,23 +454,10 @@ func (u *User) IsPasswordSet() bool {
 | 
				
			|||||||
// UploadAvatar saves custom avatar for user.
 | 
					// UploadAvatar saves custom avatar for user.
 | 
				
			||||||
// FIXME: split uploads to different subdirs in case we have massive users.
 | 
					// FIXME: split uploads to different subdirs in case we have massive users.
 | 
				
			||||||
func (u *User) UploadAvatar(data []byte) error {
 | 
					func (u *User) UploadAvatar(data []byte) error {
 | 
				
			||||||
	imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
 | 
						m, err := avatar.Prepare(data)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return fmt.Errorf("DecodeConfig: %v", err)
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if imgCfg.Width > setting.AvatarMaxWidth {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Image width is to large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if imgCfg.Height > setting.AvatarMaxHeight {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Image height is to large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	img, _, err := image.Decode(bytes.NewReader(data))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return fmt.Errorf("Decode: %v", err)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	m := resize.Resize(avatar.AvatarSize, avatar.AvatarSize, img, resize.NearestNeighbor)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	sess := x.NewSession()
 | 
						sess := x.NewSession()
 | 
				
			||||||
	defer sess.Close()
 | 
						defer sess.Close()
 | 
				
			||||||
@@ -497,7 +481,7 @@ func (u *User) UploadAvatar(data []byte) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	defer fw.Close()
 | 
						defer fw.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = png.Encode(fw, m); err != nil {
 | 
						if err = png.Encode(fw, *m); err != nil {
 | 
				
			||||||
		return fmt.Errorf("Encode: %v", err)
 | 
							return fmt.Errorf("Encode: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,13 +5,20 @@
 | 
				
			|||||||
package avatar
 | 
					package avatar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"image"
 | 
						"image"
 | 
				
			||||||
	"image/color/palette"
 | 
						"image/color/palette"
 | 
				
			||||||
 | 
						// Enable PNG support:
 | 
				
			||||||
 | 
						_ "image/png"
 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/issue9/identicon"
 | 
						"github.com/issue9/identicon"
 | 
				
			||||||
 | 
						"github.com/nfnt/resize"
 | 
				
			||||||
 | 
						"github.com/oliamb/cutter"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AvatarSize returns avatar's size
 | 
					// AvatarSize returns avatar's size
 | 
				
			||||||
@@ -42,3 +49,46 @@ func RandomImageSize(size int, data []byte) (image.Image, error) {
 | 
				
			|||||||
func RandomImage(data []byte) (image.Image, error) {
 | 
					func RandomImage(data []byte) (image.Image, error) {
 | 
				
			||||||
	return RandomImageSize(AvatarSize, data)
 | 
						return RandomImageSize(AvatarSize, data)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prepare accepts a byte slice as input, validates it contains an image of an
 | 
				
			||||||
 | 
					// acceptable format, and crops and resizes it appropriately.
 | 
				
			||||||
 | 
					func Prepare(data []byte) (*image.Image, error) {
 | 
				
			||||||
 | 
						imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("DecodeConfig: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if imgCfg.Width > setting.AvatarMaxWidth {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.AvatarMaxWidth)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if imgCfg.Height > setting.AvatarMaxHeight {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.AvatarMaxHeight)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						img, _, err := image.Decode(bytes.NewReader(data))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Decode: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if imgCfg.Width != imgCfg.Height {
 | 
				
			||||||
 | 
							var newSize, ax, ay int
 | 
				
			||||||
 | 
							if imgCfg.Width > imgCfg.Height {
 | 
				
			||||||
 | 
								newSize = imgCfg.Height
 | 
				
			||||||
 | 
								ax = (imgCfg.Width - imgCfg.Height) / 2
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								newSize = imgCfg.Width
 | 
				
			||||||
 | 
								ay = (imgCfg.Height - imgCfg.Width) / 2
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							img, err = cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
								Width:  newSize,
 | 
				
			||||||
 | 
								Height: newSize,
 | 
				
			||||||
 | 
								Anchor: image.Point{ax, ay},
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						img = resize.Resize(AvatarSize, AvatarSize, img, resize.NearestNeighbor)
 | 
				
			||||||
 | 
						return &img, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,11 @@
 | 
				
			|||||||
package avatar
 | 
					package avatar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,3 +20,49 @@ func Test_RandomImage(t *testing.T) {
 | 
				
			|||||||
	_, err = RandomImageSize(0, []byte("gogs@local"))
 | 
						_, err = RandomImageSize(0, []byte("gogs@local"))
 | 
				
			||||||
	assert.Error(t, err)
 | 
						assert.Error(t, err)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_PrepareWithPNG(t *testing.T) {
 | 
				
			||||||
 | 
						setting.AvatarMaxWidth = 4096
 | 
				
			||||||
 | 
						setting.AvatarMaxHeight = 4096
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile("testdata/avatar.png")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						imgPtr, err := Prepare(data)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, 290, (*imgPtr).Bounds().Max.X)
 | 
				
			||||||
 | 
						assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_PrepareWithJPEG(t *testing.T) {
 | 
				
			||||||
 | 
						setting.AvatarMaxWidth = 4096
 | 
				
			||||||
 | 
						setting.AvatarMaxHeight = 4096
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile("testdata/avatar.jpeg")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						imgPtr, err := Prepare(data)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assert.Equal(t, 290, (*imgPtr).Bounds().Max.X)
 | 
				
			||||||
 | 
						assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_PrepareWithInvalidImage(t *testing.T) {
 | 
				
			||||||
 | 
						setting.AvatarMaxWidth = 5
 | 
				
			||||||
 | 
						setting.AvatarMaxHeight = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err := Prepare([]byte{})
 | 
				
			||||||
 | 
						assert.EqualError(t, err, "DecodeConfig: image: unknown format")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func Test_PrepareWithInvalidImageSize(t *testing.T) {
 | 
				
			||||||
 | 
						setting.AvatarMaxWidth = 5
 | 
				
			||||||
 | 
						setting.AvatarMaxHeight = 5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile("testdata/avatar.png")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, err = Prepare(data)
 | 
				
			||||||
 | 
						assert.EqualError(t, err, "Image width is too large: 10 > 5")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								modules/avatar/testdata/avatar.jpeg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								modules/avatar/testdata/avatar.jpeg
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 521 B  | 
							
								
								
									
										
											BIN
										
									
								
								modules/avatar/testdata/avatar.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								modules/avatar/testdata/avatar.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 159 B  | 
							
								
								
									
										22
									
								
								vendor/github.com/oliamb/cutter/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/oliamb/cutter/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					# Compiled Object files, Static and Dynamic libs (Shared Objects)
 | 
				
			||||||
 | 
					*.o
 | 
				
			||||||
 | 
					*.a
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Folders
 | 
				
			||||||
 | 
					_obj
 | 
				
			||||||
 | 
					_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Architecture specific extensions/prefixes
 | 
				
			||||||
 | 
					*.[568vq]
 | 
				
			||||||
 | 
					[568vq].out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*.cgo1.go
 | 
				
			||||||
 | 
					*.cgo2.c
 | 
				
			||||||
 | 
					_cgo_defun.c
 | 
				
			||||||
 | 
					_cgo_gotypes.go
 | 
				
			||||||
 | 
					_cgo_export.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_testmain.go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
							
								
								
									
										6
									
								
								vendor/github.com/oliamb/cutter/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/oliamb/cutter/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					language: go
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go:
 | 
				
			||||||
 | 
					  - 1.0
 | 
				
			||||||
 | 
					  - 1.1
 | 
				
			||||||
 | 
					  - tip
 | 
				
			||||||
							
								
								
									
										20
									
								
								vendor/github.com/oliamb/cutter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/oliamb/cutter/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2014 Olivier Amblet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy of
 | 
				
			||||||
 | 
					this software and associated documentation files (the "Software"), to deal in
 | 
				
			||||||
 | 
					the Software without restriction, including without limitation the rights to
 | 
				
			||||||
 | 
					use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 | 
				
			||||||
 | 
					the Software, and to permit persons to whom the Software is furnished to do so,
 | 
				
			||||||
 | 
					subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 | 
				
			||||||
 | 
					FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 | 
				
			||||||
 | 
					COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 | 
				
			||||||
 | 
					IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 | 
				
			||||||
 | 
					CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | 
				
			||||||
							
								
								
									
										107
									
								
								vendor/github.com/oliamb/cutter/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								vendor/github.com/oliamb/cutter/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					Cutter
 | 
				
			||||||
 | 
					======
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A Go library to crop images.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://travis-ci.org/oliamb/cutter)
 | 
				
			||||||
 | 
					[](https://godoc.org/github.com/oliamb/cutter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Cutter was initially developped to be able
 | 
				
			||||||
 | 
					to crop image resized using github.com/nfnt/resize.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Usage
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Read the doc on https://godoc.org/github.com/oliamb/cutter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Import package with
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					import "github.com/oliamb/cutter"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Package cutter provides a function to crop image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By default, the original image will be cropped at the
 | 
				
			||||||
 | 
					given size from the top left corner.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
					  Width:  250,
 | 
				
			||||||
 | 
					  Height: 500,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Most of the time, the cropped image will share some memory
 | 
				
			||||||
 | 
					with the original, so it should be used read only. You must
 | 
				
			||||||
 | 
					ask explicitely for a copy if nedded.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
					  Width:  250,
 | 
				
			||||||
 | 
					  Height: 500,
 | 
				
			||||||
 | 
					  Options: cutter.Copy,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is possible to specify the top left position:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
					  Width:  250,
 | 
				
			||||||
 | 
					  Height: 500,
 | 
				
			||||||
 | 
					  Anchor: image.Point{100, 100},
 | 
				
			||||||
 | 
					  Mode:   cutter.TopLeft, // optional, default value
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Anchor property can represents the center of the cropped image
 | 
				
			||||||
 | 
					instead of the top left corner:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
					  Width: 250,
 | 
				
			||||||
 | 
					  Height: 500,
 | 
				
			||||||
 | 
					  Mode: cutter.Centered,
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The default crop use the specified dimension, but it is possible
 | 
				
			||||||
 | 
					to use Width and Heigth as a ratio instead. In this case,
 | 
				
			||||||
 | 
					the resulting image will be as big as possible to fit the asked ratio
 | 
				
			||||||
 | 
					from the anchor position.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```go
 | 
				
			||||||
 | 
					croppedImg, err := cutter.Crop(baseImage, cutter.Config{
 | 
				
			||||||
 | 
					  Width: 4,
 | 
				
			||||||
 | 
					  Height: 3,
 | 
				
			||||||
 | 
					  Mode: cutter.Centered,
 | 
				
			||||||
 | 
					  Options: cutter.Ratio&cutter.Copy, // Copy is useless here
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					About resize
 | 
				
			||||||
 | 
					------------
 | 
				
			||||||
 | 
					This lib only manage crop and won't resize image, but it works great in combination with [github.com/nfnt/resize](https://github.com/nfnt/resize)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Contributing
 | 
				
			||||||
 | 
					------------
 | 
				
			||||||
 | 
					I'd love to see your contributions to Cutter. If you'd like to hack on it: 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- fork the project,
 | 
				
			||||||
 | 
					- hack on it,
 | 
				
			||||||
 | 
					- ensure tests pass,
 | 
				
			||||||
 | 
					- make a pull request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you plan to modify the API, let's disscuss it first.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensing
 | 
				
			||||||
 | 
					---------
 | 
				
			||||||
 | 
					MIT License, Please see the file called LICENSE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Credits
 | 
				
			||||||
 | 
					-------
 | 
				
			||||||
 | 
					Test Picture: Gopher picture from Heidi Schuyt, http://www.flickr.com/photos/hschuyt/7674222278/,
 | 
				
			||||||
 | 
					© copyright Creative Commons(http://creativecommons.org/licenses/by-nc-sa/2.0/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thanks to Urturn(http://www.urturn.com) for the time allocated to develop the library.
 | 
				
			||||||
							
								
								
									
										192
									
								
								vendor/github.com/oliamb/cutter/cutter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								vendor/github.com/oliamb/cutter/cutter.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,192 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Package cutter provides a function to crop image.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					By default, the original image will be cropped at the
 | 
				
			||||||
 | 
					given size from the top left corner.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
							  Width:  250,
 | 
				
			||||||
 | 
							  Height: 500,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Most of the time, the cropped image will share some memory
 | 
				
			||||||
 | 
					with the original, so it should be used read only. You must
 | 
				
			||||||
 | 
					ask explicitely for a copy if nedded.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
					      Width:  250,
 | 
				
			||||||
 | 
					      Height: 500,
 | 
				
			||||||
 | 
					      Options: Copy,
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is possible to specify the top left position:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
							  Width:  250,
 | 
				
			||||||
 | 
							  Height: 500,
 | 
				
			||||||
 | 
							  Anchor: image.Point{100, 100},
 | 
				
			||||||
 | 
							  Mode:   TopLeft, // optional, default value
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The Anchor property can represents the center of the cropped image
 | 
				
			||||||
 | 
					instead of the top left corner:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							croppedImg, err := cutter.Crop(img, cutter.Config{
 | 
				
			||||||
 | 
							  Width: 250,
 | 
				
			||||||
 | 
							  Height: 500,
 | 
				
			||||||
 | 
							  Mode: Centered,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The default crop use the specified dimension, but it is possible
 | 
				
			||||||
 | 
					to use Width and Heigth as a ratio instead. In this case,
 | 
				
			||||||
 | 
					the resulting image will be as big as possible to fit the asked ratio
 | 
				
			||||||
 | 
					from the anchor position.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							croppedImg, err := cutter.Crop(baseImage, cutter.Config{
 | 
				
			||||||
 | 
							  Width: 4,
 | 
				
			||||||
 | 
							  Height: 3,
 | 
				
			||||||
 | 
							  Mode: Centered,
 | 
				
			||||||
 | 
							  Options: Ratio,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					package cutter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"image"
 | 
				
			||||||
 | 
						"image/draw"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Config is used to defined
 | 
				
			||||||
 | 
					// the way the crop should be realized.
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						Width, Height int
 | 
				
			||||||
 | 
						Anchor        image.Point // The Anchor Point in the source image
 | 
				
			||||||
 | 
						Mode          AnchorMode  // Which point in the resulting image the Anchor Point is referring to
 | 
				
			||||||
 | 
						Options       Option
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AnchorMode is an enumeration of the position an anchor can represent.
 | 
				
			||||||
 | 
					type AnchorMode int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// TopLeft defines the Anchor Point
 | 
				
			||||||
 | 
						// as the top left of the cropped picture.
 | 
				
			||||||
 | 
						TopLeft AnchorMode = iota
 | 
				
			||||||
 | 
						// Centered defines the Anchor Point
 | 
				
			||||||
 | 
						// as the center of the cropped picture.
 | 
				
			||||||
 | 
						Centered = iota
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Option flags to modify the way the crop is done.
 | 
				
			||||||
 | 
					type Option int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// Ratio flag is use when Width and Height
 | 
				
			||||||
 | 
						// must be used to compute a ratio rather
 | 
				
			||||||
 | 
						// than absolute size in pixels.
 | 
				
			||||||
 | 
						Ratio Option = 1 << iota
 | 
				
			||||||
 | 
						// Copy flag is used to enforce the function
 | 
				
			||||||
 | 
						// to retrieve a copy of the selected pixels.
 | 
				
			||||||
 | 
						// This disable the use of SubImage method
 | 
				
			||||||
 | 
						// to compute the result.
 | 
				
			||||||
 | 
						Copy = 1 << iota
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// An interface that is
 | 
				
			||||||
 | 
					// image.Image + SubImage method.
 | 
				
			||||||
 | 
					type subImageSupported interface {
 | 
				
			||||||
 | 
						SubImage(r image.Rectangle) image.Image
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Crop retrieves an image that is a
 | 
				
			||||||
 | 
					// cropped copy of the original img.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// The crop is made given the informations provided in config.
 | 
				
			||||||
 | 
					func Crop(img image.Image, c Config) (image.Image, error) {
 | 
				
			||||||
 | 
						maxBounds := c.maxBounds(img.Bounds())
 | 
				
			||||||
 | 
						size := c.computeSize(maxBounds, image.Point{c.Width, c.Height})
 | 
				
			||||||
 | 
						cr := c.computedCropArea(img.Bounds(), size)
 | 
				
			||||||
 | 
						cr = img.Bounds().Intersect(cr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if c.Options&Copy == Copy {
 | 
				
			||||||
 | 
							return cropWithCopy(img, cr)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if dImg, ok := img.(subImageSupported); ok {
 | 
				
			||||||
 | 
							return dImg.SubImage(cr), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return cropWithCopy(img, cr)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func cropWithCopy(img image.Image, cr image.Rectangle) (image.Image, error) {
 | 
				
			||||||
 | 
						result := image.NewRGBA(cr)
 | 
				
			||||||
 | 
						draw.Draw(result, cr, img, cr.Min, draw.Src)
 | 
				
			||||||
 | 
						return result, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c Config) maxBounds(bounds image.Rectangle) (r image.Rectangle) {
 | 
				
			||||||
 | 
						if c.Mode == Centered {
 | 
				
			||||||
 | 
							anchor := c.centeredMin(bounds)
 | 
				
			||||||
 | 
							w := min(anchor.X-bounds.Min.X, bounds.Max.X-anchor.X)
 | 
				
			||||||
 | 
							h := min(anchor.Y-bounds.Min.Y, bounds.Max.Y-anchor.Y)
 | 
				
			||||||
 | 
							r = image.Rect(anchor.X-w, anchor.Y-h, anchor.X+w, anchor.Y+h)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							r = image.Rect(c.Anchor.X, c.Anchor.Y, bounds.Max.X, bounds.Max.Y)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// computeSize retrieve the effective size of the cropped image.
 | 
				
			||||||
 | 
					// It is defined by Height, Width, and Ratio option.
 | 
				
			||||||
 | 
					func (c Config) computeSize(bounds image.Rectangle, ratio image.Point) (p image.Point) {
 | 
				
			||||||
 | 
						if c.Options&Ratio == Ratio {
 | 
				
			||||||
 | 
							// Ratio option is on, so we take the biggest size available that fit the given ratio.
 | 
				
			||||||
 | 
							if float64(ratio.X)/float64(bounds.Dx()) > float64(ratio.Y)/float64(bounds.Dy()) {
 | 
				
			||||||
 | 
								p = image.Point{bounds.Dx(), (bounds.Dx() / ratio.X) * ratio.Y}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								p = image.Point{(bounds.Dy() / ratio.Y) * ratio.X, bounds.Dy()}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							p = image.Point{ratio.X, ratio.Y}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// computedCropArea retrieve the theorical crop area.
 | 
				
			||||||
 | 
					// It is defined by Height, Width, Mode and
 | 
				
			||||||
 | 
					func (c Config) computedCropArea(bounds image.Rectangle, size image.Point) (r image.Rectangle) {
 | 
				
			||||||
 | 
						min := bounds.Min
 | 
				
			||||||
 | 
						switch c.Mode {
 | 
				
			||||||
 | 
						case Centered:
 | 
				
			||||||
 | 
							rMin := c.centeredMin(bounds)
 | 
				
			||||||
 | 
							r = image.Rect(rMin.X-size.X/2, rMin.Y-size.Y/2, rMin.X-size.X/2+size.X, rMin.Y-size.Y/2+size.Y)
 | 
				
			||||||
 | 
						default: // TopLeft
 | 
				
			||||||
 | 
							rMin := image.Point{min.X + c.Anchor.X, min.Y + c.Anchor.Y}
 | 
				
			||||||
 | 
							r = image.Rect(rMin.X, rMin.Y, rMin.X+size.X, rMin.Y+size.Y)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *Config) centeredMin(bounds image.Rectangle) (rMin image.Point) {
 | 
				
			||||||
 | 
						if c.Anchor.X == 0 && c.Anchor.Y == 0 {
 | 
				
			||||||
 | 
							rMin = image.Point{
 | 
				
			||||||
 | 
								X: bounds.Dx() / 2,
 | 
				
			||||||
 | 
								Y: bounds.Dy() / 2,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							rMin = image.Point{
 | 
				
			||||||
 | 
								X: c.Anchor.X,
 | 
				
			||||||
 | 
								Y: c.Anchor.Y,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func min(a, b int) (r int) {
 | 
				
			||||||
 | 
						if a < b {
 | 
				
			||||||
 | 
							r = a
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							r = b
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							@@ -261,6 +261,8 @@ github.com/mschoch/smat
 | 
				
			|||||||
github.com/msteinert/pam
 | 
					github.com/msteinert/pam
 | 
				
			||||||
# github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
 | 
					# github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5
 | 
				
			||||||
github.com/nfnt/resize
 | 
					github.com/nfnt/resize
 | 
				
			||||||
 | 
					# github.com/oliamb/cutter v0.2.2
 | 
				
			||||||
 | 
					github.com/oliamb/cutter
 | 
				
			||||||
# github.com/pelletier/go-buffruneio v0.2.0
 | 
					# github.com/pelletier/go-buffruneio v0.2.0
 | 
				
			||||||
github.com/pelletier/go-buffruneio
 | 
					github.com/pelletier/go-buffruneio
 | 
				
			||||||
# github.com/philhofer/fwd v1.0.0
 | 
					# github.com/philhofer/fwd v1.0.0
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user