Skip to content

Commit e1f2ad7

Browse files
committed
feat: use perceptual color comparison in pi.DecodeCanvas
Use perceptual color comparison in pi.DecodeCanvas for more accurate matching.
1 parent 156bb6b commit e1f2ad7

File tree

6 files changed

+54
-6
lines changed

6 files changed

+54
-6
lines changed

internal/color.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ func (c *ClosestColorPicker[RGB, Color]) IndexInPalette(rgba color.Color) (Color
3030
for i, paletteCol := range c.Palette {
3131
r, g, b, _ := rgba.RGBA()
3232
r, g, b = r&0xff, g&0xff, b&0xff
33-
r2, g2, b2 := paletteCol>>16&0xff, paletteCol>>8&0xff, paletteCol&0xff
34-
if r == uint32(r2) && g == uint32(g2) && b == uint32(b2) {
33+
r2, g2, b2 := uint32(paletteCol>>16&0xff),
34+
uint32(paletteCol>>8&0xff),
35+
uint32(paletteCol&0xff)
36+
if r == r2 && g == g2 && b == b2 {
3537
// found perfect match. Short circuit
3638
closestColor = Color(i)
3739
break
3840
}
39-
distance := math.Sqrt(
40-
math.Pow(float64(r2-RGB(r)), 2) +
41-
math.Pow(float64(g2-RGB(g)), 2) +
42-
math.Pow(float64(b2-RGB(b)), 2))
41+
distance := perceptualColorDistance(r, g, b, r2, g2, b2)
42+
4343
if distance < smallestDistance {
4444
smallestDistance = distance
4545
closestColor = Color(i)
@@ -50,3 +50,10 @@ func (c *ClosestColorPicker[RGB, Color]) IndexInPalette(rgba color.Color) (Color
5050

5151
return closestColor, nil
5252
}
53+
54+
func perceptualColorDistance(r1, g1, b1, r2, g2, b2 uint32) float64 {
55+
rd := float64(r1 - r2)
56+
gd := float64(g1 - g2)
57+
bd := float64(b1 - b2)
58+
return math.Sqrt(0.299*rd*rd + 0.587*gd*gd + 0.114*bd*bd)
59+
}
161 Bytes
Loading

internal/test/decode/indexed.png

161 Bytes
Loading
133 Bytes
Loading

internal/test/decode/rgb.png

133 Bytes
Loading

surface_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
package pi_test
55

66
import (
7+
_ "embed"
8+
"github.com/elgopher/pi/pitest"
9+
"github.com/stretchr/testify/require"
710
"math/rand"
811
"testing"
912

@@ -12,6 +15,44 @@ import (
1215
"github.com/elgopher/pi"
1316
)
1417

18+
var (
19+
//go:embed "internal/test/decode/indexed.png"
20+
indexedPNG []byte
21+
//go:embed "internal/test/decode/rgb.png"
22+
rgbPNG []byte
23+
//go:embed "internal/test/decode/indexed-brighter.png"
24+
brighterIndexedPNG []byte
25+
//go:embed "internal/test/decode/rgb-brighter.png"
26+
rgbBrighterPNG []byte
27+
)
28+
29+
func TestDecodeCanvasOrErr(t *testing.T) {
30+
tests := map[string][]byte{
31+
"indexed png when palette is the same": indexedPNG,
32+
"RGB png": rgbPNG,
33+
"indexed png when palette is slightly brighter": brighterIndexedPNG,
34+
"RGB png when palette is slightly brighter": rgbBrighterPNG,
35+
}
36+
37+
for testName, png := range tests {
38+
t.Run(testName, func(t *testing.T) {
39+
pi.Palette = pi.DecodePalette(indexedPNG)
40+
// when
41+
canvas, err := pi.DecodeCanvasOrErr(png)
42+
// then
43+
require.NoError(t, err)
44+
expected := pi.NewCanvas(4, 4)
45+
expected.SetAll(
46+
0, 1, 2, 3,
47+
4, 5, 6, 7,
48+
8, 9, 10, 11,
49+
12, 13, 14, 15,
50+
)
51+
pitest.AssertSurfaceEqual(t, expected, canvas)
52+
})
53+
}
54+
}
55+
1556
func TestSet(t *testing.T) {
1657
t.Run("should be noop when outside surface", func(t *testing.T) {
1758
width := 2

0 commit comments

Comments
 (0)