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 }