Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Float math operations optimization #54

Merged
merged 2 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/img/edgeDetection.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func createGradientMapImage(i *image.NRGBA, g [][]gradientPoint) (*image.NRGBA,
outputImage := newWhiteNRGBA(image.Rect(0, 0, width, height))
pimit.ParallelReadWrite(outputImage, func(xIndex, yIndex int, _ color.Color) color.Color {
magnitude := g[xIndex][yIndex].magnitude
colorValue := uint8(math.Max(0, math.Min(255, magnitude)))
colorValue := uint8(utils.ClampFloat64(0, magnitude, 255))

return color.Gray{Y: colorValue}
})
Expand All @@ -153,7 +153,7 @@ func performNonMaxSuppresion(i *image.NRGBA, g [][]gradientPoint) (*image.NRGBA,
outputImage := newWhiteNRGBA(image.Rect(0, 0, width, height))
pimit.ParallelReadWrite(outputImage, func(xIndex, yIndex int, _ color.Color) color.Color {
magnitude := g[xIndex][yIndex].magnitude
colorValue := uint8(math.Max(0, math.Min(255, magnitude)))
colorValue := uint8(utils.ClampFloat64(0, magnitude, 255))

if xIndex == 0 || xIndex == width-1 || yIndex == 0 || yIndex == height-1 {
return color.Gray{Y: colorValue}
Expand Down
21 changes: 11 additions & 10 deletions pkg/utils/color.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ const (
// Convert the color.NRGBA color to the Y grayscale component represented as a integer in range from 0 to 255.
// Note that the alpha channel is not taken under account and the result is non-alpha-premultiplied.
func NrgbaToGrayscaleComponent(c color.NRGBA) int {
y := (float64(c.R) * 0.299) + (float64(c.G) * 0.587) + (float64(c.B) * 0.114)
return int(math.Max(0, math.Min(255, y)))
y := int((float64(c.R) * 0.299) + (float64(c.G) * 0.587) + (float64(c.B) * 0.114))

return ClampInt(0, y, 255)
}

// Convert the color.RGBA struct tu individual RGB components represented as floating point numbers in range from 0.0 to 1.0
Expand All @@ -36,8 +37,8 @@ func RgbaToHsla(c color.RGBA) (int, float64, float64, float64) {

alpha := float64(c.A) / 255.0

min := math.Min(rNorm, math.Min(gNorm, bNorm))
max := math.Max(rNorm, math.Max(gNorm, bNorm))
min := Min3Float64(rNorm, gNorm, bNorm)
max := Max3Float64(rNorm, gNorm, bNorm)
delta := max - min

lightness := (max + min) / 2.0
Expand Down Expand Up @@ -80,17 +81,17 @@ func BlendNrgba(a, b color.NRGBA, mode BlendingMode) color.NRGBA {
switch mode {
case LightenOnly:
{
r := uint8(math.Max(float64(a.R), float64(b.R)))
g := uint8(math.Max(float64(a.G), float64(b.G)))
b := uint8(math.Max(float64(a.B), float64(b.B)))
r := Max2Uint8(a.R, b.R)
g := Max2Uint8(a.G, b.G)
b := Max2Uint8(a.B, b.B)

return color.NRGBA{r, g, b, 0xff}
}
case DarkenOnly:
{
r := uint8(math.Min(float64(a.R), float64(b.R)))
g := uint8(math.Min(float64(a.G), float64(b.G)))
b := uint8(math.Min(float64(a.B), float64(b.B)))
r := Min2Uint8(a.R, b.R)
g := Min2Uint8(a.G, b.G)
b := Min2Uint8(a.B, b.B)

return color.NRGBA{r, g, b, 0xff}
}
Expand Down
70 changes: 70 additions & 0 deletions pkg/utils/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,73 @@ func Lerp(a, b, t float64) float64 {

return (1.0-t)*a + t*b
}

// Clamp the x int between min and max. Return min if x is smaller than min and max if x is greater than max, otherwise return x.
func ClampInt(min, x, max int) int {
if x < min {
return min
}

if x > max {
return max
}

return x
}

// Clamp the x float64 between min and max. Return min if x is smaller than min and max if x is greater than max, otherwise return x.
func ClampFloat64(min, x, max float64) float64 {
if x < min {
return min
}

if x > max {
return max
}

return x
}

// Get the smallest float64 from the three provided values.
func Min3Float64(a, b, c float64) float64 {
if a > b {
a = b
}

if a > c {
a = c
}

return a
}

// Get the greatest float64 from the three provided values.
func Max3Float64(a, b, c float64) float64 {
if a < b {
a = b
}

if a < c {
a = c
}

return a
}

// Get the smallest uint8 from the two provided values.
func Min2Uint8(a, b uint8) uint8 {
if a > b {
return b
}

return a
}

// Get the greatest uint8 from the two provided values.
func Max2Uint8(a, b uint8) uint8 {
if a < b {
return b
}

return a
}
122 changes: 122 additions & 0 deletions pkg/utils/math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,125 @@ func TestLerpShouldCorrecltyCalculateTheLinearInterpolation(t *testing.T) {
assert.InDelta(t, expected, actual, delta)
}
}

func TestClampIntShouldCorrectlyClampValues(t *testing.T) {
cases := map[struct {
min int
x int
max int
}]int{
{2, 0, 6}: 2,
{2, 2, 6}: 2,
{2, 4, 6}: 4,
{2, 6, 6}: 6,
{2, 8, 6}: 6,
}

for c, expected := range cases {
actual := ClampInt(c.min, c.x, c.max)

assert.Equal(t, expected, actual)
}
}

func TestClampFloat64ShouldCorrectlyClampValues(t *testing.T) {
cases := map[struct {
min float64
x float64
max float64
}]float64{
{2, 0, 6}: 2,
{2, 2, 6}: 2,
{2, 4, 6}: 4,
{2, 6, 6}: 6,
{2, 8, 6}: 6,
}

var delta float64 = 1e-7

for c, expected := range cases {
actual := ClampFloat64(c.min, c.x, c.max)

assert.InDelta(t, expected, actual, delta)
}
}

func TestMin3Float64ShouldCorrecltyReturnMinValue(t *testing.T) {
cases := map[struct {
a float64
b float64
c float64
}]float64{
{1, 2, 3}: 1,
{3, 1, 2}: 1,
{2, 3, 1}: 1,
{1, 1, 1}: 1,
}

var delta float64 = 1e-7

for c, expected := range cases {
actual := Min3Float64(c.a, c.b, c.c)

assert.InDelta(t, expected, actual, delta)
}
}

func TestMax3Float64ShouldCorrecltyReturnMaxValue(t *testing.T) {
cases := map[struct {
a float64
b float64
c float64
}]float64{
{1, 2, 3}: 3,
{3, 1, 2}: 3,
{2, 3, 1}: 3,
{1, 1, 1}: 1,
}

var delta float64 = 1e-7

for c, expected := range cases {
actual := Max3Float64(c.a, c.b, c.c)

assert.InDelta(t, expected, actual, delta)
}
}

func TestMin2Uint8ShouldCorrecltyReturnMinValue(t *testing.T) {
cases := map[struct {
a uint8
b uint8
}]uint8{
{1, 2}: 1,
{3, 1}: 1,
{1, 1}: 1,
}

var delta float64 = 1e-7

for c, expected := range cases {
actual := Min2Uint8(c.a, c.b)

assert.InDelta(t, expected, actual, delta)
}
}

func TestMax2Uint8ShouldCorrecltyReturnMaxValue(t *testing.T) {
cases := map[struct {
a uint8
b uint8
}]uint8{
{1, 2}: 2,
{3, 1}: 3,
{1, 1}: 1,
}

var delta float64 = 1e-7

for c, expected := range cases {
actual := Max2Uint8(c.a, c.b)

assert.InDelta(t, expected, actual, delta)
}
}