Is there a way to affect the interpolation between translucent colours in WPF?

53 views Asked by At

As discussed in this question I would have expected the following code to result in two identical looking gradients:

<Border Background="Black">
    <StackPanel>
        <Border x:Name="withOpacity" Height="32">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                    <GradientStop Offset="0" Color="#ff222222" />
                    <GradientStop Offset="1" Color="#33ff8000" />
                </LinearGradientBrush>
            </Border.Background>
        </Border>
        <Border x:Name="withoutOpacity" Height="32">
            <Border.Background>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                    <GradientStop Offset="0" Color="#ff222222" />
                    <GradientStop Offset="1" Color="#ff331a00" />
                </LinearGradientBrush>
            </Border.Background>
        </Border>
    </StackPanel>
</Border>

The visible start and end colours of both gradients are the same (in the second gradient the end colour was calculated by blending the end colour of the first one with the black background at the given opacity of #33, i.e. 20%). Unfortunately, the interpolation is very different:

Two Gradients with different interpolations

The accepted answer to the question linked above explains why this is the case. What I'd like to know is whether anyone could think of a (programmatic) way to replicate the interpolation of the second gradient given the input values of the first one. Also, I'm looking for solutions for both gradients and colour animations.

The answerer of my previous questions suggested that I might just have to tweak the interpolation of the alpha component and can leave the solid colour values alone, but if that is the case, I haven't found the correct algorithm yet... (as the original use case was animation, I experimented with various easing functions on the opacity) After some experiments, I'm also no longer sure whether it was entirely correct to equate opacity with intensity as was the case in that explanation...

Please note that I'm not (necessarily) looking for ready-to-use solutions, but rather suggestions on how to approach the problem that would be most likely to be worthwhile. E.g. creating custom implementations of ColorAnimation and *GradientBrush vs adding more explicit GradientStops/keyframes vs ... something I haven't thought of yet?

My reason for exploring this is that I would very much prefer to work with translucent colours rather than pre-blended ones as it would make the gradients and animations independent of what background they're being displayed on (ideally, also including non-solid colour backgrounds).

1

There are 1 answers

2
egeer On

Since you know the percentage of the gradient's transparency, you can calculate the percent black and derive the second color. You could hand this calculation off to a value converter to do the work for you. You might need to tweak this calculation to get exactly what you want, but it should send you on the right path.

Here is what the value converter might look like:

public class GradientNormalizer : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Color color)) return value;

        var opacity = color.ScA;
        var r = color.ScR;
        var g = color.ScG;
        var b = color.ScB;

        var normalizedColor = new Color
        {

            ScA = 1,
            // since color is interpreted as approximately square
            ScR = r * opacity * opacity,
            ScG = g * opacity * opacity,
            ScB = b * opacity * opacity,
        };


        return normalizedColor;
    }

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

Here is how you might use it in your xaml

<Border x:Name="withOpacity"
        Height="32">
    <Border.Resources>
        <local:GradientNormalizer x:Key="Normalizer" />
    </Border.Resources>
    <Border.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
            <GradientStop Offset="0"
                          Color="{Binding StartColor, Converter={StaticResource Normalizer}}" />
            <GradientStop Offset="1"
                          Color="{Binding EndColor, Converter={StaticResource Normalizer}}" />
        </LinearGradientBrush>
    </Border.Background>
</Border>

And the datamodel in this case:

public Color StartColor => (Color)ColorConverter.ConvertFromString("#ff222222");
public Color EndColor => (Color)ColorConverter.ConvertFromString("#33ff8000");

enter image description here