I needed to know how many pixels of each color an image contains. Super ultimate goal would be to combine all like pixels to one blob with a number representing the color and a legend showing what color is what number. Sound familiar? Basically paint by number. I don't need to go that far, even if I could. I just need the number of pixels in each (roughly) color.
My solution was to create a function that goes through each pixel in an image and check the RGB values. I imposed a limit of 1000x1000 size because I am using this for a real world object and by the time I get to one million pieces it just won't be feasible. I also added some ability to combine similar colors. That is mostly what this post will cover.
Technically I think it would work with a larger image but I have a strong suspicion that the code would blow up with out of memory errors somewhere along the way. That is another reason to limit it to smaller images.
I started with this simple Nintendo screenshot from Mario.
The original image is 256 x 240 , 61,440 pixels. I went with this screenshot because it was the image I wanted the color counts on initially. I chose something from that era though because the color palette is pretty small and nostaligic.
I opened up Paint.NET and rescaled the height and width by 200%. Making it 512 x 480, 245,760 pixels. Notice that is far more pixels than the original image. Some of these are going to be gradients as it fills in pixels that did not exist before.
The output for the original image looks like this:
The image after scaling had 16,179 different groups of colors. Not very easy to screenshot. Also not very practical as many of those colors were very similar (below image).
I knew that this would happen because the RGB values were looking for exact matches. The solution I came up with was to add a threshold parameter that would determine how far from the current color to search for and then combine the results instead of adding another color. Adding a threshold of just 50 reduces the results to 34 colors.
The code to reduce isn't even perfect but it's good enough. The problem is that the RGB values might vary wildly but visually they will be similar. For example, rgba(52,129,5,1) and rgba(1,123,1,1) are visually close enough to me but are different enough when looking at these RGB values. There are going to be some colors that are going to be much more obvious but I can manually combine the colors when the results get practical enough.
The code first checks if the color (plus or minus) the threshold for each component of RGB. The problem is that not all the components are the same value all the time. This was catching colors that were similar if all three parts were withing the range at the same time.
1,2,1 == 2,3,2
1,2,100 != 1,22,100
I added another check before declaring it a new color by checking if one part was outside of a secondary threshold with a minimum of 50. I chose 50 because I could not see the difference between (0, 0, 150) and (0,0,100), then it would check against the two values that were not that part only.
0,0,50 == 0,0,100
This worked because the only value that was outside the threshold range was the blue (or red or green). This would rerun the check ignoring blue. Just comparing red and green (or red/blue, or green/blue, etc) in the threshold check.
This still isn't perfect but it's much better. Initially I was worried that looping through pixels individually would be too slow but this hasn't been an issue with the images I am working with.
If it's super confusing don't worry, I didn't want to have a ridiculous length article to explain it in the most detail. Just play around with it a bit.