diff --git a/components/Animations/src/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs b/components/Animations/src/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs
index b09c3287..5fc551d7 100644
--- a/components/Animations/src/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs
+++ b/components/Animations/src/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs
@@ -2,9 +2,9 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-#if WINAPPSDK
+#if WINUI3
 using Microsoft.UI.Composition;
-#else
+#elif WINUI2
 using Windows.UI.Composition;
 #endif
 
diff --git a/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj b/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj
index 196bf0ef..fde8d32e 100644
--- a/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj
+++ b/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj
@@ -10,7 +10,10 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <InternalsVisibleTo Include="CommunityToolkit.WinUI.Behaviors.Animations" />
+    <InternalsVisibleTo Include="CommunityToolkit.WinUI.Media" />
     <InternalsVisibleTo Include="CommunityToolkit.WinUI.Behaviors" />
+    
     <PackageReference Include="PolySharp" Version="1.13.1">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
diff --git a/components/Animations/src/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs b/components/Animations/src/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs
index 667f3e64..cc98af6e 100644
--- a/components/Animations/src/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs
+++ b/components/Animations/src/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs
@@ -86,11 +86,13 @@ public IList<IKeyFrame<TKeyFrame>> KeyFrames
     /// <summary>
     /// Gets the explicit target for the animation. This is the primary target property that is animated.
     /// </summary>
-    protected abstract string ExplicitTarget { get; }
+    protected abstract string? ExplicitTarget { get; }
 
     /// <inheritdoc/>
     public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
     {
+        default(ArgumentNullException).ThrowIfNull(ExplicitTarget);
+
         return builder.NormalizedKeyFrames<TKeyFrame, (Animation<TValue, TKeyFrame> This, EasingType? EasingTypeHint, EasingMode? EasingModeHint)>(
             property: ExplicitTarget,
             state: (this, easingTypeHint, easingModeHint),
diff --git a/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs b/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs
index ef1bef0b..dde31d2b 100644
--- a/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs
+++ b/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs
@@ -34,11 +34,13 @@ public abstract class CustomAnimation<TValue, TKeyFrame> : ImplicitAnimation<TVa
 #endif
 
     /// <inheritdoc/>
-    protected override string ExplicitTarget => Target!;
+    protected override string? ExplicitTarget => Target;
 
     /// <inheritdoc/>
     public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
     {
+        default(ArgumentNullException).ThrowIfNull(ExplicitTarget);
+
         return builder.NormalizedKeyFrames<TKeyFrame, (CustomAnimation<TValue, TKeyFrame> This, EasingType? EasingTypeHint, EasingMode? EasingModeHint)>(
             property: ExplicitTarget,
             state: (this, easingTypeHint, easingModeHint),
diff --git a/components/Animations/src/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs b/components/Animations/src/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs
index 2380e79c..f5c1fb77 100644
--- a/components/Animations/src/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs
+++ b/components/Animations/src/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs
@@ -46,6 +46,8 @@ protected ImplicitAnimation()
     /// <inheritdoc/>
     public CompositionAnimation GetAnimation(UIElement element, out string? target)
     {
+        default(ArgumentNullException).ThrowIfNull(ExplicitTarget);
+
         NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition builder = new(
             ExplicitTarget,
             Delay ?? DefaultDelay,
diff --git a/components/Animations/src/Xaml/Default/ClipAnimation.cs b/components/Animations/src/Xaml/Default/ClipAnimation.cs
index 69cb97a7..9f8330ea 100644
--- a/components/Animations/src/Xaml/Default/ClipAnimation.cs
+++ b/components/Animations/src/Xaml/Default/ClipAnimation.cs
@@ -12,9 +12,7 @@ namespace CommunityToolkit.WinUI.Animations;
 public sealed class ClipAnimation : Animation<Thickness?, Thickness>
 {
     /// <inheritdoc/>
-#pragma warning disable CA1065 // Do not raise exceptions in unexpected locations
-    protected override string ExplicitTarget => throw new NotImplementedException();
-#pragma warning restore CA1065 // Do not raise exceptions in unexpected locations
+    protected override string? ExplicitTarget => null;
 
     /// <inheritdoc/>
     public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
diff --git a/components/Extensions/src/ArgumentNullExceptionExtensions.cs b/components/Extensions/src/ArgumentNullExceptionExtensions.cs
new file mode 100644
index 00000000..545654bf
--- /dev/null
+++ b/components/Extensions/src/ArgumentNullExceptionExtensions.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace System;
+
+/// <summary>
+/// Throw helper extensions for <see cref="ArgumentNullException"/>.
+/// </summary>
+internal static class ArgumentNullExceptionExtensions
+{
+    /// <summary>
+    /// Throws an <see cref="ArgumentNullException"/> for a given parameter name.
+    /// </summary>
+    /// <param name="_">Dummy value to invoke the extension upon (always pass <see langword="null"/>.</param>
+    /// <param name="parameterName">The name of the parameter to report in the exception.</param>
+    /// <exception cref="ArgumentNullException">Thrown with <paramref name="parameterName"/>.</exception>
+    [DoesNotReturn]
+    public static void Throw(this ArgumentNullException? _, string? parameterName)
+    {
+        throw new ArgumentNullException(parameterName);
+    }
+
+    /// <summary>
+    /// Throws an <see cref="ArgumentNullException"/> if <paramref name="argument"/> is <see langword="null"/>.
+    /// </summary>
+    /// <param name="_">Dummy value to invoke the extension upon (always pass <see langword="null"/>.</param>
+    /// <param name="argument">The reference type argument to validate as non-<see langword="null"/>.</param>
+    /// <param name="parameterName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="argument"/> is <see langword="null"/>.</exception>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static void ThrowIfNull(this ArgumentNullException? _, [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? parameterName = null)
+    {
+        if (argument is null)
+        {
+            Throw(parameterName);
+        }
+    }
+
+    /// <summary>
+    /// Throws an <see cref="ArgumentNullException"/> if <paramref name="argument"/> is <see langword="null"/>.
+    /// </summary>
+    /// <param name="_">Dummy value to invoke the extension upon (always pass <see langword="null"/>.</param>
+    /// <param name="argument">The pointer argument to validate as non-<see langword="null"/>.</param>
+    /// <param name="parameterName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
+    /// <exception cref="ArgumentNullException">Thrown if <paramref name="argument"/> is <see langword="null"/>.</exception>
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public static unsafe void ThrowIfNull(this ArgumentNullException? _, [NotNull] void* argument, [CallerArgumentExpression(nameof(argument))] string? parameterName = null)
+    {
+        if (argument is null)
+        {
+            Throw(parameterName);
+        }
+    }
+
+    /// <inheritdoc cref="Throw(ArgumentNullException?, string?)"/>
+    [DoesNotReturn]
+    private static void Throw(string? parameterName)
+    {
+        throw new ArgumentNullException(parameterName);
+    }
+}
diff --git a/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj b/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj
index dceba6eb..e4df1fff 100644
--- a/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj
+++ b/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj
@@ -6,11 +6,21 @@
 
     <!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
     <RootNamespace>CommunityToolkit.WinUI.ExtensionsRns</RootNamespace>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <!-- Sets this up as a toolkit component's source project -->
   <Import Project="$(ToolingDirectory)\ToolkitComponent.SourceProject.props" />
+
   <ItemGroup>
     <PackageReference Include="CommunityToolkit.Common" Version="8.1.0" />
+
+    <PackageReference Include="PolySharp" Version="1.13.1">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+
+    <InternalsVisibleTo Include="CommunityToolkit.WinUI.Animations" />
+    <InternalsVisibleTo Include="CommunityToolkit.WinUI.Media" />
   </ItemGroup>
 </Project>
diff --git a/components/Extensions/src/Markup/FontIconExtension.cs b/components/Extensions/src/Markup/FontIconExtension.cs
index 4afac400..4a090833 100644
--- a/components/Extensions/src/Markup/FontIconExtension.cs
+++ b/components/Extensions/src/Markup/FontIconExtension.cs
@@ -23,6 +23,8 @@ public class FontIconExtension : TextIconExtension
     /// <inheritdoc/>
     protected override object ProvideValue()
     {
+        default(ArgumentNullException).ThrowIfNull(Glyph);
+
         var fontIcon = new FontIcon
         {
             Glyph = Glyph,
diff --git a/components/Media/OpenSolution.bat b/components/Media/OpenSolution.bat
new file mode 100644
index 00000000..814a56d4
--- /dev/null
+++ b/components/Media/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/Media/samples/Assets/icon.png b/components/Media/samples/Assets/icon.png
new file mode 100644
index 00000000..5f574cec
Binary files /dev/null and b/components/Media/samples/Assets/icon.png differ
diff --git a/components/Media/samples/Dependencies.props b/components/Media/samples/Dependencies.props
new file mode 100644
index 00000000..e622e1df
--- /dev/null
+++ b/components/Media/samples/Dependencies.props
@@ -0,0 +1,31 @@
+<!--
+    WinUI 2 under UWP uses TargetFramework uap10.0.*
+    WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
+    However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.
+    
+    MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.
+
+    For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
+    For UWP / WinAppSDK / Uno packages, place the package references here.
+-->
+<Project>
+    <!-- WinUI 2 / UWP -->
+    <ItemGroup Condition="'$(IsUwp)' == 'true'">
+        <!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
+    </ItemGroup>
+
+    <!-- WinUI 2 / Uno -->
+    <ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2'">
+        <!-- <PackageReference Include="Uno.Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.11"/> -->
+    </ItemGroup>
+
+    <!-- WinUI 3 / WinAppSdk -->
+    <ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
+        <!-- <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2"/> -->
+    </ItemGroup>
+    
+    <!-- WinUI 3 / Uno -->
+    <ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3'">
+        <!-- <PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.100-dev.15.g12261e2626"/> -->
+    </ItemGroup>
+</Project>
diff --git a/components/Media/samples/Media.Samples.csproj b/components/Media/samples/Media.Samples.csproj
new file mode 100644
index 00000000..ce5843af
--- /dev/null
+++ b/components/Media/samples/Media.Samples.csproj
@@ -0,0 +1,8 @@
+<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
+  <PropertyGroup>
+    <ToolkitComponentName>Media</ToolkitComponentName>
+  </PropertyGroup>
+
+  <!-- Sets this up as a toolkit component's sample project -->
+  <Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
+</Project>
diff --git a/components/Media/src/AdditionalAssemblyInfo.cs b/components/Media/src/AdditionalAssemblyInfo.cs
new file mode 100644
index 00000000..7e58d70e
--- /dev/null
+++ b/components/Media/src/AdditionalAssemblyInfo.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+// These `InternalsVisibleTo` calls are intended to make it easier for
+// for any internal code to be testable in all the different test projects
+// used with the Labs infrastructure.
+[assembly: InternalsVisibleTo("Media.Tests.Uwp")]
+[assembly: InternalsVisibleTo("Media.Tests.WinAppSdk")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")]
diff --git a/components/Media/src/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs b/components/Media/src/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs
new file mode 100644
index 00000000..4663e338
--- /dev/null
+++ b/components/Media/src/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+using static CommunityToolkit.WinUI.Animations.AnimationExtensions;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// A custom animation targeting a property on an <see cref="IPipelineEffect"/> instance.
+/// </summary>
+/// <typeparam name="TEffect">The type of effect to animate.</typeparam>
+/// <typeparam name="TValue">
+/// The type to use for the public <see cref="Animation{TValue,TKeyFrame}.To"/> and <see cref="Animation{TValue,TKeyFrame}.From"/>
+/// properties. This can differ from <typeparamref name="TKeyFrame"/> to facilitate XAML parsing.
+/// </typeparam>
+/// <typeparam name="TKeyFrame">The actual type of keyframe values in use.</typeparam>
+public abstract class EffectAnimation<TEffect, TValue, TKeyFrame> : Animation<TValue, TKeyFrame>
+    where TEffect : class, IPipelineEffect
+    where TKeyFrame : unmanaged
+{
+    /// <summary>
+    /// Gets or sets the linked <typeparamref name="TEffect"/> instance to animate.
+    /// </summary>
+    public TEffect? Target
+    {
+        get => (TEffect?)GetValue(TargetProperty);
+        set => SetValue(TargetProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <seealso cref="Target"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
+        nameof(Target),
+        typeof(TEffect),
+        typeof(EffectAnimation<TEffect, TValue, TKeyFrame>),
+        new PropertyMetadata(null));
+
+    /// <inheritdoc/>
+    public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
+    {
+        if (Target is not TEffect target)
+        {
+            static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target effect is null, make sure to set the Target property");
+
+            return ThrowArgumentNullException();
+        }
+
+        if (ExplicitTarget is not string explicitTarget)
+        {
+            static AnimationBuilder ThrowArgumentNullException()
+            {
+                throw new ArgumentNullException(
+                    "The target effect cannot be animated at this time. If you're targeting one of the " +
+                    "built-in effects, make sure that the PipelineEffect.IsAnimatable property is set to true.");
+            }
+
+            return ThrowArgumentNullException();
+        }
+
+        NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
+            explicitTarget,
+            Delay ?? delayHint ?? DefaultDelay,
+            Duration ?? durationHint ?? DefaultDuration,
+            Repeat,
+            DelayBehavior);
+
+        AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
+
+        CompositionAnimation animation = keyFrameBuilder.GetAnimation(target.Brush!, out _);
+
+        return builder.ExternalAnimation(target.Brush!, animation);
+    }
+}
diff --git a/components/Media/src/Animations/BlurEffectAnimation.cs b/components/Media/src/Animations/BlurEffectAnimation.cs
new file mode 100644
index 00000000..dd44d3c5
--- /dev/null
+++ b/components/Media/src/Animations/BlurEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="BlurEffect.Amount"/>.
+/// </summary>
+public sealed class BlurEffectAnimation : EffectAnimation<BlurEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/ColorEffectAnimation.cs b/components/Media/src/Animations/ColorEffectAnimation.cs
new file mode 100644
index 00000000..c3aaffd6
--- /dev/null
+++ b/components/Media/src/Animations/ColorEffectAnimation.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="TintEffect.Color"/>.
+/// </summary>
+public sealed class ColorEffectAnimation : EffectAnimation<TintEffect, Color?, Color>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (Color?, Color?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/CrossFadeEffectAnimation.cs b/components/Media/src/Animations/CrossFadeEffectAnimation.cs
new file mode 100644
index 00000000..1ee76d92
--- /dev/null
+++ b/components/Media/src/Animations/CrossFadeEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="CrossFadeEffect.Factor"/>.
+/// </summary>
+public sealed class CrossFadeEffectAnimation : EffectAnimation<CrossFadeEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/ExposureEffectAnimation.cs b/components/Media/src/Animations/ExposureEffectAnimation.cs
new file mode 100644
index 00000000..7bd45706
--- /dev/null
+++ b/components/Media/src/Animations/ExposureEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="ExposureEffect.Amount"/>.
+/// </summary>
+public sealed class ExposureEffectAnimation : EffectAnimation<ExposureEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/HueRotationEffectAnimation.cs b/components/Media/src/Animations/HueRotationEffectAnimation.cs
new file mode 100644
index 00000000..723806c1
--- /dev/null
+++ b/components/Media/src/Animations/HueRotationEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="HueRotationEffect.Angle"/>.
+/// </summary>
+public sealed class HueRotationEffectAnimation : EffectAnimation<HueRotationEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/OpacityEffectAnimation.cs b/components/Media/src/Animations/OpacityEffectAnimation.cs
new file mode 100644
index 00000000..e4f76b5b
--- /dev/null
+++ b/components/Media/src/Animations/OpacityEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="OpacityEffect.Value"/>.
+/// </summary>
+public sealed class OpacityEffectAnimation : EffectAnimation<OpacityEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/SaturationEffectAnimation.cs b/components/Media/src/Animations/SaturationEffectAnimation.cs
new file mode 100644
index 00000000..014a8648
--- /dev/null
+++ b/components/Media/src/Animations/SaturationEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="SaturationEffect.Value"/>.
+/// </summary>
+public sealed class SaturationEffectAnimation : EffectAnimation<SaturationEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Animations/SepiaEffectAnimation.cs b/components/Media/src/Animations/SepiaEffectAnimation.cs
new file mode 100644
index 00000000..f32d62e3
--- /dev/null
+++ b/components/Media/src/Animations/SepiaEffectAnimation.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media;
+
+namespace CommunityToolkit.WinUI.Animations;
+
+/// <summary>
+/// An effect animation that targets <see cref="SepiaEffect.Intensity"/>.
+/// </summary>
+public sealed class SepiaEffectAnimation : EffectAnimation<SepiaEffect, double?, double>
+{
+    /// <inheritdoc/>
+    protected override string? ExplicitTarget => Target?.Id;
+
+    /// <inheritdoc/>
+    protected override (double?, double?) GetParsedValues()
+    {
+        return (To, From);
+    }
+}
diff --git a/components/Media/src/Brushes/AcrylicBrush.cs b/components/Media/src/Brushes/AcrylicBrush.cs
new file mode 100644
index 00000000..ba95daea
--- /dev/null
+++ b/components/Media/src/Brushes/AcrylicBrush.cs
@@ -0,0 +1,222 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if WINUI2
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.UI;
+using Windows.UI.Composition;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A <see cref="XamlCompositionBrush"/> that implements an acrylic effect with customizable parameters
+/// </summary>
+public sealed class AcrylicBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance in use to set the blur amount
+    /// </summary>
+    /// <remarks>This is only set when <see cref="BackgroundSource"/> is <see cref="AcrylicBackgroundSource.Backdrop"/></remarks>
+    private EffectSetter<float>? blurAmountSetter;
+
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance in use to set the tint color
+    /// </summary>
+    private EffectSetter<Color>? tintColorSetter;
+
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance in use to set the tint mix amount
+    /// </summary>
+    private EffectSetter<float>? tintOpacitySetter;
+
+    /// <summary>
+    /// Gets or sets the background source mode for the effect (the default is <see cref="AcrylicBackgroundSource.Backdrop"/>).
+    /// </summary>
+    public AcrylicBackgroundSource BackgroundSource
+    {
+        get => (AcrylicBackgroundSource)GetValue(BackgroundSourceProperty);
+        set => SetValue(BackgroundSourceProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BackgroundSource"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BackgroundSourceProperty = DependencyProperty.Register(
+        nameof(BackgroundSource),
+        typeof(AcrylicBackgroundSource),
+        typeof(AcrylicBrush),
+        new PropertyMetadata(AcrylicBackgroundSource.Backdrop, OnSourcePropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when <see cref="BackgroundSource"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="AcrylicBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="BackgroundSourceProperty"/></param>
+    private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is AcrylicBrush brush &&
+            brush.CompositionBrush != null)
+        {
+            brush.OnDisconnected();
+            brush.OnConnected();
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the blur amount for the effect (must be a positive value)
+    /// </summary>
+    /// <remarks>This property is ignored when the active mode is <see cref="AcrylicBackgroundSource.HostBackdrop"/></remarks>
+    public double BlurAmount
+    {
+        get => (double)GetValue(BlurAmountProperty);
+        set => SetValue(BlurAmountProperty, Math.Max(value, 0));
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BlurAmount"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BlurAmountProperty = DependencyProperty.Register(
+        nameof(BlurAmount),
+        typeof(double),
+        typeof(AcrylicBrush),
+        new PropertyMetadata(0.0, OnBlurAmountPropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when <see cref="BackgroundSource"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="AcrylicBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="BackgroundSourceProperty"/></param>
+    private static void OnBlurAmountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is AcrylicBrush brush &&
+            brush.BackgroundSource != AcrylicBackgroundSource.HostBackdrop && // Blur is fixed by OS when using HostBackdrop source.
+            brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.blurAmountSetter?.Invoke(target, (float)(double)e.NewValue);
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the tint for the effect
+    /// </summary>
+    public Color TintColor
+    {
+        get => (Color)GetValue(TintColorProperty);
+        set => SetValue(TintColorProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="TintColor"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty TintColorProperty = DependencyProperty.Register(
+        nameof(TintColor),
+        typeof(Color),
+        typeof(AcrylicBrush),
+        new PropertyMetadata(default(Color), OnTintColorPropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when <see cref="TintColor"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="AcrylicBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="TintColorProperty"/></param>
+    private static void OnTintColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is AcrylicBrush brush &&
+            brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.tintColorSetter?.Invoke(target, (Color)e.NewValue);
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the tint opacity factor for the effect (default is 0.5, must be in the [0, 1] range)
+    /// </summary>
+    public double TintOpacity
+    {
+        get => (double)GetValue(TintOpacityProperty);
+        set => SetValue(TintOpacityProperty, Math.Clamp(value, 0, 1));
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="TintOpacity"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty TintOpacityProperty = DependencyProperty.Register(
+        nameof(TintOpacity),
+        typeof(double),
+        typeof(AcrylicBrush),
+        new PropertyMetadata(0.5, OnTintOpacityPropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when <see cref="TintOpacity"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="AcrylicBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="TintOpacityProperty"/></param>
+    private static void OnTintOpacityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is AcrylicBrush brush &&
+            brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.tintOpacitySetter?.Invoke(target, (float)(double)e.NewValue);
+        }
+    }
+
+    /// <summary>
+    /// Gets or sets the <see cref="Uri"/> for the texture to use
+    /// </summary>
+    public Uri TextureUri
+    {
+        get => (Uri)GetValue(TextureUriProperty);
+        set => SetValue(TextureUriProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="TextureUri"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register(
+        nameof(TextureUri),
+        typeof(Uri),
+        typeof(AcrylicBrush),
+        new PropertyMetadata(default, OnTextureUriPropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when <see cref="TextureUri"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="AcrylicBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="TextureUriProperty"/></param>
+    private static void OnTextureUriPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is AcrylicBrush brush &&
+            brush.CompositionBrush != null)
+        {
+            brush.OnDisconnected();
+            brush.OnConnected();
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        switch (BackgroundSource)
+        {
+            case AcrylicBackgroundSource.Backdrop:
+                return PipelineBuilder.FromBackdropAcrylic(
+                    TintColor,
+                    out this.tintColorSetter,
+                    (float)TintOpacity,
+                    out this.tintOpacitySetter,
+                    (float)BlurAmount,
+                    out blurAmountSetter,
+                    TextureUri);
+            case AcrylicBackgroundSource.HostBackdrop:
+                return PipelineBuilder.FromHostBackdropAcrylic(
+                    TintColor,
+                    out this.tintColorSetter,
+                    (float)TintOpacity,
+                    out this.tintOpacitySetter,
+                    TextureUri);
+            default: throw new ArgumentOutOfRangeException(nameof(BackgroundSource), $"Invalid acrylic source: {BackgroundSource}");
+        }
+    }
+}
+#endif
diff --git a/components/Media/src/Brushes/BackdropBlurBrush.cs b/components/Media/src/Brushes/BackdropBlurBrush.cs
new file mode 100644
index 00000000..b6159604
--- /dev/null
+++ b/components/Media/src/Brushes/BackdropBlurBrush.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//// Example brush from https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.media.xamlcompositionbrushbase
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// The <see cref="BackdropBlurBrush"/> is a <see cref="Brush"/> that blurs whatever is behind it in the application.
+/// </summary>
+public class BackdropBlurBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance currently in use
+    /// </summary>
+    private EffectSetter<float>? amountSetter;
+
+    /// <summary>
+    /// Gets or sets the amount of gaussian blur to apply to the background.
+    /// </summary>
+    public double Amount
+    {
+        get => (double)GetValue(AmountProperty);
+        set => SetValue(AmountProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Amount"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty AmountProperty = DependencyProperty.Register(
+        nameof(Amount),
+        typeof(double),
+        typeof(BackdropBlurBrush),
+        new PropertyMetadata(0.0, new PropertyChangedCallback(OnAmountChanged)));
+
+    /// <summary>
+    /// Updates the UI when <see cref="Amount"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="BackdropBlurBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="AmountProperty"/></param>
+    private static void OnAmountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is BackdropBlurBrush brush &&
+            brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.amountSetter?.Invoke(target, (float)brush.Amount);
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        return PipelineBuilder.FromBackdrop().Blur((float)Amount, out this.amountSetter);
+    }
+}
diff --git a/components/Media/src/Brushes/BackdropGammaTransferBrush.cs b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs
new file mode 100644
index 00000000..1b7de60b
--- /dev/null
+++ b/components/Media/src/Brushes/BackdropGammaTransferBrush.cs
@@ -0,0 +1,406 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Graphics.Canvas.Effects;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A brush which alters the colors of whatever is behind it in the application by applying a per-channel gamma transfer function.  See https://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_GammaTransferEffect.htm.
+/// </summary>
+public class BackdropGammaTransferBrush : XamlCompositionBrushBase
+{
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the alpha chennel.
+    /// </summary>
+    public double AlphaAmplitude
+    {
+        get => (double)GetValue(AlphaAmplitudeProperty);
+        set => SetValue(AlphaAmplitudeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="AlphaAmplitude"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty AlphaAmplitudeProperty = DependencyProperty.Register(
+        nameof(AlphaAmplitude),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaAmplitude))));
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to disable alpha transfer.
+    /// </summary>
+    public bool AlphaDisable
+    {
+        get => (bool)GetValue(AlphaDisableProperty);
+        set => SetValue(AlphaDisableProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="AlphaDisable"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty AlphaDisableProperty = DependencyProperty.Register(
+        nameof(AlphaDisable),
+        typeof(bool),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(AlphaDisable))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the alpha chennel.
+    /// </summary>
+    public double AlphaExponent
+    {
+        get => (double)GetValue(AlphaExponentProperty);
+        set => SetValue(AlphaExponentProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="AlphaExponent"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty AlphaExponentProperty = DependencyProperty.Register(
+        nameof(AlphaExponent),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaExponent))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the alpha chennel.
+    /// </summary>
+    public double AlphaOffset
+    {
+        get => (double)GetValue(AlphaOffsetProperty);
+        set => SetValue(AlphaOffsetProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="AlphaOffset"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty AlphaOffsetProperty = DependencyProperty.Register(
+        nameof(AlphaOffset),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(AlphaOffset))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Blue chennel.
+    /// </summary>
+    public double BlueAmplitude
+    {
+        get => (double)GetValue(BlueAmplitudeProperty);
+        set => SetValue(BlueAmplitudeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BlueAmplitude"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BlueAmplitudeProperty = DependencyProperty.Register(
+        nameof(BlueAmplitude),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueAmplitude))));
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to disable Blue transfer.
+    /// </summary>
+    public bool BlueDisable
+    {
+        get => (bool)GetValue(BlueDisableProperty);
+        set => SetValue(BlueDisableProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BlueDisable"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BlueDisableProperty = DependencyProperty.Register(
+        nameof(BlueDisable),
+        typeof(bool),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(BlueDisable))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Blue chennel.
+    /// </summary>
+    public double BlueExponent
+    {
+        get => (double)GetValue(BlueExponentProperty);
+        set => SetValue(BlueExponentProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BlueExponent"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BlueExponentProperty = DependencyProperty.Register(
+        nameof(BlueExponent),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueExponent))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Blue chennel.
+    /// </summary>
+    public double BlueOffset
+    {
+        get => (double)GetValue(BlueOffsetProperty);
+        set => SetValue(BlueOffsetProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="BlueOffset"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty BlueOffsetProperty = DependencyProperty.Register(
+        nameof(BlueOffset),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(BlueOffset))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Green chennel.
+    /// </summary>
+    public double GreenAmplitude
+    {
+        get => (double)GetValue(GreenAmplitudeProperty);
+        set => SetValue(GreenAmplitudeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="GreenAmplitude"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty GreenAmplitudeProperty = DependencyProperty.Register(
+        nameof(GreenAmplitude),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenAmplitude))));
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to disable Green transfer.
+    /// </summary>
+    public bool GreenDisable
+    {
+        get => (bool)GetValue(GreenDisableProperty);
+        set => SetValue(GreenDisableProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="GreenDisable"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty GreenDisableProperty = DependencyProperty.Register(
+        nameof(GreenDisable),
+        typeof(bool),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(GreenDisable))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Green chennel.
+    /// </summary>
+    public double GreenExponent
+    {
+        get => (double)GetValue(GreenExponentProperty);
+        set => SetValue(GreenExponentProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="GreenExponent"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty GreenExponentProperty = DependencyProperty.Register(
+        nameof(GreenExponent),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenExponent))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Green chennel.
+    /// </summary>
+    public double GreenOffset
+    {
+        get => (double)GetValue(GreenOffsetProperty);
+        set => SetValue(GreenOffsetProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="GreenOffset"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty GreenOffsetProperty = DependencyProperty.Register(
+        nameof(GreenOffset),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(GreenOffset))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Red chennel.
+    /// </summary>
+    public double RedAmplitude
+    {
+        get => (double)GetValue(RedAmplitudeProperty);
+        set => SetValue(RedAmplitudeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="RedAmplitude"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty RedAmplitudeProperty = DependencyProperty.Register(
+        nameof(RedAmplitude),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedAmplitude))));
+
+    /// <summary>
+    /// Gets or sets a value indicating whether to disable Red transfer.
+    /// </summary>
+    public bool RedDisable
+    {
+        get => (bool)GetValue(RedDisableProperty);
+        set => SetValue(RedDisableProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="RedDisable"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty RedDisableProperty = DependencyProperty.Register(
+        nameof(RedDisable),
+        typeof(bool),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(RedDisable))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Red chennel.
+    /// </summary>
+    public double RedExponent
+    {
+        get => (double)GetValue(RedExponentProperty);
+        set => SetValue(RedExponentProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="RedExponent"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty RedExponentProperty = DependencyProperty.Register(
+        nameof(RedExponent),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedExponent))));
+
+    /// <summary>
+    /// Gets or sets the amount of scale to apply to the Red chennel.
+    /// </summary>
+    public double RedOffset
+    {
+        get => (double)GetValue(RedOffsetProperty);
+        set => SetValue(RedOffsetProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="RedOffset"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty RedOffsetProperty = DependencyProperty.Register(
+        nameof(RedOffset),
+        typeof(double),
+        typeof(BackdropGammaTransferBrush),
+        new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(RedOffset))));
+
+    private static PropertyChangedCallback OnScalarPropertyChangedHelper(string propertyname)
+    {
+        return (d, e) =>
+        {
+            var brush = (BackdropGammaTransferBrush)d;
+
+            // Unbox and set a new blur amount if the CompositionBrush exists.
+            brush.CompositionBrush?.Properties.InsertScalar("GammaTransfer." + propertyname, (float)(double)e.NewValue);
+        };
+    }
+
+    private static PropertyChangedCallback OnBooleanPropertyChangedHelper(string propertyname)
+    {
+        return (d, e) =>
+        {
+            var brush = (BackdropGammaTransferBrush)d;
+
+            // We can't animate our boolean properties so recreate our internal brush.
+            brush.OnDisconnected();
+            brush.OnConnected();
+        };
+    }
+
+    /// <inheritdoc/>
+    protected override void OnConnected()
+    {
+        // Delay creating composition resources until they're required.
+        if (CompositionBrush == null)
+        {
+#if WINUI2
+            var compositionCapabilities = CompositionCapabilities.GetForCurrentView();
+#else
+            var compositionCapabilities = new CompositionCapabilities();
+#endif
+            // Abort if effects aren't supported.
+            if (!compositionCapabilities.AreEffectsSupported())
+            {
+                return;
+            }
+
+            var backdrop = Window.Current.Compositor.CreateBackdropBrush();
+
+            // Use a Win2D blur affect applied to a CompositionBackdropBrush.
+            var graphicsEffect = new GammaTransferEffect
+            {
+                Name = "GammaTransfer",
+                AlphaAmplitude = (float)AlphaAmplitude,
+                AlphaDisable = AlphaDisable,
+                AlphaExponent = (float)AlphaExponent,
+                AlphaOffset = (float)AlphaOffset,
+                RedAmplitude = (float)RedAmplitude,
+                RedDisable = RedDisable,
+                RedExponent = (float)RedExponent,
+                RedOffset = (float)RedOffset,
+                GreenAmplitude = (float)GreenAmplitude,
+                GreenDisable = GreenDisable,
+                GreenExponent = (float)GreenExponent,
+                GreenOffset = (float)GreenOffset,
+                BlueAmplitude = (float)BlueAmplitude,
+                BlueDisable = BlueDisable,
+                BlueExponent = (float)BlueExponent,
+                BlueOffset = (float)BlueOffset,
+                Source = new CompositionEffectSourceParameter("backdrop")
+            };
+
+            var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect, new[]
+            {
+                "GammaTransfer.AlphaAmplitude",
+                "GammaTransfer.AlphaExponent",
+                "GammaTransfer.AlphaOffset",
+                "GammaTransfer.RedAmplitude",
+                "GammaTransfer.RedExponent",
+                "GammaTransfer.RedOffset",
+                "GammaTransfer.GreenAmplitude",
+                "GammaTransfer.GreenExponent",
+                "GammaTransfer.GreenOffset",
+                "GammaTransfer.BlueAmplitude",
+                "GammaTransfer.BlueExponent",
+                "GammaTransfer.BlueOffset",
+            });
+            var effectBrush = effectFactory.CreateBrush();
+
+            effectBrush.SetSourceParameter("backdrop", backdrop);
+
+            CompositionBrush = effectBrush;
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override void OnDisconnected()
+    {
+        // Dispose of composition resources when no longer in use.
+        if (CompositionBrush != null)
+        {
+            CompositionBrush.Dispose();
+            CompositionBrush = null;
+        }
+    }
+}
diff --git a/components/Media/src/Brushes/BackdropInvertBrush.cs b/components/Media/src/Brushes/BackdropInvertBrush.cs
new file mode 100644
index 00000000..b5cc729e
--- /dev/null
+++ b/components/Media/src/Brushes/BackdropInvertBrush.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//// Example brush from https://blogs.windows.com/buildingapps/2017/07/18/working-brushes-content-xaml-visual-layer-interop-part-one/#z70vPv1QMAvZsceo.97
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// The <see cref="BackdropInvertBrush"/> is a <see cref="Brush"/> which inverts whatever is behind it in the application.
+/// </summary>
+public class BackdropInvertBrush : XamlCompositionEffectBrushBase
+{
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        return PipelineBuilder.FromBackdrop().Invert();
+    }
+}
diff --git a/components/Media/src/Brushes/BackdropSaturationBrush.cs b/components/Media/src/Brushes/BackdropSaturationBrush.cs
new file mode 100644
index 00000000..f965bb2e
--- /dev/null
+++ b/components/Media/src/Brushes/BackdropSaturationBrush.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Brush which applies a SaturationEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SaturationEffect.htm
+/// </summary>
+public class BackdropSaturationBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance currently in use
+    /// </summary>
+    private EffectSetter<float>? setter;
+
+    /// <summary>
+    /// Gets or sets the amount of gaussian blur to apply to the background.
+    /// </summary>
+    public double Saturation
+    {
+        get => (double)GetValue(SaturationProperty);
+        set => SetValue(SaturationProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Saturation"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty SaturationProperty = DependencyProperty.Register(
+        nameof(Saturation),
+        typeof(double),
+        typeof(BackdropSaturationBrush),
+        new PropertyMetadata(0.5, new PropertyChangedCallback(OnSaturationChanged)));
+
+    /// <summary>
+    /// Updates the UI when <see cref="Saturation"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="BackdropSaturationBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="SaturationProperty"/></param>
+    private static void OnSaturationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        var brush = (BackdropSaturationBrush)d;
+
+        // Clamp Value as per docs http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SaturationEffect.htm
+        var value = (float)(double)e.NewValue;
+        if (value > 1.0)
+        {
+            brush.Saturation = 1.0;
+        }
+        else if (value < 0.0)
+        {
+            brush.Saturation = 0.0;
+        }
+
+        // Unbox and set a new blur amount if the CompositionBrush exists
+        if (brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.setter?.Invoke(target, (float)brush.Saturation);
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        return PipelineBuilder.FromBackdrop().Saturation((float)Saturation, out setter);
+    }
+}
diff --git a/components/Media/src/Brushes/BackdropSepiaBrush.cs b/components/Media/src/Brushes/BackdropSepiaBrush.cs
new file mode 100644
index 00000000..73f28a36
--- /dev/null
+++ b/components/Media/src/Brushes/BackdropSepiaBrush.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Brush which applies a SepiaEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SepiaEffect.htm
+/// </summary>
+public class BackdropSepiaBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// The <see cref="EffectSetter{T}"/> instance currently in use
+    /// </summary>
+    private EffectSetter<float>? setter;
+
+    /// <summary>
+    /// Gets or sets the amount of gaussian blur to apply to the background.
+    /// </summary>
+    public double Intensity
+    {
+        get => (double)GetValue(IntensityProperty);
+        set => SetValue(IntensityProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Intensity"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty IntensityProperty = DependencyProperty.Register(
+        nameof(Intensity),
+        typeof(double),
+        typeof(BackdropSepiaBrush),
+        new PropertyMetadata(0.5, new PropertyChangedCallback(OnIntensityChanged)));
+
+    /// <summary>
+    /// Updates the UI when <see cref="Intensity"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="BackdropSepiaBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="IntensityProperty"/></param>
+    private static void OnIntensityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        var brush = (BackdropSepiaBrush)d;
+
+        // Clamp Value as per docs http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SepiaEffect.htm
+        var value = (float)(double)e.NewValue;
+        if (value > 1.0)
+        {
+            brush.Intensity = 1.0;
+        }
+        else if (value < 0.0)
+        {
+            brush.Intensity = 0.0;
+        }
+
+        // Unbox and set a new blur amount if the CompositionBrush exists.
+        if (brush.CompositionBrush is CompositionBrush target)
+        {
+            brush.setter?.Invoke(target, (float)brush.Intensity);
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        return PipelineBuilder.FromBackdrop().Sepia((float)Intensity, out setter);
+    }
+}
diff --git a/components/Media/src/Brushes/Base/XamlCompositionEffectBrushBase.cs b/components/Media/src/Brushes/Base/XamlCompositionEffectBrushBase.cs
new file mode 100644
index 00000000..e4b6f921
--- /dev/null
+++ b/components/Media/src/Brushes/Base/XamlCompositionEffectBrushBase.cs
@@ -0,0 +1,143 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A custom <see cref="XamlCompositionBrushBase"/> <see langword="class"/> that's ready to be used with a custom <see cref="PipelineBuilder"/> pipeline.
+/// </summary>
+public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase
+{
+    /// <summary>
+    /// The initialization <see cref="AsyncMutex"/> instance.
+    /// </summary>
+    private readonly AsyncMutex connectedMutex = new AsyncMutex();
+
+    /// <summary>
+    /// A method that builds and returns the <see cref="PipelineBuilder"/> pipeline to use in the current instance.<para/>
+    /// This method can also be used to store any needed <see cref="EffectSetter{T}"/> or <see cref="EffectAnimation{T}"/>
+    /// instances in local fields, for later use (they will need to be called upon <see cref="XamlCompositionBrushBase.CompositionBrush"/>).
+    /// </summary>
+    /// <returns>A <see cref="PipelineBuilder"/> instance to create the brush to display.</returns>
+    protected abstract PipelineBuilder OnPipelineRequested();
+
+    private bool isEnabled = true;
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the current brush is using the provided pipeline, or the fallback color.
+    /// </summary>
+    public bool IsEnabled
+    {
+        get => this.isEnabled;
+        set => this.OnEnabledToggled(value);
+    }
+
+    /// <inheritdoc/>
+    protected override async void OnConnected()
+    {
+        using (await this.connectedMutex.LockAsync())
+        {
+            if (CompositionBrush == null)
+            {
+#if WINUI2
+                var compositionCapabilities = CompositionCapabilities.GetForCurrentView();
+#else
+                var compositionCapabilities = new CompositionCapabilities();
+#endif
+                // Abort if effects aren't supported.
+                if (!compositionCapabilities.AreEffectsSupported())
+                {
+                    return;
+                }
+
+                if (this.isEnabled)
+                {
+                    CompositionBrush = await OnPipelineRequested().BuildAsync();
+                }
+                else
+                {
+                    CompositionBrush = await PipelineBuilder.FromColor(FallbackColor).BuildAsync();
+                }
+
+                OnCompositionBrushUpdated();
+            }
+        }
+
+        base.OnConnected();
+    }
+
+    /// <inheritdoc/>
+    protected override async void OnDisconnected()
+    {
+        using (await this.connectedMutex.LockAsync())
+        {
+            if (CompositionBrush != null)
+            {
+                CompositionBrush.Dispose();
+                CompositionBrush = null;
+
+                OnCompositionBrushUpdated();
+            }
+        }
+
+        base.OnDisconnected();
+    }
+
+    /// <summary>
+    /// Updates the <see cref="XamlCompositionBrushBase.CompositionBrush"/> property depending on the input value.
+    /// </summary>
+    /// <param name="value">The new value being set to the <see cref="IsEnabled"/> property.</param>
+    protected async void OnEnabledToggled(bool value)
+    {
+        using (await this.connectedMutex.LockAsync())
+        {
+            if (this.isEnabled == value)
+            {
+                return;
+            }
+
+            this.isEnabled = value;
+
+            if (CompositionBrush != null)
+            {
+#if WINUI2
+                var compositionCapabilities = CompositionCapabilities.GetForCurrentView();
+#else
+                var compositionCapabilities = new CompositionCapabilities();
+#endif
+                // Abort if effects aren't supported.
+                if (!compositionCapabilities.AreEffectsSupported())
+                {
+                    return;
+                }
+
+                if (this.isEnabled)
+                {
+                    CompositionBrush = await OnPipelineRequested().BuildAsync();
+                }
+                else
+                {
+                    CompositionBrush = await PipelineBuilder.FromColor(FallbackColor).BuildAsync();
+                }
+
+                OnCompositionBrushUpdated();
+            }
+        }
+    }
+
+    /// <summary>
+    /// Invoked whenever the <see cref="XamlCompositionBrushBase.CompositionBrush"/> property is updated.
+    /// </summary>
+    protected virtual void OnCompositionBrushUpdated()
+    {
+    }
+}
diff --git a/components/Media/src/Brushes/CanvasBrushBase.cs b/components/Media/src/Brushes/CanvasBrushBase.cs
new file mode 100644
index 00000000..55cd1be0
--- /dev/null
+++ b/components/Media/src/Brushes/CanvasBrushBase.cs
@@ -0,0 +1,149 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.UI.Composition;
+
+#if WINUI2
+using Windows.UI.Composition;
+using Windows.Graphics.DirectX;
+#elif WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.Graphics.DirectX;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Helper Brush class to interop with Win2D Canvas calls.
+/// </summary>
+public abstract class CanvasBrushBase : XamlCompositionBrushBase
+{
+    private CompositionSurfaceBrush? _surfaceBrush;
+
+    /// <summary>
+    /// Gets or sets the internal surface render width.  Modify during construction.
+    /// </summary>
+    protected float SurfaceWidth { get; set; }
+
+    /// <summary>
+    /// Gets or sets the internal surface render height.  Modify during construction.
+    /// </summary>
+    protected float SurfaceHeight { get; set; }
+
+    private CanvasDevice? _device;
+
+    private CompositionGraphicsDevice? _graphics;
+
+    /// <summary>
+    /// Implemented by parent class and called when canvas is being constructed for brush.
+    /// </summary>
+    /// <param name="device">Canvas device.</param>
+    /// <param name="session">Canvas drawing session.</param>
+    /// <param name="size">Size of surface to draw on.</param>
+    /// <returns>True if drawing was completed and the brush is ready, otherwise return False to not create brush yet.</returns>
+    protected abstract bool OnDraw(CanvasDevice device, CanvasDrawingSession session, Vector2 size);
+
+    /// <summary>
+    /// Initializes the Composition Brush.
+    /// </summary>
+    protected override void OnConnected()
+    {
+        base.OnConnected();
+
+        if (_device != null)
+        {
+            _device.DeviceLost -= CanvasDevice_DeviceLost;
+        }
+
+        _device = CanvasDevice.GetSharedDevice();
+        _device.DeviceLost += CanvasDevice_DeviceLost;
+
+        if (_graphics != null)
+        {
+            _graphics.RenderingDeviceReplaced -= CanvasDevice_RenderingDeviceReplaced;
+        }
+
+        _graphics = CanvasComposition.CreateCompositionGraphicsDevice(Window.Current.Compositor, _device);
+        _graphics.RenderingDeviceReplaced += CanvasDevice_RenderingDeviceReplaced;
+
+        // Delay creating composition resources until they're required.
+        if (CompositionBrush == null)
+        {
+#if WINUI2
+            var compositionCapabilities = CompositionCapabilities.GetForCurrentView();
+#else
+            var compositionCapabilities = new CompositionCapabilities();
+#endif
+            // Abort if effects aren't supported.
+            if (!compositionCapabilities.AreEffectsSupported())
+            {
+                return;
+            }
+
+            var size = new Vector2(SurfaceWidth, SurfaceHeight);
+            var surface = _graphics.CreateDrawingSurface(size.ToSize(), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
+
+            using (var session = CanvasComposition.CreateDrawingSession(surface))
+            {
+                // Call Implementor to draw on session.
+                if (!OnDraw(_device, session, size))
+                {
+                    return;
+                }
+            }
+
+            _surfaceBrush = Window.Current.Compositor.CreateSurfaceBrush(surface);
+            _surfaceBrush.Stretch = CompositionStretch.Fill;
+
+            CompositionBrush = _surfaceBrush;
+        }
+    }
+
+    private void CanvasDevice_RenderingDeviceReplaced(CompositionGraphicsDevice sender, object args)
+    {
+        OnDisconnected();
+        OnConnected();
+    }
+
+    private void CanvasDevice_DeviceLost(CanvasDevice sender, object args)
+    {
+        OnDisconnected();
+        OnConnected();
+    }
+
+    /// <summary>
+    /// Deconstructs the Composition Brush.
+    /// </summary>
+    protected override void OnDisconnected()
+    {
+        base.OnDisconnected();
+
+        if (_device != null)
+        {
+            _device.DeviceLost -= CanvasDevice_DeviceLost;
+            _device = null;
+        }
+
+        if (_graphics != null)
+        {
+            _graphics.RenderingDeviceReplaced -= CanvasDevice_RenderingDeviceReplaced;
+            _graphics = null;
+        }
+
+        // Dispose of composition resources when no longer in use.
+        if (CompositionBrush != null)
+        {
+            CompositionBrush.Dispose();
+            CompositionBrush = null;
+        }
+
+        if (_surfaceBrush != null)
+        {
+            _surfaceBrush.Dispose();
+            _surfaceBrush = null;
+        }
+    }
+}
diff --git a/components/Media/src/Brushes/ImageBlendBrush.cs b/components/Media/src/Brushes/ImageBlendBrush.cs
new file mode 100644
index 00000000..4f5123ba
--- /dev/null
+++ b/components/Media/src/Brushes/ImageBlendBrush.cs
@@ -0,0 +1,214 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//// Image loading reference from https://blogs.windows.com/buildingapps/2017/07/18/working-brushes-content-xaml-visual-layer-interop-part-one/#MA0k4EYWzqGKV501.97
+
+using Microsoft.Graphics.Canvas.Effects;
+using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Media.Imaging;
+#elif WINUI2
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Media.Imaging;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Brush which blends a <see cref="BitmapImage"/> to the Backdrop in a given mode. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffect.htm.
+/// </summary>
+public class ImageBlendBrush : XamlCompositionBrushBase
+{
+    private LoadedImageSurface? _surface;
+    private CompositionSurfaceBrush? _surfaceBrush;
+
+    /// <summary>
+    /// Gets or sets the <see cref="BitmapImage"/> source of the image to composite.
+    /// </summary>
+    public ImageSource Source
+    {
+        get => (ImageSource)GetValue(SourceProperty);
+        set => SetValue(SourceProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Source"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
+        nameof(Source),
+        typeof(ImageSource), // We use ImageSource type so XAML engine will automatically construct proper object from String.
+        typeof(ImageBlendBrush),
+        new PropertyMetadata(null, OnImageSourceChanged));
+
+    /// <summary>
+    /// Gets or sets how to stretch the image within the brush.
+    /// </summary>
+    public Stretch Stretch
+    {
+        get => (Stretch)GetValue(StretchProperty);
+        set => SetValue(StretchProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Stretch"/> dependency property.
+    /// Requires 16299 or higher for modes other than None.
+    /// </summary>
+    public static readonly DependencyProperty StretchProperty = DependencyProperty.Register(
+        nameof(Stretch),
+        typeof(Stretch),
+        typeof(ImageBlendBrush),
+        new PropertyMetadata(Stretch.None, OnStretchChanged));
+
+    /// <summary>
+    /// Gets or sets how to blend the image with the backdrop.
+    /// </summary>
+    public ImageBlendMode Mode
+    {
+        get => (ImageBlendMode)GetValue(ModeProperty);
+        set => SetValue(ModeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="Mode"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
+        nameof(Mode),
+        typeof(ImageBlendMode),
+        typeof(ImageBlendBrush),
+        new PropertyMetadata(ImageBlendMode.Multiply, OnModeChanged));
+
+    private static void OnImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        var brush = (ImageBlendBrush)d;
+
+        // Unbox and update surface if CompositionBrush exists
+        if (brush._surfaceBrush != null)
+        {
+            // If UriSource is invalid, StartLoadFromUri will return a blank texture.
+            var uri = (e.NewValue as BitmapImage)?.UriSource ?? new Uri("ms-appx:///");
+            var newSurface = LoadedImageSurface.StartLoadFromUri(uri);
+
+            brush._surface = newSurface;
+            brush._surfaceBrush.Surface = newSurface;
+        }
+        else
+        {
+            // If we didn't initially have a valid surface, we need to recreate our effect now.
+            brush.OnDisconnected();
+            brush.OnConnected();
+        }
+    }
+
+    private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        var brush = (ImageBlendBrush)d;
+
+        // Unbox and update surface if CompositionBrush exists
+        if (brush._surfaceBrush != null)
+        {
+            // Modify the stretch property on our brush.
+            brush._surfaceBrush.Stretch = CompositionStretchFromStretch((Stretch)e.NewValue);
+        }
+    }
+
+    private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        var brush = (ImageBlendBrush)d;
+
+        // We can't animate our enum properties so recreate our internal brush.
+        brush.OnDisconnected();
+        brush.OnConnected();
+    }
+
+    /// <inheritdoc/>
+    protected override void OnConnected()
+    {
+        // Delay creating composition resources until they're required.
+        if (CompositionBrush == null && Source != null && Source is BitmapImage bitmap)
+        {
+            // Use LoadedImageSurface API to get ICompositionSurface from image uri provided
+            // If UriSource is invalid, StartLoadFromUri will return a blank texture.
+            _surface = LoadedImageSurface.StartLoadFromUri(bitmap.UriSource);
+
+            // Load Surface onto SurfaceBrush
+            _surfaceBrush = Window.Current.Compositor.CreateSurfaceBrush(_surface);
+            _surfaceBrush.Stretch = CompositionStretchFromStretch(Stretch);
+
+#if WINUI2
+            var compositionCapabilities = CompositionCapabilities.GetForCurrentView();
+#else
+            var compositionCapabilities = new CompositionCapabilities();
+#endif
+            // Abort if effects aren't supported.
+            if (!compositionCapabilities.AreEffectsSupported())
+            {
+                // Just use image straight-up, if we don't support effects.
+                CompositionBrush = _surfaceBrush;
+                return;
+            }
+
+            var backdrop = Window.Current.Compositor.CreateBackdropBrush();
+
+            // Use a Win2D invert affect applied to a CompositionBackdropBrush.
+            var graphicsEffect = new CanvasBlendEffect
+            {
+                Name = "Invert",
+                Mode = (BlendEffectMode)(int)Mode,
+                Background = new CompositionEffectSourceParameter("backdrop"),
+                Foreground = new CompositionEffectSourceParameter("image")
+            };
+
+            var effectFactory = Window.Current.Compositor.CreateEffectFactory(graphicsEffect);
+            var effectBrush = effectFactory.CreateBrush();
+
+            effectBrush.SetSourceParameter("backdrop", backdrop);
+            effectBrush.SetSourceParameter("image", _surfaceBrush);
+
+            CompositionBrush = effectBrush;
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override void OnDisconnected()
+    {
+        // Dispose of composition resources when no longer in use.
+        if (CompositionBrush != null)
+        {
+            CompositionBrush.Dispose();
+            CompositionBrush = null;
+        }
+
+        if (_surfaceBrush != null)
+        {
+            _surfaceBrush.Dispose();
+            _surfaceBrush = null;
+        }
+
+        if (_surface != null)
+        {
+            _surface.Dispose();
+            _surface = null;
+        }
+    }
+
+    //// Helper to allow XAML developer to use XAML stretch property rather than another enum.
+    private static CompositionStretch CompositionStretchFromStretch(Stretch value)
+    {
+        switch (value)
+        {
+            case Stretch.None:
+                return CompositionStretch.None;
+            case Stretch.Fill:
+                return CompositionStretch.Fill;
+            case Stretch.Uniform:
+                return CompositionStretch.Uniform;
+            case Stretch.UniformToFill:
+                return CompositionStretch.UniformToFill;
+        }
+
+        return CompositionStretch.None;
+    }
+}
diff --git a/components/Media/src/Brushes/PipelineBrush.cs b/components/Media/src/Brushes/PipelineBrush.cs
new file mode 100644
index 00000000..8267c7dd
--- /dev/null
+++ b/components/Media/src/Brushes/PipelineBrush.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A <see cref="Brush"/> that renders a customizable Composition/Win2D effects pipeline
+/// </summary>
+[ContentProperty(Name = nameof(Effects))]
+public sealed class PipelineBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// Gets or sets the source for the current pipeline (defaults to a <see cref="BackdropSourceExtension"/> with <see cref="AcrylicBackgroundSource.Backdrop"/> source).
+    /// </summary>
+    public PipelineBuilder? Source { get; set; }
+
+    /// <summary>
+    /// Gets or sets the collection of effects to use in the current pipeline.
+    /// </summary>
+    public IList<PipelineEffect> Effects
+    {
+        get
+        {
+            if (GetValue(EffectsProperty) is not IList<PipelineEffect> effects)
+            {
+                effects = new List<PipelineEffect>();
+
+                SetValue(EffectsProperty, effects);
+            }
+
+            return effects;
+        }
+        set => SetValue(EffectsProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <seealso cref="Effects"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register(
+        nameof(Effects),
+        typeof(IList<PipelineEffect>),
+        typeof(PipelineBrush),
+        new PropertyMetadata(null));
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        PipelineBuilder builder = Source ?? PipelineBuilder.FromBackdrop();
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            builder = effect.AppendToBuilder(builder);
+        }
+
+        return builder;
+    }
+
+    /// <inheritdoc/>
+    protected override void OnCompositionBrushUpdated()
+    {
+        foreach (IPipelineEffect effect in Effects)
+        {
+            effect.NotifyCompositionBrushInUse(CompositionBrush);
+        }
+    }
+}
diff --git a/components/Media/src/Brushes/TilesBrush.cs b/components/Media/src/Brushes/TilesBrush.cs
new file mode 100644
index 00000000..894e98ab
--- /dev/null
+++ b/components/Media/src/Brushes/TilesBrush.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A <see cref="XamlCompositionBrush"/> that displays a tiled image
+/// </summary>
+public sealed class TilesBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// Gets or sets the <see cref="Uri"/> to the texture to use
+    /// </summary>
+    public Uri TextureUri
+    {
+        get => (Uri)GetValue(TextureUriProperty);
+        set => SetValue(TextureUriProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="TextureUri"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register(
+        nameof(TextureUri),
+        typeof(Uri),
+        typeof(TilesBrush),
+        new PropertyMetadata(default, OnDependencyPropertyChanged));
+
+    /// <summary>
+    /// Gets or sets the DPI mode used to render the texture (the default is <see cref="Media.DpiMode.DisplayDpiWith96AsLowerBound"/>)
+    /// </summary>
+    public DpiMode DpiMode
+    {
+        get => (DpiMode)GetValue(DpiModeProperty);
+        set => SetValue(DpiModeProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <see cref="DpiMode"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty DpiModeProperty = DependencyProperty.Register(
+        nameof(DpiMode),
+        typeof(DpiMode),
+        typeof(TilesBrush),
+        new PropertyMetadata(DpiMode.DisplayDpiWith96AsLowerBound, OnDependencyPropertyChanged));
+
+    /// <summary>
+    /// Updates the UI when either <see cref="TextureUri"/> or <see cref="DpiMode"/> changes
+    /// </summary>
+    /// <param name="d">The current <see cref="TilesBrush"/> instance</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for <see cref="TextureUriProperty"/> or <see cref="DpiModeProperty"/></param>
+    private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        if (d is TilesBrush brush &&
+            brush.CompositionBrush != null)
+        {
+            brush.OnDisconnected();
+            brush.OnConnected();
+        }
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        if (TextureUri is Uri uri)
+        {
+            return PipelineBuilder.FromTiles(uri, DpiMode);
+        }
+
+        return PipelineBuilder.FromColor(default);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Brushes/XamlCompositionBrush.cs b/components/Media/src/Brushes/XamlCompositionBrush.cs
new file mode 100644
index 00000000..2b507da9
--- /dev/null
+++ b/components/Media/src/Brushes/XamlCompositionBrush.cs
@@ -0,0 +1,93 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.System;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A <see langword="delegate"/> that represents a custom effect setter that can be applied to a <see cref="XamlCompositionBrush"/> instance
+/// </summary>
+/// <typeparam name="T">The type of property value to set</typeparam>
+/// <param name="value">The effect target value</param>
+public delegate void XamlEffectSetter<in T>(T value)
+    where T : unmanaged;
+
+/// <summary>
+/// A <see langword="delegate"/> that represents a custom effect animation that can be applied to a <see cref="XamlCompositionBrush"/> instance
+/// </summary>
+/// <typeparam name="T">The type of property value to animate</typeparam>
+/// <param name="value">The animation target value</param>
+/// <param name="duration">The animation duration</param>
+/// <returns>A <see cref="Task"/> that completes when the target animation completes</returns>
+public delegate Task XamlEffectAnimation<in T>(T value, TimeSpan duration)
+    where T : unmanaged;
+
+/// <summary>
+/// A simple <see langword="class"/> that can be used to quickly create XAML brushes from arbitrary <see cref="PipelineBuilder"/> pipelines
+/// </summary>
+public sealed class XamlCompositionBrush : XamlCompositionEffectBrushBase
+{
+    /// <summary>
+    /// Gets the <see cref="PipelineBuilder"/> pipeline for the current instance
+    /// </summary>
+    public PipelineBuilder Pipeline { get; }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="XamlCompositionBrush"/> class.
+    /// </summary>
+    /// <param name="pipeline">The <see cref="PipelineBuilder"/> instance to create the effect</param>
+    public XamlCompositionBrush(PipelineBuilder pipeline) => this.Pipeline = pipeline;
+
+    /// <summary>
+    /// Binds an <see cref="EffectSetter{T}"/> to the composition brush in the current instance
+    /// </summary>
+    /// <typeparam name="T">The type of property value to set</typeparam>
+    /// <param name="setter">The input setter</param>
+    /// <param name="bound">The resulting setter</param>
+    /// <returns>The current <see cref="XamlCompositionBrush"/> instance</returns>
+    [Pure]
+    public XamlCompositionBrush Bind<T>(EffectSetter<T> setter, out XamlEffectSetter<T> bound)
+        where T : unmanaged
+    {
+        bound = value => setter(this.CompositionBrush, value);
+
+        return this;
+    }
+
+    /// <summary>
+    /// Binds an <see cref="EffectAnimation{T}"/> to the composition brush in the current instance
+    /// </summary>
+    /// <typeparam name="T">The type of property value to animate</typeparam>
+    /// <param name="animation">The input animation</param>
+    /// <param name="bound">The resulting animation</param>
+    /// <returns>The current <see cref="XamlCompositionBrush"/> instance</returns>
+    [Pure]
+    public XamlCompositionBrush Bind<T>(EffectAnimation<T> animation, out XamlEffectAnimation<T> bound)
+        where T : unmanaged
+    {
+        bound = (value, duration) => animation(this.CompositionBrush, value, duration);
+
+        return this;
+    }
+
+    /// <inheritdoc cref="XamlCompositionEffectBrushBase"/>
+    protected override PipelineBuilder OnPipelineRequested() => this.Pipeline;
+
+    /// <summary>
+    /// Clones the current instance by rebuilding the source <see cref="Windows.UI.Xaml.Media.Brush"/>. Use this method to reuse the same effects pipeline on a different <see cref="DispatcherQueue"/>
+    /// </summary>
+    /// <remarks>
+    /// If your code is already on the same thread, you can just assign this brush to an arbitrary number of controls and it will still work correctly.
+    /// This method is only meant to be used to create a new instance of this brush using the same pipeline, on threads that can't access the current instance, for example in secondary app windows.
+    /// </remarks>
+    /// <returns>A <see cref="XamlCompositionBrush"/> instance using the current effects pipeline</returns>
+    [Pure]
+    public XamlCompositionBrush Clone()
+    {
+        return new XamlCompositionBrush(this.Pipeline);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/CommunityToolkit.WinUI.Media.csproj b/components/Media/src/CommunityToolkit.WinUI.Media.csproj
new file mode 100644
index 00000000..dc295949
--- /dev/null
+++ b/components/Media/src/CommunityToolkit.WinUI.Media.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
+  <PropertyGroup>
+    <ToolkitComponentName>Media</ToolkitComponentName>
+    <Description>This package contains Media.</Description>
+    <Version>8.0.0-beta.1</Version>
+
+    <!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
+    <RootNamespace>CommunityToolkit.WinUI.MediaRns</RootNamespace>
+  </PropertyGroup>
+
+  <!-- Sets this up as a toolkit component's source project -->
+  <Import Project="$(ToolingDirectory)\ToolkitComponent.SourceProject.props" />
+
+  <ItemGroup>
+    <ProjectReference Include="$(ToolkitAnimationSourceProject)" />
+  </ItemGroup>
+</Project>
diff --git a/components/Media/src/Dependencies.props b/components/Media/src/Dependencies.props
new file mode 100644
index 00000000..07187681
--- /dev/null
+++ b/components/Media/src/Dependencies.props
@@ -0,0 +1,22 @@
+<!--
+    WinUI 2 under UWP uses TargetFramework uap10.0.*
+    WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
+    However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.
+    
+    MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.
+
+    For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
+    For UWP / WinAppSDK / Uno packages, place the package references here.
+-->
+<Project>
+  <!-- WinUI 2 / UWP -->
+  <ItemGroup Condition="'$(IsUwp)' == 'true'">
+    <PackageReference Include="Win2D.uwp" Version="1.26.0" />
+    <PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
+  </ItemGroup>
+
+  <!-- WinUI 3 / WinAppSdk -->
+  <ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
+    <PackageReference Include="Microsoft.Graphics.Win2D" Version="1.0.5.1" />
+  </ItemGroup>
+</Project>
diff --git a/components/Media/src/Effects/Abstract/ImageSourceBaseExtension.cs b/components/Media/src/Effects/Abstract/ImageSourceBaseExtension.cs
new file mode 100644
index 00000000..011b6c0c
--- /dev/null
+++ b/components/Media/src/Effects/Abstract/ImageSourceBaseExtension.cs
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An image based effect that loads an image at the specified location
+/// </summary>
+[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))]
+public abstract class ImageSourceBaseExtension : MarkupExtension
+{
+    /// <summary>
+    /// Gets or sets the <see cref="System.Uri"/> for the image to load
+    /// </summary>
+    public Uri? Uri { get; set; }
+
+    /// <summary>
+    /// Gets or sets the DPI mode used to render the image (the default is <see cref="Media.DpiMode.DisplayDpiWith96AsLowerBound"/>)
+    /// </summary>
+    public DpiMode DpiMode { get; set; } = DpiMode.DisplayDpiWith96AsLowerBound;
+
+    /// <summary>
+    /// Gets or sets the cache mode to use when loading the image (the default is <see cref="Media.CacheMode.Default"/>)
+    /// </summary>
+    public CacheMode CacheMode { get; set; } = CacheMode.Default;
+}
diff --git a/components/Media/src/Effects/Abstract/PipelineEffect.cs b/components/Media/src/Effects/Abstract/PipelineEffect.cs
new file mode 100644
index 00000000..0a59447f
--- /dev/null
+++ b/components/Media/src/Effects/Abstract/PipelineEffect.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A base pipeline effect.
+/// </summary>
+public abstract class PipelineEffect : DependencyObject, IPipelineEffect
+{
+    /// <inheritdoc/>
+    public CompositionBrush? Brush { get; private set; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether the effect can be animated.
+    /// </summary>
+    public bool IsAnimatable { get; set; }
+
+    /// <inheritdoc/>
+    public abstract PipelineBuilder AppendToBuilder(PipelineBuilder builder);
+
+    /// <inheritdoc/>
+    public virtual void NotifyCompositionBrushInUse(CompositionBrush brush)
+    {
+        Brush = brush;
+    }
+}
diff --git a/components/Media/src/Effects/BlendEffect.cs b/components/Media/src/Effects/BlendEffect.cs
new file mode 100644
index 00000000..04933d3d
--- /dev/null
+++ b/components/Media/src/Effects/BlendEffect.cs
@@ -0,0 +1,66 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.Graphics.Canvas.Effects;
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A blend effect that merges the current builder with an input one
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.BlendEffect"/> effect</remarks>
+[ContentProperty(Name = nameof(Effects))]
+public sealed class BlendEffect : PipelineEffect
+{
+    /// <summary>
+    /// Gets or sets the input to merge with the current instance (defaults to a <see cref="BackdropSourceExtension"/> with <see cref="Windows.UI.Xaml.Media.AcrylicBackgroundSource.Backdrop"/> source).
+    /// </summary>
+    public PipelineBuilder? Source { get; set; }
+
+    /// <summary>
+    /// Gets or sets the effects to apply to the input to merge with the current instance
+    /// </summary>
+    public List<IPipelineEffect> Effects { get; set; } = new List<IPipelineEffect>();
+
+    /// <summary>
+    /// Gets or sets the blending mode to use (the default mode is <see cref="ImageBlendMode.Multiply"/>)
+    /// </summary>
+    public ImageBlendMode Mode { get; set; }
+
+    /// <summary>
+    /// Gets or sets the placement of the input builder with respect to the current one (the default is <see cref="Media.Placement.Foreground"/>)
+    /// </summary>
+    public Placement Placement { get; set; } = Placement.Foreground;
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        PipelineBuilder inputBuilder = Source ?? PipelineBuilder.FromBackdrop();
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            inputBuilder = effect.AppendToBuilder(inputBuilder);
+        }
+
+        return builder.Blend(inputBuilder, (BlendEffectMode)Mode, Placement);
+    }
+
+    /// <inheritdoc/>
+    public override void NotifyCompositionBrushInUse(CompositionBrush brush)
+    {
+        base.NotifyCompositionBrushInUse(brush);
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            effect.NotifyCompositionBrushInUse(brush);
+        }
+    }
+}
diff --git a/components/Media/src/Effects/BlurEffect.cs b/components/Media/src/Effects/BlurEffect.cs
new file mode 100644
index 00000000..42284934
--- /dev/null
+++ b/components/Media/src/Effects/BlurEffect.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A gaussian blur effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.GaussianBlurEffect"/> effect</remarks>
+public sealed class BlurEffect : PipelineEffect
+{
+    private double amount;
+
+    /// <summary>
+    /// Gets or sets the blur amount for the effect (must be a positive value)
+    /// </summary>
+    public double Amount
+    {
+        get => this.amount;
+        set => this.amount = Math.Max(value, 0);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Blur((float)Amount, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Blur((float)Amount);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/CrossFadeEffect.cs b/components/Media/src/Effects/CrossFadeEffect.cs
new file mode 100644
index 00000000..84210ab0
--- /dev/null
+++ b/components/Media/src/Effects/CrossFadeEffect.cs
@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A blend effect that merges the current builder with an input one
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.CrossFadeEffect"/> effect</remarks>
+[ContentProperty(Name = nameof(Effects))]
+public sealed class CrossFadeEffect : PipelineEffect
+{
+    /// <summary>
+    /// Gets or sets the input to merge with the current instance (defaults to a <see cref="BackdropSourceExtension"/> with <see cref="Windows.UI.Xaml.Media.AcrylicBackgroundSource.Backdrop"/> source).
+    /// </summary>
+    public PipelineBuilder? Source { get; set; }
+
+    /// <summary>
+    /// Gets or sets the effects to apply to the input to merge with the current instance
+    /// </summary>
+    public List<IPipelineEffect> Effects { get; set; } = new List<IPipelineEffect>();
+
+    private double factor = 0.5;
+
+    /// <summary>
+    /// Gets or sets the The cross fade factor to blend the input effects (default to 0.5, should be in the [0, 1] range)
+    /// </summary>
+    public double Factor
+    {
+        get => this.factor;
+        set => this.factor = Math.Clamp(value, 0, 1);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        PipelineBuilder inputBuilder = Source ?? PipelineBuilder.FromBackdrop();
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            inputBuilder = effect.AppendToBuilder(inputBuilder);
+        }
+
+        if (IsAnimatable)
+        {
+            builder = builder.CrossFade(inputBuilder, (float)Factor, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.CrossFade(inputBuilder, (float)Factor);
+    }
+
+    /// <inheritdoc/>
+    public override void NotifyCompositionBrushInUse(CompositionBrush brush)
+    {
+        base.NotifyCompositionBrushInUse(brush);
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            effect.NotifyCompositionBrushInUse(brush);
+        }
+    }
+}
diff --git a/components/Media/src/Effects/ExposureEffect.cs b/components/Media/src/Effects/ExposureEffect.cs
new file mode 100644
index 00000000..5f19ac8e
--- /dev/null
+++ b/components/Media/src/Effects/ExposureEffect.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An exposure effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.ExposureEffect"/> effect</remarks>
+public sealed class ExposureEffect : PipelineEffect
+{
+    private double amount;
+
+    /// <summary>
+    /// Gets or sets the amount of exposure to apply to the background (defaults to 0, should be in the [-2, 2] range).
+    /// </summary>
+    public double Amount
+    {
+        get => this.amount;
+        set => this.amount = Math.Clamp(value, -2, 2);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Exposure((float)Amount, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Exposure((float)Amount);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs b/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs
new file mode 100644
index 00000000..cb555ab2
--- /dev/null
+++ b/components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs
@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if WINUI2
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A custom acrylic effect that can be inserted into a pipeline
+/// </summary>
+/// <remarks>This effect mirrors the look of the default <see cref="AcrylicBrush"/> implementation</remarks>
+[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))]
+public sealed class AcrylicSourceExtension : MarkupExtension
+{
+    /// <summary>
+    /// Gets or sets the background source mode for the effect (the default is <see cref="AcrylicBackgroundSource.Backdrop"/>).
+    /// </summary>
+    public AcrylicBackgroundSource BackgroundSource { get; set; } = AcrylicBackgroundSource.Backdrop;
+
+    private double blurAmount;
+
+    /// <summary>
+    /// Gets or sets the blur amount for the effect (must be a positive value)
+    /// </summary>
+    /// <remarks>This property is ignored when the active mode is <see cref="AcrylicBackgroundSource.HostBackdrop"/></remarks>
+    public double BlurAmount
+    {
+        get => this.blurAmount;
+        set => this.blurAmount = Math.Max(value, 0);
+    }
+
+    /// <summary>
+    /// Gets or sets the tint for the effect
+    /// </summary>
+    public Color TintColor { get; set; }
+
+    private double tintOpacity = 0.5f;
+
+    /// <summary>
+    /// Gets or sets the color for the tint effect (default is 0.5, must be in the [0, 1] range)
+    /// </summary>
+    public double TintOpacity
+    {
+        get => this.tintOpacity;
+        set => this.tintOpacity = Math.Clamp(value, 0, 1);
+    }
+
+    /// <summary>
+    /// Gets or sets the <see cref="Uri"/> to the texture to use
+    /// </summary>
+    public Uri? TextureUri { get; set; }
+
+    /// <inheritdoc/>
+    protected override object ProvideValue()
+    {
+        return BackgroundSource switch
+        {
+            AcrylicBackgroundSource.Backdrop => PipelineBuilder.FromBackdropAcrylic(this.TintColor, (float)this.TintOpacity, (float)BlurAmount, TextureUri),
+            AcrylicBackgroundSource.HostBackdrop => PipelineBuilder.FromHostBackdropAcrylic(this.TintColor, (float)this.TintOpacity, TextureUri),
+            _ => throw new ArgumentException($"Invalid source mode for acrylic effect: {BackgroundSource}")
+        };
+    }
+}
+
+#endif
diff --git a/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs b/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs
new file mode 100644
index 00000000..6ed06966
--- /dev/null
+++ b/components/Media/src/Effects/Extensions/BackdropSourceExtension.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if WINDOWS_UWP
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A backdrop effect that can sample from a specified source
+/// </summary>
+[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))]
+public sealed class BackdropSourceExtension : MarkupExtension
+{
+    /// <summary>
+    /// Gets or sets the background source mode for the effect (the default is <see cref="AcrylicBackgroundSource.Backdrop"/>).
+    /// </summary>
+    public AcrylicBackgroundSource BackgroundSource { get; set; } = AcrylicBackgroundSource.Backdrop;
+
+    /// <inheritdoc/>
+    protected override object ProvideValue()
+    {
+        return BackgroundSource switch
+        {
+            AcrylicBackgroundSource.Backdrop => PipelineBuilder.FromBackdrop(),
+            AcrylicBackgroundSource.HostBackdrop => PipelineBuilder.FromHostBackdrop(),
+            _ => throw new ArgumentException($"Invalid source for backdrop effect: {BackgroundSource}")
+        };
+    }
+}
+#endif
diff --git a/components/Media/src/Effects/Extensions/ImageSourceExtension.cs b/components/Media/src/Effects/Extensions/ImageSourceExtension.cs
new file mode 100644
index 00000000..8c04b7a9
--- /dev/null
+++ b/components/Media/src/Effects/Extensions/ImageSourceExtension.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An image effect, which displays an image loaded as a Win2D surface
+/// </summary>
+public sealed class ImageSourceExtension : ImageSourceBaseExtension
+{
+    /// <inheritdoc/>
+    protected override object ProvideValue()
+    {
+        default(ArgumentNullException).ThrowIfNull(Uri);
+        return PipelineBuilder.FromImage(Uri, DpiMode, CacheMode);
+    }
+}
diff --git a/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs b/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs
new file mode 100644
index 00000000..1f713f44
--- /dev/null
+++ b/components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An effect that renders a standard 8bit SDR color on the available surface
+/// </summary>
+[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))]
+public sealed class SolidColorSourceExtension : MarkupExtension
+{
+    /// <summary>
+    /// Gets or sets the color to display
+    /// </summary>
+    public Color Color { get; set; }
+
+    /// <inheritdoc/>
+    protected override object ProvideValue()
+    {
+        return PipelineBuilder.FromColor(Color);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/Extensions/TileSourceExtension.cs b/components/Media/src/Effects/Extensions/TileSourceExtension.cs
new file mode 100644
index 00000000..a68956ee
--- /dev/null
+++ b/components/Media/src/Effects/Extensions/TileSourceExtension.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An effect that loads an image and replicates it to cover all the available surface area
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.BorderEffect"/> effect</remarks>
+public sealed class TileSourceExtension : ImageSourceBaseExtension
+{
+    /// <inheritdoc/>
+    protected override object ProvideValue()
+    {
+        default(ArgumentNullException).ThrowIfNull(Uri);
+        return PipelineBuilder.FromTiles(Uri, DpiMode, CacheMode);
+    }
+}
diff --git a/components/Media/src/Effects/GrayscaleEffect.cs b/components/Media/src/Effects/GrayscaleEffect.cs
new file mode 100644
index 00000000..c9f5a943
--- /dev/null
+++ b/components/Media/src/Effects/GrayscaleEffect.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A grayscale effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.GrayscaleEffect"/> effect</remarks>
+public sealed class GrayscaleEffect : PipelineEffect
+{
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        return builder.Grayscale();
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/HueRotationEffect.cs b/components/Media/src/Effects/HueRotationEffect.cs
new file mode 100644
index 00000000..2b4d80b3
--- /dev/null
+++ b/components/Media/src/Effects/HueRotationEffect.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A hue rotation effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.HueRotationEffect"/> effect</remarks>
+public sealed class HueRotationEffect : PipelineEffect
+{
+    /// <summary>
+    /// Gets or sets the angle to rotate the hue, in radians
+    /// </summary>
+    public double Angle { get; set; }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.HueRotation((float)Angle, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.HueRotation((float)Angle);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/Interfaces/IPipelineEffect.cs b/components/Media/src/Effects/Interfaces/IPipelineEffect.cs
new file mode 100644
index 00000000..e93a5a00
--- /dev/null
+++ b/components/Media/src/Effects/Interfaces/IPipelineEffect.cs
@@ -0,0 +1,39 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// The base <see langword="interface"/> for all the builder effects to be used in a <see cref="CompositionBrush"/>.
+/// </summary>
+public interface IPipelineEffect
+{
+    /// <summary>
+    /// Gets the current <see cref="CompositionBrush"/> instance, if one is in use.
+    /// </summary>
+    CompositionBrush? Brush { get; }
+
+    /// <summary>
+    /// Appends the current effect to the input <see cref="PipelineBuilder"/> instance.
+    /// </summary>
+    /// <param name="builder">The source <see cref="PipelineBuilder"/> instance to add the effect to.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> with the new effects added to it.</returns>
+    PipelineBuilder AppendToBuilder(PipelineBuilder builder);
+
+    /// <summary>
+    /// Notifies that a given <see cref="CompositionBrush"/> is now in use.
+    /// </summary>
+    /// <param name="brush">The <see cref="CompositionBrush"/> in use.</param>
+    void NotifyCompositionBrushInUse(CompositionBrush brush);
+}
diff --git a/components/Media/src/Effects/InvertEffect.cs b/components/Media/src/Effects/InvertEffect.cs
new file mode 100644
index 00000000..9ec5437b
--- /dev/null
+++ b/components/Media/src/Effects/InvertEffect.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An color inversion effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.InvertEffect"/> effect</remarks>
+public sealed class InvertEffect : PipelineEffect
+{
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        return builder.Invert();
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/LuminanceToAlphaEffect.cs b/components/Media/src/Effects/LuminanceToAlphaEffect.cs
new file mode 100644
index 00000000..789f9829
--- /dev/null
+++ b/components/Media/src/Effects/LuminanceToAlphaEffect.cs
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A luminance to alpha effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.LuminanceToAlphaEffect"/> effect</remarks>
+public sealed class LuminanceToAlphaEffect : PipelineEffect
+{
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        return builder.LuminanceToAlpha();
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/OpacityEffect.cs b/components/Media/src/Effects/OpacityEffect.cs
new file mode 100644
index 00000000..e4696415
--- /dev/null
+++ b/components/Media/src/Effects/OpacityEffect.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An opacity effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.OpacityEffect"/> effect</remarks>
+public sealed class OpacityEffect : PipelineEffect
+{
+    private double value = 1;
+
+    /// <summary>
+    /// Gets or sets the opacity value to apply to the background (defaults to 1, should be in the [0, 1] range).
+    /// </summary>
+    public double Value
+    {
+        get => this.value;
+        set => this.value = Math.Clamp(value, 0, 1);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Opacity((float)Value, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Opacity((float)Value);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/SaturationEffect.cs b/components/Media/src/Effects/SaturationEffect.cs
new file mode 100644
index 00000000..83ac5d18
--- /dev/null
+++ b/components/Media/src/Effects/SaturationEffect.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A saturation effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.SaturationEffect"/> effect</remarks>
+public sealed class SaturationEffect : PipelineEffect
+{
+    private double value = 1;
+
+    /// <summary>
+    /// Gets or sets the saturation amount to apply to the background (defaults to 1, should be in the [0, 1] range).
+    /// </summary>
+    public double Value
+    {
+        get => this.value;
+        set => this.value = Math.Clamp(value, 0, 1);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Saturation((float)Value, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Saturation((float)Value);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/SepiaEffect.cs b/components/Media/src/Effects/SepiaEffect.cs
new file mode 100644
index 00000000..e393ea67
--- /dev/null
+++ b/components/Media/src/Effects/SepiaEffect.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A sepia effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.SepiaEffect"/> effect</remarks>
+public sealed class SepiaEffect : PipelineEffect
+{
+    private double intensity = 0.5;
+
+    /// <summary>
+    /// Gets or sets the intensity of the effect (defaults to 0.5, should be in the [0, 1] range).
+    /// </summary>
+    public double Intensity
+    {
+        get => this.intensity;
+        set => this.intensity = Math.Clamp(value, 0, 1);
+    }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Sepia((float)Intensity, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Sepia((float)Intensity);
+    }
+}
diff --git a/components/Media/src/Effects/ShadeEffect.cs b/components/Media/src/Effects/ShadeEffect.cs
new file mode 100644
index 00000000..e56169b6
--- /dev/null
+++ b/components/Media/src/Effects/ShadeEffect.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An effect that overlays a color layer over the current builder, with a specified intensity
+/// </summary>
+public sealed class ShadeEffect : PipelineEffect
+{
+    /// <summary>
+    /// Gets or sets the color to use
+    /// </summary>
+    public Color Color { get; set; }
+
+    private double intensity = 0.5;
+
+    /// <summary>
+    /// Gets or sets the intensity of the color layer (default to 0.5, should be in the [0, 1] range)
+    /// </summary>
+    public double Intensity
+    {
+        get => this.intensity;
+        set => this.intensity = Math.Clamp(value, 0, 1);
+    }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        return builder.Shade(Color, (float)Intensity);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/TemperatureAndTintEffect.cs b/components/Media/src/Effects/TemperatureAndTintEffect.cs
new file mode 100644
index 00000000..385fb237
--- /dev/null
+++ b/components/Media/src/Effects/TemperatureAndTintEffect.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A temperature and tint effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.TemperatureAndTintEffect"/> effect</remarks>
+public sealed class TemperatureAndTintEffect : PipelineEffect
+{
+    private double temperature;
+
+    /// <summary>
+    /// Gets or sets the value of the temperature for the current effect (defaults to 0, should be in the [-1, 1] range)
+    /// </summary>
+    public double Temperature
+    {
+        get => this.temperature;
+        set => this.temperature = Math.Clamp(value, -1, 1);
+    }
+
+    private double tint;
+
+    /// <summary>
+    /// Gets or sets the value of the tint for the current effect (defaults to 0, should be in the [-1, 1] range)
+    /// </summary>
+    public double Tint
+    {
+        get => this.tint;
+        set => this.tint = Math.Clamp(value, -1, 1);
+    }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        return builder.TemperatureAndTint((float)Temperature, (float)Tint);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Effects/TintEffect.cs b/components/Media/src/Effects/TintEffect.cs
new file mode 100644
index 00000000..e986481e
--- /dev/null
+++ b/components/Media/src/Effects/TintEffect.cs
@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+using Windows.UI;
+
+#nullable enable
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A tint effect
+/// </summary>
+/// <remarks>This effect maps to the Win2D <see cref="Graphics.Canvas.Effects.TintEffect"/> effect</remarks>
+public sealed class TintEffect : PipelineEffect
+{
+    /// <summary>
+    /// Gets or sets the int color to use
+    /// </summary>
+    public Color Color { get; set; }
+
+    /// <summary>
+    /// Gets the unique id for the effect, if <see cref="PipelineEffect.IsAnimatable"/> is set.
+    /// </summary>
+    internal string? Id { get; private set; }
+
+    /// <inheritdoc/>
+    public override PipelineBuilder AppendToBuilder(PipelineBuilder builder)
+    {
+        if (IsAnimatable)
+        {
+            builder = builder.Tint(Color, out string id);
+
+            Id = id;
+
+            return builder;
+        }
+
+        return builder.Tint(Color);
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Enums/AlphaMode.cs b/components/Media/src/Enums/AlphaMode.cs
new file mode 100644
index 00000000..1cfa2b31
--- /dev/null
+++ b/components/Media/src/Enums/AlphaMode.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Specifies the way in which an alpha channel affects color channels.
+/// </summary>
+public enum AlphaMode
+{
+    /// <summary>
+    /// Provides better transparent effects without a white bloom.
+    /// </summary>
+    Premultiplied = 0,
+
+    /// <summary>
+    /// WPF default handling of alpha channel during transparent blending.
+    /// </summary>
+    Straight = 1,
+}
\ No newline at end of file
diff --git a/components/Media/src/Enums/CacheMode.cs b/components/Media/src/Enums/CacheMode.cs
new file mode 100644
index 00000000..57153b0b
--- /dev/null
+++ b/components/Media/src/Enums/CacheMode.cs
@@ -0,0 +1,26 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Indicates the cache mode to use when loading a Win2D image
+/// </summary>
+public enum CacheMode
+{
+    /// <summary>
+    /// The default behavior, the cache is enabled
+    /// </summary>
+    Default,
+
+    /// <summary>
+    /// Reload the target image and overwrite the cached entry, if it exists
+    /// </summary>
+    Overwrite,
+
+    /// <summary>
+    /// The cache is disabled and new images are always reloaded
+    /// </summary>
+    Disabled
+}
\ No newline at end of file
diff --git a/components/Media/src/Enums/DpiMode.cs b/components/Media/src/Enums/DpiMode.cs
new file mode 100644
index 00000000..4167eac9
--- /dev/null
+++ b/components/Media/src/Enums/DpiMode.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Indicates the DPI mode to use to load an image
+/// </summary>
+public enum DpiMode
+{
+    /// <summary>
+    /// Uses the original DPI settings of the loaded image
+    /// </summary>
+    UseSourceDpi,
+
+    /// <summary>
+    /// Uses the default value of 96 DPI
+    /// </summary>
+    Default96Dpi,
+
+    /// <summary>
+    /// Overrides the image DPI settings with the current screen DPI value
+    /// </summary>
+    DisplayDpi,
+
+    /// <summary>
+    /// Overrides the image DPI settings with the current screen DPI value and ensures the resulting value is at least 96
+    /// </summary>
+    DisplayDpiWith96AsLowerBound
+}
\ No newline at end of file
diff --git a/components/Media/src/Enums/ImageBlendMode.cs b/components/Media/src/Enums/ImageBlendMode.cs
new file mode 100644
index 00000000..28d997f5
--- /dev/null
+++ b/components/Media/src/Enums/ImageBlendMode.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//// Composition supported version of http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm.
+
+using Microsoft.Graphics.Canvas.Effects;
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - see http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm.
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Blend mode to use when compositing effects.
+/// See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffectMode.htm for details.
+/// Dissolve is not supported.
+/// </summary>
+public enum ImageBlendMode
+{
+    Multiply = BlendEffectMode.Multiply,
+    Screen = BlendEffectMode.Screen,
+    Darken = BlendEffectMode.Darken,
+    Lighten = BlendEffectMode.Lighten,
+    ColorBurn = BlendEffectMode.ColorBurn,
+    LinearBurn = BlendEffectMode.LinearBurn,
+    DarkerColor = BlendEffectMode.DarkerColor,
+    LighterColor = BlendEffectMode.LighterColor,
+    ColorDodge = BlendEffectMode.ColorDodge,
+    LinearDodge = BlendEffectMode.LinearDodge,
+    Overlay = BlendEffectMode.Overlay,
+    SoftLight = BlendEffectMode.SoftLight,
+    HardLight = BlendEffectMode.HardLight,
+    VividLight = BlendEffectMode.VividLight,
+    LinearLight = BlendEffectMode.LinearLight,
+    PinLight = BlendEffectMode.PinLight,
+    HardMix = BlendEffectMode.HardMix,
+    Difference = BlendEffectMode.Difference,
+    Exclusion = BlendEffectMode.Exclusion,
+
+    /// <summary>
+    /// Hue blend mode.
+    /// </summary>
+    Hue = BlendEffectMode.Hue,
+
+    /// <summary>
+    /// Saturation blend mode.
+    /// </summary>
+    Saturation = BlendEffectMode.Saturation,
+
+    /// <summary>
+    /// Color blend mode.
+    /// </summary>
+    Color = BlendEffectMode.Color,
+
+    /// <summary>
+    /// Luminosity blend mode.
+    /// </summary>
+    Luminosity = BlendEffectMode.Luminosity,
+    Subtract = BlendEffectMode.Subtract,
+    Division = BlendEffectMode.Division,
+}
\ No newline at end of file
diff --git a/components/Media/src/Enums/InnerContentClipMode.cs b/components/Media/src/Enums/InnerContentClipMode.cs
new file mode 100644
index 00000000..a154ebb6
--- /dev/null
+++ b/components/Media/src/Enums/InnerContentClipMode.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// The method that each instance of <see cref="AttachedCardShadow"/> uses when clipping its inner content.
+/// </summary>
+public enum InnerContentClipMode
+{
+    /// <summary>
+    /// Do not clip inner content.
+    /// </summary>
+    None,
+
+    /// <summary>
+    /// Use <see cref="Windows.UI.Composition.CompositionMaskBrush"/> to clip inner content.
+    /// </summary>
+    /// <remarks>
+    /// This mode has better performance than <see cref="CompositionGeometricClip"/>.
+    /// </remarks>
+    CompositionMaskBrush,
+
+    /// <summary>
+    /// Use <see cref="Windows.UI.Composition.CompositionGeometricClip"/> to clip inner content.
+    /// </summary>
+    /// <remarks>
+    /// Content clipped in this mode will have smoother corners than when using <see cref="CompositionMaskBrush"/>.
+    /// </remarks>
+    CompositionGeometricClip
+}
diff --git a/components/Media/src/Enums/Placement.cs b/components/Media/src/Enums/Placement.cs
new file mode 100644
index 00000000..b2c6854c
--- /dev/null
+++ b/components/Media/src/Enums/Placement.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Graphics.Effects;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An <see langword="enum"/> used to modify the default placement of the input <see cref="IGraphicsEffectSource"/> instance in a blend operation
+/// </summary>
+public enum Placement
+{
+    /// <summary>
+    /// The instance used to call the blend method is placed on top of the other
+    /// </summary>
+    Foreground,
+
+    /// <summary>
+    /// The instance used to call the blend method is placed behind the other
+    /// </summary>
+    Background
+}
\ No newline at end of file
diff --git a/components/Media/src/Extensions/System.Collections.Generic/GenericExtensions.cs b/components/Media/src/Extensions/System.Collections.Generic/GenericExtensions.cs
new file mode 100644
index 00000000..ce09a05f
--- /dev/null
+++ b/components/Media/src/Extensions/System.Collections.Generic/GenericExtensions.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An extension <see langword="class"/> for the <see cref="System.Collections.Generic"/> <see langword="namespace"/>
+/// </summary>
+internal static class GenericExtensions
+{
+    /// <summary>
+    /// Merges the two input <see cref="IReadOnlyDictionary{TKey,TValue}"/> instances and makes sure no duplicate keys are present
+    /// </summary>
+    /// <typeparam name="TKey">The type of keys in the input dictionaries</typeparam>
+    /// <typeparam name="TValue">The type of values in the input dictionaries</typeparam>
+    /// <param name="a">The first <see cref="IReadOnlyDictionary{TKey,TValue}"/> to merge</param>
+    /// <param name="b">The second <see cref="IReadOnlyDictionary{TKey,TValue}"/> to merge</param>
+    /// <returns>An <see cref="IReadOnlyDictionary{TKey,TValue}"/> instance with elements from both <paramref name="a"/> and <paramref name="b"/></returns>
+    [Pure]
+    public static IReadOnlyDictionary<TKey, TValue> Merge<TKey, TValue>(
+        this IReadOnlyDictionary<TKey, TValue> a,
+        IReadOnlyDictionary<TKey, TValue> b)
+        where TKey : notnull
+    {
+        if (a.Keys.FirstOrDefault(b.ContainsKey) is TKey key)
+        {
+            throw new InvalidOperationException($"The key {key} already exists in the current pipeline");
+        }
+
+        return new Dictionary<TKey, TValue>(a.Concat(b));
+    }
+
+    /// <summary>
+    /// Merges the two input <see cref="IReadOnlyCollection{T}"/> instances and makes sure no duplicate items are present
+    /// </summary>
+    /// <typeparam name="T">The type of elements in the input collections</typeparam>
+    /// <param name="a">The first <see cref="IReadOnlyCollection{T}"/> to merge</param>
+    /// <param name="b">The second <see cref="IReadOnlyCollection{T}"/> to merge</param>
+    /// <returns>An <see cref="IReadOnlyCollection{T}"/> instance with elements from both <paramref name="a"/> and <paramref name="b"/></returns>
+    [Pure]
+    public static IReadOnlyCollection<T> Merge<T>(this IReadOnlyCollection<T> a, IReadOnlyCollection<T> b)
+    {
+        if (a.Any(b.Contains))
+        {
+            throw new InvalidOperationException("The input collection has at least an item already present in the second collection");
+        }
+
+        return a.Concat(b).ToArray();
+    }
+}
diff --git a/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs b/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs
new file mode 100644
index 00000000..d30e29ac
--- /dev/null
+++ b/components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs
@@ -0,0 +1,58 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An <see langword="async"/> <see cref="AsyncMutex"/> implementation that can be easily used inside a <see langword="using"/> block
+/// </summary>
+#pragma warning disable CA1001 // Types that own disposable fields should be disposable
+internal sealed class AsyncMutex
+#pragma warning restore CA1001 // Types that own disposable fields should be disposable
+{
+    /// <summary>
+    /// The underlying <see cref="SemaphoreSlim"/> instance in use
+    /// </summary>
+    private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1);
+
+    /// <summary>
+    /// Acquires a lock for the current instance, that is automatically released outside the <see langword="using"/> block
+    /// </summary>
+    /// <returns>A <see cref="Task{T}"/> that returns an <see cref="IDisposable"/> instance to release the lock</returns>
+    public async Task<IDisposable> LockAsync()
+    {
+        await this.semaphore.WaitAsync().ConfigureAwait(false);
+
+        return new Lock(this.semaphore);
+    }
+
+    /// <summary>
+    /// Private class that implements the automatic release of the semaphore
+    /// </summary>
+    private sealed class Lock : IDisposable
+    {
+        /// <summary>
+        /// The <see cref="SemaphoreSlim"/> instance of the parent class
+        /// </summary>
+        private readonly SemaphoreSlim semaphore;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Lock"/> class.
+        /// </summary>
+        /// <param name="semaphore">The <see cref="SemaphoreSlim"/> instance of the parent class</param>
+        public Lock(SemaphoreSlim semaphore)
+        {
+            this.semaphore = semaphore;
+        }
+
+        /// <inheritdoc/>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        void IDisposable.Dispose()
+        {
+            this.semaphore.Release();
+        }
+    }
+}
diff --git a/components/Media/src/Extensions/System/UriExtensions.cs b/components/Media/src/Extensions/System/UriExtensions.cs
new file mode 100644
index 00000000..71c20e80
--- /dev/null
+++ b/components/Media/src/Extensions/System/UriExtensions.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An extension <see langword="class"/> for the <see cref="Uri"/> type
+/// </summary>
+internal static class UriExtensions
+{
+    /// <summary>
+    /// Returns an <see cref="Uri"/> that starts with the ms-appx:// prefix
+    /// </summary>
+    /// <param name="uri">The input <see cref="Uri"/> to process</param>
+    /// <returns>A <see cref="Uri"/> equivalent to the first but relative to ms-appx://</returns>
+    /// <remarks>This is needed because the XAML converter doesn't use the ms-appx:// prefix</remarks>
+    [Pure]
+    public static Uri ToAppxUri(this Uri uri)
+    {
+        if (uri.Scheme.Equals("ms-resource"))
+        {
+            string path = uri.AbsolutePath.StartsWith("/Files")
+                ? uri.AbsolutePath.Replace("/Files", string.Empty)
+                : uri.AbsolutePath;
+
+            return new Uri($"ms-appx://{path}");
+        }
+
+        return uri;
+    }
+
+    /// <summary>
+    /// Returns an <see cref="Uri"/> that starts with the ms-appx:// prefix
+    /// </summary>
+    /// <param name="path">The input relative path to convert</param>
+    /// <returns>A <see cref="Uri"/> with <paramref name="path"/> relative to ms-appx://</returns>
+    [Pure]
+    public static Uri ToAppxUri(this string path)
+    {
+        string prefix = $"ms-appx://{(path.StartsWith('/') ? string.Empty : "/")}";
+
+        return new Uri($"{prefix}{path}");
+    }
+}
\ No newline at end of file
diff --git a/components/Media/src/Extensions/UIElementExtensions.cs b/components/Media/src/Extensions/UIElementExtensions.cs
new file mode 100644
index 00000000..569fa16f
--- /dev/null
+++ b/components/Media/src/Extensions/UIElementExtensions.cs
@@ -0,0 +1,65 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+#elif WINUI2
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// Attached properties to support attaching custom pipelines to UI elements.
+/// </summary>
+public static class UIElementExtensions
+{
+    /// <summary>
+    /// Identifies the VisualFactory XAML attached property.
+    /// </summary>
+    public static readonly DependencyProperty VisualFactoryProperty = DependencyProperty.RegisterAttached(
+        "VisualFactory",
+        typeof(AttachedVisualFactoryBase),
+        typeof(UIElementExtensions),
+        new PropertyMetadata(null, OnVisualFactoryPropertyChanged));
+
+    /// <summary>
+    /// Gets the value of <see cref="VisualFactoryProperty"/>.
+    /// </summary>
+    /// <param name="element">The <see cref="UIElement"/> to get the value for.</param>
+    /// <returns>The retrieved <see cref="AttachedVisualFactoryBase"/> item.</returns>
+    public static AttachedVisualFactoryBase GetVisualFactory(UIElement element)
+    {
+        return (AttachedVisualFactoryBase)element.GetValue(VisualFactoryProperty);
+    }
+
+    /// <summary>
+    /// Sets the value of <see cref="VisualFactoryProperty"/>.
+    /// </summary>
+    /// <param name="element">The <see cref="UIElement"/> to set the value for.</param>
+    /// <param name="value">The <see cref="AttachedVisualFactoryBase"/> value to set.</param>
+    public static void SetVisualFactory(UIElement element, AttachedVisualFactoryBase value)
+    {
+        element.SetValue(VisualFactoryProperty, value);
+    }
+
+    /// <summary>
+    /// Callback to apply the visual for <see cref="VisualFactoryProperty"/>.
+    /// </summary>
+    /// <param name="d">The target object the property was changed for.</param>
+    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for the current event.</param>
+    private static async void OnVisualFactoryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+    {
+        UIElement element = (UIElement)d;
+        Visual attachedVisual = await ((AttachedVisualFactoryBase)e.NewValue).GetAttachedVisualAsync(element);
+
+        attachedVisual.RelativeSizeAdjustment = Vector2.One;
+
+        ElementCompositionPreview.SetElementChildVisual(element, attachedVisual);
+    }
+}
diff --git a/components/Media/src/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs b/components/Media/src/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs
new file mode 100644
index 00000000..195b0ce3
--- /dev/null
+++ b/components/Media/src/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs
@@ -0,0 +1,91 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using Windows.UI;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+#elif WINUI2
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// An extension <see langword="class"/> for the <see cref="CompositionObject"/> type
+/// </summary>
+internal static class CompositionObjectExtensions
+{
+    /// <summary>
+    /// Starts an <see cref="ExpressionAnimation"/> to keep the size of the source <see cref="Visual"/> in sync with the target <see cref="UIElement"/>
+    /// </summary>
+    /// <param name="source">The <see cref="Visual"/> to start the animation on</param>
+    /// <param name="target">The target <see cref="UIElement"/> to read the size updates from</param>
+    public static void BindSize(this Visual source, UIElement target)
+    {
+        var visual = ElementCompositionPreview.GetElementVisual(target);
+        var bindSizeAnimation = source.Compositor.CreateExpressionAnimation($"{nameof(visual)}.Size");
+
+        bindSizeAnimation.SetReferenceParameter(nameof(visual), visual);
+
+        // Start the animation
+        source.StartAnimation("Size", bindSizeAnimation);
+    }
+
+    /// <summary>
+    /// Starts an animation on the given property of a <see cref="CompositionObject"/>
+    /// </summary>
+    /// <typeparam name="T">The type of the property to animate</typeparam>
+    /// <param name="target">The target <see cref="CompositionObject"/></param>
+    /// <param name="property">The name of the property to animate</param>
+    /// <param name="value">The final value of the property</param>
+    /// <param name="duration">The animation duration</param>
+    /// <returns>A <see cref="Task"/> that completes when the created animation completes</returns>
+    public static Task StartAnimationAsync<T>(this CompositionObject target, string property, T value, TimeSpan duration)
+        where T : unmanaged
+    {
+        // Stop previous animations
+        target.StopAnimation(property);
+
+        // Setup the animation to run
+        KeyFrameAnimation animation;
+        switch (value)
+        {
+            case float f:
+                var scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation();
+                scalarAnimation.InsertKeyFrame(1f, f);
+                animation = scalarAnimation;
+                break;
+            case Color c:
+                var colorAnimation = target.Compositor.CreateColorKeyFrameAnimation();
+                colorAnimation.InsertKeyFrame(1f, c);
+                animation = colorAnimation;
+                break;
+            case Vector4 v4:
+                var vector4Animation = target.Compositor.CreateVector4KeyFrameAnimation();
+                vector4Animation.InsertKeyFrame(1f, v4);
+                animation = vector4Animation;
+                break;
+            default: throw new ArgumentException($"Invalid animation type: {typeof(T)}", nameof(value));
+        }
+
+        animation.Duration = duration;
+
+        // Get the batch and start the animations
+        var batch = target.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation);
+
+        var tcs = new TaskCompletionSource<object?>();
+
+        batch.Completed += (s, e) => tcs.SetResult(null);
+
+        target.StartAnimation(property, animation);
+
+        batch.End();
+
+        return tcs.Task;
+    }
+}
diff --git a/components/Media/src/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs b/components/Media/src/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs
new file mode 100644
index 00000000..7846b220
--- /dev/null
+++ b/components/Media/src/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Helpers.Cache;
+
+/// <summary>
+/// A <see langword="class"/> used to cache reusable <see cref="CompositionObject"/> instances with an associated key
+/// </summary>
+/// <typeparam name="TKey">The type of key to classify the items in the cache</typeparam>
+/// <typeparam name="TValue">The type of items stored in the cache</typeparam>
+internal sealed class CompositionObjectCache<TKey, TValue>
+    where TValue : CompositionObject
+    where TKey : notnull
+{
+    /// <summary>
+    /// The cache of weak references of type <typeparamref name="TValue"/> to <typeparamref name="TKey"/> instances, to avoid memory leaks
+    /// </summary>
+    private readonly ConditionalWeakTable<Compositor, Dictionary<TKey, WeakReference<TValue>>> cache = new ConditionalWeakTable<Compositor, Dictionary<TKey, WeakReference<TValue>>>();
+
+    /// <summary>
+    /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found
+    /// </summary>
+    /// <param name="compositor">The current <see cref="Compositor"/> instance to get the value for</param>
+    /// <param name="key">The key to look for</param>
+    /// <param name="result">The resulting value, if existing</param>
+    /// <returns><see langword="true"/> if the target value has been found, <see langword="false"/> otherwise</returns>
+    public bool TryGetValue(Compositor compositor, TKey key, out TValue? result)
+    {
+        lock (this.cache)
+        {
+            if (this.cache.TryGetValue(compositor, out var map) &&
+                map.TryGetValue(key, out var reference) &&
+                reference.TryGetTarget(out result))
+            {
+                return true;
+            }
+
+            result = null;
+            return false;
+        }
+    }
+
+    /// <summary>
+    /// Adds or updates a value with the specified key to the cache
+    /// </summary>
+    /// <param name="compositor">The current <see cref="Compositor"/> instance to get the value for</param>
+    /// <param name="key">The key of the item to add</param>
+    /// <param name="value">The value to add</param>
+    public void AddOrUpdate(Compositor compositor, TKey key, TValue value)
+    {
+        lock (this.cache)
+        {
+            if (this.cache.TryGetValue(compositor, out var map))
+            {
+                _ = map.Remove(key);
+
+                map.Add(key, new WeakReference<TValue>(value));
+            }
+            else
+            {
+                map = new Dictionary<TKey, WeakReference<TValue>> { [key] = new WeakReference<TValue>(value) };
+
+                this.cache.Add(compositor, map);
+            }
+        }
+    }
+}
diff --git a/components/Media/src/Helpers/Cache/CompositionObjectCache{T}.cs b/components/Media/src/Helpers/Cache/CompositionObjectCache{T}.cs
new file mode 100644
index 00000000..6465b941
--- /dev/null
+++ b/components/Media/src/Helpers/Cache/CompositionObjectCache{T}.cs
@@ -0,0 +1,50 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+#if WINUI2
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Helpers.Cache;
+
+/// <summary>
+/// A <see langword="class"/> used to cache reusable <see cref="CompositionObject"/> instances in each UI thread
+/// </summary>
+/// <typeparam name="T">The type of instances to cache</typeparam>
+internal sealed class CompositionObjectCache<T>
+    where T : CompositionObject
+{
+    /// <summary>
+    /// The cache of weak references of type <typeparamref name="T"/>, to avoid memory leaks
+    /// </summary>
+    private readonly ConditionalWeakTable<Compositor, WeakReference<T>> cache = new ConditionalWeakTable<Compositor, WeakReference<T>>();
+
+    /// <summary>
+    /// Tries to retrieve a valid <typeparamref name="T"/> instance from the cache, and uses the provided factory if an existing item is not found
+    /// </summary>
+    /// <param name="compositor">The current <see cref="Compositor"/> instance to get the value for</param>
+    /// <param name="producer">A <see cref="Func{TResult}"/> instance used to produce a <typeparamref name="T"/> instance</param>
+    /// <returns>A <typeparamref name="T"/> instance that is linked to <paramref name="compositor"/></returns>
+    public T GetValue(Compositor compositor, Func<Compositor, T> producer)
+    {
+        lock (cache)
+        {
+            if (this.cache.TryGetValue(compositor, out var reference) &&
+                reference.TryGetTarget(out var instance))
+            {
+                return instance;
+            }
+
+            // Create a new instance when needed
+            var fallback = producer(compositor);
+            this.cache.AddOrUpdate(compositor, new WeakReference<T>(fallback));
+
+            return fallback;
+        }
+    }
+}
diff --git a/components/Media/src/Helpers/SurfaceLoader.Instance.cs b/components/Media/src/Helpers/SurfaceLoader.Instance.cs
new file mode 100644
index 00000000..013e4aad
--- /dev/null
+++ b/components/Media/src/Helpers/SurfaceLoader.Instance.cs
@@ -0,0 +1,229 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.Text;
+using Microsoft.Graphics.Canvas.UI.Composition;
+using Windows.UI;
+
+#if WINUI2
+using Windows.Graphics.DirectX;
+using Windows.UI.Composition;
+#elif WINUI3
+using Microsoft.Graphics.DirectX;
+using Microsoft.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Helpers;
+
+/// <summary>
+/// A delegate for load time effects.
+/// </summary>
+/// <param name="bitmap">The bitmap.</param>
+/// <param name="device">The device.</param>
+/// <param name="sizeTarget">The size target.</param>
+/// <returns>A CompositeDrawingSurface</returns>
+public delegate CompositionDrawingSurface LoadTimeEffectHandler(CanvasBitmap bitmap, CompositionGraphicsDevice device, Size sizeTarget);
+
+/// <summary>
+/// A <see langword="class"/> that can load and draw images and other objects to Win2D surfaces and brushes
+/// </summary>
+public sealed partial class SurfaceLoader : IDisposable
+{
+    /// <summary>
+    /// The cache of <see cref="SurfaceLoader"/> instances currently available
+    /// </summary>
+    private static readonly ConditionalWeakTable<Compositor, SurfaceLoader> Instances = new ConditionalWeakTable<Compositor, SurfaceLoader>();
+
+    /// <summary>
+    /// Gets a <see cref="SurfaceLoader"/> instance for the <see cref="Compositor"/> of the current window
+    /// </summary>
+    /// <returns>A <see cref="SurfaceLoader"/> instance to use in the current window</returns>
+    public static SurfaceLoader GetInstance()
+    {
+        return GetInstance(Window.Current.Compositor);
+    }
+
+    /// <summary>
+    /// Gets a <see cref="SurfaceLoader"/> instance for a given <see cref="Compositor"/>
+    /// </summary>
+    /// <param name="compositor">The input <see cref="Compositor"/> object to use</param>
+    /// <returns>A <see cref="SurfaceLoader"/> instance associated with <paramref name="compositor"/></returns>
+    public static SurfaceLoader GetInstance(Compositor compositor)
+    {
+        lock (Instances)
+        {
+            if (Instances.TryGetValue(compositor, out var instance))
+            {
+                return instance;
+            }
+
+            instance = new SurfaceLoader(compositor);
+
+            Instances.Add(compositor, instance);
+
+            return instance;
+        }
+    }
+
+    /// <summary>
+    /// The <see cref="Compositor"/> instance in use.
+    /// </summary>
+    private readonly Compositor compositor;
+
+    /// <summary>
+    /// The <see cref="CanvasDevice"/> instance in use.
+    /// </summary>
+    private CanvasDevice? canvasDevice;
+
+    /// <summary>
+    /// The <see cref="CompositionGraphicsDevice"/> instance to determine which GPU is handling the request.
+    /// </summary>
+    private CompositionGraphicsDevice? compositionDevice;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="SurfaceLoader"/> class.
+    /// </summary>
+    /// <param name="compositor">The <see cref="Compositor"/> instance to use</param>
+    private SurfaceLoader(Compositor compositor)
+    {
+        this.compositor = compositor;
+
+        this.InitializeDevices();
+    }
+
+    /// <summary>
+    /// Reloads the <see cref="canvasDevice"/> and <see cref="compositionDevice"/> fields.
+    /// </summary>
+    private void InitializeDevices()
+    {
+        if (!(this.canvasDevice is null))
+        {
+            this.canvasDevice.DeviceLost -= CanvasDevice_DeviceLost;
+        }
+
+        if (!(this.compositionDevice is null))
+        {
+            this.compositionDevice.RenderingDeviceReplaced -= CompositionDevice_RenderingDeviceReplaced;
+        }
+
+        this.canvasDevice = new CanvasDevice();
+        this.compositionDevice = CanvasComposition.CreateCompositionGraphicsDevice(this.compositor, this.canvasDevice);
+
+        this.canvasDevice.DeviceLost += CanvasDevice_DeviceLost;
+        this.compositionDevice.RenderingDeviceReplaced += CompositionDevice_RenderingDeviceReplaced;
+    }
+
+    /// <summary>
+    /// Invokes <see cref="InitializeDevices"/> when the current <see cref="CanvasDevice"/> is lost.
+    /// </summary>
+    private void CanvasDevice_DeviceLost(CanvasDevice sender, object args)
+    {
+        InitializeDevices();
+    }
+
+    /// <summary>
+    /// Invokes <see cref="InitializeDevices"/> when the current <see cref="CompositionGraphicsDevice"/> changes rendering device.
+    /// </summary>
+    private void CompositionDevice_RenderingDeviceReplaced(CompositionGraphicsDevice sender, RenderingDeviceReplacedEventArgs args)
+    {
+        InitializeDevices();
+    }
+
+    /// <summary>
+    /// Loads an image from the URI.
+    /// </summary>
+    /// <param name="uri">The URI.</param>
+    /// <returns><see cref="CompositionDrawingSurface"/></returns>
+    public async Task<CompositionDrawingSurface> LoadFromUri(Uri uri)
+    {
+        return await LoadFromUri(uri, Size.Empty);
+    }
+
+    /// <summary>
+    /// Loads an image from URI with a specified size.
+    /// </summary>
+    /// <param name="uri">The URI.</param>
+    /// <param name="sizeTarget">The size target.</param>
+    /// <returns><see cref="CompositionDrawingSurface"/></returns>
+    public async Task<CompositionDrawingSurface> LoadFromUri(Uri uri, Size sizeTarget)
+    {
+        default(ArgumentNullException).ThrowIfNull(compositionDevice);
+
+        var bitmap = await CanvasBitmap.LoadAsync(canvasDevice, uri);
+        var sizeSource = bitmap.Size;
+
+        if (sizeTarget.IsEmpty)
+        {
+            sizeTarget = sizeSource;
+        }
+
+        var surface = compositionDevice.CreateDrawingSurface(
+            sizeTarget,
+            DirectXPixelFormat.B8G8R8A8UIntNormalized,
+            DirectXAlphaMode.Premultiplied);
+
+        using (var ds = CanvasComposition.CreateDrawingSession(surface))
+        {
+            ds.Clear(Color.FromArgb(0, 0, 0, 0));
+            ds.DrawImage(bitmap, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), new Rect(0, 0, sizeSource.Width, sizeSource.Height));
+        }
+
+        return surface;
+    }
+
+    /// <summary>
+    /// Loads the text on to a <see cref="CompositionDrawingSurface"/>.
+    /// </summary>
+    /// <param name="text">The text.</param>
+    /// <param name="sizeTarget">The size target.</param>
+    /// <param name="textFormat">The text format.</param>
+    /// <param name="textColor">Color of the text.</param>
+    /// <param name="bgColor">Color of the bg.</param>
+    /// <returns><see cref="CompositionDrawingSurface"/></returns>
+    public CompositionDrawingSurface LoadText(string text, Size sizeTarget, CanvasTextFormat textFormat, Color textColor, Color bgColor)
+    {
+        default(ArgumentNullException).ThrowIfNull(compositionDevice);
+
+        var surface = compositionDevice.CreateDrawingSurface(
+            sizeTarget,
+            DirectXPixelFormat.B8G8R8A8UIntNormalized,
+            DirectXAlphaMode.Premultiplied);
+
+        using (var ds = CanvasComposition.CreateDrawingSession(surface))
+        {
+            ds.Clear(bgColor);
+            ds.DrawText(text, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), textColor, textFormat);
+        }
+
+        return surface;
+    }
+
+    /// <summary>
+    /// Loads an image from URI, with a specified size.
+    /// </summary>
+    /// <param name="uri">The URI.</param>
+    /// <param name="sizeTarget">The size target.</param>
+    /// <param name="loadEffectHandler">The load effect handler callback.</param>
+    /// <returns><see cref="CompositionDrawingSurface"/></returns>
+    public async Task<CompositionDrawingSurface> LoadFromUri(Uri uri, Size sizeTarget, LoadTimeEffectHandler loadEffectHandler)
+    {
+        default(ArgumentNullException).ThrowIfNull(compositionDevice);
+
+        if (loadEffectHandler != null)
+        {
+            var bitmap = await CanvasBitmap.LoadAsync(canvasDevice, uri);
+            return loadEffectHandler(bitmap, compositionDevice, sizeTarget);
+        }
+
+        return await LoadFromUri(uri, sizeTarget);
+    }
+
+    public void Dispose()
+    {
+        compositionDevice?.Dispose();
+        canvasDevice?.Dispose();
+    }
+}
diff --git a/components/Media/src/Helpers/SurfaceLoader.cs b/components/Media/src/Helpers/SurfaceLoader.cs
new file mode 100644
index 00000000..ea40b913
--- /dev/null
+++ b/components/Media/src/Helpers/SurfaceLoader.cs
@@ -0,0 +1,150 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Numerics;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.UI.Composition;
+using CommunityToolkit.WinUI.Media.Helpers.Cache;
+using Windows.Graphics.Display;
+using Windows.Graphics.Imaging;
+using Windows.UI;
+
+#if WINUI2
+using Windows.UI.Composition;
+using Windows.Graphics.DirectX;
+#elif WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.Graphics.DirectX;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Helpers;
+
+/// <summary>
+/// A <see langword="class"/> that can load and draw images and other objects to Win2D surfaces and brushes
+/// </summary>
+public sealed partial class SurfaceLoader
+{
+    /// <summary>
+    /// Synchronization mutex to access the cache and load Win2D images concurrently
+    /// </summary>
+    private static readonly AsyncMutex Win2DMutex = new AsyncMutex();
+
+    /// <summary>
+    /// Gets the local cache mapping for previously loaded Win2D images
+    /// </summary>
+    private static readonly CompositionObjectCache<Uri, CompositionBrush> Cache = new CompositionObjectCache<Uri, CompositionBrush>();
+
+    /// <summary>
+    /// Loads a <see cref="CompositionBrush"/> instance with the target image from the shared <see cref="CanvasDevice"/> instance
+    /// </summary>
+    /// <param name="uri">The path to the image to load</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <param name="cacheMode">Indicates the cache option to use to load the image</param>
+    /// <returns>A <see cref="Task{T}"/> that returns the loaded <see cref="CompositionBrush"/> instance</returns>
+    public static async Task<CompositionBrush?> LoadImageAsync(Uri uri, DpiMode dpiMode, CacheMode cacheMode = CacheMode.Default)
+    {
+        var compositor = Window.Current.Compositor;
+
+        // Lock and check the cache first
+        using (await Win2DMutex.LockAsync())
+        {
+            uri = uri.ToAppxUri();
+
+            if (cacheMode == CacheMode.Default &&
+                Cache.TryGetValue(compositor, uri, out var cached))
+            {
+                return cached;
+            }
+
+            // Load the image
+            CompositionBrush? brush;
+            try
+            {
+                // This will throw and the canvas will re-initialize the Win2D device if needed
+                var sharedDevice = CanvasDevice.GetSharedDevice();
+                brush = await LoadSurfaceBrushAsync(sharedDevice, compositor, uri, dpiMode);
+            }
+            catch
+            {
+                // Device error
+                brush = null;
+            }
+
+            // Cache when needed and return the result
+            if (brush != null &&
+                cacheMode != CacheMode.Disabled)
+            {
+                Cache.AddOrUpdate(compositor, uri, brush);
+            }
+
+            return brush;
+        }
+    }
+
+    /// <summary>
+    /// Loads a <see cref="CompositionBrush"/> from the input <see cref="System.Uri"/>, and prepares it to be used in a tile effect
+    /// </summary>
+    /// <param name="canvasDevice">The device to use to process the Win2D image</param>
+    /// <param name="compositor">The compositor instance to use to create the final brush</param>
+    /// <param name="uri">The path to the image to load</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <returns>A <see cref="Task{T}"/> that returns the loaded <see cref="CompositionBrush"/> instance</returns>
+    private static async Task<CompositionBrush> LoadSurfaceBrushAsync(
+        CanvasDevice canvasDevice,
+        Compositor compositor,
+        Uri uri,
+        DpiMode dpiMode)
+    {
+        var displayInformation = DisplayInformation.GetForCurrentView();
+        float dpi = displayInformation.LogicalDpi;
+
+        // Load the bitmap with the appropriate settings
+        using CanvasBitmap bitmap = dpiMode switch
+        {
+            DpiMode.UseSourceDpi => await CanvasBitmap.LoadAsync(canvasDevice, uri),
+            DpiMode.Default96Dpi => await CanvasBitmap.LoadAsync(canvasDevice, uri, 96),
+            DpiMode.DisplayDpi => await CanvasBitmap.LoadAsync(canvasDevice, uri, dpi),
+            DpiMode.DisplayDpiWith96AsLowerBound => await CanvasBitmap.LoadAsync(canvasDevice, uri, dpi >= 96 ? dpi : 96),
+            _ => throw new ArgumentOutOfRangeException(nameof(dpiMode), dpiMode, $"Invalid DPI mode: {dpiMode}")
+        };
+
+        // Calculate the surface size
+        Size
+            size = bitmap.Size,
+            sizeInPixels = new Size(bitmap.SizeInPixels.Width, bitmap.SizeInPixels.Height);
+
+        // Get the device and the target surface
+        using CompositionGraphicsDevice graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, canvasDevice);
+
+        // Create the drawing surface
+        var drawingSurface = graphicsDevice.CreateDrawingSurface(
+            sizeInPixels,
+            DirectXPixelFormat.B8G8R8A8UIntNormalized,
+            DirectXAlphaMode.Premultiplied);
+
+        // Create a drawing session for the target surface
+        using (var drawingSession = CanvasComposition.CreateDrawingSession(drawingSurface, new Rect(0, 0, sizeInPixels.Width, sizeInPixels.Height), dpi))
+        {
+            // Fill the target surface
+            drawingSession.Clear(Color.FromArgb(0, 0, 0, 0));
+            drawingSession.DrawImage(bitmap, new Rect(0, 0, size.Width, size.Height), new Rect(0, 0, size.Width, size.Height));
+            drawingSession.EffectTileSize = new BitmapSize { Width = (uint)size.Width, Height = (uint)size.Height };
+        }
+
+        // Setup the effect brush to use
+        var surfaceBrush = compositor.CreateSurfaceBrush(drawingSurface);
+        surfaceBrush.Stretch = CompositionStretch.None;
+
+        double pixels = displayInformation.RawPixelsPerViewPixel;
+
+        // Adjust the scale if the DPI scaling is greater than 100%
+        if (pixels > 1)
+        {
+            surfaceBrush.Scale = new Vector2((float)(1 / pixels));
+            surfaceBrush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.NearestNeighbor;
+        }
+
+        return surfaceBrush;
+    }
+}
diff --git a/components/Media/src/MultiTarget.props b/components/Media/src/MultiTarget.props
new file mode 100644
index 00000000..67f1c274
--- /dev/null
+++ b/components/Media/src/MultiTarget.props
@@ -0,0 +1,9 @@
+<Project>
+  <PropertyGroup>
+    <!--
+          MultiTarget is a custom property that indicates which target a project is designed to be built for / run on.
+          Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages.
+        -->
+    <MultiTarget>uwp;wasdk;</MultiTarget>
+  </PropertyGroup>
+</Project>
diff --git a/components/Media/src/Pipelines/BrushProvider.cs b/components/Media/src/Pipelines/BrushProvider.cs
new file mode 100644
index 00000000..56572dc2
--- /dev/null
+++ b/components/Media/src/Pipelines/BrushProvider.cs
@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+#elif WINUI2
+using Windows.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A simple container <see langword="class"/> used to store info on a custom composition effect to create
+/// </summary>
+public sealed class BrushProvider
+{
+    /// <summary>
+    /// Gets the name of the target <see cref="CompositionEffectSourceParameter"/>
+    /// </summary>
+    internal string Name { get; }
+
+    /// <summary>
+    /// Gets the stored effect initializer
+    /// </summary>
+    internal Func<ValueTask<CompositionBrush>> Initializer { get; }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="BrushProvider"/> class.
+    /// </summary>
+    /// <param name="name">The name of the target <see cref="CompositionEffectSourceParameter"/></param>
+    /// <param name="initializer">The stored effect initializer</param>
+    private BrushProvider(string name, Func<ValueTask<CompositionBrush>> initializer)
+    {
+        this.Name = name;
+        this.Initializer = initializer;
+    }
+
+    /// <summary>
+    /// Creates a new instance with the info on a given <see cref="CompositionEffectSourceParameter"/> to initialize
+    /// </summary>
+    /// <param name="name">The target effect name</param>
+    /// <param name="brush">A <see cref="CompositionBrush"/> to use to initialize the effect</param>
+    /// <returns>A <see cref="BrushProvider"/> instance with the input initializer</returns>
+    [Pure]
+    public static BrushProvider New(string name, CompositionBrush brush) => new BrushProvider(name, () => new ValueTask<CompositionBrush>(brush));
+
+    /// <summary>
+    /// Creates a new instance with the info on a given <see cref="CompositionEffectSourceParameter"/> to initialize
+    /// </summary>
+    /// <param name="name">The target effect name</param>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will produce the <see cref="CompositionBrush"/> to use to initialize the effect</param>
+    /// <returns>A <see cref="BrushProvider"/> instance with the input initializer</returns>
+    [Pure]
+    public static BrushProvider New(string name, Func<CompositionBrush> factory) => new BrushProvider(name, () => new ValueTask<CompositionBrush>(factory()));
+
+    /// <summary>
+    /// Creates a new instance with the info on a given <see cref="CompositionEffectSourceParameter"/> to initialize
+    /// </summary>
+    /// <param name="name">The target effect name</param>
+    /// <param name="factory">An asynchronous <see cref="Func{TResult}"/> instance that will produce the <see cref="CompositionBrush"/> to use to initialize the effect</param>
+    /// <returns>A <see cref="BrushProvider"/> instance with the input initializer</returns>
+    [Pure]
+    public static BrushProvider New(string name, Func<Task<CompositionBrush>> factory) => new BrushProvider(name, () => new ValueTask<CompositionBrush>(factory()));
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.Effects.Internals.cs b/components/Media/src/Pipelines/PipelineBuilder.Effects.Internals.cs
new file mode 100644
index 00000000..a93932f1
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.Effects.Internals.cs
@@ -0,0 +1,218 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using Microsoft.Graphics.Canvas.Effects;
+using CommunityToolkit.WinUI.Animations;
+using Windows.Graphics.Effects;
+using Windows.UI;
+using CanvasCrossFadeEffect = Microsoft.Graphics.Canvas.Effects.CrossFadeEffect;
+using CanvasExposureEffect = Microsoft.Graphics.Canvas.Effects.ExposureEffect;
+using CanvasHueRotationEffect = Microsoft.Graphics.Canvas.Effects.HueRotationEffect;
+using CanvasOpacityEffect = Microsoft.Graphics.Canvas.Effects.OpacityEffect;
+using CanvasSaturationEffect = Microsoft.Graphics.Canvas.Effects.SaturationEffect;
+using CanvasSepiaEffect = Microsoft.Graphics.Canvas.Effects.SepiaEffect;
+using CanvasTintEffect = Microsoft.Graphics.Canvas.Effects.TintEffect;
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+    /// <summary>
+    /// Adds a new <see cref="GaussianBlurEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="blur">The blur amount to apply</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <param name="mode">The <see cref="EffectBorderMode"/> parameter for the effect, defaults to <see cref="EffectBorderMode.Hard"/></param>
+    /// <param name="optimization">The <see cref="EffectOptimization"/> parameter to use, defaults to <see cref="EffectOptimization.Balanced"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    internal PipelineBuilder Blur(
+        float blur,
+        out string target,
+        EffectBorderMode mode = EffectBorderMode.Hard,
+        EffectOptimization optimization = EffectOptimization.Balanced)
+    {
+        string name = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{name}.{nameof(GaussianBlurEffect.BlurAmount)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new GaussianBlurEffect
+        {
+            BlurAmount = blur,
+            BorderMode = mode,
+            Optimization = optimization,
+            Source = await this.sourceProducer(),
+            Name = name
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Cross fades two pipelines using an <see cref="CanvasCrossFadeEffect"/> instance
+    /// </summary>
+    /// <param name="pipeline">The second <see cref="PipelineBuilder"/> instance to cross fade</param>
+    /// <param name="factor">The cross fade factor to blend the input effects (should be in the [0, 1] range)</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasCrossFadeEffect
+        {
+            CrossFade = factor,
+            Source1 = await this.sourceProducer(),
+            Source2 = await pipeline.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(Factory, this, pipeline, new[] { target });
+    }
+
+    /// <summary>
+    /// Applies an exposure effect on the current pipeline
+    /// </summary>
+    /// <param name="amount">The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range)</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Exposure(float amount, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasExposureEffect.Exposure)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasExposureEffect
+        {
+            Exposure = amount,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Applies a hue rotation effect on the current pipeline
+    /// </summary>
+    /// <param name="angle">The angle to rotate the hue, in radians</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder HueRotation(float angle, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasHueRotationEffect.Angle)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasHueRotationEffect
+        {
+            Angle = angle,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasOpacityEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="opacity">The opacity value to apply to the pipeline</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Opacity(float opacity, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasOpacityEffect.Opacity)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasOpacityEffect
+        {
+            Opacity = opacity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSaturationEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="saturation">The initial saturation amount for the new effect (should be in the [0, 1] range)</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Saturation(float saturation, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasSaturationEffect.Saturation)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSaturationEffect
+        {
+            Saturation = saturation,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSepiaEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="intensity">The sepia effect intensity for the new effect</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Sepia(float intensity, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasSepiaEffect.Intensity)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSepiaEffect
+        {
+            Intensity = intensity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+
+    /// <summary>
+    /// Applies a tint effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="target">The target property to animate the resulting effect.</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Tint(Color color, out string target)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        target = $"{id}.{nameof(CanvasTintEffect.Color)}";
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTintEffect
+        {
+            Color = color,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        return new PipelineBuilder(this, Factory, new[] { target });
+    }
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.Effects.cs b/components/Media/src/Pipelines/PipelineBuilder.Effects.cs
new file mode 100644
index 00000000..a65ca17a
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.Effects.cs
@@ -0,0 +1,693 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using Microsoft.Graphics.Canvas.Effects;
+using CommunityToolkit.WinUI.Animations;
+using Windows.Graphics.Effects;
+using Windows.UI;
+using Windows.UI.Composition;
+using CanvasExposureEffect = Microsoft.Graphics.Canvas.Effects.ExposureEffect;
+using CanvasGrayscaleEffect = Microsoft.Graphics.Canvas.Effects.GrayscaleEffect;
+using CanvasHueRotationEffect = Microsoft.Graphics.Canvas.Effects.HueRotationEffect;
+using CanvasInvertEffect = Microsoft.Graphics.Canvas.Effects.InvertEffect;
+using CanvasLuminanceToAlphaEffect = Microsoft.Graphics.Canvas.Effects.LuminanceToAlphaEffect;
+using CanvasOpacityEffect = Microsoft.Graphics.Canvas.Effects.OpacityEffect;
+using CanvasSaturationEffect = Microsoft.Graphics.Canvas.Effects.SaturationEffect;
+using CanvasSepiaEffect = Microsoft.Graphics.Canvas.Effects.SepiaEffect;
+using CanvasTemperatureAndTintEffect = Microsoft.Graphics.Canvas.Effects.TemperatureAndTintEffect;
+using CanvasTintEffect = Microsoft.Graphics.Canvas.Effects.TintEffect;
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+    /// <summary>
+    /// Adds a new <see cref="GaussianBlurEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="blur">The blur amount to apply</param>
+    /// <param name="mode">The <see cref="EffectBorderMode"/> parameter for the effect, defaults to <see cref="EffectBorderMode.Hard"/></param>
+    /// <param name="optimization">The <see cref="EffectOptimization"/> parameter to use, defaults to <see cref="EffectOptimization.Balanced"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Blur(float blur, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new GaussianBlurEffect
+        {
+            BlurAmount = blur,
+            BorderMode = mode,
+            Optimization = optimization,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="GaussianBlurEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="blur">The initial blur amount</param>
+    /// <param name="setter">The optional blur setter for the effect</param>
+    /// <param name="mode">The <see cref="EffectBorderMode"/> parameter for the effect, defaults to <see cref="EffectBorderMode.Hard"/></param>
+    /// <param name="optimization">The <see cref="EffectOptimization"/> parameter to use, defaults to <see cref="EffectOptimization.Balanced"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Blur(float blur, out EffectSetter<float> setter, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new GaussianBlurEffect
+        {
+            BlurAmount = blur,
+            BorderMode = mode,
+            Optimization = optimization,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(GaussianBlurEffect.BlurAmount)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(GaussianBlurEffect.BlurAmount)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="GaussianBlurEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="blur">The initial blur amount</param>
+    /// <param name="animation">The optional blur animation for the effect</param>
+    /// <param name="mode">The <see cref="EffectBorderMode"/> parameter for the effect, defaults to <see cref="EffectBorderMode.Hard"/></param>
+    /// <param name="optimization">The <see cref="EffectOptimization"/> parameter to use, defaults to <see cref="EffectOptimization.Balanced"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Blur(float blur, out EffectAnimation<float> animation, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new GaussianBlurEffect
+        {
+            BlurAmount = blur,
+            BorderMode = mode,
+            Optimization = optimization,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(GaussianBlurEffect.BlurAmount)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(GaussianBlurEffect.BlurAmount)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSaturationEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="saturation">The saturation amount for the new effect (should be in the [0, 1] range)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Saturation(float saturation)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSaturationEffect
+        {
+            Saturation = saturation,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSaturationEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="saturation">The initial saturation amount for the new effect (should be in the [0, 1] range)</param>
+    /// <param name="setter">The optional saturation setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Saturation(float saturation, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSaturationEffect
+        {
+            Saturation = saturation,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasSaturationEffect.Saturation)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSaturationEffect.Saturation)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSaturationEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="saturation">The initial saturation amount for the new effect (should be in the [0, 1] range)</param>
+    /// <param name="animation">The optional saturation animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Saturation(float saturation, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSaturationEffect
+        {
+            Saturation = saturation,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasSaturationEffect.Saturation)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSaturationEffect.Saturation)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSepiaEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="intensity">The sepia effect intensity for the new effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Sepia(float intensity)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSepiaEffect
+        {
+            Intensity = intensity,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSepiaEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="intensity">The sepia effect intensity for the new effect</param>
+    /// <param name="setter">The optional sepia intensity setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Sepia(float intensity, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSepiaEffect
+        {
+            Intensity = intensity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasSepiaEffect.Intensity)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSepiaEffect.Intensity)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasSepiaEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="intensity">The sepia effect intensity for the new effect</param>
+    /// <param name="animation">The sepia intensity animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Sepia(float intensity, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasSepiaEffect
+        {
+            Intensity = intensity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasSepiaEffect.Intensity)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSepiaEffect.Intensity)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasOpacityEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="opacity">The opacity value to apply to the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Opacity(float opacity)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasOpacityEffect
+        {
+            Opacity = opacity,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasOpacityEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="opacity">The opacity value to apply to the pipeline</param>
+    /// <param name="setter">The optional opacity setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Opacity(float opacity, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasOpacityEffect
+        {
+            Opacity = opacity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasOpacityEffect.Opacity)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasOpacityEffect.Opacity)}" });
+    }
+
+    /// <summary>
+    /// Adds a new <see cref="CanvasOpacityEffect"/> to the current pipeline
+    /// </summary>
+    /// <param name="opacity">The opacity value to apply to the pipeline</param>
+    /// <param name="animation">The optional opacity animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Opacity(float opacity, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasOpacityEffect
+        {
+            Opacity = opacity,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasOpacityEffect.Opacity)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasOpacityEffect.Opacity)}" });
+    }
+
+    /// <summary>
+    /// Applies an exposure effect on the current pipeline
+    /// </summary>
+    /// <param name="amount">The amount of exposure to apply over the current effect (should be in the [-2, 2] range)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Exposure(float amount)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasExposureEffect
+        {
+            Exposure = amount,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies an exposure effect on the current pipeline
+    /// </summary>
+    /// <param name="amount">The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range)</param>
+    /// <param name="setter">The optional amount setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Exposure(float amount, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasExposureEffect
+        {
+            Exposure = amount,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasExposureEffect.Exposure)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasExposureEffect.Exposure)}" });
+    }
+
+    /// <summary>
+    /// Applies an exposure effect on the current pipeline
+    /// </summary>
+    /// <param name="amount">The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range)</param>
+    /// <param name="animation">The optional amount animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Exposure(float amount, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasExposureEffect
+        {
+            Exposure = amount,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasExposureEffect.Exposure)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasExposureEffect.Exposure)}" });
+    }
+
+    /// <summary>
+    /// Applies a hue rotation effect on the current pipeline
+    /// </summary>
+    /// <param name="angle">The angle to rotate the hue, in radians</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder HueRotation(float angle)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasHueRotationEffect
+        {
+            Angle = angle,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies a hue rotation effect on the current pipeline
+    /// </summary>
+    /// <param name="angle">The angle to rotate the hue, in radians</param>
+    /// <param name="setter">The optional rotation angle setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder HueRotation(float angle, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasHueRotationEffect
+        {
+            Angle = angle,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasHueRotationEffect.Angle)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasHueRotationEffect.Angle)}" });
+    }
+
+    /// <summary>
+    /// Applies a hue rotation effect on the current pipeline
+    /// </summary>
+    /// <param name="angle">The angle to rotate the hue, in radians</param>
+    /// <param name="animation">The optional rotation angle animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder HueRotation(float angle, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasHueRotationEffect
+        {
+            Angle = angle,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasHueRotationEffect.Angle)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasHueRotationEffect.Angle)}" });
+    }
+
+    /// <summary>
+    /// Applies a tint effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Tint(Color color)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTintEffect
+        {
+            Color = color,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies a tint effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="setter">The optional color setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Tint(Color color, out EffectSetter<Color> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTintEffect
+        {
+            Color = color,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(CanvasTintEffect.Color)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTintEffect.Color)}" });
+    }
+
+    /// <summary>
+    /// Applies a tint effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="animation">The optional color animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Tint(Color color, out EffectAnimation<Color> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTintEffect
+        {
+            Color = color,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTintEffect.Color)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTintEffect.Color)}" });
+    }
+
+    /// <summary>
+    /// Applies a temperature and tint effect on the current pipeline
+    /// </summary>
+    /// <param name="temperature">The temperature value to use (should be in the [-1, 1] range)</param>
+    /// <param name="tint">The tint value to use (should be in the [-1, 1] range)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder TemperatureAndTint(float temperature, float tint)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTemperatureAndTintEffect
+        {
+            Temperature = temperature,
+            Tint = tint,
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies a temperature and tint effect on the current pipeline
+    /// </summary>
+    /// <param name="temperature">The temperature value to use (should be in the [-1, 1] range)</param>
+    /// <param name="temperatureSetter">The optional temperature setter for the effect</param>
+    /// <param name="tint">The tint value to use (should be in the [-1, 1] range)</param>
+    /// <param name="tintSetter">The optional tint setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder TemperatureAndTint(
+        float temperature,
+        out EffectSetter<float> temperatureSetter,
+        float tint,
+        out EffectSetter<float> tintSetter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTemperatureAndTintEffect
+        {
+            Temperature = temperature,
+            Tint = tint,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        temperatureSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", value);
+
+        tintSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}", value);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}" });
+    }
+
+    /// <summary>
+    /// Applies a temperature and tint effect on the current pipeline
+    /// </summary>
+    /// <param name="temperature">The temperature value to use (should be in the [-1, 1] range)</param>
+    /// <param name="temperatureAnimation">The optional temperature animation for the effect</param>
+    /// <param name="tint">The tint value to use (should be in the [-1, 1] range)</param>
+    /// <param name="tintAnimation">The optional tint animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder TemperatureAndTint(
+        float temperature,
+        out EffectAnimation<float> temperatureAnimation,
+        float tint,
+        out EffectAnimation<float> tintAnimation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasTemperatureAndTintEffect
+        {
+            Temperature = temperature,
+            Tint = tint,
+            Source = await this.sourceProducer(),
+            Name = id
+        };
+
+        temperatureAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", value, duration);
+
+        tintAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}", value, duration);
+
+        return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}" });
+    }
+
+    /// <summary>
+    /// Applies a shade effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="mix">The amount of mix to apply over the current effect (must be in the [0, 1] range)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Shade(Color color, float mix)
+    {
+        return FromColor(color).CrossFade(this, mix);
+    }
+
+    /// <summary>
+    /// Applies a shade effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="colorSetter">The optional color setter for the effect</param>
+    /// <param name="mix">The initial amount of mix to apply over the current effect (must be in the [0, 1] range)</param>
+    /// <param name="mixSetter">The optional mix setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Shade(
+        Color color,
+        out EffectSetter<Color> colorSetter,
+        float mix,
+        out EffectSetter<float> mixSetter)
+    {
+        return FromColor(color, out colorSetter).CrossFade(this, mix, out mixSetter);
+    }
+
+    /// <summary>
+    /// Applies a shade effect on the current pipeline
+    /// </summary>
+    /// <param name="color">The color to use</param>
+    /// <param name="colorAnimation">The optional color animation for the effect</param>
+    /// <param name="mix">The initial amount of mix to apply over the current effect (must be in the [0, 1] range)</param>
+    /// <param name="mixAnimation">The optional mix animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Shade(
+        Color color,
+        out EffectAnimation<Color> colorAnimation,
+        float mix,
+        out EffectAnimation<float> mixAnimation)
+    {
+        return FromColor(color, out colorAnimation).CrossFade(this, mix, out mixAnimation);
+    }
+
+    /// <summary>
+    /// Applies a luminance to alpha effect on the current pipeline
+    /// </summary>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder LuminanceToAlpha()
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasLuminanceToAlphaEffect
+        {
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies an invert effect on the current pipeline
+    /// </summary>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Invert()
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasInvertEffect
+        {
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies a grayscale on the current pipeline
+    /// </summary>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Grayscale()
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasGrayscaleEffect
+        {
+            Source = await this.sourceProducer()
+        };
+
+        return new PipelineBuilder(this, Factory);
+    }
+
+    /// <summary>
+    /// Applies a custom effect to the current pipeline
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{T, TResult}"/> that takes the current <see cref="IGraphicsEffectSource"/> instance and produces a new effect to display</param>
+    /// <param name="animations">The list of optional animatable properties in the returned effect</param>
+    /// <param name="initializers">The list of source parameters that require deferred initialization (see <see cref="CompositionEffectSourceParameter"/> for more info)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Effect(
+        Func<IGraphicsEffectSource, IGraphicsEffectSource> factory,
+        IEnumerable<string>? animations = null,
+        IEnumerable<BrushProvider>? initializers = null)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => factory(await this.sourceProducer());
+
+        return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+    }
+
+    /// <summary>
+    /// Applies a custom effect to the current pipeline
+    /// </summary>
+    /// <param name="factory">An asynchronous <see cref="Func{T, TResult}"/> that takes the current <see cref="IGraphicsEffectSource"/> instance and produces a new effect to display</param>
+    /// <param name="animations">The list of optional animatable properties in the returned effect</param>
+    /// <param name="initializers">The list of source parameters that require deferred initialization (see <see cref="CompositionEffectSourceParameter"/> for more info)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Effect(
+        Func<IGraphicsEffectSource, Task<IGraphicsEffectSource>> factory,
+        IEnumerable<string>? animations = null,
+        IEnumerable<BrushProvider>? initializers = null)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => await factory(await this.sourceProducer());
+
+        return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+    }
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs b/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs
new file mode 100644
index 00000000..8997d397
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.Initialization.cs
@@ -0,0 +1,327 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using System.Numerics;
+using Microsoft.Graphics.Canvas;
+using Microsoft.Graphics.Canvas.Effects;
+using CommunityToolkit.WinUI.Animations;
+using CommunityToolkit.WinUI.Media.Helpers;
+using CommunityToolkit.WinUI.Media.Helpers.Cache;
+using Windows.Graphics.Effects;
+using Windows.UI;
+
+#if WINUI3
+using Microsoft.UI.Xaml.Hosting;
+using Microsoft.UI.Composition;
+#elif WINUI2
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+    /// <summary>
+    /// The cache manager for backdrop brushes
+    /// </summary>
+    private static readonly CompositionObjectCache<CompositionBrush> BackdropBrushCache = new CompositionObjectCache<CompositionBrush>();
+
+    /// <summary>
+    /// The cache manager for host backdrop brushes
+    /// </summary>
+    private static readonly CompositionObjectCache<CompositionBrush> HostBackdropBrushCache = new CompositionObjectCache<CompositionBrush>();
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the <see cref="CompositionBrush"/> returned by <see cref="Compositor.CreateBackdropBrush"/>
+    /// </summary>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBackdrop()
+    {
+        ValueTask<CompositionBrush> Factory()
+        {
+            var brush = BackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateBackdropBrush());
+
+            return new ValueTask<CompositionBrush>(brush);
+        }
+
+        return new PipelineBuilder(Factory);
+    }
+
+#if WINUI2
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the <see cref="CompositionBrush"/> returned by <see cref="Compositor.CreateHostBackdropBrush"/>
+    /// </summary>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHostBackdrop()
+    {
+        ValueTask<CompositionBrush> Factory()
+        {
+            var brush = HostBackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateHostBackdropBrush());
+
+            return new ValueTask<CompositionBrush>(brush);
+        }
+
+        return new PipelineBuilder(Factory);
+    }
+#endif
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromColor(Color color)
+    {
+        return new PipelineBuilder(() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect { Color = color }));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <param name="setter">The optional color setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromColor(Color color, out EffectSetter<Color> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        ValueTask<IGraphicsEffectSource> Factory() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect
+        {
+            Color = color,
+            Name = id
+        });
+
+        setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(ColorSourceEffect.Color)}", value);
+
+        return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.Color)}" });
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <param name="animation">The optional color animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromColor(Color color, out EffectAnimation<Color> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        ValueTask<IGraphicsEffectSource> Factory() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect
+        {
+            Color = color,
+            Name = id
+        });
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ColorSourceEffect.Color)}", value, duration);
+
+        return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.Color)}" });
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHdrColor(Vector4 color)
+    {
+        return new PipelineBuilder(() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect { ColorHdr = color }));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <param name="setter">The optional color setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHdrColor(Vector4 color, out EffectSetter<Vector4> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        ValueTask<IGraphicsEffectSource> Factory() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect
+        {
+            ColorHdr = color,
+            Name = id
+        });
+
+        setter = (brush, value) => brush.Properties.InsertVector4($"{id}.{nameof(ColorSourceEffect.ColorHdr)}", value);
+
+        return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.ColorHdr)}" });
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a solid <see cref="CompositionBrush"/> with the specified color
+    /// </summary>
+    /// <param name="color">The desired color for the initial <see cref="CompositionBrush"/></param>
+    /// <param name="animation">The optional color animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHdrColor(Vector4 color, out EffectAnimation<Vector4> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        ValueTask<IGraphicsEffectSource> Factory() => new ValueTask<IGraphicsEffectSource>(new ColorSourceEffect
+        {
+            ColorHdr = color,
+            Name = id
+        });
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ColorSourceEffect.ColorHdr)}", value, duration);
+
+        return new PipelineBuilder(Factory, new[] { $"{id}.{nameof(ColorSourceEffect.ColorHdr)}" });
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="CompositionBrush"/> instance
+    /// </summary>
+    /// <param name="brush">A <see cref="CompositionBrush"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBrush(CompositionBrush brush)
+    {
+        return new PipelineBuilder(() => new ValueTask<CompositionBrush>(brush));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="CompositionBrush"/> instance
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> that synchronously returns a <see cref="CompositionBrush"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBrush(Func<CompositionBrush> factory)
+    {
+        return new PipelineBuilder(() => new ValueTask<CompositionBrush>(factory()));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="CompositionBrush"/> instance
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> that asynchronously returns a <see cref="CompositionBrush"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBrush(Func<Task<CompositionBrush>> factory)
+    {
+        async ValueTask<CompositionBrush> Factory() => await factory();
+
+        return new PipelineBuilder(Factory);
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="IGraphicsEffectSource"/> instance
+    /// </summary>
+    /// <param name="effect">A <see cref="IGraphicsEffectSource"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromEffect(IGraphicsEffectSource effect)
+    {
+        return new PipelineBuilder(() => new ValueTask<IGraphicsEffectSource>(effect));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="IGraphicsEffectSource"/> instance
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> that synchronously returns a <see cref="IGraphicsEffectSource"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromEffect(Func<IGraphicsEffectSource> factory)
+    {
+        return new PipelineBuilder(() => new ValueTask<IGraphicsEffectSource>(factory()));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the input <see cref="IGraphicsEffectSource"/> instance
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> that asynchronously returns a <see cref="IGraphicsEffectSource"/> instance to start the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromEffect(Func<Task<IGraphicsEffectSource>> factory)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => await factory();
+
+        return new PipelineBuilder(Factory);
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a Win2D image
+    /// </summary>
+    /// <param name="relativePath">The relative path for the image to load (eg. "/Assets/image.png")</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromImage(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default)
+    {
+        return FromImage(relativePath.ToAppxUri(), dpiMode, cacheMode);
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a Win2D image
+    /// </summary>
+    /// <param name="uri">The path for the image to load</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromImage(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default)
+    {
+        return new PipelineBuilder(() => new ValueTask<CompositionBrush>(SurfaceLoader.LoadImageAsync(uri, dpiMode, cacheMode)!));
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a Win2D image tiled to cover the available space
+    /// </summary>
+    /// <param name="relativePath">The relative path for the image to load (eg. "/Assets/image.png")</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromTiles(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default)
+    {
+        return FromTiles(relativePath.ToAppxUri(), dpiMode, cacheMode);
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from a Win2D image tiled to cover the available space
+    /// </summary>
+    /// <param name="uri">The path for the image to load</param>
+    /// <param name="dpiMode">Indicates the desired DPI mode to use when loading the image</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromTiles(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default)
+    {
+        var image = FromImage(uri, dpiMode, cacheMode);
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new BorderEffect
+        {
+            ExtendX = CanvasEdgeBehavior.Wrap,
+            ExtendY = CanvasEdgeBehavior.Wrap,
+            Source = await image.sourceProducer()
+        };
+
+        return new PipelineBuilder(image, Factory);
+    }
+
+    /// <summary>
+    /// Starts a new <see cref="PipelineBuilder"/> pipeline from the <see cref="CompositionBrush"/> returned by <see cref="Compositor.CreateBackdropBrush"/> on the input <see cref="UIElement"/>
+    /// </summary>
+    /// <param name="element">The source <see cref="UIElement"/> to use to create the pipeline</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromUIElement(UIElement element)
+    {
+        return new PipelineBuilder(() => new ValueTask<CompositionBrush>(ElementCompositionPreview.GetElementVisual(element).Compositor.CreateBackdropBrush()));
+    }
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.Merge.cs b/components/Media/src/Pipelines/PipelineBuilder.Merge.cs
new file mode 100644
index 00000000..5fa5db08
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.Merge.cs
@@ -0,0 +1,160 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using Microsoft.Graphics.Canvas.Effects;
+using CommunityToolkit.WinUI.Animations;
+using Windows.Graphics.Effects;
+using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect;
+using CanvasCrossFadeEffect = Microsoft.Graphics.Canvas.Effects.CrossFadeEffect;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+#elif WINUI2
+using Windows.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+    /// <summary>
+    /// Blends two pipelines using a <see cref="BlendEffect"/> instance with the specified mode
+    /// </summary>
+    /// <param name="pipeline">The second <see cref="PipelineBuilder"/> instance to blend</param>
+    /// <param name="mode">The desired <see cref="BlendEffectMode"/> to use to blend the input pipelines</param>
+    /// <param name="placement">The placemeht to use with the two input pipelines (the default is <see cref="Placement.Foreground"/>)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Blend(PipelineBuilder pipeline, BlendEffectMode mode, Placement placement = Placement.Foreground)
+    {
+        var (foreground, background) = placement switch
+        {
+            Placement.Foreground => (pipeline, this),
+            Placement.Background => (this, pipeline),
+            _ => throw new ArgumentException($"Invalid placement value: {placement}")
+        };
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasBlendEffect
+        {
+            Foreground = await foreground.sourceProducer(),
+            Background = await background.sourceProducer(),
+            Mode = mode
+        };
+
+        return new PipelineBuilder(Factory, foreground, background);
+    }
+
+    /// <summary>
+    /// Cross fades two pipelines using an <see cref="CanvasCrossFadeEffect"/> instance
+    /// </summary>
+    /// <param name="pipeline">The second <see cref="PipelineBuilder"/> instance to cross fade</param>
+    /// <param name="factor">The cross fade factor to blend the input effects (default is 0.5, must be in the [0, 1] range)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor = 0.5f)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasCrossFadeEffect
+        {
+            CrossFade = factor,
+            Source1 = await this.sourceProducer(),
+            Source2 = await pipeline.sourceProducer()
+        };
+
+        return new PipelineBuilder(Factory, this, pipeline);
+    }
+
+    /// <summary>
+    /// Cross fades two pipelines using an <see cref="CanvasCrossFadeEffect"/> instance
+    /// </summary>
+    /// <param name="pipeline">The second <see cref="PipelineBuilder"/> instance to cross fade</param>
+    /// <param name="factor">The cross fade factor to blend the input effects (should be in the [0, 1] range)</param>
+    /// <param name="setter">The optional blur setter for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectSetter<float> setter)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasCrossFadeEffect
+        {
+            CrossFade = factor,
+            Source1 = await this.sourceProducer(),
+            Source2 = await pipeline.sourceProducer(),
+            Name = id
+        };
+
+        setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}", value);
+
+        return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}" });
+    }
+
+    /// <summary>
+    /// Cross fades two pipelines using an <see cref="CanvasCrossFadeEffect"/> instance
+    /// </summary>
+    /// <param name="pipeline">The second <see cref="PipelineBuilder"/> instance to cross fade</param>
+    /// <param name="factor">The cross fade factor to blend the input effects (should be in the [0, 1] range)</param>
+    /// <param name="animation">The optional blur animation for the effect</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectAnimation<float> animation)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        async ValueTask<IGraphicsEffectSource> Factory() => new CanvasCrossFadeEffect
+        {
+            CrossFade = factor,
+            Source1 = await this.sourceProducer(),
+            Source2 = await pipeline.sourceProducer(),
+            Name = id
+        };
+
+        animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}", value, duration);
+
+        return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}" });
+    }
+
+    /// <summary>
+    /// Blends two pipelines using the provided <see cref="Func{T1, T2, TResult}"/> to do so
+    /// </summary>
+    /// <param name="factory">The blend function to use</param>
+    /// <param name="background">The background pipeline to blend with the current instance</param>
+    /// <param name="animations">The list of optional animatable properties in the returned effect</param>
+    /// <param name="initializers">The list of source parameters that require deferred initialization (see <see cref="CompositionEffectSourceParameter"/> for more info)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Merge(
+        Func<IGraphicsEffectSource, IGraphicsEffectSource, IGraphicsEffectSource> factory,
+        PipelineBuilder background,
+        IEnumerable<string>? animations = null,
+        IEnumerable<BrushProvider>? initializers = null)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => factory(await this.sourceProducer(), await background.sourceProducer());
+
+        return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+    }
+
+    /// <summary>
+    /// Blends two pipelines using the provided asynchronous <see cref="Func{T1, T2, TResult}"/> to do so
+    /// </summary>
+    /// <param name="factory">The asynchronous blend function to use</param>
+    /// <param name="background">The background pipeline to blend with the current instance</param>
+    /// <param name="animations">The list of optional animatable properties in the returned effect</param>
+    /// <param name="initializers">The list of source parameters that require deferred initialization (see <see cref="CompositionEffectSourceParameter"/> for more info)</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public PipelineBuilder Merge(
+        Func<IGraphicsEffectSource, IGraphicsEffectSource, Task<IGraphicsEffectSource>> factory,
+        PipelineBuilder background,
+        IEnumerable<string>? animations = null,
+        IEnumerable<BrushProvider>? initializers = null)
+    {
+        async ValueTask<IGraphicsEffectSource> Factory() => await factory(await this.sourceProducer(), await background.sourceProducer());
+
+        return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer));
+    }
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.Prebuilt.cs b/components/Media/src/Pipelines/PipelineBuilder.Prebuilt.cs
new file mode 100644
index 00000000..bb95863e
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.Prebuilt.cs
@@ -0,0 +1,214 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using Microsoft.Graphics.Canvas.Effects;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="Windows.UI.Composition.CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+#if WINUI2
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the host backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHostBackdropAcrylic(
+        Color tintColor,
+        float tintOpacity,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline =
+            FromHostBackdrop()
+            .LuminanceToAlpha()
+            .Opacity(0.4f)
+            .Blend(FromHostBackdrop(), BlendEffectMode.Multiply)
+            .Shade(tintColor, tintOpacity);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the host backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintColorSetter">The optional tint color setter for the effect</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect</param>
+    /// <param name="tintOpacitySetter">The optional tint mix setter for the effect</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHostBackdropAcrylic(
+        Color tintColor,
+        out EffectSetter<Color> tintColorSetter,
+        float tintOpacity,
+        out EffectSetter<float> tintOpacitySetter,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline =
+            FromHostBackdrop()
+            .LuminanceToAlpha()
+            .Opacity(0.4f)
+            .Blend(FromHostBackdrop(), BlendEffectMode.Multiply)
+            .Shade(tintColor, out tintColorSetter, tintOpacity, out tintOpacitySetter);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the host backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintColorAnimation">The optional tint color animation for the effect</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect</param>
+    /// <param name="tintOpacityAnimation">The optional tint mix animation for the effect</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromHostBackdropAcrylic(
+        Color tintColor,
+        out EffectAnimation<Color> tintColorAnimation,
+        float tintOpacity,
+        out EffectAnimation<float> tintOpacityAnimation,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline =
+            FromHostBackdrop()
+            .LuminanceToAlpha()
+            .Opacity(0.4f)
+            .Blend(FromHostBackdrop(), BlendEffectMode.Multiply)
+            .Shade(tintColor, out tintColorAnimation, tintOpacity, out tintOpacityAnimation);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+#endif
+
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the in-app backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect (must be in the [0, 1] range)</param>
+    /// <param name="blurAmount">The amount of blur to apply to the acrylic brush</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBackdropAcrylic(
+        Color tintColor,
+        float tintOpacity,
+        float blurAmount,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline = FromBackdrop().Shade(tintColor, tintOpacity).Blur(blurAmount);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the in-app backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintColorSetter">The optional tint color setter for the effect</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect</param>
+    /// <param name="tintOpacitySetter">The optional tint mix setter for the effect</param>
+    /// <param name="blurAmount">The amount of blur to apply to the acrylic brush</param>
+    /// <param name="blurAmountSetter">The optional blur setter for the effect</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBackdropAcrylic(
+        Color tintColor,
+        out EffectSetter<Color> tintColorSetter,
+        float tintOpacity,
+        out EffectSetter<float> tintOpacitySetter,
+        float blurAmount,
+        out EffectSetter<float> blurAmountSetter,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline =
+            FromBackdrop()
+            .Shade(tintColor, out tintColorSetter, tintOpacity, out tintOpacitySetter)
+            .Blur(blurAmount, out blurAmountSetter);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+
+    /// <summary>
+    /// Returns a new <see cref="PipelineBuilder"/> instance that implements the in-app backdrop acrylic effect
+    /// </summary>
+    /// <param name="tintColor">The tint color to use</param>
+    /// <param name="tintAnimation">The optional tint color animation for the effect</param>
+    /// <param name="tintOpacity">The amount of tint to apply over the current effect</param>
+    /// <param name="tintOpacityAnimation">The optional tint mix animation for the effect</param>
+    /// <param name="blurAmount">The amount of blur to apply to the acrylic brush</param>
+    /// <param name="blurAmountAnimation">The optional blur animation for the effect</param>
+    /// <param name="noiseUri">The <see cref="Uri"/> for the noise texture to load for the acrylic effect</param>
+    /// <param name="cacheMode">The cache mode to use to load the image</param>
+    /// <returns>A new <see cref="PipelineBuilder"/> instance to use to keep adding new effects</returns>
+    [Pure]
+    public static PipelineBuilder FromBackdropAcrylic(
+        Color tintColor,
+        out EffectAnimation<Color> tintAnimation,
+        float tintOpacity,
+        out EffectAnimation<float> tintOpacityAnimation,
+        float blurAmount,
+        out EffectAnimation<float> blurAmountAnimation,
+        Uri? noiseUri,
+        CacheMode cacheMode = CacheMode.Default)
+    {
+        var pipeline =
+            FromBackdrop()
+            .Shade(tintColor, out tintAnimation, tintOpacity, out tintOpacityAnimation)
+            .Blur(blurAmount, out blurAmountAnimation);
+
+        if (noiseUri != null)
+        {
+            return pipeline.Blend(FromTiles(noiseUri, cacheMode: cacheMode), BlendEffectMode.Overlay);
+        }
+
+        return pipeline;
+    }
+}
diff --git a/components/Media/src/Pipelines/PipelineBuilder.cs b/components/Media/src/Pipelines/PipelineBuilder.cs
new file mode 100644
index 00000000..4057c7ea
--- /dev/null
+++ b/components/Media/src/Pipelines/PipelineBuilder.cs
@@ -0,0 +1,224 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using System.Numerics;
+using CommunityToolkit.WinUI.Animations;
+using Windows.Graphics.Effects;
+
+#if WINUI2
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+#elif WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+#endif
+
+namespace CommunityToolkit.WinUI.Media.Pipelines;
+
+/// <summary>
+/// A <see langword="delegate"/> that represents a custom effect property setter that can be applied to a <see cref="CompositionBrush"/>
+/// </summary>
+/// <typeparam name="T">The type of property value to set</typeparam>
+/// <param name="brush">The target <see cref="CompositionBrush"/> instance to target</param>
+/// <param name="value">The property value to set</param>
+public delegate void EffectSetter<in T>(CompositionBrush brush, T value)
+    where T : unmanaged;
+
+/// <summary>
+/// A <see langword="delegate"/> that represents a custom effect property animation that can be applied to a <see cref="CompositionBrush"/>
+/// </summary>
+/// <typeparam name="T">The type of property value to animate</typeparam>
+/// <param name="brush">The target <see cref="CompositionBrush"/> instance to use to start the animation</param>
+/// <param name="value">The animation target value</param>
+/// <param name="duration">The animation duration</param>
+/// <returns>A <see cref="Task"/> that completes when the target animation completes</returns>
+public delegate Task EffectAnimation<in T>(CompositionBrush brush, T value, TimeSpan duration)
+    where T : unmanaged;
+
+/// <summary>
+/// A <see langword="class"/> that allows to build custom effects pipelines and create <see cref="CompositionBrush"/> instances from them
+/// </summary>
+public sealed partial class PipelineBuilder
+{
+    /// <summary>
+    /// The <see cref="Func{TResult}"/> instance used to produce the output <see cref="IGraphicsEffectSource"/> for this pipeline
+    /// </summary>
+    private readonly Func<ValueTask<IGraphicsEffectSource>> sourceProducer;
+
+    /// <summary>
+    /// The collection of animation properties present in the current pipeline
+    /// </summary>
+    private readonly IReadOnlyCollection<string> animationProperties;
+
+    /// <summary>
+    /// The collection of info on the parameters that need to be initialized after creating the final <see cref="CompositionBrush"/>
+    /// </summary>
+    private readonly IReadOnlyDictionary<string, Func<ValueTask<CompositionBrush>>> lazyParameters;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will return the initial <see cref="CompositionBrush"/></param>
+    private PipelineBuilder(Func<ValueTask<CompositionBrush>> factory)
+    {
+        string id = Guid.NewGuid().ToUppercaseAsciiLetters();
+
+        this.sourceProducer = () => new ValueTask<IGraphicsEffectSource>(new CompositionEffectSourceParameter(id));
+        this.animationProperties = Array.Empty<string>();
+        this.lazyParameters = new Dictionary<string, Func<ValueTask<CompositionBrush>>> { { id, factory } };
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will return the initial <see cref="IGraphicsEffectSource"/></param>
+    private PipelineBuilder(Func<ValueTask<IGraphicsEffectSource>> factory)
+        : this(
+            factory,
+            Array.Empty<string>(),
+            new Dictionary<string, Func<ValueTask<CompositionBrush>>>())
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will produce the new <see cref="IGraphicsEffectSource"/> to add to the pipeline</param>
+    /// <param name="animations">The collection of animation properties for the new effect</param>
+    private PipelineBuilder(
+        Func<ValueTask<IGraphicsEffectSource>> factory,
+        IReadOnlyCollection<string> animations)
+        : this(
+            factory,
+            animations,
+            new Dictionary<string, Func<ValueTask<CompositionBrush>>>())
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will produce the new <see cref="IGraphicsEffectSource"/> to add to the pipeline</param>
+    /// <param name="animations">The collection of animation properties for the new effect</param>
+    /// <param name="lazy">The collection of <see cref="CompositionBrush"/> instances that needs to be initialized for the new effect</param>
+    private PipelineBuilder(
+        Func<ValueTask<IGraphicsEffectSource>> factory,
+        IReadOnlyCollection<string> animations,
+        IReadOnlyDictionary<string, Func<ValueTask<CompositionBrush>>> lazy)
+    {
+        this.sourceProducer = factory;
+        this.animationProperties = animations;
+        this.lazyParameters = lazy;
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="source">The source pipeline to attach the new effect to</param>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will produce the new <see cref="IGraphicsEffectSource"/> to add to the pipeline</param>
+    /// <param name="animations">The collection of animation properties for the new effect</param>
+    /// <param name="lazy">The collection of <see cref="CompositionBrush"/> instances that needs to be initialized for the new effect</param>
+    private PipelineBuilder(
+        PipelineBuilder source,
+        Func<ValueTask<IGraphicsEffectSource>> factory,
+        IReadOnlyCollection<string>? animations = null,
+        IReadOnlyDictionary<string, Func<ValueTask<CompositionBrush>>>? lazy = null)
+        : this(
+            factory,
+            animations?.Merge(source.animationProperties) ?? source.animationProperties,
+            lazy?.Merge(source.lazyParameters) ?? source.lazyParameters)
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PipelineBuilder"/> class.
+    /// </summary>
+    /// <param name="factory">A <see cref="Func{TResult}"/> instance that will produce the new <see cref="IGraphicsEffectSource"/> to add to the pipeline</param>
+    /// <param name="a">The first pipeline to merge</param>
+    /// <param name="b">The second pipeline to merge</param>
+    /// <param name="animations">The collection of animation properties for the new effect</param>
+    /// <param name="lazy">The collection of <see cref="CompositionBrush"/> instances that needs to be initialized for the new effect</param>
+    private PipelineBuilder(
+        Func<ValueTask<IGraphicsEffectSource>> factory,
+        PipelineBuilder a,
+        PipelineBuilder b,
+        IReadOnlyCollection<string>? animations = null,
+        IReadOnlyDictionary<string, Func<ValueTask<CompositionBrush>>>? lazy = null)
+        : this(
+            factory,
+            animations?.Merge(a.animationProperties.Merge(b.animationProperties)) ?? a.animationProperties.Merge(b.animationProperties),
+            lazy?.Merge(a.lazyParameters.Merge(b.lazyParameters)) ?? a.lazyParameters.Merge(b.lazyParameters))
+    {
+    }
+
+    /// <summary>
+    /// Builds a <see cref="CompositionBrush"/> instance from the current effects pipeline
+    /// </summary>
+    /// <returns>A <see cref="Task{T}"/> that returns the final <see cref="CompositionBrush"/> instance to use</returns>
+    [Pure]
+    public async Task<CompositionBrush> BuildAsync()
+    {
+        var effect = await this.sourceProducer() as IGraphicsEffect;
+
+        // Validate the pipeline
+        if (effect is null)
+        {
+            throw new InvalidOperationException("The pipeline doesn't contain a valid effects sequence");
+        }
+
+        // Build the effects factory
+        var factory = this.animationProperties.Count > 0
+            ? Window.Current.Compositor.CreateEffectFactory(effect, this.animationProperties)
+            : Window.Current.Compositor.CreateEffectFactory(effect);
+
+        // Create the effect factory and apply the final effect
+        var effectBrush = factory.CreateBrush();
+        foreach (var pair in this.lazyParameters)
+        {
+            effectBrush.SetSourceParameter(pair.Key, await pair.Value());
+        }
+
+        return effectBrush;
+    }
+
+    /// <summary>
+    /// Builds the current pipeline and creates a <see cref="SpriteVisual"/> that is applied to the input <see cref="UIElement"/>
+    /// </summary>
+    /// <param name="target">The target <see cref="UIElement"/> to apply the brush to</param>
+    /// <param name="reference">An optional <see cref="UIElement"/> to use to bind the size of the created brush</param>
+    /// <returns>A <see cref="Task{T}"/> that returns the final <see cref="SpriteVisual"/> instance to use</returns>
+    public async Task<SpriteVisual> AttachAsync(UIElement target, UIElement? reference = null)
+    {
+        SpriteVisual visual = Window.Current.Compositor.CreateSpriteVisual();
+
+        visual.Brush = await BuildAsync();
+
+        ElementCompositionPreview.SetElementChildVisual(target, visual);
+
+        if (reference != null)
+        {
+            if (reference == target)
+            {
+                visual.RelativeSizeAdjustment = Vector2.One;
+            }
+            else
+            {
+                visual.BindSize(reference);
+            }
+        }
+
+        return visual;
+    }
+
+    /// <summary>
+    /// Creates a new <see cref="XamlCompositionBrush"/> from the current effects pipeline
+    /// </summary>
+    /// <returns>A <see cref="XamlCompositionBrush"/> instance ready to be displayed</returns>
+    [Pure]
+    public XamlCompositionBrush AsBrush()
+    {
+        return new XamlCompositionBrush(this);
+    }
+}
diff --git a/components/Media/src/Visuals/AttachedVisualFactoryBase.cs b/components/Media/src/Visuals/AttachedVisualFactoryBase.cs
new file mode 100644
index 00000000..c2d4eaae
--- /dev/null
+++ b/components/Media/src/Visuals/AttachedVisualFactoryBase.cs
@@ -0,0 +1,24 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if WINUI3
+using Microsoft.UI.Composition;
+#elif WINUI2
+using Windows.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A type responsible for creating <see cref="Visual"/> instances to attach to target elements.
+/// </summary>
+public abstract class AttachedVisualFactoryBase : DependencyObject
+{
+    /// <summary>
+    /// Creates a <see cref="Visual"/> to attach to the target element.
+    /// </summary>
+    /// <param name="element">The target <see cref="UIElement"/> the visual will be attached to.</param>
+    /// <returns>A <see cref="Visual"/> instance that the caller will attach to the target element.</returns>
+    public abstract ValueTask<Visual> GetAttachedVisualAsync(UIElement element);
+}
diff --git a/components/Media/src/Visuals/PipelineVisualFactory.cs b/components/Media/src/Visuals/PipelineVisualFactory.cs
new file mode 100644
index 00000000..494fc44b
--- /dev/null
+++ b/components/Media/src/Visuals/PipelineVisualFactory.cs
@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+#elif WINUI2
+using Windows.UI.Composition;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A builder type for <see cref="SpriteVisual"/> instance to apply to UI elements.
+/// </summary>
+[ContentProperty(Name = nameof(Effects))]
+public sealed class PipelineVisualFactory : PipelineVisualFactoryBase
+{
+    /// <summary>
+    /// Gets or sets the source for the current pipeline (defaults to a <see cref="BackdropSourceExtension"/> with <see cref="AcrylicBackgroundSource.Backdrop"/> source).
+    /// </summary>
+    public PipelineBuilder? Source { get; set; }
+
+    /// <summary>
+    /// Gets or sets the collection of effects to use in the current pipeline.
+    /// </summary>
+    public IList<PipelineEffect> Effects
+    {
+        get
+        {
+            if (GetValue(EffectsProperty) is not IList<PipelineEffect> effects)
+            {
+                effects = new List<PipelineEffect>();
+
+                SetValue(EffectsProperty, effects);
+            }
+
+            return effects;
+        }
+        set => SetValue(EffectsProperty, value);
+    }
+
+    /// <summary>
+    /// Identifies the <seealso cref="Effects"/> dependency property.
+    /// </summary>
+    public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register(
+        nameof(Effects),
+        typeof(IList<PipelineEffect>),
+        typeof(PipelineVisualFactory),
+        new PropertyMetadata(null));
+
+    /// <inheritdoc/>
+    public override async ValueTask<Visual> GetAttachedVisualAsync(UIElement element)
+    {
+        var visual = (SpriteVisual)await base.GetAttachedVisualAsync(element);
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            effect.NotifyCompositionBrushInUse(visual.Brush);
+        }
+
+        return visual;
+    }
+
+    /// <inheritdoc/>
+    protected override PipelineBuilder OnPipelineRequested()
+    {
+        PipelineBuilder builder = Source ?? PipelineBuilder.FromBackdrop();
+
+        foreach (IPipelineEffect effect in Effects)
+        {
+            builder = effect.AppendToBuilder(builder);
+        }
+
+        return builder;
+    }
+}
diff --git a/components/Media/src/Visuals/PipelineVisualFactoryBase.cs b/components/Media/src/Visuals/PipelineVisualFactoryBase.cs
new file mode 100644
index 00000000..938546c4
--- /dev/null
+++ b/components/Media/src/Visuals/PipelineVisualFactoryBase.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Media.Pipelines;
+
+#if WINUI3
+using Microsoft.UI.Composition;
+using Microsoft.UI.Xaml.Hosting;
+#elif WINUI2
+using Windows.UI.Composition;
+using Windows.UI.Xaml.Hosting;
+#endif
+
+namespace CommunityToolkit.WinUI.Media;
+
+/// <summary>
+/// A base class that extends <see cref="AttachedVisualFactoryBase"/> by leveraging the <see cref="PipelineBuilder"/> APIs.
+/// </summary>
+public abstract class PipelineVisualFactoryBase : AttachedVisualFactoryBase
+{
+    /// <inheritdoc/>
+    public override async ValueTask<Visual> GetAttachedVisualAsync(UIElement element)
+    {
+        var visual = ElementCompositionPreview.GetElementVisual(element).Compositor.CreateSpriteVisual();
+
+        visual.Brush = await OnPipelineRequested().BuildAsync();
+
+        return visual;
+    }
+
+    /// <summary>
+    /// A method that builds and returns the <see cref="PipelineBuilder"/> pipeline to use in the current instance.
+    /// </summary>
+    /// <returns>A <see cref="PipelineBuilder"/> instance to create the <see cref="Visual"/> to display.</returns>
+    protected abstract PipelineBuilder OnPipelineRequested();
+}
diff --git a/components/Media/tests/Media.Tests.projitems b/components/Media/tests/Media.Tests.projitems
new file mode 100644
index 00000000..f6bb5968
--- /dev/null
+++ b/components/Media/tests/Media.Tests.projitems
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>29B0EAEF-DDC3-461E-BE63-4052976510E8</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>MediaExperiment.Tests</Import_RootNamespace>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/components/Media/tests/Media.Tests.shproj b/components/Media/tests/Media.Tests.shproj
new file mode 100644
index 00000000..a14f5f00
--- /dev/null
+++ b/components/Media/tests/Media.Tests.shproj
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>29B0EAEF-DDC3-461E-BE63-4052976510E8</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="Media.Tests.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>
diff --git a/tooling b/tooling
index a852f23d..38728ba6 160000
--- a/tooling
+++ b/tooling
@@ -1 +1 @@
-Subproject commit a852f23dabb110b7a51c068662309d00834d90a1
+Subproject commit 38728ba616661853b3bbcec9269ab1e362daa72a