mirror of
https://github.com/esimov/caire.git
synced 2025-10-05 00:32:48 +08:00
Initial commit
This commit is contained in:
136
carver.go
Normal file
136
carver.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
type DPTable struct {
|
||||
width int
|
||||
height int
|
||||
table []float64
|
||||
}
|
||||
|
||||
type Seam []Point
|
||||
|
||||
type Point struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
// Get energy pixel value
|
||||
func (dpt *DPTable) get(x, y int) float64 {
|
||||
px := x + y * dpt.width
|
||||
return dpt.table[px]
|
||||
}
|
||||
|
||||
// Set energy pixel value
|
||||
func (dpt *DPTable) set(x, y int, px float64) {
|
||||
idx := x + y * dpt.width
|
||||
dpt.table[idx] = px
|
||||
}
|
||||
|
||||
// Compute the minimum energy level based on the following logic:
|
||||
// - traverse the image from the second row to the last row
|
||||
// and compute the cumulative minimum energy M for all possible
|
||||
// connected seams for each entry (i, j).
|
||||
//
|
||||
// - the minimum energy level is calculated by summing up the current pixel value
|
||||
// with the minimum pixel value of the neighboring pixels from the previous row.
|
||||
func (dpt *DPTable) ComputeSeams(img *image.NRGBA) []float64 {
|
||||
width, height := img.Bounds().Max.X, img.Bounds().Max.Y
|
||||
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
r, _, _, a := img.At(x, y).RGBA()
|
||||
dpt.set(x, y, float64(r) / float64(a))
|
||||
}
|
||||
}
|
||||
|
||||
computeEnergyLevel := func(x, y int) {
|
||||
var left, middle, right float64
|
||||
left, right = dpt.get(x, y-1), dpt.get(x, y-1)
|
||||
|
||||
// Do not compute edge cases: pixels are far left.
|
||||
if x > 0 {
|
||||
left = dpt.get(x-1, y-1)
|
||||
}
|
||||
middle = dpt.get(x, y-1)
|
||||
// Do not compute edge cases: pixels are far right.
|
||||
if x < width-1 {
|
||||
right = dpt.get(x+1, y-1)
|
||||
}
|
||||
// Obtain the minimum pixel value
|
||||
min := math.Min(math.Min(left, middle), right)
|
||||
dpt.set(x, y, dpt.get(x, y) + min)
|
||||
}
|
||||
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 1; y < height; y++ {
|
||||
computeEnergyLevel(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Println(dpt.table)
|
||||
return dpt.table
|
||||
}
|
||||
|
||||
// Find the lowest vertical energy seam.
|
||||
func (dpt *DPTable) FindLowestEnergySeams() []Point {
|
||||
// Find the lowest cost seam from the energy matrix starting from the last row.
|
||||
var min float64 = math.MaxFloat64
|
||||
var px int
|
||||
seams := make([]Point, 0)
|
||||
|
||||
// Find the lowest seam from the bottom row
|
||||
for x := 0; x < dpt.width; x++ {
|
||||
seam := dpt.get(x, dpt.height-1)
|
||||
if seam < min {
|
||||
min = seam
|
||||
px = x
|
||||
}
|
||||
}
|
||||
seams = append(seams, Point{X: px, Y: dpt.height-1})
|
||||
|
||||
// Walk up in the matrix table,
|
||||
// check the immediate three top pixel seam level and
|
||||
// add add the one which has the lowest cumulative energy.
|
||||
for y := dpt.height-2; y >= 0; y-- {
|
||||
left, center, right := math.MaxFloat64, math.MaxFloat64, math.MaxFloat64
|
||||
// Leftmost seam, no child to the left
|
||||
if px == 0 {
|
||||
right = dpt.get(px+1, y)
|
||||
center = dpt.get(px, y)
|
||||
if right < center {
|
||||
px += 1
|
||||
}
|
||||
// Rightmost seam, no child to the right
|
||||
} else if px == dpt.width-1 {
|
||||
left = dpt.get(px-1, y)
|
||||
center = dpt.get(px, y)
|
||||
if left < center {
|
||||
px -= 1
|
||||
}
|
||||
} else {
|
||||
left = dpt.get(px-1, y)
|
||||
center = dpt.get(px, y)
|
||||
right = dpt.get(px+1, y)
|
||||
min := math.Min(math.Min(left, center), right)
|
||||
|
||||
if min == left {
|
||||
px -= 1
|
||||
} else if min == right {
|
||||
px += 1
|
||||
}
|
||||
}
|
||||
seams = append(seams, Point{X: px, Y: y})
|
||||
}
|
||||
fmt.Println(seams)
|
||||
return seams
|
||||
}
|
||||
|
||||
// Remove image pixels based on energy seams level
|
||||
func (dpt *DPTable) RemoveSeams(seams Seam) {
|
||||
|
||||
}
|
145
cmd/caire/main.go
Normal file
145
cmd/caire/main.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/esimov/caire"
|
||||
)
|
||||
|
||||
var (
|
||||
// Flags
|
||||
source = flag.String("in", "", "Source")
|
||||
destination = flag.String("out", "", "Destination")
|
||||
blurRadius = flag.Int("blur", 1, "Blur radius")
|
||||
sobelThreshold = flag.Int("sobel", 1, "Sobel filter threshold")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if len(*source) == 0 || len(*destination) == 0 {
|
||||
log.Fatal("Usage: caire -in input.jpg -out out.jpg")
|
||||
}
|
||||
|
||||
fs, err := os.Stat(*source)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to open source: %v", err)
|
||||
}
|
||||
|
||||
toProcess := make(map[string]string)
|
||||
|
||||
p := &caire.Processor{
|
||||
BlurRadius: *blurRadius,
|
||||
SobelThreshold: *sobelThreshold,
|
||||
}
|
||||
|
||||
switch mode := fs.Mode(); {
|
||||
case mode.IsDir():
|
||||
// Supported image files.
|
||||
extensions := []string{".jpg", ".png"}
|
||||
|
||||
// Read source directory.
|
||||
files, err := ioutil.ReadDir(*source)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to read dir: %v", err)
|
||||
}
|
||||
|
||||
// Read destination file or directory.
|
||||
dst, err := os.Stat(*destination)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get dir stats: %v", err)
|
||||
}
|
||||
|
||||
// Check if the image destination is a directory or a file.
|
||||
if dst.Mode().IsRegular() {
|
||||
log.Fatal("Please specify a directory as destination!")
|
||||
os.Exit(2)
|
||||
}
|
||||
output, err := filepath.Abs(filepath.Base(*destination))
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to get absolute path: %v", err)
|
||||
}
|
||||
|
||||
// Range over all the image files and save them into a slice.
|
||||
images := []string{}
|
||||
for _, f := range files {
|
||||
ext := filepath.Ext(f.Name())
|
||||
for _, iex := range extensions {
|
||||
if ext == iex {
|
||||
images = append(images, f.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process images from directory.
|
||||
for _, img := range images {
|
||||
// Get the file base name.
|
||||
name := strings.TrimSuffix(img, filepath.Ext(img))
|
||||
dir := strings.TrimRight(*source, "/")
|
||||
out := output + "/" + name + ".png"
|
||||
in := dir + "/" + img
|
||||
|
||||
toProcess[in] = out
|
||||
}
|
||||
|
||||
case mode.IsRegular():
|
||||
toProcess[*source] = *destination
|
||||
}
|
||||
|
||||
for in, out := range toProcess {
|
||||
file, err := os.Open(in)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to open source file: %v", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
s := new(spinner)
|
||||
s.start("Process start...")
|
||||
start := time.Now()
|
||||
_, processErr := p.Process(file, out)
|
||||
s.stop()
|
||||
|
||||
if err == nil {
|
||||
fmt.Printf("\nRescaled in: \x1b[92m%.2fs\n", time.Since(start).Seconds())
|
||||
fmt.Printf("Saved as: %s \x1b[92m✓\n\n", path.Base(out))
|
||||
} else {
|
||||
fmt.Printf("\nError rescaling image: %s: %s", file.Name(), processErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type spinner struct {
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
// Start process
|
||||
func (s *spinner) start(message string) {
|
||||
s.stopChan = make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
for _, r := range `-\|/` {
|
||||
select {
|
||||
case <-s.stopChan:
|
||||
return
|
||||
default:
|
||||
fmt.Printf("\r%s%s %c%s", message, "\x1b[92m", r, "\x1b[39m")
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// End process
|
||||
func (s *spinner) stop() {
|
||||
s.stopChan <- struct{}{}
|
||||
}
|
BIN
cmd/caire/me.png
Normal file
BIN
cmd/caire/me.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 241 KiB |
BIN
cmd/caire/red-ara-parrot.jpg
Executable file
BIN
cmd/caire/red-ara-parrot.jpg
Executable file
Binary file not shown.
After Width: | Height: | Size: 934 KiB |
BIN
cmd/caire/sample-image.jpeg
Normal file
BIN
cmd/caire/sample-image.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 205 KiB |
21
grayscale.go
Normal file
21
grayscale.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Convert image to grayscale.
|
||||
func Grayscale(src *image.NRGBA) *image.NRGBA {
|
||||
dx, dy := src.Bounds().Max.X, src.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(src.Bounds())
|
||||
for x := 0; x < dx; x++ {
|
||||
for y := 0; y < dy; y++ {
|
||||
r, g, b, _ := src.At(x, y).RGBA()
|
||||
lum := float32(r)*0.299 + float32(g)*0.587 + float32(b)*0.114
|
||||
pixel := color.Gray{uint8(lum / 256)}
|
||||
dst.Set(x, y, pixel)
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
111
process.go
Normal file
111
process.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
// Processor options
|
||||
type Processor struct {
|
||||
BlurRadius int
|
||||
SobelThreshold int
|
||||
}
|
||||
|
||||
// Process : Convert image
|
||||
func (p *Processor) Process(file io.Reader, output string) (*os.File, error) {
|
||||
src, _, err := image.Decode(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
width, height := src.Bounds().Dx(), src.Bounds().Dy()
|
||||
ctx := gg.NewContext(width, height)
|
||||
ctx.DrawRectangle(0, 0, float64(width), float64(height))
|
||||
ctx.SetRGBA(1, 1, 1, 1)
|
||||
ctx.Fill()
|
||||
|
||||
img := toNRGBA(src)
|
||||
|
||||
blur := Stackblur(img, uint32(width), uint32(height), uint32(p.BlurRadius))
|
||||
gray := Grayscale(blur)
|
||||
sobel := SobelFilter(gray, float64(p.SobelThreshold))
|
||||
|
||||
dpTable := &DPTable{width, height, make([]float64, width*height)}
|
||||
dpTable.ComputeSeams(sobel)
|
||||
dpTable.FindLowestEnergySeams()
|
||||
|
||||
fq, err := os.Create(output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer fq.Close()
|
||||
|
||||
if err = png.Encode(fq, sobel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fq, err
|
||||
}
|
||||
|
||||
// toNRGBA converts any image type to *image.NRGBA with min-point at (0, 0).
|
||||
func toNRGBA(img image.Image) *image.NRGBA {
|
||||
srcBounds := img.Bounds()
|
||||
if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 {
|
||||
if src0, ok := img.(*image.NRGBA); ok {
|
||||
return src0
|
||||
}
|
||||
}
|
||||
srcMinX := srcBounds.Min.X
|
||||
srcMinY := srcBounds.Min.Y
|
||||
|
||||
dstBounds := srcBounds.Sub(srcBounds.Min)
|
||||
dstW := dstBounds.Dx()
|
||||
dstH := dstBounds.Dy()
|
||||
dst := image.NewNRGBA(dstBounds)
|
||||
|
||||
switch src := img.(type) {
|
||||
case *image.NRGBA:
|
||||
rowSize := srcBounds.Dx() * 4
|
||||
for dstY := 0; dstY < dstH; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
si := src.PixOffset(srcMinX, srcMinY+dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize])
|
||||
}
|
||||
}
|
||||
case *image.YCbCr:
|
||||
for dstY := 0; dstY < dstH; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
srcX := srcMinX + dstX
|
||||
srcY := srcMinY + dstY
|
||||
siy := src.YOffset(srcX, srcY)
|
||||
sic := src.COffset(srcX, srcY)
|
||||
r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic])
|
||||
dst.Pix[di+0] = r
|
||||
dst.Pix[di+1] = g
|
||||
dst.Pix[di+2] = b
|
||||
dst.Pix[di+3] = 0xff
|
||||
di += 4
|
||||
}
|
||||
}
|
||||
default:
|
||||
for dstY := 0; dstY < dstH; dstY++ {
|
||||
di := dst.PixOffset(0, dstY)
|
||||
for dstX := 0; dstX < dstW; dstX++ {
|
||||
c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA)
|
||||
dst.Pix[di+0] = c.R
|
||||
dst.Pix[di+1] = c.G
|
||||
dst.Pix[di+2] = c.B
|
||||
dst.Pix[di+3] = c.A
|
||||
di += 4
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
105
sobel.go
Normal file
105
sobel.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
)
|
||||
|
||||
type kernel [][]int32
|
||||
|
||||
var (
|
||||
kernelX kernel = kernel{
|
||||
{-1, 0, 1},
|
||||
{-2, 0, 2},
|
||||
{-1, 0, 1},
|
||||
}
|
||||
|
||||
kernelY kernel = kernel{
|
||||
{-1, -2, -1},
|
||||
{0, 0, 0},
|
||||
{1, 2, 1},
|
||||
}
|
||||
)
|
||||
|
||||
// Detect image edges.
|
||||
// See https://en.wikipedia.org/wiki/Sobel_operator
|
||||
func SobelFilter(img *image.NRGBA, threshold float64) *image.NRGBA {
|
||||
var sumX, sumY int32
|
||||
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
||||
dst := image.NewNRGBA(img.Bounds())
|
||||
|
||||
// Get 3x3 window of pixels because image data given is just a 1D array of pixels
|
||||
maxPixelOffset := dx*dy + len(kernelX) - 1
|
||||
|
||||
data := getImageData(img)
|
||||
length := len(data) - maxPixelOffset
|
||||
magnitudes := make([]int32, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
// Sum each pixel with the kernel value
|
||||
sumX, sumY = 0, 0
|
||||
for x := 0; x < len(kernelX); x++ {
|
||||
for y := 0; y < len(kernelY); y++ {
|
||||
px := data[i+(dx*y)+x]
|
||||
if len(px) > 0 {
|
||||
r := px[0]
|
||||
// We are using px[0] (i.e. R value) because the image is grayscale anyway
|
||||
sumX += int32(r) * kernelX[y][x]
|
||||
sumY += int32(r) * kernelY[y][x]
|
||||
}
|
||||
}
|
||||
}
|
||||
magnitude := math.Sqrt(float64(sumX*sumX) + float64(sumY*sumY))
|
||||
// Check for pixel color boundaries
|
||||
if magnitude < 0 {
|
||||
magnitude = 0
|
||||
} else if magnitude > 255 {
|
||||
magnitude = 255
|
||||
}
|
||||
|
||||
// Set magnitude to 0 if doesn't exceed threshold, else set to magnitude
|
||||
if magnitude > threshold {
|
||||
magnitudes[i] = int32(magnitude)
|
||||
} else {
|
||||
magnitudes[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
dataLength := dx * dy * 4
|
||||
edges := make([]int32, dataLength)
|
||||
|
||||
// Apply the kernel values.
|
||||
for i := 0; i < dataLength; i++ {
|
||||
if i%4 != 0 {
|
||||
m := magnitudes[i/4]
|
||||
if m != 0 {
|
||||
edges[i-1] = m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate the new image with the sobel filter applied.
|
||||
for idx := 0; idx < len(edges); idx += 4 {
|
||||
dst.Pix[idx] = uint8(edges[idx])
|
||||
dst.Pix[idx+1] = uint8(edges[idx+1])
|
||||
dst.Pix[idx+2] = uint8(edges[idx+2])
|
||||
dst.Pix[idx+3] = 255
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Group pixels into 2D array, each one containing the pixel RGB value.
|
||||
func getImageData(img *image.NRGBA) [][]uint8 {
|
||||
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
||||
pixels := make([][]uint8, dx*dy*4)
|
||||
|
||||
for i := 0; i < len(pixels); i += 4 {
|
||||
pixels[i/4] = []uint8{
|
||||
img.Pix[i],
|
||||
img.Pix[i+1],
|
||||
img.Pix[i+2],
|
||||
img.Pix[i+3],
|
||||
}
|
||||
}
|
||||
return pixels
|
||||
}
|
357
stackblur.go
Normal file
357
stackblur.go
Normal file
@@ -0,0 +1,357 @@
|
||||
// Go implementation of StackBlur algorithm described here:
|
||||
// http://incubator.quasimondo.com/processing/fast_blur_deluxe.php
|
||||
|
||||
package caire
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
type blurstack struct {
|
||||
r, g, b, a uint32
|
||||
next *blurstack
|
||||
}
|
||||
|
||||
var mul_table []uint32 = []uint32{
|
||||
512, 512, 456, 512, 328, 456, 335, 512, 405, 328, 271, 456, 388, 335, 292, 512,
|
||||
454, 405, 364, 328, 298, 271, 496, 456, 420, 388, 360, 335, 312, 292, 273, 512,
|
||||
482, 454, 428, 405, 383, 364, 345, 328, 312, 298, 284, 271, 259, 496, 475, 456,
|
||||
437, 420, 404, 388, 374, 360, 347, 335, 323, 312, 302, 292, 282, 273, 265, 512,
|
||||
497, 482, 468, 454, 441, 428, 417, 405, 394, 383, 373, 364, 354, 345, 337, 328,
|
||||
320, 312, 305, 298, 291, 284, 278, 271, 265, 259, 507, 496, 485, 475, 465, 456,
|
||||
446, 437, 428, 420, 412, 404, 396, 388, 381, 374, 367, 360, 354, 347, 341, 335,
|
||||
329, 323, 318, 312, 307, 302, 297, 292, 287, 282, 278, 273, 269, 265, 261, 512,
|
||||
505, 497, 489, 482, 475, 468, 461, 454, 447, 441, 435, 428, 422, 417, 411, 405,
|
||||
399, 394, 389, 383, 378, 373, 368, 364, 359, 354, 350, 345, 341, 337, 332, 328,
|
||||
324, 320, 316, 312, 309, 305, 301, 298, 294, 291, 287, 284, 281, 278, 274, 271,
|
||||
268, 265, 262, 259, 257, 507, 501, 496, 491, 485, 480, 475, 470, 465, 460, 456,
|
||||
451, 446, 442, 437, 433, 428, 424, 420, 416, 412, 408, 404, 400, 396, 392, 388,
|
||||
385, 381, 377, 374, 370, 367, 363, 360, 357, 354, 350, 347, 344, 341, 338, 335,
|
||||
332, 329, 326, 323, 320, 318, 315, 312, 310, 307, 304, 302, 299, 297, 294, 292,
|
||||
289, 287, 285, 282, 280, 278, 275, 273, 271, 269, 267, 265, 263, 261, 259,
|
||||
}
|
||||
|
||||
var shg_table []uint32 = []uint32{
|
||||
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
|
||||
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
|
||||
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
|
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
|
||||
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
|
||||
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
|
||||
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
|
||||
}
|
||||
|
||||
func (bs *blurstack) NewBlurStack() *blurstack {
|
||||
return &blurstack{bs.r, bs.g, bs.b, bs.a, bs.next}
|
||||
}
|
||||
|
||||
func Stackblur(img *image.NRGBA, width, height, radius uint32) *image.NRGBA {
|
||||
var stackEnd, stackIn, stackOut *blurstack
|
||||
var (
|
||||
div, widthMinus1, heightMinus1, radiusPlus1, sumFactor uint32
|
||||
x, y, i, p, yp, yi, yw,
|
||||
r_sum, g_sum, b_sum, a_sum,
|
||||
r_out_sum, g_out_sum, b_out_sum, a_out_sum,
|
||||
r_in_sum, g_in_sum, b_in_sum, a_in_sum,
|
||||
pr, pg, pb, pa uint32
|
||||
)
|
||||
|
||||
div = radius + radius + 1
|
||||
widthMinus1 = width - 1
|
||||
heightMinus1 = height - 1
|
||||
radiusPlus1 = radius + 1
|
||||
sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2
|
||||
|
||||
bs := blurstack{}
|
||||
stackStart := bs.NewBlurStack()
|
||||
stack := stackStart
|
||||
|
||||
for i = 1; i < div; i++ {
|
||||
stack.next = bs.NewBlurStack()
|
||||
stack = stack.next
|
||||
if i == radiusPlus1 {
|
||||
stackEnd = stack
|
||||
}
|
||||
}
|
||||
stack.next = stackStart
|
||||
|
||||
mul_sum := mul_table[radius]
|
||||
shg_sum := shg_table[radius]
|
||||
|
||||
for y = 0; y < height; y++ {
|
||||
r_in_sum, g_in_sum, b_in_sum, a_in_sum, r_sum, g_sum, b_sum, a_sum = 0, 0, 0, 0, 0, 0, 0, 0
|
||||
|
||||
pr = uint32(img.Pix[yi])
|
||||
pg = uint32(img.Pix[yi+1])
|
||||
pb = uint32(img.Pix[yi+2])
|
||||
pa = uint32(img.Pix[yi+3])
|
||||
|
||||
r_out_sum = radiusPlus1 * pr
|
||||
g_out_sum = radiusPlus1 * pg
|
||||
b_out_sum = radiusPlus1 * pb
|
||||
a_out_sum = radiusPlus1 * pa
|
||||
|
||||
r_sum += sumFactor * pr
|
||||
g_sum += sumFactor * pg
|
||||
b_sum += sumFactor * pb
|
||||
a_sum += sumFactor * pa
|
||||
|
||||
stack = stackStart
|
||||
|
||||
for i = 0; i < radiusPlus1; i++ {
|
||||
stack.r = pr
|
||||
stack.g = pg
|
||||
stack.b = pb
|
||||
stack.a = pa
|
||||
stack = stack.next
|
||||
}
|
||||
|
||||
for i = 1; i < radiusPlus1; i++ {
|
||||
var diff uint32
|
||||
if widthMinus1 < i {
|
||||
diff = widthMinus1
|
||||
} else {
|
||||
diff = i
|
||||
}
|
||||
p = yi + (diff << 2)
|
||||
pr = uint32(img.Pix[p])
|
||||
pg = uint32(img.Pix[p+1])
|
||||
pb = uint32(img.Pix[p+2])
|
||||
pa = uint32(img.Pix[p+3])
|
||||
|
||||
stack.r = pr
|
||||
stack.g = pg
|
||||
stack.b = pb
|
||||
stack.a = pa
|
||||
|
||||
r_sum += stack.r * (radiusPlus1 - i)
|
||||
g_sum += stack.g * (radiusPlus1 - i)
|
||||
b_sum += stack.b * (radiusPlus1 - i)
|
||||
a_sum += stack.a * (radiusPlus1 - i)
|
||||
|
||||
r_in_sum += pr
|
||||
g_in_sum += pg
|
||||
b_in_sum += pb
|
||||
a_in_sum += pa
|
||||
|
||||
stack = stack.next
|
||||
}
|
||||
stackIn = stackStart
|
||||
stackOut = stackEnd
|
||||
|
||||
for x = 0; x < width; x++ {
|
||||
pa = (a_sum * mul_sum) >> shg_sum
|
||||
img.Pix[yi+3] = uint8(pa)
|
||||
|
||||
if pa != 0 {
|
||||
pa = 255 / pa
|
||||
img.Pix[yi] = uint8((r_sum * mul_sum) >> shg_sum)
|
||||
img.Pix[yi+1] = uint8((g_sum * mul_sum) >> shg_sum)
|
||||
img.Pix[yi+2] = uint8((b_sum * mul_sum) >> shg_sum)
|
||||
} else {
|
||||
img.Pix[yi] = 0
|
||||
img.Pix[yi+1] = 0
|
||||
img.Pix[yi+2] = 0
|
||||
}
|
||||
|
||||
r_sum -= r_out_sum
|
||||
g_sum -= g_out_sum
|
||||
b_sum -= b_out_sum
|
||||
a_sum -= a_out_sum
|
||||
|
||||
r_out_sum -= stackIn.r
|
||||
g_out_sum -= stackIn.g
|
||||
b_out_sum -= stackIn.b
|
||||
a_out_sum -= stackIn.a
|
||||
|
||||
p = x + radius + 1
|
||||
|
||||
if p > widthMinus1 {
|
||||
p = widthMinus1
|
||||
}
|
||||
p = (yw + p) << 2
|
||||
|
||||
stackIn.r = uint32(img.Pix[p])
|
||||
stackIn.g = uint32(img.Pix[p+1])
|
||||
stackIn.b = uint32(img.Pix[p+2])
|
||||
stackIn.a = uint32(img.Pix[p+3])
|
||||
|
||||
r_in_sum += stackIn.r
|
||||
g_in_sum += stackIn.g
|
||||
b_in_sum += stackIn.b
|
||||
a_in_sum += stackIn.a
|
||||
|
||||
r_sum += r_in_sum
|
||||
g_sum += g_in_sum
|
||||
b_sum += b_in_sum
|
||||
a_sum += a_in_sum
|
||||
|
||||
stackIn = stackIn.next
|
||||
|
||||
pr = stackOut.r
|
||||
pg = stackOut.g
|
||||
pb = stackOut.b
|
||||
pa = stackOut.a
|
||||
|
||||
r_out_sum += pr
|
||||
g_out_sum += pg
|
||||
b_out_sum += pb
|
||||
a_out_sum += pa
|
||||
|
||||
r_in_sum -= pr
|
||||
g_in_sum -= pg
|
||||
b_in_sum -= pb
|
||||
a_in_sum -= pa
|
||||
|
||||
stackOut = stackOut.next
|
||||
|
||||
yi += 4
|
||||
}
|
||||
yw += width
|
||||
}
|
||||
|
||||
for x = 0; x < width; x++ {
|
||||
r_in_sum, g_in_sum, b_in_sum, a_in_sum, r_sum, g_sum, b_sum, a_sum = 0, 0, 0, 0, 0, 0, 0, 0
|
||||
|
||||
yi = x << 2
|
||||
pr = uint32(img.Pix[yi])
|
||||
pg = uint32(img.Pix[yi+1])
|
||||
pb = uint32(img.Pix[yi+2])
|
||||
pa = uint32(img.Pix[yi+3])
|
||||
|
||||
r_out_sum = radiusPlus1 * pr
|
||||
g_out_sum = radiusPlus1 * pg
|
||||
b_out_sum = radiusPlus1 * pb
|
||||
a_out_sum = radiusPlus1 * pa
|
||||
|
||||
r_sum += sumFactor * pr
|
||||
g_sum += sumFactor * pg
|
||||
b_sum += sumFactor * pb
|
||||
a_sum += sumFactor * pa
|
||||
|
||||
stack = stackStart
|
||||
|
||||
for i = 0; i < radiusPlus1; i++ {
|
||||
stack.r = pr
|
||||
stack.g = pg
|
||||
stack.b = pb
|
||||
stack.a = pa
|
||||
stack = stack.next
|
||||
}
|
||||
|
||||
yp = width
|
||||
|
||||
for i = 1; i <= radius; i++ {
|
||||
yi = (yp + x) << 2
|
||||
pr = uint32(img.Pix[yi])
|
||||
pg = uint32(img.Pix[yi+1])
|
||||
pb = uint32(img.Pix[yi+2])
|
||||
pa = uint32(img.Pix[yi+3])
|
||||
|
||||
stack.r = pr
|
||||
stack.g = pg
|
||||
stack.b = pb
|
||||
stack.a = pa
|
||||
|
||||
r_sum += stack.r * (radiusPlus1 - i)
|
||||
g_sum += stack.g * (radiusPlus1 - i)
|
||||
b_sum += stack.b * (radiusPlus1 - i)
|
||||
a_sum += stack.a * (radiusPlus1 - i)
|
||||
|
||||
r_in_sum += pr
|
||||
g_in_sum += pg
|
||||
b_in_sum += pb
|
||||
a_in_sum += pa
|
||||
|
||||
stack = stack.next
|
||||
|
||||
if i < heightMinus1 {
|
||||
yp += width
|
||||
}
|
||||
}
|
||||
|
||||
yi = x
|
||||
stackIn = stackStart
|
||||
stackOut = stackEnd
|
||||
|
||||
for y = 0; y < height; y++ {
|
||||
p = yi << 2
|
||||
pa = (a_sum * mul_sum) >> shg_sum
|
||||
img.Pix[p+3] = uint8(pa)
|
||||
|
||||
if pa > 0 {
|
||||
pa = 255 / pa
|
||||
img.Pix[p] = uint8((r_sum * mul_sum) >> shg_sum)
|
||||
img.Pix[p+1] = uint8((g_sum * mul_sum) >> shg_sum)
|
||||
img.Pix[p+2] = uint8((b_sum * mul_sum) >> shg_sum)
|
||||
} else {
|
||||
img.Pix[p] = 0
|
||||
img.Pix[p+1] = 0
|
||||
img.Pix[p+2] = 0
|
||||
}
|
||||
|
||||
r_sum -= r_out_sum
|
||||
g_sum -= g_out_sum
|
||||
b_sum -= b_out_sum
|
||||
a_sum -= a_out_sum
|
||||
|
||||
r_out_sum -= stackIn.r
|
||||
g_out_sum -= stackIn.g
|
||||
b_out_sum -= stackIn.b
|
||||
a_out_sum -= stackIn.a
|
||||
|
||||
p = y + radiusPlus1
|
||||
|
||||
if p > heightMinus1 {
|
||||
p = heightMinus1
|
||||
}
|
||||
p = (x + (p * width)) << 2
|
||||
|
||||
stackIn.r = uint32(img.Pix[p])
|
||||
stackIn.g = uint32(img.Pix[p+1])
|
||||
stackIn.b = uint32(img.Pix[p+2])
|
||||
stackIn.a = uint32(img.Pix[p+3])
|
||||
|
||||
r_in_sum += stackIn.r
|
||||
g_in_sum += stackIn.g
|
||||
b_in_sum += stackIn.b
|
||||
a_in_sum += stackIn.a
|
||||
|
||||
r_sum += r_in_sum
|
||||
g_sum += g_in_sum
|
||||
b_sum += b_in_sum
|
||||
a_sum += a_in_sum
|
||||
|
||||
stackIn = stackIn.next
|
||||
|
||||
pr = stackOut.r
|
||||
pg = stackOut.g
|
||||
pb = stackOut.b
|
||||
pa = stackOut.a
|
||||
|
||||
r_out_sum += pr
|
||||
g_out_sum += pg
|
||||
b_out_sum += pb
|
||||
a_out_sum += pa
|
||||
|
||||
r_in_sum -= pr
|
||||
g_in_sum -= pg
|
||||
b_in_sum -= pb
|
||||
a_in_sum -= pa
|
||||
|
||||
stackOut = stackOut.next
|
||||
|
||||
yi += width
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
Reference in New Issue
Block a user