[C#] Precision of Color class values when used with Dictionary and HashSet

Godot Version

Godot 4.5.1 Stable Mono

Question

Hello. I’m working on a strategy map game and I wanted to use a colored map for efficient map state look-up (games like Victoria 3 and Europa Universalis 5 use the same method). However what I encountered is that if I take the base Godot Color class/struct in C# the precision isn’t consistent or the Dictionary and HashSet data structures don’t use correct functions to evaluate the individual colors.

HashSet<Color> colorSet = []
Image image = generateMapImage(data, colorSet){...} // Generates an image from the data where every cell is saved in a json and each cell is assigned a unique color that is then put into the color set.
ImageTexture imageTexture = ImageTexture(image)
mapImageTextureRect.SetTexture(ImageTexture)

...

// Getting the color on mouse press
Color pixelColor = mapImageTextureRect.GetTexture().GetImage().GetPixel(mouseX, mouseY) // This retrieves close but not quite the same color - I understand this is probably due to floating point precision but it's important for later

colorSet.contains(pixelColor) // Always returns false because the even if the color is close enough due to the imprecision it evaluates as not the same.

My question is: Is this intended function or should I file a bug report for the Color class ?

By default, HashSet (as well as any other C# collection) uses object.Equals to compare keys. This means if the values are not exactly the same then it won’t find it.

What you probably want is to specify a custom comparer in your HashSet so it considers similar colors to be the same key, here’s how you can do that:

HashSet<Color> colorSet = new HashSet<Color>(comparer: new MyColorEqualityComparer());

// Somewhere else:
class MyColorEqualityComparer : IEqualityComparer<Color>
{
    public bool Equals(Color x, Color y)
    {
        // Custom code to check if the colors are similar enough
        // to be considered the same key.
        return x.IsEqualApprox(y);
    }

    public int GetHashCode(Color obj)
    {
        // This is important too, returning the same hash doesn't
        // necessarily imply equality. But returning a different hash
        // means they are not equal and will skip calling `Equals`.
        return 0;
    }
}

If the reason the colors are not considered equal is just floating point precision as you suspect, then using IsEqualApprox should be enough, but you may also want to implement your own algorithm that is more forgiving (for example CIEDE2000).

1 Like

Thanks I already implemented an algorithm that goes over the colors, picks highly probable matches and then returns a highest probability match. But I’ll check this algorithm out :+1: