> ## Documentation Index
> Fetch the complete documentation index at: https://ahmed-walid.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Applying Composition effects to XAML elements

> And also alpha masking XAML elements

<Note>
  **Update**

  I found a [better way](#update) to achieve this than the method described in this article, I recommend using that way instead.
</Note>

## **Backstory**

I was exploring XAML's [Handoff Visual](https://learn.microsoft.com/uwp/api/windows.ui.xaml.hosting.elementcompositionpreview.getelementvisual) implementation and how it's created, clipped, wrapped, etc... and found out that the function responsible for creating that visual is `CUIElement::EnsureHandOffVisual` and when I looked at this function I noticed that it has a `bool createLayerVisual` parameter:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img1.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=324ab51b094895d44e0e337882b1200d" alt="Img1" width="639" height="202" data-path="images/img1.png" />

So I looked at the xrefs of this function to see if and when this parameter gets used, and noticed there's a function called `CUIElement::GetHandOffLayerVisual` in the xrefs:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img2-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=2e0fa0467da31b921c7647619f0d9e8e" alt="Img2 Xl" width="1169" height="219" data-path="images/img2-xl.png" />

So I was curious and checked the xrefs of it, and found a very interesting function, `DirectUI::UIElementFactory::GetElementLayerVisual`

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img3-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=af14b9b7183ee86a4293467033f055b4" alt="Img3 Xl" width="1287" height="219" data-path="images/img3-xl.png" />

<br />

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img4-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=400802f3b288f0e20ae8eb24e502ed22" alt="Img4 Xl" width="1001" height="216" data-path="images/img4-xl.png" />

Stuff under the `DirectUI` namespace are usually exposed via interfaces, and that was the case here too!

Xrefs showed that it's exposed through the `Windows::UI::Xaml::IUIElementStaticsPrivate` interface:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img5-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=189377c3f96824dceccb4fee653af0df" alt="Img5 Xl" width="1221" height="340" data-path="images/img5-xl.png" />

I was curious when this was introduced since I have never seen it before, and after checking multiple **Windows.UI.Xaml.dll** versions, it turns out that it was introduced in **Windows 11 Build 22000**.

<Note>
  `LayerVisual` is a visual type that allows you to apply effects and attach shadows to its child visuals.
</Note>

## **Experimentation**

Now that we have the interface and the function signature, the only thing missing is the interface **IID**, checking the `UIElementFactory` constructor reveals that the interface is stored at offset **0x50** of the factory class object:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img6-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=02784d57e884c3afdc3980c64be338e6" alt="Img6 Xl" width="1201" height="385" data-path="images/img6-xl.png" />

And if we look at the `QueryInterface` implementation, we can see that it wraps/calls another `QueryInterface` function but with a pointer to the object at offset **0x10** passed as a `this` parameter:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img7-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=ef9e42e35686ecdd9e232ac7f7f27642" alt="Img7 Xl" width="919" height="174" data-path="images/img7-xl.png" />

This means that we are looking for the **IID** of object returned at offset **0x40** ( **0x50 - 0x10** ) in that wrapped  `QueryInterface` function.

If we look at that function we see that IDA mistyped the `this` parameter and also wasn't able to detect that it's the `this` parameter and showed it as a regular register variable:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img8-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=f92fbc6333d70dee25fc1b1874b390f2" alt="Img8 Xl" width="762" height="603" data-path="images/img8-xl.png" />

To fix this mess, we will simply change the type of this variable to `void*`, this way we can see the offsets more clearly, and here we go, we found our **IID**!

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img9-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=a71b63e7379d3f5dd333910c68f53cc8" alt="Img9 Xl" width="627" height="240" data-path="images/img9-xl.png" />

We are using **UWP .NET 9** for this experiment so we have to use **Source Generated COM** to declare this interface definition (technically we can use a projection project or handroll the interface projection codegen too but this is simpler, well, *kinda*)

```csharp theme={null}
[GeneratedComInterface]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("7cc8cb07-5a0d-46bb-8c54-9beafe63b476")]
internal unsafe partial interface IUIElementStaticsPrivate
{
    // IInspectable methods
    void GetIids();
    void GetRuntimeClassName();
    void GetTrustLevel();

    // IUIElementStaticsPrivate methods
    void add_PopupOpening();
    void remove_PopupOpening();
    void add_PopupPlacement();
    void remove_PopupPlacement();
    void InternalGetIsEnabled();
    void InternalPutIsEnabled();
    void GetRasterizationScale();
    void PutRasterizationScale();
    void PutPopupRootLightDismissBounds();
    void EnablePopupZIndexSorting();
    void* GetElementLayerVisual(void* pElement);
}
```

Now we only need to write some test code and see if it works

```jsx theme={null}
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Orientation="Vertical">
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
            <ContentControl x:Name="contentControl" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button Content="Hello, World!" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="50"/>
            </ContentControl>
            <Button x:Name="hiButton" Content="Hello, World!" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="50"/>
        </StackPanel>
        <Button x:Name="playButton" Content="Play Animation" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20" Click="playButton_Click"/>
    </StackPanel>
</Grid>
```

```csharp expandable theme={null}
private void playButton_Click(object sender, RoutedEventArgs e)
{
    // In CsWinRT 3, this probably needs to be changed to something like:
    // using var objRef = WindowsRuntimeActivationFactory.GetActivationFactory(typeof(UIElement).FullName, typeof(IUIElementStaticsPrivate).GUID);
    // var staticsPrivate = ComInterfaceMarshaller<IUIElementStaticsPrivate>.ConvertToManaged(objRef.GetThisPtrUnsafe());
    var staticsPrivate = UIElement.As<IUIElementStaticsPrivate>();

    // GetElementLayerVisual expects an IUIElement* but CsWinRT won't give us an IUIElement from a composed class (such as ContentControl) unless
    // it's passed to a projected method that takes an UIElement, so we have to QI manually
    Marshal.QueryInterface(((IWinRTObject)contentControl).NativeObject.ThisPtr, new("676d0be9-b65c-41c6-ba40-58cf87f201c1") /* IUIElement */, out nint ppv);
    var layerVisualPtr = staticsPrivate.GetElementLayerVisual((void*)ppv);
    Marshal.Release(ppv);

    var layerVisual = LayerVisual.FromAbi((nint)layerVisualPtr);

    var compositor = layerVisual.Compositor;

    var blur = new GaussianBlurEffect
    {
        Name = "Blur",
        BlurAmount = 0.0f,
        BorderMode = EffectBorderMode.Soft,
        Optimization = EffectOptimization.Balanced,
        Source = new CompositionEffectSourceParameter("source")
    };

    var effectFactory = compositor.CreateEffectFactory(blur, new[] { "Blur.BlurAmount" });
    var effectBrush = effectFactory.CreateBrush();
    layerVisual.Effect = effectBrush;

    var easing = CompositionEasingFunction.CreateExponentialEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 6);

    var animation = compositor.CreateScalarKeyFrameAnimation();
    animation.InsertKeyFrame(0.0f, 0.0f, easing);
    animation.InsertKeyFrame(1.0f, 10.0f, easing);
    animation.Duration = TimeSpan.FromSeconds(2);
    animation.IterationBehavior = AnimationIterationBehavior.Forever;
    animation.Direction = AnimationDirection.Alternate;
    effectBrush.StartAnimation("Blur.BlurAmount", animation);

    Marshal.QueryInterface(((IWinRTObject)hiButton).NativeObject.ThisPtr, new("676d0be9-b65c-41c6-ba40-58cf87f201c1")  /* IUIElement */, out nint ppv2);
    var layerVisual2Ptr = staticsPrivate.GetElementLayerVisual((void*)ppv2);
    Marshal.Release(ppv2);

    var layerVisual2 = LayerVisual.FromAbi((nint)layerVisual2Ptr);

    var alphaMask = new AlphaMaskEffect
    {
        Name = "AlphaMask",
        Source = new CompositionEffectSourceParameter("source"),
        AlphaMask = new CompositionEffectSourceParameter("mask")
    };

    var gradientBrush = compositor.CreateLinearGradientBrush();
    gradientBrush.StartPoint = new(0, 0);
    gradientBrush.EndPoint = new(1, 0);
    gradientBrush.MappingMode = CompositionMappingMode.Relative;

    CompositionColorGradientStop stop;
    gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(-1.0f, Colors.Black));
    gradientBrush.ColorStops.Add(stop = compositor.CreateColorGradientStop(1.0f, Colors.Black));
    gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(1.0f, Colors.Transparent));

    var easing2 = CompositionEasingFunction.CreatePowerEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 3);

    var animation2 = compositor.CreateScalarKeyFrameAnimation();
    animation2.InsertKeyFrame(0.0f, 1.0f, easing2);
    animation2.InsertKeyFrame(1.0f, -1.0f, easing2);
    animation2.Duration = TimeSpan.FromSeconds(2);
    animation2.IterationBehavior = AnimationIterationBehavior.Forever;
    animation2.Direction = AnimationDirection.Alternate;
    stop.StartAnimation("Offset", animation2);

    var effectFactory2 = compositor.CreateEffectFactory(alphaMask);
    var effectBrush2 = effectFactory2.CreateBrush();
    effectBrush2.SetSourceParameter("mask", gradientBrush);
    layerVisual2.Effect = effectBrush2;
}
```

*And voila!*

<video controls className="w-full aspect-video rounded-xl" src="https://gist.github.com/user-attachments/assets/8880922f-480e-49a9-8cfd-ea44fd8ba70e" type="video/mp4" />

<Warning>
  **Warning**

  * `GetElementLayerVisual` has to be called **before** any handoff visual was created for the element, so if `ElementCompositionPreview.GetElementVisual` was called on the element prior it, the function will fail.
  * If you are calling that method on an element that has any `Popup` children, you have to call `ElementCompositionPreview.GetElementVisual` on the element after calling `GetElementLayerVisual` or otherwise the `Popup` will break.
</Warning>

## **But what about Windows 10?**

This function we just used doesn't exist in the **Windows 10** version of that interface, but looking at how `createLayerVisual` is used inside `CUIElement::EnsureHandOffVisual` we can see that the `LayerVisual` and the normal visual paths are *very* similar, so similar that we can hook `IDCompositionDevice2::CreateVisual` and redirect it to `Windows.UI.Compositor.CreateLayerVisual` and it will just work.

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img10-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=4ca35459ecada14b49fcffdb457fb818" alt="Img10 Xl" width="1074" height="440" data-path="images/img10-xl.png" />

<br />

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img11-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=5652a3ab644ffe3524d13978da765de8" alt="Img11 Xl" width="1061" height="352" data-path="images/img11-xl.png" />

We will be using a **C++/WinRT Runtime Component** for this since we are gonna need **Detours**, and using **Detours** in C# is a bit harder than using it from C++ directly.

So let's create a simple WinRT helper static class for this, lets start with the **IDL**:

```cpp theme={null}
namespace XamlCompositionHelpers
{
    static runtimeclass ElementCompositionPreviewEx
    {
        static Windows.UI.Composition.LayerVisual GetElementLayerVisual(Windows.UI.Xaml.UIElement element);
    }
}
```

And now the implementation:

```cpp expandable theme={null}
namespace winrt::XamlCompositionHelpers::implementation
{
    bool ElementCompositionPreviewEx::s_Hooked = false;
    DWORD ElementCompositionPreviewEx::s_ThreadId = NULL;
    std::mutex ElementCompositionPreviewEx::s_Mutex = { };
    decltype(ElementCompositionPreviewEx::s_CreateVisual) ElementCompositionPreviewEx::s_CreateVisual = nullptr;
	decltype(ElementCompositionPreviewEx::s_QueryInterface) ElementCompositionPreviewEx::s_QueryInterface = nullptr;

    static bool IsOnWindows11OrHigher()
    {
        bool isWin11 = ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 14);
        return isWin11;
    }

    LayerVisual ElementCompositionPreviewEx::GetElementLayerVisual(UIElement const& element)
    {
        // On Windows 11 b22000 and higher, we can use the IUIElementStaticsPrivate interface to get the LayerVisual directly
        com_ptr<IUIElementStaticsPrivate> uiElementPrivate;
        if (IsOnWindows11OrHigher() && (uiElementPrivate = try_get_activation_factory<UIElement, IUIElementStaticsPrivate>()))
        {
            LayerVisual layerVisual { nullptr };
            check_hresult(uiElementPrivate->GetElementLayerVisual(winrt::get_abi(element), put_abi(layerVisual)));
            return layerVisual;
        }

        // We are using the thread ID to verify and ensure that we aren't hoking any other ElementCompositionPreview::GetElementVisual call
        // that happened to be going in another thread at the same time we are hooking the function to return a LayerVisual,
        // and we use a lock to ensure that only one thread can be hooking at a time so that thread ID doesn't get changed mid-hook.
        std::scoped_lock lock(s_Mutex);
        EnsureHooked();

        s_ThreadId = GetCurrentThreadId();
        auto visual = ElementCompositionPreview::GetElementVisual(element);
        s_ThreadId = NULL;
        return visual.as<LayerVisual>();
    }

    void ElementCompositionPreviewEx::EnsureHooked()
    {
        if (!s_Hooked)
        {
            // assuming we are on the UI thread, it wouldn't work otherwise anyway
            auto compositor = Window::Current().Compositor();
            auto device3 = compositor.as<IDCompositionDevice3>();
            auto vtbl = *reinterpret_cast<void***>(device3.get());
            s_CreateVisual = reinterpret_cast<decltype(s_CreateVisual)>(vtbl[6]);

            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)s_CreateVisual, &ElementCompositionPreviewEx::CreateVisualHook);
            check_win32(DetourTransactionCommit());

            // some older Windows builds don't have the createLayerVisual parameter
            // and on these builds XAML actually tries to use the visual as IDCompsitionVisual*
            // which fails because LayerVisual is not an InteropVisual, we work around this
            // by creating a dummy InteropVisual and hooking QI calls on the LayerVisual
            // that query for IDCompsitionVisual* and redirect them to our dummy InteropVisual
            check_hresult(device3->CreateVisual(&s_dummyVisual));

            auto layerVisual = compositor.CreateLayerVisual().as<Visual>();
            auto layerVtbl = *reinterpret_cast<void***>(winrt::get_abi(layerVisual));
            s_QueryInterface = reinterpret_cast<decltype(s_QueryInterface)>(layerVtbl[0]);

            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)s_QueryInterface, &ElementCompositionPreviewEx::QueryInterfaceHook);
            check_win32(DetourTransactionCommit());

            s_Hooked = true;
        }
    }

    HRESULT WINAPI ElementCompositionPreviewEx::CreateVisualHook(IDCompositionDevice2* pThis, IDCompositionVisual2** ppVisual)
    {
        // Ensure that we are only hooking our own calls to ElementCompositionPreview::GetElementVisual / IDCompositionDevice2::CreateVisual
        if (s_ThreadId != GetCurrentThreadId())
            return s_CreateVisual(pThis, ppVisual);

        Compositor compositor { nullptr };
        copy_from_abi(compositor, pThis);

        LayerVisual layerVisual = compositor.CreateLayerVisual();
        copy_to_abi(layerVisual, *(void**&)ppVisual);

        return S_OK;
    }

    HRESULT WINAPI ElementCompositionPreviewEx::QueryInterfaceHook(IUnknown* pThis, REFIID riid, void** ppv)
    {
        const static auto xamlModule = GetModuleHandleW(L"Windows.UI.Xaml.dll");
        static const constexpr auto IID1 = std::bit_cast<GUID>(guid("1CD4B256-A5ED-4DED-A0C1-82A7A20B8755"));
        static const constexpr auto IID2 = std::bit_cast<GUID>(guid("86C82676-5D2A-4E6A-A89F-50A22FF86263"));
        static const constexpr auto IID3 = std::bit_cast<GUID>(guid("A0AD01B5-9C60-4A6F-A7E2-04E18CF5E011"));

        LayerVisual layerVisual { nullptr };

        HMODULE module = NULL;
        HRESULT result = s_QueryInterface(pThis, riid, ppv);
        if (FAILED(result) &&
            s_dummyVisual &&
            (riid == IID1 || riid == IID2 || riid == IID3) &&
            GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)_ReturnAddress(), &module) &&
            module == xamlModule &&
            SUCCEEDED(s_QueryInterface(pThis, winrt::guid_of<ILayerVisual>(), winrt::put_abi(layerVisual))))
        {
            result = s_dummyVisual->QueryInterface(riid, ppv);
        }

        return result;
    }
}
```

And with this we can then replace our C# code with this:

```csharp expandable theme={null}
private void playButton_Click(object sender, RoutedEventArgs e)
{
    var layerVisual = ElementCompositionPreviewEx.GetElementLayerVisual(contentControl);
    var compositor = layerVisual.Compositor;

    var blur = new GaussianBlurEffect
    {
        Name = "Blur",
        BlurAmount = 0.0f,
        BorderMode = EffectBorderMode.Soft,
        Optimization = EffectOptimization.Balanced,
        Source = new CompositionEffectSourceParameter("source")
    };

    var effectFactory = compositor.CreateEffectFactory(blur, new[] { "Blur.BlurAmount" });
    var effectBrush = effectFactory.CreateBrush();
    layerVisual.Effect = effectBrush;

    var easing = CompositionEasingFunctionEx.CreateExponentialEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 6);

    var animation = compositor.CreateScalarKeyFrameAnimation();
    animation.InsertKeyFrame(0.0f, 0.0f, easing);
    animation.InsertKeyFrame(1.0f, 10.0f, easing);
    animation.Duration = TimeSpan.FromSeconds(2);
    animation.IterationBehavior = AnimationIterationBehavior.Forever;
    animation.Direction = AnimationDirection.Alternate;
    effectBrush.StartAnimation("Blur.BlurAmount", animation);

    var layerVisual2 = ElementCompositionPreviewEx.GetElementLayerVisual(hiButton);

    var alphaMask = new AlphaMaskEffect
    {
        Name = "AlphaMask",
        Source = new CompositionEffectSourceParameter("source"),
        AlphaMask = new CompositionEffectSourceParameter("mask")
    };

    var gradientBrush = compositor.CreateLinearGradientBrush();
    gradientBrush.StartPoint = new(0, 0);
    gradientBrush.EndPoint = new(1, 0);
    gradientBrush.MappingMode = CompositionMappingMode.Relative;

    CompositionColorGradientStop stop;
    gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(-1.0f, Colors.Black));
    gradientBrush.ColorStops.Add(stop = compositor.CreateColorGradientStop(1.0f, Colors.Black));
    gradientBrush.ColorStops.Add(compositor.CreateColorGradientStop(1.0f, Colors.Transparent));

    var easing2 = CompositionEasingFunctionEx.CreatePowerEasingFunction(compositor, CompositionEasingFunctionMode.InOut, 3);

    var animation2 = compositor.CreateScalarKeyFrameAnimation();
    animation2.InsertKeyFrame(0.0f, 1.0f, easing2);
    animation2.InsertKeyFrame(1.0f, -1.0f, easing2);
    animation2.Duration = TimeSpan.FromSeconds(2);
    animation2.IterationBehavior = AnimationIterationBehavior.Forever;
    animation2.Direction = AnimationDirection.Alternate;
    stop.StartAnimation("Offset", animation2);

    var effectFactory2 = compositor.CreateEffectFactory(alphaMask);
    var effectBrush2 = effectFactory2.CreateBrush();
    effectBrush2.SetSourceParameter("mask", gradientBrush);
    layerVisual2.Effect = effectBrush2;
}
```

*And voila!*

<img src="https://mintcdn.com/ahmedwalid/hdZTzsnbZlXAf8UE/images/img12-xl.png?fit=max&auto=format&n=hdZTzsnbZlXAf8UE&q=85&s=f44d2612cb2631edfa1cc18d41f137ed" alt="Img12 Xl" width="696" height="571" data-path="images/img12-xl.png" />

<Note>
  As you might have noticed I'm using `CompositionEasingFunctionEx` instead of `CompositionEasingFunction` to create easing functions in the \*\*Windows 10 \*\*code, that's because the latter only got the support for creating easing functions in **Windows 11**, so I created a  `CompositionEasingFunctionEx` helper class to allow creating such easing functions under **Windows 10**, but that's a story for another article.
</Note>

## **But what about WinUI 3?**

Unfortunately WinUI 3 doesn't have that interface method at all, neither in latest version or older ones, \*\*BUT \*\*we can use the same trick we used for **Windows 10**, so lets take a look at the `CUIElement::EnsureHandOffVisual` function in **WinUI 3**:

<img src="https://mintcdn.com/ahmedwalid/TEZFgSLe1CaUbGbq/images/img13-xl.png?fit=max&auto=format&n=TEZFgSLe1CaUbGbq&q=85&s=13238e29fe3d5c60d8e9ab55ef8f31a6" alt="Img13 Xl" width="911" height="446" data-path="images/img13-xl.png" />

<br />

<img src="https://mintcdn.com/ahmedwalid/hdZTzsnbZlXAf8UE/images/img14-xl.png?fit=max&auto=format&n=hdZTzsnbZlXAf8UE&q=85&s=5d25372c263d0c4079df7c4cc3bcb139" alt="Img14 Xl" width="821" height="301" data-path="images/img14-xl.png" />

Interesting, so it uses the WinRT `Microsoft.UI.Composition` API for both cases, so we need to copy the C++ implementation code we wrote before and make few changes to accommodate that (previous comments are omitted, so check them in the UWP/WUX code if you haven't)

```cpp expandable theme={null}
namespace winrt::WinUICompositionHelpers::implementation
{
    bool ElementCompositionPreviewEx::s_Hooked = false;
    DWORD ElementCompositionPreviewEx::s_ThreadId = NULL;
    std::mutex ElementCompositionPreviewEx::s_Mutex = { };
    decltype(ElementCompositionPreviewEx::s_CreateContainerVisual) ElementCompositionPreviewEx::s_CreateContainerVisual = nullptr;

    LayerVisual ElementCompositionPreviewEx::GetElementLayerVisual(UIElement const& element)
    {
        std::scoped_lock lock(s_Mutex);
        assert(element.DispatcherQueue().HasThreadAccess());
        EnsureHooked(CompositionTarget::GetCompositorForCurrentThread());

        s_ThreadId = GetCurrentThreadId();
        auto visual = ElementCompositionPreview::GetElementVisual(element);
        s_ThreadId = NULL;
        return visual.as<LayerVisual>();
    }

    void ElementCompositionPreviewEx::EnsureHooked(Compositor compositor)
    {
        if (!s_Hooked)
        {
            auto vtbl = *reinterpret_cast<void***>(winrt::get_abi(compositor));
            s_CreateContainerVisual = reinterpret_cast<decltype(s_CreateContainerVisual)>(vtbl[9]);

            DetourTransactionBegin();
            DetourUpdateThread(GetCurrentThread());
            DetourAttach(&(PVOID&)s_CreateContainerVisual, &ElementCompositionPreviewEx::CreateContainerVisualHook);
            check_win32(DetourTransactionCommit());
            s_Hooked = true;
        }
    }

    HRESULT WINAPI ElementCompositionPreviewEx::CreateContainerVisualHook(void* pThis, void** ppVisual)
    {
        if (s_ThreadId != GetCurrentThreadId())
            return s_CreateContainerVisual(pThis, ppVisual);

        Compositor compositor { nullptr };
        copy_from_abi(compositor, pThis);

        LayerVisual layerVisual = compositor.CreateLayerVisual();
        copy_to_abi(layerVisual, *(void**&)ppVisual);

        return S_OK;
    }
}
```

The C# code is the same as UWP/WUX version, so no need to include it again, and with that in place.... *voila!*

<video controls className="w-full aspect-video rounded-xl" src="https://gist.github.com/user-attachments/assets/b3797aee-ef40-45e1-842b-741894b9f42a" type="video/mp4" />

And that's it, see you in the next article!

## **Update**

I accidentally found a better and easier way to achieve this without hooking or any complex stuff at all while working on another thing.

So basically I found out that `CompositionVisualSurface` completely ignores the `Opacity` property of the source Visual, so you can just get the Visual of the target `UIElement`, make its `Opacity` **0**, then create a `CompositionSurfaceBrush` over a `CompositionVisualSurface` of the Visual then use that to create a `SpriteVisual` with `CompositionEffectBrush` or `CompositionMaskBrush` (with the source brush set to the `CompositionSurfaceBrush`) on top of it!

*Special thanks to* [*Dongle*](https://x.com/a_donglee) *for proofreading the article*
