git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/imaging/effects.go (about)

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