diff options
author | Rob Watson <rfwatson@users.noreply.github.com> | 2019-05-25 13:46:14 +0200 |
---|---|---|
committer | Lauris BH <lauris@nix.lv> | 2019-05-25 14:46:14 +0300 |
commit | df2557835b2235b48d1ed979abb1a1d42607e96a (patch) | |
tree | 31ff762fc5c0023f7ddad6f7673b9a210e5021ed /vendor/github.com/oliamb/cutter/cutter.go | |
parent | 5f05aa13e00eb9f8098066c81d2cd916d91e9874 (diff) | |
download | gitea-df2557835b2235b48d1ed979abb1a1d42607e96a.tar.gz gitea-df2557835b2235b48d1ed979abb1a1d42607e96a.zip |
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>
Diffstat (limited to 'vendor/github.com/oliamb/cutter/cutter.go')
-rw-r--r-- | vendor/github.com/oliamb/cutter/cutter.go | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/vendor/github.com/oliamb/cutter/cutter.go b/vendor/github.com/oliamb/cutter/cutter.go new file mode 100644 index 0000000000..29d9d2f758 --- /dev/null +++ b/vendor/github.com/oliamb/cutter/cutter.go @@ -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 +} |