convolution.go raw

   1  package imaging
   2  
   3  import (
   4  	"image"
   5  )
   6  
   7  // ConvolveOptions are convolution parameters.
   8  type 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.
  21  func 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.
  27  func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA {
  28  	return convolve(img, kernel[:], options)
  29  }
  30  
  31  func 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  
 131  func 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  }
 149