[WPF] Use Effect to play with Shadows, Inner Shadows, Long Shadows

Posted by GBS on Sat, 26 Feb 2022 18:21:03 +0100

Python WeChat Subscription Applet Course Video

https://edu.csdn.net/course/detail/36074

Python Actual Quantitative Transaction Finance System

https://edu.csdn.net/course/detail/35475
Recently, I've been learning how to write custom effects using Shazzam Shader Editor and trying to achieve effects for shadows, inner shadows, and long shadows. As a result, I gave up the first step because the Gaussian blurring algorithm used in shadows was so difficult for my wife and wife that I had to use some speculative tricks to imitate the effects.

1. Shadows

DropShadowEffect in WPF simply means blurring the input source image by Gaussian, then modifying the color, transparency and displacement based on the properties of Color, Opacity, Direction, ShadowDepth to form a new image as a shadow, which is tiled behind the original image. The biggest difficulty in implementing DropShadowEffect on your own is Gauss Blur. Since you can't write a Gauss Blur algorithm, you have to rely on what WPF has. My practice is to use a VisualBrush to get the image that needs to be shaded, and then use WPF's BlurEffect to blur it:

Copy<Grid ClipToBounds="True">
    <Grid>
        <Grid.Effect>
            <BlurEffect Radius="38" />
        Grid.Effect>
        <Grid.Background>
            <VisualBrush Stretch="None" Visual="{Binding ElementName=ForegroundElement}" />
        Grid.Background>
    Grid>
Grid>
<Grid x:Name="ForegroundElement">
    <TextBlock VerticalAlignment="Center"
 FontFamily="Lucida Handwriting"
 FontSize="148"
 FontWeight="ExtraBold"
 Foreground="#f7e681"
 TextAlignment="Center">
        FAKE<LineBreak />
        SHADOWTextBlock>
Grid>

Now it looks like this.

Then write a FakeDropShadowEffect. It takes the Alpha channel of the input source, replaces the RGB with the specified color (default is black) and combines it into a new color. Then the offset is calculated by Angle and Depth:

Copyfloat4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 c = 0;
    float rad = Angle * 0.0174533f;
    float xOffset = cos(rad) * Depth;
    float yOffset = sin(rad) * Depth;

    uv.x += xOffset;
    uv.y += yOffset;
    c = tex2D(Texture1Sampler, uv);
    c.rgb = Color.rgb * c.a * Opacity;
    c.a = c.a * Opacity;
    return c;
}

Finally, you apply the FakeDrpShadowEffect you just wrote to the Grid by putting another layer of Grid outside the elements that apply the BlurEffect:

Copy<Grid ClipToBounds="True">
    <Grid.Effect>
        <effects:FakeDropShadowEffect Angle="225"
 Depth="0.03"
 Opacity="0.5" />
    Grid.Effect>

The result is almost identical to DropShadowEffect, as shown in the figure above.

2. Inner Shadow

I have written another article about the implementation of inner shadows before: Inner Shadow Implementing WPF . Now with Effect, the first thing I think about is to overlay two elements, and the upper element cuts out a hole based on the VisualBrush of another element, and then casts a shadow through the hole:

Copy<Grid x:Name="BackgroundElement">
    <TextBlock x:Name="Text"
 HorizontalAlignment="Center"
 VerticalAlignment="Center"
 FontSize="150"
 FontWeight="Bold"
 Foreground="{StaticResource LightBackground}"
 TextAlignment="Center">
        INNER<LineBreak />
        SHADOWTextBlock>
Grid>
<Grid ClipToBounds="True">
    <Grid.Effect>
        <DropShadowEffect BlurRadius="8"
 Opacity="0.7"
 ShadowDepth="5" />
    Grid.Effect>
    <Grid Background="{StaticResource LightBackground}">
        <Grid.Effect>
            <effects:ClipEffect>
                <effects:ClipEffect.Blend>
                    <VisualBrush Stretch="None" Visual="{Binding ElementName=BackgroundElement}" />
                effects:ClipEffect.Blend>
            effects:ClipEffect>
        Grid.Effect>
    Grid>
Grid>

In the XAML above, ClipEffect has another input Blend, which is the shape to be cut. ClipEffect's code is simple, just a few lines, and the key function is to use the Alpha channel of input minus the Alpha channel of blend as the result output:

Copysampler2D blend : register(s1);

float4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 inputColor = tex2D(input, uv);
    float4 blendColor = tex2D(blend, uv);
    float4 resultColor = 0;
    float opacity = inputColor.a - blendColor.a;
    resultColor.rgb = inputColor.rgb * opacity;
    resultColor.a = opacity;

    return resultColor;
}

The following illustration shows the effect of the XAML implementation above:

3. Long Shadow

I've written a blog about long shadows in UWP before: Use GetAlphaMask and ContainerVisual to make Long Shadow . This time it's implemented again in WPF with Effect. The principle of long shadows is to keep checking the upper left corner (because lazy is just shading the lower right) until you encounter a pixel with an Alpha channel of 1, and then calculate the distance of that pixel from itself to get the Alpha shadows, all codes are as follows:

Copyfloat4 main(float2 uv : TEXCOORD) : COLOR
{
    float4 srcColor = tex2D(input, uv);
    if (srcColor.a == 1)
    {
        return srcColor;
    }

    float4 tempColor = 0;
    float2 offset = 0;
    int maxDepth = 400;
    float a = 0;
    for (float i = 1; i < maxDepth; i++)
    {
        if (i < ShadowLength)
        {
            if (a == 0)
            {
                offset = uv.xy - float2(i / Width, i / Height);
                if (offset.x > 0 && offset.y > 0)
                {
                    tempColor = tex2D(input, offset);
                    if (tempColor.a == 1)
                    {
                        a = (1 - i / max(1,ShadowLength));
                    }
                }
            }
        }
    }

    if (a == 0)
    {
        return srcColor;
    }

    a = min(1,a);
    tempColor.rgb = Color.rgb * a * Opacity;
    tempColor.a = a * Opacity;
    float4 outColor = (1 - srcColor.a) * tempColor + srcColor;
    return outColor;
}

The XAML used has the following effects: ShadowLength and Color need to be input, because Effect cannot know the size of the input source, so Width and Height need to be input actively:

Copy<Grid x:Name="Root" Background="Transparent">
    <Grid.Effect>
        <effects:LongShadowEffect Width="{Binding ElementName=Root, Path=ActualWidth}"
 Height="{Binding ElementName=Root, Path=ActualHeight}
 ShadowLength="100"
 Color="Red" />
    Grid.Effect>
    <TextBlock x:Name="TextBlock"
 HorizontalAlignment="Center"
 VerticalAlignment="Center"
 FontSize="150"
 FontWeight="Bold"
 Text="NEXT" />
Grid>

4. Source Code

https://github.com/DinoChan/wpf_design_and_animation_lab

Topics: Android WPF computer microsoft