├── .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 | [![Go Reference](https://pkg.go.dev/badge/github.com/esimov/stackblur-go.svg)](https://pkg.go.dev/github.com/esimov/stackblur-go) 4 | [![build](https://github.com/esimov/stackblur-go/actions/workflows/build.yml/badge.svg)](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 | --------------------------------------------------------------------------------