github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/gift/convolution.go (about)

     1  package gift
     2  
     3  import (
     4  	"image"
     5  	"image/draw"
     6  	"math"
     7  )
     8  
     9  func prepareConvolutionWeights(kernel []float32, normalize bool) (int, []uvweight) {
    10  	size := int(math.Sqrt(float64(len(kernel))))
    11  	if size%2 == 0 {
    12  		size--
    13  	}
    14  	if size < 1 {
    15  		return 0, []uvweight{}
    16  	}
    17  	center := size / 2
    18  
    19  	weights := []uvweight{}
    20  	for i := 0; i < size; i++ {
    21  		for j := 0; j < size; j++ {
    22  			k := j*size + i
    23  			w := float32(0.0)
    24  			if k < len(kernel) {
    25  				w = kernel[k]
    26  			}
    27  			if w != 0.0 {
    28  				weights = append(weights, uvweight{u: i - center, v: j - center, weight: w})
    29  			}
    30  		}
    31  	}
    32  
    33  	if !normalize {
    34  		return size, weights
    35  	}
    36  
    37  	var sum, sumpositive float32
    38  	for _, w := range weights {
    39  		sum += w.weight
    40  		if w.weight > 0 {
    41  			sumpositive += w.weight
    42  		}
    43  	}
    44  
    45  	var div float32
    46  	if sum != 0.0 {
    47  		div = sum
    48  	} else if sumpositive != 0.0 {
    49  		div = sumpositive
    50  	} else {
    51  		return size, weights
    52  	}
    53  
    54  	for i := 0; i < len(weights); i++ {
    55  		weights[i].weight /= div
    56  	}
    57  
    58  	return size, weights
    59  }
    60  
    61  type convolutionFilter struct {
    62  	kernel    []float32
    63  	normalize bool
    64  	alpha     bool
    65  	abs       bool
    66  	delta     float32
    67  }
    68  
    69  func (p *convolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
    70  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
    71  	return
    72  }
    73  
    74  func (p *convolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) {
    75  	if options == nil {
    76  		options = &defaultOptions
    77  	}
    78  
    79  	srcb := src.Bounds()
    80  	dstb := dst.Bounds()
    81  
    82  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
    83  		return
    84  	}
    85  
    86  	ksize, weights := prepareConvolutionWeights(p.kernel, p.normalize)
    87  	kcenter := ksize / 2
    88  
    89  	if ksize < 1 {
    90  		copyimage(dst, src, options)
    91  		return
    92  	}
    93  
    94  	pixGetter := newPixelGetter(src)
    95  	pixSetter := newPixelSetter(dst)
    96  
    97  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
    98  		// init temp rows
    99  		starty := pmin
   100  		rows := make([][]pixel, ksize)
   101  		for i := 0; i < ksize; i++ {
   102  			rowy := starty + i - kcenter
   103  			if rowy < srcb.Min.Y {
   104  				rowy = srcb.Min.Y
   105  			} else if rowy > srcb.Max.Y-1 {
   106  				rowy = srcb.Max.Y - 1
   107  			}
   108  			row := make([]pixel, srcb.Dx())
   109  			pixGetter.getPixelRow(rowy, &row)
   110  			rows[i] = row
   111  		}
   112  
   113  		for y := pmin; y < pmax; y++ {
   114  			// calculate dst row
   115  			for x := srcb.Min.X; x < srcb.Max.X; x++ {
   116  				var r, g, b, a float32
   117  				for _, w := range weights {
   118  					wx := x + w.u
   119  					if wx < srcb.Min.X {
   120  						wx = srcb.Min.X
   121  					} else if wx > srcb.Max.X-1 {
   122  						wx = srcb.Max.X - 1
   123  					}
   124  					rowsx := wx - srcb.Min.X
   125  					rowsy := kcenter + w.v
   126  
   127  					px := rows[rowsy][rowsx]
   128  					r += px.R * w.weight
   129  					g += px.G * w.weight
   130  					b += px.B * w.weight
   131  					if p.alpha {
   132  						a += px.A * w.weight
   133  					}
   134  				}
   135  				if p.abs {
   136  					r = absf32(r)
   137  					g = absf32(g)
   138  					b = absf32(b)
   139  					if p.alpha {
   140  						a = absf32(a)
   141  					}
   142  				}
   143  				if p.delta != 0.0 {
   144  					r += p.delta
   145  					g += p.delta
   146  					b += p.delta
   147  					if p.alpha {
   148  						a += p.delta
   149  					}
   150  				}
   151  				if !p.alpha {
   152  					a = rows[kcenter][x-srcb.Min.X].A
   153  				}
   154  				pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
   155  			}
   156  
   157  			// rotate temp rows
   158  			if y < pmax-1 {
   159  				tmprow := rows[0]
   160  				for i := 0; i < ksize-1; i++ {
   161  					rows[i] = rows[i+1]
   162  				}
   163  				nextrowy := y + ksize/2 + 1
   164  				if nextrowy > srcb.Max.Y-1 {
   165  					nextrowy = srcb.Max.Y - 1
   166  				}
   167  				pixGetter.getPixelRow(nextrowy, &tmprow)
   168  				rows[ksize-1] = tmprow
   169  			}
   170  		}
   171  	})
   172  }
   173  
   174  // Convolution creates a filter that applies a square convolution kernel to an image.
   175  // 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).
   176  // Excessive slice members will be ignored.
   177  // If normalize parameter is true, the kernel will be normalized before applying the filter.
   178  // If alpha parameter is true, the alpha component of color will be filtered too.
   179  // If abs parameter is true, absolute values of color components will be taken after doing calculations.
   180  // If delta parameter is not zero, this value will be added to the filtered pixels.
   181  //
   182  // Example:
   183  //
   184  //	// Apply the emboss filter to an image.
   185  //	g := gift.New(
   186  //		gift.Convolution(
   187  //			[]float32{
   188  //				-1, -1, 0,
   189  //				-1, 1, 1,
   190  //				0, 1, 1,
   191  //			},
   192  //			false, false, false, 0,
   193  //		),
   194  //	)
   195  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   196  //	g.Draw(dst, src)
   197  //
   198  func Convolution(kernel []float32, normalize, alpha, abs bool, delta float32) Filter {
   199  	return &convolutionFilter{
   200  		kernel:    kernel,
   201  		normalize: normalize,
   202  		alpha:     alpha,
   203  		abs:       abs,
   204  		delta:     delta,
   205  	}
   206  }
   207  
   208  // prepare pixel weights using convolution kernel. weights equal to 0 are excluded
   209  func prepareConvolutionWeights1d(kernel []float32) (int, []uweight) {
   210  	size := len(kernel)
   211  	if size%2 == 0 {
   212  		size--
   213  	}
   214  	if size < 1 {
   215  		return 0, []uweight{}
   216  	}
   217  	center := size / 2
   218  	weights := []uweight{}
   219  	for i := 0; i < size; i++ {
   220  		w := float32(0.0)
   221  		if i < len(kernel) {
   222  			w = kernel[i]
   223  		}
   224  		if w != 0 {
   225  			weights = append(weights, uweight{i - center, w})
   226  		}
   227  	}
   228  	return size, weights
   229  }
   230  
   231  // calculate pixels for one line according to weights
   232  func convolveLine(dstBuf []pixel, srcBuf []pixel, weights []uweight) {
   233  	max := len(srcBuf) - 1
   234  	if max < 0 {
   235  		return
   236  	}
   237  	for dstu := 0; dstu < len(srcBuf); dstu++ {
   238  		var r, g, b, a float32
   239  		for _, w := range weights {
   240  			k := dstu + w.u
   241  			if k < 0 {
   242  				k = 0
   243  			} else if k > max {
   244  				k = max
   245  			}
   246  			c := srcBuf[k]
   247  			wa := c.A * w.weight
   248  			r += c.R * wa
   249  			g += c.G * wa
   250  			b += c.B * wa
   251  			a += wa
   252  		}
   253  		if a != 0 {
   254  			r /= a
   255  			g /= a
   256  			b /= a
   257  		}
   258  		dstBuf[dstu] = pixel{r, g, b, a}
   259  	}
   260  }
   261  
   262  // fast vertical 1d convolution
   263  func convolve1dv(dst draw.Image, src image.Image, kernel []float32, options *Options) {
   264  	srcb := src.Bounds()
   265  	dstb := dst.Bounds()
   266  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   267  		return
   268  	}
   269  	if kernel == nil || len(kernel) < 1 {
   270  		copyimage(dst, src, options)
   271  		return
   272  	}
   273  	_, weights := prepareConvolutionWeights1d(kernel)
   274  	pixGetter := newPixelGetter(src)
   275  	pixSetter := newPixelSetter(dst)
   276  	parallelize(options.Parallelization, srcb.Min.X, srcb.Max.X, func(pmin, pmax int) {
   277  		srcBuf := make([]pixel, srcb.Dy())
   278  		dstBuf := make([]pixel, srcb.Dy())
   279  		for x := pmin; x < pmax; x++ {
   280  			pixGetter.getPixelColumn(x, &srcBuf)
   281  			convolveLine(dstBuf, srcBuf, weights)
   282  			pixSetter.setPixelColumn(dstb.Min.X+x-srcb.Min.X, dstBuf)
   283  		}
   284  	})
   285  }
   286  
   287  // fast horizontal 1d convolution
   288  func convolve1dh(dst draw.Image, src image.Image, kernel []float32, options *Options) {
   289  	srcb := src.Bounds()
   290  	dstb := dst.Bounds()
   291  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   292  		return
   293  	}
   294  	if kernel == nil || len(kernel) < 1 {
   295  		copyimage(dst, src, options)
   296  		return
   297  	}
   298  	_, weights := prepareConvolutionWeights1d(kernel)
   299  	pixGetter := newPixelGetter(src)
   300  	pixSetter := newPixelSetter(dst)
   301  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
   302  		srcBuf := make([]pixel, srcb.Dx())
   303  		dstBuf := make([]pixel, srcb.Dx())
   304  		for y := pmin; y < pmax; y++ {
   305  			pixGetter.getPixelRow(y, &srcBuf)
   306  			convolveLine(dstBuf, srcBuf, weights)
   307  			pixSetter.setPixelRow(dstb.Min.Y+y-srcb.Min.Y, dstBuf)
   308  		}
   309  	})
   310  }
   311  
   312  func gaussianBlurKernel(x, sigma float32) float32 {
   313  	return float32(math.Exp(-float64(x*x)/float64(2*sigma*sigma)) / (float64(sigma) * math.Sqrt(2*math.Pi)))
   314  }
   315  
   316  type gausssianBlurFilter struct {
   317  	sigma float32
   318  }
   319  
   320  func (p *gausssianBlurFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   321  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
   322  	return
   323  }
   324  
   325  func (p *gausssianBlurFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   326  	if options == nil {
   327  		options = &defaultOptions
   328  	}
   329  
   330  	srcb := src.Bounds()
   331  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   332  		return
   333  	}
   334  
   335  	if p.sigma <= 0 {
   336  		copyimage(dst, src, options)
   337  		return
   338  	}
   339  
   340  	radius := int(math.Ceil(float64(p.sigma * 3.0)))
   341  	size := 2*radius + 1
   342  	center := radius
   343  	kernel := make([]float32, size)
   344  
   345  	kernel[center] = gaussianBlurKernel(0.0, p.sigma)
   346  	sum := kernel[center]
   347  
   348  	for i := 1; i <= radius; i++ {
   349  		f := gaussianBlurKernel(float32(i), p.sigma)
   350  		kernel[center-i] = f
   351  		kernel[center+i] = f
   352  		sum += 2 * f
   353  	}
   354  
   355  	for i := 0; i < len(kernel); i++ {
   356  		kernel[i] /= sum
   357  	}
   358  
   359  	tmp := createTempImage(srcb)
   360  	convolve1dh(tmp, src, kernel, options)
   361  	convolve1dv(dst, tmp, kernel, options)
   362  }
   363  
   364  // GaussianBlur creates a filter that applies a gaussian blur to an image.
   365  // The sigma parameter must be positive and indicates how much the image will be blurred.
   366  // Blur affected radius roughly equals 3 * sigma.
   367  //
   368  // Example:
   369  //
   370  //	g := gift.New(
   371  //		gift.GaussianBlur(1.5),
   372  //	)
   373  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   374  //	g.Draw(dst, src)
   375  //
   376  func GaussianBlur(sigma float32) Filter {
   377  	return &gausssianBlurFilter{
   378  		sigma: sigma,
   379  	}
   380  }
   381  
   382  type unsharpMaskFilter struct {
   383  	sigma    float32
   384  	amount   float32
   385  	thresold float32
   386  }
   387  
   388  func (p *unsharpMaskFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   389  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
   390  	return
   391  }
   392  
   393  func unsharp(orig, blurred, amount, thresold float32) float32 {
   394  	dif := (orig - blurred) * amount
   395  	if absf32(dif) > absf32(thresold) {
   396  		return orig + dif
   397  	}
   398  	return orig
   399  }
   400  
   401  func (p *unsharpMaskFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   402  	if options == nil {
   403  		options = &defaultOptions
   404  	}
   405  
   406  	srcb := src.Bounds()
   407  	dstb := dst.Bounds()
   408  
   409  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   410  		return
   411  	}
   412  
   413  	blurred := createTempImage(srcb)
   414  	blur := GaussianBlur(p.sigma)
   415  	blur.Draw(blurred, src, options)
   416  
   417  	pixGetterOrig := newPixelGetter(src)
   418  	pixGetterBlur := newPixelGetter(blurred)
   419  	pixelSetter := newPixelSetter(dst)
   420  
   421  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
   422  		for y := pmin; y < pmax; y++ {
   423  			for x := srcb.Min.X; x < srcb.Max.X; x++ {
   424  				pxOrig := pixGetterOrig.getPixel(x, y)
   425  				pxBlur := pixGetterBlur.getPixel(x, y)
   426  
   427  				r := unsharp(pxOrig.R, pxBlur.R, p.amount, p.thresold)
   428  				g := unsharp(pxOrig.G, pxBlur.G, p.amount, p.thresold)
   429  				b := unsharp(pxOrig.B, pxBlur.B, p.amount, p.thresold)
   430  				a := unsharp(pxOrig.A, pxBlur.A, p.amount, p.thresold)
   431  
   432  				pixelSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, a})
   433  			}
   434  		}
   435  	})
   436  }
   437  
   438  // UnsharpMask creates a filter that sharpens an image.
   439  // The sigma parameter is used in a gaussian function and affects the radius of effect.
   440  // Sigma must be positive. Sharpen radius roughly equals 3 * sigma.
   441  // The amount parameter controls how much darker and how much lighter the edge borders become. Typically between 0.5 and 1.5.
   442  // The thresold parameter controls the minimum brightness change that will be sharpened. Typically between 0 and 0.05.
   443  //
   444  // Example:
   445  //
   446  //	g := gift.New(
   447  //		gift.UnsharpMask(1.0, 1.0, 0.0),
   448  //	)
   449  //	dst := image.NewRGBA(g.Bounds(src.Bounds()))
   450  //	g.Draw(dst, src)
   451  //
   452  func UnsharpMask(sigma, amount, thresold float32) Filter {
   453  	return &unsharpMaskFilter{
   454  		sigma:    sigma,
   455  		amount:   amount,
   456  		thresold: thresold,
   457  	}
   458  }
   459  
   460  type meanFilter struct {
   461  	ksize int
   462  	disk  bool
   463  }
   464  
   465  func (p *meanFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   466  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
   467  	return
   468  }
   469  
   470  func (p *meanFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   471  	if options == nil {
   472  		options = &defaultOptions
   473  	}
   474  
   475  	srcb := src.Bounds()
   476  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   477  		return
   478  	}
   479  
   480  	ksize := p.ksize
   481  	if ksize%2 == 0 {
   482  		ksize--
   483  	}
   484  
   485  	if ksize <= 1 {
   486  		copyimage(dst, src, options)
   487  		return
   488  	}
   489  
   490  	if p.disk {
   491  		diskKernel := genDisk(p.ksize)
   492  		f := Convolution(diskKernel, true, true, false, 0.0)
   493  		f.Draw(dst, src, options)
   494  	} else {
   495  		kernel := make([]float32, ksize*ksize)
   496  		for i := range kernel {
   497  			kernel[i] = 1.0
   498  		}
   499  		f := Convolution(kernel, true, true, false, 0.0)
   500  		f.Draw(dst, src, options)
   501  	}
   502  }
   503  
   504  // Mean creates a local mean image filter.
   505  // Takes an average across a neighborhood for each pixel.
   506  // The ksize parameter is the kernel size. It must be an odd positive integer (for example: 3, 5, 7).
   507  // If the disk parameter is true, a disk-shaped neighborhood will be used instead of a square neighborhood.
   508  func Mean(ksize int, disk bool) Filter {
   509  	return &meanFilter{
   510  		ksize: ksize,
   511  		disk:  disk,
   512  	}
   513  }
   514  
   515  type hvConvolutionFilter struct {
   516  	hkernel, vkernel []float32
   517  }
   518  
   519  func (p *hvConvolutionFilter) Bounds(srcBounds image.Rectangle) (dstBounds image.Rectangle) {
   520  	dstBounds = image.Rect(0, 0, srcBounds.Dx(), srcBounds.Dy())
   521  	return
   522  }
   523  
   524  func (p *hvConvolutionFilter) Draw(dst draw.Image, src image.Image, options *Options) {
   525  	if options == nil {
   526  		options = &defaultOptions
   527  	}
   528  
   529  	srcb := src.Bounds()
   530  	dstb := dst.Bounds()
   531  
   532  	if srcb.Dx() <= 0 || srcb.Dy() <= 0 {
   533  		return
   534  	}
   535  
   536  	tmph := createTempImage(srcb)
   537  	Convolution(p.hkernel, false, false, true, 0).Draw(tmph, src, options)
   538  	pixGetterH := newPixelGetter(tmph)
   539  
   540  	tmpv := createTempImage(srcb)
   541  	Convolution(p.vkernel, false, false, true, 0).Draw(tmpv, src, options)
   542  	pixGetterV := newPixelGetter(tmpv)
   543  
   544  	pixSetter := newPixelSetter(dst)
   545  
   546  	parallelize(options.Parallelization, srcb.Min.Y, srcb.Max.Y, func(pmin, pmax int) {
   547  		for y := pmin; y < pmax; y++ {
   548  			for x := srcb.Min.X; x < srcb.Max.X; x++ {
   549  				pxh := pixGetterH.getPixel(x, y)
   550  				pxv := pixGetterV.getPixel(x, y)
   551  				r := sqrtf32(pxh.R*pxh.R + pxv.R*pxv.R)
   552  				g := sqrtf32(pxh.G*pxh.G + pxv.G*pxv.G)
   553  				b := sqrtf32(pxh.B*pxh.B + pxv.B*pxv.B)
   554  				pixSetter.setPixel(dstb.Min.X+x-srcb.Min.X, dstb.Min.Y+y-srcb.Min.Y, pixel{r, g, b, pxh.A})
   555  			}
   556  		}
   557  	})
   558  
   559  }
   560  
   561  // Sobel creates a filter that applies a sobel operator to an image.
   562  func Sobel() Filter {
   563  	return &hvConvolutionFilter{
   564  		hkernel: []float32{-1, 0, 1, -2, 0, 2, -1, 0, 1},
   565  		vkernel: []float32{-1, -2, -1, 0, 0, 0, 1, 2, 1},
   566  	}
   567  }