SandpointsGitHook/vendor/github.com/disintegration/gift/gift.go

215 lines
5.6 KiB
Go

/*
Package gift provides a set of useful image processing filters.
Basic usage:
// 1. Create a new filter list and add some filters.
g := gift.New(
gift.Resize(800, 0, gift.LanczosResampling),
gift.UnsharpMask(1, 1, 0),
)
// 2. Create a new image of the corresponding size.
// dst is a new target image, src is the original image.
dst := image.NewRGBA(g.Bounds(src.Bounds()))
// 3. Use the Draw func to apply the filters to src and store the result in dst.
g.Draw(dst, src)
*/
package gift
import (
"image"
"image/draw"
)
// Filter is an image processing filter.
type Filter interface {
// Draw applies the filter to the src image and outputs the result to the dst image.
Draw(dst draw.Image, src image.Image, options *Options)
// Bounds calculates the appropriate bounds of an image after applying the filter.
Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle)
}
// Options is the parameters passed to image processing filters.
type Options struct {
Parallelization bool
}
var defaultOptions = Options{
Parallelization: true,
}
// GIFT is a list of image processing filters.
type GIFT struct {
Filters []Filter
Options Options
}
// New creates a new filter list and initializes it with the given slice of filters.
func New(filters ...Filter) *GIFT {
return &GIFT{
Filters: filters,
Options: defaultOptions,
}
}
// SetParallelization enables or disables the image processing parallelization.
// Parallelization is enabled by default.
func (g *GIFT) SetParallelization(isEnabled bool) {
g.Options.Parallelization = isEnabled
}
// Parallelization returns the current state of parallelization option.
func (g *GIFT) Parallelization() bool {
return g.Options.Parallelization
}
// Add appends the given filters to the list of filters.
func (g *GIFT) Add(filters ...Filter) {
g.Filters = append(g.Filters, filters...)
}
// Empty removes all the filters from the list.
func (g *GIFT) Empty() {
g.Filters = []Filter{}
}
// Bounds calculates the appropriate bounds for the result image after applying all the added filters.
// Parameter srcBounds is the bounds of the source image.
//
// Example:
//
// src := image.NewRGBA(image.Rect(0, 0, 100, 200))
// g := gift.New(gift.Rotate90())
//
// // calculate image bounds after applying rotation and create a new image of that size.
// dst := image.NewRGBA(g.Bounds(src.Bounds())) // dst bounds: (0, 0, 200, 100)
//
//
func (g *GIFT) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
b := srcBounds
for _, f := range g.Filters {
b = f.Bounds(b)
}
dstBounds = b
return
}
// Draw applies all the added filters to the src image and outputs the result to the dst image.
func (g *GIFT) Draw(dst draw.Image, src image.Image) {
if len(g.Filters) == 0 {
copyimage(dst, src, &g.Options)
return
}
first, last := 0, len(g.Filters)-1
var tmpIn image.Image
var tmpOut draw.Image
for i, f := range g.Filters {
if i == first {
tmpIn = src
} else {
tmpIn = tmpOut
}
if i == last {
tmpOut = dst
} else {
tmpOut = createTempImage(f.Bounds(tmpIn.Bounds()))
}
f.Draw(tmpOut, tmpIn, &g.Options)
}
}
// Operator is an image composition operator.
type Operator int
// Composition operators.
const (
CopyOperator Operator = iota
OverOperator
)
// DrawAt applies all the added filters to the src image and outputs the result to the dst image
// at the specified position pt using the specified composition operator op.
func (g *GIFT) DrawAt(dst draw.Image, src image.Image, pt image.Point, op Operator) {
switch op {
case OverOperator:
tb := g.Bounds(src.Bounds())
tb = tb.Sub(tb.Min).Add(pt)
tmp := createTempImage(tb)
g.Draw(tmp, src)
pixGetterDst := newPixelGetter(dst)
pixGetterTmp := newPixelGetter(tmp)
pixSetterDst := newPixelSetter(dst)
ib := tb.Intersect(dst.Bounds())
parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(start, stop int) {
for y := start; y < stop; y++ {
for x := ib.Min.X; x < ib.Max.X; x++ {
px0 := pixGetterDst.getPixel(x, y)
px1 := pixGetterTmp.getPixel(x, y)
c1 := px1.a
c0 := (1 - c1) * px0.a
cs := c0 + c1
c0 /= cs
c1 /= cs
r := px0.r*c0 + px1.r*c1
g := px0.g*c0 + px1.g*c1
b := px0.b*c0 + px1.b*c1
a := px0.a + px1.a*(1-px0.a)
pixSetterDst.setPixel(x, y, pixel{r, g, b, a})
}
}
})
default:
if pt.Eq(dst.Bounds().Min) {
g.Draw(dst, src)
return
}
if subimg, ok := getSubImage(dst, pt); ok {
g.Draw(subimg, src)
return
}
tb := g.Bounds(src.Bounds())
tb = tb.Sub(tb.Min).Add(pt)
tmp := createTempImage(tb)
g.Draw(tmp, src)
pixGetter := newPixelGetter(tmp)
pixSetter := newPixelSetter(dst)
ib := tb.Intersect(dst.Bounds())
parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(start, stop int) {
for y := start; y < stop; y++ {
for x := ib.Min.X; x < ib.Max.X; x++ {
pixSetter.setPixel(x, y, pixGetter.getPixel(x, y))
}
}
})
}
}
func getSubImage(img draw.Image, pt image.Point) (draw.Image, bool) {
if !pt.In(img.Bounds()) {
return nil, false
}
switch img := img.(type) {
case *image.Gray:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
case *image.Gray16:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
case *image.RGBA:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
case *image.RGBA64:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
case *image.NRGBA:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
case *image.NRGBA64:
return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
default:
return nil, false
}
}