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:
Rob Watson 2019-05-25 13:46:14 +02:00 committed by Lauris BH
parent 5f05aa13e0
commit df2557835b
13 changed files with 454 additions and 19 deletions

View file

@ -5,13 +5,20 @@
package avatar
import (
"bytes"
"fmt"
"image"
"image/color/palette"
// Enable PNG support:
_ "image/png"
"math/rand"
"time"
"code.gitea.io/gitea/modules/setting"
"github.com/issue9/identicon"
"github.com/nfnt/resize"
"github.com/oliamb/cutter"
)
// 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) {
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
}

View file

@ -5,8 +5,11 @@
package avatar
import (
"io/ioutil"
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@ -17,3 +20,49 @@ func Test_RandomImage(t *testing.T) {
_, err = RandomImageSize(0, []byte("gogs@local"))
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

BIN
modules/avatar/testdata/avatar.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B