effects.go raw

   1  package imaging
   2  
   3  import (
   4  	"image"
   5  	"math"
   6  )
   7  
   8  func gaussianBlurKernel(x, sigma float64) float64 {
   9  	return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
  10  }
  11  
  12  // Blur produces a blurred version of the image using a Gaussian function.
  13  // Sigma parameter must be positive and indicates how much the image will be blurred.
  14  //
  15  // Example:
  16  //
  17  //	dstImage := imaging.Blur(srcImage, 3.5)
  18  //
  19  func Blur(img image.Image, sigma float64) *image.NRGBA {
  20  	if sigma <= 0 {
  21  		return Clone(img)
  22  	}
  23  
  24  	radius := int(math.Ceil(sigma * 3.0))
  25  	kernel := make([]float64, radius+1)
  26  
  27  	for i := 0; i <= radius; i++ {
  28  		kernel[i] = gaussianBlurKernel(float64(i), sigma)
  29  	}
  30  
  31  	return blurVertical(blurHorizontal(img, kernel), kernel)
  32  }
  33  
  34  func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA {
  35  	src := newScanner(img)
  36  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  37  	radius := len(kernel) - 1
  38  
  39  	parallel(0, src.h, func(ys <-chan int) {
  40  		scanLine := make([]uint8, src.w*4)
  41  		scanLineF := make([]float64, len(scanLine))
  42  		for y := range ys {
  43  			src.scan(0, y, src.w, y+1, scanLine)
  44  			for i, v := range scanLine {
  45  				scanLineF[i] = float64(v)
  46  			}
  47  			for x := 0; x < src.w; x++ {
  48  				min := x - radius
  49  				if min < 0 {
  50  					min = 0
  51  				}
  52  				max := x + radius
  53  				if max > src.w-1 {
  54  					max = src.w - 1
  55  				}
  56  				var r, g, b, a, wsum float64
  57  				for ix := min; ix <= max; ix++ {
  58  					i := ix * 4
  59  					weight := kernel[absint(x-ix)]
  60  					wsum += weight
  61  					s := scanLineF[i : i+4 : i+4]
  62  					wa := s[3] * weight
  63  					r += s[0] * wa
  64  					g += s[1] * wa
  65  					b += s[2] * wa
  66  					a += wa
  67  				}
  68  				if a != 0 {
  69  					aInv := 1 / a
  70  					j := y*dst.Stride + x*4
  71  					d := dst.Pix[j : j+4 : j+4]
  72  					d[0] = clamp(r * aInv)
  73  					d[1] = clamp(g * aInv)
  74  					d[2] = clamp(b * aInv)
  75  					d[3] = clamp(a / wsum)
  76  				}
  77  			}
  78  		}
  79  	})
  80  
  81  	return dst
  82  }
  83  
  84  func blurVertical(img image.Image, kernel []float64) *image.NRGBA {
  85  	src := newScanner(img)
  86  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
  87  	radius := len(kernel) - 1
  88  
  89  	parallel(0, src.w, func(xs <-chan int) {
  90  		scanLine := make([]uint8, src.h*4)
  91  		scanLineF := make([]float64, len(scanLine))
  92  		for x := range xs {
  93  			src.scan(x, 0, x+1, src.h, scanLine)
  94  			for i, v := range scanLine {
  95  				scanLineF[i] = float64(v)
  96  			}
  97  			for y := 0; y < src.h; y++ {
  98  				min := y - radius
  99  				if min < 0 {
 100  					min = 0
 101  				}
 102  				max := y + radius
 103  				if max > src.h-1 {
 104  					max = src.h - 1
 105  				}
 106  				var r, g, b, a, wsum float64
 107  				for iy := min; iy <= max; iy++ {
 108  					i := iy * 4
 109  					weight := kernel[absint(y-iy)]
 110  					wsum += weight
 111  					s := scanLineF[i : i+4 : i+4]
 112  					wa := s[3] * weight
 113  					r += s[0] * wa
 114  					g += s[1] * wa
 115  					b += s[2] * wa
 116  					a += wa
 117  				}
 118  				if a != 0 {
 119  					aInv := 1 / a
 120  					j := y*dst.Stride + x*4
 121  					d := dst.Pix[j : j+4 : j+4]
 122  					d[0] = clamp(r * aInv)
 123  					d[1] = clamp(g * aInv)
 124  					d[2] = clamp(b * aInv)
 125  					d[3] = clamp(a / wsum)
 126  				}
 127  			}
 128  		}
 129  	})
 130  
 131  	return dst
 132  }
 133  
 134  // Sharpen produces a sharpened version of the image.
 135  // Sigma parameter must be positive and indicates how much the image will be sharpened.
 136  //
 137  // Example:
 138  //
 139  //	dstImage := imaging.Sharpen(srcImage, 3.5)
 140  //
 141  func Sharpen(img image.Image, sigma float64) *image.NRGBA {
 142  	if sigma <= 0 {
 143  		return Clone(img)
 144  	}
 145  
 146  	src := newScanner(img)
 147  	dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h))
 148  	blurred := Blur(img, sigma)
 149  
 150  	parallel(0, src.h, func(ys <-chan int) {
 151  		scanLine := make([]uint8, src.w*4)
 152  		for y := range ys {
 153  			src.scan(0, y, src.w, y+1, scanLine)
 154  			j := y * dst.Stride
 155  			for i := 0; i < src.w*4; i++ {
 156  				val := int(scanLine[i])<<1 - int(blurred.Pix[j])
 157  				if val < 0 {
 158  					val = 0
 159  				} else if val > 0xff {
 160  					val = 0xff
 161  				}
 162  				dst.Pix[j] = uint8(val)
 163  				j++
 164  			}
 165  		}
 166  	})
 167  
 168  	return dst
 169  }
 170