1/*
2
3Package gift provides a set of useful image processing filters.
4
5Basic usage:
6
7 // 1. Create a new GIFT and add some filters:
8
9 g := gift.New(
10 gift.Resize(800, 0, gift.LanczosResampling),
11 gift.UnsharpMask(1, 1, 0),
12 )
13
14 // 2. Create a new image of the corresponding size.
15 // dst is a new target image, src is the original image
16
17 dst := image.NewRGBA(g.Bounds(src.Bounds()))
18
19 // 3. Use Draw func to apply the filters to src and store the result in dst:
20
21 g.Draw(dst, src)
22
23*/
24package gift
25
26import (
27 "image"
28 "image/draw"
29)
30
31// Filter is an image processing filter.
32type Filter interface {
33 // Draw applies the filter to the src image and outputs the result to the dst image.
34 Draw(dst draw.Image, src image.Image, options *Options)
35 // Bounds calculates the appropriate bounds of an image after applying the filter.
36 Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle)
37}
38
39// Options is the parameters passed to image processing filters.
40type Options struct {
41 Parallelization bool
42}
43
44var defaultOptions = Options{
45 Parallelization: true,
46}
47
48// GIFT implements a list of filters that can be applied to an image at once.
49type GIFT struct {
50 Filters []Filter
51 Options Options
52}
53
54// New creates a new instance of the filter toolkit and initializes it with the given list of filters.
55func New(filters ...Filter) *GIFT {
56 return &GIFT{
57 Filters: filters,
58 Options: defaultOptions,
59 }
60}
61
62// SetParallelization enables or disables faster image processing using parallel goroutines.
63// Parallelization is enabled by default.
64func (g *GIFT) SetParallelization(isEnabled bool) {
65 g.Options.Parallelization = isEnabled
66}
67
68// Parallelization returns the current state of parallelization option.
69func (g *GIFT) Parallelization() bool {
70 return g.Options.Parallelization
71}
72
73// Add appends the given filters to the list of filters.
74func (g *GIFT) Add(filters ...Filter) {
75 g.Filters = append(g.Filters, filters...)
76}
77
78// Empty removes all the filters from the list.
79func (g *GIFT) Empty() {
80 g.Filters = []Filter{}
81}
82
83// Bounds calculates the appropriate bounds for the result image after applying all the added filters.
84// Parameter srcBounds is the bounds of the source image.
85//
86// Example:
87//
88// src := image.NewRGBA(image.Rect(0, 0, 100, 200))
89// g := gift.New(gift.Rotate90())
90//
91// // calculate image bounds after applying rotation and create a new image of that size.
92// dst := image.NewRGBA(g.Bounds(src.Bounds())) // dst bounds: (0, 0, 200, 100)
93//
94//
95func (g *GIFT) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
96 b := srcBounds
97 for _, f := range g.Filters {
98 b = f.Bounds(b)
99 }
100 dstBounds = b
101 return
102}
103
104// Draw applies all the added filters to the src image and outputs the result to the dst image.
105func (g *GIFT) Draw(dst draw.Image, src image.Image) {
106 if len(g.Filters) == 0 {
107 copyimage(dst, src, &g.Options)
108 return
109 }
110
111 first, last := 0, len(g.Filters)-1
112 var tmpIn image.Image
113 var tmpOut draw.Image
114
115 for i, f := range g.Filters {
116 if i == first {
117 tmpIn = src
118 } else {
119 tmpIn = tmpOut
120 }
121
122 if i == last {
123 tmpOut = dst
124 } else {
125 tmpOut = createTempImage(f.Bounds(tmpIn.Bounds()))
126 }
127
128 f.Draw(tmpOut, tmpIn, &g.Options)
129 }
130}
131
132// Operator is an image composition operator.
133type Operator int
134
135// Composition operators.
136const (
137 CopyOperator Operator = iota
138 OverOperator
139)
140
141// DrawAt applies all the added filters to the src image and outputs the result to the dst image
142// at the specified position pt using the specified composition operator op.
143func (g *GIFT) DrawAt(dst draw.Image, src image.Image, pt image.Point, op Operator) {
144 switch op {
145 case OverOperator:
146 tb := g.Bounds(src.Bounds())
147 tb = tb.Sub(tb.Min).Add(pt)
148 tmp := createTempImage(tb)
149 g.Draw(tmp, src)
150 pixGetterDst := newPixelGetter(dst)
151 pixGetterTmp := newPixelGetter(tmp)
152 pixSetterDst := newPixelSetter(dst)
153 ib := tb.Intersect(dst.Bounds())
154 parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(pmin, pmax int) {
155 for y := pmin; y < pmax; y++ {
156 for x := ib.Min.X; x < ib.Max.X; x++ {
157 px0 := pixGetterDst.getPixel(x, y)
158 px1 := pixGetterTmp.getPixel(x, y)
159 c1 := px1.a
160 c0 := (1 - c1) * px0.a
161 cs := c0 + c1
162 c0 /= cs
163 c1 /= cs
164 r := px0.r*c0 + px1.r*c1
165 g := px0.g*c0 + px1.g*c1
166 b := px0.b*c0 + px1.b*c1
167 a := px0.a + px1.a*(1-px0.a)
168 pixSetterDst.setPixel(x, y, pixel{r, g, b, a})
169 }
170 }
171 })
172
173 default:
174 if pt.Eq(dst.Bounds().Min) {
175 g.Draw(dst, src)
176 return
177 }
178 if subimg, ok := getSubImage(dst, pt); ok {
179 g.Draw(subimg, src)
180 return
181 }
182 tb := g.Bounds(src.Bounds())
183 tb = tb.Sub(tb.Min).Add(pt)
184 tmp := createTempImage(tb)
185 g.Draw(tmp, src)
186 pixGetter := newPixelGetter(tmp)
187 pixSetter := newPixelSetter(dst)
188 ib := tb.Intersect(dst.Bounds())
189 parallelize(g.Options.Parallelization, ib.Min.Y, ib.Max.Y, func(pmin, pmax int) {
190 for y := pmin; y < pmax; y++ {
191 for x := ib.Min.X; x < ib.Max.X; x++ {
192 pixSetter.setPixel(x, y, pixGetter.getPixel(x, y))
193 }
194 }
195 })
196 }
197}
198
199func getSubImage(img draw.Image, pt image.Point) (draw.Image, bool) {
200 if !pt.In(img.Bounds()) {
201 return nil, false
202 }
203 switch img := img.(type) {
204 case *image.Gray:
205 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
206 case *image.Gray16:
207 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
208 case *image.RGBA:
209 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
210 case *image.RGBA64:
211 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
212 case *image.NRGBA:
213 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
214 case *image.NRGBA64:
215 return img.SubImage(image.Rectangle{pt, img.Bounds().Max}).(draw.Image), true
216 default:
217 return nil, false
218 }
219}