Question

I'm trying to figure out how to sample all of the pixels in an image and generate a palette of colors from it, something like this or this. I have no idea where to even begin. Can anyone point me in the right direction?

__EDIT: __

This is what I've ended up with so far:

I used this Pixelate function to get large block sections like joe_coolish suggested. It's working perfectly and giving me a pretty good sample of colors to work with (this is from the windows sample jelly fish picture):

Now, if someone could help me get the 5 most distinct colors (darkest blue, lightest blue, orange, gray and peach(?)), I would love you forever. I really don't understand how to average or add colors together. I also can't figure out how to tell if a color is similar programatically, there are some many numbers and variables in you explanations that I get lost trying to figure out what's doing what to whom.

Was it helpful?

Solution

The answers involving the code show you how to get the full palette. If you want to get the average colors like in the websites you posted, this is how I would do it.

Source Image:

Source

First, I would average the colors by applying a lowpass filter (something like a Gaussian Blur)

enter image description here

That way you are limiting the total palette. From there I would divide the screen into N blocks (N being the total number of pixels you want in your palette)

enter image description here

From there, target each block and iterate over each pixel, and get the average pixel for that block and add it to your palette index. The result is something like this:

enter image description here

That way your palette is limited and you get the average colors from the different regions. You can do all of that in code, and if you'd like some help with that, let me know and I'll post some. This is just the High Level "what I would do".

OTHER TIPS

First, take the pixels in the picture: (assumes using System.Drawing.Imaging; and using System.Runtime.InteropServices)

Bitmap b = new Bitmap(myImage);
BitmapData bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, ImageFormat.Format32Bpp);
int[] arr = new int[bd.Width * bd.Height - 1];
Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
b.UnlockBits(bd);

Then you can create your palette:

var distinctColors = arr.Distinct();

Optionally: Eliminate similar colors until you have your preferred palette size. Here's how you might do that (though this is most definitely not the most efficient or accurate way, just the simplest):

var dc = distinctColors.toArray(); // int dc[] = distinctColors.toArray() is what it used to be
int cmIndex1 = -1;
int cmIndex2 = -1;
int cmDiff = -1;
for (int i = 0; i < dc.length; i++) {
    Color c1 = Color.FromArgb(dc[i]);
    for (int j = i + 1; j < dc.length; j++) {
        Color c2 = Color.FromArgb(dc[j]);
        // Note: you might want to include alpha below
        int diff = Math.Abs(c1.R - c2.R) + Math.Abs(c1.G - c2.G) + Math.Abs(c1.B - c2.B);
        if (cmDiff < 0 || diff < cmDiff) {
            cmIndex1 = i;
            cmIndex2 = j;
            cmDiff = diff;
        }
    }
}
// Remove the colors, replace with average, repeat until you have the desired number of colors

It is likely in any rich image that most of your colors will be unique in some way. It would follow, then, that fetching distinct colors will likely not help you accomplish your goal.

I recommend inspecting the HSV values for each pixel in your image. I'll leave you to countless online examples of retrieving images as arrays of HSV values.

With your HSV values, you can calculate clusters of prominent hues by create an integer array of 256 hue counts, computing a histogram of hues in your image data. You can determine prominent hues by finding clusters of 4-6 sequential hues with a high count sum.

After picking several prominent hues, subdivide pixels of those hues into another histogram measuring saturation, and pick out prominent clusters, and so on.

Rough Example

The code below makes some attempt to help identify prominent hues. There are most likely other awesome ways to do this; however, this may provide some ideas.

First, I get all the image colors as an array of Color objects, like so:

private static Color[] GetImageData(Image image)
{
    using (var b = new Bitmap(image))
    {
        var bd = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        byte[] arr = new byte[bd.Width * bd.Height * 3];
        Color[] colors = new Color[bd.Width * bd.Height];
        Marshal.Copy(bd.Scan0, arr, 0, arr.Length);
        b.UnlockBits(bd);

        for (int i = 0; i < colors.Length; i++)
        {
            var start = i*3;
            colors[i] = Color.FromArgb(arr[start], arr[start + 1], arr[start + 2]);
        }

        return colors;
    }
}

You might consider validating that I got the order of RGB in the Color.FromArgb method call in the correct order.

Next, I stash aside a utility method for converting to HSV. In my example, I'll only work with hues, but here's a full working example of the conversion:

private static void ColorToHSV(Color color, out int hue, out int saturation, out int value)
{
    int max = Math.Max(color.R, Math.Max(color.G, color.B));
    int min = Math.Min(color.R, Math.Min(color.G, color.B));

    hue = (int)(color.GetHue() * 256f / 360f);
    saturation = (max == 0) ? 0 : (int)(1d - (1d * min / max));
    value = (int)(max / 255d);
}

Finally, I build the hue histogram, define a width of hues (say, 9 hues) in which to aggregate counts together, and then I report the counts to the console.

private static void ProcessImage(Color[] imagecolors)
{
    var hues = new int[256];
    var hueclusters = new int[256];
    int hue, saturation, value;

    // build hue histogram.
    foreach (var color in imagecolors) {
        ColorToHSV(color, out hue, out saturation, out value);
        hues[hue]++;
    }

    // calculate counts for clusters of colors.
    for (int i = 0; i < 256; i++) {
        int huecluster = 0;
        for (int count = 0, j = i; count < 9; count++, j++) {
            huecluster += hues[j % 256];
        }

        hueclusters[(i + 5) % 256] = huecluster;
    }

    // Print clusters on the console
    for (int i = 0; i < 256; i++) {
        Console.WriteLine("Hue {0}, Score {1}.", i, hueclusters[i]);
    }
}

I've made no attempt to filter down to which hues to choose. You may need to consider some heuristics rather than blindly picking the top so-many counts, because you probably want to pick hues that are somewhat separated on the color spectrum. I haven't time to explore this any further, but I hope this provides some insight into a strategy you can consider.

I started here:

System.Drawing.Image img = System.Drawing.Bitmap.FromFile("file");
System.Drawing.Imaging.ColorPalette palette = img.Palette;
foreach (Color color in palette.Entries)
{
  //...
}

I'm going to describe the best approach at a very high level.

First you construct a histogram of colors in the picture and their frequency.

You end up with a list of all colors in the image, you can use data clustering to find candidate colors to merge. Colors that are merged together into a weighted average based on the frequency of the original colors.

This way you can incrementally decrease the palette to a desired fidelity, while preserving high contrast but fine details and only losing fidelity where the gradients are much more subtle.

Once you have the reduced palette, you recolor the picture using the nearest neighbor color that is in the palette.

The K-Means clustering algorithm works well for this problem. It does a great job of extracting centroids of image color clusters, but be aware that its non-deterministic behavior makes determining the actual prominence of each cluster difficult.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top