mirror of https://github.com/go-gitea/gitea.git
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>pull/7024/head^2
parent
5f05aa13e0
commit
df2557835b
Binary file not shown.
After Width: | Height: | Size: 521 B |
Binary file not shown.
After Width: | Height: | Size: 159 B |
@ -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
|
@ -0,0 +1,6 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.0
|
||||
- 1.1
|
||||
- tip
|
@ -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.
|
@ -0,0 +1,107 @@
|
||||
Cutter
|
||||
======
|
||||
|
||||
A Go library to crop images.
|
||||
|
||||
[![Build Status](https://travis-ci.org/oliamb/cutter.png?branch=master)](https://travis-ci.org/oliamb/cutter)
|
||||
[![GoDoc](https://godoc.org/github.com/oliamb/cutter?status.png)](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.
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue