Microsoft-ui-xaml: Proposal: Accent color aware brushes (SolidColorContrastBrush).

Created on 8 Apr 2020  路  8Comments  路  Source: microsoft/microsoft-ui-xaml

Describe the bug

The text is black instead of white (Foreground Brush) in an AppBarToggleButton. This is a problem with the default style.
image

Steps to reproduce the bug
Add an AppBarToggleButton to a CommandBar and toggle it so IsChecked = True.

            <CommandBar
                Background="Transparent"
                HorizontalAlignment="Right"
                VerticalAlignment="Stretch"
                ClosedDisplayMode="Compact"
                DefaultLabelPosition="Right">
                <CommandBar.PrimaryCommands>
                    <AppBarToggleButton
                        x:Name="ListViewToggleButton"
                        Label="&lt;List&gt;">
                        <AppBarToggleButton.Icon>
                            <FontIcon
                                Glyph="&#xE8BC;" />
                        </AppBarToggleButton.Icon>
                    </AppBarToggleButton>
                    <AppBarToggleButton
                        x:Name="TableViewToggleButton"
                        Label="&lt;Table&gt;">
                        <AppBarToggleButton.Icon>
                            <FontIcon
                                Glyph="&#xE8A9;" />
                        </AppBarToggleButton.Icon>
                    </AppBarToggleButton>
                </CommandBar.PrimaryCommands>
            </CommandBar>

Expected behavior
Should behave as other toggle buttons:

Screenshots

Bug
image

Expected Style
image

Note that the ToggleButton already works correctly. How the Foreground brush is modified needs to be understood.

Version Info

NuGet package version:
Microsoft.UI.Xaml 2.3.200213001
Microsoft Windows 10 Home Version 10.0.18362 Build 18362


| Windows 10 version | Saw the problem? |
| :--------------------------------- | :-------------------- |
| May 2019 Update (18362) | Yes |


| Device form factor | Saw the problem? |
| :-------------------- | :------------------- |
| Desktop | Yes |
| Mobile | Not tested |
| Xbox | Not tested |
| Surface Hub | Not tested |
| IoT | Not tested |

area-Commanding area-DesignDiscussion area-Styling feature proposal team-Controls

Most helpful comment

If there was a ForegroundContrastBrush which took the pixel colour behind the element, and chose to use the light or dark specified colour - these situations would be eliminated.

All 8 comments

Alternatively, adhere to the design (if it's up to date):

List Items

The issue is that we do not change the colors based on the picked accent color. @chigy as FYI. This is currently by design. @robloo would you like to create a proposal to figure out a way to fix this ?

@ranjeshj Makes sense that the foreground brush for text doesn't change based on accent color. I wonder how the standard ToggleButton works though? That might give a good indication how to fix this. I might be misunderstanding something here though.

As far as knowing when to switch between light/dark text based on background color, this is the algorithm I've used elsewhere in my apps. It's worked well so far.

First determine what I call brightness:

        /// <summary>
        /// Gets the perceived brightness or intensity of the color.
        /// This value is normalized between zero (black) and one (white).
        /// </summary>
        /// <remarks>
        /// 
        /// The base formula for luminance is from Rec. ITU-R BT.601-7 ():
        ///    https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
        ///    Section 2.5.1 Construction of luminance (EY) and colour-difference (ER鈥揈Y) and (EB鈥揈Y) signals.
        /// 
        /// This formula accounts for physiological aspects: the human eyeball is most sensitive to green light, 
        /// less to red and least to blue.
        /// 
        ///    Luminance = (0.299 * Red) + (0.587 * Green) + (0.114 * Blue)
        /// 
        /// This formula is also recommended by the W3C Techniques For Accessibility Evaluation And Repair Tools
        ///    https://www.w3.org/TR/AERT/#color-contrast
        /// 
        /// Contrary to the above formula, this is not called luminance and is called brightness instead.
        /// This value is not measurable and is subjective which better fits the definition of brightness:
        ///    - Luminance is the luminous intensity, projected on a given area and direction.
        ///      Luminance is an objectively measurable attribute. The unit is 'Candela per Square Meter' (cd/m2).
        ///    - Brightness is a subjective attribute of light. The monitor can be adjusted to a level of light 
        ///      between very dim and very bright. Brightness is perceived and cannot be measured objectively.
        /// 
        /// Other useful information can be found here:
        ///    http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
        /// 
        /// </remarks>
        public double Brightness
        {
            get { return (((0.299 * this._R) + (0.587 * this._G) + (0.114 * this._B)) / 255); }
        }

Then, with this brightness value, determine the text brush color:

if (backgroundBrushColor.Brightness() > 0.5)
{
    // Bright colors, use a dark font color
    someSontrol.Foreground = darkBrush;
}
else
{
    // Dark colors, use a light font color
    someSontrol.Foreground = lightBrush;
}

I still am looking forward to understanding how the ToggleButton solves this problem. Otherwise, I'll wait until WinUI 3.0 is open sourced and take a look myself. The same solution should be applied to AppBarToggleButton.

My guess is it is overriding the Foreground Brush based on the contrast with the Background Brush. My simple logic above would also work and could be extended to invert the Foreground color if it was too dark.

Some pseudocode:

if (backgroundBrushColor.Brightness() > 0.5)
{
    // Bright background, use a dark font color
    if (foregroundBrushColor.Brightness() > 0.5)
    {
        // The existing foreground is too bright so must be inverted
        someControl.Foreground = foregroundBrushColor.Invert();
    }
    else
    {
        someSontrol.Foreground = foregroundBrushColor;
    }
}
else
{
    // Dark background , use a light font color
    // ... similar logic as above
}

If there was a ForegroundContrastBrush which took the pixel colour behind the element, and chose to use the light or dark specified colour - these situations would be eliminated.

@mdtauk,

If there was a ForegroundContrastBrush which took the pixel colour behind the element, and chose to use the light or dark specified colour - these situations would be eliminated

Yes, that would probably be the most robust solution. Something like the RevealBorderBrush which is dynamic and controlled by the platform. However, I wish all of this wasn't done with an unknown behind-the-scenes implementation. It would be ideal to somehow do all of this in XAML itself without 'magic' brushes. However, the precedent is already there and it would solve this problem quite well.

I have suggested this kind of brush before on other issues. You would create a brush resource with a light and dark value. So the LightColor could use a dark black value, and the DarkColor could use a light white colour.

The colour used for the element would then automatically use which value gives the best contrast.

@mdtauk I like where you are going with that idea. There could be a new brush and during composition one of two defined colors would be used.

Below is my proposal.

Brush SolidColorContrastBrush 

SolidColorContrastBrush (Color lightColor, Color darkColor) // contrastThreshold defaults to 0.5
SolidColorContrastBrush (Color lightColor, Color darkColor, double contrastThreshold)

/// <summary>
/// The light color used when the background is darker than the defined <see cref="ContrastThreshold "/>
/// </summary>
public Color LightColor { get; }

/// <summary>
/// The dark color used when the background is lighter than the defined <see cref="ContrastThreshold "/>
/// </summary>
public Color DarkColor { get; }

/// <summary>
/// The threshold used to determine when to switch between the light and dark color.
/// The threshold represents perceived brightness normalized between zero (black) and one (white).
/// </summary>
/// <remarks>
/// 
/// The base formula for luminance is from Rec. ITU-R BT.601-7 ():
///    https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.601-7-201103-I!!PDF-E.pdf
///    Section 2.5.1 Construction of luminance (EY) and colour-difference (ER鈥揈Y) and (EB鈥揈Y) signals.
/// 
/// This formula accounts for physiological aspects: the human eyeball is most sensitive to green light, 
/// less to red and least to blue.
/// 
///    Luminance = (0.299 * Red) + (0.587 * Green) + (0.114 * Blue)
 /// 
/// This formula is also recommended by the W3C Techniques For Accessibility Evaluation And Repair Tools
///    https://www.w3.org/TR/AERT/#color-contrast
/// 
/// Contrary to the above formula, this is not called luminance and is called brightness instead.
/// This value is not measurable and is subjective which better fits the definition of brightness:
///    - Luminance is the luminous intensity, projected on a given area and direction.
///      Luminance is an objectively measurable attribute. The unit is 'Candela per Square Meter' (cd/m2).
///    - Brightness is a subjective attribute of light. The monitor can be adjusted to a level of light 
///      between very dim and very bright. Brightness is perceived and cannot be measured objectively.
/// 
/// Other useful information can be found here:
///    http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx
/// 
/// Brightness = (((0.299 * this._R) + (0.587 * this._G) + (0.114 * this._B)) / 255)
/// </remarks>
public double ContrastThreshold { get; }
Was this page helpful?
0 / 5 - 0 ratings