简体   繁体   中英

From a list of colors, how can I get a light or dark color that is visible over each color simultaneously?

In my app, I allow the user to choose up to three colors to use as a 'theme color'. These colors are displayed as a LinearGradientBrush (or SolidColorBrush if one color is chosen) under the status bar. I need a light or dark color (not necessarily only black or white) for the status bar foreground color. This will be more complex than just determining if white or black should be used, but including colors like dark gray (IE if a user chooses white and black for their theme, my current algorithm chooses black as the foreground which can't be seen over the black they chose). Also, the StatusBar.ForegroundColor ignores the alpha channel, so I can't just change the opacity. How can I do this?

Thanks

Contrast color can be determine by the opposite of HSB color

For black and white only, you can just use

ForeColor = BackColor.GetBrightness() > 0.4 ? Color.Black : Color.White;

You can calculate the brightness of a Color from it's R, G, and B values then pick a brush based on that. I've used the following converter to pick a Foreground color that contrasts with the bound Background color.

public class ColorConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        Color c = (Color)value;
        int brightness = (int)(c.R * 0.30 + c.G * 0.59 + c.B * 0.11);
        if (brightness < 127)
            return App.Current.Resources["LightBrush"];
        else
            return App.Current.Resources["DarkBrush"];
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

I used this for a color picker bound to all of the named colors in the Colors class. If you target only a few theme colors then you could hand-craft a table of tinted colors that still maintain contrast rather than forcing everything to black and white.

For more colors you could try converting from RGB to HSL, modifying the luminosity while keeping the Hue, and then converting back, but that may be more trouble than it's worth and you can run out of gamut while round tripping and get poor results.

A couple of years ago I created something for a similar problem such that given a color it would return the color that is opposite it on the color wheel .

I used it to create a second, or complimentary, color brush for use in apps where I wanted more than just the single, user selected, system wide accent color but wanted to ensure that the colors would work together and not clash.

Maybe you could extend this idea to three colors.

The code is on GitHub: https://github.com/mrlacey/WPMisc/blob/master/AccentComplimentBrush.cs

I did some playing around and I think I got a method that work perfectly. I started with one algorithm from this question , and mixed it with similar code from Matt Lacey's and Rob Caplan's answers, cleaned it up and modified it to work with a list of colors. I think it works just fine.

private static Color DetermineForegroundColor(List<Color> colors) {
            double r = 0, g = 0, b = 0;
            foreach (Color color in colors) {
                r += color.R;
                g += color.G;
                b += color.B;
            }
            if (r > g && r > b) {
                Debug.WriteLine("First condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)r,
                    G = (byte)r,
                    B = (byte)r
                };
            }
            else if (g > r && g > b) {
                Debug.WriteLine("Second condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)g,
                    G = (byte)g,
                    B = (byte)g
                };
            }
            else if (b > r && b > g) {
                Debug.WriteLine("Third condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)b,
                    G = (byte)b,
                    B = (byte)b
                };
            }
            else if (r == g) {
                Debug.WriteLine("Fourth condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)b,
                    G = (byte)b,
                    B = (byte)b
                };
            }
            else if (r == b) {
                Debug.WriteLine("Fifth condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)g,
                    G = (byte)g,
                    B = (byte)g
                };
            }
            else if (g == b) {
                Debug.WriteLine("Sixth condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = (byte)r,
                    G = (byte)r,
                    B = (byte)r
                };
            }
            else {
                Debug.WriteLine("No condition matched ({0}, {1}, {2})", (byte)r, (byte)g, (byte)b);
                return new Color() {
                    R = 255,
                    G = 255,
                    B = 255
                };
            }
        }

I might replace the large if/else-if/else blocks with a switch/case block in the future to make it less bulky.

Try either:

  • Invert each RGB value, and then possibly adjust to a closest colour from a set of predefined colours.

OR

  • OR Multiple each value of the RGB by a given amount (say .25 - .5). If > 128 multiply and then subtract the difference twice, and if < 128 just multiple.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM