20func prepareConvolutionWeights(kernel []float32, normalize bool) (int, []uvweight) {
21 size := int(math.Sqrt(float64(len(kernel))))
26 return 0, []uvweight{}
30 weights := []uvweight{}
31 for i := 0; i < size; i++ {
32 for j := 0; j < size; j++ {
39 weights = append(weights, uvweight{u: i - center, v: j - center, weight: w})
48 var sum, sumpositive float32
49 for _, w := range weights {
52 sumpositive += w.weight
59 } else if sumpositive != 0 {
65 for i := 0; i < len(weights); i++ {
66 weights[i].weight /= div
72type convolutionFilter struct {
80func (p *convolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
81 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
85func (p *convolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) {
87 options = &defaultOptions
93 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
97 ksize, weights := prepareConvolutionWeights(p.kernel, p.normalize)
101 copyimage(dst, src, options)
105 pixGetter := newPixelGetter(src)
106 pixSetter := newPixelSetter(dst)
108 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
111 rows := make([][]pixel, ksize)
112 for i := 0; i < ksize; i++ {
113 rowy := starty + i - kcenter
114 if rowy < srcb.Min.Y {
116 } else if rowy > srcb.Max.Y-1 {
117 rowy = srcb.Max.Y - 1
119 row := make([]pixel, srcb.Dx())
120 pixGetter.getPixelRow(rowy, &row)
124 for y := pmin; y < pmax; y++ {
126 for x := srcb.Min.X; x < srcb.Max.X; x++ {
127 var r, g, b, a float32
128 for _, w := range weights {
132 } else if wx > srcb.Max.X-1 {
135 rowsx := wx - srcb.Min.X
136 rowsy := kcenter + w.v
138 px := rows[rowsy][rowsx]
163 a = rows[kcenter][x-srcb.Min.X].a
165 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
171 for i := 0; i < ksize-1; i++ {
174 nextrowy := y + ksize/2 + 1
175 if nextrowy > srcb.Max.Y-1 {
176 nextrowy = srcb.Max.Y - 1
178 pixGetter.getPixelRow(nextrowy, &tmprow)
179 rows[ksize-1] = tmprow
185// Convolution creates a filter that applies a square convolution kernel to an image.
186// The length of the kernel slice must be the square of an odd kernel size (e.g. 9 for 3x3 kernel, 25 for 5x5 kernel).
187// Excessive slice members will be ignored.
188// If normalize parameter is true, the kernel will be normalized before applying the filter.
189// If alpha parameter is true, the alpha component of color will be filtered too.
190// If abs parameter is true, absolute values of color components will be taken after doing calculations.
191// If delta parameter is not zero, this value will be added to the filtered pixels.
195// // Apply the emboss filter to an image.
203// false, false, false, 0,
206// dst := image.NewRGBA(g.Bounds(src.Bounds()))
209func Convolution(kernel []float32, normalize, alpha, abs bool, delta float32) Filter {
210 return &convolutionFilter{
212 normalize: normalize,
219// prepare pixel weights using convolution kernel. weights equal to 0 are excluded
220func prepareConvolutionWeights1d(kernel []float32) (int, []uweight) {
226 return 0, []uweight{}
229 weights := []uweight{}
230 for i := 0; i < size; i++ {
236 weights = append(weights, uweight{i - center, w})
242// calculate pixels for one line according to weights
243func convolveLine(dstBuf []pixel, srcBuf []pixel, weights []uweight) {
244 max := len(srcBuf) - 1
248 for dstu := 0; dstu < len(srcBuf); dstu++ {
249 var r, g, b, a float32
250 for _, w := range weights {
269 dstBuf[dstu] = pixel{r, g, b, a}
273// fast vertical 1d convolution
274func convolve1dv(dst draw.Image, src image.Image, kernel []float32, options *Options) {
277 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
280 if kernel == nil || len(kernel) < 1 {
281 copyimage(dst, src, options)
284 _, weights := prepareConvolutionWeights1d(kernel)
285 pixGetter := newPixelGetter(src)
286 pixSetter := newPixelSetter(dst)
287 parallelize(options.Parallelization, srcb.Min.X, srcb.Max.X, func(pmin, pmax int) {
288 srcBuf := make([]pixel, srcb.Dy())
289 dstBuf := make([]pixel, srcb.Dy())
290 for x := pmin; x < pmax; x++ {
291 pixGetter.getPixelColumn(x, &srcBuf)
292 convolveLine(dstBuf, srcBuf, weights)
293 pixSetter.setPixelColumn(dstb.Min.X+x-srcb.Min.X, dstBuf)
298// fast horizontal 1d convolution
299func convolve1dh(dst draw.Image, src image.Image, kernel []float32, options *Options) {
302 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
305 if kernel == nil || len(kernel) < 1 {
306 copyimage(dst, src, options)
309 _, weights := prepareConvolutionWeights1d(kernel)
310 pixGetter := newPixelGetter(src)
311 pixSetter := newPixelSetter(dst)
312 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
313 srcBuf := make([]pixel, srcb.Dx())
314 dstBuf := make([]pixel, srcb.Dx())
315 for y := pmin; y < pmax; y++ {
316 pixGetter.getPixelRow(y, &srcBuf)
317 convolveLine(dstBuf, srcBuf, weights)
318 pixSetter.setPixelRow(dstb.Min.Y+y-srcb.Min.Y, dstBuf)
323func gaussianBlurKernel(x, sigma float32) float32 {
324 return float32(math.Exp(-float64(x*x)/float64(2*sigma*sigma)) / (float64(sigma) * math.Sqrt(2*math.Pi)))
327type gausssianBlurFilter struct {
331func (p *gausssianBlurFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
332 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
336func (p *gausssianBlurFilter) Draw(dst draw.Image, src image.Image, options *Options) {
338 options = &defaultOptions
342 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
347 copyimage(dst, src, options)
351 radius := int(math.Ceil(float64(p.sigma * 3)))
354 kernel := make([]float32, size)
356 kernel[center] = gaussianBlurKernel(0, p.sigma)
357 sum := kernel[center]
359 for i := 1; i <= radius; i++ {
360 f := gaussianBlurKernel(float32(i), p.sigma)
366 for i := 0; i < len(kernel); i++ {
370 tmp := createTempImage(srcb)
371 convolve1dh(tmp, src, kernel, options)
372 convolve1dv(dst, tmp, kernel, options)
375// GaussianBlur creates a filter that applies a gaussian blur to an image.
376// The sigma parameter must be positive and indicates how much the image will be blurred.
377// Blur affected radius roughly equals 3 * sigma.
382// gift.GaussianBlur(1.5),
384// dst := image.NewRGBA(g.Bounds(src.Bounds()))
387func GaussianBlur(sigma float32) Filter {
388 return &gausssianBlurFilter{
393type unsharpMaskFilter struct {
399func (p *unsharpMaskFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
400 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
404func unsharp(orig, blurred, amount, threshold float32) float32 {
405 dif := (orig - blurred) * amount
406 if absf32(dif) > absf32(threshold) {
412func (p *unsharpMaskFilter) Draw(dst draw.Image, src image.Image, options *Options) {
414 options = &defaultOptions
420 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
424 blurred := createTempImage(srcb)
425 blur := GaussianBlur(p.sigma)
426 blur.Draw(blurred, src, options)
428 pixGetterOrig := newPixelGetter(src)
429 pixGetterBlur := newPixelGetter(blurred)
430 pixelSetter := newPixelSetter(dst)
432 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
433 for y := pmin; y < pmax; y++ {
434 for x := srcb.Min.X; x < srcb.Max.X; x++ {
435 pxOrig := pixGetterOrig.getPixel(x, y)
436 pxBlur := pixGetterBlur.getPixel(x, y)
438 r := unsharp(pxOrig.r, pxBlur.r, p.amount, p.threshold)
439 g := unsharp(pxOrig.g, pxBlur.g, p.amount, p.threshold)
440 b := unsharp(pxOrig.b, pxBlur.b, p.amount, p.threshold)
441 a := unsharp(pxOrig.a, pxBlur.a, p.amount, p.threshold)
443 pixelSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
449// UnsharpMask creates a filter that sharpens an image.
450// The sigma parameter is used in a gaussian function and affects the radius of effect.
451// Sigma must be positive. Sharpen radius roughly equals 3 * sigma.
452// The amount parameter controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5.
453// The threshold parameter controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05.
458// gift.UnsharpMask(1, 1, 0),
460// dst := image.NewRGBA(g.Bounds(src.Bounds()))
463func UnsharpMask(sigma, amount, threshold float32) Filter {
464 return &unsharpMaskFilter{
467 threshold: threshold,
471type meanFilter struct {
476func (p *meanFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
477 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
481func (p *meanFilter) Draw(dst draw.Image, src image.Image, options *Options) {
483 options = &defaultOptions
487 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
497 copyimage(dst, src, options)
502 diskKernel := genDisk(p.ksize)
503 f := Convolution(diskKernel, true, true, false, 0)
504 f.Draw(dst, src, options)
506 kernel := make([]float32, ksize*ksize)
507 for i := range kernel {
510 f := Convolution(kernel, true, true, false, 0)
511 f.Draw(dst, src, options)
515// Mean creates a local mean image filter.
516// Takes an average across a neighborhood for each pixel.
517// The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
518// If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
519func Mean(ksize int, disk bool) Filter {
526type hvConvolutionFilter struct {
527 hkernel, vkernel []float32
530func (p *hvConvolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
531 dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
535func (p *hvConvolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) {
537 options = &defaultOptions
543 if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
547 tmph := createTempImage(srcb)
548 Convolution(p.hkernel, false, false, true, 0).Draw(tmph, src, options)
549 pixGetterH := newPixelGetter(tmph)
551 tmpv := createTempImage(srcb)
552 Convolution(p.vkernel, false, false, true, 0).Draw(tmpv, src, options)
553 pixGetterV := newPixelGetter(tmpv)
555 pixSetter := newPixelSetter(dst)
557 parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
558 for y := pmin; y < pmax; y++ {
559 for x := srcb.Min.X; x < srcb.Max.X; x++ {
560 pxh := pixGetterH.getPixel(x, y)
561 pxv := pixGetterV.getPixel(x, y)
562 r := sqrtf32(pxh.r*pxh.r + pxv.r*pxv.r)
563 g := sqrtf32(pxh.g*pxh.g + pxv.g*pxv.g)
564 b := sqrtf32(pxh.b*pxh.b + pxv.b*pxv.b)
565 pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, pxh.a})
572// Sobel creates a filter that applies a sobel operator to an image.
574 return &hvConvolutionFilter{
575 hkernel: []float32{-1, 0, 1, -2, 0, 2, -1, 0, 1},
576 vkernel: []float32{-1, -2, -1, 0, 0, 0, 1, 2, 1},
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list