1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
|
/*
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
}
}
|