├── .github
├── FUNDING.yml
└── workflows
│ └── build.yml
├── .gitignore
├── LICENSE
├── README.md
├── cmd
└── stackblur
│ └── main.go
├── doc.go
├── go.mod
├── image
├── output.png
└── sample.png
└── stackblur.go
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: esimov
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | name: Build
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | go-version: [~1.16, ~1.17]
16 | os: [ubuntu-latest, macos-latest]
17 | runs-on: ${{ matrix.os }}
18 | env:
19 | GO111MODULE: "on"
20 | steps:
21 | - name: Install Go
22 | uses: actions/setup-go@v2
23 | with:
24 | go-version: ${{ matrix.go-version }}
25 |
26 | - name: Cache-Go
27 | uses: actions/cache@v4
28 | with:
29 | path: |
30 | ~/go/pkg/mod # Module download cache
31 | ~/.cache/go-build # Build cache (Linux)
32 | ~/Library/Caches/go-build # Build cache (Mac)
33 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
34 | restore-keys: |
35 | ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
36 |
37 | - name: Checkout code
38 | uses: actions/checkout@v2
39 |
40 | - name: Download Go modules
41 | run: go mod download
42 |
43 | - name: Install project
44 | run: go install
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.jpg
2 | *.jpeg
3 | *.png
4 | *.bmp
5 | *.gif
6 | !/image/*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Endre Simó
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # stackblur-go
2 |
3 | [](https://pkg.go.dev/github.com/esimov/stackblur-go)
4 | [](https://github.com/esimov/stackblur-go/actions/workflows/build.yml)
5 |
6 | Go port of Mario Klingemann's [Stackblur](http://incubator.quasimondo.com/processing/fast_blur_deluxe.php) algorithm.
7 |
8 | Stackblur is a compromise between Gaussian blur and Box blur, but it creates much better looking blurs than Box blur and it is ~7x faster than Gaussian blur.
9 |
10 | Comparing to the Javascript implementation the Go version is at least 50% faster (depending on the image size and blur radius), applied on the same image with the same bluring radius.
11 |
12 | ### Benchmark
13 | Radius | Javascript | Go
14 | -------------|-------------|-------------
15 | 20 | ~15ms | ~7.4ms
16 |
17 | ## Installation
18 |
19 | ```bash
20 | $ go install github.com/esimov/stackblur-go/cmd/stackblur@latest
21 | ```
22 |
23 | #### CLI example
24 |
25 | The provided CLI example supports the following flags:
26 | ```bash
27 | $ stackblur --help
28 |
29 | Usage of stackblur:
30 | -gif
31 | Output Gif
32 | -in string
33 | Source
34 | -out string
35 | Destination
36 | -radius int
37 | Radius (default 20)
38 | ```
39 | The command below will generate the blurred version of the source image.
40 |
41 | ```bash
42 | $ stackblur -in image/sample.png -out image/output.png -radius 10
43 | ```
44 | The cli command supports a `-gif` flag, which if set as true it visualize the bluring process by outputting the result into a gif file.
45 |
46 | ## API
47 |
48 | The usage of the API is very simple: it exposes a single public `Process` function which requires a destination and a source image together with a blur radius. The blured image will be encoded into the destination image.
49 |
50 | ```Go
51 | stackblur.Process(dst, src, blurRadius)
52 | ```
53 |
54 | ## Results
55 |
56 | | Original image | Blurred image |
57 | |:--:|:--:|
58 | |
|
|
59 |
60 |
61 | ## License
62 |
63 | This project is under the MIT License. See the [LICENSE](https://github.com/esimov/stackblur-go/blob/master/LICENSE) file for the full license text.
64 |
--------------------------------------------------------------------------------
/cmd/stackblur/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "image"
7 | "image/color/palette"
8 | "image/draw"
9 | "image/gif"
10 | "image/jpeg"
11 | "image/png"
12 | "log"
13 | "os"
14 | "path"
15 | "path/filepath"
16 | "sort"
17 | "sync"
18 | "time"
19 |
20 | "github.com/esimov/stackblur-go"
21 | )
22 |
23 | var (
24 | source = flag.String("in", "", "Source")
25 | destination = flag.String("out", "", "Destination")
26 | radius = flag.Int("radius", 20, "Radius")
27 | outputGif = flag.Bool("gif", false, "Output Gif")
28 | )
29 |
30 | func main() {
31 | flag.Parse()
32 |
33 | if len(*source) == 0 || len(*destination) == 0 {
34 | log.Fatal("Usage: stackblur -in input.jpg -out out.jpg")
35 | }
36 |
37 | var imgs = make([]image.Image, *radius)
38 |
39 | img, err := os.Open(*source)
40 | if err != nil {
41 | log.Fatalf("could not open the source file: %v", err)
42 | }
43 |
44 | defer func() {
45 | if err := img.Close(); err != nil {
46 | log.Fatalf("error closing the opened file: %v", err)
47 | }
48 | }()
49 |
50 | src, _, err := image.Decode(img)
51 | if err != nil {
52 | log.Fatalf("could not decode the source image: %v", err)
53 | }
54 |
55 | wg := &sync.WaitGroup{}
56 | start := time.Now()
57 |
58 | if *outputGif {
59 | wg.Add(*radius)
60 |
61 | for i := 0; i < *radius; i++ {
62 | go func(idx int) {
63 | dst := image.NewNRGBA(src.Bounds())
64 | err := stackblur.Process(dst, src, uint32(idx+1))
65 | if err != nil {
66 | log.Fatal(err)
67 | }
68 | fmt.Printf("frame %d/%d\n", idx, *radius)
69 | imgs[idx] = dst
70 |
71 | wg.Done()
72 | }(i)
73 | }
74 | wg.Wait()
75 |
76 | sort.Slice(imgs, func(i, j int) bool { return i < j })
77 |
78 | fmt.Printf("encoding GIF file...\n")
79 |
80 | dest := path.Dir(*destination) + "/" + "output.gif"
81 | if err := encodeGIF(imgs, dest); err != nil {
82 | log.Fatal(err)
83 | }
84 | } else {
85 | dst := image.NewNRGBA(src.Bounds())
86 | err := stackblur.Process(dst, src, uint32(*radius))
87 | if err != nil {
88 | log.Fatal(err)
89 | }
90 | if err := generateImage(*destination, dst); err != nil {
91 | log.Fatalf("error generating the blurred image: %v", err)
92 | }
93 | }
94 | end := time.Since(start)
95 |
96 | fmt.Printf("Generated in: %.2fs\n", end.Seconds())
97 | }
98 |
99 | // encodeGIF encodes the generated output into a gif file
100 | func encodeGIF(imgs []image.Image, path string) error {
101 | // load static image and construct output gif file
102 | g := new(gif.GIF)
103 | for _, src := range imgs {
104 | dst := image.NewPaletted(src.Bounds(), palette.Plan9)
105 | draw.Draw(dst, src.Bounds(), src, image.Point{}, draw.Src)
106 | g.Image = append(g.Image, dst)
107 | g.Delay = append(g.Delay, 0)
108 | }
109 | f, err := os.Create(path)
110 | if err != nil {
111 | return err
112 | }
113 |
114 | defer func() {
115 | if err := f.Close(); err != nil {
116 | log.Fatalf("error closing the opened file: %v", err)
117 | }
118 | }()
119 |
120 | return gif.EncodeAll(f, g)
121 | }
122 |
123 | // generateImage generates the image type depending on the provided extension
124 | func generateImage(dst string, img image.Image) error {
125 | out, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR, 0755)
126 | if err != nil {
127 | return err
128 | }
129 | defer func() {
130 | if err := out.Close(); err != nil {
131 | log.Fatalf("error closing the opened file: %v", err)
132 | }
133 | }()
134 |
135 | ext := filepath.Ext(out.Name())
136 |
137 | switch ext {
138 | case ".jpg", ".jpeg":
139 | if err = jpeg.Encode(out, img, &jpeg.Options{Quality: 100}); err != nil {
140 | return err
141 | }
142 | case ".png":
143 | if err = png.Encode(out, img); err != nil {
144 | return err
145 | }
146 | }
147 |
148 | return nil
149 | }
150 |
--------------------------------------------------------------------------------
/doc.go:
--------------------------------------------------------------------------------
1 | /*
2 | stackblur-go is a Go port of the Stackblur algorithm.
3 |
4 | Stackblur is a compromise between Gaussian blur and Box blur, but it creates much better looking blurs than Box blur and it is ~7x faster than Gaussian blur.
5 |
6 | The usage of the API is very simple: it exposes a single public `Process` function which requires a destination and a source image together with a blur radius. The blured image will be encoded into the destination image.
7 |
8 | func Process(dst, src image.Image, radius uint32) error
9 |
10 | Below is a very simple example of how you can use this package.
11 |
12 | package main
13 |
14 | import (
15 | "image"
16 | "image/jpeg"
17 | "log"
18 | "os"
19 |
20 | "github.com/esimov/stackblur-go"
21 | )
22 |
23 | func main() {
24 | var radius uint32 = 5
25 | f, err := os.Open("sample.png")
26 | if err != nil {
27 | log.Fatalf("could not open source file: %v", err)
28 | }
29 | defer f.Close()
30 |
31 | src, _, err := image.Decode(f)
32 | if err != nil {
33 | log.Fatalf("could not decode source file: %v", err)
34 | }
35 |
36 | dst := image.NewNRGBA(src.Bounds())
37 | err = stackblur.Process(dst, src, radius)
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 |
42 | output, err := os.OpenFile("output.jpg", os.O_CREATE|os.O_RDWR, 0755)
43 | if err != nil {
44 | log.Fatalf("could not open destination file: %v", err)
45 | }
46 | defer output.Close()
47 |
48 | if err = jpeg.Encode(output, dst, &jpeg.Options{Quality: 100}); err != nil {
49 | log.Fatalf("could not encode destination image: %v", err)
50 | }
51 | }
52 | */
53 | package stackblur
54 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/esimov/stackblur-go
2 |
3 | go 1.24
4 |
5 | // Unstable API
6 | retract (
7 | [v1.0.0, v1.0.2]
8 | )
9 |
--------------------------------------------------------------------------------
/image/output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/stackblur-go/7661dcb66879ae0bcc0938b7215ae8a70afcb5b6/image/output.png
--------------------------------------------------------------------------------
/image/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/esimov/stackblur-go/7661dcb66879ae0bcc0938b7215ae8a70afcb5b6/image/sample.png
--------------------------------------------------------------------------------
/stackblur.go:
--------------------------------------------------------------------------------
1 | // Go implementation of the StackBlur algorithm
2 | // http://incubator.quasimondo.com/processing/fast_blur_deluxe.php
3 |
4 | package stackblur
5 |
6 | import (
7 | "errors"
8 | "image"
9 | "image/color"
10 | )
11 |
12 | // blurStack is a linked list containing the color value and a pointer to the next struct.
13 | type blurStack struct {
14 | r, g, b, a uint32
15 | next *blurStack
16 | }
17 |
18 | var mulTable = []uint32{
19 | 512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
20 | 454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
21 | 482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
22 | 437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
23 | 497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
24 | 320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
25 | 446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
26 | 329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
27 | 505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
28 | 399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
29 | 324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
30 | 268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
31 | 451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
32 | 385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
33 | 332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
34 | 289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259,
35 | }
36 |
37 | var shgTable = []uint32{
38 | 9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
39 | 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
40 | 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
41 | 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
42 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
43 | 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
44 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
45 | 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
46 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
47 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
48 | 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
49 | 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
50 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
51 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
52 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
53 | 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
54 | }
55 |
56 | // Process takes the source image and returns it's blurred version by applying the blur radius defined as parameter. The destination image must be a image.NRGBA.
57 | func Process(dst, src image.Image, radius uint32) error {
58 | // Limit the maximum blur radius to 255 to avoid overflowing the multable.
59 | if int(radius) >= len(mulTable) {
60 | radius = uint32(len(mulTable) - 1)
61 | }
62 |
63 | if radius < 1 {
64 | return errors.New("blur radius must be greater than 0")
65 | }
66 |
67 | img, ok := dst.(*image.NRGBA)
68 | if !ok {
69 | return errors.New("the destination image must be image.NRGBA")
70 | }
71 |
72 | process(img, src, radius)
73 | return nil
74 | }
75 |
76 | func process(dst *image.NRGBA, src image.Image, radius uint32) {
77 | srcBounds := src.Bounds()
78 | srcMinX := srcBounds.Min.X
79 | srcMinY := srcBounds.Min.Y
80 |
81 | dstBounds := srcBounds.Sub(srcBounds.Min)
82 | dstW := dstBounds.Dx()
83 | dstH := dstBounds.Dy()
84 |
85 | switch src0 := src.(type) {
86 | case *image.NRGBA:
87 | rowSize := srcBounds.Dx() * 4
88 | for dstY := 0; dstY < dstH; dstY++ {
89 | di := src0.PixOffset(0, dstY)
90 | si := src0.PixOffset(srcMinX, srcMinY+dstY)
91 | for dstX := 0; dstX < dstW; dstX++ {
92 | copy(dst.Pix[di:di+rowSize], src0.Pix[si:si+rowSize])
93 | }
94 | }
95 | case *image.YCbCr:
96 | for dstY := 0; dstY < dstH; dstY++ {
97 | di := dst.PixOffset(0, dstY)
98 | for dstX := 0; dstX < dstW; dstX++ {
99 | srcX := srcMinX + dstX
100 | srcY := srcMinY + dstY
101 | siy := src0.YOffset(srcX, srcY)
102 | sic := src0.COffset(srcX, srcY)
103 | r, g, b := color.YCbCrToRGB(src0.Y[siy], src0.Cb[sic], src0.Cr[sic])
104 | dst.Pix[di+0] = r
105 | dst.Pix[di+1] = g
106 | dst.Pix[di+2] = b
107 | dst.Pix[di+3] = 0xff
108 | di += 4
109 | }
110 | }
111 | default:
112 | for dstY := 0; dstY < dstH; dstY++ {
113 | di := dst.PixOffset(0, dstY)
114 | for dstX := 0; dstX < dstW; dstX++ {
115 | c := color.NRGBAModel.Convert(src.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
116 | dst.Pix[di+0] = c.R
117 | dst.Pix[di+1] = c.G
118 | dst.Pix[di+2] = c.B
119 | dst.Pix[di+3] = c.A
120 | di += 4
121 | }
122 | }
123 | }
124 |
125 | blurImage(dst, radius)
126 | }
127 |
128 | func blurImage(src *image.NRGBA, radius uint32) {
129 | var (
130 | stackEnd *blurStack
131 | stackIn *blurStack
132 | stackOut *blurStack
133 | )
134 |
135 | var width, height = uint32(src.Bounds().Dx()), uint32(src.Bounds().Dy())
136 | var (
137 | div, widthMinus1, heightMinus1, radiusPlus1, sumFactor uint32
138 | x, y, i, p, yp, yi, yw,
139 | rSum, gSum, bSum, aSum,
140 | rOutSum, gOutSum, bOutSum, aOutSum,
141 | rInSum, gInSum, bInSum, aInSum,
142 | pr, pg, pb, pa uint32
143 | )
144 |
145 | div = radius + radius + 1
146 | widthMinus1 = width - 1
147 | heightMinus1 = height - 1
148 | radiusPlus1 = radius + 1
149 | sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2
150 |
151 | stackStart := new(blurStack)
152 | stack := stackStart
153 |
154 | for i = 1; i < div; i++ {
155 | stack.next = new(blurStack)
156 | stack = stack.next
157 | if i == radiusPlus1 {
158 | stackEnd = stack
159 | }
160 | }
161 | stack.next = stackStart
162 |
163 | mulSum := mulTable[radius]
164 | shgSum := shgTable[radius]
165 |
166 | for y = 0; y < height; y++ {
167 | rInSum, gInSum, bInSum, aInSum, rSum, gSum, bSum, aSum = 0, 0, 0, 0, 0, 0, 0, 0
168 |
169 | pr = uint32(src.Pix[yi])
170 | pg = uint32(src.Pix[yi+1])
171 | pb = uint32(src.Pix[yi+2])
172 | pa = uint32(src.Pix[yi+3])
173 |
174 | rOutSum = radiusPlus1 * pr
175 | gOutSum = radiusPlus1 * pg
176 | bOutSum = radiusPlus1 * pb
177 | aOutSum = radiusPlus1 * pa
178 |
179 | rSum += sumFactor * pr
180 | gSum += sumFactor * pg
181 | bSum += sumFactor * pb
182 | aSum += sumFactor * pa
183 |
184 | stack = stackStart
185 |
186 | for i = 0; i < radiusPlus1; i++ {
187 | stack.r = pr
188 | stack.g = pg
189 | stack.b = pb
190 | stack.a = pa
191 | stack = stack.next
192 | }
193 |
194 | for i = 1; i < radiusPlus1; i++ {
195 | var diff uint32
196 | if widthMinus1 < i {
197 | diff = widthMinus1
198 | } else {
199 | diff = i
200 | }
201 | p = yi + (diff << 2)
202 | pr = uint32(src.Pix[p])
203 | pg = uint32(src.Pix[p+1])
204 | pb = uint32(src.Pix[p+2])
205 | pa = uint32(src.Pix[p+3])
206 |
207 | stack.r = pr
208 | stack.g = pg
209 | stack.b = pb
210 | stack.a = pa
211 |
212 | rSum += stack.r * (radiusPlus1 - i)
213 | gSum += stack.g * (radiusPlus1 - i)
214 | bSum += stack.b * (radiusPlus1 - i)
215 | aSum += stack.a * (radiusPlus1 - i)
216 |
217 | rInSum += pr
218 | gInSum += pg
219 | bInSum += pb
220 | aInSum += pa
221 |
222 | stack = stack.next
223 | }
224 | stackIn = stackStart
225 | stackOut = stackEnd
226 |
227 | for x = 0; x < width; x++ {
228 | pa = (aSum * mulSum) >> shgSum
229 | src.Pix[yi+3] = uint8(pa)
230 |
231 | if pa != 0 {
232 | src.Pix[yi] = uint8((rSum * mulSum) >> shgSum)
233 | src.Pix[yi+1] = uint8((gSum * mulSum) >> shgSum)
234 | src.Pix[yi+2] = uint8((bSum * mulSum) >> shgSum)
235 | } else {
236 | src.Pix[yi] = 0
237 | src.Pix[yi+1] = 0
238 | src.Pix[yi+2] = 0
239 | }
240 |
241 | rSum -= rOutSum
242 | gSum -= gOutSum
243 | bSum -= bOutSum
244 | aSum -= aOutSum
245 |
246 | rOutSum -= stackIn.r
247 | gOutSum -= stackIn.g
248 | bOutSum -= stackIn.b
249 | aOutSum -= stackIn.a
250 |
251 | p = x + radius + 1
252 |
253 | if p > widthMinus1 {
254 | p = widthMinus1
255 | }
256 | p = (yw + p) << 2
257 |
258 | stackIn.r = uint32(src.Pix[p])
259 | stackIn.g = uint32(src.Pix[p+1])
260 | stackIn.b = uint32(src.Pix[p+2])
261 | stackIn.a = uint32(src.Pix[p+3])
262 |
263 | rInSum += stackIn.r
264 | gInSum += stackIn.g
265 | bInSum += stackIn.b
266 | aInSum += stackIn.a
267 |
268 | rSum += rInSum
269 | gSum += gInSum
270 | bSum += bInSum
271 | aSum += aInSum
272 |
273 | stackIn = stackIn.next
274 |
275 | pr = stackOut.r
276 | pg = stackOut.g
277 | pb = stackOut.b
278 | pa = stackOut.a
279 |
280 | rOutSum += pr
281 | gOutSum += pg
282 | bOutSum += pb
283 | aOutSum += pa
284 |
285 | rInSum -= pr
286 | gInSum -= pg
287 | bInSum -= pb
288 | aInSum -= pa
289 |
290 | stackOut = stackOut.next
291 |
292 | yi += 4
293 | }
294 | yw += width
295 | }
296 |
297 | for x = 0; x < width; x++ {
298 | rInSum, gInSum, bInSum, aInSum, rSum, gSum, bSum, aSum = 0, 0, 0, 0, 0, 0, 0, 0
299 |
300 | yi = x << 2
301 | pr = uint32(src.Pix[yi])
302 | pg = uint32(src.Pix[yi+1])
303 | pb = uint32(src.Pix[yi+2])
304 | pa = uint32(src.Pix[yi+3])
305 |
306 | rOutSum = radiusPlus1 * pr
307 | gOutSum = radiusPlus1 * pg
308 | bOutSum = radiusPlus1 * pb
309 | aOutSum = radiusPlus1 * pa
310 |
311 | rSum += sumFactor * pr
312 | gSum += sumFactor * pg
313 | bSum += sumFactor * pb
314 | aSum += sumFactor * pa
315 |
316 | stack = stackStart
317 |
318 | for i = 0; i < radiusPlus1; i++ {
319 | stack.r = pr
320 | stack.g = pg
321 | stack.b = pb
322 | stack.a = pa
323 | stack = stack.next
324 | }
325 |
326 | yp = width
327 |
328 | for i = 1; i <= radius; i++ {
329 | yi = (yp + x) << 2
330 | pr = uint32(src.Pix[yi])
331 | pg = uint32(src.Pix[yi+1])
332 | pb = uint32(src.Pix[yi+2])
333 | pa = uint32(src.Pix[yi+3])
334 |
335 | stack.r = pr
336 | stack.g = pg
337 | stack.b = pb
338 | stack.a = pa
339 |
340 | rSum += stack.r * (radiusPlus1 - i)
341 | gSum += stack.g * (radiusPlus1 - i)
342 | bSum += stack.b * (radiusPlus1 - i)
343 | aSum += stack.a * (radiusPlus1 - i)
344 |
345 | rInSum += pr
346 | gInSum += pg
347 | bInSum += pb
348 | aInSum += pa
349 |
350 | stack = stack.next
351 |
352 | if i < heightMinus1 {
353 | yp += width
354 | }
355 | }
356 |
357 | yi = x
358 | stackIn = stackStart
359 | stackOut = stackEnd
360 |
361 | for y = 0; y < height; y++ {
362 | p = yi << 2
363 | pa = (aSum * mulSum) >> shgSum
364 | src.Pix[p+3] = uint8(pa)
365 |
366 | if pa > 0 {
367 | src.Pix[p] = uint8((rSum * mulSum) >> shgSum)
368 | src.Pix[p+1] = uint8((gSum * mulSum) >> shgSum)
369 | src.Pix[p+2] = uint8((bSum * mulSum) >> shgSum)
370 | } else {
371 | src.Pix[p] = 0
372 | src.Pix[p+1] = 0
373 | src.Pix[p+2] = 0
374 | }
375 |
376 | rSum -= rOutSum
377 | gSum -= gOutSum
378 | bSum -= bOutSum
379 | aSum -= aOutSum
380 |
381 | rOutSum -= stackIn.r
382 | gOutSum -= stackIn.g
383 | bOutSum -= stackIn.b
384 | aOutSum -= stackIn.a
385 |
386 | p = y + radiusPlus1
387 |
388 | if p > heightMinus1 {
389 | p = heightMinus1
390 | }
391 | p = (x + (p * width)) << 2
392 |
393 | stackIn.r = uint32(src.Pix[p])
394 | stackIn.g = uint32(src.Pix[p+1])
395 | stackIn.b = uint32(src.Pix[p+2])
396 | stackIn.a = uint32(src.Pix[p+3])
397 |
398 | rInSum += stackIn.r
399 | gInSum += stackIn.g
400 | bInSum += stackIn.b
401 | aInSum += stackIn.a
402 |
403 | rSum += rInSum
404 | gSum += gInSum
405 | bSum += bInSum
406 | aSum += aInSum
407 |
408 | stackIn = stackIn.next
409 |
410 | pr = stackOut.r
411 | pg = stackOut.g
412 | pb = stackOut.b
413 | pa = stackOut.a
414 |
415 | rOutSum += pr
416 | gOutSum += pg
417 | bOutSum += pb
418 | aOutSum += pa
419 |
420 | rInSum -= pr
421 | gInSum -= pg
422 | bInSum -= pb
423 | aInSum -= pa
424 |
425 | stackOut = stackOut.next
426 |
427 | yi += width
428 | }
429 | }
430 | }
431 |
--------------------------------------------------------------------------------