Finding the most used color in a WriteableBitmap

I had a simple idea which would require the most used color in an Image. Sounds like something simple, just walk through all the pixels and for each occurring color track the occurrence count. I started with a bit of googling to find if someone else already found a solution. Indeed someone answered a similar question on codeproject.

 

It’s not compatible with the Windows Universal Platform I’m working on right now, however rewriting it is not that difficult. Just had to use the GetPixel operation from the WriteableBitmapEx. A working solution, but it was slow, really slow. It took about 40 seconds for a bitmap of 182 by 182, way too slow be acceptable for my usage. I did look at possible optimizations using Task that run on the ThreadPool but that didn’t help me anything, accessing the WriteableBitmap via a different thread is not supported.

WriteableBitmapEx to the Rescue

So I thought again, why wouldn’t WriteableBitmapEx have a solution I can use to do the calculation? And indeed it has a solution, a ForEach method. After rewriting my code to make use of the ForEach method, it runs fast, really fast. It now takes 0.03 seconds for a bitmap of 182 by 182. That is more than a factor 1000 faster.

I did not investigate what’s happening inside the ForEach implementation of WriteableBitmapEx, but it is better than the double looping over the x and y axis to get all pixels. Don’t forget to add the NuGet Package of WriteableBitmapEx to your project.

public static Color GetMostUsedColor(this WriteableBitmap bitmap)
{
    var colorUsage = new Dictionary<int, int>();

    bitmap.ForEach((x, y, colorin) =>
    {
        var pixelColor = colorin.ToArgb();

        if (colorUsage.Keys.Contains(pixelColor))
        {
            colorUsage[pixelColor]++;
        }
        else
        {
            colorUsage.Add(pixelColor, 1);
        }
        return colorin;
    });

    var sortedColorUsage = colorUsage
        .OrderByDescending(x => x.Value)
        .ToDictionary(x => x.Key, x => x.Value);

    return sortedColorUsage.First().Key.FromArgb();
}

public static int ToArgb(this Color color)
{
    return (color.A << 24) | (color.R << 16) | (color.G << 8) | color.B;
}

public static Color FromArgb(this int argb)
{
    return Color.FromArgb((byte)(argb >> 24),
        (byte)(argb >> 16),
        (byte)(argb >> 8),
        (byte)(argb));
}

Bonus: Perceived brightness, is the color bright or dark?

My intention is to use the most-used-color as a background color. Because the image I want to base this on can be anything, I need to know a good color to use for text on top of this color. Someone else already wrote a good working solution about the perceived brightness and explains how he came to that solution.

public static Color BestForeground(this Color backgroundColor)
{
    return backgroundColor.PerceivedBrightness() > 130 ? Colors.Black : Colors.White;
}

public static int PerceivedBrightness(this Color color)
{
    return (int)Math.Sqrt(
        color.R * color.R * .241 +
        color.G * color.G * .691 +
        color.B * color.B * .068);
}