Browse Source

adjust saturation fixes and tests

Grigory Dryapak 6 năm trước cách đây
mục cha
commit
b6b4936896
6 tập tin đã thay đổi với 322 bổ sung308 xóa
  1. 8 11
      adjust.go
  2. 151 120
      adjust_test.go
  3. BIN
      testdata/out_saturation_m30.png
  4. BIN
      testdata/out_saturation_p30.png
  5. 55 92
      utils.go
  6. 108 85
      utils_test.go

+ 8 - 11
adjust.go

@@ -52,7 +52,7 @@ func Invert(img image.Image) *image.NRGBA {
 // AdjustSaturation changes the saturation of the image using the percentage parameter and returns the adjusted image.
 // The percentage must be in the range (-100, 100).
 // The percentage = 0 gives the original image.
-// The percentage = 100 gives the image with the saturation maxed for each pixel.
+// The percentage = 100 gives the image with the saturation value doubled for each pixel.
 // The percentage = -100 gives the image with the saturation value zeroed for each pixel (grayscale).
 //
 // Examples:
@@ -61,19 +61,16 @@ func Invert(img image.Image) *image.NRGBA {
 //
 func AdjustSaturation(img image.Image, percentage float64) *image.NRGBA {
 	percentage = math.Min(math.Max(percentage, -100), 100)
-	multiplier := percentage / 100
+	multiplier := 1 + percentage/100
 
 	return AdjustFunc(img, func(c color.NRGBA) color.NRGBA {
-		h, s, l := nrgbaToHSL(c)
-		if multiplier > 0 {
-			s += (1 - s) * multiplier
-		} else {
-			s += s * multiplier
+		h, s, l := rgbToHSL(c.R, c.G, c.B)
+		s *= multiplier
+		if s > 1 {
+			s = 1
 		}
-
-		newColor := hslToNRGBA(h, s, l)
-		newColor.A = c.A
-		return newColor
+		r, g, b := hslToRGB(h, s, l)
+		return color.NRGBA{r, g, b, c.A}
 	})
 }
 

+ 151 - 120
adjust_test.go

@@ -96,6 +96,157 @@ func BenchmarkInvert(b *testing.B) {
 	}
 }
 
+func TestAdjustSaturation(t *testing.T) {
+	testCases := []struct {
+		name string
+		src  image.Image
+		p    float64
+		want *image.NRGBA
+	}{
+		{
+			"AdjustSaturation 3x3 10",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 2, 2),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+			10,
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 3, 3),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x0f, 0x22, 0x35, 0xff, 0x35, 0x22, 0x0f, 0xff, 0xaf, 0x2c, 0xc2, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+		},
+		{
+			"AdjustSaturation 3x3 100",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 2, 2),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+			100,
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 3, 3),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x00, 0x22, 0x44, 0xff, 0x44, 0x22, 0x00, 0xff, 0xd0, 0x00, 0xee, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+		},
+		{
+			"AdjustSaturation 3x3 -10",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 2, 2),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+			-10,
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 3, 3),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xc2, 0x0a, 0x0a, 0x01, 0x0a, 0xc2, 0x0a, 0x02, 0x0a, 0x0a, 0xc2, 0x03,
+					0x13, 0x22, 0x31, 0xff, 0x31, 0x22, 0x13, 0xff, 0xa5, 0x3a, 0xb4, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+		},
+		{
+			"AdjustSaturation 3x3 -100",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 2, 2),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+			-100,
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 3, 3),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0x66, 0x66, 0x66, 0x01, 0x66, 0x66, 0x66, 0x02, 0x66, 0x66, 0x66, 0x03,
+					0x22, 0x22, 0x22, 0xff, 0x22, 0x22, 0x22, 0xff, 0x77, 0x77, 0x77, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+		},
+		{
+			"AdjustSaturation 3x3 0",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 2, 2),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+			0,
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 3, 3),
+				Stride: 3 * 4,
+				Pix: []uint8{
+					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
+					0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
+					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			got := AdjustSaturation(tc.src, tc.p)
+			if !compareNRGBA(got, tc.want, 0) {
+				t.Fatalf("got result %#v want %#v", got, tc.want)
+			}
+		})
+	}
+}
+
+func TestAdjustSaturationGolden(t *testing.T) {
+	for name, p := range map[string]float64{
+		"out_saturation_m30.png": -30,
+		"out_saturation_p30.png": 30,
+	} {
+		got := AdjustSaturation(testdataFlowersSmallPNG, p)
+		want, err := Open("testdata/" + name)
+		if err != nil {
+			t.Fatalf("failed to open image: %v", err)
+		}
+		if !compareNRGBA(got, toNRGBA(want), 0) {
+			t.Errorf("resulting image differs from golden: %s", name)
+		}
+	}
+}
+
+func BenchmarkAdjustSaturation(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		AdjustSaturation(testdataBranchesJPG, 10)
+	}
+}
+
 func TestAdjustContrast(t *testing.T) {
 	testCases := []struct {
 		name string
@@ -669,123 +820,3 @@ func TestAdjustFunc(t *testing.T) {
 		})
 	}
 }
-
-func TestAdjustSaturation(t *testing.T) {
-	src := &image.NRGBA{
-		Rect:   image.Rect(-1, -1, 2, 2),
-		Stride: 3 * 4,
-		Pix:    []uint8{
-			0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
-			0x11, 0x22, 0x33, 0xff, 0x33, 0x22, 0x11, 0xff, 0xaa, 0x33, 0xbb, 0xff,
-			0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
-		},
-	}
-
-	testCases := []struct {
-		name string
-		src  image.Image
-		p    float64
-		want *image.NRGBA
-	}{
-		{
-			"AdjustSaturation 3x3 10",
-			src,
-			10,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
-					0x0f, 0x21, 0x34, 0xff, 0x34, 0x21, 0x0f, 0xff, 0xad, 0x2d, 0xc0, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x38, 0x2d, 0x2d, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-		{
-			"AdjustSaturation 3x3 50",
-			src,
-			50,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
-					0x08, 0x21, 0x3b, 0xff, 0x3b, 0x21, 0x08, 0xff, 0xbd, 0x19, 0xd4, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x4c, 0x19, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-		{
-			"AdjustSaturation 3x3 100",
-			src,
-			100,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0xcc, 0x00, 0x00, 0x01, 0x00, 0xcc, 0x00, 0x02, 0x00, 0x00, 0xcc, 0x03,
-					0x00, 0x21, 0x44, 0xff, 0x44, 0x21, 0x00, 0xff, 0xd0, 0x00, 0xee, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x66, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-		{
-			"AdjustSaturation 3x3 -10",
-			src,
-			-10,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0xc1, 0x0a, 0x0a, 0x01, 0x0a, 0xc1, 0x0a, 0x02, 0x0a, 0x0a, 0xc1, 0x03,
-					0x12, 0x21, 0x31, 0xff, 0x31, 0x21, 0x12, 0xff, 0xa4, 0x39, 0xb4, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-		{
-			"AdjustSaturation 3x3 -50",
-			src,
-			-50,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0x99, 0x32, 0x32, 0x01, 0x32, 0x99, 0x32, 0x02, 0x32, 0x32, 0x99, 0x03,
-					0x19, 0x21, 0x2a, 0xff, 0x2a, 0x22, 0x19, 0xff, 0x90, 0x55, 0x99, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-		{
-			"AdjustSaturation 3x3 -100",
-			src,
-			-100,
-			&image.NRGBA{
-				Rect:   image.Rect(0, 0, 3, 3),
-				Stride: 3 * 4,
-				Pix: []uint8{
-					0x66, 0x66, 0x66, 0x01, 0x66, 0x66, 0x66, 0x02, 0x66, 0x66, 0x66, 0x03,
-					0x22, 0x22, 0x22, 0xff, 0x22, 0x22, 0x22, 0xff, 0x77, 0x77, 0x77, 0xff,
-					0x00, 0x00, 0x00, 0xff, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff,
-				},
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			got := AdjustSaturation(tc.src, tc.p)
-			if !compareNRGBA(got, tc.want, 0) {
-				t.Fatalf("got result %#v want %#v", got, tc.want)
-			}
-		})
-	}
-}
-
-func BenchmarkAdjustSaturation(b *testing.B) {
-	b.ReportAllocs()
-	for i := 0; i < b.N; i++ {
-		AdjustSaturation(testdataBranchesJPG, 50)
-	}
-}

BIN
testdata/out_saturation_m30.png


BIN
testdata/out_saturation_p30.png


+ 55 - 92
utils.go

@@ -2,10 +2,9 @@ package imaging
 
 import (
 	"image"
+	"math"
 	"runtime"
 	"sync"
-	"math"
-	"image/color"
 )
 
 // parallel processes the data in separate goroutines.
@@ -84,119 +83,83 @@ func toNRGBA(img image.Image) *image.NRGBA {
 	return Clone(img)
 }
 
-// nrgbaToHSL converts NRGBA to HSL.
-func nrgbaToHSL(c color.NRGBA) (float64, float64, float64) {
-	var h, s, l float64
+// rgbToHSL converts a color from RGB to HSL.
+func rgbToHSL(r, g, b uint8) (float64, float64, float64) {
+	rr := float64(r) / 255
+	gg := float64(g) / 255
+	bb := float64(b) / 255
 
-	r := float64(c.R) / float64(255)
-	g := float64(c.G) / float64(255)
-	b := float64(c.B) / float64(255)
+	max := math.Max(rr, math.Max(gg, bb))
+	min := math.Min(rr, math.Min(gg, bb))
 
-	min := math.Min(math.Min(r, g), b)
-	max := math.Max(math.Max(r, g), b)
+	l := (max + min) / 2
 
-	l = (max + min) / 2
+	if max == min {
+		return 0, 0, l
+	}
 
-	if min == max {
-		s = 0
-		h = 0
+	var h, s float64
+	d := max - min
+	if l > 0.5 {
+		s = d / (2 - max - min)
 	} else {
-		if l < 0.5 {
-			s = (max - min) / (max + min)
-		} else {
-			s = (max - min) / (2.0 - max - min)
-		}
-
-		if max == r {
-			h = (g - b) / (max - min)
-		} else if max == g {
-			h = 2.0 + (b-r)/(max-min)
-		} else {
-			h = 4.0 + (r-g)/(max-min)
-		}
-
-		h *= 60
+		s = d / (max + min)
+	}
 
-		if h < 0 {
-			h += 360
+	switch max {
+	case rr:
+		h = (gg - bb) / d
+		if g < b {
+			h += 6
 		}
+	case gg:
+		h = (bb-rr)/d + 2
+	case bb:
+		h = (rr-gg)/d + 4
 	}
+	h /= 6
 
 	return h, s, l
 }
 
-// hslToNRGBA converts HSL to NRGBA with A=1.
-func hslToNRGBA(h, s, l float64) color.NRGBA {
+// hslToRGB converts a color from HSL to RGB.
+func hslToRGB(h, s, l float64) (uint8, uint8, uint8) {
+	var r, g, b float64
 	if s == 0 {
-		c := uint8(l * 255)
-		return color.NRGBA{R: c, G: c, B: c, A: 1}
+		v := clamp(l * 255)
+		return v, v, v
 	}
 
-	var r, g, b float64
-	var t1, t2, tr, tg, tb float64
-
+	var q float64
 	if l < 0.5 {
-		t1 = l * (1.0 + s)
+		q = l * (1 + s)
 	} else {
-		t1 = l + s - l*s
+		q = l + s - l*s
 	}
+	p := 2*l - q
 
-	t2 = 2*l - t1
-	h = h / 360
-	tr = h + 1.0/3.0
-	tg = h
-	tb = h - 1.0/3.0
+	r = hueToRGB(p, q, h+1/3.0)
+	g = hueToRGB(p, q, h)
+	b = hueToRGB(p, q, h-1/3.0)
 
-	if tr < 0 {
-		tr++
-	} else if tr > 1 {
-		tr--
-	}
+	return clamp(r * 255), clamp(g * 255), clamp(b * 255)
+}
 
-	if tg < 0 {
-		tg++
-	} else if tg > 1 {
-		tg--
+func hueToRGB(p, q, t float64) float64 {
+	if t < 0 {
+		t++
 	}
-
-	if tb < 0 {
-		tb++
-	} else if tb > 1 {
-		tb--
+	if t > 1 {
+		t--
 	}
-
-	// Red
-	if 6*tr < 1 {
-		r = t2 + (t1-t2)*6*tr
-	} else if 2*tr < 1 {
-		r = t1
-	} else if 3*tr < 2 {
-		r = t2 + (t1-t2)*(2.0/3.0-tr)*6
-	} else {
-		r = t2
+	if t < 1/6.0 {
+		return p + (q-p)*6*t
 	}
-
-	// Green
-	if 6*tg < 1 {
-		g = t2 + (t1-t2)*6*tg
-	} else if 2*tg < 1 {
-		g = t1
-	} else if 3*tg < 2 {
-		g = t2 + (t1-t2)*(2.0/3.0-tg)*6
-	} else {
-		g = t2
+	if t < 1/2.0 {
+		return q
 	}
-
-	// Blue
-	if 6*tb < 1 {
-		b = t2 + (t1-t2)*6*tb
-	} else if 2*tb < 1 {
-		b = t1
-	} else if 3*tb < 2 {
-		b = t2 + (t1-t2)*(2.0/3.0-tb)*6
-	} else {
-		b = t2
+	if t < 2/3.0 {
+		return p + (q-p)*(2/3.0-t)*6
 	}
-
-	return color.NRGBA{R: uint8(r * 255), G: uint8(g * 255), B: uint8(b * 255)}
-}
+	return p
+}

+ 108 - 85
utils_test.go

@@ -2,7 +2,6 @@ package imaging
 
 import (
 	"image"
-	"image/color"
 	"math"
 	"runtime"
 	"testing"
@@ -131,133 +130,157 @@ func compareFloat64(a, b, delta float64) bool {
 	return math.Abs(a-b) <= delta
 }
 
-func compareUint8(a, b, delta uint8) bool {
-	if a > b {
-		return a - b <= delta
-	} else {
-		return b - a <= delta
-	}
-}
-
-var nrgbaHslTestCases = []struct {
-	rgb     color.NRGBA
+var rgbHSLTestCases = []struct {
+	r, g, b uint8
 	h, s, l float64
 }{
 	{
-		rgb: color.NRGBA{R: 255, G: 0, B: 0},
-		h:   0,
-		s:   1,
-		l:   0.5,
+		r: 255,
+		g: 0,
+		b: 0,
+		h: 0.000,
+		s: 1.000,
+		l: 0.500,
 	},
 	{
-		rgb: color.NRGBA{R: 191, G: 191, B: 0},
-		h:   60,
-		s:   1,
-		l:   0.375,
+		r: 191,
+		g: 191,
+		b: 0,
+		h: 0.167,
+		s: 1.000,
+		l: 0.375,
 	},
 	{
-		rgb: color.NRGBA{R: 0, G: 128, B: 0},
-		h:   120,
-		s:   1,
-		l:   0.25,
+		r: 0,
+		g: 128,
+		b: 0,
+		h: 0.333,
+		s: 1.000,
+		l: 0.251,
 	},
 	{
-		rgb: color.NRGBA{R: 128, G: 255, B: 255},
-		h:   180,
-		s:   1,
-		l:   0.75,
+		r: 128,
+		g: 255,
+		b: 255,
+		h: 0.500,
+		s: 1.000,
+		l: 0.751,
 	},
 	{
-		rgb: color.NRGBA{R: 128, G: 128, B: 255},
-		h:   240,
-		s:   1,
-		l:   0.75,
+		r: 128,
+		g: 128,
+		b: 255,
+		h: 0.667,
+		s: 1.000,
+		l: 0.751,
 	},
 	{
-		rgb: color.NRGBA{R: 191, G: 64, B: 191},
-		h:   300,
-		s:   0.5,
-		l:   0.5,
+		r: 191,
+		g: 64,
+		b: 191,
+		h: 0.833,
+		s: 0.498,
+		l: 0.500,
 	},
 	{
-		rgb: color.NRGBA{R: 160, G: 164, B: 36},
-		h:   61.8,
-		s:   0.638,
-		l:   0.393,
+		r: 160,
+		g: 164,
+		b: 36,
+		h: 0.172,
+		s: 0.640,
+		l: 0.392,
 	},
 	{
-		rgb: color.NRGBA{R: 65, G: 27, B: 234},
-		h:   251.1,
-		s:   0.832,
-		l:   0.511,
+		r: 65,
+		g: 27,
+		b: 234,
+		h: 0.697,
+		s: 0.831,
+		l: 0.512,
 	},
 	{
-		rgb: color.NRGBA{R: 30, G: 172, B: 65},
-		h:   134.9,
-		s:   0.707,
-		l:   0.396,
+		r: 30,
+		g: 172,
+		b: 65,
+		h: 0.374,
+		s: 0.703,
+		l: 0.396,
 	},
 	{
-		rgb: color.NRGBA{R: 240, G: 200, B: 14},
-		h:   49.5,
-		s:   0.893,
-		l:   0.497,
+		r: 240,
+		g: 200,
+		b: 14,
+		h: 0.137,
+		s: 0.890,
+		l: 0.498,
 	},
 	{
-		rgb: color.NRGBA{R: 180, G: 48, B: 229},
-		h:   283.7,
-		s:   0.775,
-		l:   0.542,
+		r: 180,
+		g: 48,
+		b: 229,
+		h: 0.788,
+		s: 0.777,
+		l: 0.543,
 	},
 	{
-		rgb: color.NRGBA{R: 237, G: 118, B: 81},
-		h:   14.3,
-		s:   0.817,
-		l:   0.624,
+		r: 237,
+		g: 119,
+		b: 81,
+		h: 0.040,
+		s: 0.813,
+		l: 0.624,
 	},
 	{
-		rgb: color.NRGBA{R: 254, G: 248, B: 136},
-		h:   56.9,
-		s:   0.991,
-		l:   0.765,
+		r: 254,
+		g: 248,
+		b: 136,
+		h: 0.158,
+		s: 0.983,
+		l: 0.765,
 	},
 	{
-		rgb: color.NRGBA{R: 25, G: 203, B: 151},
-		h:   162.4,
-		s:   0.779,
-		l:   0.447,
+		r: 25,
+		g: 203,
+		b: 151,
+		h: 0.451,
+		s: 0.781,
+		l: 0.447,
 	},
 	{
-		rgb: color.NRGBA{R: 54, G: 38, B: 152},
-		h:   248.3,
-		s:   0.601,
-		l:   0.373,
+		r: 54,
+		g: 38,
+		b: 152,
+		h: 0.690,
+		s: 0.600,
+		l: 0.373,
 	},
 	{
-		rgb: color.NRGBA{R: 126, G: 126, B: 184},
-		h:   240.5,
-		s:   0.29,
-		l:   0.607,
+		r: 126,
+		g: 126,
+		b: 184,
+		h: 0.667,
+		s: 0.290,
+		l: 0.608,
 	},
 }
 
-func TestNrgbaToHSL(t *testing.T) {
-	for _, tc := range nrgbaHslTestCases {
+func TestRGBToHSL(t *testing.T) {
+	for _, tc := range rgbHSLTestCases {
 		t.Run("", func(t *testing.T) {
-			h, s, l := nrgbaToHSL(tc.rgb)
-			if !compareFloat64(h, tc.h, 1) || !compareFloat64(s, tc.s, 1) || !compareFloat64(l, tc.l, 1) {
-				t.Fatalf("with %v, expected (%.2f, %.2f, %.2f) but got (%.2f, %.2f, %.2f)", tc.rgb, h, s, l, tc.h, tc.s, tc.l)
+			h, s, l := rgbToHSL(tc.r, tc.g, tc.b)
+			if !compareFloat64(h, tc.h, 0.001) || !compareFloat64(s, tc.s, 0.001) || !compareFloat64(l, tc.l, 0.001) {
+				t.Fatalf("(%d, %d, %d): got (%.3f, %.3f, %.3f) want (%.3f, %.3f, %.3f)", tc.r, tc.g, tc.b, h, s, l, tc.h, tc.s, tc.l)
 			}
 		})
 	}
 }
 
-func TestHslToNRGBA(t *testing.T) {
-	for _, tc := range nrgbaHslTestCases {
+func TestHSLToRGB(t *testing.T) {
+	for _, tc := range rgbHSLTestCases {
 		t.Run("", func(t *testing.T) {
-			rgb := hslToNRGBA(tc.h, tc.s, tc.l)
-			if !compareUint8(rgb.R, tc.rgb.R, 1) || !compareUint8(rgb.G, tc.rgb.G, 1) || !compareUint8(rgb.B, tc.rgb.B, 1) {
-				t.Fatalf("expected %+v but got %+v", tc.rgb, rgb)
+			r, g, b := hslToRGB(tc.h, tc.s, tc.l)
+			if r != tc.r || g != tc.g || b != tc.b {
+				t.Fatalf("(%.3f, %.3f, %.3f): got (%d, %d, %d) want (%d, %d, %d)", tc.h, tc.s, tc.l, r, g, b, tc.r, tc.g, tc.b)
 			}
 		})
 	}