1package imaging
  2
  3import (
  4	"image"
  5)
  6
  7// ConvolveOptions are convolution parameters.
  8type ConvolveOptions struct {
  9	// If Normalize is true the kernel is normalized before convolution.
 10	Normalize bool
 11
 12	// If Abs is true the absolute value of each color channel is taken after convolution.
 13	Abs bool
 14
 15	// Bias is added to each color channel value after convolution.
 16	Bias int
 17}
 18
 19// Convolve3x3 convolves the image with the specified 3x3 convolution kernel.
 20// Default parameters are used if a nil *ConvolveOptions is passed.
 21func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA {
 22	return convolve(img, kernel[:], options)
 23}
 24
 25// Convolve5x5 convolves the image with the specified 5x5 convolution kernel.
 26// Default parameters are used if a nil *ConvolveOptions is passed.
 27func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA {
 28	return convolve(img, kernel[:], options)
 29}
 30
 31func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA {
 32	src := toNRGBA(img)
 33	w := src.Bounds().Max.X
 34	h := src.Bounds().Max.Y
 35	dst := image.NewNRGBA(image.Rect(0, 0, w, h))
 36
 37	if w < 1 || h < 1 {
 38		return dst
 39	}
 40
 41	if options == nil {
 42		options = &ConvolveOptions{}
 43	}
 44
 45	if options.Normalize {
 46		normalizeKernel(kernel)
 47	}
 48
 49	type coef struct {
 50		x, y int
 51		k    float64
 52	}
 53	var coefs []coef
 54	var m int
 55
 56	switch len(kernel) {
 57	case 9:
 58		m = 1
 59	case 25:
 60		m = 2
 61	}
 62
 63	i := 0
 64	for y := -m; y <= m; y++ {
 65		for x := -m; x <= m; x++ {
 66			if kernel[i] != 0 {
 67				coefs = append(coefs, coef{x: x, y: y, k: kernel[i]})
 68			}
 69			i++
 70		}
 71	}
 72
 73	parallel(0, h, func(ys <-chan int) {
 74		for y := range ys {
 75			for x := 0; x < w; x++ {
 76				var r, g, b float64
 77				for _, c := range coefs {
 78					ix := x + c.x
 79					if ix < 0 {
 80						ix = 0
 81					} else if ix >= w {
 82						ix = w - 1
 83					}
 84
 85					iy := y + c.y
 86					if iy < 0 {
 87						iy = 0
 88					} else if iy >= h {
 89						iy = h - 1
 90					}
 91
 92					off := iy*src.Stride + ix*4
 93					s := src.Pix[off : off+3 : off+3]
 94					r += float64(s[0]) * c.k
 95					g += float64(s[1]) * c.k
 96					b += float64(s[2]) * c.k
 97				}
 98
 99				if options.Abs {
100					if r < 0 {
101						r = -r
102					}
103					if g < 0 {
104						g = -g
105					}
106					if b < 0 {
107						b = -b
108					}
109				}
110
111				if options.Bias != 0 {
112					r += float64(options.Bias)
113					g += float64(options.Bias)
114					b += float64(options.Bias)
115				}
116
117				srcOff := y*src.Stride + x*4
118				dstOff := y*dst.Stride + x*4
119				d := dst.Pix[dstOff : dstOff+4 : dstOff+4]
120				d[0] = clamp(r)
121				d[1] = clamp(g)
122				d[2] = clamp(b)
123				d[3] = src.Pix[srcOff+3]
124			}
125		}
126	})
127
128	return dst
129}
130
131func normalizeKernel(kernel []float64) {
132	var sum, sumpos float64
133	for i := range kernel {
134		sum += kernel[i]
135		if kernel[i] > 0 {
136			sumpos += kernel[i]
137		}
138	}
139	if sum != 0 {
140		for i := range kernel {
141			kernel[i] /= sum
142		}
143	} else if sumpos != 0 {
144		for i := range kernel {
145			kernel[i] /= sumpos
146		}
147	}
148}