|
|
@ -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 |
|
|
|
} |