2018-01-11 23:14:52 +08:00
|
|
|
package caire
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image"
|
|
|
|
"math"
|
|
|
|
)
|
|
|
|
|
|
|
|
type kernel [][]int32
|
|
|
|
|
|
|
|
var (
|
2018-02-12 03:14:19 +08:00
|
|
|
kernelX = kernel{
|
2018-01-11 23:14:52 +08:00
|
|
|
{-1, 0, 1},
|
|
|
|
{-2, 0, 2},
|
|
|
|
{-1, 0, 1},
|
|
|
|
}
|
|
|
|
|
2018-02-12 03:14:19 +08:00
|
|
|
kernelY = kernel{
|
2018-01-11 23:14:52 +08:00
|
|
|
{-1, -2, -1},
|
|
|
|
{0, 0, 0},
|
|
|
|
{1, 2, 1},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2018-02-12 03:14:19 +08:00
|
|
|
// SobelFilter detects image edges.
|
2018-01-11 23:14:52 +08:00
|
|
|
// See https://en.wikipedia.org/wiki/Sobel_operator
|
2021-05-28 20:46:12 +08:00
|
|
|
func (c *Carver) SobelFilter(img *image.NRGBA, threshold float64) *image.NRGBA {
|
2018-01-11 23:14:52 +08:00
|
|
|
var sumX, sumY int32
|
|
|
|
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
|
|
|
dst := image.NewNRGBA(img.Bounds())
|
|
|
|
|
2021-04-22 21:35:03 +08:00
|
|
|
// Get 3x3 window of pixels because image data given is just a 1D array of pixels
|
|
|
|
maxPixelOffset := dx*2 + len(kernelX) - 1
|
2018-01-11 23:14:52 +08:00
|
|
|
|
2021-05-28 20:46:12 +08:00
|
|
|
data := c.getImageData(img)
|
2021-04-22 21:35:03 +08:00
|
|
|
length := len(data)*4 - maxPixelOffset
|
|
|
|
magnitudes := make([]uint8, length)
|
2018-01-11 23:14:52 +08:00
|
|
|
|
|
|
|
for i := 0; i < length; i++ {
|
|
|
|
// Sum each pixel with the kernel value
|
|
|
|
sumX, sumY = 0, 0
|
|
|
|
for x := 0; x < len(kernelX); x++ {
|
|
|
|
for y := 0; y < len(kernelY); y++ {
|
2021-04-22 21:35:03 +08:00
|
|
|
if idx := i + (dx * y) + x; idx < len(data) {
|
|
|
|
r := data[i+(dx*y)+x]
|
2018-01-11 23:14:52 +08:00
|
|
|
sumX += int32(r) * kernelX[y][x]
|
|
|
|
sumY += int32(r) * kernelY[y][x]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
magnitude := math.Sqrt(float64(sumX*sumX) + float64(sumY*sumY))
|
|
|
|
// Check for pixel color boundaries
|
|
|
|
if magnitude < 0 {
|
|
|
|
magnitude = 0
|
|
|
|
} else if magnitude > 255 {
|
|
|
|
magnitude = 255
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set magnitude to 0 if doesn't exceed threshold, else set to magnitude
|
|
|
|
if magnitude > threshold {
|
2021-04-22 21:35:03 +08:00
|
|
|
magnitudes[i] = uint8(magnitude)
|
2018-01-11 23:14:52 +08:00
|
|
|
} else {
|
|
|
|
magnitudes[i] = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dataLength := dx * dy * 4
|
|
|
|
edges := make([]int32, dataLength)
|
|
|
|
|
2019-04-04 18:14:10 +08:00
|
|
|
// Apply the kernel values
|
2018-01-11 23:14:52 +08:00
|
|
|
for i := 0; i < dataLength; i++ {
|
2021-04-22 21:35:03 +08:00
|
|
|
edges[i] = 0
|
2018-01-11 23:14:52 +08:00
|
|
|
if i%4 != 0 {
|
|
|
|
m := magnitudes[i/4]
|
|
|
|
if m != 0 {
|
2021-04-22 21:35:03 +08:00
|
|
|
edges[i-1] = int32(m)
|
2018-01-11 23:14:52 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:35:03 +08:00
|
|
|
// Generate the new image with the sobel filter applied
|
2018-01-11 23:14:52 +08:00
|
|
|
for idx := 0; idx < len(edges); idx += 4 {
|
|
|
|
dst.Pix[idx] = uint8(edges[idx])
|
|
|
|
dst.Pix[idx+1] = uint8(edges[idx+1])
|
|
|
|
dst.Pix[idx+2] = uint8(edges[idx+2])
|
|
|
|
dst.Pix[idx+3] = 255
|
|
|
|
}
|
|
|
|
return dst
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:35:03 +08:00
|
|
|
// getImageData returns an array of pixel grayscale brightness values
|
|
|
|
// for the image (taking the red component of each pixel).
|
2021-05-28 20:46:12 +08:00
|
|
|
func (c *Carver) getImageData(img *image.NRGBA) []uint8 {
|
2018-01-11 23:14:52 +08:00
|
|
|
dx, dy := img.Bounds().Max.X, img.Bounds().Max.Y
|
2021-04-22 21:35:03 +08:00
|
|
|
pixels := make([]uint8, dx*dy)
|
2018-01-11 23:14:52 +08:00
|
|
|
|
2021-04-22 21:35:03 +08:00
|
|
|
for i := range pixels {
|
|
|
|
pixels[i] = img.Pix[i*4]
|
2018-01-11 23:14:52 +08:00
|
|
|
}
|
|
|
|
return pixels
|
2018-01-15 16:06:26 +08:00
|
|
|
}
|