215 lines
5.6 KiB
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
|
|
}
|
|
}
|