From f5d45f808316557418c17e88de4da6558e6b71af Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Wed, 5 Jul 2023 14:24:53 -0500 Subject: [PATCH 01/13] Initial port of CommunityToolkit.WinUI.Media to 8.x --- .../src/ArgumentNullExceptionExtensions.cs | 61 ++ ...KeyFrameAnimationBuilder{T}.Composition.cs | 4 +- .../CommunityToolkit.WinUI.Animations.csproj | 2 + .../Abstract/Animation{TValue,TKeyFrame}.cs | 4 +- .../CustomAnimation{TValue,TKeyFrame}.cs | 4 +- .../ImplicitAnimation{TValue,TKeyFrame}.cs | 2 + .../src/Xaml/Default/ClipAnimation.cs | 4 +- components/Media/OpenSolution.bat | 3 + components/Media/samples/Assets/icon.png | Bin 0 -> 6192 bytes components/Media/samples/Dependencies.props | 31 + components/Media/samples/Media.Samples.csproj | 8 + components/Media/samples/Media.md | 15 + .../Media/src/AdditionalAssemblyInfo.cs | 13 + ...fectAnimation{TEffect,TValue,TKeyFrame}.cs | 82 ++ .../src/Animations/BlurEffectAnimation.cs | 22 + .../src/Animations/ColorEffectAnimation.cs | 23 + .../Animations/CrossFadeEffectAnimation.cs | 22 + .../src/Animations/ExposureEffectAnimation.cs | 22 + .../Animations/HueRotationEffectAnimation.cs | 22 + .../src/Animations/OpacityEffectAnimation.cs | 22 + .../Animations/SaturationEffectAnimation.cs | 22 + .../src/Animations/SepiaEffectAnimation.cs | 22 + components/Media/src/Brushes/AcrylicBrush.cs | 222 +++++ .../Media/src/Brushes/BackdropBlurBrush.cs | 64 ++ .../src/Brushes/BackdropGammaTransferBrush.cs | 406 +++++++++ .../Media/src/Brushes/BackdropInvertBrush.cs | 21 + .../src/Brushes/BackdropSaturationBrush.cs | 75 ++ .../Media/src/Brushes/BackdropSepiaBrush.cs | 75 ++ .../Base/XamlCompositionEffectBrushBase.cs | 143 +++ .../Media/src/Brushes/CanvasBrushBase.cs | 149 ++++ .../Media/src/Brushes/ImageBlendBrush.cs | 214 +++++ components/Media/src/Brushes/PipelineBrush.cs | 69 ++ .../Brushes/RadialGradientBrush.Properties.cs | 131 +++ .../Media/src/Brushes/RadialGradientBrush.cs | 103 +++ .../src/Brushes/RadialGradientBrushInterop.cs | 95 ++ components/Media/src/Brushes/TilesBrush.cs | 75 ++ .../Media/src/Brushes/XamlCompositionBrush.cs | 93 ++ .../src/CommunityToolkit.WinUI.Media.csproj | 17 + components/Media/src/Dependencies.props | 22 + .../Abstract/ImageSourceBaseExtension.cs | 29 + .../src/Effects/Abstract/PipelineEffect.cs | 41 + components/Media/src/Effects/BlendEffect.cs | 66 ++ components/Media/src/Effects/BlurEffect.cs | 47 + .../Media/src/Effects/CrossFadeEffect.cs | 82 ++ .../Media/src/Effects/ExposureEffect.cs | 47 + .../Extensions/AcrylicSourceExtension.cs | 69 ++ .../Extensions/BackdropSourceExtension.cs | 33 + .../Extensions/ImageSourceExtension.cs | 20 + .../Extensions/SolidColorSourceExtension.cs | 26 + .../Effects/Extensions/TileSourceExtension.cs | 21 + .../Media/src/Effects/GrayscaleEffect.cs | 20 + .../Media/src/Effects/HueRotationEffect.cs | 41 + .../src/Effects/Interfaces/IPipelineEffect.cs | 39 + components/Media/src/Effects/InvertEffect.cs | 20 + .../src/Effects/LuminanceToAlphaEffect.cs | 20 + components/Media/src/Effects/OpacityEffect.cs | 47 + .../Media/src/Effects/SaturationEffect.cs | 47 + components/Media/src/Effects/SepiaEffect.cs | 47 + components/Media/src/Effects/ShadeEffect.cs | 36 + .../src/Effects/TemperatureAndTintEffect.cs | 42 + components/Media/src/Effects/TintEffect.cs | 42 + components/Media/src/Enums/AlphaMode.cs | 21 + components/Media/src/Enums/CacheMode.cs | 26 + components/Media/src/Enums/DpiMode.cs | 31 + components/Media/src/Enums/ImageBlendMode.cs | 61 ++ .../Media/src/Enums/InnerContentClipMode.cs | 32 + components/Media/src/Enums/Placement.cs | 23 + .../GenericExtensions.cs | 53 ++ .../System.Threading.Tasks/AsyncMutex.cs | 58 ++ .../src/Extensions/System/UriExtensions.cs | 47 + .../src/Extensions/UIElementExtensions.cs | 65 ++ .../CompositionObjectExtensions.cs | 91 ++ .../CanvasDrawingSessionExtensions.cs | 305 +++++++ .../Geometry/CanvasPathBuilderExtensions.cs | 458 ++++++++++ .../Media/src/Geometry/CanvasPathGeometry.cs | 137 +++ components/Media/src/Geometry/CanvasStroke.cs | 111 +++ .../Geometry/CompositorGeometryExtensions.cs | 97 +++ .../src/Geometry/Core/CanvasRoundRect.cs | 278 ++++++ .../Geometry/Core/GeometryTypeDefinitions.cs | 58 ++ .../src/Geometry/Core/PathElementFactory.cs | 154 ++++ .../Media/src/Geometry/Core/RegexFactory.cs | 599 +++++++++++++ .../Media/src/Geometry/CultureShield.cs | 57 ++ .../Brush/AbstractCanvasBrushElement.cs | 70 ++ .../Elements/Brush/ICanvasBrushElement.cs | 38 + .../Brush/LinearGradientBrushElement.cs | 210 +++++ .../Brush/LinearGradientHdrBrushElement.cs | 207 +++++ .../Brush/RadialGradientBrushElement.cs | 233 +++++ .../Brush/RadialGradientHdrBrushElement.cs | 233 +++++ .../Elements/Brush/SolidColorBrushElement.cs | 72 ++ .../Elements/Path/AbstractPathElement.cs | 105 +++ .../src/Geometry/Elements/Path/ArcElement.cs | 98 +++ .../Elements/Path/CanvasEllipseFigure.cs | 80 ++ .../Elements/Path/CanvasPathFigure.cs | 132 +++ .../Elements/Path/CanvasPolygonFigure.cs | 82 ++ .../Elements/Path/CanvasRectangleFigure.cs | 87 ++ .../Path/CanvasRoundRectangleFigure.cs | 101 +++ .../Elements/Path/ClosePathElement.cs | 99 +++ .../Elements/Path/CubicBezierElement.cs | 103 +++ .../Geometry/Elements/Path/FillRuleElement.cs | 75 ++ .../Elements/Path/HorizontalLineElement.cs | 67 ++ .../Elements/Path/ICanvasPathElement.cs | 63 ++ .../src/Geometry/Elements/Path/LineElement.cs | 72 ++ .../Geometry/Elements/Path/MoveToElement.cs | 85 ++ .../Elements/Path/QuadraticBezierElement.cs | 95 ++ .../Elements/Path/SmoothCubicBezierElement.cs | 115 +++ .../Path/SmoothQuadraticBezierElement.cs | 104 +++ .../Elements/Path/VerticalLineElement.cs | 67 ++ .../Stroke/AbstractCanvasStrokeElement.cs | 64 ++ .../Elements/Stroke/CanvasStrokeElement.cs | 105 +++ .../Stroke/CanvasStrokeStyleElement.cs | 177 ++++ .../Elements/Stroke/ICanvasStrokeElement.cs | 35 + .../Stroke/ICanvasStrokeStyleElement.cs | 34 + .../Media/src/Geometry/ICanvasStroke.cs | 35 + .../src/Geometry/Parsers/CanvasBrushParser.cs | 122 +++ .../Geometry/Parsers/CanvasGeometryParser.cs | 134 +++ .../Geometry/Parsers/CanvasStrokeParser.cs | 96 +++ .../Parsers/CanvasStrokeStyleParser.cs | 90 ++ .../Media/src/Geometry/Parsers/ColorParser.cs | 147 ++++ components/Media/src/Geometry/Scalar.cs | 61 ++ components/Media/src/Geometry/Utils.cs | 815 ++++++++++++++++++ .../CompositionObjectCache{TKey,TValue}.cs | 76 ++ .../Cache/CompositionObjectCache{T}.cs | 50 ++ .../src/Helpers/SurfaceLoader.Instance.cs | 229 +++++ components/Media/src/Helpers/SurfaceLoader.cs | 150 ++++ components/Media/src/MultiTarget.props | 9 + .../Media/src/Pipelines/BrushProvider.cs | 67 ++ .../PipelineBuilder.Effects.Internals.cs | 218 +++++ .../src/Pipelines/PipelineBuilder.Effects.cs | 693 +++++++++++++++ .../PipelineBuilder.Initialization.cs | 327 +++++++ .../src/Pipelines/PipelineBuilder.Merge.cs | 160 ++++ .../src/Pipelines/PipelineBuilder.Prebuilt.cs | 214 +++++ .../Media/src/Pipelines/PipelineBuilder.cs | 224 +++++ .../src/Visuals/AttachedVisualFactoryBase.cs | 24 + .../src/Visuals/PipelineVisualFactory.cs | 79 ++ .../src/Visuals/PipelineVisualFactoryBase.cs | 37 + components/Media/tests/Media.Tests.projitems | 11 + components/Media/tests/Media.Tests.shproj | 13 + tooling | 2 +- 138 files changed, 13211 insertions(+), 8 deletions(-) create mode 100644 components/Animations/src/ArgumentNullExceptionExtensions.cs create mode 100644 components/Media/OpenSolution.bat create mode 100644 components/Media/samples/Assets/icon.png create mode 100644 components/Media/samples/Dependencies.props create mode 100644 components/Media/samples/Media.Samples.csproj create mode 100644 components/Media/samples/Media.md create mode 100644 components/Media/src/AdditionalAssemblyInfo.cs create mode 100644 components/Media/src/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs create mode 100644 components/Media/src/Animations/BlurEffectAnimation.cs create mode 100644 components/Media/src/Animations/ColorEffectAnimation.cs create mode 100644 components/Media/src/Animations/CrossFadeEffectAnimation.cs create mode 100644 components/Media/src/Animations/ExposureEffectAnimation.cs create mode 100644 components/Media/src/Animations/HueRotationEffectAnimation.cs create mode 100644 components/Media/src/Animations/OpacityEffectAnimation.cs create mode 100644 components/Media/src/Animations/SaturationEffectAnimation.cs create mode 100644 components/Media/src/Animations/SepiaEffectAnimation.cs create mode 100644 components/Media/src/Brushes/AcrylicBrush.cs create mode 100644 components/Media/src/Brushes/BackdropBlurBrush.cs create mode 100644 components/Media/src/Brushes/BackdropGammaTransferBrush.cs create mode 100644 components/Media/src/Brushes/BackdropInvertBrush.cs create mode 100644 components/Media/src/Brushes/BackdropSaturationBrush.cs create mode 100644 components/Media/src/Brushes/BackdropSepiaBrush.cs create mode 100644 components/Media/src/Brushes/Base/XamlCompositionEffectBrushBase.cs create mode 100644 components/Media/src/Brushes/CanvasBrushBase.cs create mode 100644 components/Media/src/Brushes/ImageBlendBrush.cs create mode 100644 components/Media/src/Brushes/PipelineBrush.cs create mode 100644 components/Media/src/Brushes/RadialGradientBrush.Properties.cs create mode 100644 components/Media/src/Brushes/RadialGradientBrush.cs create mode 100644 components/Media/src/Brushes/RadialGradientBrushInterop.cs create mode 100644 components/Media/src/Brushes/TilesBrush.cs create mode 100644 components/Media/src/Brushes/XamlCompositionBrush.cs create mode 100644 components/Media/src/CommunityToolkit.WinUI.Media.csproj create mode 100644 components/Media/src/Dependencies.props create mode 100644 components/Media/src/Effects/Abstract/ImageSourceBaseExtension.cs create mode 100644 components/Media/src/Effects/Abstract/PipelineEffect.cs create mode 100644 components/Media/src/Effects/BlendEffect.cs create mode 100644 components/Media/src/Effects/BlurEffect.cs create mode 100644 components/Media/src/Effects/CrossFadeEffect.cs create mode 100644 components/Media/src/Effects/ExposureEffect.cs create mode 100644 components/Media/src/Effects/Extensions/AcrylicSourceExtension.cs create mode 100644 components/Media/src/Effects/Extensions/BackdropSourceExtension.cs create mode 100644 components/Media/src/Effects/Extensions/ImageSourceExtension.cs create mode 100644 components/Media/src/Effects/Extensions/SolidColorSourceExtension.cs create mode 100644 components/Media/src/Effects/Extensions/TileSourceExtension.cs create mode 100644 components/Media/src/Effects/GrayscaleEffect.cs create mode 100644 components/Media/src/Effects/HueRotationEffect.cs create mode 100644 components/Media/src/Effects/Interfaces/IPipelineEffect.cs create mode 100644 components/Media/src/Effects/InvertEffect.cs create mode 100644 components/Media/src/Effects/LuminanceToAlphaEffect.cs create mode 100644 components/Media/src/Effects/OpacityEffect.cs create mode 100644 components/Media/src/Effects/SaturationEffect.cs create mode 100644 components/Media/src/Effects/SepiaEffect.cs create mode 100644 components/Media/src/Effects/ShadeEffect.cs create mode 100644 components/Media/src/Effects/TemperatureAndTintEffect.cs create mode 100644 components/Media/src/Effects/TintEffect.cs create mode 100644 components/Media/src/Enums/AlphaMode.cs create mode 100644 components/Media/src/Enums/CacheMode.cs create mode 100644 components/Media/src/Enums/DpiMode.cs create mode 100644 components/Media/src/Enums/ImageBlendMode.cs create mode 100644 components/Media/src/Enums/InnerContentClipMode.cs create mode 100644 components/Media/src/Enums/Placement.cs create mode 100644 components/Media/src/Extensions/System.Collections.Generic/GenericExtensions.cs create mode 100644 components/Media/src/Extensions/System.Threading.Tasks/AsyncMutex.cs create mode 100644 components/Media/src/Extensions/System/UriExtensions.cs create mode 100644 components/Media/src/Extensions/UIElementExtensions.cs create mode 100644 components/Media/src/Extensions/Windows.UI.Composition/CompositionObjectExtensions.cs create mode 100644 components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs create mode 100644 components/Media/src/Geometry/CanvasPathBuilderExtensions.cs create mode 100644 components/Media/src/Geometry/CanvasPathGeometry.cs create mode 100644 components/Media/src/Geometry/CanvasStroke.cs create mode 100644 components/Media/src/Geometry/CompositorGeometryExtensions.cs create mode 100644 components/Media/src/Geometry/Core/CanvasRoundRect.cs create mode 100644 components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs create mode 100644 components/Media/src/Geometry/Core/PathElementFactory.cs create mode 100644 components/Media/src/Geometry/Core/RegexFactory.cs create mode 100644 components/Media/src/Geometry/CultureShield.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/ArcElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs create mode 100644 components/Media/src/Geometry/Elements/Path/ClosePathElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/FillRuleElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/LineElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/MoveToElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs create mode 100644 components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs create mode 100644 components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs create mode 100644 components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs create mode 100644 components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs create mode 100644 components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs create mode 100644 components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs create mode 100644 components/Media/src/Geometry/ICanvasStroke.cs create mode 100644 components/Media/src/Geometry/Parsers/CanvasBrushParser.cs create mode 100644 components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs create mode 100644 components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs create mode 100644 components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs create mode 100644 components/Media/src/Geometry/Parsers/ColorParser.cs create mode 100644 components/Media/src/Geometry/Scalar.cs create mode 100644 components/Media/src/Geometry/Utils.cs create mode 100644 components/Media/src/Helpers/Cache/CompositionObjectCache{TKey,TValue}.cs create mode 100644 components/Media/src/Helpers/Cache/CompositionObjectCache{T}.cs create mode 100644 components/Media/src/Helpers/SurfaceLoader.Instance.cs create mode 100644 components/Media/src/Helpers/SurfaceLoader.cs create mode 100644 components/Media/src/MultiTarget.props create mode 100644 components/Media/src/Pipelines/BrushProvider.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.Effects.Internals.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.Effects.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.Initialization.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.Merge.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.Prebuilt.cs create mode 100644 components/Media/src/Pipelines/PipelineBuilder.cs create mode 100644 components/Media/src/Visuals/AttachedVisualFactoryBase.cs create mode 100644 components/Media/src/Visuals/PipelineVisualFactory.cs create mode 100644 components/Media/src/Visuals/PipelineVisualFactoryBase.cs create mode 100644 components/Media/tests/Media.Tests.projitems create mode 100644 components/Media/tests/Media.Tests.shproj diff --git a/components/Animations/src/ArgumentNullExceptionExtensions.cs b/components/Animations/src/ArgumentNullExceptionExtensions.cs new file mode 100644 index 00000000..640cc2ec --- /dev/null +++ b/components/Animations/src/ArgumentNullExceptionExtensions.cs @@ -0,0 +1,61 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace System; + +/// +/// Throw helper extensions for . +/// +internal static class ArgumentNullExceptionExtensions +{ + /// + /// Throws an for a given parameter name. + /// + /// Dummy value to invoke the extension upon (always pass . + /// The name of the parameter to report in the exception. + /// Thrown with . + [DoesNotReturn] + public static void Throw(this ArgumentNullException? _, string? parameterName) + { + throw new ArgumentNullException(parameterName); + } + + /// + /// Throws an if is . + /// + /// Dummy value to invoke the extension upon (always pass . + /// The reference type argument to validate as non-. + /// The name of the parameter with which corresponds. + /// Thrown if is . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNull(this ArgumentNullException? _, [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? parameterName = null) + { + if (argument is null) + { + Throw(parameterName); + } + } + + /// + /// Throws an if is . + /// + /// Dummy value to invoke the extension upon (always pass . + /// The pointer argument to validate as non-. + /// The name of the parameter with which corresponds. + /// Thrown if is . + [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); + } + } + + /// + [DoesNotReturn] + private static void Throw(string? parameterName) + { + throw new ArgumentNullException(parameterName); + } +} 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 69216741..11a81481 100644 --- a/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj +++ b/components/Animations/src/CommunityToolkit.WinUI.Animations.csproj @@ -11,6 +11,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive 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> KeyFrames /// /// Gets the explicit target for the animation. This is the primary target property that is animated. /// - protected abstract string ExplicitTarget { get; } + protected abstract string? ExplicitTarget { get; } /// public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) { + default(ArgumentNullException).ThrowIfNull(ExplicitTarget); + return builder.NormalizedKeyFrames 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 92d6fa0d..6dbdb9c5 100644 --- a/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs +++ b/components/Animations/src/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs @@ -28,11 +28,13 @@ public abstract class CustomAnimation : ImplicitAnimation - protected override string ExplicitTarget => Target!; + protected override string? ExplicitTarget => Target; /// public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) { + default(ArgumentNullException).ThrowIfNull(ExplicitTarget); + return builder.NormalizedKeyFrames 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() /// public CompositionAnimation GetAnimation(UIElement element, out string? target) { + default(ArgumentNullException).ThrowIfNull(ExplicitTarget); + NormalizedKeyFrameAnimationBuilder.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 { /// -#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; /// public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) 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 0000000000000000000000000000000000000000..5f574ceca233597ecc5d775fd2f516c0d9685cfa GIT binary patch literal 6192 zcmV-07|-X4P)(A#0FSU0V~+BLKO>0TE|I) z+7v-aRYc9N|<-MF- zi1b%r@3F{l^-$8!0R-@AuNN68cbxCgtQtH~7r^fAZ0@y8>k33}5Dv?5~4wfAB#-0ASbA8OC7v zdvB0BF+HH3LzI&{1NSS$#1$BHAZ^$ImXFJa&|Ht|TD5@H>%^+bwcx_$VDO2p zAaz>-D&y|21)R8kV4+z03WI%ydM}-j-7mfjOROZC6tq#GA7cP2XmPy#5KOZbFFF}0 zr3Ka&NUurFCG<@*2IF)y2RX4cOt$+&PKovnH#fh$6^|io=hR&>XIoJJJV);FqQA5o zLW5>th!&1EfZj1V_(VC#W#`8^R2dX&5+8ZE34m>w*KxJuRz^CAe430IF_=m@M;pqm zy8@&K3w4$LKrL0l2OuvS3}*P%@NyPL~F(Dl+_< zxE_IpRuhh~AociWTqVbp)MpH~EQ+x;SqKRF;bDj9$``VkRBAOE;PDNO*!l{` zLQ(o5@|0LA)8l-i)a$ej4mv_*J9c1UnpjR1VkUsw!Yj~@)paq4%sdfRc?+9(j7Y#m z>=+@tw$QndMVh1{Sz$D0CaM_y;uFrqY@nixtL)orhC`$z6t{RSQ`v9danJrO0wwZW*r)G4nrFBGDzsLFQJ^Q+qY}aE}mInA2zy2XCo0~lF ziTsYzYzmtVWx5JJR}<+PryeS`6qsxRUHJf(7e~Fp_a+DZ3EMGvmCx(MX)Ca>^R??a zePE~&@Cp*a;+PNSG1Iss7<4u-a@*w+p^-98h$VB>mxPYUcc916mka;zale4o#uit^(5Kx1N>kkR zS72rT+%{IREFcce-&5%`&nJvK5NPL8L!>P$+~|&o8CaEC$A$wIBT$R&Ls-fF9aE z4<~--SK;nw{~OM|!v}Rk^C@V~He9ou-FX5a2dNG~xMx|et;(=jKnAAHGsko#Er7ZjJR#Gkpl|r@6XVkpJ(l14I(muTv-~!iTe)Y^1{Hlh65-#WXzcx z-H_~k$6o`IFX~2oDu*P0MlviMf*ywMruGzRBZFBOoQgr}PGgYTv?|_SplYp~PM(4>o+p}HqH|>d?l}Ge zui%AD-G>>ZllLEjWsV3om>9JcBtvxu=v1%XtJK3gvLbFiv}K63_LnMXLiy37n6 zCi7n7uo7_2zB5FcnMWGTdIU6*AeuBOzQ{oNPbUKj^fdvZK)o5b6`6jcVpL7WX(_>% z8IGAr3((Cj;`-(y-F{3!x|qiAFl)rA2M)u^98X%pP3t&|m=aktn5*|M>2dVCCW!(J zGM-vJpbj~}apu6;cbQEydx?g8&~Rv=2ZJDd6mHR)Sbf5&7zEz%4)kTYOPDAhYc&2U zs5(D{!3c_ErnyC2+n8r9_;+PeyhM|pf9R)4K!Om^2p_i>Rhw)qQTE93%gn%2M-TFK zM#-GDH!r~*Pkx`)-WwClEHRsBd8#8dAeO~KOAAKdCRvlf5cWU67K5O%5foiP3x^^e zWar`9>D;TqL9I^yQf!IKr8^b zk@JPfjAohx%$yG^b81+Fe-M8|1<0OakU>l)Xr%_BmObW>mjp#kA=Rv90pT@JfHnm{;9DalJ&BpK z;%ONvMK#XBFpyMzl&mOgl7$=bO0Th*yzTLCFq1C0K8Pj_@#F&sQ8Y$DGV#!CcY4*B zW1$($`97?_Ct@)E&1Bguqb zI<_>Q3oBa%=ncu7$&?{hvKY&Iqz3g|X+s|MdF}prv@ueUz(5*|86>U|7^JgT7U1^J zeN)Y3T$`OSp8BnipgCl4=3LgW5^!*Kfvb7RRwGYQ*rsT3!4)oJFv6;NZOg<10hnia zd6A8BfZYKKI#e(cFu%SYl(kqEp7CBY8F?KC;f&oba|J(OO_8?6jDW;t$>+Y+YfT7a zTISrL%$Z>mxPYQLQspOUbR1Q4G6g#o!X$>FQ;eF8$(#g4@jaCf0^Mcw{BEo7#bhbD zSQL{mis>^pY$C0&Es;YFJm1#z>qxd?$IWF4A=gZ=eJ~Hd|CKY;&pP(?2VeaGSt?(U zeh}YgXg;eIM@zC;nWRFaU%?_DYrd~xKyFt^cU5tkhMDJZvTvAfp}0y%YV1v>DL*3o zO7mPwlq@=xiOgwW*@tVULpSY(V-Nol_VvPkKDK%*EZ?}qrQTSbr>suvSx_&C%!@&S_qTTPgq8$PDQ0)+%chXASF z-B@O=Ir-?vVP)TJf>T=*Y$o@O@x-Ga<4)noI<~2bfG)@$>)w8WcxXw&e9~i(i=~H> zTOk+Fz|}%F+m;a&`AcdR3`TFWlasQ`it0X29UlfmlK*87wv`JSdZ6e*wkaC0EL7}>_ zzbim4y3PRXiFAyuXlJe!a4>^dFxnXI_e7h(qcF}@=X`SY0JSZPH5TN%j(rziJYV3R z$GIcOwN?oC3N!8N58R9g7Z{+jfxVTgF;2mxU_z7PLzK?{2+p`zft}Mab z$G*+GCDj51NbIFL{e`NV|M*t2Pp9X7EGf-$qtW<`dRTfr^p&1(yoh? zrm$3b;FXrJI>|!Ingc<739GxGI>UwMn52gc(k5kpB=r@HM!ZiZKyg{ZX7c{y-+@-R zCHsB#(>H0&CQPzNXCl?Q08`HcIqfTO$r#FPGo6!e9)vXZK=>HQR)=BG4#08(HEB~H zfE9t|>~o5l6Ag9&$HMB2^mDYz#}V&}aI?!*60OHQvQ`N*X?0LL zw;vD8Y13S_NL_NN&b(d4+#QIG$oxACm{O$O?4{SJJE3*cFS2R%8CjFaV~CqH}LHOyZr@ zzq@xoO1KG=s4C^IBE+Iz^8T_$7)IIR8~xUVE4ezxyom~O zfHRp?=G^;*S9ISBx!HvZ&e(^F=RGn;=2_C=dGR-v0zS~gxN%Ojkdwz3?9{%DP z4Y%-R2Uzo-IdU@`nBSt4&ZueaL zR4J+a$WyNX3o9&c2rbo?YUVtn#f`z@kom+Qsco|l$m=Ih*)>zy^V5>$sI7A}i*Vm2!-@xaue3?uW&p;s z*bT<6S|XlOKW&P!V+Xtm^298EXAD?W#q7y@&O)8`YU7xRdK@V$P-1^_1-%Xvyt#+Q z?O1l<=RcT*N5Awkz5do^X!WrI5Z+{Fp`n`~rC*OxKbsfT`_b0G8(`}v1j)Ut@0IKdlZDz&qw}ReYqb@`R`C9i^ z|HBtQ$05IE>dFFd(npy@Q`KTo6{|-Gk)$e|wiW~Uvy=a$DRv^eq0NBWLC2O$>21u} zSDY6Q7;Jsepw}(f@_A)o|F|}+g5ek6{Q#c+)(`ooK^tqBKFG6Jhp^-R{0@>>@1GfT9-(+QV-CB<0!wwRd+9XLXY8iEx@GeFldM z$Zl|eHFv{%#j;|ZeW!S?#O$%L!;D%TL+FQ<1ek}Dfn&zUhvR<~tkVww8;}JPY(Ec0 zkursMLj)u)SHwm&-Ojpf=EIjza2G$q{dkk zZ2kmDQ>5>7qWw8LMXvQf=85~ajc_Lwc;?A9%yoCA)o1ZBcTj{iomvdAXDdrOW~sx_ z`?fCHQvgKC*%ak0UhQuU&HGDZi}$Hmj=)=K83y(PswqQ%@C|xK%u08vIoMkiMjgvK zrCIGLUj0uKzzrW^^jO=)1z}$cF;bqNJi+VL1wBD zXvFjtU>rDXq(+-e#PS+<+lJ z1xhVODF#oTUQ15Yq{zz~dl`u7$MWe6zc*wrDSA6TS&P0xlqNBgM7xWMO-;7_wNpRn zRT~gM)fJ+cG^L@VoNSjBxVJYQ;8azg5JvY+B_#HdDyYypUcaCI#Tj@*-h`TBC&5V- zyBQ3&F)eNiuKEx@DhpxEFxf(x6T1U6gh!1<0fJGlx>f%SvPtA8mXJe?!ZRkFjHxcN z+5If(Vy!TRtU;>m5m78!=mkafPxVPCs#y~p)L7hlPuEn}5DJDPUhKNL&+HD+XFu^8 zY{D<{=_lHYH$q+ex>VJ-*^{!R(}uXc@8Y}!Cl<9>+sreRT&L@Wpg$D`)Z*(v84EhB zV5PsIge#I<1SG}%ZQ*qpF7W>^47%B0c>jq{o`vhL>#yst>#v`n*Z%>kPgzNgtS&GB O0000 + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + Media + + + + + diff --git a/components/Media/samples/Media.md b/components/Media/samples/Media.md new file mode 100644 index 00000000..95478ac4 --- /dev/null +++ b/components/Media/samples/Media.md @@ -0,0 +1,15 @@ +--- +title: Media +author: githubaccount +description: TODO: Your experiment's description here +keywords: Media +dev_langs: + - csharp +category: Animations +subcategory: Media +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + +TODO: Fill in information about this experiment and how to get started here... 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; + +/// +/// A custom animation targeting a property on an instance. +/// +/// The type of effect to animate. +/// +/// The type to use for the public and +/// properties. This can differ from to facilitate XAML parsing. +/// +/// The actual type of keyframe values in use. +public abstract class EffectAnimation : Animation + where TEffect : class, IPipelineEffect + where TKeyFrame : unmanaged +{ + /// + /// Gets or sets the linked instance to animate. + /// + public TEffect? Target + { + get => (TEffect?)GetValue(TargetProperty); + set => SetValue(TargetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetProperty = DependencyProperty.Register( + nameof(Target), + typeof(TEffect), + typeof(EffectAnimation), + new PropertyMetadata(null)); + + /// + 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.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; + +/// +/// An effect animation that targets . +/// +public sealed class BlurEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class ColorEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class CrossFadeEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class ExposureEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class HueRotationEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class OpacityEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class SaturationEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// An effect animation that targets . +/// +public sealed class SepiaEffectAnimation : EffectAnimation +{ + /// + protected override string? ExplicitTarget => Target?.Id; + + /// + 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; + +/// +/// A that implements an acrylic effect with customizable parameters +/// +public sealed class AcrylicBrush : XamlCompositionEffectBrushBase +{ + /// + /// The instance in use to set the blur amount + /// + /// This is only set when is + private EffectSetter? blurAmountSetter; + + /// + /// The instance in use to set the tint color + /// + private EffectSetter? tintColorSetter; + + /// + /// The instance in use to set the tint mix amount + /// + private EffectSetter? tintOpacitySetter; + + /// + /// Gets or sets the background source mode for the effect (the default is ). + /// + public AcrylicBackgroundSource BackgroundSource + { + get => (AcrylicBackgroundSource)GetValue(BackgroundSourceProperty); + set => SetValue(BackgroundSourceProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BackgroundSourceProperty = DependencyProperty.Register( + nameof(BackgroundSource), + typeof(AcrylicBackgroundSource), + typeof(AcrylicBrush), + new PropertyMetadata(AcrylicBackgroundSource.Backdrop, OnSourcePropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + /// Gets or sets the blur amount for the effect (must be a positive value) + /// + /// This property is ignored when the active mode is + public double BlurAmount + { + get => (double)GetValue(BlurAmountProperty); + set => SetValue(BlurAmountProperty, Math.Max(value, 0)); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlurAmountProperty = DependencyProperty.Register( + nameof(BlurAmount), + typeof(double), + typeof(AcrylicBrush), + new PropertyMetadata(0.0, OnBlurAmountPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + /// Gets or sets the tint for the effect + /// + public Color TintColor + { + get => (Color)GetValue(TintColorProperty); + set => SetValue(TintColorProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TintColorProperty = DependencyProperty.Register( + nameof(TintColor), + typeof(Color), + typeof(AcrylicBrush), + new PropertyMetadata(default(Color), OnTintColorPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + /// Gets or sets the tint opacity factor for the effect (default is 0.5, must be in the [0, 1] range) + /// + public double TintOpacity + { + get => (double)GetValue(TintOpacityProperty); + set => SetValue(TintOpacityProperty, Math.Clamp(value, 0, 1)); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TintOpacityProperty = DependencyProperty.Register( + nameof(TintOpacity), + typeof(double), + typeof(AcrylicBrush), + new PropertyMetadata(0.5, OnTintOpacityPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + /// Gets or sets the for the texture to use + /// + public Uri TextureUri + { + get => (Uri)GetValue(TextureUriProperty); + set => SetValue(TextureUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register( + nameof(TextureUri), + typeof(Uri), + typeof(AcrylicBrush), + new PropertyMetadata(default, OnTextureUriPropertyChanged)); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + private static void OnTextureUriPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AcrylicBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + 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; + +/// +/// The is a that blurs whatever is behind it in the application. +/// +public class BackdropBlurBrush : XamlCompositionEffectBrushBase +{ + /// + /// The instance currently in use + /// + private EffectSetter? amountSetter; + + /// + /// Gets or sets the amount of gaussian blur to apply to the background. + /// + public double Amount + { + get => (double)GetValue(AmountProperty); + set => SetValue(AmountProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AmountProperty = DependencyProperty.Register( + nameof(Amount), + typeof(double), + typeof(BackdropBlurBrush), + new PropertyMetadata(0.0, new PropertyChangedCallback(OnAmountChanged))); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + 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; + +/// +/// 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. +/// +public class BackdropGammaTransferBrush : XamlCompositionBrushBase +{ + /// + /// Gets or sets the amount of scale to apply to the alpha chennel. + /// + public double AlphaAmplitude + { + get => (double)GetValue(AlphaAmplitudeProperty); + set => SetValue(AlphaAmplitudeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AlphaAmplitudeProperty = DependencyProperty.Register( + nameof(AlphaAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaAmplitude)))); + + /// + /// Gets or sets a value indicating whether to disable alpha transfer. + /// + public bool AlphaDisable + { + get => (bool)GetValue(AlphaDisableProperty); + set => SetValue(AlphaDisableProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AlphaDisableProperty = DependencyProperty.Register( + nameof(AlphaDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(AlphaDisable)))); + + /// + /// Gets or sets the amount of scale to apply to the alpha chennel. + /// + public double AlphaExponent + { + get => (double)GetValue(AlphaExponentProperty); + set => SetValue(AlphaExponentProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AlphaExponentProperty = DependencyProperty.Register( + nameof(AlphaExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(AlphaExponent)))); + + /// + /// Gets or sets the amount of scale to apply to the alpha chennel. + /// + public double AlphaOffset + { + get => (double)GetValue(AlphaOffsetProperty); + set => SetValue(AlphaOffsetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AlphaOffsetProperty = DependencyProperty.Register( + nameof(AlphaOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(AlphaOffset)))); + + /// + /// Gets or sets the amount of scale to apply to the Blue chennel. + /// + public double BlueAmplitude + { + get => (double)GetValue(BlueAmplitudeProperty); + set => SetValue(BlueAmplitudeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlueAmplitudeProperty = DependencyProperty.Register( + nameof(BlueAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueAmplitude)))); + + /// + /// Gets or sets a value indicating whether to disable Blue transfer. + /// + public bool BlueDisable + { + get => (bool)GetValue(BlueDisableProperty); + set => SetValue(BlueDisableProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlueDisableProperty = DependencyProperty.Register( + nameof(BlueDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(BlueDisable)))); + + /// + /// Gets or sets the amount of scale to apply to the Blue chennel. + /// + public double BlueExponent + { + get => (double)GetValue(BlueExponentProperty); + set => SetValue(BlueExponentProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlueExponentProperty = DependencyProperty.Register( + nameof(BlueExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(BlueExponent)))); + + /// + /// Gets or sets the amount of scale to apply to the Blue chennel. + /// + public double BlueOffset + { + get => (double)GetValue(BlueOffsetProperty); + set => SetValue(BlueOffsetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty BlueOffsetProperty = DependencyProperty.Register( + nameof(BlueOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(BlueOffset)))); + + /// + /// Gets or sets the amount of scale to apply to the Green chennel. + /// + public double GreenAmplitude + { + get => (double)GetValue(GreenAmplitudeProperty); + set => SetValue(GreenAmplitudeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GreenAmplitudeProperty = DependencyProperty.Register( + nameof(GreenAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenAmplitude)))); + + /// + /// Gets or sets a value indicating whether to disable Green transfer. + /// + public bool GreenDisable + { + get => (bool)GetValue(GreenDisableProperty); + set => SetValue(GreenDisableProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GreenDisableProperty = DependencyProperty.Register( + nameof(GreenDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(GreenDisable)))); + + /// + /// Gets or sets the amount of scale to apply to the Green chennel. + /// + public double GreenExponent + { + get => (double)GetValue(GreenExponentProperty); + set => SetValue(GreenExponentProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GreenExponentProperty = DependencyProperty.Register( + nameof(GreenExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(GreenExponent)))); + + /// + /// Gets or sets the amount of scale to apply to the Green chennel. + /// + public double GreenOffset + { + get => (double)GetValue(GreenOffsetProperty); + set => SetValue(GreenOffsetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GreenOffsetProperty = DependencyProperty.Register( + nameof(GreenOffset), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(0.0, OnScalarPropertyChangedHelper(nameof(GreenOffset)))); + + /// + /// Gets or sets the amount of scale to apply to the Red chennel. + /// + public double RedAmplitude + { + get => (double)GetValue(RedAmplitudeProperty); + set => SetValue(RedAmplitudeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RedAmplitudeProperty = DependencyProperty.Register( + nameof(RedAmplitude), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedAmplitude)))); + + /// + /// Gets or sets a value indicating whether to disable Red transfer. + /// + public bool RedDisable + { + get => (bool)GetValue(RedDisableProperty); + set => SetValue(RedDisableProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RedDisableProperty = DependencyProperty.Register( + nameof(RedDisable), + typeof(bool), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(false, OnBooleanPropertyChangedHelper(nameof(RedDisable)))); + + /// + /// Gets or sets the amount of scale to apply to the Red chennel. + /// + public double RedExponent + { + get => (double)GetValue(RedExponentProperty); + set => SetValue(RedExponentProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RedExponentProperty = DependencyProperty.Register( + nameof(RedExponent), + typeof(double), + typeof(BackdropGammaTransferBrush), + new PropertyMetadata(1.0, OnScalarPropertyChangedHelper(nameof(RedExponent)))); + + /// + /// Gets or sets the amount of scale to apply to the Red chennel. + /// + public double RedOffset + { + get => (double)GetValue(RedOffsetProperty); + set => SetValue(RedOffsetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + 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(); + }; + } + + /// + 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; + } + } + + /// + 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; + +/// +/// The is a which inverts whatever is behind it in the application. +/// +public class BackdropInvertBrush : XamlCompositionEffectBrushBase +{ + /// + 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; + +/// +/// Brush which applies a SaturationEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SaturationEffect.htm +/// +public class BackdropSaturationBrush : XamlCompositionEffectBrushBase +{ + /// + /// The instance currently in use + /// + private EffectSetter? setter; + + /// + /// Gets or sets the amount of gaussian blur to apply to the background. + /// + public double Saturation + { + get => (double)GetValue(SaturationProperty); + set => SetValue(SaturationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SaturationProperty = DependencyProperty.Register( + nameof(Saturation), + typeof(double), + typeof(BackdropSaturationBrush), + new PropertyMetadata(0.5, new PropertyChangedCallback(OnSaturationChanged))); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + 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; + +/// +/// Brush which applies a SepiaEffect to the Backdrop. http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_SepiaEffect.htm +/// +public class BackdropSepiaBrush : XamlCompositionEffectBrushBase +{ + /// + /// The instance currently in use + /// + private EffectSetter? setter; + + /// + /// Gets or sets the amount of gaussian blur to apply to the background. + /// + public double Intensity + { + get => (double)GetValue(IntensityProperty); + set => SetValue(IntensityProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty IntensityProperty = DependencyProperty.Register( + nameof(Intensity), + typeof(double), + typeof(BackdropSepiaBrush), + new PropertyMetadata(0.5, new PropertyChangedCallback(OnIntensityChanged))); + + /// + /// Updates the UI when changes + /// + /// The current instance + /// The instance for + 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); + } + } + + /// + 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; + +/// +/// A custom that's ready to be used with a custom pipeline. +/// +public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase +{ + /// + /// The initialization instance. + /// + private readonly AsyncMutex connectedMutex = new AsyncMutex(); + + /// + /// A method that builds and returns the pipeline to use in the current instance. + /// This method can also be used to store any needed or + /// instances in local fields, for later use (they will need to be called upon ). + /// + /// A instance to create the brush to display. + protected abstract PipelineBuilder OnPipelineRequested(); + + private bool isEnabled = true; + + /// + /// Gets or sets a value indicating whether the current brush is using the provided pipeline, or the fallback color. + /// + public bool IsEnabled + { + get => this.isEnabled; + set => this.OnEnabledToggled(value); + } + + /// + 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(); + } + + /// + protected override async void OnDisconnected() + { + using (await this.connectedMutex.LockAsync()) + { + if (CompositionBrush != null) + { + CompositionBrush.Dispose(); + CompositionBrush = null; + + OnCompositionBrushUpdated(); + } + } + + base.OnDisconnected(); + } + + /// + /// Updates the property depending on the input value. + /// + /// The new value being set to the property. + 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(); + } + } + } + + /// + /// Invoked whenever the property is updated. + /// + 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; + +/// +/// Helper Brush class to interop with Win2D Canvas calls. +/// +public abstract class CanvasBrushBase : XamlCompositionBrushBase +{ + private CompositionSurfaceBrush? _surfaceBrush; + + /// + /// Gets or sets the internal surface render width. Modify during construction. + /// + protected float SurfaceWidth { get; set; } + + /// + /// Gets or sets the internal surface render height. Modify during construction. + /// + protected float SurfaceHeight { get; set; } + + private CanvasDevice? _device; + + private CompositionGraphicsDevice? _graphics; + + /// + /// Implemented by parent class and called when canvas is being constructed for brush. + /// + /// Canvas device. + /// Canvas drawing session. + /// Size of surface to draw on. + /// True if drawing was completed and the brush is ready, otherwise return False to not create brush yet. + protected abstract bool OnDraw(CanvasDevice device, CanvasDrawingSession session, Vector2 size); + + /// + /// Initializes the Composition Brush. + /// + 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(); + } + + /// + /// Deconstructs the Composition Brush. + /// + 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; + +/// +/// Brush which blends a to the Backdrop in a given mode. See http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_Effects_BlendEffect.htm. +/// +public class ImageBlendBrush : XamlCompositionBrushBase +{ + private LoadedImageSurface? _surface; + private CompositionSurfaceBrush? _surfaceBrush; + + /// + /// Gets or sets the source of the image to composite. + /// + public ImageSource Source + { + get => (ImageSource)GetValue(SourceProperty); + set => SetValue(SourceProperty, value); + } + + /// + /// Identifies the dependency property. + /// + 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)); + + /// + /// Gets or sets how to stretch the image within the brush. + /// + public Stretch Stretch + { + get => (Stretch)GetValue(StretchProperty); + set => SetValue(StretchProperty, value); + } + + /// + /// Identifies the dependency property. + /// Requires 16299 or higher for modes other than None. + /// + public static readonly DependencyProperty StretchProperty = DependencyProperty.Register( + nameof(Stretch), + typeof(Stretch), + typeof(ImageBlendBrush), + new PropertyMetadata(Stretch.None, OnStretchChanged)); + + /// + /// Gets or sets how to blend the image with the backdrop. + /// + public ImageBlendMode Mode + { + get => (ImageBlendMode)GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + 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(); + } + + /// + 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; + } + } + + /// + 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; + +/// +/// A that renders a customizable Composition/Win2D effects pipeline +/// +[ContentProperty(Name = nameof(Effects))] +public sealed class PipelineBrush : XamlCompositionEffectBrushBase +{ + /// + /// Gets or sets the source for the current pipeline (defaults to a with source). + /// + public PipelineBuilder? Source { get; set; } + + /// + /// Gets or sets the collection of effects to use in the current pipeline. + /// + public IList Effects + { + get + { + if (GetValue(EffectsProperty) is not IList effects) + { + effects = new List(); + + SetValue(EffectsProperty, effects); + } + + return effects; + } + set => SetValue(EffectsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register( + nameof(Effects), + typeof(IList), + typeof(PipelineBrush), + new PropertyMetadata(null)); + + /// + protected override PipelineBuilder OnPipelineRequested() + { + PipelineBuilder builder = Source ?? PipelineBuilder.FromBackdrop(); + + foreach (IPipelineEffect effect in Effects) + { + builder = effect.AppendToBuilder(builder); + } + + return builder; + } + + /// + protected override void OnCompositionBrushUpdated() + { + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(CompositionBrush); + } + } +} diff --git a/components/Media/src/Brushes/RadialGradientBrush.Properties.cs b/components/Media/src/Brushes/RadialGradientBrush.Properties.cs new file mode 100644 index 00000000..69a61c74 --- /dev/null +++ b/components/Media/src/Brushes/RadialGradientBrush.Properties.cs @@ -0,0 +1,131 @@ +// 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; + +/// +/// Properties for the +/// +public partial class RadialGradientBrush +{ + /// + /// Gets or sets a enumeration that specifies the way in which an alpha channel affects color channels. The default is for compatibility with WPF. + /// + public AlphaMode AlphaMode + { + get => (AlphaMode)GetValue(AlphaModeProperty); + set => SetValue(AlphaModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AlphaModeProperty = + DependencyProperty.Register(nameof(AlphaMode), typeof(AlphaMode), typeof(RadialGradientBrush), new PropertyMetadata(AlphaMode.Straight, new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets a enumeration that specifies how the gradient's colors are interpolated. The default is . + /// + public ColorInterpolationMode ColorInterpolationMode + { + get => (ColorInterpolationMode)GetValue(ColorInterpolationModeProperty); + set => SetValue(ColorInterpolationModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ColorInterpolationModeProperty = + DependencyProperty.Register(nameof(ColorInterpolationMode), typeof(ColorInterpolationMode), typeof(RadialGradientBrush), new PropertyMetadata(ColorInterpolationMode.SRgbLinearInterpolation, new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the brush's gradient stops. + /// + public GradientStopCollection GradientStops + { + get => (GradientStopCollection)GetValue(GradientStopsProperty); + set => SetValue(GradientStopsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GradientStopsProperty = + DependencyProperty.Register(nameof(GradientStops), typeof(GradientStopCollection), typeof(RadialGradientBrush), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the center of the outermost circle of the radial gradient. The default is 0.5,0.5. + /// + public Point Center + { + get => (Point)GetValue(CenterProperty); + set => SetValue(CenterProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty CenterProperty = + DependencyProperty.Register(nameof(Center), typeof(Point), typeof(RadialGradientBrush), new PropertyMetadata(new Point(0.5, 0.5), new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the location of the two-dimensional focal point that defines the beginning of the gradient. The default is 0.5,0.5. + /// + public Point GradientOrigin + { + get => (Point)GetValue(GradientOriginProperty); + set => SetValue(GradientOriginProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty GradientOriginProperty = + DependencyProperty.Register(nameof(GradientOrigin), typeof(Point), typeof(RadialGradientBrush), new PropertyMetadata(new Point(0.5, 0.5), new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the horizontal radius of the outermost circle of the radial gradient. The default is 0.5. + /// + public double RadiusX + { + get => (double)GetValue(RadiusXProperty); + set => SetValue(RadiusXProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RadiusXProperty = + DependencyProperty.Register(nameof(RadiusX), typeof(double), typeof(RadialGradientBrush), new PropertyMetadata(0.5, new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the vertical radius of the outermost circle of the radial gradient. The default is 0.5. + /// + public double RadiusY + { + get => (double)GetValue(RadiusYProperty); + set => SetValue(RadiusYProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RadiusYProperty = + DependencyProperty.Register(nameof(RadiusY), typeof(double), typeof(RadialGradientBrush), new PropertyMetadata(0.5, new PropertyChangedCallback(OnPropertyChanged))); + + /// + /// Gets or sets the type of spread method that specifies how to draw a gradient that starts or ends inside the bounds of the object to be painted. + /// + public GradientSpreadMethod SpreadMethod + { + get => (GradientSpreadMethod)GetValue(SpreadMethodProperty); + set => SetValue(SpreadMethodProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty SpreadMethodProperty = + DependencyProperty.Register(nameof(SpreadMethod), typeof(GradientSpreadMethod), typeof(RadialGradientBrush), new PropertyMetadata(GradientSpreadMethod.Pad, new PropertyChangedCallback(OnPropertyChanged))); +} \ No newline at end of file diff --git a/components/Media/src/Brushes/RadialGradientBrush.cs b/components/Media/src/Brushes/RadialGradientBrush.cs new file mode 100644 index 00000000..83f3cd26 --- /dev/null +++ b/components/Media/src/Brushes/RadialGradientBrush.cs @@ -0,0 +1,103 @@ +// 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. + +//// UWP Replacement for WPF RadialGradientBrush: https://msdn.microsoft.com/en-us/library/system.windows.media.radialgradientbrush(v=vs.110).aspx. + +using System.Numerics; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Brushes; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media; + +/// +/// RadialGradientBrush - This GradientBrush defines its Gradient as an interpolation +/// within an Ellipse. +/// +[Obsolete("Please migrate to the RadialGradientBrush control from WinUI, this control will be removed in a future release. https://aka.ms/winui")] +[ContentProperty(Name = nameof(GradientStops))] +public partial class RadialGradientBrush : CanvasBrushBase +{ + private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var brush = (RadialGradientBrush)d; + + // We need to recreate the brush on any property change. + brush.OnDisconnected(); + brush.OnConnected(); + } + + /// + /// Initializes a new instance of the class. + /// + public RadialGradientBrush() + { + // Rendering surface size, if this is too small the gradient will be pixelated. + // Larger targets aren't effected as one would expect unless the gradient is very complex. + // This seems like a good compromise. + SurfaceWidth = 512; + SurfaceHeight = 512; + + GradientStops = new GradientStopCollection(); + } + + /// + /// Initializes a new instance of the class + /// with two colors specified for GradientStops at + /// offsets 0.0 and 1.0. + /// + /// The Color at offset 0.0. + /// The Color at offset 1.0. + public RadialGradientBrush(Color startColor, Color endColor) + : this() + { + GradientStops.Add(new GradientStop() { Color = startColor, Offset = 0.0 }); + GradientStops.Add(new GradientStop() { Color = endColor, Offset = 1.0 }); + } + + /// + /// Initializes a new instance of the class with GradientStops set to the passed-in collection. + /// + /// GradientStopCollection to set on this brush. + public RadialGradientBrush(GradientStopCollection gradientStopCollection) + : this() + { + GradientStops = gradientStopCollection; + } + + /// + protected override bool OnDraw(CanvasDevice device, CanvasDrawingSession session, Vector2 size) + { + // Create our Brush + if (GradientStops != null && GradientStops.Count > 0) + { + var gradientBrush = new CanvasRadialGradientBrush( + device, + GradientStops.ToWin2DGradientStops(), + SpreadMethod.ToEdgeBehavior(), + (CanvasAlphaMode)(int)AlphaMode, + ColorInterpolationMode.ToCanvasColorSpace(), + CanvasColorSpace.Srgb, + CanvasBufferPrecision.Precision8UIntNormalized) + { + // Calculate Surface coordinates from 0.0-1.0 range given in WPF brush + RadiusX = size.X * (float)RadiusX, + RadiusY = size.Y * (float)RadiusY, + Center = size * Center.ToVector2(), + + // Calculate Win2D Offset from origin/center used in WPF brush + OriginOffset = size * (GradientOrigin.ToVector2() - Center.ToVector2()), + }; + + // Use brush to draw on our canvas + session.FillRectangle(size.ToRect(), gradientBrush); + + gradientBrush.Dispose(); + + return true; + } + + return false; + } +} diff --git a/components/Media/src/Brushes/RadialGradientBrushInterop.cs b/components/Media/src/Brushes/RadialGradientBrushInterop.cs new file mode 100644 index 00000000..21bbba57 --- /dev/null +++ b/components/Media/src/Brushes/RadialGradientBrushInterop.cs @@ -0,0 +1,95 @@ +// 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.Brushes; + +namespace CommunityToolkit.WinUI.Media; + +/// +/// Interop Helpers between WPF and Win2D used by the +/// +internal static class RadialGradientBrushInterop +{ + /// + /// Converts a WPF to a Win2D . + /// https://msdn.microsoft.com/en-us/library/system.windows.media.colorinterpolationmode(v=vs.110).aspx + /// http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasColorSpace.htm + /// + /// mode. + /// space. + public static CanvasColorSpace ToCanvasColorSpace(this ColorInterpolationMode colorspace) + { + switch (colorspace) + { + case ColorInterpolationMode.ScRgbLinearInterpolation: + return CanvasColorSpace.ScRgb; + case ColorInterpolationMode.SRgbLinearInterpolation: + return CanvasColorSpace.Srgb; + } + + return CanvasColorSpace.Custom; + } + + /// + /// Converts a WPF to a Win2D . + /// https://msdn.microsoft.com/en-us/library/system.windows.media.gradientspreadmethod(v=vs.110).aspx + /// http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasEdgeBehavior.htm + /// + /// method. + /// behavior. + public static CanvasEdgeBehavior ToEdgeBehavior(this GradientSpreadMethod method) + { + switch (method) + { + case GradientSpreadMethod.Pad: + return CanvasEdgeBehavior.Clamp; + case GradientSpreadMethod.Reflect: + return CanvasEdgeBehavior.Mirror; + case GradientSpreadMethod.Repeat: + return CanvasEdgeBehavior.Wrap; + } + + return CanvasEdgeBehavior.Clamp; + } + + /// + /// Returns a new representing the size of the . + /// + /// vector representing object size for Rectangle. + /// value. + public static Rect ToRect(this Vector2 vector) + { + return new Rect(0, 0, vector.X, vector.Y); + } + + /// + /// Returns a new representing the . + /// + /// value. + /// value. + public static Vector2 ToVector2(this Size size) + { + return new Vector2((float)size.Width, (float)size.Height); + } + + /// + /// Converts a to an array of . + /// + /// collection of gradient stops. + /// New array of stops. + public static CanvasGradientStop[] ToWin2DGradientStops(this GradientStopCollection stops) + { + var canvasStops = new CanvasGradientStop[stops.Count]; + + int x = 0; + foreach (var stop in stops) + { + canvasStops[x++] = new CanvasGradientStop { Color = stop.Color, Position = (float)stop.Offset }; + } + + return canvasStops; + } +} \ No newline at end of file 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; + +/// +/// A that displays a tiled image +/// +public sealed class TilesBrush : XamlCompositionEffectBrushBase +{ + /// + /// Gets or sets the to the texture to use + /// + public Uri TextureUri + { + get => (Uri)GetValue(TextureUriProperty); + set => SetValue(TextureUriProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TextureUriProperty = DependencyProperty.Register( + nameof(TextureUri), + typeof(Uri), + typeof(TilesBrush), + new PropertyMetadata(default, OnDependencyPropertyChanged)); + + /// + /// Gets or sets the DPI mode used to render the texture (the default is ) + /// + public DpiMode DpiMode + { + get => (DpiMode)GetValue(DpiModeProperty); + set => SetValue(DpiModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DpiModeProperty = DependencyProperty.Register( + nameof(DpiMode), + typeof(DpiMode), + typeof(TilesBrush), + new PropertyMetadata(DpiMode.DisplayDpiWith96AsLowerBound, OnDependencyPropertyChanged)); + + /// + /// Updates the UI when either or changes + /// + /// The current instance + /// The instance for or + private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is TilesBrush brush && + brush.CompositionBrush != null) + { + brush.OnDisconnected(); + brush.OnConnected(); + } + } + + /// + 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; + +/// +/// A that represents a custom effect setter that can be applied to a instance +/// +/// The type of property value to set +/// The effect target value +public delegate void XamlEffectSetter(T value) + where T : unmanaged; + +/// +/// A that represents a custom effect animation that can be applied to a instance +/// +/// The type of property value to animate +/// The animation target value +/// The animation duration +/// A that completes when the target animation completes +public delegate Task XamlEffectAnimation(T value, TimeSpan duration) + where T : unmanaged; + +/// +/// A simple that can be used to quickly create XAML brushes from arbitrary pipelines +/// +public sealed class XamlCompositionBrush : XamlCompositionEffectBrushBase +{ + /// + /// Gets the pipeline for the current instance + /// + public PipelineBuilder Pipeline { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The instance to create the effect + public XamlCompositionBrush(PipelineBuilder pipeline) => this.Pipeline = pipeline; + + /// + /// Binds an to the composition brush in the current instance + /// + /// The type of property value to set + /// The input setter + /// The resulting setter + /// The current instance + [Pure] + public XamlCompositionBrush Bind(EffectSetter setter, out XamlEffectSetter bound) + where T : unmanaged + { + bound = value => setter(this.CompositionBrush, value); + + return this; + } + + /// + /// Binds an to the composition brush in the current instance + /// + /// The type of property value to animate + /// The input animation + /// The resulting animation + /// The current instance + [Pure] + public XamlCompositionBrush Bind(EffectAnimation animation, out XamlEffectAnimation bound) + where T : unmanaged + { + bound = (value, duration) => animation(this.CompositionBrush, value, duration); + + return this; + } + + /// + protected override PipelineBuilder OnPipelineRequested() => this.Pipeline; + + /// + /// Clones the current instance by rebuilding the source . Use this method to reuse the same effects pipeline on a different + /// + /// + /// 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. + /// + /// A instance using the current effects pipeline + [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..014f6cb4 --- /dev/null +++ b/components/Media/src/CommunityToolkit.WinUI.Media.csproj @@ -0,0 +1,17 @@ + + + Media + This package contains Media. + 0.0.1 + + + CommunityToolkit.WinUI.MediaRns + + + + + + + + + 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 @@ + + + + + + + + + + + + + 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; + +/// +/// An image based effect that loads an image at the specified location +/// +[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] +public abstract class ImageSourceBaseExtension : MarkupExtension +{ + /// + /// Gets or sets the for the image to load + /// + public Uri? Uri { get; set; } + + /// + /// Gets or sets the DPI mode used to render the image (the default is ) + /// + public DpiMode DpiMode { get; set; } = DpiMode.DisplayDpiWith96AsLowerBound; + + /// + /// Gets or sets the cache mode to use when loading the image (the default is ) + /// + 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; + +/// +/// A base pipeline effect. +/// +public abstract class PipelineEffect : DependencyObject, IPipelineEffect +{ + /// + public CompositionBrush? Brush { get; private set; } + + /// + /// Gets or sets a value indicating whether the effect can be animated. + /// + public bool IsAnimatable { get; set; } + + /// + public abstract PipelineBuilder AppendToBuilder(PipelineBuilder builder); + + /// + 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; + +/// +/// A blend effect that merges the current builder with an input one +/// +/// This effect maps to the Win2D effect +[ContentProperty(Name = nameof(Effects))] +public sealed class BlendEffect : PipelineEffect +{ + /// + /// Gets or sets the input to merge with the current instance (defaults to a with source). + /// + public PipelineBuilder? Source { get; set; } + + /// + /// Gets or sets the effects to apply to the input to merge with the current instance + /// + public List Effects { get; set; } = new List(); + + /// + /// Gets or sets the blending mode to use (the default mode is ) + /// + public ImageBlendMode Mode { get; set; } + + /// + /// Gets or sets the placement of the input builder with respect to the current one (the default is ) + /// + public Placement Placement { get; set; } = Placement.Foreground; + + /// + 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); + } + + /// + 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; + +/// +/// A gaussian blur effect +/// +/// This effect maps to the Win2D effect +public sealed class BlurEffect : PipelineEffect +{ + private double amount; + + /// + /// Gets or sets the blur amount for the effect (must be a positive value) + /// + public double Amount + { + get => this.amount; + set => this.amount = Math.Max(value, 0); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// A blend effect that merges the current builder with an input one +/// +/// This effect maps to the Win2D effect +[ContentProperty(Name = nameof(Effects))] +public sealed class CrossFadeEffect : PipelineEffect +{ + /// + /// Gets or sets the input to merge with the current instance (defaults to a with source). + /// + public PipelineBuilder? Source { get; set; } + + /// + /// Gets or sets the effects to apply to the input to merge with the current instance + /// + public List Effects { get; set; } = new List(); + + private double factor = 0.5; + + /// + /// Gets or sets the The cross fade factor to blend the input effects (default to 0.5, should be in the [0, 1] range) + /// + public double Factor + { + get => this.factor; + set => this.factor = Math.Clamp(value, 0, 1); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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); + } + + /// + 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; + +/// +/// An exposure effect +/// +/// This effect maps to the Win2D effect +public sealed class ExposureEffect : PipelineEffect +{ + private double amount; + + /// + /// Gets or sets the amount of exposure to apply to the background (defaults to 0, should be in the [-2, 2] range). + /// + public double Amount + { + get => this.amount; + set => this.amount = Math.Clamp(value, -2, 2); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// A custom acrylic effect that can be inserted into a pipeline +/// +/// This effect mirrors the look of the default implementation +[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] +public sealed class AcrylicSourceExtension : MarkupExtension +{ + /// + /// Gets or sets the background source mode for the effect (the default is ). + /// + public AcrylicBackgroundSource BackgroundSource { get; set; } = AcrylicBackgroundSource.Backdrop; + + private double blurAmount; + + /// + /// Gets or sets the blur amount for the effect (must be a positive value) + /// + /// This property is ignored when the active mode is + public double BlurAmount + { + get => this.blurAmount; + set => this.blurAmount = Math.Max(value, 0); + } + + /// + /// Gets or sets the tint for the effect + /// + public Color TintColor { get; set; } + + private double tintOpacity = 0.5f; + + /// + /// Gets or sets the color for the tint effect (default is 0.5, must be in the [0, 1] range) + /// + public double TintOpacity + { + get => this.tintOpacity; + set => this.tintOpacity = Math.Clamp(value, 0, 1); + } + + /// + /// Gets or sets the to the texture to use + /// + public Uri? TextureUri { get; set; } + + /// + 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; + +/// +/// A backdrop effect that can sample from a specified source +/// +[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] +public sealed class BackdropSourceExtension : MarkupExtension +{ + /// + /// Gets or sets the background source mode for the effect (the default is ). + /// + public AcrylicBackgroundSource BackgroundSource { get; set; } = AcrylicBackgroundSource.Backdrop; + + /// + 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; + +/// +/// An image effect, which displays an image loaded as a Win2D surface +/// +public sealed class ImageSourceExtension : ImageSourceBaseExtension +{ + /// + 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; + +/// +/// An effect that renders a standard 8bit SDR color on the available surface +/// +[MarkupExtensionReturnType(ReturnType = typeof(PipelineBuilder))] +public sealed class SolidColorSourceExtension : MarkupExtension +{ + /// + /// Gets or sets the color to display + /// + public Color Color { get; set; } + + /// + 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; + +/// +/// An effect that loads an image and replicates it to cover all the available surface area +/// +/// This effect maps to the Win2D effect +public sealed class TileSourceExtension : ImageSourceBaseExtension +{ + /// + 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; + +/// +/// A grayscale effect +/// +/// This effect maps to the Win2D effect +public sealed class GrayscaleEffect : PipelineEffect +{ + /// + 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; + +/// +/// A hue rotation effect +/// +/// This effect maps to the Win2D effect +public sealed class HueRotationEffect : PipelineEffect +{ + /// + /// Gets or sets the angle to rotate the hue, in radians + /// + public double Angle { get; set; } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// The base for all the builder effects to be used in a . +/// +public interface IPipelineEffect +{ + /// + /// Gets the current instance, if one is in use. + /// + CompositionBrush? Brush { get; } + + /// + /// Appends the current effect to the input instance. + /// + /// The source instance to add the effect to. + /// A new with the new effects added to it. + PipelineBuilder AppendToBuilder(PipelineBuilder builder); + + /// + /// Notifies that a given is now in use. + /// + /// The in use. + 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; + +/// +/// An color inversion effect +/// +/// This effect maps to the Win2D effect +public sealed class InvertEffect : PipelineEffect +{ + /// + 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; + +/// +/// A luminance to alpha effect +/// +/// This effect maps to the Win2D effect +public sealed class LuminanceToAlphaEffect : PipelineEffect +{ + /// + 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; + +/// +/// An opacity effect +/// +/// This effect maps to the Win2D effect +public sealed class OpacityEffect : PipelineEffect +{ + private double value = 1; + + /// + /// Gets or sets the opacity value to apply to the background (defaults to 1, should be in the [0, 1] range). + /// + public double Value + { + get => this.value; + set => this.value = Math.Clamp(value, 0, 1); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// A saturation effect +/// +/// This effect maps to the Win2D effect +public sealed class SaturationEffect : PipelineEffect +{ + private double value = 1; + + /// + /// Gets or sets the saturation amount to apply to the background (defaults to 1, should be in the [0, 1] range). + /// + public double Value + { + get => this.value; + set => this.value = Math.Clamp(value, 0, 1); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// A sepia effect +/// +/// This effect maps to the Win2D effect +public sealed class SepiaEffect : PipelineEffect +{ + private double intensity = 0.5; + + /// + /// Gets or sets the intensity of the effect (defaults to 0.5, should be in the [0, 1] range). + /// + public double Intensity + { + get => this.intensity; + set => this.intensity = Math.Clamp(value, 0, 1); + } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// An effect that overlays a color layer over the current builder, with a specified intensity +/// +public sealed class ShadeEffect : PipelineEffect +{ + /// + /// Gets or sets the color to use + /// + public Color Color { get; set; } + + private double intensity = 0.5; + + /// + /// Gets or sets the intensity of the color layer (default to 0.5, should be in the [0, 1] range) + /// + public double Intensity + { + get => this.intensity; + set => this.intensity = Math.Clamp(value, 0, 1); + } + + /// + 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; + +/// +/// A temperature and tint effect +/// +/// This effect maps to the Win2D effect +public sealed class TemperatureAndTintEffect : PipelineEffect +{ + private double temperature; + + /// + /// Gets or sets the value of the temperature for the current effect (defaults to 0, should be in the [-1, 1] range) + /// + public double Temperature + { + get => this.temperature; + set => this.temperature = Math.Clamp(value, -1, 1); + } + + private double tint; + + /// + /// Gets or sets the value of the tint for the current effect (defaults to 0, should be in the [-1, 1] range) + /// + public double Tint + { + get => this.tint; + set => this.tint = Math.Clamp(value, -1, 1); + } + + /// + 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; + +/// +/// A tint effect +/// +/// This effect maps to the Win2D effect +public sealed class TintEffect : PipelineEffect +{ + /// + /// Gets or sets the int color to use + /// + public Color Color { get; set; } + + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + + /// + 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; + +/// +/// Specifies the way in which an alpha channel affects color channels. +/// +public enum AlphaMode +{ + /// + /// Provides better transparent effects without a white bloom. + /// + Premultiplied = 0, + + /// + /// WPF default handling of alpha channel during transparent blending. + /// + 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; + +/// +/// Indicates the cache mode to use when loading a Win2D image +/// +public enum CacheMode +{ + /// + /// The default behavior, the cache is enabled + /// + Default, + + /// + /// Reload the target image and overwrite the cached entry, if it exists + /// + Overwrite, + + /// + /// The cache is disabled and new images are always reloaded + /// + 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; + +/// +/// Indicates the DPI mode to use to load an image +/// +public enum DpiMode +{ + /// + /// Uses the original DPI settings of the loaded image + /// + UseSourceDpi, + + /// + /// Uses the default value of 96 DPI + /// + Default96Dpi, + + /// + /// Overrides the image DPI settings with the current screen DPI value + /// + DisplayDpi, + + /// + /// Overrides the image DPI settings with the current screen DPI value and ensures the resulting value is at least 96 + /// + 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; + +/// +/// 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. +/// +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, + + /// + /// Hue blend mode. + /// + Hue = BlendEffectMode.Hue, + + /// + /// Saturation blend mode. + /// + Saturation = BlendEffectMode.Saturation, + + /// + /// Color blend mode. + /// + Color = BlendEffectMode.Color, + + /// + /// Luminosity blend mode. + /// + 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; + +/// +/// The method that each instance of uses when clipping its inner content. +/// +public enum InnerContentClipMode +{ + /// + /// Do not clip inner content. + /// + None, + + /// + /// Use to clip inner content. + /// + /// + /// This mode has better performance than . + /// + CompositionMaskBrush, + + /// + /// Use to clip inner content. + /// + /// + /// Content clipped in this mode will have smoother corners than when using . + /// + 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; + +/// +/// An used to modify the default placement of the input instance in a blend operation +/// +public enum Placement +{ + /// + /// The instance used to call the blend method is placed on top of the other + /// + Foreground, + + /// + /// The instance used to call the blend method is placed behind the other + /// + 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; + +/// +/// An extension for the +/// +internal static class GenericExtensions +{ + /// + /// Merges the two input instances and makes sure no duplicate keys are present + /// + /// The type of keys in the input dictionaries + /// The type of values in the input dictionaries + /// The first to merge + /// The second to merge + /// An instance with elements from both and + [Pure] + public static IReadOnlyDictionary Merge( + this IReadOnlyDictionary a, + IReadOnlyDictionary 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(a.Concat(b)); + } + + /// + /// Merges the two input instances and makes sure no duplicate items are present + /// + /// The type of elements in the input collections + /// The first to merge + /// The second to merge + /// An instance with elements from both and + [Pure] + public static IReadOnlyCollection Merge(this IReadOnlyCollection a, IReadOnlyCollection 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; + +/// +/// An implementation that can be easily used inside a block +/// +#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 +{ + /// + /// The underlying instance in use + /// + private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1); + + /// + /// Acquires a lock for the current instance, that is automatically released outside the block + /// + /// A that returns an instance to release the lock + public async Task LockAsync() + { + await this.semaphore.WaitAsync().ConfigureAwait(false); + + return new Lock(this.semaphore); + } + + /// + /// Private class that implements the automatic release of the semaphore + /// + private sealed class Lock : IDisposable + { + /// + /// The instance of the parent class + /// + private readonly SemaphoreSlim semaphore; + + /// + /// Initializes a new instance of the class. + /// + /// The instance of the parent class + public Lock(SemaphoreSlim semaphore) + { + this.semaphore = semaphore; + } + + /// + [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; + +/// +/// An extension for the type +/// +internal static class UriExtensions +{ + /// + /// Returns an that starts with the ms-appx:// prefix + /// + /// The input to process + /// A equivalent to the first but relative to ms-appx:// + /// This is needed because the XAML converter doesn't use the ms-appx:// prefix + [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; + } + + /// + /// Returns an that starts with the ms-appx:// prefix + /// + /// The input relative path to convert + /// A with relative to ms-appx:// + [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; + +/// +/// Attached properties to support attaching custom pipelines to UI elements. +/// +public static class UIElementExtensions +{ + /// + /// Identifies the VisualFactory XAML attached property. + /// + public static readonly DependencyProperty VisualFactoryProperty = DependencyProperty.RegisterAttached( + "VisualFactory", + typeof(AttachedVisualFactoryBase), + typeof(UIElementExtensions), + new PropertyMetadata(null, OnVisualFactoryPropertyChanged)); + + /// + /// Gets the value of . + /// + /// The to get the value for. + /// The retrieved item. + public static AttachedVisualFactoryBase GetVisualFactory(UIElement element) + { + return (AttachedVisualFactoryBase)element.GetValue(VisualFactoryProperty); + } + + /// + /// Sets the value of . + /// + /// The to set the value for. + /// The value to set. + public static void SetVisualFactory(UIElement element, AttachedVisualFactoryBase value) + { + element.SetValue(VisualFactoryProperty, value); + } + + /// + /// Callback to apply the visual for . + /// + /// The target object the property was changed for. + /// The instance for the current event. + 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; + +/// +/// An extension for the type +/// +internal static class CompositionObjectExtensions +{ + /// + /// Starts an to keep the size of the source in sync with the target + /// + /// The to start the animation on + /// The target to read the size updates from + 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); + } + + /// + /// Starts an animation on the given property of a + /// + /// The type of the property to animate + /// The target + /// The name of the property to animate + /// The final value of the property + /// The animation duration + /// A that completes when the created animation completes + public static Task StartAnimationAsync(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(); + + batch.Completed += (s, e) => tcs.SetResult(null); + + target.StartAnimation(property, animation); + + batch.End(); + + return tcs.Task; + } +} diff --git a/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs b/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs new file mode 100644 index 00000000..dc4005f1 --- /dev/null +++ b/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs @@ -0,0 +1,305 @@ +// 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.Brushes; +using Microsoft.Graphics.Canvas.Geometry; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Extension methods for CanvasDrawingSession. +/// +public static class CanvasDrawingSessionExtensions +{ + /// + /// Draws a circle of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Center of the Circle + /// Radius of the Circle + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawCircle(this CanvasDrawingSession session, Vector2 centerPoint, float radius, ICanvasStroke stroke) + { + session.DrawCircle(centerPoint, radius, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a circle of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the Center in x axis + /// Ordinate of the Center in the y axis + /// Radius of the Circle + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawCircle(this CanvasDrawingSession session, float x, float y, float radius, ICanvasStroke stroke) + { + session.DrawCircle(x, y, radius, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws an Ellipse of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Center of the Circle + /// Radius in the X axis + /// Radius in the Y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawEllipse(this CanvasDrawingSession session, Vector2 centerPoint, float radiusX, float radiusY, ICanvasStroke stroke) + { + session.DrawEllipse(centerPoint, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws an Ellipse of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the Center on the x axis + /// Offset of the Center on the y axis + /// Radius in the X axis + /// Radius in the Y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawEllipse(this CanvasDrawingSession session, float x, float y, float radiusX, float radiusY, ICanvasStroke stroke) + { + session.DrawEllipse(x, y, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a geometry relative to the origin, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// CanvasGeometry to render + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, ICanvasStroke stroke) + { + session.DrawGeometry(geometry, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a geometry relative to the specified position, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// CanvasGeometry to render + /// Offset + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, Vector2 offset, ICanvasStroke stroke) + { + session.DrawGeometry(geometry, offset, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a geometry relative to the specified position, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// CanvasGeometry to render + /// Offset on the x axis + /// Offset on the y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, float x, float y, ICanvasStroke stroke) + { + session.DrawGeometry(geometry, x, y, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a line between the specified positions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Starting position of the line + /// Ending position of the line + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawLine(this CanvasDrawingSession session, Vector2 point0, Vector2 point1, ICanvasStroke stroke) + { + session.DrawLine(point0, point1, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a line between the specified positions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of Starting position of the line on x-axis + /// Offset of Starting position of the line on y-axis + /// Offset of Ending position of the line on x-axis + /// Offset of Ending position of the line on y-axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawLine(this CanvasDrawingSession session, float x0, float y0, float x1, float y1, ICanvasStroke stroke) + { + session.DrawLine(x0, y0, x1, y1, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Rectangle dimensions + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawRectangle(this CanvasDrawingSession session, Rect rect, ICanvasStroke stroke) + { + session.DrawRectangle(rect, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Rectangle on the x-axis + /// Offset of the top left corner of the Rectangle on the y-axis + /// Width of the Rectangle + /// Height of the Rectangle + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawRectangle(this CanvasDrawingSession session, float x, float y, float w, float h, ICanvasStroke stroke) + { + session.DrawRectangle(x, y, w, h, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a Rounded Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Rectangle dimensions + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawRoundedRectangle(this CanvasDrawingSession session, Rect rect, float radiusX, float radiusY, ICanvasStroke stroke) + { + session.DrawRoundedRectangle(rect, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a Rounded Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Rounded Rectangle on the x-axis + /// Offset of the top left corner of the Rounded Rectangle on the y-axis + /// Width of the Rounded Rectangle + /// Height of the Rounded Rectangle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawRoundedRectangle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke) + { + session.DrawRoundedRectangle(x, y, w, h, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); + } + + /// + /// Draws a Squircle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.DrawGeometry(geometry, stroke); + } + + /// + /// Draws a Squircle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// Offset of the Squircle from the origin. + /// CanvasStroke defining the stroke width, the stroke + /// color and stroke style. + public static void DrawSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, ICanvasStroke stroke) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.DrawGeometry(geometry, offset, stroke); + } + + /// + /// Fills a Squircle of the specified dimensions, using the given color. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// Color to fill the Squircle. + public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Color color) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.FillGeometry(geometry, color); + } + + /// + /// Fills a Squircle of the specified dimensions, using the given brush. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// Brush to fill the Squircle. + public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasBrush brush) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.FillGeometry(geometry, brush); + } + + /// + /// Fills a Squircle of the specified dimensions, using the given color at specified offset. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// Offset of the Squircle from the origin. + /// Color to fill the Squircle. + public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, Color color) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.FillGeometry(geometry, offset, color); + } + + /// + /// Fills a Squircle of the specified dimensions, using the given brush at specified offset. + /// + /// CanvasDrawingSession + /// Offset of the top left corner of the Squircle on the x-axis + /// Offset of the top left corner of the Squircle on the y-axis + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x axis + /// Corner Radius on the y axis + /// Offset of the Squircle from the origin. + /// Brush to fill the Squircle. + public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, ICanvasBrush brush) + { + using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); + session.FillGeometry(geometry, offset, brush); + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs b/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs new file mode 100644 index 00000000..cd34bc86 --- /dev/null +++ b/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs @@ -0,0 +1,458 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Defines extension methods for CanvasPathBuilder. +/// +public static class CanvasPathBuilderExtensions +{ + private const float SquircleFactor = 1.125f; + private const float ControlPointFactor = 46f / 64f; + + /// + /// Adds a line in the form of a cubic bezier. The control point of the quadratic bezier will be the endpoint of the line itself. + /// + /// + /// Ending location of the line segment. + public static void AddLineAsQuadraticBezier(this CanvasPathBuilder pathBuilder, Vector2 end) + { + pathBuilder.AddQuadraticBezier(end, end); + } + + /// + /// Adds a line in the form of a cubic bezier. The two control points of the cubic bezier will be the endpoints of the line itself. + /// + /// + /// Starting location of the line segment. + /// Ending location of the line segment. + public static void AddLineAsCubicBezier(this CanvasPathBuilder pathBuilder, Vector2 start, Vector2 end) + { + pathBuilder.AddCubicBezier(start, end, end); + } + + /// + /// Adds a circle figure to the path. + /// + /// + /// Center location of the circle. + /// Radius of the circle. + public static void AddCircleFigure(this CanvasPathBuilder pathBuilder, Vector2 center, float radius) + { + pathBuilder.AddEllipseFigure(center.X, center.Y, radius, radius); + } + + /// + /// Adds a circle figure to the path. + /// + /// + /// X coordinate of the center location of the circle. + /// Y coordinate of the center location of the circle. + /// Radius of the circle. + public static void AddCircleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float radius) + { + pathBuilder.AddEllipseFigure(x, y, radius, radius); + } + + /// + /// Adds an ellipse figure to the path. + /// + /// + /// Center location of the ellipse. + /// Radius of the ellipse on the X-axis. + /// Radius of the ellipse on the Y-axis. + public static void AddEllipseFigure(this CanvasPathBuilder pathBuilder, Vector2 center, float radiusX, float radiusY) + { + pathBuilder.AddEllipseFigure(center.X, center.Y, radiusX, radiusY); + } + + /// + /// Adds an ellipse figure to the path. + /// + /// + /// X coordinate of the center location of the ellipse. + /// Y coordinate of the center location of the ellipse. + /// Radius of the ellipse on the X-axis. + /// Radius of the ellipse on the Y-axis. + public static void AddEllipseFigure(this CanvasPathBuilder pathBuilder, float x, float y, float radiusX, float radiusY) + { + // Sanitize the radiusX by taking the absolute value + radiusX = Math.Abs(radiusX); + + // Sanitize the radiusY by taking the absolute value + radiusY = Math.Abs(radiusY); + + try + { + pathBuilder.BeginFigure(x + radiusX, y); + } + catch (ArgumentException) + { + // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddEllipseFigure() method. + static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddEllipseFigure occurred, " + + "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + + "before calling CanvasPathBuilder.AddEllipseFigure, to end the previous figure."); + + Throw(); + } + + // First Semi-Ellipse + pathBuilder.AddArc(new Vector2(x - radiusX, y), radiusX, radiusY, Scalar.Pi, CanvasSweepDirection.Clockwise, CanvasArcSize.Large); + + // Second Semi-Ellipse + pathBuilder.AddArc(new Vector2(x + radiusX, y), radiusX, radiusY, Scalar.Pi, CanvasSweepDirection.Clockwise, CanvasArcSize.Large); + + // End Figure + pathBuilder.EndFigure(CanvasFigureLoop.Closed); + } + + /// + /// Adds a n-sided polygon figure to the path. + /// + /// + /// Number of sides of the polygon. + /// Center location of the polygon. + /// Radius of the circle circumscribing the polygon i.e. the distance + /// of each of the vertices of the polygon from the center. + public static void AddPolygonFigure(this CanvasPathBuilder pathBuilder, int numSides, Vector2 center, float radius) + { + pathBuilder.AddPolygonFigure(numSides, center.X, center.Y, radius); + } + + /// + /// Adds a n-sided polygon figure to the path. + /// + /// + /// Number of sides of the polygon. + /// X coordinate of the center location of the polygon. + /// Y coordinate of the center location of the polygon. + /// Radius of the circle circumscribing the polygon i.e. the distance + /// of each of the vertices of the polygon from the center. + public static void AddPolygonFigure(this CanvasPathBuilder pathBuilder, int numSides, float x, float y, float radius) + { + // Sanitize the radius by taking the absolute value + radius = Math.Abs(radius); + + // A polygon should have at least 3 sides + if (numSides <= 2) + { + ThrowArgumentOutOfRangeException(); + } + + // Calculate the first vertex location based on the number of sides + var angle = Scalar.TwoPi / numSides; + var startAngle = numSides % 2 == 1 ? Scalar.PiByTwo : Scalar.PiByTwo - (angle / 2f); + + var startX = x + (float)(radius * Math.Cos(startAngle)); + var startY = y - (float)(radius * Math.Sin(startAngle)); + + try + { + pathBuilder.BeginFigure(startX, startY); + } + catch (ArgumentException) + { + // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. + static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddPolygonFigure occurred, " + + "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + + "before calling CanvasPathBuilder.AddPolygonFigure, to end the previous figure."); + + Throw(); + } + + // Add lines to the remaining vertices + for (var i = 1; i < numSides; i++) + { + var posX = x + (float)(radius * Math.Cos(startAngle + (i * angle))); + var posY = y - (float)(radius * Math.Sin(startAngle + (i * angle))); + pathBuilder.AddLine(posX, posY); + } + + // Add a line to the first vertex so that the lines join properly + pathBuilder.AddLine(startX, startY); + + // End the Figure + pathBuilder.EndFigure(CanvasFigureLoop.Closed); + + static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException($"Parameter {nameof(numSides)} must be greater than 2."); + } + + /// + /// Adds a Rectangle to the Path. + /// + /// + /// X offset of the TopLeft corner of the Rectangle + /// Y offset of the TopLeft corner of the Rectangle + /// Width of the Rectangle + /// Height of the Rectangle + public static void AddRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height) + { + // Sanitize the width by taking the absolute value + width = Math.Abs(width); + + // Sanitize the height by taking the absolute value + height = Math.Abs(height); + + try + { + pathBuilder.BeginFigure(x, y); + } + catch (ArgumentException) + { + // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. + static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddRectangleFigure occurred, " + + "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + + "before calling CanvasPathBuilder.AddRectangleFigure, to end the previous figure."); + + Throw(); + } + + // Top Side + pathBuilder.AddLine(x + width, y); + + // Right Side + pathBuilder.AddLine(x + width, y + height); + + // Bottom Side + pathBuilder.AddLine(x, y + height); + + // Left Side + pathBuilder.AddLine(x, y); + + // End the Figure + pathBuilder.EndFigure(CanvasFigureLoop.Closed); + } + + /// + /// Adds a RoundedRectangle to the Path. + /// + /// + /// X offset of the TopLeft corner of the RoundedRectangle + /// Y offset of the TopLeft corner of the RoundedRectangle + /// Width of the RoundedRectangle + /// Height of the RoundedRectangle + /// Corner Radius on the x-axis + /// Corner Radius on the y-axis + public static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height, float radiusX, float radiusY) + { + // Sanitize the width by taking the absolute value + width = Math.Abs(width); + + // Sanitize the height by taking the absolute value + height = Math.Abs(height); + + var rect = new CanvasRoundRect(x, y, width, height, radiusX, radiusY); + pathBuilder.AddRoundedRectangleFigure(ref rect, true); + } + + /// + /// Adds a RoundedRectangle to the Path. (To be used internally) + /// + /// + /// CanvasRoundRect + /// Flag to indicate whether exception should be raised + internal static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, ref CanvasRoundRect rect, bool raiseException = false) + { + try + { + // Begin path + pathBuilder.BeginFigure(new Vector2(rect.LeftTopX, rect.LeftTopY)); + } + catch (ArgumentException) + { + if (!raiseException) + { + return; + } + + // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. + static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddRoundedRectangleFigure occurred, " + + "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + + "before calling CanvasPathBuilder.AddRoundedRectangleFigure, to end the previous figure."); + + Throw(); + } + + // Top line + pathBuilder.AddLine(new Vector2(rect.RightTopX, rect.RightTopY)); + + // Upper-right corner + var radiusX = rect.TopRightX - rect.RightTopX; + var radiusY = rect.TopRightY - rect.RightTopY; + var center = new Vector2(rect.RightTopX, rect.TopRightY); + pathBuilder.AddArc(center, radiusX, radiusY, 3f * Scalar.PiByTwo, Scalar.PiByTwo); + + // Right line + pathBuilder.AddLine(new Vector2(rect.BottomRightX, rect.BottomRightY)); + + // Lower-right corner + radiusX = rect.BottomRightX - rect.RightBottomX; + radiusY = rect.RightBottomY - rect.BottomRightY; + center = new Vector2(rect.RightBottomX, rect.BottomRightY); + pathBuilder.AddArc(center, radiusX, radiusY, 0f, Scalar.PiByTwo); + + // Bottom line + pathBuilder.AddLine(new Vector2(rect.LeftBottomX, rect.LeftBottomY)); + + // Lower-left corner + radiusX = rect.LeftBottomX - rect.BottomLeftX; + radiusY = rect.LeftBottomY - rect.BottomLeftY; + center = new Vector2(rect.LeftBottomX, rect.BottomLeftY); + pathBuilder.AddArc(center, radiusX, radiusY, Scalar.PiByTwo, Scalar.PiByTwo); + + // Left line + pathBuilder.AddLine(new Vector2(rect.TopLeftX, rect.TopLeftY)); + + // Upper-left corner + radiusX = rect.LeftTopX - rect.TopLeftX; + radiusY = rect.TopLeftY - rect.LeftTopY; + center = new Vector2(rect.LeftTopX, rect.TopLeftY); + pathBuilder.AddArc(center, radiusX, radiusY, 2f * Scalar.PiByTwo, Scalar.PiByTwo); + + // End path + pathBuilder.EndFigure(CanvasFigureLoop.Closed); + } + + /// + /// Adds a Squircle to the Path. + /// + /// + /// X offset of the TopLeft corner of the Squircle + /// Y offset of the TopLeft corner of the Squircle + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x-axis + /// Corner Radius on the y-axis + public static void AddSquircleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height, float radiusX, float radiusY) + { + // Sanitize the width by taking the absolute value + width = Math.Abs(width); + + // Sanitize the height by taking the absolute value + height = Math.Abs(height); + + var rect = new CanvasRoundRect(x, y, width, height, radiusX * SquircleFactor, radiusY * SquircleFactor); + pathBuilder.AddSquircleFigure(ref rect, true); + } + + /// + /// Adds a Squircle to the Path. (To be used internally) + /// + /// + /// CanvasRoundRect + /// Flag to indicate whether exception should be raised + internal static void AddSquircleFigure(this CanvasPathBuilder pathBuilder, ref CanvasRoundRect rect, bool raiseException = false) + { + try + { + // Begin path + pathBuilder.BeginFigure(new Vector2(rect.LeftTopX, rect.LeftTopY)); + } + catch (ArgumentException) + { + if (!raiseException) + { + return; + } + + // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. + static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddSquircleFigure occurred, " + + "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + + "before calling CanvasPathBuilder.AddSquircleFigure, to end the previous figure."); + + Throw(); + } + + // Top line + pathBuilder.AddLine(new Vector2(rect.RightTopX, rect.RightTopY)); + + // Upper-right corner + var rightTopControlPoint = new Vector2(rect.RightTopX + ((rect.TopRightX - rect.RightTopX) * ControlPointFactor), rect.RightTopY); + var topRightControlPoint = new Vector2(rect.TopRightX, rect.TopRightY - ((rect.TopRightY - rect.RightTopY) * ControlPointFactor)); + + // Top Right Curve + pathBuilder.AddCubicBezier(rightTopControlPoint, topRightControlPoint, new Vector2(rect.TopRightX, rect.TopRightY)); + + // Right line + pathBuilder.AddLine(new Vector2(rect.BottomRightX, rect.BottomRightY)); + + // Lower-right corner + var bottomRightControlPoint = new Vector2(rect.BottomRightX, rect.BottomRightY + ((rect.RightBottomY - rect.BottomRightY) * ControlPointFactor)); + var rightBottomControlPoint = new Vector2(rect.RightBottomX + ((rect.BottomRightX - rect.RightBottomX) * ControlPointFactor), rect.RightBottomY); + + // Bottom Right Curve + pathBuilder.AddCubicBezier(bottomRightControlPoint, rightBottomControlPoint, new Vector2(rect.RightBottomX, rect.RightBottomY)); + + // Bottom line + pathBuilder.AddLine(new Vector2(rect.LeftBottomX, rect.LeftBottomY)); + + // Lower-left corner + var leftBottomControlPoint = new Vector2(rect.LeftBottomX - ((rect.LeftBottomX - rect.BottomLeftX) * ControlPointFactor), rect.LeftBottomY); + var bottomLeftControlPoint = new Vector2(rect.BottomLeftX, rect.BottomLeftY + ((rect.LeftBottomY - rect.BottomLeftY) * ControlPointFactor)); + + // Bottom Left Curve + pathBuilder.AddCubicBezier(leftBottomControlPoint, bottomLeftControlPoint, new Vector2(rect.BottomLeftX, rect.BottomLeftY)); + + // Left line + pathBuilder.AddLine(new Vector2(rect.TopLeftX, rect.TopLeftY)); + + // Upper-left corner + var topLeftControlPoint = new Vector2(rect.TopLeftX, rect.TopLeftY - ((rect.TopLeftY - rect.LeftTopY) * ControlPointFactor)); + var leftTopControlPoint = new Vector2(rect.LeftTopX - ((rect.LeftTopX - rect.TopLeftX) * ControlPointFactor), rect.LeftTopY); + + // Top Left Curve + pathBuilder.AddCubicBezier(topLeftControlPoint, leftTopControlPoint, new Vector2(rect.LeftTopX, rect.LeftTopY)); + + // End path + pathBuilder.EndFigure(CanvasFigureLoop.Closed); + } + + /// + /// Builds a path with the given collection of points. + /// + /// + /// Specifies whether the figure is open or closed. + /// This affects the appearance of fills and strokes, as well as geometry operations. + /// Collection of Vector2 points on the path. + /// object + public static CanvasPathBuilder BuildPathWithLines(this CanvasPathBuilder builder, CanvasFigureLoop canvasFigureLoop, IEnumerable points) + { + var first = true; + + foreach (var point in points) + { + if (first) + { + builder.BeginFigure(point); + first = false; + } + else + { + builder.AddLine(point); + } + } + + builder.EndFigure(canvasFigureLoop); + return builder; + } + + /// + /// Builds a path with the given collection of points in the (x, y) pattern. + /// + /// + /// Specifies whether the figure is open or closed. + /// This affects the appearance of fills and strokes, as well as geometry operations. + /// Collection of points in the (x, y) pattern on the path. + /// object + public static CanvasPathBuilder BuildPathWithLines(this CanvasPathBuilder builder, CanvasFigureLoop canvasFigureLoop, IEnumerable<(float x, float y)> nodes) + { + var vectors = nodes.Select(n => new Vector2(n.x, n.y)); + return BuildPathWithLines(builder, canvasFigureLoop, vectors); + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/CanvasPathGeometry.cs b/components/Media/src/Geometry/CanvasPathGeometry.cs new file mode 100644 index 00000000..56b06020 --- /dev/null +++ b/components/Media/src/Geometry/CanvasPathGeometry.cs @@ -0,0 +1,137 @@ +// 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.Brushes; +using Microsoft.Graphics.Canvas.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Parsers; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Helper Class for creating Win2d objects. +/// +public static class CanvasPathGeometry +{ + /// + /// Parses the Path data string and converts it to CanvasGeometry. + /// + /// Path data + /// + public static CanvasGeometry? CreateGeometry(string pathData) + { + return CreateGeometry(null, pathData); + } + + /// + /// Parses the Path data string and converts it to CanvasGeometry. + /// + /// + /// Path data + /// + public static CanvasGeometry? CreateGeometry(ICanvasResourceCreator? resourceCreator, string pathData) + { + using (new CultureShield("en-US")) + { + // Get the CanvasGeometry from the path data + return CanvasGeometryParser.Parse(resourceCreator, pathData); + } + } + + /// + /// Creates a Squircle geometry with the specified extents. + /// + /// Resource creator + /// X offset of the TopLeft corner of the Squircle + /// Y offset of the TopLeft corner of the Squircle + /// Width of the Squircle + /// Height of the Squircle + /// Corner Radius on the x-axis + /// Corner Radius on the y-axis + /// + public static CanvasGeometry CreateSquircle(ICanvasResourceCreator resourceCreator, float x, float y, float width, float height, float radiusX, float radiusY) + { + using var pathBuilder = new CanvasPathBuilder(resourceCreator); + pathBuilder.AddSquircleFigure(x, y, width, height, radiusX, radiusY); + return CanvasGeometry.CreatePath(pathBuilder); + } + + /// + /// Parses the given Brush data string and converts it to ICanvasBrush. + /// + /// ICanvasResourceCreator + /// Brush data in string format + /// + public static ICanvasBrush? CreateBrush(ICanvasResourceCreator resourceCreator, string brushData) + { + using (new CultureShield("en-US")) + { + return CanvasBrushParser.Parse(resourceCreator, brushData); + } + } + + /// + /// Parses the given Stroke data string and converts it to ICanvasStroke. + /// + /// ICanvasResourceCreator + /// Stroke data in string format + /// + public static ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator, string strokeData) + { + using (new CultureShield("en-US")) + { + return CanvasStrokeParser.Parse(resourceCreator, strokeData); + } + } + + /// + /// Parses the give CanvasStrokeStyle data string and converts it to CanvasStrokeStyle. + /// + /// CanvasStrokeStyle data in string format + /// object + public static CanvasStrokeStyle CreateStrokeStyle(string styleData) + { + using (new CultureShield("en-US")) + { + return CanvasStrokeStyleParser.Parse(styleData); + } + } + + /// + /// Converts the color string in Hexadecimal or HDR color format to the corresponding Color object. + /// The hexadecimal color string should be in #RRGGBB or #AARRGGBB format. + /// The '#' character is optional. + /// The HDR color string should be in R G B A format. + /// (R, G, B & A should have value in the range between 0 and 1, inclusive) + /// + /// Color string in Hexadecimal or HDR format + /// Color + public static Color CreateColor(string colorString) + { + using (new CultureShield("en-US")) + { + return ColorParser.Parse(colorString); + } + } + + /// + /// Converts a Vector4 High Dynamic Range Color to Color object. + /// Negative components of the Vector4 will be sanitized by taking the absolute + /// value of the component. The HDR Color components should have value in + /// the range between 0 and 1, inclusive. If they are more than 1, they + /// will be clamped at 1. + /// Vector4's X, Y, Z, W components match to Color's R, G, B, A components respectively. + /// + /// High Dynamic Range Color + /// Color + public static Color CreateColor(Vector4 hdrColor) + { + using (new CultureShield("en-US")) + { + return ColorParser.Parse(hdrColor); + } + } +} diff --git a/components/Media/src/Geometry/CanvasStroke.cs b/components/Media/src/Geometry/CanvasStroke.cs new file mode 100644 index 00000000..dd2f3d5b --- /dev/null +++ b/components/Media/src/Geometry/CanvasStroke.cs @@ -0,0 +1,111 @@ +// 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.Brushes; +using Microsoft.Graphics.Canvas.Geometry; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Class to represent the Stroke which can be used to render an outline on a +/// +public sealed class CanvasStroke : ICanvasStroke +{ + /// + /// Gets or sets the brush with which the stroke will be rendered + /// + public ICanvasBrush Brush { get; set; } + + /// + /// Gets or sets the width of the + /// + public float Width { get; set; } + + /// + /// Gets or sets the Style of the + /// + public CanvasStrokeStyle Style { get; set; } + + /// + /// Gets or sets the Transform matrix of the brush. + /// + public Matrix3x2 Transform + { + get => GetTransform(); + + set => SetTransform(value); + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush with which the will be rendered + /// Width of the + public CanvasStroke(ICanvasBrush brush, float strokeWidth = 1f) + : this(brush, strokeWidth, new CanvasStrokeStyle()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The brush with which the will be rendered + /// Width of the + /// Style of the + public CanvasStroke(ICanvasBrush brush, float strokeWidth, CanvasStrokeStyle strokeStyle) + { + Brush = brush; + Width = strokeWidth; + Style = strokeStyle; + } + + /// + /// Initializes a new instance of the class. + /// + /// ICanvasResourceCreator + /// Color of the + /// Width of the + public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth = 1f) + : this(device, strokeColor, strokeWidth, new CanvasStrokeStyle()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// ICanvasResourceCreator + /// Color of the + /// Width of the + /// Style of the + public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth, CanvasStrokeStyle strokeStyle) + { + Brush = new CanvasSolidColorBrush(device, strokeColor); + Width = strokeWidth; + Style = strokeStyle; + } + + /// + /// Sets the 's Transform. + /// + /// Transform matrix to set + private void SetTransform(Matrix3x2 value) + { + if (Brush != null) + { + Brush.Transform = value; + } + } + + /// + /// Gets the 's Transform. If stroke is null, then returns Matrix3x2.Identity. + /// + /// Transform matrix of the + private Matrix3x2 GetTransform() + { + return Brush?.Transform ?? Matrix3x2.Identity; + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/CompositorGeometryExtensions.cs b/components/Media/src/Geometry/CompositorGeometryExtensions.cs new file mode 100644 index 00000000..8e5f0987 --- /dev/null +++ b/components/Media/src/Geometry/CompositorGeometryExtensions.cs @@ -0,0 +1,97 @@ +// 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.Geometry; +using Windows.UI.Composition; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Extension methods for compositor to support Win2d Path Mini Language. +/// +public static class CompositorGeometryExtensions +{ + /// + /// Creates a based on the specified path data. + /// + /// + /// Path data (Win2d Path Mini Language) in string format. + /// + public static CompositionPath? CreatePath(this Compositor compositor, string pathData) + { + // Create CanvasGeometry + var geometry = CanvasPathGeometry.CreateGeometry(pathData); + + // Create CompositionPath + return new CompositionPath(geometry); + } + + /// + /// Creates a based on the given path data. + /// + /// + /// Path data (Win2d Path Mini Language) in string format. + /// + public static CompositionPathGeometry? CreatePathGeometry(this Compositor compositor, string pathData) + { + // Create CanvasGeometry + var geometry = CanvasPathGeometry.CreateGeometry(pathData); + + // Create CompositionPathGeometry + return compositor.CreatePathGeometry(new CompositionPath(geometry)); + } + + /// + /// Creates a based on the specified path data. + /// + /// + /// Path data (Win2d Path Mini Language) in string format. + /// + public static CompositionSpriteShape? CreateSpriteShape(this Compositor compositor, string pathData) + { + // Create CanvasGeometry + var geometry = CanvasPathGeometry.CreateGeometry(pathData); + + // Create CompositionPathGeometry + var pathGeometry = compositor.CreatePathGeometry(new CompositionPath(geometry)); + + // Create CompositionSpriteShape + return compositor.CreateSpriteShape(pathGeometry); + } + + /// + /// Creates a from the specified . + /// + /// + /// + /// CompositionGeometricClip + public static CompositionGeometricClip CreateGeometricClip(this Compositor compositor, CanvasGeometry geometry) + { + // Create the CompositionPath + var path = new CompositionPath(geometry); + + // Create the CompositionPathGeometry + var pathGeometry = compositor.CreatePathGeometry(path); + + // Create the CompositionGeometricClip + return compositor.CreateGeometricClip(pathGeometry); + } + + /// + /// Parses the specified path data and converts it to . + /// + /// + /// Path data (Win2d Path Mini Language) in string format. + /// + public static CompositionGeometricClip? CreateGeometricClip(this Compositor compositor, string pathData) + { + // Create the CanvasGeometry from the path data + var geometry = CanvasPathGeometry.CreateGeometry(pathData); + if (geometry is null) + return null; + + // Create the CompositionGeometricClip + return compositor.CreateGeometricClip(geometry); + } +} diff --git a/components/Media/src/Geometry/Core/CanvasRoundRect.cs b/components/Media/src/Geometry/Core/CanvasRoundRect.cs new file mode 100644 index 00000000..0457ded9 --- /dev/null +++ b/components/Media/src/Geometry/Core/CanvasRoundRect.cs @@ -0,0 +1,278 @@ +// 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; + +namespace CommunityToolkit.WinUI.Media.Geometry.Core; + +/// +/// Structure which encapsulates the details of each of the core points of the path of the rounded rectangle which is calculated based on +/// either the given (Size, CornerRadius, BorderThickness and Padding) or (Size, RadiusX and RadiusY). +/// +internal struct CanvasRoundRect +{ + private const float Factor = 0.5f; + + private readonly float _leftTopWidth; + private readonly float _topLeftHeight; + private readonly float _topRightHeight; + private readonly float _rightTopWidth; + private readonly float _rightBottomWidth; + private readonly float _bottomRightHeight; + private readonly float _bottomLeftHeight; + private readonly float _leftBottomWidth; + + // This is the location of the properties within the Rect + // |--LeftTop----------------------RightTop--| + // | | + // TopLeft TopRight + // | | + // | | + // | | + // | | + // | | + // | | + // BottomLeft BottomRight + // | | + // |--LeftBottom----------------RightBottom--| + internal float LeftTopX { get; private set; } + + internal float LeftTopY { get; private set; } + + internal float TopLeftX { get; private set; } + + internal float TopLeftY { get; private set; } + + internal float TopRightX { get; private set; } + + internal float TopRightY { get; private set; } + + internal float RightTopX { get; private set; } + + internal float RightTopY { get; private set; } + + internal float RightBottomX { get; private set; } + + internal float RightBottomY { get; private set; } + + internal float BottomRightX { get; private set; } + + internal float BottomRightY { get; private set; } + + internal float BottomLeftX { get; private set; } + + internal float BottomLeftY { get; private set; } + + internal float LeftBottomX { get; private set; } + + internal float LeftBottomY { get; private set; } + + internal float Width { get; } + + internal float Height { get; } + + /// + /// Initializes a new instance of the struct. + /// + /// Origin of the Rect (absolute location of Top Left corner) + /// Size of the Rect + /// CornerRadius + /// BorderThickness + /// Padding + /// Flag to indicate whether outer or inner border needs + /// to be calculated + internal CanvasRoundRect(Vector2 origin, Vector2 size, Vector4 cornerRadius, Vector4 borderThickness, Vector4 padding, bool isOuterBorder) + : this() + { + Width = Math.Max(0f, size.X); + Height = Math.Max(0f, size.Y); + + var left = Factor * (borderThickness.X + padding.X); + var top = Factor * (borderThickness.Y + padding.Y); + var right = Factor * (borderThickness.Z + padding.Z); + var bottom = Factor * (borderThickness.W + padding.W); + + if (isOuterBorder) + { + // Top Left corner radius + if (cornerRadius.X.IsZero()) + { + _leftTopWidth = _topLeftHeight = 0f; + } + else + { + _leftTopWidth = cornerRadius.X + left; + _topLeftHeight = cornerRadius.X + top; + } + + // Top Right corner radius + if (cornerRadius.Y.IsZero()) + { + _topRightHeight = _rightTopWidth = 0f; + } + else + { + _topRightHeight = cornerRadius.Y + top; + _rightTopWidth = cornerRadius.Y + right; + } + + // Bottom Right corner radius + if (cornerRadius.Z.IsZero()) + { + _rightBottomWidth = _bottomRightHeight = 0f; + } + else + { + _rightBottomWidth = cornerRadius.Z + right; + _bottomRightHeight = cornerRadius.Z + bottom; + } + + // Bottom Left corner radius + if (cornerRadius.W.IsZero()) + { + _bottomLeftHeight = _leftBottomWidth = 0f; + } + else + { + _bottomLeftHeight = cornerRadius.W + bottom; + _leftBottomWidth = cornerRadius.W + left; + } + } + else + { + _leftTopWidth = Math.Max(0f, cornerRadius.X - left); + _topLeftHeight = Math.Max(0f, cornerRadius.X - top); + _topRightHeight = Math.Max(0f, cornerRadius.Y - top); + _rightTopWidth = Math.Max(0f, cornerRadius.Y - right); + _rightBottomWidth = Math.Max(0f, cornerRadius.Z - right); + _bottomRightHeight = Math.Max(0f, cornerRadius.Z - bottom); + _bottomLeftHeight = Math.Max(0f, cornerRadius.W - bottom); + _leftBottomWidth = Math.Max(0f, cornerRadius.W - left); + } + + // Calculate the anchor points + ComputeCoordinates(origin.X, origin.Y); + } + + /// + /// Initializes a new instance of the struct. + /// + /// Top Left corner of the Rounded Rectangle + /// Dimensions of the Rounded Rectangle + /// Radius of the corners on the x-axis + /// Radius of the corners on the y-axis + internal CanvasRoundRect(Vector2 origin, Vector2 size, float radiusX, float radiusY) + : this(origin.X, origin.Y, size.X, size.Y, radiusX, radiusY) + { + } + + /// + /// Initializes a new instance of the struct. + /// + /// X offset of the Top Left corner of the Rounded Rectangle + /// Y offset of the Top Left corner of the Rounded Rectangle + /// Width of the Rounded Rectangle. + /// Height of the Rounded Rectangle. + /// Radius of the corners on the x-axis + /// Radius of the corners on the y-axis + internal CanvasRoundRect(float x, float y, float width, float height, float radiusX, float radiusY) + : this() + { + Width = Math.Max(0f, width); + Height = Math.Max(0f, height); + + // Sanitize the radii by taking the absolute value + radiusX = Math.Min(Math.Abs(radiusX), width / 2f); + radiusY = Math.Min(Math.Abs(radiusY), height / 2); + + _leftTopWidth = radiusX; + _rightTopWidth = radiusX; + _rightBottomWidth = radiusX; + _leftBottomWidth = radiusX; + _topLeftHeight = radiusY; + _topRightHeight = radiusY; + _bottomRightHeight = radiusY; + _bottomLeftHeight = radiusY; + + ComputeCoordinates(x, y); + } + + /// + /// Computes the coordinates of the crucial points on the CanvasRoundRect + /// + /// X coordinate of the origin. + /// Y coordinate of the origin. + private void ComputeCoordinates(float originX, float originY) + { + // compute the coordinates of the key points + var leftTopX = _leftTopWidth; + var leftTopY = 0f; + var rightTopX = Width - _rightTopWidth; + var rightTopY = 0f; + var topRightX = Width; + var topRightY = _topRightHeight; + var bottomRightX = Width; + var bottomRightY = Height - _bottomRightHeight; + var rightBottomX = Width - _rightBottomWidth; + var rightBottomY = Height; + var leftBottomX = _leftBottomWidth; + var leftBottomY = Height; + var bottomLeftX = 0f; + var bottomLeftY = Height - _bottomLeftHeight; + var topLeftX = 0f; + var topLeftY = _topLeftHeight; + + // check anchors for overlap and resolve by partitioning corners according to + // the percentage of each one. + // top edge + if (leftTopX > rightTopX) + { + var v = _leftTopWidth / (_leftTopWidth + _rightTopWidth) * Width; + leftTopX = v; + rightTopX = v; + } + + // right edge + if (topRightY > bottomRightY) + { + var v = _topRightHeight / (_topRightHeight + _bottomRightHeight) * Height; + topRightY = v; + bottomRightY = v; + } + + // bottom edge + if (leftBottomX > rightBottomX) + { + var v = _leftBottomWidth / (_leftBottomWidth + _rightBottomWidth) * Width; + rightBottomX = v; + leftBottomX = v; + } + + // left edge + if (topLeftY > bottomLeftY) + { + var v = _topLeftHeight / (_topLeftHeight + _bottomLeftHeight) * Height; + bottomLeftY = v; + topLeftY = v; + } + + // Apply origin translation + LeftTopX = leftTopX + originX; + LeftTopY = leftTopY + originY; + RightTopX = rightTopX + originX; + RightTopY = rightTopY + originY; + TopRightX = topRightX + originX; + TopRightY = topRightY + originY; + BottomRightX = bottomRightX + originX; + BottomRightY = bottomRightY + originY; + RightBottomX = rightBottomX + originX; + RightBottomY = rightBottomY + originY; + LeftBottomX = leftBottomX + originX; + LeftBottomY = leftBottomY + originY; + BottomLeftX = bottomLeftX + originX; + BottomLeftY = bottomLeftY + originY; + TopLeftX = topLeftX + originX; + TopLeftY = topLeftY + originY; + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs b/components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs new file mode 100644 index 00000000..6f896f02 --- /dev/null +++ b/components/Media/src/Geometry/Core/GeometryTypeDefinitions.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. + +namespace CommunityToolkit.WinUI.Media.Geometry.Core; + +/// +/// Enum for the various PathFigures. +/// +internal enum PathFigureType +{ + FillRule, + PathFigure, + EllipseFigure, + PolygonFigure, + RectangleFigure, + RoundedRectangleFigure +} + +/// +/// Enum for the various PathElements. +/// +internal enum PathElementType +{ + MoveTo, + Line, + HorizontalLine, + VerticalLine, + QuadraticBezier, + SmoothQuadraticBezier, + CubicBezier, + SmoothCubicBezier, + Arc, + ClosePath +} + +/// +/// Enum for the various types of Brushes. +/// +internal enum BrushType +{ + SolidColor, + LinearGradient, + RadialGradient, + LinearGradientHdr, + RadialGradientHdr +} + +/// +/// Enum for the various types of GradientStop attributes. +/// +internal enum GradientStopAttributeType +{ + Main, + Additional, + MainHdr, + AdditionalHdr +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Core/PathElementFactory.cs b/components/Media/src/Geometry/Core/PathElementFactory.cs new file mode 100644 index 00000000..a45890bf --- /dev/null +++ b/components/Media/src/Geometry/Core/PathElementFactory.cs @@ -0,0 +1,154 @@ +// 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.Geometry.Elements.Path; + +namespace CommunityToolkit.WinUI.Media.Geometry.Core; + +/// +/// Factory class to instantiate various PathElements. +/// +internal static class PathElementFactory +{ + /// + /// Creates a default Path Element for the given PathFigureType. + /// + /// PathFigureType + /// ICanvasPathElement + internal static ICanvasPathElement CreateDefaultPathElement(PathFigureType figureType) + { + if (figureType == PathFigureType.FillRule) + { + return new FillRuleElement(); + } + + static ICanvasPathElement Throw() => throw new ArgumentException("Creation of Only Default FillRuleElement is supported."); + + return Throw(); + } + + /// + /// Creates a default Path Element for the given PathElementType. + /// + /// PathElementType + /// ICanvasPathElement + internal static ICanvasPathElement CreateDefaultPathElement(PathElementType elementType) + { + if (elementType == PathElementType.ClosePath) + { + return new ClosePathElement(); + } + + static ICanvasPathElement Throw() => throw new ArgumentException("Creation of Only Default ClosePathElement is supported."); + + return Throw(); + } + + /// + /// Instantiates a PathElement based on the PathFigureType. + /// + /// PathFigureType + /// Match object + /// Index of the path element in the Path data + /// ICanvasPathElement + internal static ICanvasPathElement CreatePathFigure(PathFigureType figureType, Match match, int index) + { + var element = CreatePathElement(figureType); + element.Initialize(match, index); + return element; + } + + /// + /// Instantiates a PathElement based on the PathFigureType. + /// + /// PathFigureType + /// Capture object + /// Index of the capture + /// Indicates whether the coordinates are absolute or relative + /// ICanvasPathElement + internal static ICanvasPathElement CreateAdditionalPathFigure(PathFigureType figureType, Capture capture, int index, bool isRelative) + { + var element = CreatePathElement(figureType); + element.InitializeAdditional(capture, index, isRelative); + return element; + } + + /// + /// Instantiates a PathElement based on the PathElementType. + /// + /// PathElementType + /// Match object + /// Index of the path element in the Path data + /// ICanvasPathElement + internal static ICanvasPathElement CreatePathElement(PathElementType elementType, Match match, int index) + { + var element = CreatePathElement(elementType); + element.Initialize(match, index); + return element; + } + + /// + /// Instantiates a PathElement based on the PathElementType. + /// + /// PathElementType + /// Capture object + /// Index of the capture + /// Indicates whether the coordinates are absolute or relative + /// ICanvasPathElement + internal static ICanvasPathElement CreateAdditionalPathElement(PathElementType elementType, Capture capture, int index, bool isRelative) + { + // Additional attributes in MoveTo Command must be converted + // to Line commands + if (elementType == PathElementType.MoveTo) + { + elementType = PathElementType.Line; + } + + var element = CreatePathElement(elementType); + element.InitializeAdditional(capture, index, isRelative); + return element; + } + + /// + /// Instantiates a PathElement based on the PathFigureType. + /// + /// PathFigureType + /// ICanvasPathElement + private static ICanvasPathElement CreatePathElement(PathFigureType figureType) + { + return figureType switch + { + PathFigureType.FillRule => new FillRuleElement(), + PathFigureType.PathFigure => new CanvasPathFigure(), + PathFigureType.EllipseFigure => new CanvasEllipseFigure(), + PathFigureType.PolygonFigure => new CanvasPolygonFigure(), + PathFigureType.RectangleFigure => new CanvasRectangleFigure(), + PathFigureType.RoundedRectangleFigure => new CanvasRoundRectangleFigure(), + _ => throw new ArgumentOutOfRangeException(nameof(figureType), figureType, "Invalid PathFigureType!") + }; + } + + /// + /// Instantiates a PathElement based on the PathElementType. + /// + /// PathElementType + /// ICanvasPathElement + private static ICanvasPathElement CreatePathElement(PathElementType elementType) + { + return elementType switch + { + PathElementType.MoveTo => new MoveToElement(), + PathElementType.Line => new LineElement(), + PathElementType.HorizontalLine => new HorizontalLineElement(), + PathElementType.VerticalLine => new VerticalLineElement(), + PathElementType.QuadraticBezier => new QuadraticBezierElement(), + PathElementType.SmoothQuadraticBezier => new SmoothQuadraticBezierElement(), + PathElementType.CubicBezier => new CubicBezierElement(), + PathElementType.SmoothCubicBezier => new SmoothCubicBezierElement(), + PathElementType.Arc => new ArcElement(), + PathElementType.ClosePath => new ClosePathElement(), + _ => throw new ArgumentOutOfRangeException(nameof(elementType), elementType, "Invalid PathElementType!") + }; + } +} diff --git a/components/Media/src/Geometry/Core/RegexFactory.cs b/components/Media/src/Geometry/Core/RegexFactory.cs new file mode 100644 index 00000000..3bbff7f2 --- /dev/null +++ b/components/Media/src/Geometry/Core/RegexFactory.cs @@ -0,0 +1,599 @@ +// 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; + +[assembly: InternalsVisibleTo("UnitTests.UWP")] + +namespace CommunityToolkit.WinUI.Media.Geometry.Core; + +/// +/// Contains all the Regular Expressions which are used for parsing the Win2d Path Mini Language. +/// +internal static class RegexFactory +{ + // Whitespace + private const string Spacer = @"\s*"; + + // Whitespace or comma + private const string SpaceOrComma = @"(?:\s+|\s*,\s*)"; + + // Whitespace or comma or a minus/plus sign (look ahead) + private const string Sep = @"(?:\s+|\s*,\s*|(?=[-+.]))"; + + // Whitespace or comma or a '#' sign (look ahead) + private const string ColorSep = @"(?:\s+|\s*,\s*|(?=[#]))"; + + // Positive Integer + private const string Integer = @"[+-]?[0-9]+"; + + // Positive Integer + private const string PositiveInteger = @"[+]?[0-9]+"; + + // Floating point number + private const string Float = @"(?:[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + + // Positive Floating point number + private const string PositiveFloat = @"(?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; + + // Floating point number between 0 and 1, inclusive + // private const string Float01 = @"(?:(?[Mm]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; + + // Line + private static readonly string Line = $"(?[Ll]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; + + // Horizontal Line + private static readonly string HorizontalLine = $"(?[Hh]{Spacer}{Float}(?:{Sep}{Float})*{Spacer})"; + + // Vertical Line + private static readonly string VerticalLine = $"(?[Vv]{Spacer}{Float}(?:{Sep}{Float})*{Spacer})"; + + // Quadratic Bezier + private static readonly string QuadraticBezier = $"(?[Qq]{Spacer}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos})*{Spacer})"; + + // Smooth Quadratic Bezier + private static readonly string SmoothQuadraticBezier = $"(?[Tt]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; + + // Cubic Bezier + private static readonly string CubicBezier = $"(?[Cc]{Spacer}{Pos}{Sep}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos}{Sep}{Pos})*{Spacer})"; + + // Smooth Cubic Bezier + private static readonly string SmoothCubicBezier = $"(?[Ss]{Spacer}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos})*{Spacer})"; + + // Arc + private static readonly string Arc = $"(?[Aa]{Spacer}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos}" + + $"(?:{Sep}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos})*{Spacer})"; + + // Close Path + private static readonly string ClosePath = $"(?[Zz]{Spacer})"; + + // CanvasPathFigure + private static readonly string CanvasPathFigureRegexString = + $"{MoveTo}" + // M x,y + "(" + + $"{Line}+|" + // L x,y + $"{HorizontalLine}+|" + // H x + $"{VerticalLine}+|" + // V y + $"{QuadraticBezier}+|" + // Q x1,y1 x,y + $"{SmoothQuadraticBezier}+|" + // T x,y + $"{CubicBezier}+|" + // C x1,y1 x2,y2 x,y + $"{SmoothCubicBezier}+|" + // S x2,y2 x,y + $"{Arc}+|" + // A radX, radY, angle, isLargeArc, sweepDirection, x, y + ")+" + + $"{ClosePath}?"; // Close Path (Optional) + + // Fill Rule + private static readonly string FillRule = $"{Spacer}(?[Ff]{Spacer}[01])"; + + // PathFigure + private static readonly string PathFigure = $"{Spacer}(?{CanvasPathFigureRegexString})"; + + // Ellipse Figure + private static readonly string EllipseFigure = $"{Spacer}(?[Oo]{Spacer}{Float}{Sep}{Float}{Sep}{Pos}" + + $"(?:{Sep}{Float}{Sep}{Float}{Sep}{Pos})*)"; + + // Polygon Figure + private static readonly string PolygonFigure = $"{Spacer}(?[Pp]{Spacer}{Integer}{Sep}{Float}{Sep}{Pos}" + + $"(?:{Sep}{Integer}{Sep}{Float}{Sep}{Pos})*)"; + + // Rectangle Figure + private static readonly string RectangleFigure = $"{Spacer}(?[Rr]{Spacer}{Pos}{Sep}{Float}{Sep}{Float}" + + $"(?:{Sep}{Pos}{Sep}{Float}{Sep}{Float})*)"; + + // Rounded Rectangle Figure + private static readonly string RoundedRectangleFigure = $"{Spacer}(?[Uu]{Spacer}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float}" + + $"(?:{Sep}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float})*)"; + + // CanvasGeometry + private static readonly string CanvasGeometryRegexString = + $"{FillRule}?" + // F0 or F1 + "(" + + $"{PathFigure}+|" + // Path Figure + $"{EllipseFigure}+|" + // O radX, radY, centerX, centerY + $"{PolygonFigure}+|" + // P numSides, radius, centerX, centerY + $"{RectangleFigure}+|" + // R x, y, width, height + $"{RoundedRectangleFigure}+" + // U x, y, width, height, radiusX, radiusY + ")+"; + + // MoveTo + private static readonly string MoveToAttributes = $"(?{Float}){Sep}(?{Float})"; + private static readonly string MoveToRegexString = $"{Spacer}(?
(?[Mm]){Spacer}{MoveToAttributes})" + + $"(?{Sep}{Pos})*"; + + // Line + private static readonly string LineAttributes = $"(?{Float}){Sep}(?{Float})"; + private static readonly string LineRegexString = $"{Spacer}(?
(?[Ll]){Spacer}{LineAttributes})" + + $"(?{Sep}{Pos})*"; + + // Horizontal Line + private static readonly string HorizontalLineAttributes = $"(?{Float})"; + private static readonly string HorizontalLineRegexString = $"{Spacer}(?
(?[Hh]){Spacer}{HorizontalLineAttributes})" + + $"(?{Sep}{Float})*"; + + // Vertical Line + private static readonly string VerticalLineAttributes = $"(?{Float})"; + private static readonly string VerticalLineRegexString = $"{Spacer}(?
(?[Vv]){Spacer}{VerticalLineAttributes})" + + $"(?{Sep}{Float})*"; + + // Quadratic Bezier + private static readonly string QuadraticBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; + private static readonly string QuadraticBezierRegexString = $"{Spacer}(?
(?[Qq]){Spacer}{QuadraticBezierAttributes})" + + $"(?{Sep}{Pos}{Sep}{Pos})*"; + + // Smooth Quadratic Bezier + private static readonly string SmoothQuadraticBezierAttributes = $"(?{Float}){Sep}(?{Float})"; + private static readonly string SmoothQuadraticBezierRegexString = $"{Spacer}(?
(?[Tt]){Spacer}{SmoothQuadraticBezierAttributes})" + + $"(?{Sep}{Pos})*"; + + // Cubic Bezier + private static readonly string CubicBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}" + + $"(?{Float}){Sep}(?{Float})"; + + private static readonly string CubicBezierRegexString = $"{Spacer}(?
(?[Cc]){Spacer}{CubicBezierAttributes})" + + $"(?{Sep}{Pos}{Sep}{Pos}{Sep}{Pos})*"; + + // Smooth Cubic Bezier + private static readonly string SmoothCubicBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; + private static readonly string SmoothCubicBezierRegexString = $"{Spacer}(?
(?[Ss]){Spacer}{SmoothCubicBezierAttributes})" + + $"(?{Sep}{Pos}{Sep}{Pos})*"; + + // Arc + private static readonly string ArcAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){SpaceOrComma}" + + $"(?[01]){SpaceOrComma}(?[01]){Sep}(?{Float}){Sep}(?{Float})"; + + private static readonly string ArcRegexString = $"{Spacer}(?
(?[Aa]){Spacer}{ArcAttributes})" + + $"(?{Sep}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos})*"; + + // Close Path + private static readonly string ClosePathRegexString = $"{Spacer}(?
(?[Zz])){Spacer}"; + + // Fill Rule + private static readonly string FillRuleRegexString = $"{Spacer}(?
(?[Ff]){Spacer}(?[01]))"; + + // Path Figure + private static readonly string PathFigureRegexString = $"{Spacer}(?
{PathFigure})"; + + // Ellipse Figure + private static readonly string EllipseFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}" + + $"(?{Float}){Sep}(?{Float})"; + + private static readonly string EllipseFigureRegexString = $"{Spacer}(?
(?[Oo]){Spacer}{EllipseFigureAttributes})" + + $"(?{Sep}{Float}{Sep}{Float}{Sep}{Pos})*"; + + // Polygon Figure + private static readonly string PolygonFigureAttributes = $"(?{Integer}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; + private static readonly string PolygonFigureRegexString = $"{Spacer}(?
(?[Pp]){Spacer}{PolygonFigureAttributes})" + + $"(?{Sep}{Integer}{Sep}{Float}{Sep}{Pos})*"; + + // Rectangle Figure + private static readonly string RectangleFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; + private static readonly string RectangleFigureRegexString = $"{Spacer}(?
(?[Rr]){Spacer}{RectangleFigureAttributes})" + + $"(?{Sep}{Pos}{Sep}{Float}{Sep}{Float})*"; + + // Rectangle Figure + private static readonly string RoundedRectangleFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})" + + $"{Sep}(?{Float}){Sep}(?{Float})"; + + private static readonly string RoundedRectangleFigureRegexString = $"{Spacer}(?
(?[Uu]){Spacer}{RoundedRectangleFigureAttributes})" + + $"(?{Sep}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float})*"; + + // ARGB Color + private static readonly string HexColor = $"(?:#?(?:{Hex}{{2}})?{Hex}{{6}})"; + + // Alpha + private static readonly string Alpha = $"(?{Hex}{{2}})"; + + // Red + private static readonly string Red = $"(?{Hex}{{2}})"; + + // Green + private static readonly string Green = $"(?{Hex}{{2}})"; + + // Blue + private static readonly string Blue = $"(?{Hex}{{2}})"; + + // Hexadecimal Color + private static readonly string RgbColor = $"(?{HexColor})"; + + // HDR Color (Vector4 in which each component has a value between 0 and 1, inclusive) + private static readonly string HdrColor = $"(?{Float01}{Sep}{Float01}{Sep}{Float01}{Sep}{Float01})"; + + // Hexadecimal Color Attributes + private static readonly string RgbColorAttributes = $"(?#{{0,1}}{Alpha}{{0,1}}{Red}{Green}{Blue})"; + + // HDR Color Attributes (Vector4 in which each component has a value between 0 and 1, inclusive) + private static readonly string HdrColorAttributes = $"(?(?{Float01}){Sep}(?{Float01}){Sep}(?{Float01}){Sep}(?{Float01}))"; + + private static readonly string ColorRegexString = $"(?:{RgbColorAttributes}|{HdrColorAttributes})"; + + // Start Point + private static readonly string StartPoint = $"(?[Mm]{Spacer}{Pos}{Spacer})"; + + // End Point + private static readonly string EndPoint = $"(?[Zz]{Spacer}{Pos}{Spacer})"; + + // Opacity + private static readonly string Opacity = $"(?[Oo]{Spacer}{Float01}{Spacer})"; + + // Alpha Mode + private static readonly string AlphaMode = $"(?[Aa]{Spacer}[012]{Spacer})"; + + // Buffer Precision + private static readonly string BufferPrecision = $"(?[Bb]{Spacer}[01234]{Spacer})"; + + // Edge Behavior + private static readonly string EdgeBehavior = $"(?[Ee]{Spacer}[012]{Spacer})"; + + // PreInterpolation Color Space + private static readonly string PreColorSpace = $"(?[Pp]{Spacer}[012]{Spacer})"; + + // PostInterpolation Color Space + private static readonly string PostColorSpace = $"(?[Rr]{Spacer}[012]{Spacer})"; + + // Radius in X-axis + private static readonly string RadiusX = $"(?{Float})"; + + // Radius in Y-axis + private static readonly string RadiusY = $"(?{Float})"; + + // Center location on X-axis + private static readonly string CenterX = $"(?{Float})"; + + // Center location on Y-axis + private static readonly string CenterY = $"(?{Float})"; + + // Origin Offset + private static readonly string OriginOffset = $"(?[Ff]{Spacer}{Pos}{Spacer})"; + + // GradientStops + private static readonly string GradientStops = $"(?[Ss]{Spacer}{Float01}{ColorSep}{HexColor}(?:{Sep}{Float01}{ColorSep}{HexColor})*{Spacer})"; + + // GradientStopHdrs + private static readonly string GradientStopHdrs = $"(?[Ss]{Spacer}{Float01}{Sep}{HdrColor}(?:{Sep}{Float01}{Sep}{HdrColor})*{Spacer})"; + + // Solid Color Brush + private static readonly string SolidColorBrush = $"(?[Ss][Cc]{Spacer}(?:{RgbColor}|{HdrColor}){Spacer}{Opacity}?)"; + + // LinearGradient + private static readonly string LinearGradient = $"(?[Ll][Gg]{Spacer}{StartPoint}{EndPoint}" + + $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{PreColorSpace}?{PostColorSpace}?" + + $"{GradientStops}+{Spacer})"; + + // RadialGradient + private static readonly string RadialGradient = $"(?[Rr][Gg]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + + $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{OriginOffset}?{PreColorSpace}?{PostColorSpace}?" + + $"{GradientStops}+{Spacer})"; + + // LinearGradientHdr + private static readonly string LinearGradientHdr = $"(?[Ll][Hh]{Spacer}{StartPoint}{EndPoint}" + + $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{PreColorSpace}?{PostColorSpace}?" + + $"{GradientStopHdrs}+{Spacer})"; + + // RadialGradientHdr + private static readonly string RadialGradientHdr = $"(?[Rr][Hh]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + + $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{OriginOffset}?{PreColorSpace}?{PostColorSpace}?" + + $"{GradientStopHdrs}+{Spacer})"; + + // Regex for the CanvasBrush + private static readonly string CanvasBrushRegexString = $"(?{SolidColorBrush}|{LinearGradient}|{RadialGradient}|{LinearGradientHdr}|{RadialGradientHdr})"; + + // Start Point + private static readonly string StartPointAttr = $"(?:[Mm]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; + + // End Point + private static readonly string EndPointAttr = $"(?:[Zz]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; + + // Opacity + private static readonly string OpacityAttr = $"(?:[Oo]{Spacer}(?{Float01}){Spacer})"; + + // Alpha Mode + private static readonly string AlphaModeAttr = $"(?:[Aa]{Spacer}(?[012]){Spacer})"; + + // Buffer Precision + private static readonly string BufferPrecisionAttr = $"(?:[Bb]{Spacer}(?[01234]){Spacer})"; + + // Edge Behavior + private static readonly string EdgeBehaviorAttr = $"(?:[Ee]{Spacer}(?[012]){Spacer})"; + + // PreInterpolation Color Space + private static readonly string PreColorSpaceAttr = $"(?:[Pp]{Spacer}(?[012]){Spacer})"; + + // PostInterpolation Color Space + private static readonly string PostColorSpaceAttr = $"(?:[Rr]{Spacer}(?[012]){Spacer})"; + + // Origin Offset + private static readonly string OriginOffsetAttr = $"(?[Ff]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; + + // GradientStop Attributes + private static readonly string GradientStopAttributes = $"(?{Float01}){ColorSep}{RgbColorAttributes}"; + private static readonly string GradientStopMainAttributes = $"(?
[Ss]{Spacer}{GradientStopAttributes})"; + private static readonly string GradientStopRegexString = $"(?{GradientStopMainAttributes}" + $"(?{Sep}{Float01}{ColorSep}{HexColor})*{Spacer})"; + + // GradientStopHdr Attributes + private static readonly string GradientStopHdrAttributes = $"(?{Float01}){Sep}{HdrColorAttributes}"; + private static readonly string GradientStopHdrMainAttributes = $"(?
(?[Ss]){Spacer}{GradientStopHdrAttributes})"; + private static readonly string GradientStopHdrRegexString = $"(?{GradientStopHdrMainAttributes}" + $"(?{Sep}{Float01}{Sep}{HdrColor})*{Spacer})"; + + // Regex for SolidColorBrush Attributes + private static readonly string SolidColorBrushRegexString = $"(?:[Ss][Cc]{Spacer}(?:{RgbColorAttributes}|{HdrColorAttributes}){Spacer}{OpacityAttr}?)"; + + // Regex for LinearGradient Attributes + private static readonly string LinearGradientRegexString = $"[Ll][Gg]{Spacer}{StartPointAttr}{EndPointAttr}" + + $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?" + + $"{EdgeBehaviorAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + + $"{GradientStops}+{Spacer}"; + + // Regex for RadialGradient Attributes + private static readonly string RadialGradientRegexString = $"[Rr][Gg]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + + $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?{EdgeBehaviorAttr}?" + + $"{OriginOffsetAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + + $"{GradientStops}+{Spacer}"; + + // Regex for LinearGradientHdr Attributes + private static readonly string LinearGradientHdrRegexString = $"[Ll][Hh]{Spacer}{StartPointAttr}{EndPointAttr}" + + $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?" + + $"{EdgeBehaviorAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + + $"{GradientStopHdrs}+{Spacer}"; + + // Regex for RadialGradientHdr Attributes + private static readonly string RadialGradientHdrRegexString = $"[Rr][Hh]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + + $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?{EdgeBehaviorAttr}?" + + $"{OriginOffsetAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + + $"{GradientStopHdrs}+{Spacer}"; + + // CanvasStrokeStyle attributes + private static readonly string DashStyle = $"(?:[Dd][Ss]{Spacer}(?[01234]){Spacer})"; + private static readonly string LineJoin = $"(?:[Ll][Jj]{Spacer}(?[0123]){Spacer})"; + private static readonly string MiterLimit = $"(?:[Mm][Ll]{Spacer}(?{Float}){Spacer})"; + private static readonly string DashOffset = $"(?:[Dd][Oo]{Spacer}(?{Float}){Spacer})"; + private static readonly string StartCap = $"(?:[Ss][Cc]{Spacer}(?[0123]){Spacer})"; + private static readonly string EndCap = $"(?:[Ee][Cc]{Spacer}(?[0123]){Spacer})"; + private static readonly string DashCap = $"(?:[Dd][Cc]{Spacer}(?[0123]){Spacer})"; + private static readonly string TransformBehavior = $"(?:[Tt][Bb]{Spacer}(?[012]){Spacer})"; + private static readonly string CustomDashAttribute = $"(?{Float}){Sep}(?{Float})"; + private static readonly string CustomDashStyle = $"(?[Cc][Dd][Ss]{Spacer}(?
{CustomDashAttribute})" + $"(?{Sep}{Float}{Sep}{Float})*{Spacer})"; + + // CanvasStrokeStyle Regex + private static readonly string CanvasStrokeStyleRegexString = $"(?[Cc][Ss][Ss]{Spacer}{DashStyle}?{LineJoin}?{MiterLimit}?{DashOffset}?" + + $"{StartCap}?{EndCap}?{DashCap}?{TransformBehavior}?{CustomDashStyle}?)"; + + // CanvasStroke Regex + private static readonly string CanvasStrokeRegexString = $"(?[Ss][Tt]{Spacer}" + + $"(?{Float}){Spacer}" + + $"{CanvasBrushRegexString}{Spacer}" + + $"{CanvasStrokeStyleRegexString}?)"; + + private static readonly Dictionary PathFigureRegexes; + private static readonly Dictionary PathFigureAttributeRegexes; + private static readonly Dictionary PathElementRegexes; + private static readonly Dictionary PathElementAttributeRegexes; + private static readonly Dictionary BrushRegexes; + private static readonly Dictionary GradientStopAttributeRegexes; + + /// + /// Gets the Regex to perform validation of Path data. + /// + public static Regex ValidationRegex { get; } + + /// + /// Gets the Regex for parsing the CanvasGeometry string. + /// + public static Regex CanvasGeometryRegex { get; } + + /// + /// Gets the Regex for parsing Hexadecimal Color string. + /// + public static Regex ColorRegex { get; } + + /// + /// Gets the Regex for parsing the ICanvasBrush string. + /// + public static Regex CanvasBrushRegex { get; } + + /// + /// Gets the Regex for parsing the GradientStop string. + /// + public static Regex GradientStopRegex { get; } + + /// + /// Gets the Regex for parsing the GradientStopHdr string. + /// + public static Regex GradientStopHdrRegex { get; } + + /// + /// Gets the Regex for parsing the CanvasStrokeStyle string. + /// + public static Regex CanvasStrokeStyleRegex { get; } + + /// + /// Gets the Regex for parsing the CanvasStroke string. + /// + public static Regex CanvasStrokeRegex { get; } + + /// + /// Gets the Regex for parsing the CustomDashStyle attributes. + /// + public static Regex CustomDashAttributeRegex { get; } + + /// + /// Initializes static members of the class. + /// + static RegexFactory() + { + PathFigureRegexes = new Dictionary + { + [PathFigureType.FillRule] = new Regex(FillRuleRegexString, RegexOptions.Compiled), + [PathFigureType.PathFigure] = new Regex(PathFigureRegexString, RegexOptions.Compiled), + [PathFigureType.EllipseFigure] = new Regex(EllipseFigureRegexString, RegexOptions.Compiled), + [PathFigureType.PolygonFigure] = new Regex(PolygonFigureRegexString, RegexOptions.Compiled), + [PathFigureType.RectangleFigure] = new Regex(RectangleFigureRegexString, RegexOptions.Compiled), + [PathFigureType.RoundedRectangleFigure] = new Regex(RoundedRectangleFigureRegexString, RegexOptions.Compiled) + }; + + PathFigureAttributeRegexes = new Dictionary + { + // Not Applicable for FillRuleElement + [PathFigureType.FillRule] = null, + + // Not Applicable for CanvasPathFigure + [PathFigureType.PathFigure] = null, + [PathFigureType.EllipseFigure] = new Regex($"{Sep}{EllipseFigureAttributes}", RegexOptions.Compiled), + [PathFigureType.PolygonFigure] = new Regex($"{Sep}{PolygonFigureAttributes}", RegexOptions.Compiled), + [PathFigureType.RectangleFigure] = new Regex($"{Sep}{RectangleFigureAttributes}", RegexOptions.Compiled), + [PathFigureType.RoundedRectangleFigure] = new Regex($"{Sep}{RoundedRectangleFigureAttributes}", RegexOptions.Compiled) + }; + + PathElementRegexes = new Dictionary + { + [PathElementType.MoveTo] = new Regex(MoveToRegexString, RegexOptions.Compiled), + [PathElementType.Line] = new Regex(LineRegexString, RegexOptions.Compiled), + [PathElementType.HorizontalLine] = new Regex(HorizontalLineRegexString, RegexOptions.Compiled), + [PathElementType.VerticalLine] = new Regex(VerticalLineRegexString, RegexOptions.Compiled), + [PathElementType.QuadraticBezier] = new Regex(QuadraticBezierRegexString, RegexOptions.Compiled), + [PathElementType.SmoothQuadraticBezier] = new Regex(SmoothQuadraticBezierRegexString, RegexOptions.Compiled), + [PathElementType.CubicBezier] = new Regex(CubicBezierRegexString, RegexOptions.Compiled), + [PathElementType.SmoothCubicBezier] = new Regex(SmoothCubicBezierRegexString, RegexOptions.Compiled), + [PathElementType.Arc] = new Regex(ArcRegexString, RegexOptions.Compiled), + [PathElementType.ClosePath] = new Regex(ClosePathRegexString, RegexOptions.Compiled) + }; + + PathElementAttributeRegexes = new Dictionary + { + [PathElementType.MoveTo] = new Regex($"{Sep}{MoveToAttributes}", RegexOptions.Compiled), + [PathElementType.Line] = new Regex($"{Sep}{LineAttributes}", RegexOptions.Compiled), + [PathElementType.HorizontalLine] = new Regex($"{Sep}{HorizontalLineAttributes}", RegexOptions.Compiled), + [PathElementType.VerticalLine] = new Regex($"{Sep}{VerticalLineAttributes}", RegexOptions.Compiled), + [PathElementType.QuadraticBezier] = new Regex($"{Sep}{QuadraticBezierAttributes}", RegexOptions.Compiled), + [PathElementType.SmoothQuadraticBezier] = new Regex($"{Sep}{SmoothQuadraticBezierAttributes}", RegexOptions.Compiled), + [PathElementType.CubicBezier] = new Regex($"{Sep}{CubicBezierAttributes}", RegexOptions.Compiled), + [PathElementType.SmoothCubicBezier] = new Regex($"{Sep}{SmoothCubicBezierAttributes}", RegexOptions.Compiled), + [PathElementType.Arc] = new Regex($"{Sep}{ArcAttributes}", RegexOptions.Compiled), + + // Not Applicable for ClosePathElement as it has no attributes + [PathElementType.ClosePath] = null + }; + + BrushRegexes = new Dictionary + { + [BrushType.SolidColor] = new Regex(SolidColorBrushRegexString, RegexOptions.Compiled), + [BrushType.LinearGradient] = new Regex(LinearGradientRegexString, RegexOptions.Compiled), + [BrushType.LinearGradientHdr] = new Regex(LinearGradientHdrRegexString, RegexOptions.Compiled), + [BrushType.RadialGradient] = new Regex(RadialGradientRegexString, RegexOptions.Compiled), + [BrushType.RadialGradientHdr] = new Regex(RadialGradientHdrRegexString, RegexOptions.Compiled) + }; + + GradientStopAttributeRegexes = new Dictionary + { + [GradientStopAttributeType.Main] = new Regex(GradientStopMainAttributes, RegexOptions.Compiled), + [GradientStopAttributeType.Additional] = new Regex($"{Sep}{GradientStopAttributes}", RegexOptions.Compiled), + [GradientStopAttributeType.MainHdr] = new Regex(GradientStopHdrMainAttributes, RegexOptions.Compiled), + [GradientStopAttributeType.AdditionalHdr] = new Regex($"{Sep}{GradientStopHdrAttributes}", RegexOptions.Compiled) + }; + + ValidationRegex = new Regex(@"\s+"); + CanvasGeometryRegex = new Regex(CanvasGeometryRegexString, RegexOptions.Compiled); + ColorRegex = new Regex(ColorRegexString, RegexOptions.Compiled); + CanvasBrushRegex = new Regex(CanvasBrushRegexString, RegexOptions.Compiled); + GradientStopRegex = new Regex(GradientStopRegexString, RegexOptions.Compiled); + GradientStopHdrRegex = new Regex(GradientStopHdrRegexString, RegexOptions.Compiled); + CanvasStrokeStyleRegex = new Regex(CanvasStrokeStyleRegexString, RegexOptions.Compiled); + CanvasStrokeRegex = new Regex(CanvasStrokeRegexString, RegexOptions.Compiled); + CustomDashAttributeRegex = new Regex($"{Sep}{CustomDashAttribute}", RegexOptions.Compiled); + } + + /// + /// Get the Regex for the given PathFigureType + /// + /// PathFigureType + /// Regex + internal static Regex? GetRegex(PathFigureType figureType) + { + return PathFigureRegexes[figureType]; + } + + /// + /// Get the Regex for the given PathElementType + /// + /// PathElementType + /// Regex + internal static Regex? GetRegex(PathElementType elementType) + { + return PathElementRegexes[elementType]; + } + + /// + /// Get the Regex for extracting attributes of the given PathFigureType + /// + /// PathFigureType + /// Regex + internal static Regex? GetAttributesRegex(PathFigureType figureType) + { + return PathFigureAttributeRegexes[figureType]; + } + + /// + /// Get the Regex for extracting attributes of the given PathElementType + /// + /// PathElementType + /// Regex + internal static Regex? GetAttributesRegex(PathElementType elementType) + { + return PathElementAttributeRegexes[elementType]; + } + + /// + /// Gets the Regex for extracting the attributes of the given BrushType + /// + /// BrushType + /// Regex + internal static Regex? GetAttributesRegex(BrushType brushType) + { + return BrushRegexes[brushType]; + } + + /// + /// Gets the Regex for extracting the attributes of the given + /// GradientStopAttributeType + /// + /// GradientStopAttributeType + /// Regex + internal static Regex? GetAttributesRegex(GradientStopAttributeType gsAttrType) + { + return GradientStopAttributeRegexes[gsAttrType]; + } +} diff --git a/components/Media/src/Geometry/CultureShield.cs b/components/Media/src/Geometry/CultureShield.cs new file mode 100644 index 00000000..b8348cf0 --- /dev/null +++ b/components/Media/src/Geometry/CultureShield.cs @@ -0,0 +1,57 @@ +// 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.Globalization; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Class which can be used to encapsulate code statement(s) so that they are executed in a specific culture. +/// +/// Usage example: +/// +/// The following code block will be executed using the French culture. +/// +/// using (new CultureShield("fr-FR")) +/// +/// { +/// +/// ... +/// +/// } +/// +internal readonly ref struct CultureShield +{ + private readonly CultureInfo _prevCulture; + + /// + /// Initializes a new instance of the struct so that the encapsulated code statement(s) can be executed using the specified culture. + /// + /// Usage example: + /// + /// The following code block will be executed using the French culture. + /// + /// using (new CultureShield("fr-FR")) + /// + /// { + /// + /// ... + /// + /// } + /// + /// The culture in which the encapsulated code statement(s) are to be executed. + internal CultureShield(string culture) + { + _prevCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo(culture); + } + + /// + /// Disposes the CultureShield object. + /// + public void Dispose() + { + CultureInfo.CurrentCulture = _prevCulture; + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs new file mode 100644 index 00000000..4e3ca6ce --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs @@ -0,0 +1,70 @@ +// 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; +using Microsoft.Graphics.Canvas.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Abstract base class for all Brush Elements +/// +internal abstract class AbstractCanvasBrushElement : ICanvasBrushElement +{ +#pragma warning disable SA1401 // Fields should be private + protected float _opacity; +#pragma warning restore SA1401 // Fields should be private + + /// + /// Gets or sets the Brush data defining the Brush Element + /// + public string? Data { get; protected set; } + + /// + /// Gets or sets the number of non-whitespace characters in + /// the Brush Data + /// + public int ValidationCount { get; protected set; } + + /// + /// Initializes the Brush Element with the given Capture + /// + /// Capture object + public virtual void Initialize(Capture capture) + { + Data = capture.Value; + + var regex = GetAttributesRegex(); + var match = regex?.Match(Data); + if (match is null || !match.Success) + { + return; + } + + GetAttributes(match); + + // Get the number of non-whitespace characters in the data + ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; + } + + /// + /// Creates the ICanvasBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// ICanvasBrush + public abstract ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator); + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected abstract Regex? GetAttributesRegex(); + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected abstract void GetAttributes(Match match); +} diff --git a/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs new file mode 100644 index 00000000..7a29d1a2 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs @@ -0,0 +1,38 @@ +// 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; +using Microsoft.Graphics.Canvas.Brushes; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Interface for a Brush Element +/// +internal interface ICanvasBrushElement +{ + /// + /// Gets the Brush data defining the Brush Element + /// + string? Data { get; } + + /// + /// Gets the number of non-whitespace characters in + /// the Brush Data + /// + int ValidationCount { get; } + + /// + /// Initializes the Brush Element with the given Capture + /// + /// Capture object + void Initialize(Capture capture); + + /// + /// Creates the ICanvasBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// ICanvasBrush + ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator); +} diff --git a/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs new file mode 100644 index 00000000..471c0425 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs @@ -0,0 +1,210 @@ +// 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; +using System.Numerics; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Parsers; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Represents a CanvasLinearGradientBrush with GradientStops +/// +internal sealed class LinearGradientBrushElement : AbstractCanvasBrushElement +{ + private Vector2 _startPoint; + private Vector2 _endPoint; + private CanvasAlphaMode _alphaMode; + private CanvasBufferPrecision _bufferPrecision; + private CanvasEdgeBehavior _edgeBehavior; + private CanvasColorSpace _preInterpolationColorSpace; + private CanvasColorSpace _postInterpolationColorSpace; + private List _gradientStops; + + /// + /// Initializes a new instance of the class. + /// + /// Capture object + public LinearGradientBrushElement(Capture capture) + { + // Set the default values + _startPoint = Vector2.Zero; + _endPoint = Vector2.Zero; + _opacity = 1f; + _alphaMode = (CanvasAlphaMode)0; + _bufferPrecision = (CanvasBufferPrecision)0; + _edgeBehavior = (CanvasEdgeBehavior)0; + + // Default ColorSpace is sRGB + _preInterpolationColorSpace = CanvasColorSpace.Srgb; + _postInterpolationColorSpace = CanvasColorSpace.Srgb; + _gradientStops = new List(); + + // Initialize + Initialize(capture); + } + + /// + /// Creates the CanvasLinearGradientBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// An instance of + public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) + { + var brush = new CanvasLinearGradientBrush( + resourceCreator, + _gradientStops.ToArray(), + _edgeBehavior, + _alphaMode, + _preInterpolationColorSpace, + _postInterpolationColorSpace, + _bufferPrecision) + { + StartPoint = _startPoint, + EndPoint = _endPoint, + Opacity = _opacity + }; + + return brush; + } + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(BrushType.LinearGradient); + } + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Start Point + float.TryParse(match.Groups["StartX"].Value, out var startX); + float.TryParse(match.Groups["StartY"].Value, out var startY); + _startPoint = new Vector2(startX, startY); + + // End Point + float.TryParse(match.Groups["EndX"].Value, out var endX); + float.TryParse(match.Groups["EndY"].Value, out var endY); + _endPoint = new Vector2(endX, endY); + + // Opacity (optional) + var group = match.Groups["Opacity"]; + if (group.Success) + { + float.TryParse(group.Value, out _opacity); + } + + // Alpha Mode (optional) + group = match.Groups["AlphaMode"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _alphaMode); + } + + // Buffer Precision (optional) + group = match.Groups["BufferPrecision"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _bufferPrecision); + } + + // Edge Behavior (optional) + group = match.Groups["EdgeBehavior"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _edgeBehavior); + } + + // Pre Interpolation ColorSpace (optional) + group = match.Groups["PreColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _preInterpolationColorSpace); + } + + // Post Interpolation ColorSpace (optional) + group = match.Groups["PostColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _postInterpolationColorSpace); + } + + // GradientStops + group = match.Groups["GradientStops"]; + if (group.Success) + { + _gradientStops.Clear(); + foreach (Capture capture in group.Captures) + { + var gradientMatch = RegexFactory.GradientStopRegex.Match(capture.Value); + if (!gradientMatch.Success) + { + continue; + } + + float position; + Color color; + + // Main Attributes + var main = gradientMatch.Groups["Main"]; + if (main.Success) + { + var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Main); + default(ArgumentNullException).ThrowIfNull(regex); + + var mainMatch = regex.Match(main.Value); + default(ArgumentNullException).ThrowIfNull(regex); + float.TryParse(mainMatch.Groups["Position"].Value, out position); + color = ColorParser.Parse(mainMatch); + + _gradientStops.Add(new CanvasGradientStop() + { + Color = color, + Position = position + }); + } + + // Additional Attributes + var additional = gradientMatch.Groups["Additional"]; + if (!additional.Success) + { + continue; + } + + foreach (Capture addCapture in additional.Captures) + { + var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Additional); + default(ArgumentNullException).ThrowIfNull(regex); + + var addMatch = regex.Match(addCapture.Value); + float.TryParse(addMatch.Groups["Position"].Value, out position); + + color = ColorParser.Parse(addMatch); + + _gradientStops.Add(new CanvasGradientStop() + { + Color = color, + Position = position + }); + } + } + + // Sort the stops based on their position + if (_gradientStops.Any()) + { + _gradientStops = _gradientStops.OrderBy(g => g.Position).ToList(); + } + } + } +} diff --git a/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs new file mode 100644 index 00000000..e1680afe --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs @@ -0,0 +1,207 @@ +// 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.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Represents a CanvasLinearGradientBrush with GradientStopHdrs +/// +internal sealed class LinearGradientHdrBrushElement : AbstractCanvasBrushElement +{ + private Vector2 _startPoint; + private Vector2 _endPoint; + private CanvasAlphaMode _alphaMode; + private CanvasBufferPrecision _bufferPrecision; + private CanvasEdgeBehavior _edgeBehavior; + private CanvasColorSpace _preInterpolationColorSpace; + private CanvasColorSpace _postInterpolationColorSpace; + private List _gradientStopHdrs; + + /// + /// Initializes a new instance of the class. + /// + /// Capture object + public LinearGradientHdrBrushElement(Capture capture) + { + // Set the default values + _startPoint = Vector2.Zero; + _endPoint = Vector2.Zero; + _opacity = 1f; + _alphaMode = (CanvasAlphaMode)0; + _bufferPrecision = (CanvasBufferPrecision)0; + _edgeBehavior = (CanvasEdgeBehavior)0; + + // Default ColorSpace is sRGB + _preInterpolationColorSpace = CanvasColorSpace.Srgb; + _postInterpolationColorSpace = CanvasColorSpace.Srgb; + _gradientStopHdrs = new List(); + + // Initialize + Initialize(capture); + } + + /// + /// Creates the CanvasLinearGradientBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// Instance of + public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) + { + var brush = CanvasLinearGradientBrush.CreateHdr( + resourceCreator, + _gradientStopHdrs.ToArray(), + _edgeBehavior, + _alphaMode, + _preInterpolationColorSpace, + _postInterpolationColorSpace, + _bufferPrecision); + + brush.StartPoint = _startPoint; + brush.EndPoint = _endPoint; + brush.Opacity = _opacity; + + return brush; + } + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(BrushType.LinearGradientHdr); + } + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Start Point + float.TryParse(match.Groups["StartX"].Value, out var startX); + float.TryParse(match.Groups["StartY"].Value, out var startY); + _startPoint = new Vector2(startX, startY); + + // End Point + float.TryParse(match.Groups["EndX"].Value, out var endX); + float.TryParse(match.Groups["EndY"].Value, out var endY); + _endPoint = new Vector2(endX, endY); + + // Opacity (optional) + var group = match.Groups["Opacity"]; + if (group.Success) + { + float.TryParse(group.Value, out _opacity); + } + + // Alpha Mode (optional) + group = match.Groups["AlphaMode"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _alphaMode); + } + + // Buffer Precision (optional) + group = match.Groups["BufferPrecision"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _bufferPrecision); + } + + // Edge Behavior (optional) + group = match.Groups["EdgeBehavior"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _edgeBehavior); + } + + // Pre Interpolation ColorSpace (optional) + group = match.Groups["PreColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _preInterpolationColorSpace); + } + + // Post Interpolation ColorSpace (optional) + group = match.Groups["PostColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _postInterpolationColorSpace); + } + + // GradientStopHdrs + group = match.Groups["GradientStops"]; + if (group.Success) + { + _gradientStopHdrs.Clear(); + foreach (Capture capture in group.Captures) + { + var gradientMatch = RegexFactory.GradientStopHdrRegex.Match(capture.Value); + if (!gradientMatch.Success) + { + continue; + } + + // Main Attributes + var main = gradientMatch.Groups["Main"]; + if (main.Success) + { + var mainMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.MainHdr)? + .Match(main.Value); + float.TryParse(mainMatch?.Groups["Position"].Value, out float position); + float.TryParse(mainMatch?.Groups["X"].Value, out float x); + float.TryParse(mainMatch?.Groups["Y"].Value, out float y); + float.TryParse(mainMatch?.Groups["Z"].Value, out float z); + float.TryParse(mainMatch?.Groups["W"].Value, out float w); + + var color = new Vector4(x, y, z, w); + + _gradientStopHdrs.Add(new CanvasGradientStopHdr() + { + Color = color, + Position = position + }); + } + + // Additional Attributes + var additional = gradientMatch.Groups["Additional"]; + if (!additional.Success) + { + continue; + } + + foreach (Capture addCapture in additional.Captures) + { + var addMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.AdditionalHdr)? + .Match(addCapture.Value); + float.TryParse(addMatch?.Groups["Position"].Value, out float position); + float.TryParse(addMatch?.Groups["X"].Value, out float x); + float.TryParse(addMatch?.Groups["Y"].Value, out float y); + float.TryParse(addMatch?.Groups["Z"].Value, out float z); + float.TryParse(addMatch?.Groups["W"].Value, out float w); + + var color = new Vector4(x, y, z, w); + + _gradientStopHdrs.Add(new CanvasGradientStopHdr() + { + Color = color, + Position = position + }); + } + } + + // Sort the stops based on their position + if (_gradientStopHdrs.Any()) + { + _gradientStopHdrs = _gradientStopHdrs.OrderBy(g => g.Position).ToList(); + } + } + } +} diff --git a/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs new file mode 100644 index 00000000..dbc438ec --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs @@ -0,0 +1,233 @@ +// 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.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Parsers; +using Windows.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Represents a CanvasRadialGradientBrush with GradientStops +/// +internal sealed class RadialGradientBrushElement : AbstractCanvasBrushElement +{ + private float _radiusX; + private float _radiusY; + private Vector2 _center; + private Vector2 _originOffset; + private CanvasAlphaMode _alphaMode; + private CanvasBufferPrecision _bufferPrecision; + private CanvasEdgeBehavior _edgeBehavior; + private CanvasColorSpace _preInterpolationColorSpace; + private CanvasColorSpace _postInterpolationColorSpace; + private List _gradientStops; + + /// + /// Initializes a new instance of the class. + /// + /// Capture object + public RadialGradientBrushElement(Capture capture) + { + // Set the default values + _radiusX = 0f; + _radiusY = 0f; + _center = Vector2.Zero; + _originOffset = Vector2.Zero; + _opacity = 1f; + _alphaMode = (CanvasAlphaMode)0; + _bufferPrecision = (CanvasBufferPrecision)0; + _edgeBehavior = (CanvasEdgeBehavior)0; + + // Default ColorSpace is sRGB + _preInterpolationColorSpace = CanvasColorSpace.Srgb; + _postInterpolationColorSpace = CanvasColorSpace.Srgb; + _gradientStops = new List(); + + // Initialize + Initialize(capture); + } + + /// + /// Creates the CanvasLinearGradientBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// Instance of + public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) + { + var brush = new CanvasRadialGradientBrush( + resourceCreator, + _gradientStops.ToArray(), + _edgeBehavior, + _alphaMode, + _preInterpolationColorSpace, + _postInterpolationColorSpace, + _bufferPrecision) + { + RadiusX = _radiusX, + RadiusY = _radiusY, + Center = _center, + OriginOffset = _originOffset, + Opacity = _opacity + }; + + return brush; + } + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(BrushType.RadialGradient); + } + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // RadiusX + float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); + + // Sanitize by taking the absolute value + _radiusX = Math.Abs(_radiusX); + + // RadiusY + float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); + + // Sanitize by taking the absolute value + _radiusY = Math.Abs(_radiusY); + + // CenterX + float.TryParse(match.Groups["CenterX"].Value, out var centerX); + + // CenterY + float.TryParse(match.Groups["CenterY"].Value, out var centerY); + _center = new Vector2(centerX, centerY); + + // Opacity (optional) + var group = match.Groups["Opacity"]; + if (group.Success) + { + float.TryParse(group.Value, out _opacity); + } + + // Origin Offset (optional) + group = match.Groups["OriginOffset"]; + if (group.Success) + { + float.TryParse(match.Groups["OffsetX"].Value, out var offsetX); + float.TryParse(match.Groups["OffsetY"].Value, out var offsetY); + _originOffset = new Vector2(offsetX, offsetY); + } + + // Alpha Mode (optional) + group = match.Groups["AlphaMode"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _alphaMode); + } + + // Buffer Precision (optional) + group = match.Groups["BufferPrecision"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _bufferPrecision); + } + + // Edge Behavior (optional) + group = match.Groups["EdgeBehavior"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _edgeBehavior); + } + + // Pre Interpolation ColorSpace (optional) + group = match.Groups["PreColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _preInterpolationColorSpace); + } + + // Post Interpolation ColorSpace (optional) + group = match.Groups["PostColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _postInterpolationColorSpace); + } + + // Gradient Stops + group = match.Groups["GradientStops"]; + if (group.Success) + { + _gradientStops.Clear(); + foreach (Capture capture in group.Captures) + { + var gradientMatch = RegexFactory.GradientStopRegex.Match(capture.Value); + if (!gradientMatch.Success) + { + continue; + } + + float position; + Color color; + + // Main Attributes + var main = gradientMatch.Groups["Main"]; + if (main.Success) + { + var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Main); + default(ArgumentNullException).ThrowIfNull(regex); + + var mainMatch = regex.Match(main.Value); + float.TryParse(mainMatch.Groups["Position"].Value, out position); + + color = ColorParser.Parse(mainMatch); + + _gradientStops.Add(new CanvasGradientStop() + { + Color = color, + Position = position + }); + } + + // Additional Attributes + var additional = gradientMatch.Groups["Additional"]; + if (!additional.Success) + { + continue; + } + + foreach (Capture addCapture in additional.Captures) + { + var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Additional); + default(ArgumentNullException).ThrowIfNull(regex); + + var addMatch = regex.Match(addCapture.Value); + float.TryParse(addMatch.Groups["Position"].Value, out position); + + color = ColorParser.Parse(addMatch); + + _gradientStops.Add(new CanvasGradientStop() + { + Color = color, + Position = position + }); + } + } + + // Sort the stops based on their position + if (_gradientStops.Any()) + { + _gradientStops = _gradientStops.OrderBy(g => g.Position).ToList(); + } + } + } +} diff --git a/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs new file mode 100644 index 00000000..8e2bd14c --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs @@ -0,0 +1,233 @@ +// 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.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Represents a CanvasRadialGradientBrush with GradientStopHdrs +/// +internal sealed class RadialGradientHdrBrushElement : AbstractCanvasBrushElement +{ + private float _radiusX; + private float _radiusY; + private Vector2 _center; + private Vector2 _originOffset; + private CanvasAlphaMode _alphaMode; + private CanvasBufferPrecision _bufferPrecision; + private CanvasEdgeBehavior _edgeBehavior; + private CanvasColorSpace _preInterpolationColorSpace; + private CanvasColorSpace _postInterpolationColorSpace; + private List _gradientStopHdrs; + + /// + /// Initializes a new instance of the class. + /// + /// Capture object + public RadialGradientHdrBrushElement(Capture capture) + { + // Set the default values + _radiusX = 0f; + _radiusY = 0f; + _center = Vector2.Zero; + _originOffset = Vector2.Zero; + _opacity = 1f; + _alphaMode = (CanvasAlphaMode)0; + _bufferPrecision = (CanvasBufferPrecision)0; + _edgeBehavior = (CanvasEdgeBehavior)0; + + // Default ColorSpace is sRGB + _preInterpolationColorSpace = CanvasColorSpace.Srgb; + _postInterpolationColorSpace = CanvasColorSpace.Srgb; + _gradientStopHdrs = new List(); + + // Initialize + Initialize(capture); + } + + /// + /// Creates the CanvasLinearGradientBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// Instance of + public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) + { + var brush = CanvasRadialGradientBrush.CreateHdr( + resourceCreator, + _gradientStopHdrs.ToArray(), + _edgeBehavior, + _alphaMode, + _preInterpolationColorSpace, + _postInterpolationColorSpace, + _bufferPrecision); + + brush.RadiusX = _radiusX; + brush.RadiusY = _radiusY; + brush.Center = _center; + brush.OriginOffset = _originOffset; + brush.Opacity = _opacity; + + return brush; + } + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(BrushType.RadialGradientHdr); + } + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // RadiusX + float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); + + // Sanitize by taking the absolute value + _radiusX = Math.Abs(_radiusX); + + // RadiusY + float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); + + // Sanitize by taking the absolute value + _radiusY = Math.Abs(_radiusY); + + // CenterX + float.TryParse(match.Groups["CenterX"].Value, out var centerX); + + // CenterY + float.TryParse(match.Groups["CenterY"].Value, out var centerY); + _center = new Vector2(centerX, centerY); + + // Opacity (optional) + var group = match.Groups["Opacity"]; + if (group.Success) + { + float.TryParse(group.Value, out _opacity); + } + + // Origin Offset (optional) + group = match.Groups["OriginOffset"]; + if (group.Success) + { + float.TryParse(match.Groups["OffsetX"].Value, out var offsetX); + float.TryParse(match.Groups["OffsetY"].Value, out var offsetY); + _originOffset = new Vector2(offsetX, offsetY); + } + + // Alpha Mode (optional) + group = match.Groups["AlphaMode"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _alphaMode); + } + + // Buffer Precision (optional) + group = match.Groups["BufferPrecision"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _bufferPrecision); + } + + // Edge Behavior (optional) + group = match.Groups["EdgeBehavior"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _edgeBehavior); + } + + // Pre Interpolation ColorSpace (optional) + group = match.Groups["PreColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _preInterpolationColorSpace); + } + + // Post Interpolation ColorSpace (optional) + group = match.Groups["PostColorSpace"]; + if (group.Success) + { + Enum.TryParse(group.Value, out _postInterpolationColorSpace); + } + + // GradientStopHdrs + group = match.Groups["GradientStops"]; + if (group.Success) + { + _gradientStopHdrs.Clear(); + foreach (Capture capture in group.Captures) + { + var gradientMatch = RegexFactory.GradientStopHdrRegex.Match(capture.Value); + if (!gradientMatch.Success) + { + continue; + } + + float position; + float x = 0, y = 0, z = 0, w = 0; + var main = gradientMatch.Groups["Main"]; + if (main.Success) + { + var mainMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.MainHdr)? + .Match(main.Value); + + float.TryParse(mainMatch?.Groups["Position"].Value, out position); + float.TryParse(mainMatch?.Groups["X"].Value, out x); + float.TryParse(mainMatch?.Groups["Y"].Value, out y); + float.TryParse(mainMatch?.Groups["Z"].Value, out z); + float.TryParse(mainMatch?.Groups["W"].Value, out w); + + var color = new Vector4(x, y, z, w); + + _gradientStopHdrs.Add(new CanvasGradientStopHdr() + { + Color = color, + Position = position + }); + } + + var additional = gradientMatch.Groups["Additional"]; + if (!additional.Success) + { + continue; + } + + foreach (Capture addCapture in additional.Captures) + { + var addMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.AdditionalHdr)? + .Match(addCapture.Value); + + float.TryParse(addMatch?.Groups["Position"].Value, out position); + float.TryParse(addMatch?.Groups["X"].Value, out x); + float.TryParse(addMatch?.Groups["Y"].Value, out y); + float.TryParse(addMatch?.Groups["Z"].Value, out z); + float.TryParse(addMatch?.Groups["W"].Value, out w); + + var color = new Vector4(x, y, z, w); + + _gradientStopHdrs.Add(new CanvasGradientStopHdr() + { + Color = color, + Position = position + }); + } + } + + // Sort the stops based on their position + if (_gradientStopHdrs.Any()) + { + _gradientStopHdrs = _gradientStopHdrs.OrderBy(g => g.Position).ToList(); + } + } + } +} diff --git a/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs new file mode 100644 index 00000000..a1dcd9d9 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs @@ -0,0 +1,72 @@ +// 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; +using Microsoft.Graphics.Canvas.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Parsers; +using Windows.UI; +using Microsoft.UI; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +/// +/// Represents a CanvasSolidColorBrush +/// +internal sealed class SolidColorBrushElement : AbstractCanvasBrushElement +{ + private Color _color; + + /// + /// Initializes a new instance of the class. + /// + /// Capture object + public SolidColorBrushElement(Capture capture) + { + // Set the default values + _color = Colors.Transparent; + _opacity = 1f; + + Initialize(capture); + } + + /// + /// Creates the ICanvasBrush from the parsed data + /// + /// ICanvasResourceCreator object + /// Instance of + public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) + { + return new CanvasSolidColorBrush(resourceCreator, _color) + { + Opacity = _opacity + }; + } + + /// + /// Gets the Regex for extracting Brush Element Attributes + /// + /// Regex + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(BrushType.SolidColor); + } + + /// + /// Gets the Brush Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Parse the Color + _color = ColorParser.Parse(match); + + // Opacity (optional) + var group = match.Groups["Opacity"]; + if (group.Success) + { + float.TryParse(group.Value, out _opacity); + } + } +} diff --git a/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs b/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs new file mode 100644 index 00000000..ffe0bc13 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs @@ -0,0 +1,105 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Abstract base class for all Path Elements +/// +internal abstract class AbstractPathElement : ICanvasPathElement +{ + /// + /// Gets or sets index of the Path Element in the Path Data + /// + public int Index { get; protected set; } = -1; + + /// + /// Gets or sets path data defining the Path Element + /// + public string Data { get; protected set; } = string.Empty; + + /// + /// Gets or sets number of non-whitespace characters in + /// the Path Element Data + /// + public int ValidationCount { get; protected set; } = 0; + + /// + /// Gets or sets a value indicating whether the path element contains + /// absolute or relative coordinates. + /// + public bool IsRelative { get; protected set; } = false; + + /// + /// Initializes the Path Element with the given Match + /// + /// Match object + /// Index within the match + public virtual void Initialize(Match match, int index) + { + var main = match.Groups["Main"]; + Index = index; + Data = main.Value; + var command = match.Groups["Command"].Value[0]; + IsRelative = char.IsLower(command); + + // Get the Path Element attributes + GetAttributes(match); + + // Get the number of non-whitespace characters in the data + ValidationCount = RegexFactory.ValidationRegex.Replace(main.Value, string.Empty).Length; + } + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + public virtual void InitializeAdditional(Capture capture, int index, bool isRelative) + { + Index = index; + Data = capture.Value; + IsRelative = isRelative; + + var match = GetAttributesRegex()!.Match(Data); + if (match.Captures.Count != 1) + { + return; + } + + // Get the Path Element attributes + GetAttributes(match); + + // Get the number of non-whitespace characters in the data + ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public abstract Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement); + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected abstract Regex? GetAttributesRegex(); + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected abstract void GetAttributes(Match match); +} diff --git a/components/Media/src/Geometry/Elements/Path/ArcElement.cs b/components/Media/src/Geometry/Elements/Path/ArcElement.cs new file mode 100644 index 00000000..ecadb1bf --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/ArcElement.cs @@ -0,0 +1,98 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Arc Element in a Path Geometry +/// +internal class ArcElement : AbstractPathElement +{ + private float _radiusX; + private float _radiusY; + private float _angle; + private CanvasArcSize _arcSize; + private CanvasSweepDirection _sweepDirection; + private float _x; + private float _y; + + /// + /// Initializes a new instance of the class. + /// + public ArcElement() + { + _radiusX = _radiusY = _angle = 0; + _arcSize = CanvasArcSize.Small; + _sweepDirection = CanvasSweepDirection.Clockwise; + _sweepDirection = 0; + _x = _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var point = new Vector2(_x, _y); + if (IsRelative) + { + point += currentPoint; + } + + // Execute command + pathBuilder.AddArc(point, _radiusX, _radiusY, _angle, _sweepDirection, _arcSize); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.Arc); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); + + // Sanitize by taking only the absolute value + _radiusX = Math.Abs(_radiusX); + float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); + + // Sanitize by taking only the absolute value + _radiusY = Math.Abs(_radiusY); + + // Angle is provided in degrees + float.TryParse(match.Groups["Angle"].Value, out _angle); + + // Convert angle to radians as CanvasPathBuilder.AddArc() method + // requires the angle to be in radians + _angle *= Scalar.DegreesToRadians; + Enum.TryParse(match.Groups["IsLargeArc"].Value, out _arcSize); + Enum.TryParse(match.Groups["SweepDirection"].Value, out _sweepDirection); + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs new file mode 100644 index 00000000..19a66905 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs @@ -0,0 +1,80 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Ellipse Figure in a Path Geometry +/// +internal class CanvasEllipseFigure : AbstractPathElement +{ + private float _radiusX; + private float _radiusY; + private float _x; + private float _y; + + /// + /// Initializes a new instance of the class. + /// + public CanvasEllipseFigure() + { + _radiusX = _radiusY = _x = _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the EllipseFigure + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the EllipseFigure + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var center = new Vector2(_x, _y); + if (IsRelative) + { + center += currentPoint; + } + + // Execute command + pathBuilder.AddEllipseFigure(center.X, center.Y, _radiusX, _radiusY); + + // No need to update the lastElement or currentPoint here as we are creating + // a separate closed figure here. So current point will not change. + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathFigureType.EllipseFigure); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); + + // Sanitize by taking the absolute value + _radiusX = Math.Abs(_radiusX); + float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); + + // Sanitize by taking the absolute value + _radiusY = Math.Abs(_radiusY); + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs new file mode 100644 index 00000000..75276a1f --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs @@ -0,0 +1,132 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using System.Text.RegularExpressions; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class which contains a collection of ICanvasPathElements +/// which can be used to create CanvasGeometry. +/// +internal class CanvasPathFigure : AbstractPathElement +{ + // Collection of Path Elements + private List _elements; + + public CanvasPathFigure() + { + _elements = new List(); + ValidationCount = 0; + } + + /// + /// Initializes the Path Element with the given Match + /// + /// Match object + /// Index of the path element in the Path data. + public override void Initialize(Match match, int index) + { + Index = index; + var main = match.Groups["Main"]; + Data = main.Value; + + var elements = new List(); + foreach (PathElementType type in Enum.GetValues(typeof(PathElementType))) + { + foreach (Capture elementCapture in match.Groups[type.ToString()].Captures) + { + var elementRootIndex = elementCapture.Index; + var regex = RegexFactory.GetRegex(type); + var elementMatch = regex?.Match(elementCapture.Value); + var isRelative = false; + + // Process the 'Main' Group which contains the Path Command and + // corresponding attributes + if (elementMatch?.Groups["Main"].Captures.Count == 1) + { + var figure = PathElementFactory.CreatePathElement(type, elementMatch, elementRootIndex); + elements.Add(figure); + isRelative = figure.IsRelative; + } + + // Process the 'Additional' Group which contains just the attributes + elements.AddRange(from Capture capture in (elementMatch?.Groups["Additional"].Captures ?? Enumerable.Empty()) + select PathElementFactory.CreateAdditionalPathElement(type, capture, elementRootIndex + capture.Index, isRelative)); + } + } + + // Sort the path elements based on their index value + _elements.AddRange(elements.OrderBy(e => e.Index)); + if (_elements.Count <= 0) + { + return; + } + + // Check if the last path element in the figure is an ClosePathElement + // which would indicate that the path needs to be closed. Otherwise, + // add a default ClosePathElement at the end to indicate that the path + // is not closed. + var lastElement = _elements.ElementAt(_elements.Count - 1); + if ((lastElement as ClosePathElement) == null) + { + _elements.Add(PathElementFactory.CreateDefaultPathElement(PathElementType.ClosePath)); + } + + // Validation Count will be the cumulative sum of the validation count + // of child elements of the PathFigure + ValidationCount = _elements.Sum(x => x.ValidationCount); + } + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + public override void InitializeAdditional(Capture capture, int index, bool isRelative) + { + // Do nothing as this scenario is not valid for CanvasPathFigure + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + foreach (var pathElement in _elements) + { + currentPoint = pathElement.CreatePath(pathBuilder, currentPoint, ref lastElement); + } + + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return null; + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Do nothing + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs new file mode 100644 index 00000000..dbd4a1a5 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.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 System.Numerics; +using Microsoft.Graphics.Canvas.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Polygon Figure in a Path Geometry +/// +internal class CanvasPolygonFigure : AbstractPathElement +{ + private int _numSides; + private float _radius; + private float _x; + private float _y; + + public CanvasPolygonFigure() + { + _numSides = 0; + _radius = _x = _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the PolygonFigure + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the PolygonFigure + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var center = new Vector2(_x, _y); + if (IsRelative) + { + center += currentPoint; + } + + // Execute command + pathBuilder.AddPolygonFigure(_numSides, center.X, center.Y, _radius); + + // No need to update the lastElement or currentPoint here as we are creating + // a separate closed figure here. So current point will not change. + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathFigureType.PolygonFigure); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Sides + int.TryParse(match.Groups["Sides"].Value, out _numSides); + + // Sanitize by taking the absolute value + _numSides = Math.Abs(_numSides); + + // Radius + float.TryParse(match.Groups["Radius"].Value, out _radius); + + // Sanitize by taking the absolute value + _radius = Math.Abs(_radius); + + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs new file mode 100644 index 00000000..18f36cf3 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs @@ -0,0 +1,87 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Rectangle Figure in a Path Geometry +/// +internal sealed class CanvasRectangleFigure : AbstractPathElement +{ + private float _x; + private float _y; + private float _width; + private float _height; + + /// + /// Initializes a new instance of the class. + /// + public CanvasRectangleFigure() + { + _x = _y = _width = _height = 0f; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the PolygonFigure + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the PolygonFigure + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var topLeft = new Vector2(_x, _y); + if (IsRelative) + { + topLeft += currentPoint; + } + + // Execute command + pathBuilder.AddRectangleFigure(topLeft.X, topLeft.Y, _width, _height); + + // No need to update the lastElement or currentPoint here as we are creating + // a separate closed figure here.So current point will not change. + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathFigureType.RectangleFigure); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // X + float.TryParse(match.Groups["X"].Value, out _x); + + // Y + float.TryParse(match.Groups["Y"].Value, out _y); + + // Width + float.TryParse(match.Groups["Width"].Value, out _width); + + // Sanitize by taking the absolute value + _width = Math.Abs(_width); + + // Height + float.TryParse(match.Groups["Height"].Value, out _height); + + // Sanitize by taking the absolute value + _height = Math.Abs(_height); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs new file mode 100644 index 00000000..b6a9f5ff --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs @@ -0,0 +1,101 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the RoundRectangle Figure in a Path Geometry +/// +internal sealed class CanvasRoundRectangleFigure : AbstractPathElement +{ + private float _x; + private float _y; + private float _width; + private float _height; + private float _radiusX; + private float _radiusY; + + /// + /// Initializes a new instance of the class. + /// + public CanvasRoundRectangleFigure() + { + _x = _y = _width = _height = _radiusX = _radiusY = 0f; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the PolygonFigure + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the PolygonFigure + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var topLeft = new Vector2(_x, _y); + if (IsRelative) + { + topLeft += currentPoint; + } + + // Execute command + pathBuilder.AddRoundedRectangleFigure(topLeft.X, topLeft.Y, _width, _height, _radiusX, _radiusY); + + // No need to update the lastElement or currentPoint here as we are creating + // a separate closed figure here.So current point will not change. + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathFigureType.RoundedRectangleFigure); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // X + float.TryParse(match.Groups["X"].Value, out _x); + + // Y + float.TryParse(match.Groups["Y"].Value, out _y); + + // Width + float.TryParse(match.Groups["Width"].Value, out _width); + + // Sanitize by taking the absolute value + _width = Math.Abs(_width); + + // Height + float.TryParse(match.Groups["Height"].Value, out _height); + + // Sanitize by taking the absolute value + _height = Math.Abs(_height); + + // RadiusX + float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); + + // Sanitize by taking the absolute value + _radiusX = Math.Abs(_radiusX); + + // RadiusY + float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); + + // Sanitize by taking the absolute value + _radiusY = Math.Abs(_radiusY); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs b/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs new file mode 100644 index 00000000..a67d7766 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs @@ -0,0 +1,99 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the ClosePath command in a Path Geometry +/// +internal class ClosePathElement : AbstractPathElement +{ + private bool _isFigureClosed; + + /// + /// Initializes a new instance of the class. + /// + public ClosePathElement() + { + _isFigureClosed = false; + } + + /// + /// Initializes the Path Element with the given Match + /// + /// Match object + /// Index of the Path Element in the Path data. + public override void Initialize(Match match, int index) + { + var main = match.Groups["Main"]; + Index = index; + Data = main.Value; + var command = match.Groups["Command"]; + + // If the Command is captured, it means that 'Z' is provided and hence + // the figure must be closed + if (command.Captures.Count == 1) + { + _isFigureClosed = true; + } + + // Get the number of non-whitespace characters in the data + ValidationCount = RegexFactory.ValidationRegex.Replace(main.Value, string.Empty).Length; + } + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + public override void InitializeAdditional(Capture capture, int index, bool isRelative) + { + // Do nothing as this scenario is not valid for this Path Element + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Execute command + pathBuilder.EndFigure(_isFigureClosed ? CanvasFigureLoop.Closed : CanvasFigureLoop.Open); + + // Set Last Element + lastElement = this; + + // Return current point + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + // Attributes are not present + return null; + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Do Nothing + } +} diff --git a/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs b/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs new file mode 100644 index 00000000..514c8c30 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs @@ -0,0 +1,103 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Cubic Bezier Element in a Path Geometry +/// +internal class CubicBezierElement : AbstractPathElement +{ + private float _x1; + private float _y1; + private float _x2; + private float _y2; + private float _x; + private float _y; + + private Vector2 _absoluteControlPoint2; + + /// + /// Initializes a new instance of the class. + /// + public CubicBezierElement() + { + _x1 = _y1 = 0; + _x2 = _y2 = 0; + _x = _y = 0; + _absoluteControlPoint2 = Vector2.Zero; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var controlPoint1 = new Vector2(_x1, _y1); + var controlPoint2 = new Vector2(_x2, _y2); + var point = new Vector2(_x, _y); + + if (IsRelative) + { + controlPoint1 += currentPoint; + controlPoint2 += currentPoint; + point += currentPoint; + } + + // Save the second absolute control point so that it can be used by the following + // SmoothCubicBezierElement (if any) + _absoluteControlPoint2 = controlPoint2; + + // Execute command + pathBuilder.AddCubicBezier(controlPoint1, controlPoint2, point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Gets the Second Control Point of this Cubic Bezier + /// + /// Vector2 + public Vector2 GetControlPoint() + { + return _absoluteControlPoint2; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.CubicBezier); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X1"].Value, out _x1); + float.TryParse(match.Groups["Y1"].Value, out _y1); + float.TryParse(match.Groups["X2"].Value, out _x2); + float.TryParse(match.Groups["Y2"].Value, out _y2); + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/FillRuleElement.cs b/components/Media/src/Geometry/Elements/Path/FillRuleElement.cs new file mode 100644 index 00000000..f4b8110e --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/FillRuleElement.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 System.Numerics; +using Microsoft.Graphics.Canvas.Geometry; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Fill Rule Element in a Path Geometry +/// +internal class FillRuleElement : AbstractPathElement +{ + private CanvasFilledRegionDetermination _fillValue; + + /// + /// Initializes a new instance of the class. + /// + public FillRuleElement() + { + _fillValue = CanvasFilledRegionDetermination.Alternate; + } + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + public override void InitializeAdditional(Capture capture, int index, bool isRelative) + { + // Do nothing as this scenario is not valid for this Path Element + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Execute command + pathBuilder.SetFilledRegionDetermination(_fillValue); + + // Set Last Element + lastElement = this; + + // Return current point + return currentPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + // Not applicable for this Path Element + return null; + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + Enum.TryParse(match.Groups["FillValue"].Value, out _fillValue); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs b/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs new file mode 100644 index 00000000..8d761666 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.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.Numerics; +using Microsoft.Graphics.Canvas.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Horizontal Line Element in a Path Geometry +/// +internal class HorizontalLineElement : AbstractPathElement +{ + private float _x; + + /// + /// Initializes a new instance of the class. + /// + public HorizontalLineElement() + { + _x = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var point = IsRelative ? + new Vector2(currentPoint.X + _x, currentPoint.Y) : new Vector2(_x, currentPoint.Y); + + // Execute command + pathBuilder.AddLine(point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.HorizontalLine); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X"].Value, out _x); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs b/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs new file mode 100644 index 00000000..7ba47b8d --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs @@ -0,0 +1,63 @@ +// 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.Geometry; +using System.Numerics; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Interface for a Path Element which serves +/// as a building block for CanvasPathGeometry +/// +internal interface ICanvasPathElement +{ + /// + /// Gets index of the Path Element in the Path Data + /// + int Index { get; } + + /// + /// Gets path data defining the Path Element + /// + string Data { get; } + + /// + /// Gets number of non-whitespace characters in + /// the Path Element Data + /// + int ValidationCount { get; } + + /// + /// Gets a value indicating whether the path element contains + /// absolute or relative coordinates. + /// + bool IsRelative { get; } + + /// + /// Initializes the Path Element with the given Match + /// + /// Match object + /// Index of the Path Element in the Path data. + void Initialize(Match match, int index); + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + void InitializeAdditional(Capture capture, int index, bool isRelative); + + /// + /// Adds the Path Element to the PathBuilder. + /// + /// CanvasPathBuilder object + /// The current point on the path before the path element is added + /// The previous PathElement in the Path. + /// The current point on the path after the path element is added + Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement); + +} diff --git a/components/Media/src/Geometry/Elements/Path/LineElement.cs b/components/Media/src/Geometry/Elements/Path/LineElement.cs new file mode 100644 index 00000000..80460307 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/LineElement.cs @@ -0,0 +1,72 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Line Element in a Path Geometry +/// +internal class LineElement : AbstractPathElement +{ + private float _x; + private float _y; + + /// + /// Initializes a new instance of the class. + /// + public LineElement() + { + _x = _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var point = new Vector2(_x, _y); + if (IsRelative) + { + point += currentPoint; + } + + // Execute command + pathBuilder.AddLine(point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.Line); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/MoveToElement.cs b/components/Media/src/Geometry/Elements/Path/MoveToElement.cs new file mode 100644 index 00000000..ff66b41c --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/MoveToElement.cs @@ -0,0 +1,85 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the MoveTo Command in a Path Geometry +/// +internal class MoveToElement : AbstractPathElement +{ + private float _x; + private float _y; + + /// + /// Initializes a new instance of the class. + /// + public MoveToElement() + { + _x = _y = 0; + } + + /// + /// Initializes the Path Element with the given Capture + /// + /// Capture object + /// Index of the Path Element in the Path data. + /// Indicates whether the Path Element coordinates are + /// absolute or relative + public override void InitializeAdditional(Capture capture, int index, bool isRelative) + { + // Do nothing as this scenario is not valid for MoveTo Command + // Additional coordinates specified with MoveTo will be converted to Line Commands + } + + /// + /// Adds the Path Element to the PathBuilder. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var point = new Vector2(_x, _y); + if (IsRelative) + { + point += currentPoint; + } + + // Execute command + pathBuilder.BeginFigure(point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.MoveTo); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs b/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs new file mode 100644 index 00000000..ba27e244 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs @@ -0,0 +1,95 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Quadratic Bezier Element in a Path Geometry +/// +internal class QuadraticBezierElement : AbstractPathElement +{ + private float _x1; + private float _y1; + private float _x; + private float _y; + private Vector2 _absoluteControlPoint; + + /// + /// Initializes a new instance of the class. + /// + public QuadraticBezierElement() + { + _x1 = _y1 = 0; + _x = _y = 0; + _absoluteControlPoint = Vector2.Zero; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var controlPoint = new Vector2(_x1, _y1); + var point = new Vector2(_x, _y); + + if (IsRelative) + { + controlPoint += currentPoint; + point += currentPoint; + } + + // Save the absolute control point so that it can be used by the following + // SmoothQuadraticBezierElement (if any) + _absoluteControlPoint = controlPoint; + + // Execute command + pathBuilder.AddQuadraticBezier(controlPoint, point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Gets the Control Point of this Quadratic Bezier + /// + /// Vector2 + public Vector2 GetControlPoint() + { + return _absoluteControlPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.QuadraticBezier); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X1"].Value, out _x1); + float.TryParse(match.Groups["Y1"].Value, out _y1); + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs b/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs new file mode 100644 index 00000000..508f0d42 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs @@ -0,0 +1,115 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Smooth Cubic Bezier Element in a Path Geometry +/// +internal class SmoothCubicBezierElement : AbstractPathElement +{ + private float _x2; + private float _y2; + private float _x; + private float _y; + private Vector2 _absoluteControlPoint2; + + /// + /// Initializes a new instance of the class. + /// + public SmoothCubicBezierElement() + { + _x2 = _y2 = 0; + _x = _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + // Check if the last element was a Cubic Bezier + Vector2 controlPoint1; + if (lastElement is CubicBezierElement cubicBezier) + { + // Reflect the second control point of the cubic bezier over the current point. The + // resulting point will be the first control point of this Bezier. + controlPoint1 = Utils.Reflect(cubicBezier.GetControlPoint(), currentPoint); + } + + // Or if the last element was s Smooth Cubic Bezier + else + { + // If the last element was a Smooth Cubic Bezier then reflect its second control point + // over the current point. The resulting point will be the first control point of this + // Bezier. Otherwise, if the last element was not a Smooth Cubic Bezier then the + // currentPoint will be the first control point of this Bezier + controlPoint1 = lastElement is SmoothCubicBezierElement smoothCubicBezier + ? Utils.Reflect(smoothCubicBezier.GetControlPoint(), currentPoint) + : currentPoint; + } + + var controlPoint2 = new Vector2(_x2, _y2); + var point = new Vector2(_x, _y); + + if (IsRelative) + { + controlPoint2 += currentPoint; + point += currentPoint; + } + + // Save the second absolute control point so that it can be used by the following + // SmoothCubicBezierElement (if any) + _absoluteControlPoint2 = controlPoint2; + + // Execute command + pathBuilder.AddCubicBezier(controlPoint1, controlPoint2, point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Gets the Second Control Point of this Cubic Bezier + /// + /// Vector2 + public Vector2 GetControlPoint() + { + return _absoluteControlPoint2; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.SmoothCubicBezier); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X2"].Value, out _x2); + float.TryParse(match.Groups["Y2"].Value, out _y2); + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs b/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs new file mode 100644 index 00000000..6746e128 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs @@ -0,0 +1,104 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Smooth Quadratic Bezier Element in a Path Geometry +/// +internal class SmoothQuadraticBezierElement : AbstractPathElement +{ + private float _x; + private float _y; + private Vector2 _absoluteControlPoint; + + /// + /// Initializes a new instance of the class. + /// + public SmoothQuadraticBezierElement() + { + _x = _y = 0; + _absoluteControlPoint = Vector2.Zero; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + // Check if the last element was a Quadratic Bezier + if (lastElement is QuadraticBezierElement quadBezier) + { + // Reflect the control point of the Quadratic Bezier over the current point. The + // resulting point will be the control point of this Bezier. + _absoluteControlPoint = Utils.Reflect(quadBezier.GetControlPoint(), currentPoint); + } + + // Or if the last element was s Smooth Quadratic Bezier + else + { + // If the last element was a Smooth Quadratic Bezier then reflect its control point + // over the current point. The resulting point will be the control point of this + // Bezier. Otherwise, if the last element was not a Smooth Quadratic Bezier, + // then the currentPoint will be the control point of this Bezier. + _absoluteControlPoint = lastElement is SmoothQuadraticBezierElement smoothQuadBezier + ? Utils.Reflect(smoothQuadBezier.GetControlPoint(), currentPoint) + : currentPoint; + } + + var point = new Vector2(_x, _y); + + if (IsRelative) + { + point += currentPoint; + } + + // Execute command + pathBuilder.AddQuadraticBezier(_absoluteControlPoint, point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Gets the Control Point of this Quadratic Bezier + /// + /// Vector2 + public Vector2 GetControlPoint() + { + return _absoluteControlPoint; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.SmoothQuadraticBezier); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["X"].Value, out _x); + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs b/components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs new file mode 100644 index 00000000..c3bacc4a --- /dev/null +++ b/components/Media/src/Geometry/Elements/Path/VerticalLineElement.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.Numerics; +using Microsoft.Graphics.Canvas.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +/// +/// Class representing the Vertical Line Element in a Path Geometry +/// +internal class VerticalLineElement : AbstractPathElement +{ + private float _y; + + /// + /// Initializes a new instance of the class. + /// + public VerticalLineElement() + { + _y = 0; + } + + /// + /// Adds the Path Element to the Path. + /// + /// CanvasPathBuilder object + /// The last active location in the Path before adding + /// the Path Element + /// The previous PathElement in the Path. + /// The latest location in the Path after adding the Path Element + public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) + { + // Calculate coordinates + var point = IsRelative ? + new Vector2(currentPoint.X, currentPoint.Y + _y) : new Vector2(currentPoint.X, _y); + + // Execute command + pathBuilder.AddLine(point); + + // Set Last Element + lastElement = this; + + // Return current point + return point; + } + + /// + /// Get the Regex for extracting Path Element Attributes + /// + /// Instance of + protected override Regex? GetAttributesRegex() + { + return RegexFactory.GetAttributesRegex(PathElementType.VerticalLine); + } + + /// + /// Gets the Path Element Attributes from the Match + /// + /// Match object + protected override void GetAttributes(Match match) + { + float.TryParse(match.Groups["Y"].Value, out _y); + } +} diff --git a/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs new file mode 100644 index 00000000..db82f92b --- /dev/null +++ b/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.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. + +using Microsoft.Graphics.Canvas; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +/// +/// Abstract base class for Stroke Element. +/// +internal abstract class AbstractCanvasStrokeElement : ICanvasStrokeElement +{ + /// + /// Gets or sets the Stroke data defining the Brush Element + /// + public string? Data { get; protected set; } + + /// + /// Gets or sets the number of non-whitespace characters in the Stroke Data. + /// + public int ValidationCount { get; protected set; } + + /// + /// Initializes the Stroke Element with the given Capture. + /// + /// Match object + public virtual void Initialize(Match match) + { + Data = match.Value; + + if (!match.Success) + { + return; + } + + GetAttributes(match); + + // Update the validation count + Validate(); + } + + /// + /// Gets the number of non-whitespace characters in the data. + /// + protected virtual void Validate() + { + default(ArgumentNullException).ThrowIfNull(Data); + ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; + } + + /// + /// Creates the ICanvasStroke from the parsed data. + /// + /// ICanvasStroke + public abstract ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator); + + /// + /// Gets the Stroke Element Attributes from the Match. + /// + /// Match object + protected abstract void GetAttributes(Match match); +} diff --git a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs new file mode 100644 index 00000000..9bddb36c --- /dev/null +++ b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs @@ -0,0 +1,105 @@ +// 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; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; +using CommunityToolkit.WinUI.Media.Geometry.Parsers; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +/// +/// Represents a Stroke Element. +/// +internal sealed class CanvasStrokeElement : AbstractCanvasStrokeElement +{ + private float _width; + private ICanvasBrushElement? _brush; + private ICanvasStrokeStyleElement? _style; + private int _widthValidationCount; + + /// + /// Initializes a new instance of the class. + /// + /// Match object + public CanvasStrokeElement(Match match) + { + _width = 1f; + _brush = null; + _style = null; + _widthValidationCount = 0; + + Initialize(match); + } + + /// + /// Creates the ICanvasStroke from the parsed data. + /// + /// ICanvasStroke + public override ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator) + { + default(ArgumentNullException).ThrowIfNull(_brush); + default(ArgumentNullException).ThrowIfNull(_style); + + return new CanvasStroke(_brush.CreateBrush(resourceCreator), _width, _style.Style); + } + + /// + /// Gets the Stroke Element Attributes from the Match. + /// + /// Match object + protected override void GetAttributes(Match match) + { + // Stroke Width + var group = match.Groups["StrokeWidth"]; + float.TryParse(group.Value, out _width); + + // Sanitize by taking the absolute value + _width = Math.Abs(_width); + + _widthValidationCount = RegexFactory.ValidationRegex.Replace(group.Value, string.Empty).Length; + + // Stroke Brush + group = match.Groups["CanvasBrush"]; + if (group.Success) + { + _brush = CanvasBrushParser.Parse(group.Value); + } + + // If the ICanvasBrushElement was not created, then the ICanvasStroke cannot be created + if (_brush == null) + { + static void Throw(string value) => throw new ArgumentException($"Unable to create a valid ICanvasBrush for the ICanvasStroke with the following Brush data - '{value}'."); + + Throw(group.Value); + } + + // Stroke Style + _style = CanvasStrokeStyleParser.Parse(match); + } + + /// + /// Gets the number of non-whitespace characters in the data. + /// + protected override void Validate() + { + // Add 2 to the Validation Count to include the stroke command 'ST' + ValidationCount += 2; + + // StrokeWidth Validation Count + ValidationCount += _widthValidationCount; + + // Stroke Brush Validation Count + if (_brush != null) + { + ValidationCount += _brush.ValidationCount; + } + + // Stroke Style Validation Count + if (_style != null) + { + ValidationCount += _style.ValidationCount; + } + } +} diff --git a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs new file mode 100644 index 00000000..02f20e06 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs @@ -0,0 +1,177 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +/// +/// Represents a CanvasStrokeStyle Element. +/// +internal sealed class CanvasStrokeStyleElement : ICanvasStrokeStyleElement +{ + /// + /// Gets the Stroke data defining the Brush Element + /// + public string Data { get; private set; } + + /// + /// Gets the number of non-whitespace characters in + /// the Stroke Data + /// + public int ValidationCount { get; private set; } + + /// + /// Gets the CanvasStrokeStyle obtained by parsing + /// the style data. + /// + public CanvasStrokeStyle Style { get; private set; } + + /// + /// Initializes a new instance of the class. + /// + /// The matching data + public CanvasStrokeStyleElement(Match match) + { + Data = match.Groups["CanvasStrokeStyle"].Value; + + Style = new CanvasStrokeStyle(); + + Initialize(match); + + // Get the number of non-whitespace characters in the data + ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; + } + + /// + /// Initializes the Stroke Element with the given Capture. + /// + /// Match object + public void Initialize(Match match) + { + var group = match.Groups["CanvasStrokeStyle"]; + if (group.Success) + { + // DashStyle + group = match.Groups["DashStyle"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasDashStyle dashStyle); + Style.DashStyle = dashStyle; + } + + // LineJoin + group = match.Groups["LineJoin"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasLineJoin lineJoin); + Style.LineJoin = lineJoin; + } + + // MiterLimit + group = match.Groups["MiterLimit"]; + if (group.Success) + { + float.TryParse(group.Value, out var miterLimit); + + // Sanitize by taking the absolute value + Style.MiterLimit = Math.Abs(miterLimit); + } + + // DashOffset + group = match.Groups["DashOffset"]; + if (group.Success) + { + float.TryParse(group.Value, out var dashOffset); + Style.DashOffset = dashOffset; + } + + // StartCap + group = match.Groups["StartCap"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasCapStyle capStyle); + Style.StartCap = capStyle; + } + + // EndCap + group = match.Groups["EndCap"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasCapStyle capStyle); + Style.EndCap = capStyle; + } + + // DashCap + group = match.Groups["DashCap"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasCapStyle capStyle); + Style.DashCap = capStyle; + } + + // TransformBehavior + group = match.Groups["TransformBehavior"]; + if (group.Success) + { + Enum.TryParse(group.Value, out CanvasStrokeTransformBehavior transformBehavior); + Style.TransformBehavior = transformBehavior; + } + + // CustomDashStyle + group = match.Groups["CustomDashStyle"]; + if (group.Success) + { + List dashes = new List(); + group = match.Groups["Main"]; + if (group.Success) + { + if (float.TryParse(match.Groups["DashSize"].Value, out var dashSize)) + { + // Sanitize by taking the absolute value + dashes.Add(Math.Abs(dashSize)); + } + + if (float.TryParse(match.Groups["SpaceSize"].Value, out var spaceSize)) + { + // Sanitize by taking the absolute value + dashes.Add(Math.Abs(spaceSize)); + } + } + + group = match.Groups["Additional"]; + if (group.Success) + { + foreach (Capture capture in group.Captures) + { + var dashMatch = RegexFactory.CustomDashAttributeRegex.Match(capture.Value); + if (!dashMatch.Success) + { + continue; + } + + if (float.TryParse(dashMatch.Groups["DashSize"].Value, out var dashSize)) + { + // Sanitize by taking the absolute value + dashes.Add(Math.Abs(dashSize)); + } + + if (float.TryParse(dashMatch.Groups["SpaceSize"].Value, out var spaceSize)) + { + // Sanitize by taking the absolute value + dashes.Add(Math.Abs(spaceSize)); + } + } + } + + // Any valid dashes? + if (dashes.Any()) + { + Style.CustomDashStyle = dashes.ToArray(); + } + } + } + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs new file mode 100644 index 00000000..ec5d23ad --- /dev/null +++ b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs @@ -0,0 +1,35 @@ +// 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; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +/// +/// Interface for Stroke Element +/// +internal interface ICanvasStrokeElement +{ + /// + /// Gets the Stroke data defining the Brush Element. + /// + string? Data { get; } + + /// + /// Gets the number of non-whitespace characters in the Stroke Data. + /// + int ValidationCount { get; } + + /// + /// Initializes the Stroke Element with the given Capture. + /// + /// Match object + void Initialize(Match match); + + /// + /// Creates the ICanvasStroke from the parsed data. + /// + /// ICanvasStroke + ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator); +} diff --git a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs new file mode 100644 index 00000000..a4e4c657 --- /dev/null +++ b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs @@ -0,0 +1,34 @@ +// 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.Geometry; + +namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +/// +/// Interface for the CanvasStrokeStyle Element +/// +internal interface ICanvasStrokeStyleElement +{ + /// + /// Gets the Stroke data defining the Brush Element. + /// + string Data { get; } + + /// + /// Gets the number of non-whitespace characters in the Stroke Data. + /// + int ValidationCount { get; } + + /// + /// Gets the CanvasStrokeStyle obtained by parsing the style data. + /// + CanvasStrokeStyle Style { get; } + + /// + /// Initializes the Stroke Element with the given Capture. + /// + /// Match object + void Initialize(Match match); +} \ No newline at end of file diff --git a/components/Media/src/Geometry/ICanvasStroke.cs b/components/Media/src/Geometry/ICanvasStroke.cs new file mode 100644 index 00000000..935e1f7c --- /dev/null +++ b/components/Media/src/Geometry/ICanvasStroke.cs @@ -0,0 +1,35 @@ +// 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.Brushes; +using Microsoft.Graphics.Canvas.Geometry; + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Interface to represent the Stroke which can be used to render an outline on a . +/// +public interface ICanvasStroke +{ + /// + /// Gets or sets the brush with which the will be rendered. + /// + ICanvasBrush Brush { get; set; } + + /// + /// Gets or sets the width of the . + /// + float Width { get; set; } + + /// + /// Gets or sets the Style of the . + /// + CanvasStrokeStyle Style { get; set; } + + /// + /// Gets or sets transform matrix of the brush. + /// + Matrix3x2 Transform { get; set; } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs b/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs new file mode 100644 index 00000000..cbe49706 --- /dev/null +++ b/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs @@ -0,0 +1,122 @@ +// 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.Brushes; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; + +namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; + +/// +/// Parser for ICanvasBrush. +/// +internal static class CanvasBrushParser +{ + /// + /// Parses the Brush data string and converts it into ICanvasBrushElement. + /// + /// Brush data + /// ICanvasBrushElement + internal static ICanvasBrushElement? Parse(string brushData) + { + var matches = RegexFactory.CanvasBrushRegex.Matches(brushData); + + // If no match is found or no captures in the match, then it means that the brush data is invalid. + if (matches.Count == 0) + { + ThrowForZeroCount(); + } + + // If the match contains more than one captures, it means that there are multiple brushes present in the brush data. + // There should be only one brush defined in the brush data. + if (matches.Count > 1) + { + ThrowForNotOneCount(); + } + + // There should be only one match + var match = matches[0]; + AbstractCanvasBrushElement? brushElement = null; + if (match.Groups["SolidColorBrush"].Success) + { + brushElement = new SolidColorBrushElement(match.Groups["SolidColorBrush"].Captures[0]); + } + else if (match.Groups["LinearGradient"].Success) + { + brushElement = new LinearGradientBrushElement(match.Groups["LinearGradient"].Captures[0]); + } + else if (match.Groups["LinearGradientHdr"].Success) + { + brushElement = new LinearGradientHdrBrushElement(match.Groups["LinearGradientHdr"].Captures[0]); + } + else if (match.Groups["RadialGradient"].Success) + { + brushElement = new RadialGradientBrushElement(match.Groups["RadialGradient"].Captures[0]); + } + else if (match.Groups["RadialGradientHdr"].Success) + { + brushElement = new RadialGradientHdrBrushElement(match.Groups["RadialGradientHdr"].Captures[0]); + } + + if (brushElement == null) + { + return null; + } + + // Perform validation to check if there are any invalid characters in the brush data that were not captured + var preValidationCount = RegexFactory.ValidationRegex.Replace(brushData, string.Empty).Length; + var postValidationCount = brushElement.ValidationCount; + + // If there are invalid characters, extract them and add them to the ArgumentException message + if (preValidationCount != postValidationCount) + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowForInvalidCharacters(AbstractCanvasBrushElement brushElement, string brushData) + { + var parseIndex = 0; + if (!string.IsNullOrWhiteSpace(brushElement.Data)) + { + parseIndex = brushData.IndexOf(brushElement.Data, parseIndex, StringComparison.Ordinal); + } + + var errorString = brushData.Substring(parseIndex); + if (errorString.Length > 30) + { + errorString = $"{errorString.Substring(0, 30)}..."; + } + + throw new ArgumentException($"BRUSH_ERR003:Brush data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); + } + + ThrowForInvalidCharacters(brushElement, brushData); + } + + return brushElement; + + static void ThrowForZeroCount() => throw new ArgumentException("BRUSH_ERR001:Invalid Brush data! No matching brush data found!"); + static void ThrowForNotOneCount() => throw new ArgumentException("BRUSH_ERR002:Multiple Brushes defined in Brush Data! " + + "There should be only one Brush definition within the Brush Data. " + + "You can either remove Brush definitions or split the Brush Data " + + "into multiple Brush Data and call the CanvasPathGeometry.CreateBrush() method on each of them."); + } + + /// + /// Parses the Brush data string and converts it into ICanvasBrush. + /// + /// ICanvasResourceCreator + /// Brush data string + /// ICanvasBrush + internal static ICanvasBrush? Parse(ICanvasResourceCreator resourceCreator, string brushData) + { + // Parse the brush data to get the ICanvasBrushElement + var brushElement = Parse(brushData); + if (brushElement is null) + return null; + + // Create ICanvasBrush from the brushElement + return brushElement.CreateBrush(resourceCreator); + } +} diff --git a/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs b/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs new file mode 100644 index 00000000..8903c13d --- /dev/null +++ b/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs @@ -0,0 +1,134 @@ +// 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 System.Runtime.CompilerServices; +using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Elements.Path; + +namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; + +/// +/// Parser for CanvasGeometry. +/// +internal static class CanvasGeometryParser +{ + /// + /// Parses the Path data in string format and converts it to . + /// + /// ICanvasResourceCreator + /// Path data + /// + internal static CanvasGeometry? Parse(ICanvasResourceCreator? resourceCreator, string pathData) + { + var pathFigures = new List(); + + var matches = RegexFactory.CanvasGeometryRegex.Matches(pathData); + + // If no match is found or no captures in the match, then it means // that the path data is invalid. + if (matches.Count == 0) + { + ThrowForZeroCount(); + } + + // If the match contains more than one captures, it means that there are multiple FillRuleElements present in the path data. + // There can be only one FillRuleElement in the path data (at the beginning). + if (matches.Count > 1) + { + ThrowForNotOneCount(); + } + + var figures = new List(); + + foreach (PathFigureType type in Enum.GetValues(typeof(PathFigureType))) + { + foreach (Capture figureCapture in matches[0].Groups[type.ToString()].Captures) + { + var figureRootIndex = figureCapture.Index; + var regex = RegexFactory.GetRegex(type); + default(ArgumentNullException).ThrowIfNull(regex); + + var figureMatch = regex.Match(figureCapture.Value); + if (!figureMatch.Success) + { + continue; + } + + // Process the 'Main' Group which contains the Path Command and + // corresponding attributes + var figure = PathElementFactory.CreatePathFigure(type, figureMatch, figureRootIndex); + figures.Add(figure); + + // Process the 'Additional' Group which contains just the attributes + figures.AddRange(from Capture capture in figureMatch.Groups["Additional"].Captures + select PathElementFactory.CreateAdditionalPathFigure(type, capture, figureRootIndex + capture.Index, figure.IsRelative)); + } + } + + // Sort the figures by their indices + pathFigures.AddRange(figures.OrderBy(f => f.Index)); + if (pathFigures.Count > 0) + { + // Check if the first element in the _figures list is a FillRuleElement + // which would indicate the fill rule to be followed while creating the + // path. If it is not present, then insert a default FillRuleElement at + // the beginning. + if ((pathFigures.ElementAt(0) as FillRuleElement) == null) + { + pathFigures.Insert(0, PathElementFactory.CreateDefaultPathElement(PathFigureType.FillRule)); + } + } + else + { + return null; + } + + // Perform validation to check if there are any invalid characters in the path data that were not captured + var preValidationCount = RegexFactory.ValidationRegex.Replace(pathData, string.Empty).Length; + var postValidationCount = pathFigures.Sum(x => x.ValidationCount); + + // If there are invalid characters, extract them and add them to the ArgumentException message + if (preValidationCount != postValidationCount) + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowForInvalidCharacters(List pathFigures, string pathData) + { + var parseIndex = 0; + foreach (var figure in pathFigures) + { + parseIndex = pathData.IndexOf(figure.Data, parseIndex, StringComparison.Ordinal) + figure.Data.Length; + } + + var errorString = pathData.Substring(parseIndex); + if (errorString.Length > 30) + { + errorString = $"{errorString.Substring(0, 30)}..."; + } + + throw new ArgumentException($"PATH_ERR003:Path data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); + } + + ThrowForInvalidCharacters(pathFigures, pathData); + } + + ICanvasPathElement? lastElement = null; + var currentPoint = Vector2.Zero; + + using var pathBuilder = new CanvasPathBuilder(resourceCreator); + foreach (var pathFigure in pathFigures) + { + currentPoint = pathFigure.CreatePath(pathBuilder, currentPoint, ref lastElement); + } + + return CanvasGeometry.CreatePath(pathBuilder); + + static void ThrowForZeroCount() => throw new ArgumentException("PATH_ERR000:Invalid Path data! No matching path data found!"); + static void ThrowForNotOneCount() => throw new ArgumentException("PATH_ERR001:Multiple FillRule elements present in Path Data!\n" + + "There should be only one FillRule within the Path Data. " + + "You can either remove additional FillRule elements or split the Path Data " + + "into multiple Path Data and call the CanvasPathGeometry.CreateGeometry() method on each of them."); + } +} diff --git a/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs b/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs new file mode 100644 index 00000000..182d4ef2 --- /dev/null +++ b/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs @@ -0,0 +1,96 @@ +// 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 CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; + +/// +/// Parser for CanvasStroke +/// +internal static class CanvasStrokeParser +{ + /// + /// Parses the Stroke Data string and converts it into ICanvasStrokeElement. + /// + /// Stroke Data string + /// ICanvasStrokeElement + internal static ICanvasStrokeElement Parse(string strokeData) + { + var matches = RegexFactory.CanvasStrokeRegex.Matches(strokeData); + + // If no match is found or no captures in the match, then it means + // that the stroke data is invalid. + if (matches.Count == 0) + { + ThrowForZeroCount(); + } + + // If the match contains more than one captures, it means that there + // are multiple CanvasStrokes present in the stroke data. There should + // be only one CanvasStroke defined in the stroke data. + if (matches.Count > 1) + { + ThrowForNotOneCount(); + } + + // There should be only one match + var match = matches[0]; + var strokeElement = new CanvasStrokeElement(match); + + // Perform validation to check if there are any invalid characters in the stroke data that were not captured + var preValidationCount = RegexFactory.ValidationRegex.Replace(strokeData, string.Empty).Length; + var postValidationCount = strokeElement.ValidationCount; + + // If there are invalid characters, extract them and add them to the ArgumentException message + if (preValidationCount != postValidationCount) + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowForInvalidCharacters(CanvasStrokeElement strokeElement, string strokeData) + { + var parseIndex = 0; + if (!string.IsNullOrWhiteSpace(strokeElement.Data)) + { + parseIndex = strokeData.IndexOf(strokeElement.Data, parseIndex, StringComparison.Ordinal); + } + + var errorString = strokeData.Substring(parseIndex); + if (errorString.Length > 30) + { + errorString = $"{errorString.Substring(0, 30)}..."; + } + + throw new ArgumentException($"STROKE_ERR003:Stroke data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); + } + + ThrowForInvalidCharacters(strokeElement, strokeData); + } + + return strokeElement; + + static void ThrowForZeroCount() => throw new ArgumentException("STROKE_ERR001:Invalid Stroke data! No matching CanvasStroke found!"); + static void ThrowForNotOneCount() => throw new ArgumentException("STROKE_ERR002:Multiple CanvasStrokes defined in Stroke Data! " + + "There should be only one CanvasStroke definition within the Stroke Data. " + + "You can either remove CanvasStroke definitions or split the Stroke Data " + + "into multiple Stroke Data and call the CanvasPathGeometry.CreateStroke() method on each of them."); + } + + /// + /// Parses the Stroke Data string and converts it into CanvasStroke. + /// + /// ICanvasResourceCreator + /// Stroke Data string + /// ICanvasStroke + internal static ICanvasStroke Parse(ICanvasResourceCreator resourceCreator, string strokeData) + { + // Parse the stroke data to create the ICanvasStrokeElement + var strokeElement = Parse(strokeData); + + // Create the CanvasStroke from the strokeElement + return strokeElement.CreateStroke(resourceCreator); + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs b/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs new file mode 100644 index 00000000..b8f436a2 --- /dev/null +++ b/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs @@ -0,0 +1,90 @@ +// 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.Geometry; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; + +namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; + +/// +/// Parser for the CanvasStrokeStyle +/// +internal static class CanvasStrokeStyleParser +{ + /// + /// Parses the given style data and converts it to CanvasStrokeStyle. + /// + /// Style data + /// CanvasStrokeStyle + internal static CanvasStrokeStyle Parse(string styleData) + { + var matches = RegexFactory.CanvasStrokeStyleRegex.Matches(styleData); + + // If no match is found or no captures in the match, then it means that the style data is invalid. + if (matches.Count == 0) + { + ThrowForZeroCount(); + } + + // If the match contains more than one captures, it means that there + // are multiple CanvasStrokeStyles present in the CanvasStrokeStyle data. There should + // be only one CanvasStrokeStyle defined in the CanvasStrokeStyle data. + if (matches.Count > 1) + { + ThrowForNotOneCount(); + } + + // There should be only one match + var match = matches[0]; + var styleElement = new CanvasStrokeStyleElement(match); + + // Perform validation to check if there are any invalid characters in the brush data that were not captured + var preValidationCount = RegexFactory.ValidationRegex.Replace(styleData, string.Empty).Length; + var postValidationCount = styleElement.ValidationCount; + + // If there are invalid characters, extract them and add them to the ArgumentException message + if (preValidationCount != postValidationCount) + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowForInvalidCharacters(CanvasStrokeStyleElement styleElement, string styleData) + { + var parseIndex = 0; + if (!string.IsNullOrWhiteSpace(styleElement.Data)) + { + parseIndex = styleData.IndexOf(styleElement.Data, parseIndex, StringComparison.Ordinal); + } + + var errorString = styleData.Substring(parseIndex); + if (errorString.Length > 30) + { + errorString = $"{errorString.Substring(0, 30)}..."; + } + + throw new ArgumentException($"STYLE_ERR003:Style data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); + } + + ThrowForInvalidCharacters(styleElement, styleData); + } + + return styleElement.Style; + + static void ThrowForZeroCount() => throw new ArgumentException("STYLE_ERR001:Invalid CanvasStrokeStyle data! No matching CanvasStrokeStyle found!"); + static void ThrowForNotOneCount() => throw new ArgumentException("STYLE_ERR002:Multiple CanvasStrokeStyles defined in CanvasStrokeStyle Data! " + + "There should be only one CanvasStrokeStyle definition within the CanvasStrokeStyle Data. " + + "You can either remove CanvasStrokeStyle definitions or split the CanvasStrokeStyle Data " + + "into multiple CanvasStrokeStyle Data and call the CanvasPathGeometry.CreateStrokeStyle() method on each of them."); + } + + /// + /// Parses and constructs a ICanvasStrokeStyleElement from the specified Match object. + /// + /// Match object + /// ICanvasStrokeStyleElement + internal static ICanvasStrokeStyleElement Parse(Match match) + { + return new CanvasStrokeStyleElement(match); + } +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/ColorParser.cs b/components/Media/src/Geometry/Parsers/ColorParser.cs new file mode 100644 index 00000000..2fe96561 --- /dev/null +++ b/components/Media/src/Geometry/Parsers/ColorParser.cs @@ -0,0 +1,147 @@ +// 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 System.Runtime.CompilerServices; +using CommunityToolkit.WinUI.Media.Geometry.Core; +using Windows.UI; + +#if WINUI3 +using Colors = Microsoft.UI.Colors; +#endif + +namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; + +/// +/// Parser for Color +/// +internal static class ColorParser +{ + /// + /// Converts the color string in Hexadecimal or HDR color format to the corresponding Color object. + /// The hexadecimal color string should be in #RRGGBB or #AARRGGBB format. + /// The '#' character is optional. + /// The HDR color string should be in R G B A format. + /// (R, G, B & A should have value in the range between 0 and 1, inclusive) + /// + /// Color string in Hexadecimal or HDR format + /// Color + internal static Color Parse(string colorString) + { + var match = RegexFactory.ColorRegex.Match(colorString); + + if (!match.Success) + { + ThrowArgumentException(); + } + + // Perform validation to check if there are any invalid characters in the colorString that were not captured + var preValidationCount = RegexFactory.ValidationRegex.Replace(colorString, string.Empty).Length; + var postValidationCount = RegexFactory.ValidationRegex.Replace(match.Value, string.Empty).Length; + + // If there are invalid characters, extract them and add them to the ArgumentException message + if (preValidationCount != postValidationCount) + { + [MethodImpl(MethodImplOptions.NoInlining)] + static void ThrowForInvalidCharacters(Match match, string colorString) + { + var parseIndex = 0; + if (!string.IsNullOrWhiteSpace(match.Value)) + { + parseIndex = colorString.IndexOf(match.Value, parseIndex, StringComparison.Ordinal); + } + + var errorString = colorString.Substring(parseIndex); + if (errorString.Length > 30) + { + errorString = $"{errorString.Substring(0, 30)}..."; + } + + throw new ArgumentException($"COLOR_ERR003:Color data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); + } + + ThrowForInvalidCharacters(match, colorString); + } + + return Parse(match); + + static void ThrowArgumentException() => throw new ArgumentException("COLOR_ERR001:Invalid value provided in Color Data! No matching color found in the Color Data."); + } + + /// + /// Converts a Vector4 High Dynamic Range Color to Color. Negative components of the Vector4 will be sanitized by taking the absolute value of the component. + /// The HDR Color components should have value in the range between 0 and 1, inclusive. If they are more than 1, they will be clamped at 1. + /// + /// High Dynamic Range Color + /// Color + internal static Color Parse(Vector4 hdrColor) + { + // Vector4's X, Y, Z, W components match to + // Color's R, G, B, A components respectively + return Parse(hdrColor.X, hdrColor.Y, hdrColor.Z, hdrColor.W); + } + + /// + /// Converts the given HDR color values to Color. + /// + /// Red Component + /// Green Component + /// Blue Component + /// Alpha Component + /// Instance of Color. + internal static Color Parse(float x, float y, float z, float w) + { + Vector4 v4 = new Vector4(x, y, z, w); + v4 = Vector4.Min(Vector4.Abs(v4) * 255f, new Vector4(255f)); + + var r = (byte)v4.X; + var g = (byte)v4.Y; + var b = (byte)v4.Z; + var a = (byte)v4.W; + + return Color.FromArgb(a, r, g, b); + } + + /// + /// Parses and constructs a Color object from the specified Match object. + /// + /// Match object + /// Color + internal static Color Parse(Match match) + { + if (match.Groups["RgbColor"].Success) + { + // Alpha component + byte alpha = 255; + var alphaStr = match.Groups["Alpha"].Value; + if (!string.IsNullOrWhiteSpace(alphaStr)) + { + alpha = (byte)Convert.ToInt32(alphaStr, 16); + } + + // Red component + var red = (byte)Convert.ToInt32(match.Groups["Red"].Value, 16); + + // Green component + var green = (byte)Convert.ToInt32(match.Groups["Green"].Value, 16); + + // Blue component + var blue = (byte)Convert.ToInt32(match.Groups["Blue"].Value, 16); + + return Color.FromArgb(alpha, red, green, blue); + } + + if (match.Groups["HdrColor"].Success) + { + float.TryParse(match.Groups["X"].Value, out var x); + float.TryParse(match.Groups["Y"].Value, out var y); + float.TryParse(match.Groups["Z"].Value, out var z); + float.TryParse(match.Groups["W"].Value, out var w); + + return Parse(x, y, z, w); + } + + return Colors.Transparent; + } +} diff --git a/components/Media/src/Geometry/Scalar.cs b/components/Media/src/Geometry/Scalar.cs new file mode 100644 index 00000000..6317a279 --- /dev/null +++ b/components/Media/src/Geometry/Scalar.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. + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Class containing some constants +/// represented as Floating point numbers. +/// +internal static class Scalar +{ + // Pi related floating point constants + + /// + /// (float)Math.PI radians ( or 180 degrees). + /// + internal const float Pi = (float)Math.PI; + + /// + /// Two times (float)Math.PI radians ( or 360 degrees). + /// + internal const float TwoPi = 2f * Pi; + + /// + /// Half of (float)Math.PI radians ( or 90 degrees). + /// + internal const float PiByTwo = Pi / 2f; + + /// + /// One third of (float)Math.PI radians ( or 60 degrees). + /// + internal const float PiByThree = Pi / 3f; + + /// + /// One fourth of (float)Math.PI radians ( or 45 degrees). + /// + internal const float PiByFour = Pi / 4f; + + /// + /// One sixth of (float)Math.PI radians ( or 30 degrees). + /// + internal const float PiBySix = Pi / 6f; + + /// + /// Three times half of (float)Math.PI radians ( or 270 degrees). + /// + internal const float ThreePiByTwo = 3f * Pi / 2f; + + // Conversion constants + + /// + /// 1 degree in radians. + /// + internal const float DegreesToRadians = Pi / 180f; + + /// + /// 1 radian in degrees. + /// + internal const float RadiansToDegrees = 180f / Pi; +} \ No newline at end of file diff --git a/components/Media/src/Geometry/Utils.cs b/components/Media/src/Geometry/Utils.cs new file mode 100644 index 00000000..884b0ff6 --- /dev/null +++ b/components/Media/src/Geometry/Utils.cs @@ -0,0 +1,815 @@ +// 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 Colors = Microsoft.UI.Colors; +#endif + +namespace CommunityToolkit.WinUI.Media.Geometry; + +/// +/// Class containing collection of useful methods for various types +/// +public static class Utils +{ + // Constant values + + // Smallest double value such that 1.0 + DoubleEpsilon != 1.0 + internal const double DoubleEpsilon = 2.2250738585072014E-308; + + // Smallest float value such that 1.0f + FloatMin != 1.0f + internal const float FloatMin = 1.175494351E-38F; + + /// + /// Returns whether or not two doubles are "close". + /// + /// The first double to compare. + /// The second double to compare. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsCloseTo(this double value1, double value2) + { + // In case they are Infinities or NaN (then epsilon check does not work) + if ((double.IsInfinity(value1) && + double.IsInfinity(value2)) || + (double.IsNaN(value1) && double.IsNaN(value2))) + { + return true; + } + + // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DoubleEpsilon + var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon; + var delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// Returns whether or not the first double is less than the second double. + /// + /// The first double to compare. + /// The second double to compare. + /// + /// bool - the result of the LessThan comparison. + /// + public static bool IsLessThan(this double value1, double value2) + { + return (value1 < value2) && !value1.IsCloseTo(value2); + } + + /// + /// Returns whether or not the first double is greater than the second double. + /// + /// The first double to compare. + /// The second double to compare. + /// + /// bool - the result of the GreaterThan comparison. + /// + public static bool IsGreaterThan(this double value1, double value2) + { + return (value1 > value2) && !value1.IsCloseTo(value2); + } + + /// + /// Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), + /// but this is faster. + /// + /// The double to compare to 1. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsOne(this double value) + { + return Math.Abs(value - 1.0d) < 10.0d * DoubleEpsilon; + } + + /// + /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), + /// but this is faster. + /// + /// The double to compare to 0. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsZero(this double value) + { + return Math.Abs(value) < 10.0d * DoubleEpsilon; + } + + /// + /// Returns whether or not two floats are "close". + /// + /// The first float to compare. + /// The second float to compare. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsCloseTo(this float value1, float value2) + { + // In case they are Infinities or NaN (then epsilon check does not work) + if ((float.IsInfinity(value1) && + float.IsInfinity(value2)) || + (float.IsNaN(value1) && float.IsNaN(value2))) + { + return true; + } + + // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < FloatMin + var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatMin; + var delta = value1 - value2; + return (-eps < delta) && (eps > delta); + } + + /// + /// Returns whether or not the first float is less than the second float. + /// + /// The first float to compare. + /// The second float to compare. + /// + /// bool - the result of the LessThan comparison. + /// + public static bool IsLessThan(this float value1, float value2) + { + return (value1 < value2) && !value1.IsCloseTo(value2); + } + + /// + /// Returns whether or not the first float is greater than the second float. + /// + /// The first float to compare. + /// The second float to compare. + /// + /// bool - the result of the GreaterThan comparison. + /// + public static bool IsGreaterThan(this float value1, float value2) + { + return (value1 > value2) && !value1.IsCloseTo(value2); + } + + /// + /// Returns whether or not the float is "close" to 1. Same as AreClose(float, 1), + /// but this is faster. + /// + /// The float to compare to 1. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsOne(this float value) + { + return Math.Abs(value - 1.0f) < 10.0f * FloatMin; + } + + /// + /// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0), + /// but this is faster. + /// + /// The float to compare to 0. + /// + /// bool - the result of the AreClose comparison. + /// + public static bool IsZero(this float value) + { + return Math.Abs(value) < 10.0f * FloatMin; + } + + /// + /// Compares two points for fuzzy equality. This function + /// helps compensate for the fact that double values can + /// acquire error when operated upon + /// + /// The first point to compare + /// The second point to compare + /// Whether or not the two points are equal + public static bool IsCloseTo(this Point point1, Point point2) + { + return point1.X.IsCloseTo(point2.X) && point1.Y.IsCloseTo(point2.Y); + } + + /// + /// Compares two Size instances for fuzzy equality. This function + /// helps compensate for the fact that double values can + /// acquire error when operated upon + /// + /// The first size to compare + /// The second size to compare + /// Whether or not the two Size instances are equal + public static bool IsCloseTo(this Size size1, Size size2) + { + return size1.Width.IsCloseTo(size2.Width) && size1.Height.IsCloseTo(size2.Height); + } + + /// + /// Compares two rectangles for fuzzy equality. This function + /// helps compensate for the fact that double values can + /// acquire error when operated upon + /// + /// The first rectangle to compare + /// The second rectangle to compare + /// Whether or not the two rectangles are equal + public static bool IsCloseTo(this Rect rect1, Rect rect2) + { + // If they're both empty, don't bother with the double logic. + if (rect1.IsEmpty) + { + return rect2.IsEmpty; + } + + // At this point, rect1 isn't empty, so the first thing we can test is rect2.IsEmpty, followed by property-wise compares. + return (!rect2.IsEmpty) + && rect1.X.IsCloseTo(rect2.X) && rect1.Y.IsCloseTo(rect2.Y) + && rect1.Height.IsCloseTo(rect2.Height) && rect1.Width.IsCloseTo(rect2.Width); + } + + /// + /// Rounds the given value based on the DPI scale + /// + /// Value to round + /// DPI Scale + /// The rounded value + public static double RoundLayoutValue(double value, double dpiScale) + { + double newValue; + + // If DPI == 1, don't use DPI-aware rounding. + if (!dpiScale.IsCloseTo(1.0)) + { + newValue = Math.Round(value * dpiScale) / dpiScale; + + // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. + if (double.IsNaN(newValue) || + double.IsInfinity(newValue) || + newValue.IsCloseTo(double.MaxValue)) + { + newValue = value; + } + } + else + { + newValue = Math.Round(value); + } + + return newValue; + } + + /// + /// Calculates the linear interpolated value based on the given values. + /// + /// Starting value. + /// Ending value. + /// Weight-age given to the ending value. + /// Linear interpolated value. + public static float Lerp(this float start, float end, float amount) + { + return start + ((end - start) * amount); + } + + /// + /// Verifies if this Thickness contains only valid values. The set of validity checks is passed as parameters. + /// + /// Thickness value + /// allows negative values + /// allows double.NaN + /// allows double.PositiveInfinity + /// allows double.NegativeInfinity + /// Whether or not the thickness complies to the range specified + public static bool IsValid(this Thickness thick, bool allowNegative, bool allowNaN, bool allowPositiveInfinity, bool allowNegativeInfinity) + { + if (!allowNegative) + { + if (thick.Left < 0d || thick.Right < 0d || thick.Top < 0d || thick.Bottom < 0d) + { + return false; + } + } + + if (!allowNaN) + { + if (double.IsNaN(thick.Left) || double.IsNaN(thick.Right) + || double.IsNaN(thick.Top) || double.IsNaN(thick.Bottom)) + { + return false; + } + } + + if (!allowPositiveInfinity) + { + if (double.IsPositiveInfinity(thick.Left) || double.IsPositiveInfinity(thick.Right) + || double.IsPositiveInfinity(thick.Top) || double.IsPositiveInfinity(thick.Bottom)) + { + return false; + } + } + + if (!allowNegativeInfinity) + { + if (double.IsNegativeInfinity(thick.Left) || double.IsNegativeInfinity(thick.Right) + || double.IsNegativeInfinity(thick.Top) || double.IsNegativeInfinity(thick.Bottom)) + { + return false; + } + } + + return true; + } + + /// + /// Method to add up the left and right size as width, as well as the top and bottom size as height. + /// + /// Thickness + /// Size + public static Size CollapseThickness(this Thickness thick) + { + return new Size(thick.Left + thick.Right, thick.Top + thick.Bottom); + } + + /// + /// Verifies if the Thickness contains only zero values. + /// + /// Thickness + /// Size + public static bool IsZero(this Thickness thick) + { + return thick.Left.IsZero() + && thick.Top.IsZero() + && thick.Right.IsZero() + && thick.Bottom.IsZero(); + } + + /// + /// Verifies if all the values in Thickness are same. + /// + /// Thickness + /// true if yes, otherwise false + public static bool IsUniform(this Thickness thick) + { + return thick.Left.IsCloseTo(thick.Top) + && thick.Left.IsCloseTo(thick.Right) + && thick.Left.IsCloseTo(thick.Bottom); + } + + /// + /// Converts the Thickness object to Vector4. If the Thickness object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. + /// + /// Thickness object + /// Vector4 + public static Vector4 ToVector4(this Thickness thickness) + { + if (thickness.IsValid(true, false, false, false)) + { + // Sanitize the component by taking only + return new Vector4( + (float)thickness.Left, + (float)thickness.Top, + (float)thickness.Right, + (float)thickness.Bottom); + } + + return Vector4.Zero; + } + + /// + /// Converts the Thickness object to Vector4. If the Thickness object contains negative components they will be converted to positive values. If the Thickness object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. + /// + /// Thickness object + /// Vector2 + public static Vector4 ToAbsVector4(this Thickness thickness) + { + if (thickness.IsValid(true, false, false, false)) + { + // Sanitize the component by taking only + return new Vector4( + Math.Abs((float)thickness.Left), + Math.Abs((float)thickness.Top), + Math.Abs((float)thickness.Right), + Math.Abs((float)thickness.Bottom)); + } + + return Vector4.Zero; + } + + /// + /// Gets the top left corner of the thickness structure. + /// + /// Thickness object + /// Vector2 + public static Vector2 GetOffset(this Thickness thickness) + { + return new Vector2((float)thickness.Left, (float)thickness.Top); + } + + /// + /// Verifies if the CornerRadius contains only zero values. + /// + /// CornerRadius + /// true if yes, otherwise false + public static bool IsZero(this CornerRadius corner) + { + return corner.TopLeft.IsZero() + && corner.TopRight.IsZero() + && corner.BottomRight.IsZero() + && corner.BottomLeft.IsZero(); + } + + /// + /// Verifies if the CornerRadius contains same values. + /// + /// CornerRadius + /// true if yes, otherwise false + public static bool IsUniform(this CornerRadius corner) + { + var topLeft = corner.TopLeft; + return topLeft.IsCloseTo(corner.TopRight) && + topLeft.IsCloseTo(corner.BottomLeft) && + topLeft.IsCloseTo(corner.BottomRight); + } + + /// + /// Converts the given corner value to a valid positive value. Returns zero if the corner value is Infinity or NaN or 0. + /// + /// Corner value + /// Valid Corner value + public static double ConvertToValidCornerValue(double corner) + { + if (double.IsNaN(corner) || + double.IsInfinity(corner) || + (corner < 0d)) + { + return 0d; + } + + return corner; + } + + /// + /// Converts the CornerRadius object to Vector4. If the CornerRadius object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. + /// + /// CornerRadius object + /// Vector4 + public static Vector4 ToVector4(this CornerRadius corner) + { + return new Vector4( + (float)corner.TopLeft, + (float)corner.TopRight, + (float)corner.BottomRight, + (float)corner.BottomLeft); + } + + /// + /// Deflates rectangle by given thickness. + /// + /// Rectangle + /// Thickness + /// Deflated Rectangle + public static Rect Deflate(this Rect rect, Thickness thick) + { + return new Rect( + rect.Left + thick.Left, + rect.Top + thick.Top, + Math.Max(0.0, rect.Width - thick.Left - thick.Right), + Math.Max(0.0, rect.Height - thick.Top - thick.Bottom)); + } + + /// + /// Inflates rectangle by given thickness. + /// + /// Rectangle + /// Thickness + /// Inflated Rectangle + public static Rect Inflate(this Rect rect, Thickness thick) + { + return new Rect( + rect.Left - thick.Left, + rect.Top - thick.Top, + Math.Max(0.0, rect.Width + thick.Left + thick.Right), + Math.Max(0.0, rect.Height + thick.Top + thick.Bottom)); + } + + /// + /// Verifies if the given brush is a SolidColorBrush and its color does not include transparency. + /// + /// Brush + /// true if yes, otherwise false + public static bool IsOpaqueSolidColorBrush(this Brush brush) + { + return (brush as SolidColorBrush)?.Color.A == 0xff; + } + + /// + /// Verifies if the given brush is the same as the otherBrush. + /// + /// Given + /// The to match it with + /// true if yes, otherwise false + public static bool IsEqualTo(this Brush brush, Brush otherBrush) + { + if (brush.GetType() != otherBrush.GetType()) + { + return false; + } + + if (ReferenceEquals(brush, otherBrush)) + { + return true; + } + + // Are both instances of SolidColorBrush + if ((brush is SolidColorBrush solidBrushA) && (otherBrush is SolidColorBrush solidBrushB)) + { + return (solidBrushA.Color == solidBrushB.Color) + && solidBrushA.Opacity.IsCloseTo(solidBrushB.Opacity); + } + + // Are both instances of LinearGradientBrush + if ((brush is LinearGradientBrush linGradBrushA) && (otherBrush is LinearGradientBrush linGradBrushB)) + { + var result = (linGradBrushA.ColorInterpolationMode == linGradBrushB.ColorInterpolationMode) + && (linGradBrushA.EndPoint == linGradBrushB.EndPoint) + && (linGradBrushA.MappingMode == linGradBrushB.MappingMode) + && linGradBrushA.Opacity.IsCloseTo(linGradBrushB.Opacity) + && (linGradBrushA.StartPoint == linGradBrushB.StartPoint) + && (linGradBrushA.SpreadMethod == linGradBrushB.SpreadMethod) + && (linGradBrushA.GradientStops.Count == linGradBrushB.GradientStops.Count); + if (!result) + { + return false; + } + + for (var i = 0; i < linGradBrushA.GradientStops.Count; i++) + { + result = (linGradBrushA.GradientStops[i].Color == linGradBrushB.GradientStops[i].Color) + && linGradBrushA.GradientStops[i].Offset.IsCloseTo(linGradBrushB.GradientStops[i].Offset); + + if (!result) + { + break; + } + } + + return result; + } + + // Are both instances of ImageBrush + if ((brush is ImageBrush imgBrushA) && (otherBrush is ImageBrush imgBrushB)) + { + var result = (imgBrushA.AlignmentX == imgBrushB.AlignmentX) + && (imgBrushA.AlignmentY == imgBrushB.AlignmentY) + && imgBrushA.Opacity.IsCloseTo(imgBrushB.Opacity) + && (imgBrushA.Stretch == imgBrushB.Stretch) + && (imgBrushA.ImageSource == imgBrushB.ImageSource); + + return result; + } + + return false; + } + + /// + /// Compares one URI with another URI. + /// + /// URI to compare with + /// URI to compare + /// true if yes, otherwise false + public static bool IsEqualTo(this Uri uri, Uri otherUri) + { + return + Uri.Compare(uri, otherUri, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0; + } + + /// + /// Reflects point 'a' over point 'b'. + /// + /// Point to be reflected + /// Point of reflection + /// Reflected point + public static Vector2 Reflect(Vector2 a, Vector2 b) + { + // Let 'c' be the reflected point. Then point 'b' + // becomes the middle point between 'a' and 'c'. + // As per MidPoint formula, + // b.X = (a.X + c.X) / 2 and + // b.Y = (a.Y + c.Y) / 2 + // Therefore, c.X = 2 * b.X - a.X + // c.y = 2 * b.Y - a.Y + return new Vector2((2f * b.X) - a.X, (2f * b.Y) - a.Y); + } + + /// + /// Converts a Vector2 structure (x,y) to Vector3 structure (x, y, 0). + /// + /// Input Vector2 + /// Vector3 + public static Vector3 ToVector3(this Vector2 v) + { + return new Vector3(v, 0); + } + + /// + /// Verifies if the Vector4 contains only zero values. + /// + /// Vector4 + /// true if yes, otherwise false + public static bool IsZero(this Vector4 vector) + { + return vector.X.IsZero() + && vector.Y.IsZero() + && vector.Z.IsZero() + && vector.W.IsZero(); + } + + /// + /// Useful in converting the four components of Thickness or Padding to two components by taking a sum of alternate components (X & Z and Y & W). + /// + /// Vector4 + /// Vector3 + public static Vector2 Collapse(this Vector4 vector) + { + return new Vector2(vector.X + vector.Z, vector.Y + vector.W); + } + + /// + /// Useful in converting the four components of Thickness or Padding to two components by adding alternate components - (X & Z and Y & W). + /// + /// Vector4 + /// Size + public static Size ToSize(this Vector4 vector) + { + return new Size(vector.X + vector.Z, vector.Y + vector.W); + } + + /// + /// Converts the Vector4 to Thickness - Left(X), Top(Y), Right(Z), Bottom(W). + /// + /// Vector4 + /// Thickness + public static Thickness ToThickness(this Vector4 vector) + { + return new Thickness(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + /// Converts the Vector4 to CornerRadius - TopLeft(X), TopRight(Y), BottomRight(Z), BottomLeft(W). + /// + /// Vector4 + /// CornerRadius + public static CornerRadius ToCornerRadius(this Vector4 vector) + { + return new CornerRadius(vector.X, vector.Y, vector.Z, vector.W); + } + + /// + /// Calculates the linear interpolated Color based on the given Color values. + /// + /// Source Color. + /// Target Color. + /// Weightage given to the target color. + /// Linear Interpolated Color. + public static Color Lerp(this Color colorFrom, Color colorTo, float amount) + { + // Convert colorFrom components to lerp-able floats + float sa = colorFrom.A, + sr = colorFrom.R, + sg = colorFrom.G, + sb = colorFrom.B; + + // Convert colorTo components to lerp-able floats + float ea = colorTo.A, + er = colorTo.R, + eg = colorTo.G, + eb = colorTo.B; + + // lerp the colors to get the difference + byte a = (byte)Math.Max(0, Math.Min(255, sa.Lerp(ea, amount))), + r = (byte)Math.Max(0, Math.Min(255, sr.Lerp(er, amount))), + g = (byte)Math.Max(0, Math.Min(255, sg.Lerp(eg, amount))), + b = (byte)Math.Max(0, Math.Min(255, sb.Lerp(eb, amount))); + + // return the new color + return Color.FromArgb(a, r, g, b); + } + + /// + /// Darkens the color by the given percentage. + /// + /// Source color. + /// Percentage to darken. Value should be between 0 and 1. + /// Color + public static Color DarkerBy(this Color color, float amount) + { + return color.Lerp(Colors.Black, amount); + } + + /// + /// Lightens the color by the given percentage. + /// + /// Source color. + /// Percentage to lighten. Value should be between 0 and 1. + /// Color + public static Color LighterBy(this Color color, float amount) + { + return color.Lerp(Colors.White, amount); + } + + /// + /// Converts the Point structure P (X,Y) to Vector3 structure + /// V (P.X, P.Y, 0); + /// + /// Point structure + /// Vector3 + public static Vector3 ToVector3(this Point p) + { + return new Vector3((float)p.X, (float)p.Y, 0f); + } + + /// + /// Calculates the best size that can fit in the destination area based on the given stretch and alignment options. + /// + /// Width of the source. + /// Height of the source. + /// Width of the destination area. + /// Height of the destination area. + /// Defines how the source should stretch to fit the destination. + /// Horizontal Alignment + /// Vertical Alignment + /// The best fitting Rectangle in the destination area. + public static Rect GetOptimumSize(double srcWidth, double srcHeight, double destWidth, double destHeight, Stretch stretch, AlignmentX horizontalAlignment, AlignmentY verticalAlignment) + { + var ratio = srcWidth / srcHeight; + var targetWidth = 0d; + var targetHeight = 0d; + + // Stretch Mode + switch (stretch) + { + case Stretch.None: + targetWidth = srcWidth; + targetHeight = srcHeight; + break; + case Stretch.Fill: + targetWidth = destWidth; + targetHeight = destHeight; + break; + case Stretch.Uniform: + // If width is greater than height + if (ratio > 1.0) + { + targetHeight = Math.Min(destWidth / ratio, destHeight); + targetWidth = targetHeight * ratio; + } + else + { + targetWidth = Math.Min(destHeight * ratio, destWidth); + targetHeight = targetWidth / ratio; + } + + break; + case Stretch.UniformToFill: + // If width is greater than height + if (ratio > 1.0) + { + targetHeight = Math.Max(destWidth / ratio, destHeight); + targetWidth = targetHeight * ratio; + } + else + { + targetWidth = Math.Max(destHeight * ratio, destWidth); + targetHeight = targetWidth / ratio; + } + + break; + } + + var left = 0d; + switch (horizontalAlignment) + { + case AlignmentX.Left: + left = 0; + break; + case AlignmentX.Center: + left = (destWidth - targetWidth) / 2.0; + break; + case AlignmentX.Right: + left = destWidth - targetWidth; + break; + } + + var top = 0d; + switch (verticalAlignment) + { + case AlignmentY.Top: + top = 0; + break; + case AlignmentY.Center: + top = (destHeight - targetHeight) / 2.0; + break; + case AlignmentY.Bottom: + top = destHeight - targetHeight; + break; + } + + return new Rect(left, top, targetWidth, targetHeight); + } +} 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; + +/// +/// A used to cache reusable instances with an associated key +/// +/// The type of key to classify the items in the cache +/// The type of items stored in the cache +internal sealed class CompositionObjectCache + where TValue : CompositionObject + where TKey : notnull +{ + /// + /// The cache of weak references of type to instances, to avoid memory leaks + /// + private readonly ConditionalWeakTable>> cache = new ConditionalWeakTable>>(); + + /// + /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found + /// + /// The current instance to get the value for + /// The key to look for + /// The resulting value, if existing + /// if the target value has been found, otherwise + 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; + } + } + + /// + /// Adds or updates a value with the specified key to the cache + /// + /// The current instance to get the value for + /// The key of the item to add + /// The value to add + 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(value)); + } + else + { + map = new Dictionary> { [key] = new WeakReference(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; + +/// +/// A used to cache reusable instances in each UI thread +/// +/// The type of instances to cache +internal sealed class CompositionObjectCache + where T : CompositionObject +{ + /// + /// The cache of weak references of type , to avoid memory leaks + /// + private readonly ConditionalWeakTable> cache = new ConditionalWeakTable>(); + + /// + /// Tries to retrieve a valid instance from the cache, and uses the provided factory if an existing item is not found + /// + /// The current instance to get the value for + /// A instance used to produce a instance + /// A instance that is linked to + public T GetValue(Compositor compositor, Func 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(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; + +/// +/// A delegate for load time effects. +/// +/// The bitmap. +/// The device. +/// The size target. +/// A CompositeDrawingSurface +public delegate CompositionDrawingSurface LoadTimeEffectHandler(CanvasBitmap bitmap, CompositionGraphicsDevice device, Size sizeTarget); + +/// +/// A that can load and draw images and other objects to Win2D surfaces and brushes +/// +public sealed partial class SurfaceLoader : IDisposable +{ + /// + /// The cache of instances currently available + /// + private static readonly ConditionalWeakTable Instances = new ConditionalWeakTable(); + + /// + /// Gets a instance for the of the current window + /// + /// A instance to use in the current window + public static SurfaceLoader GetInstance() + { + return GetInstance(Window.Current.Compositor); + } + + /// + /// Gets a instance for a given + /// + /// The input object to use + /// A instance associated with + 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; + } + } + + /// + /// The instance in use. + /// + private readonly Compositor compositor; + + /// + /// The instance in use. + /// + private CanvasDevice? canvasDevice; + + /// + /// The instance to determine which GPU is handling the request. + /// + private CompositionGraphicsDevice? compositionDevice; + + /// + /// Initializes a new instance of the class. + /// + /// The instance to use + private SurfaceLoader(Compositor compositor) + { + this.compositor = compositor; + + this.InitializeDevices(); + } + + /// + /// Reloads the and fields. + /// + 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; + } + + /// + /// Invokes when the current is lost. + /// + private void CanvasDevice_DeviceLost(CanvasDevice sender, object args) + { + InitializeDevices(); + } + + /// + /// Invokes when the current changes rendering device. + /// + private void CompositionDevice_RenderingDeviceReplaced(CompositionGraphicsDevice sender, RenderingDeviceReplacedEventArgs args) + { + InitializeDevices(); + } + + /// + /// Loads an image from the URI. + /// + /// The URI. + /// + public async Task LoadFromUri(Uri uri) + { + return await LoadFromUri(uri, Size.Empty); + } + + /// + /// Loads an image from URI with a specified size. + /// + /// The URI. + /// The size target. + /// + public async Task 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; + } + + /// + /// Loads the text on to a . + /// + /// The text. + /// The size target. + /// The text format. + /// Color of the text. + /// Color of the bg. + /// + 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; + } + + /// + /// Loads an image from URI, with a specified size. + /// + /// The URI. + /// The size target. + /// The load effect handler callback. + /// + public async Task 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; + +/// +/// A that can load and draw images and other objects to Win2D surfaces and brushes +/// +public sealed partial class SurfaceLoader +{ + /// + /// Synchronization mutex to access the cache and load Win2D images concurrently + /// + private static readonly AsyncMutex Win2DMutex = new AsyncMutex(); + + /// + /// Gets the local cache mapping for previously loaded Win2D images + /// + private static readonly CompositionObjectCache Cache = new CompositionObjectCache(); + + /// + /// Loads a instance with the target image from the shared instance + /// + /// The path to the image to load + /// Indicates the desired DPI mode to use when loading the image + /// Indicates the cache option to use to load the image + /// A that returns the loaded instance + public static async Task 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; + } + } + + /// + /// Loads a from the input , and prepares it to be used in a tile effect + /// + /// The device to use to process the Win2D image + /// The compositor instance to use to create the final brush + /// The path to the image to load + /// Indicates the desired DPI mode to use when loading the image + /// A that returns the loaded instance + private static async Task 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 @@ + + + + uwp;wasdk; + + 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; + +/// +/// A simple container used to store info on a custom composition effect to create +/// +public sealed class BrushProvider +{ + /// + /// Gets the name of the target + /// + internal string Name { get; } + + /// + /// Gets the stored effect initializer + /// + internal Func> Initializer { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the target + /// The stored effect initializer + private BrushProvider(string name, Func> initializer) + { + this.Name = name; + this.Initializer = initializer; + } + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// A to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, CompositionBrush brush) => new BrushProvider(name, () => new ValueTask(brush)); + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// A instance that will produce the to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, Func factory) => new BrushProvider(name, () => new ValueTask(factory())); + + /// + /// Creates a new instance with the info on a given to initialize + /// + /// The target effect name + /// An asynchronous instance that will produce the to use to initialize the effect + /// A instance with the input initializer + [Pure] + public static BrushProvider New(string name, Func> factory) => new BrushProvider(name, () => new ValueTask(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; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ + /// + /// Adds a new to the current pipeline + /// + /// The blur amount to apply + /// The target property to animate the resulting effect. + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [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 Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer(), + Name = name + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects (should be in the [0, 1] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}"; + + async ValueTask Factory() => new CanvasCrossFadeEffect + { + CrossFade = factor, + Source1 = await this.sourceProducer(), + Source2 = await pipeline.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(Factory, this, pipeline, new[] { target }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasExposureEffect.Exposure)}"; + + async ValueTask Factory() => new CanvasExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasHueRotationEffect.Angle)}"; + + async ValueTask Factory() => new CanvasHueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasOpacityEffect.Opacity)}"; + + async ValueTask Factory() => new CanvasOpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect (should be in the [0, 1] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasSaturationEffect.Saturation)}"; + + async ValueTask Factory() => new CanvasSaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasSepiaEffect.Intensity)}"; + + async ValueTask Factory() => new CanvasSepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasTintEffect.Color)}"; + + async ValueTask 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; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ + /// + /// Adds a new to the current pipeline + /// + /// The blur amount to apply + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + async ValueTask Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial blur amount + /// The optional blur setter for the effect + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, out EffectSetter setter, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial blur amount + /// The optional blur animation for the effect + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Blur(float blur, out EffectAnimation animation, EffectBorderMode mode = EffectBorderMode.Hard, EffectOptimization optimization = EffectOptimization.Balanced) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The saturation amount for the new effect (should be in the [0, 1] range) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation) + { + async ValueTask Factory() => new CanvasSaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect (should be in the [0, 1] range) + /// The optional saturation setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect (should be in the [0, 1] range) + /// The optional saturation animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity) + { + async ValueTask Factory() => new CanvasSepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The optional sepia intensity setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The sepia intensity animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity) + { + async ValueTask Factory() => new CanvasOpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The optional opacity setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The optional opacity animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The amount of exposure to apply over the current effect (should be in the [-2, 2] range) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount) + { + async ValueTask Factory() => new CanvasExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range) + /// The optional amount setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range) + /// The optional amount animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle) + { + async ValueTask Factory() => new CanvasHueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The optional rotation angle setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The optional rotation angle animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color) + { + async ValueTask Factory() => new CanvasTintEffect + { + Color = color, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use (should be in the [-1, 1] range) + /// The tint value to use (should be in the [-1, 1] range) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint(float temperature, float tint) + { + async ValueTask Factory() => new CanvasTemperatureAndTintEffect + { + Temperature = temperature, + Tint = tint, + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use (should be in the [-1, 1] range) + /// The optional temperature setter for the effect + /// The tint value to use (should be in the [-1, 1] range) + /// The optional tint setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint( + float temperature, + out EffectSetter temperatureSetter, + float tint, + out EffectSetter tintSetter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a temperature and tint effect on the current pipeline + /// + /// The temperature value to use (should be in the [-1, 1] range) + /// The optional temperature animation for the effect + /// The tint value to use (should be in the [-1, 1] range) + /// The optional tint animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder TemperatureAndTint( + float temperature, + out EffectAnimation temperatureAnimation, + float tint, + out EffectAnimation tintAnimation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The amount of mix to apply over the current effect (must be in the [0, 1] range) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade(Color color, float mix) + { + return FromColor(color).CrossFade(this, mix); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The optional color setter for the effect + /// The initial amount of mix to apply over the current effect (must be in the [0, 1] range) + /// The optional mix setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade( + Color color, + out EffectSetter colorSetter, + float mix, + out EffectSetter mixSetter) + { + return FromColor(color, out colorSetter).CrossFade(this, mix, out mixSetter); + } + + /// + /// Applies a shade effect on the current pipeline + /// + /// The color to use + /// The optional color animation for the effect + /// The initial amount of mix to apply over the current effect (must be in the [0, 1] range) + /// The optional mix animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Shade( + Color color, + out EffectAnimation colorAnimation, + float mix, + out EffectAnimation mixAnimation) + { + return FromColor(color, out colorAnimation).CrossFade(this, mix, out mixAnimation); + } + + /// + /// Applies a luminance to alpha effect on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder LuminanceToAlpha() + { + async ValueTask Factory() => new CanvasLuminanceToAlphaEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies an invert effect on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Invert() + { + async ValueTask Factory() => new CanvasInvertEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a grayscale on the current pipeline + /// + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Grayscale() + { + async ValueTask Factory() => new CanvasGrayscaleEffect + { + Source = await this.sourceProducer() + }; + + return new PipelineBuilder(this, Factory); + } + + /// + /// Applies a custom effect to the current pipeline + /// + /// A that takes the current instance and produces a new effect to display + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Effect( + Func factory, + IEnumerable? animations = null, + IEnumerable? initializers = null) + { + async ValueTask Factory() => factory(await this.sourceProducer()); + + return new PipelineBuilder(this, Factory, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + + /// + /// Applies a custom effect to the current pipeline + /// + /// An asynchronous that takes the current instance and produces a new effect to display + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Effect( + Func> factory, + IEnumerable? animations = null, + IEnumerable? initializers = null) + { + async ValueTask 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; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ + /// + /// The cache manager for backdrop brushes + /// + private static readonly CompositionObjectCache BackdropBrushCache = new CompositionObjectCache(); + + /// + /// The cache manager for host backdrop brushes + /// + private static readonly CompositionObjectCache HostBackdropBrushCache = new CompositionObjectCache(); + + /// + /// Starts a new pipeline from the returned by + /// + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdrop() + { + ValueTask Factory() + { + var brush = BackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateBackdropBrush()); + + return new ValueTask(brush); + } + + return new PipelineBuilder(Factory); + } + +#if WINUI2 + /// + /// Starts a new pipeline from the returned by + /// + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdrop() + { + ValueTask Factory() + { + var brush = HostBackdropBrushCache.GetValue(Window.Current.Compositor, c => c.CreateHostBackdropBrush()); + + return new ValueTask(brush); + } + + return new PipelineBuilder(Factory); + } +#endif + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color) + { + return new PipelineBuilder(() => new ValueTask(new ColorSourceEffect { Color = color })); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(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)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromColor(Color color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(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)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color) + { + return new PipelineBuilder(() => new ValueTask(new ColorSourceEffect { ColorHdr = color })); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(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)}" }); + } + + /// + /// Starts a new pipeline from a solid with the specified color + /// + /// The desired color for the initial + /// The optional color animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHdrColor(Vector4 color, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + ValueTask Factory() => new ValueTask(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)}" }); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(CompositionBrush brush) + { + return new PipelineBuilder(() => new ValueTask(brush)); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that synchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(Func factory) + { + return new PipelineBuilder(() => new ValueTask(factory())); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that asynchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBrush(Func> factory) + { + async ValueTask Factory() => await factory(); + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(IGraphicsEffectSource effect) + { + return new PipelineBuilder(() => new ValueTask(effect)); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that synchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(Func factory) + { + return new PipelineBuilder(() => new ValueTask(factory())); + } + + /// + /// Starts a new pipeline from the input instance + /// + /// A that asynchronously returns a instance to start the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromEffect(Func> factory) + { + async ValueTask Factory() => await factory(); + + return new PipelineBuilder(Factory); + } + + /// + /// Starts a new pipeline from a Win2D image + /// + /// The relative path for the image to load (eg. "/Assets/image.png") + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromImage(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return FromImage(relativePath.ToAppxUri(), dpiMode, cacheMode); + } + + /// + /// Starts a new pipeline from a Win2D image + /// + /// The path for the image to load + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromImage(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return new PipelineBuilder(() => new ValueTask(SurfaceLoader.LoadImageAsync(uri, dpiMode, cacheMode)!)); + } + + /// + /// Starts a new pipeline from a Win2D image tiled to cover the available space + /// + /// The relative path for the image to load (eg. "/Assets/image.png") + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromTiles(string relativePath, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + return FromTiles(relativePath.ToAppxUri(), dpiMode, cacheMode); + } + + /// + /// Starts a new pipeline from a Win2D image tiled to cover the available space + /// + /// The path for the image to load + /// Indicates the desired DPI mode to use when loading the image + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromTiles(Uri uri, DpiMode dpiMode = DpiMode.DisplayDpiWith96AsLowerBound, CacheMode cacheMode = CacheMode.Default) + { + var image = FromImage(uri, dpiMode, cacheMode); + + async ValueTask Factory() => new BorderEffect + { + ExtendX = CanvasEdgeBehavior.Wrap, + ExtendY = CanvasEdgeBehavior.Wrap, + Source = await image.sourceProducer() + }; + + return new PipelineBuilder(image, Factory); + } + + /// + /// Starts a new pipeline from the returned by on the input + /// + /// The source to use to create the pipeline + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromUIElement(UIElement element) + { + return new PipelineBuilder(() => new ValueTask(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; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ + /// + /// Blends two pipelines using a instance with the specified mode + /// + /// The second instance to blend + /// The desired to use to blend the input pipelines + /// The placemeht to use with the two input pipelines (the default is ) + /// A new instance to use to keep adding new effects + [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 Factory() => new CanvasBlendEffect + { + Foreground = await foreground.sourceProducer(), + Background = await background.sourceProducer(), + Mode = mode + }; + + return new PipelineBuilder(Factory, foreground, background); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects (default is 0.5, must be in the [0, 1] range) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor = 0.5f) + { + async ValueTask Factory() => new CanvasCrossFadeEffect + { + CrossFade = factor, + Source1 = await this.sourceProducer(), + Source2 = await pipeline.sourceProducer() + }; + + return new PipelineBuilder(Factory, this, pipeline); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects (should be in the [0, 1] range) + /// The optional blur setter for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectSetter setter) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects (should be in the [0, 1] range) + /// The optional blur animation for the effect + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out EffectAnimation animation) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + async ValueTask 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)}" }); + } + + /// + /// Blends two pipelines using the provided to do so + /// + /// The blend function to use + /// The background pipeline to blend with the current instance + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Merge( + Func factory, + PipelineBuilder background, + IEnumerable? animations = null, + IEnumerable? initializers = null) + { + async ValueTask Factory() => factory(await this.sourceProducer(), await background.sourceProducer()); + + return new PipelineBuilder(Factory, this, background, animations?.ToArray(), initializers?.ToDictionary(item => item.Name, item => item.Initializer)); + } + + /// + /// Blends two pipelines using the provided asynchronous to do so + /// + /// The asynchronous blend function to use + /// The background pipeline to blend with the current instance + /// The list of optional animatable properties in the returned effect + /// The list of source parameters that require deferred initialization (see for more info) + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Merge( + Func> factory, + PipelineBuilder background, + IEnumerable? animations = null, + IEnumerable? initializers = null) + { + async ValueTask 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; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ +#if WINUI2 + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The amount of tint to apply over the current effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [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; + } + + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color setter for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix setter for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdropAcrylic( + Color tintColor, + out EffectSetter tintColorSetter, + float tintOpacity, + out EffectSetter 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; + } + + /// + /// Returns a new instance that implements the host backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color animation for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix animation for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromHostBackdropAcrylic( + Color tintColor, + out EffectAnimation tintColorAnimation, + float tintOpacity, + out EffectAnimation 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 + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The amount of tint to apply over the current effect (must be in the [0, 1] range) + /// The amount of blur to apply to the acrylic brush + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [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; + } + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color setter for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix setter for the effect + /// The amount of blur to apply to the acrylic brush + /// The optional blur setter for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdropAcrylic( + Color tintColor, + out EffectSetter tintColorSetter, + float tintOpacity, + out EffectSetter tintOpacitySetter, + float blurAmount, + out EffectSetter 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; + } + + /// + /// Returns a new instance that implements the in-app backdrop acrylic effect + /// + /// The tint color to use + /// The optional tint color animation for the effect + /// The amount of tint to apply over the current effect + /// The optional tint mix animation for the effect + /// The amount of blur to apply to the acrylic brush + /// The optional blur animation for the effect + /// The for the noise texture to load for the acrylic effect + /// The cache mode to use to load the image + /// A new instance to use to keep adding new effects + [Pure] + public static PipelineBuilder FromBackdropAcrylic( + Color tintColor, + out EffectAnimation tintAnimation, + float tintOpacity, + out EffectAnimation tintOpacityAnimation, + float blurAmount, + out EffectAnimation 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; + +/// +/// A that represents a custom effect property setter that can be applied to a +/// +/// The type of property value to set +/// The target instance to target +/// The property value to set +public delegate void EffectSetter(CompositionBrush brush, T value) + where T : unmanaged; + +/// +/// A that represents a custom effect property animation that can be applied to a +/// +/// The type of property value to animate +/// The target instance to use to start the animation +/// The animation target value +/// The animation duration +/// A that completes when the target animation completes +public delegate Task EffectAnimation(CompositionBrush brush, T value, TimeSpan duration) + where T : unmanaged; + +/// +/// A that allows to build custom effects pipelines and create instances from them +/// +public sealed partial class PipelineBuilder +{ + /// + /// The instance used to produce the output for this pipeline + /// + private readonly Func> sourceProducer; + + /// + /// The collection of animation properties present in the current pipeline + /// + private readonly IReadOnlyCollection animationProperties; + + /// + /// The collection of info on the parameters that need to be initialized after creating the final + /// + private readonly IReadOnlyDictionary>> lazyParameters; + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will return the initial + private PipelineBuilder(Func> factory) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + this.sourceProducer = () => new ValueTask(new CompositionEffectSourceParameter(id)); + this.animationProperties = Array.Empty(); + this.lazyParameters = new Dictionary>> { { id, factory } }; + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will return the initial + private PipelineBuilder(Func> factory) + : this( + factory, + Array.Empty(), + new Dictionary>>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + private PipelineBuilder( + Func> factory, + IReadOnlyCollection animations) + : this( + factory, + animations, + new Dictionary>>()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + Func> factory, + IReadOnlyCollection animations, + IReadOnlyDictionary>> lazy) + { + this.sourceProducer = factory; + this.animationProperties = animations; + this.lazyParameters = lazy; + } + + /// + /// Initializes a new instance of the class. + /// + /// The source pipeline to attach the new effect to + /// A instance that will produce the new to add to the pipeline + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + PipelineBuilder source, + Func> factory, + IReadOnlyCollection? animations = null, + IReadOnlyDictionary>>? lazy = null) + : this( + factory, + animations?.Merge(source.animationProperties) ?? source.animationProperties, + lazy?.Merge(source.lazyParameters) ?? source.lazyParameters) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance that will produce the new to add to the pipeline + /// The first pipeline to merge + /// The second pipeline to merge + /// The collection of animation properties for the new effect + /// The collection of instances that needs to be initialized for the new effect + private PipelineBuilder( + Func> factory, + PipelineBuilder a, + PipelineBuilder b, + IReadOnlyCollection? animations = null, + IReadOnlyDictionary>>? 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)) + { + } + + /// + /// Builds a instance from the current effects pipeline + /// + /// A that returns the final instance to use + [Pure] + public async Task 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; + } + + /// + /// Builds the current pipeline and creates a that is applied to the input + /// + /// The target to apply the brush to + /// An optional to use to bind the size of the created brush + /// A that returns the final instance to use + public async Task 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; + } + + /// + /// Creates a new from the current effects pipeline + /// + /// A instance ready to be displayed + [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; + +/// +/// A type responsible for creating instances to attach to target elements. +/// +public abstract class AttachedVisualFactoryBase : DependencyObject +{ + /// + /// Creates a to attach to the target element. + /// + /// The target the visual will be attached to. + /// A instance that the caller will attach to the target element. + public abstract ValueTask 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; + +/// +/// A builder type for instance to apply to UI elements. +/// +[ContentProperty(Name = nameof(Effects))] +public sealed class PipelineVisualFactory : PipelineVisualFactoryBase +{ + /// + /// Gets or sets the source for the current pipeline (defaults to a with source). + /// + public PipelineBuilder? Source { get; set; } + + /// + /// Gets or sets the collection of effects to use in the current pipeline. + /// + public IList Effects + { + get + { + if (GetValue(EffectsProperty) is not IList effects) + { + effects = new List(); + + SetValue(EffectsProperty, effects); + } + + return effects; + } + set => SetValue(EffectsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register( + nameof(Effects), + typeof(IList), + typeof(PipelineVisualFactory), + new PropertyMetadata(null)); + + /// + public override async ValueTask GetAttachedVisualAsync(UIElement element) + { + var visual = (SpriteVisual)await base.GetAttachedVisualAsync(element); + + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(visual.Brush); + } + + return visual; + } + + /// + 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; + +/// +/// A base class that extends by leveraging the APIs. +/// +public abstract class PipelineVisualFactoryBase : AttachedVisualFactoryBase +{ + /// + public override async ValueTask GetAttachedVisualAsync(UIElement element) + { + var visual = ElementCompositionPreview.GetElementVisual(element).Compositor.CreateSpriteVisual(); + + visual.Brush = await OnPipelineRequested().BuildAsync(); + + return visual; + } + + /// + /// A method that builds and returns the pipeline to use in the current instance. + /// + /// A instance to create the to display. + 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 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 29B0EAEF-DDC3-461E-BE63-4052976510E8 + + + MediaExperiment.Tests + + \ 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 @@ + + + + 29B0EAEF-DDC3-461E-BE63-4052976510E8 + 14.0 + + + + + + + + diff --git a/tooling b/tooling index a852f23d..b32c3896 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit a852f23dabb110b7a51c068662309d00834d90a1 +Subproject commit b32c38965894ba107b93b7a0ae20107ba6adb040 From 5644222a83f3ec123f016eab8d436309a2e7cd84 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 6 Jul 2023 11:49:44 -0500 Subject: [PATCH 02/13] Unport Geometry --- components/Media/samples/Media.md | 15 - .../CanvasDrawingSessionExtensions.cs | 305 ------- .../Geometry/CanvasPathBuilderExtensions.cs | 458 ---------- .../Media/src/Geometry/CanvasPathGeometry.cs | 137 --- components/Media/src/Geometry/CanvasStroke.cs | 111 --- .../Geometry/CompositorGeometryExtensions.cs | 97 --- .../src/Geometry/Core/CanvasRoundRect.cs | 278 ------ .../Geometry/Core/GeometryTypeDefinitions.cs | 58 -- .../src/Geometry/Core/PathElementFactory.cs | 154 ---- .../Media/src/Geometry/Core/RegexFactory.cs | 599 ------------- .../Media/src/Geometry/CultureShield.cs | 57 -- .../Brush/AbstractCanvasBrushElement.cs | 70 -- .../Elements/Brush/ICanvasBrushElement.cs | 38 - .../Brush/LinearGradientBrushElement.cs | 210 ----- .../Brush/LinearGradientHdrBrushElement.cs | 207 ----- .../Brush/RadialGradientBrushElement.cs | 233 ----- .../Brush/RadialGradientHdrBrushElement.cs | 233 ----- .../Elements/Brush/SolidColorBrushElement.cs | 72 -- .../Elements/Path/AbstractPathElement.cs | 105 --- .../src/Geometry/Elements/Path/ArcElement.cs | 98 --- .../Elements/Path/CanvasEllipseFigure.cs | 80 -- .../Elements/Path/CanvasPathFigure.cs | 132 --- .../Elements/Path/CanvasPolygonFigure.cs | 82 -- .../Elements/Path/CanvasRectangleFigure.cs | 87 -- .../Path/CanvasRoundRectangleFigure.cs | 101 --- .../Elements/Path/ClosePathElement.cs | 99 --- .../Elements/Path/CubicBezierElement.cs | 103 --- .../Geometry/Elements/Path/FillRuleElement.cs | 75 -- .../Elements/Path/HorizontalLineElement.cs | 67 -- .../Elements/Path/ICanvasPathElement.cs | 63 -- .../src/Geometry/Elements/Path/LineElement.cs | 72 -- .../Geometry/Elements/Path/MoveToElement.cs | 85 -- .../Elements/Path/QuadraticBezierElement.cs | 95 -- .../Elements/Path/SmoothCubicBezierElement.cs | 115 --- .../Path/SmoothQuadraticBezierElement.cs | 104 --- .../Elements/Path/VerticalLineElement.cs | 67 -- .../Stroke/AbstractCanvasStrokeElement.cs | 64 -- .../Elements/Stroke/CanvasStrokeElement.cs | 105 --- .../Stroke/CanvasStrokeStyleElement.cs | 177 ---- .../Elements/Stroke/ICanvasStrokeElement.cs | 35 - .../Stroke/ICanvasStrokeStyleElement.cs | 34 - .../Media/src/Geometry/ICanvasStroke.cs | 35 - .../src/Geometry/Parsers/CanvasBrushParser.cs | 122 --- .../Geometry/Parsers/CanvasGeometryParser.cs | 134 --- .../Geometry/Parsers/CanvasStrokeParser.cs | 96 --- .../Parsers/CanvasStrokeStyleParser.cs | 90 -- .../Media/src/Geometry/Parsers/ColorParser.cs | 147 ---- components/Media/src/Geometry/Scalar.cs | 61 -- components/Media/src/Geometry/Utils.cs | 815 ------------------ 49 files changed, 6877 deletions(-) delete mode 100644 components/Media/samples/Media.md delete mode 100644 components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs delete mode 100644 components/Media/src/Geometry/CanvasPathBuilderExtensions.cs delete mode 100644 components/Media/src/Geometry/CanvasPathGeometry.cs delete mode 100644 components/Media/src/Geometry/CanvasStroke.cs delete mode 100644 components/Media/src/Geometry/CompositorGeometryExtensions.cs delete mode 100644 components/Media/src/Geometry/Core/CanvasRoundRect.cs delete mode 100644 components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs delete mode 100644 components/Media/src/Geometry/Core/PathElementFactory.cs delete mode 100644 components/Media/src/Geometry/Core/RegexFactory.cs delete mode 100644 components/Media/src/Geometry/CultureShield.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/ArcElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/ClosePathElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/FillRuleElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/LineElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/MoveToElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs delete mode 100644 components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs delete mode 100644 components/Media/src/Geometry/ICanvasStroke.cs delete mode 100644 components/Media/src/Geometry/Parsers/CanvasBrushParser.cs delete mode 100644 components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs delete mode 100644 components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs delete mode 100644 components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs delete mode 100644 components/Media/src/Geometry/Parsers/ColorParser.cs delete mode 100644 components/Media/src/Geometry/Scalar.cs delete mode 100644 components/Media/src/Geometry/Utils.cs diff --git a/components/Media/samples/Media.md b/components/Media/samples/Media.md deleted file mode 100644 index 95478ac4..00000000 --- a/components/Media/samples/Media.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: Media -author: githubaccount -description: TODO: Your experiment's description here -keywords: Media -dev_langs: - - csharp -category: Animations -subcategory: Media -discussion-id: 0 -issue-id: 0 -icon: assets/icon.png ---- - -TODO: Fill in information about this experiment and how to get started here... diff --git a/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs b/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs deleted file mode 100644 index dc4005f1..00000000 --- a/components/Media/src/Geometry/CanvasDrawingSessionExtensions.cs +++ /dev/null @@ -1,305 +0,0 @@ -// 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.Brushes; -using Microsoft.Graphics.Canvas.Geometry; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Extension methods for CanvasDrawingSession. -/// -public static class CanvasDrawingSessionExtensions -{ - /// - /// Draws a circle of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Center of the Circle - /// Radius of the Circle - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawCircle(this CanvasDrawingSession session, Vector2 centerPoint, float radius, ICanvasStroke stroke) - { - session.DrawCircle(centerPoint, radius, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a circle of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the Center in x axis - /// Ordinate of the Center in the y axis - /// Radius of the Circle - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawCircle(this CanvasDrawingSession session, float x, float y, float radius, ICanvasStroke stroke) - { - session.DrawCircle(x, y, radius, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws an Ellipse of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Center of the Circle - /// Radius in the X axis - /// Radius in the Y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawEllipse(this CanvasDrawingSession session, Vector2 centerPoint, float radiusX, float radiusY, ICanvasStroke stroke) - { - session.DrawEllipse(centerPoint, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws an Ellipse of at the given center, having the specified radius, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the Center on the x axis - /// Offset of the Center on the y axis - /// Radius in the X axis - /// Radius in the Y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawEllipse(this CanvasDrawingSession session, float x, float y, float radiusX, float radiusY, ICanvasStroke stroke) - { - session.DrawEllipse(x, y, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a geometry relative to the origin, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// CanvasGeometry to render - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, ICanvasStroke stroke) - { - session.DrawGeometry(geometry, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a geometry relative to the specified position, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// CanvasGeometry to render - /// Offset - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, Vector2 offset, ICanvasStroke stroke) - { - session.DrawGeometry(geometry, offset, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a geometry relative to the specified position, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// CanvasGeometry to render - /// Offset on the x axis - /// Offset on the y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawGeometry(this CanvasDrawingSession session, CanvasGeometry geometry, float x, float y, ICanvasStroke stroke) - { - session.DrawGeometry(geometry, x, y, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a line between the specified positions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Starting position of the line - /// Ending position of the line - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawLine(this CanvasDrawingSession session, Vector2 point0, Vector2 point1, ICanvasStroke stroke) - { - session.DrawLine(point0, point1, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a line between the specified positions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of Starting position of the line on x-axis - /// Offset of Starting position of the line on y-axis - /// Offset of Ending position of the line on x-axis - /// Offset of Ending position of the line on y-axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawLine(this CanvasDrawingSession session, float x0, float y0, float x1, float y1, ICanvasStroke stroke) - { - session.DrawLine(x0, y0, x1, y1, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Rectangle dimensions - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawRectangle(this CanvasDrawingSession session, Rect rect, ICanvasStroke stroke) - { - session.DrawRectangle(rect, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Rectangle on the x-axis - /// Offset of the top left corner of the Rectangle on the y-axis - /// Width of the Rectangle - /// Height of the Rectangle - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawRectangle(this CanvasDrawingSession session, float x, float y, float w, float h, ICanvasStroke stroke) - { - session.DrawRectangle(x, y, w, h, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a Rounded Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Rectangle dimensions - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawRoundedRectangle(this CanvasDrawingSession session, Rect rect, float radiusX, float radiusY, ICanvasStroke stroke) - { - session.DrawRoundedRectangle(rect, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a Rounded Rectangle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Rounded Rectangle on the x-axis - /// Offset of the top left corner of the Rounded Rectangle on the y-axis - /// Width of the Rounded Rectangle - /// Height of the Rounded Rectangle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawRoundedRectangle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke) - { - session.DrawRoundedRectangle(x, y, w, h, radiusX, radiusY, stroke.Brush, stroke.Width, stroke.Style); - } - - /// - /// Draws a Squircle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasStroke stroke) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.DrawGeometry(geometry, stroke); - } - - /// - /// Draws a Squircle of the specified dimensions, using a CanvasStroke to define the stroke width, the stroke color and stroke style. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// Offset of the Squircle from the origin. - /// CanvasStroke defining the stroke width, the stroke - /// color and stroke style. - public static void DrawSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, ICanvasStroke stroke) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.DrawGeometry(geometry, offset, stroke); - } - - /// - /// Fills a Squircle of the specified dimensions, using the given color. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// Color to fill the Squircle. - public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Color color) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.FillGeometry(geometry, color); - } - - /// - /// Fills a Squircle of the specified dimensions, using the given brush. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// Brush to fill the Squircle. - public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, ICanvasBrush brush) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.FillGeometry(geometry, brush); - } - - /// - /// Fills a Squircle of the specified dimensions, using the given color at specified offset. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// Offset of the Squircle from the origin. - /// Color to fill the Squircle. - public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, Color color) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.FillGeometry(geometry, offset, color); - } - - /// - /// Fills a Squircle of the specified dimensions, using the given brush at specified offset. - /// - /// CanvasDrawingSession - /// Offset of the top left corner of the Squircle on the x-axis - /// Offset of the top left corner of the Squircle on the y-axis - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x axis - /// Corner Radius on the y axis - /// Offset of the Squircle from the origin. - /// Brush to fill the Squircle. - public static void FillSquircle(this CanvasDrawingSession session, float x, float y, float w, float h, float radiusX, float radiusY, Vector2 offset, ICanvasBrush brush) - { - using var geometry = CanvasPathGeometry.CreateSquircle(session.Device, x, y, w, h, radiusX, radiusY); - session.FillGeometry(geometry, offset, brush); - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs b/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs deleted file mode 100644 index cd34bc86..00000000 --- a/components/Media/src/Geometry/CanvasPathBuilderExtensions.cs +++ /dev/null @@ -1,458 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Defines extension methods for CanvasPathBuilder. -/// -public static class CanvasPathBuilderExtensions -{ - private const float SquircleFactor = 1.125f; - private const float ControlPointFactor = 46f / 64f; - - /// - /// Adds a line in the form of a cubic bezier. The control point of the quadratic bezier will be the endpoint of the line itself. - /// - /// - /// Ending location of the line segment. - public static void AddLineAsQuadraticBezier(this CanvasPathBuilder pathBuilder, Vector2 end) - { - pathBuilder.AddQuadraticBezier(end, end); - } - - /// - /// Adds a line in the form of a cubic bezier. The two control points of the cubic bezier will be the endpoints of the line itself. - /// - /// - /// Starting location of the line segment. - /// Ending location of the line segment. - public static void AddLineAsCubicBezier(this CanvasPathBuilder pathBuilder, Vector2 start, Vector2 end) - { - pathBuilder.AddCubicBezier(start, end, end); - } - - /// - /// Adds a circle figure to the path. - /// - /// - /// Center location of the circle. - /// Radius of the circle. - public static void AddCircleFigure(this CanvasPathBuilder pathBuilder, Vector2 center, float radius) - { - pathBuilder.AddEllipseFigure(center.X, center.Y, radius, radius); - } - - /// - /// Adds a circle figure to the path. - /// - /// - /// X coordinate of the center location of the circle. - /// Y coordinate of the center location of the circle. - /// Radius of the circle. - public static void AddCircleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float radius) - { - pathBuilder.AddEllipseFigure(x, y, radius, radius); - } - - /// - /// Adds an ellipse figure to the path. - /// - /// - /// Center location of the ellipse. - /// Radius of the ellipse on the X-axis. - /// Radius of the ellipse on the Y-axis. - public static void AddEllipseFigure(this CanvasPathBuilder pathBuilder, Vector2 center, float radiusX, float radiusY) - { - pathBuilder.AddEllipseFigure(center.X, center.Y, radiusX, radiusY); - } - - /// - /// Adds an ellipse figure to the path. - /// - /// - /// X coordinate of the center location of the ellipse. - /// Y coordinate of the center location of the ellipse. - /// Radius of the ellipse on the X-axis. - /// Radius of the ellipse on the Y-axis. - public static void AddEllipseFigure(this CanvasPathBuilder pathBuilder, float x, float y, float radiusX, float radiusY) - { - // Sanitize the radiusX by taking the absolute value - radiusX = Math.Abs(radiusX); - - // Sanitize the radiusY by taking the absolute value - radiusY = Math.Abs(radiusY); - - try - { - pathBuilder.BeginFigure(x + radiusX, y); - } - catch (ArgumentException) - { - // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddEllipseFigure() method. - static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddEllipseFigure occurred, " + - "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + - "before calling CanvasPathBuilder.AddEllipseFigure, to end the previous figure."); - - Throw(); - } - - // First Semi-Ellipse - pathBuilder.AddArc(new Vector2(x - radiusX, y), radiusX, radiusY, Scalar.Pi, CanvasSweepDirection.Clockwise, CanvasArcSize.Large); - - // Second Semi-Ellipse - pathBuilder.AddArc(new Vector2(x + radiusX, y), radiusX, radiusY, Scalar.Pi, CanvasSweepDirection.Clockwise, CanvasArcSize.Large); - - // End Figure - pathBuilder.EndFigure(CanvasFigureLoop.Closed); - } - - /// - /// Adds a n-sided polygon figure to the path. - /// - /// - /// Number of sides of the polygon. - /// Center location of the polygon. - /// Radius of the circle circumscribing the polygon i.e. the distance - /// of each of the vertices of the polygon from the center. - public static void AddPolygonFigure(this CanvasPathBuilder pathBuilder, int numSides, Vector2 center, float radius) - { - pathBuilder.AddPolygonFigure(numSides, center.X, center.Y, radius); - } - - /// - /// Adds a n-sided polygon figure to the path. - /// - /// - /// Number of sides of the polygon. - /// X coordinate of the center location of the polygon. - /// Y coordinate of the center location of the polygon. - /// Radius of the circle circumscribing the polygon i.e. the distance - /// of each of the vertices of the polygon from the center. - public static void AddPolygonFigure(this CanvasPathBuilder pathBuilder, int numSides, float x, float y, float radius) - { - // Sanitize the radius by taking the absolute value - radius = Math.Abs(radius); - - // A polygon should have at least 3 sides - if (numSides <= 2) - { - ThrowArgumentOutOfRangeException(); - } - - // Calculate the first vertex location based on the number of sides - var angle = Scalar.TwoPi / numSides; - var startAngle = numSides % 2 == 1 ? Scalar.PiByTwo : Scalar.PiByTwo - (angle / 2f); - - var startX = x + (float)(radius * Math.Cos(startAngle)); - var startY = y - (float)(radius * Math.Sin(startAngle)); - - try - { - pathBuilder.BeginFigure(startX, startY); - } - catch (ArgumentException) - { - // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. - static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddPolygonFigure occurred, " + - "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + - "before calling CanvasPathBuilder.AddPolygonFigure, to end the previous figure."); - - Throw(); - } - - // Add lines to the remaining vertices - for (var i = 1; i < numSides; i++) - { - var posX = x + (float)(radius * Math.Cos(startAngle + (i * angle))); - var posY = y - (float)(radius * Math.Sin(startAngle + (i * angle))); - pathBuilder.AddLine(posX, posY); - } - - // Add a line to the first vertex so that the lines join properly - pathBuilder.AddLine(startX, startY); - - // End the Figure - pathBuilder.EndFigure(CanvasFigureLoop.Closed); - - static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException($"Parameter {nameof(numSides)} must be greater than 2."); - } - - /// - /// Adds a Rectangle to the Path. - /// - /// - /// X offset of the TopLeft corner of the Rectangle - /// Y offset of the TopLeft corner of the Rectangle - /// Width of the Rectangle - /// Height of the Rectangle - public static void AddRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height) - { - // Sanitize the width by taking the absolute value - width = Math.Abs(width); - - // Sanitize the height by taking the absolute value - height = Math.Abs(height); - - try - { - pathBuilder.BeginFigure(x, y); - } - catch (ArgumentException) - { - // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. - static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddRectangleFigure occurred, " + - "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + - "before calling CanvasPathBuilder.AddRectangleFigure, to end the previous figure."); - - Throw(); - } - - // Top Side - pathBuilder.AddLine(x + width, y); - - // Right Side - pathBuilder.AddLine(x + width, y + height); - - // Bottom Side - pathBuilder.AddLine(x, y + height); - - // Left Side - pathBuilder.AddLine(x, y); - - // End the Figure - pathBuilder.EndFigure(CanvasFigureLoop.Closed); - } - - /// - /// Adds a RoundedRectangle to the Path. - /// - /// - /// X offset of the TopLeft corner of the RoundedRectangle - /// Y offset of the TopLeft corner of the RoundedRectangle - /// Width of the RoundedRectangle - /// Height of the RoundedRectangle - /// Corner Radius on the x-axis - /// Corner Radius on the y-axis - public static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height, float radiusX, float radiusY) - { - // Sanitize the width by taking the absolute value - width = Math.Abs(width); - - // Sanitize the height by taking the absolute value - height = Math.Abs(height); - - var rect = new CanvasRoundRect(x, y, width, height, radiusX, radiusY); - pathBuilder.AddRoundedRectangleFigure(ref rect, true); - } - - /// - /// Adds a RoundedRectangle to the Path. (To be used internally) - /// - /// - /// CanvasRoundRect - /// Flag to indicate whether exception should be raised - internal static void AddRoundedRectangleFigure(this CanvasPathBuilder pathBuilder, ref CanvasRoundRect rect, bool raiseException = false) - { - try - { - // Begin path - pathBuilder.BeginFigure(new Vector2(rect.LeftTopX, rect.LeftTopY)); - } - catch (ArgumentException) - { - if (!raiseException) - { - return; - } - - // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. - static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddRoundedRectangleFigure occurred, " + - "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + - "before calling CanvasPathBuilder.AddRoundedRectangleFigure, to end the previous figure."); - - Throw(); - } - - // Top line - pathBuilder.AddLine(new Vector2(rect.RightTopX, rect.RightTopY)); - - // Upper-right corner - var radiusX = rect.TopRightX - rect.RightTopX; - var radiusY = rect.TopRightY - rect.RightTopY; - var center = new Vector2(rect.RightTopX, rect.TopRightY); - pathBuilder.AddArc(center, radiusX, radiusY, 3f * Scalar.PiByTwo, Scalar.PiByTwo); - - // Right line - pathBuilder.AddLine(new Vector2(rect.BottomRightX, rect.BottomRightY)); - - // Lower-right corner - radiusX = rect.BottomRightX - rect.RightBottomX; - radiusY = rect.RightBottomY - rect.BottomRightY; - center = new Vector2(rect.RightBottomX, rect.BottomRightY); - pathBuilder.AddArc(center, radiusX, radiusY, 0f, Scalar.PiByTwo); - - // Bottom line - pathBuilder.AddLine(new Vector2(rect.LeftBottomX, rect.LeftBottomY)); - - // Lower-left corner - radiusX = rect.LeftBottomX - rect.BottomLeftX; - radiusY = rect.LeftBottomY - rect.BottomLeftY; - center = new Vector2(rect.LeftBottomX, rect.BottomLeftY); - pathBuilder.AddArc(center, radiusX, radiusY, Scalar.PiByTwo, Scalar.PiByTwo); - - // Left line - pathBuilder.AddLine(new Vector2(rect.TopLeftX, rect.TopLeftY)); - - // Upper-left corner - radiusX = rect.LeftTopX - rect.TopLeftX; - radiusY = rect.TopLeftY - rect.LeftTopY; - center = new Vector2(rect.LeftTopX, rect.TopLeftY); - pathBuilder.AddArc(center, radiusX, radiusY, 2f * Scalar.PiByTwo, Scalar.PiByTwo); - - // End path - pathBuilder.EndFigure(CanvasFigureLoop.Closed); - } - - /// - /// Adds a Squircle to the Path. - /// - /// - /// X offset of the TopLeft corner of the Squircle - /// Y offset of the TopLeft corner of the Squircle - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x-axis - /// Corner Radius on the y-axis - public static void AddSquircleFigure(this CanvasPathBuilder pathBuilder, float x, float y, float width, float height, float radiusX, float radiusY) - { - // Sanitize the width by taking the absolute value - width = Math.Abs(width); - - // Sanitize the height by taking the absolute value - height = Math.Abs(height); - - var rect = new CanvasRoundRect(x, y, width, height, radiusX * SquircleFactor, radiusY * SquircleFactor); - pathBuilder.AddSquircleFigure(ref rect, true); - } - - /// - /// Adds a Squircle to the Path. (To be used internally) - /// - /// - /// CanvasRoundRect - /// Flag to indicate whether exception should be raised - internal static void AddSquircleFigure(this CanvasPathBuilder pathBuilder, ref CanvasRoundRect rect, bool raiseException = false) - { - try - { - // Begin path - pathBuilder.BeginFigure(new Vector2(rect.LeftTopX, rect.LeftTopY)); - } - catch (ArgumentException) - { - if (!raiseException) - { - return; - } - - // An ArgumentException will be raised if another figure was already begun( and not ended) before calling AddPolygonFigure() method. - static void Throw() => throw new InvalidOperationException("A call to CanvasPathBuilder.AddSquircleFigure occurred, " + - "when another figure was already begun. Please call CanvasPathBuilder.EndFigure method, " + - "before calling CanvasPathBuilder.AddSquircleFigure, to end the previous figure."); - - Throw(); - } - - // Top line - pathBuilder.AddLine(new Vector2(rect.RightTopX, rect.RightTopY)); - - // Upper-right corner - var rightTopControlPoint = new Vector2(rect.RightTopX + ((rect.TopRightX - rect.RightTopX) * ControlPointFactor), rect.RightTopY); - var topRightControlPoint = new Vector2(rect.TopRightX, rect.TopRightY - ((rect.TopRightY - rect.RightTopY) * ControlPointFactor)); - - // Top Right Curve - pathBuilder.AddCubicBezier(rightTopControlPoint, topRightControlPoint, new Vector2(rect.TopRightX, rect.TopRightY)); - - // Right line - pathBuilder.AddLine(new Vector2(rect.BottomRightX, rect.BottomRightY)); - - // Lower-right corner - var bottomRightControlPoint = new Vector2(rect.BottomRightX, rect.BottomRightY + ((rect.RightBottomY - rect.BottomRightY) * ControlPointFactor)); - var rightBottomControlPoint = new Vector2(rect.RightBottomX + ((rect.BottomRightX - rect.RightBottomX) * ControlPointFactor), rect.RightBottomY); - - // Bottom Right Curve - pathBuilder.AddCubicBezier(bottomRightControlPoint, rightBottomControlPoint, new Vector2(rect.RightBottomX, rect.RightBottomY)); - - // Bottom line - pathBuilder.AddLine(new Vector2(rect.LeftBottomX, rect.LeftBottomY)); - - // Lower-left corner - var leftBottomControlPoint = new Vector2(rect.LeftBottomX - ((rect.LeftBottomX - rect.BottomLeftX) * ControlPointFactor), rect.LeftBottomY); - var bottomLeftControlPoint = new Vector2(rect.BottomLeftX, rect.BottomLeftY + ((rect.LeftBottomY - rect.BottomLeftY) * ControlPointFactor)); - - // Bottom Left Curve - pathBuilder.AddCubicBezier(leftBottomControlPoint, bottomLeftControlPoint, new Vector2(rect.BottomLeftX, rect.BottomLeftY)); - - // Left line - pathBuilder.AddLine(new Vector2(rect.TopLeftX, rect.TopLeftY)); - - // Upper-left corner - var topLeftControlPoint = new Vector2(rect.TopLeftX, rect.TopLeftY - ((rect.TopLeftY - rect.LeftTopY) * ControlPointFactor)); - var leftTopControlPoint = new Vector2(rect.LeftTopX - ((rect.LeftTopX - rect.TopLeftX) * ControlPointFactor), rect.LeftTopY); - - // Top Left Curve - pathBuilder.AddCubicBezier(topLeftControlPoint, leftTopControlPoint, new Vector2(rect.LeftTopX, rect.LeftTopY)); - - // End path - pathBuilder.EndFigure(CanvasFigureLoop.Closed); - } - - /// - /// Builds a path with the given collection of points. - /// - /// - /// Specifies whether the figure is open or closed. - /// This affects the appearance of fills and strokes, as well as geometry operations. - /// Collection of Vector2 points on the path. - /// object - public static CanvasPathBuilder BuildPathWithLines(this CanvasPathBuilder builder, CanvasFigureLoop canvasFigureLoop, IEnumerable points) - { - var first = true; - - foreach (var point in points) - { - if (first) - { - builder.BeginFigure(point); - first = false; - } - else - { - builder.AddLine(point); - } - } - - builder.EndFigure(canvasFigureLoop); - return builder; - } - - /// - /// Builds a path with the given collection of points in the (x, y) pattern. - /// - /// - /// Specifies whether the figure is open or closed. - /// This affects the appearance of fills and strokes, as well as geometry operations. - /// Collection of points in the (x, y) pattern on the path. - /// object - public static CanvasPathBuilder BuildPathWithLines(this CanvasPathBuilder builder, CanvasFigureLoop canvasFigureLoop, IEnumerable<(float x, float y)> nodes) - { - var vectors = nodes.Select(n => new Vector2(n.x, n.y)); - return BuildPathWithLines(builder, canvasFigureLoop, vectors); - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/CanvasPathGeometry.cs b/components/Media/src/Geometry/CanvasPathGeometry.cs deleted file mode 100644 index 56b06020..00000000 --- a/components/Media/src/Geometry/CanvasPathGeometry.cs +++ /dev/null @@ -1,137 +0,0 @@ -// 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.Brushes; -using Microsoft.Graphics.Canvas.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Parsers; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Helper Class for creating Win2d objects. -/// -public static class CanvasPathGeometry -{ - /// - /// Parses the Path data string and converts it to CanvasGeometry. - /// - /// Path data - /// - public static CanvasGeometry? CreateGeometry(string pathData) - { - return CreateGeometry(null, pathData); - } - - /// - /// Parses the Path data string and converts it to CanvasGeometry. - /// - /// - /// Path data - /// - public static CanvasGeometry? CreateGeometry(ICanvasResourceCreator? resourceCreator, string pathData) - { - using (new CultureShield("en-US")) - { - // Get the CanvasGeometry from the path data - return CanvasGeometryParser.Parse(resourceCreator, pathData); - } - } - - /// - /// Creates a Squircle geometry with the specified extents. - /// - /// Resource creator - /// X offset of the TopLeft corner of the Squircle - /// Y offset of the TopLeft corner of the Squircle - /// Width of the Squircle - /// Height of the Squircle - /// Corner Radius on the x-axis - /// Corner Radius on the y-axis - /// - public static CanvasGeometry CreateSquircle(ICanvasResourceCreator resourceCreator, float x, float y, float width, float height, float radiusX, float radiusY) - { - using var pathBuilder = new CanvasPathBuilder(resourceCreator); - pathBuilder.AddSquircleFigure(x, y, width, height, radiusX, radiusY); - return CanvasGeometry.CreatePath(pathBuilder); - } - - /// - /// Parses the given Brush data string and converts it to ICanvasBrush. - /// - /// ICanvasResourceCreator - /// Brush data in string format - /// - public static ICanvasBrush? CreateBrush(ICanvasResourceCreator resourceCreator, string brushData) - { - using (new CultureShield("en-US")) - { - return CanvasBrushParser.Parse(resourceCreator, brushData); - } - } - - /// - /// Parses the given Stroke data string and converts it to ICanvasStroke. - /// - /// ICanvasResourceCreator - /// Stroke data in string format - /// - public static ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator, string strokeData) - { - using (new CultureShield("en-US")) - { - return CanvasStrokeParser.Parse(resourceCreator, strokeData); - } - } - - /// - /// Parses the give CanvasStrokeStyle data string and converts it to CanvasStrokeStyle. - /// - /// CanvasStrokeStyle data in string format - /// object - public static CanvasStrokeStyle CreateStrokeStyle(string styleData) - { - using (new CultureShield("en-US")) - { - return CanvasStrokeStyleParser.Parse(styleData); - } - } - - /// - /// Converts the color string in Hexadecimal or HDR color format to the corresponding Color object. - /// The hexadecimal color string should be in #RRGGBB or #AARRGGBB format. - /// The '#' character is optional. - /// The HDR color string should be in R G B A format. - /// (R, G, B & A should have value in the range between 0 and 1, inclusive) - /// - /// Color string in Hexadecimal or HDR format - /// Color - public static Color CreateColor(string colorString) - { - using (new CultureShield("en-US")) - { - return ColorParser.Parse(colorString); - } - } - - /// - /// Converts a Vector4 High Dynamic Range Color to Color object. - /// Negative components of the Vector4 will be sanitized by taking the absolute - /// value of the component. The HDR Color components should have value in - /// the range between 0 and 1, inclusive. If they are more than 1, they - /// will be clamped at 1. - /// Vector4's X, Y, Z, W components match to Color's R, G, B, A components respectively. - /// - /// High Dynamic Range Color - /// Color - public static Color CreateColor(Vector4 hdrColor) - { - using (new CultureShield("en-US")) - { - return ColorParser.Parse(hdrColor); - } - } -} diff --git a/components/Media/src/Geometry/CanvasStroke.cs b/components/Media/src/Geometry/CanvasStroke.cs deleted file mode 100644 index dd2f3d5b..00000000 --- a/components/Media/src/Geometry/CanvasStroke.cs +++ /dev/null @@ -1,111 +0,0 @@ -// 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.Brushes; -using Microsoft.Graphics.Canvas.Geometry; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Class to represent the Stroke which can be used to render an outline on a -/// -public sealed class CanvasStroke : ICanvasStroke -{ - /// - /// Gets or sets the brush with which the stroke will be rendered - /// - public ICanvasBrush Brush { get; set; } - - /// - /// Gets or sets the width of the - /// - public float Width { get; set; } - - /// - /// Gets or sets the Style of the - /// - public CanvasStrokeStyle Style { get; set; } - - /// - /// Gets or sets the Transform matrix of the brush. - /// - public Matrix3x2 Transform - { - get => GetTransform(); - - set => SetTransform(value); - } - - /// - /// Initializes a new instance of the class. - /// - /// The brush with which the will be rendered - /// Width of the - public CanvasStroke(ICanvasBrush brush, float strokeWidth = 1f) - : this(brush, strokeWidth, new CanvasStrokeStyle()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The brush with which the will be rendered - /// Width of the - /// Style of the - public CanvasStroke(ICanvasBrush brush, float strokeWidth, CanvasStrokeStyle strokeStyle) - { - Brush = brush; - Width = strokeWidth; - Style = strokeStyle; - } - - /// - /// Initializes a new instance of the class. - /// - /// ICanvasResourceCreator - /// Color of the - /// Width of the - public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth = 1f) - : this(device, strokeColor, strokeWidth, new CanvasStrokeStyle()) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// ICanvasResourceCreator - /// Color of the - /// Width of the - /// Style of the - public CanvasStroke(ICanvasResourceCreator device, Color strokeColor, float strokeWidth, CanvasStrokeStyle strokeStyle) - { - Brush = new CanvasSolidColorBrush(device, strokeColor); - Width = strokeWidth; - Style = strokeStyle; - } - - /// - /// Sets the 's Transform. - /// - /// Transform matrix to set - private void SetTransform(Matrix3x2 value) - { - if (Brush != null) - { - Brush.Transform = value; - } - } - - /// - /// Gets the 's Transform. If stroke is null, then returns Matrix3x2.Identity. - /// - /// Transform matrix of the - private Matrix3x2 GetTransform() - { - return Brush?.Transform ?? Matrix3x2.Identity; - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/CompositorGeometryExtensions.cs b/components/Media/src/Geometry/CompositorGeometryExtensions.cs deleted file mode 100644 index 8e5f0987..00000000 --- a/components/Media/src/Geometry/CompositorGeometryExtensions.cs +++ /dev/null @@ -1,97 +0,0 @@ -// 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.Geometry; -using Windows.UI.Composition; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Extension methods for compositor to support Win2d Path Mini Language. -/// -public static class CompositorGeometryExtensions -{ - /// - /// Creates a based on the specified path data. - /// - /// - /// Path data (Win2d Path Mini Language) in string format. - /// - public static CompositionPath? CreatePath(this Compositor compositor, string pathData) - { - // Create CanvasGeometry - var geometry = CanvasPathGeometry.CreateGeometry(pathData); - - // Create CompositionPath - return new CompositionPath(geometry); - } - - /// - /// Creates a based on the given path data. - /// - /// - /// Path data (Win2d Path Mini Language) in string format. - /// - public static CompositionPathGeometry? CreatePathGeometry(this Compositor compositor, string pathData) - { - // Create CanvasGeometry - var geometry = CanvasPathGeometry.CreateGeometry(pathData); - - // Create CompositionPathGeometry - return compositor.CreatePathGeometry(new CompositionPath(geometry)); - } - - /// - /// Creates a based on the specified path data. - /// - /// - /// Path data (Win2d Path Mini Language) in string format. - /// - public static CompositionSpriteShape? CreateSpriteShape(this Compositor compositor, string pathData) - { - // Create CanvasGeometry - var geometry = CanvasPathGeometry.CreateGeometry(pathData); - - // Create CompositionPathGeometry - var pathGeometry = compositor.CreatePathGeometry(new CompositionPath(geometry)); - - // Create CompositionSpriteShape - return compositor.CreateSpriteShape(pathGeometry); - } - - /// - /// Creates a from the specified . - /// - /// - /// - /// CompositionGeometricClip - public static CompositionGeometricClip CreateGeometricClip(this Compositor compositor, CanvasGeometry geometry) - { - // Create the CompositionPath - var path = new CompositionPath(geometry); - - // Create the CompositionPathGeometry - var pathGeometry = compositor.CreatePathGeometry(path); - - // Create the CompositionGeometricClip - return compositor.CreateGeometricClip(pathGeometry); - } - - /// - /// Parses the specified path data and converts it to . - /// - /// - /// Path data (Win2d Path Mini Language) in string format. - /// - public static CompositionGeometricClip? CreateGeometricClip(this Compositor compositor, string pathData) - { - // Create the CanvasGeometry from the path data - var geometry = CanvasPathGeometry.CreateGeometry(pathData); - if (geometry is null) - return null; - - // Create the CompositionGeometricClip - return compositor.CreateGeometricClip(geometry); - } -} diff --git a/components/Media/src/Geometry/Core/CanvasRoundRect.cs b/components/Media/src/Geometry/Core/CanvasRoundRect.cs deleted file mode 100644 index 0457ded9..00000000 --- a/components/Media/src/Geometry/Core/CanvasRoundRect.cs +++ /dev/null @@ -1,278 +0,0 @@ -// 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; - -namespace CommunityToolkit.WinUI.Media.Geometry.Core; - -/// -/// Structure which encapsulates the details of each of the core points of the path of the rounded rectangle which is calculated based on -/// either the given (Size, CornerRadius, BorderThickness and Padding) or (Size, RadiusX and RadiusY). -/// -internal struct CanvasRoundRect -{ - private const float Factor = 0.5f; - - private readonly float _leftTopWidth; - private readonly float _topLeftHeight; - private readonly float _topRightHeight; - private readonly float _rightTopWidth; - private readonly float _rightBottomWidth; - private readonly float _bottomRightHeight; - private readonly float _bottomLeftHeight; - private readonly float _leftBottomWidth; - - // This is the location of the properties within the Rect - // |--LeftTop----------------------RightTop--| - // | | - // TopLeft TopRight - // | | - // | | - // | | - // | | - // | | - // | | - // BottomLeft BottomRight - // | | - // |--LeftBottom----------------RightBottom--| - internal float LeftTopX { get; private set; } - - internal float LeftTopY { get; private set; } - - internal float TopLeftX { get; private set; } - - internal float TopLeftY { get; private set; } - - internal float TopRightX { get; private set; } - - internal float TopRightY { get; private set; } - - internal float RightTopX { get; private set; } - - internal float RightTopY { get; private set; } - - internal float RightBottomX { get; private set; } - - internal float RightBottomY { get; private set; } - - internal float BottomRightX { get; private set; } - - internal float BottomRightY { get; private set; } - - internal float BottomLeftX { get; private set; } - - internal float BottomLeftY { get; private set; } - - internal float LeftBottomX { get; private set; } - - internal float LeftBottomY { get; private set; } - - internal float Width { get; } - - internal float Height { get; } - - /// - /// Initializes a new instance of the struct. - /// - /// Origin of the Rect (absolute location of Top Left corner) - /// Size of the Rect - /// CornerRadius - /// BorderThickness - /// Padding - /// Flag to indicate whether outer or inner border needs - /// to be calculated - internal CanvasRoundRect(Vector2 origin, Vector2 size, Vector4 cornerRadius, Vector4 borderThickness, Vector4 padding, bool isOuterBorder) - : this() - { - Width = Math.Max(0f, size.X); - Height = Math.Max(0f, size.Y); - - var left = Factor * (borderThickness.X + padding.X); - var top = Factor * (borderThickness.Y + padding.Y); - var right = Factor * (borderThickness.Z + padding.Z); - var bottom = Factor * (borderThickness.W + padding.W); - - if (isOuterBorder) - { - // Top Left corner radius - if (cornerRadius.X.IsZero()) - { - _leftTopWidth = _topLeftHeight = 0f; - } - else - { - _leftTopWidth = cornerRadius.X + left; - _topLeftHeight = cornerRadius.X + top; - } - - // Top Right corner radius - if (cornerRadius.Y.IsZero()) - { - _topRightHeight = _rightTopWidth = 0f; - } - else - { - _topRightHeight = cornerRadius.Y + top; - _rightTopWidth = cornerRadius.Y + right; - } - - // Bottom Right corner radius - if (cornerRadius.Z.IsZero()) - { - _rightBottomWidth = _bottomRightHeight = 0f; - } - else - { - _rightBottomWidth = cornerRadius.Z + right; - _bottomRightHeight = cornerRadius.Z + bottom; - } - - // Bottom Left corner radius - if (cornerRadius.W.IsZero()) - { - _bottomLeftHeight = _leftBottomWidth = 0f; - } - else - { - _bottomLeftHeight = cornerRadius.W + bottom; - _leftBottomWidth = cornerRadius.W + left; - } - } - else - { - _leftTopWidth = Math.Max(0f, cornerRadius.X - left); - _topLeftHeight = Math.Max(0f, cornerRadius.X - top); - _topRightHeight = Math.Max(0f, cornerRadius.Y - top); - _rightTopWidth = Math.Max(0f, cornerRadius.Y - right); - _rightBottomWidth = Math.Max(0f, cornerRadius.Z - right); - _bottomRightHeight = Math.Max(0f, cornerRadius.Z - bottom); - _bottomLeftHeight = Math.Max(0f, cornerRadius.W - bottom); - _leftBottomWidth = Math.Max(0f, cornerRadius.W - left); - } - - // Calculate the anchor points - ComputeCoordinates(origin.X, origin.Y); - } - - /// - /// Initializes a new instance of the struct. - /// - /// Top Left corner of the Rounded Rectangle - /// Dimensions of the Rounded Rectangle - /// Radius of the corners on the x-axis - /// Radius of the corners on the y-axis - internal CanvasRoundRect(Vector2 origin, Vector2 size, float radiusX, float radiusY) - : this(origin.X, origin.Y, size.X, size.Y, radiusX, radiusY) - { - } - - /// - /// Initializes a new instance of the struct. - /// - /// X offset of the Top Left corner of the Rounded Rectangle - /// Y offset of the Top Left corner of the Rounded Rectangle - /// Width of the Rounded Rectangle. - /// Height of the Rounded Rectangle. - /// Radius of the corners on the x-axis - /// Radius of the corners on the y-axis - internal CanvasRoundRect(float x, float y, float width, float height, float radiusX, float radiusY) - : this() - { - Width = Math.Max(0f, width); - Height = Math.Max(0f, height); - - // Sanitize the radii by taking the absolute value - radiusX = Math.Min(Math.Abs(radiusX), width / 2f); - radiusY = Math.Min(Math.Abs(radiusY), height / 2); - - _leftTopWidth = radiusX; - _rightTopWidth = radiusX; - _rightBottomWidth = radiusX; - _leftBottomWidth = radiusX; - _topLeftHeight = radiusY; - _topRightHeight = radiusY; - _bottomRightHeight = radiusY; - _bottomLeftHeight = radiusY; - - ComputeCoordinates(x, y); - } - - /// - /// Computes the coordinates of the crucial points on the CanvasRoundRect - /// - /// X coordinate of the origin. - /// Y coordinate of the origin. - private void ComputeCoordinates(float originX, float originY) - { - // compute the coordinates of the key points - var leftTopX = _leftTopWidth; - var leftTopY = 0f; - var rightTopX = Width - _rightTopWidth; - var rightTopY = 0f; - var topRightX = Width; - var topRightY = _topRightHeight; - var bottomRightX = Width; - var bottomRightY = Height - _bottomRightHeight; - var rightBottomX = Width - _rightBottomWidth; - var rightBottomY = Height; - var leftBottomX = _leftBottomWidth; - var leftBottomY = Height; - var bottomLeftX = 0f; - var bottomLeftY = Height - _bottomLeftHeight; - var topLeftX = 0f; - var topLeftY = _topLeftHeight; - - // check anchors for overlap and resolve by partitioning corners according to - // the percentage of each one. - // top edge - if (leftTopX > rightTopX) - { - var v = _leftTopWidth / (_leftTopWidth + _rightTopWidth) * Width; - leftTopX = v; - rightTopX = v; - } - - // right edge - if (topRightY > bottomRightY) - { - var v = _topRightHeight / (_topRightHeight + _bottomRightHeight) * Height; - topRightY = v; - bottomRightY = v; - } - - // bottom edge - if (leftBottomX > rightBottomX) - { - var v = _leftBottomWidth / (_leftBottomWidth + _rightBottomWidth) * Width; - rightBottomX = v; - leftBottomX = v; - } - - // left edge - if (topLeftY > bottomLeftY) - { - var v = _topLeftHeight / (_topLeftHeight + _bottomLeftHeight) * Height; - bottomLeftY = v; - topLeftY = v; - } - - // Apply origin translation - LeftTopX = leftTopX + originX; - LeftTopY = leftTopY + originY; - RightTopX = rightTopX + originX; - RightTopY = rightTopY + originY; - TopRightX = topRightX + originX; - TopRightY = topRightY + originY; - BottomRightX = bottomRightX + originX; - BottomRightY = bottomRightY + originY; - RightBottomX = rightBottomX + originX; - RightBottomY = rightBottomY + originY; - LeftBottomX = leftBottomX + originX; - LeftBottomY = leftBottomY + originY; - BottomLeftX = bottomLeftX + originX; - BottomLeftY = bottomLeftY + originY; - TopLeftX = topLeftX + originX; - TopLeftY = topLeftY + originY; - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs b/components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs deleted file mode 100644 index 6f896f02..00000000 --- a/components/Media/src/Geometry/Core/GeometryTypeDefinitions.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.Geometry.Core; - -/// -/// Enum for the various PathFigures. -/// -internal enum PathFigureType -{ - FillRule, - PathFigure, - EllipseFigure, - PolygonFigure, - RectangleFigure, - RoundedRectangleFigure -} - -/// -/// Enum for the various PathElements. -/// -internal enum PathElementType -{ - MoveTo, - Line, - HorizontalLine, - VerticalLine, - QuadraticBezier, - SmoothQuadraticBezier, - CubicBezier, - SmoothCubicBezier, - Arc, - ClosePath -} - -/// -/// Enum for the various types of Brushes. -/// -internal enum BrushType -{ - SolidColor, - LinearGradient, - RadialGradient, - LinearGradientHdr, - RadialGradientHdr -} - -/// -/// Enum for the various types of GradientStop attributes. -/// -internal enum GradientStopAttributeType -{ - Main, - Additional, - MainHdr, - AdditionalHdr -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Core/PathElementFactory.cs b/components/Media/src/Geometry/Core/PathElementFactory.cs deleted file mode 100644 index a45890bf..00000000 --- a/components/Media/src/Geometry/Core/PathElementFactory.cs +++ /dev/null @@ -1,154 +0,0 @@ -// 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.Geometry.Elements.Path; - -namespace CommunityToolkit.WinUI.Media.Geometry.Core; - -/// -/// Factory class to instantiate various PathElements. -/// -internal static class PathElementFactory -{ - /// - /// Creates a default Path Element for the given PathFigureType. - /// - /// PathFigureType - /// ICanvasPathElement - internal static ICanvasPathElement CreateDefaultPathElement(PathFigureType figureType) - { - if (figureType == PathFigureType.FillRule) - { - return new FillRuleElement(); - } - - static ICanvasPathElement Throw() => throw new ArgumentException("Creation of Only Default FillRuleElement is supported."); - - return Throw(); - } - - /// - /// Creates a default Path Element for the given PathElementType. - /// - /// PathElementType - /// ICanvasPathElement - internal static ICanvasPathElement CreateDefaultPathElement(PathElementType elementType) - { - if (elementType == PathElementType.ClosePath) - { - return new ClosePathElement(); - } - - static ICanvasPathElement Throw() => throw new ArgumentException("Creation of Only Default ClosePathElement is supported."); - - return Throw(); - } - - /// - /// Instantiates a PathElement based on the PathFigureType. - /// - /// PathFigureType - /// Match object - /// Index of the path element in the Path data - /// ICanvasPathElement - internal static ICanvasPathElement CreatePathFigure(PathFigureType figureType, Match match, int index) - { - var element = CreatePathElement(figureType); - element.Initialize(match, index); - return element; - } - - /// - /// Instantiates a PathElement based on the PathFigureType. - /// - /// PathFigureType - /// Capture object - /// Index of the capture - /// Indicates whether the coordinates are absolute or relative - /// ICanvasPathElement - internal static ICanvasPathElement CreateAdditionalPathFigure(PathFigureType figureType, Capture capture, int index, bool isRelative) - { - var element = CreatePathElement(figureType); - element.InitializeAdditional(capture, index, isRelative); - return element; - } - - /// - /// Instantiates a PathElement based on the PathElementType. - /// - /// PathElementType - /// Match object - /// Index of the path element in the Path data - /// ICanvasPathElement - internal static ICanvasPathElement CreatePathElement(PathElementType elementType, Match match, int index) - { - var element = CreatePathElement(elementType); - element.Initialize(match, index); - return element; - } - - /// - /// Instantiates a PathElement based on the PathElementType. - /// - /// PathElementType - /// Capture object - /// Index of the capture - /// Indicates whether the coordinates are absolute or relative - /// ICanvasPathElement - internal static ICanvasPathElement CreateAdditionalPathElement(PathElementType elementType, Capture capture, int index, bool isRelative) - { - // Additional attributes in MoveTo Command must be converted - // to Line commands - if (elementType == PathElementType.MoveTo) - { - elementType = PathElementType.Line; - } - - var element = CreatePathElement(elementType); - element.InitializeAdditional(capture, index, isRelative); - return element; - } - - /// - /// Instantiates a PathElement based on the PathFigureType. - /// - /// PathFigureType - /// ICanvasPathElement - private static ICanvasPathElement CreatePathElement(PathFigureType figureType) - { - return figureType switch - { - PathFigureType.FillRule => new FillRuleElement(), - PathFigureType.PathFigure => new CanvasPathFigure(), - PathFigureType.EllipseFigure => new CanvasEllipseFigure(), - PathFigureType.PolygonFigure => new CanvasPolygonFigure(), - PathFigureType.RectangleFigure => new CanvasRectangleFigure(), - PathFigureType.RoundedRectangleFigure => new CanvasRoundRectangleFigure(), - _ => throw new ArgumentOutOfRangeException(nameof(figureType), figureType, "Invalid PathFigureType!") - }; - } - - /// - /// Instantiates a PathElement based on the PathElementType. - /// - /// PathElementType - /// ICanvasPathElement - private static ICanvasPathElement CreatePathElement(PathElementType elementType) - { - return elementType switch - { - PathElementType.MoveTo => new MoveToElement(), - PathElementType.Line => new LineElement(), - PathElementType.HorizontalLine => new HorizontalLineElement(), - PathElementType.VerticalLine => new VerticalLineElement(), - PathElementType.QuadraticBezier => new QuadraticBezierElement(), - PathElementType.SmoothQuadraticBezier => new SmoothQuadraticBezierElement(), - PathElementType.CubicBezier => new CubicBezierElement(), - PathElementType.SmoothCubicBezier => new SmoothCubicBezierElement(), - PathElementType.Arc => new ArcElement(), - PathElementType.ClosePath => new ClosePathElement(), - _ => throw new ArgumentOutOfRangeException(nameof(elementType), elementType, "Invalid PathElementType!") - }; - } -} diff --git a/components/Media/src/Geometry/Core/RegexFactory.cs b/components/Media/src/Geometry/Core/RegexFactory.cs deleted file mode 100644 index 3bbff7f2..00000000 --- a/components/Media/src/Geometry/Core/RegexFactory.cs +++ /dev/null @@ -1,599 +0,0 @@ -// 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; - -[assembly: InternalsVisibleTo("UnitTests.UWP")] - -namespace CommunityToolkit.WinUI.Media.Geometry.Core; - -/// -/// Contains all the Regular Expressions which are used for parsing the Win2d Path Mini Language. -/// -internal static class RegexFactory -{ - // Whitespace - private const string Spacer = @"\s*"; - - // Whitespace or comma - private const string SpaceOrComma = @"(?:\s+|\s*,\s*)"; - - // Whitespace or comma or a minus/plus sign (look ahead) - private const string Sep = @"(?:\s+|\s*,\s*|(?=[-+.]))"; - - // Whitespace or comma or a '#' sign (look ahead) - private const string ColorSep = @"(?:\s+|\s*,\s*|(?=[#]))"; - - // Positive Integer - private const string Integer = @"[+-]?[0-9]+"; - - // Positive Integer - private const string PositiveInteger = @"[+]?[0-9]+"; - - // Floating point number - private const string Float = @"(?:[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; - - // Positive Floating point number - private const string PositiveFloat = @"(?:[+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)"; - - // Floating point number between 0 and 1, inclusive - // private const string Float01 = @"(?:(?[Mm]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; - - // Line - private static readonly string Line = $"(?[Ll]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; - - // Horizontal Line - private static readonly string HorizontalLine = $"(?[Hh]{Spacer}{Float}(?:{Sep}{Float})*{Spacer})"; - - // Vertical Line - private static readonly string VerticalLine = $"(?[Vv]{Spacer}{Float}(?:{Sep}{Float})*{Spacer})"; - - // Quadratic Bezier - private static readonly string QuadraticBezier = $"(?[Qq]{Spacer}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos})*{Spacer})"; - - // Smooth Quadratic Bezier - private static readonly string SmoothQuadraticBezier = $"(?[Tt]{Spacer}{Pos}(?:{Sep}{Pos})*{Spacer})"; - - // Cubic Bezier - private static readonly string CubicBezier = $"(?[Cc]{Spacer}{Pos}{Sep}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos}{Sep}{Pos})*{Spacer})"; - - // Smooth Cubic Bezier - private static readonly string SmoothCubicBezier = $"(?[Ss]{Spacer}{Pos}{Sep}{Pos}(?:{Sep}{Pos}{Sep}{Pos})*{Spacer})"; - - // Arc - private static readonly string Arc = $"(?[Aa]{Spacer}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos}" + - $"(?:{Sep}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos})*{Spacer})"; - - // Close Path - private static readonly string ClosePath = $"(?[Zz]{Spacer})"; - - // CanvasPathFigure - private static readonly string CanvasPathFigureRegexString = - $"{MoveTo}" + // M x,y - "(" + - $"{Line}+|" + // L x,y - $"{HorizontalLine}+|" + // H x - $"{VerticalLine}+|" + // V y - $"{QuadraticBezier}+|" + // Q x1,y1 x,y - $"{SmoothQuadraticBezier}+|" + // T x,y - $"{CubicBezier}+|" + // C x1,y1 x2,y2 x,y - $"{SmoothCubicBezier}+|" + // S x2,y2 x,y - $"{Arc}+|" + // A radX, radY, angle, isLargeArc, sweepDirection, x, y - ")+" + - $"{ClosePath}?"; // Close Path (Optional) - - // Fill Rule - private static readonly string FillRule = $"{Spacer}(?[Ff]{Spacer}[01])"; - - // PathFigure - private static readonly string PathFigure = $"{Spacer}(?{CanvasPathFigureRegexString})"; - - // Ellipse Figure - private static readonly string EllipseFigure = $"{Spacer}(?[Oo]{Spacer}{Float}{Sep}{Float}{Sep}{Pos}" + - $"(?:{Sep}{Float}{Sep}{Float}{Sep}{Pos})*)"; - - // Polygon Figure - private static readonly string PolygonFigure = $"{Spacer}(?[Pp]{Spacer}{Integer}{Sep}{Float}{Sep}{Pos}" + - $"(?:{Sep}{Integer}{Sep}{Float}{Sep}{Pos})*)"; - - // Rectangle Figure - private static readonly string RectangleFigure = $"{Spacer}(?[Rr]{Spacer}{Pos}{Sep}{Float}{Sep}{Float}" + - $"(?:{Sep}{Pos}{Sep}{Float}{Sep}{Float})*)"; - - // Rounded Rectangle Figure - private static readonly string RoundedRectangleFigure = $"{Spacer}(?[Uu]{Spacer}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float}" + - $"(?:{Sep}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float})*)"; - - // CanvasGeometry - private static readonly string CanvasGeometryRegexString = - $"{FillRule}?" + // F0 or F1 - "(" + - $"{PathFigure}+|" + // Path Figure - $"{EllipseFigure}+|" + // O radX, radY, centerX, centerY - $"{PolygonFigure}+|" + // P numSides, radius, centerX, centerY - $"{RectangleFigure}+|" + // R x, y, width, height - $"{RoundedRectangleFigure}+" + // U x, y, width, height, radiusX, radiusY - ")+"; - - // MoveTo - private static readonly string MoveToAttributes = $"(?{Float}){Sep}(?{Float})"; - private static readonly string MoveToRegexString = $"{Spacer}(?
(?[Mm]){Spacer}{MoveToAttributes})" + - $"(?{Sep}{Pos})*"; - - // Line - private static readonly string LineAttributes = $"(?{Float}){Sep}(?{Float})"; - private static readonly string LineRegexString = $"{Spacer}(?
(?[Ll]){Spacer}{LineAttributes})" + - $"(?{Sep}{Pos})*"; - - // Horizontal Line - private static readonly string HorizontalLineAttributes = $"(?{Float})"; - private static readonly string HorizontalLineRegexString = $"{Spacer}(?
(?[Hh]){Spacer}{HorizontalLineAttributes})" + - $"(?{Sep}{Float})*"; - - // Vertical Line - private static readonly string VerticalLineAttributes = $"(?{Float})"; - private static readonly string VerticalLineRegexString = $"{Spacer}(?
(?[Vv]){Spacer}{VerticalLineAttributes})" + - $"(?{Sep}{Float})*"; - - // Quadratic Bezier - private static readonly string QuadraticBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; - private static readonly string QuadraticBezierRegexString = $"{Spacer}(?
(?[Qq]){Spacer}{QuadraticBezierAttributes})" + - $"(?{Sep}{Pos}{Sep}{Pos})*"; - - // Smooth Quadratic Bezier - private static readonly string SmoothQuadraticBezierAttributes = $"(?{Float}){Sep}(?{Float})"; - private static readonly string SmoothQuadraticBezierRegexString = $"{Spacer}(?
(?[Tt]){Spacer}{SmoothQuadraticBezierAttributes})" + - $"(?{Sep}{Pos})*"; - - // Cubic Bezier - private static readonly string CubicBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}" + - $"(?{Float}){Sep}(?{Float})"; - - private static readonly string CubicBezierRegexString = $"{Spacer}(?
(?[Cc]){Spacer}{CubicBezierAttributes})" + - $"(?{Sep}{Pos}{Sep}{Pos}{Sep}{Pos})*"; - - // Smooth Cubic Bezier - private static readonly string SmoothCubicBezierAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; - private static readonly string SmoothCubicBezierRegexString = $"{Spacer}(?
(?[Ss]){Spacer}{SmoothCubicBezierAttributes})" + - $"(?{Sep}{Pos}{Sep}{Pos})*"; - - // Arc - private static readonly string ArcAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){SpaceOrComma}" + - $"(?[01]){SpaceOrComma}(?[01]){Sep}(?{Float}){Sep}(?{Float})"; - - private static readonly string ArcRegexString = $"{Spacer}(?
(?[Aa]){Spacer}{ArcAttributes})" + - $"(?{Sep}{Float}{Sep}{Float}{Sep}{Float}{SpaceOrComma}[01]{SpaceOrComma}[01]{Sep}{Pos})*"; - - // Close Path - private static readonly string ClosePathRegexString = $"{Spacer}(?
(?[Zz])){Spacer}"; - - // Fill Rule - private static readonly string FillRuleRegexString = $"{Spacer}(?
(?[Ff]){Spacer}(?[01]))"; - - // Path Figure - private static readonly string PathFigureRegexString = $"{Spacer}(?
{PathFigure})"; - - // Ellipse Figure - private static readonly string EllipseFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}" + - $"(?{Float}){Sep}(?{Float})"; - - private static readonly string EllipseFigureRegexString = $"{Spacer}(?
(?[Oo]){Spacer}{EllipseFigureAttributes})" + - $"(?{Sep}{Float}{Sep}{Float}{Sep}{Pos})*"; - - // Polygon Figure - private static readonly string PolygonFigureAttributes = $"(?{Integer}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; - private static readonly string PolygonFigureRegexString = $"{Spacer}(?
(?[Pp]){Spacer}{PolygonFigureAttributes})" + - $"(?{Sep}{Integer}{Sep}{Float}{Sep}{Pos})*"; - - // Rectangle Figure - private static readonly string RectangleFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})"; - private static readonly string RectangleFigureRegexString = $"{Spacer}(?
(?[Rr]){Spacer}{RectangleFigureAttributes})" + - $"(?{Sep}{Pos}{Sep}{Float}{Sep}{Float})*"; - - // Rectangle Figure - private static readonly string RoundedRectangleFigureAttributes = $"(?{Float}){Sep}(?{Float}){Sep}(?{Float}){Sep}(?{Float})" + - $"{Sep}(?{Float}){Sep}(?{Float})"; - - private static readonly string RoundedRectangleFigureRegexString = $"{Spacer}(?
(?[Uu]){Spacer}{RoundedRectangleFigureAttributes})" + - $"(?{Sep}{Pos}{Sep}{Float}{Sep}{Float}{Sep}{Float}{Sep}{Float})*"; - - // ARGB Color - private static readonly string HexColor = $"(?:#?(?:{Hex}{{2}})?{Hex}{{6}})"; - - // Alpha - private static readonly string Alpha = $"(?{Hex}{{2}})"; - - // Red - private static readonly string Red = $"(?{Hex}{{2}})"; - - // Green - private static readonly string Green = $"(?{Hex}{{2}})"; - - // Blue - private static readonly string Blue = $"(?{Hex}{{2}})"; - - // Hexadecimal Color - private static readonly string RgbColor = $"(?{HexColor})"; - - // HDR Color (Vector4 in which each component has a value between 0 and 1, inclusive) - private static readonly string HdrColor = $"(?{Float01}{Sep}{Float01}{Sep}{Float01}{Sep}{Float01})"; - - // Hexadecimal Color Attributes - private static readonly string RgbColorAttributes = $"(?#{{0,1}}{Alpha}{{0,1}}{Red}{Green}{Blue})"; - - // HDR Color Attributes (Vector4 in which each component has a value between 0 and 1, inclusive) - private static readonly string HdrColorAttributes = $"(?(?{Float01}){Sep}(?{Float01}){Sep}(?{Float01}){Sep}(?{Float01}))"; - - private static readonly string ColorRegexString = $"(?:{RgbColorAttributes}|{HdrColorAttributes})"; - - // Start Point - private static readonly string StartPoint = $"(?[Mm]{Spacer}{Pos}{Spacer})"; - - // End Point - private static readonly string EndPoint = $"(?[Zz]{Spacer}{Pos}{Spacer})"; - - // Opacity - private static readonly string Opacity = $"(?[Oo]{Spacer}{Float01}{Spacer})"; - - // Alpha Mode - private static readonly string AlphaMode = $"(?[Aa]{Spacer}[012]{Spacer})"; - - // Buffer Precision - private static readonly string BufferPrecision = $"(?[Bb]{Spacer}[01234]{Spacer})"; - - // Edge Behavior - private static readonly string EdgeBehavior = $"(?[Ee]{Spacer}[012]{Spacer})"; - - // PreInterpolation Color Space - private static readonly string PreColorSpace = $"(?[Pp]{Spacer}[012]{Spacer})"; - - // PostInterpolation Color Space - private static readonly string PostColorSpace = $"(?[Rr]{Spacer}[012]{Spacer})"; - - // Radius in X-axis - private static readonly string RadiusX = $"(?{Float})"; - - // Radius in Y-axis - private static readonly string RadiusY = $"(?{Float})"; - - // Center location on X-axis - private static readonly string CenterX = $"(?{Float})"; - - // Center location on Y-axis - private static readonly string CenterY = $"(?{Float})"; - - // Origin Offset - private static readonly string OriginOffset = $"(?[Ff]{Spacer}{Pos}{Spacer})"; - - // GradientStops - private static readonly string GradientStops = $"(?[Ss]{Spacer}{Float01}{ColorSep}{HexColor}(?:{Sep}{Float01}{ColorSep}{HexColor})*{Spacer})"; - - // GradientStopHdrs - private static readonly string GradientStopHdrs = $"(?[Ss]{Spacer}{Float01}{Sep}{HdrColor}(?:{Sep}{Float01}{Sep}{HdrColor})*{Spacer})"; - - // Solid Color Brush - private static readonly string SolidColorBrush = $"(?[Ss][Cc]{Spacer}(?:{RgbColor}|{HdrColor}){Spacer}{Opacity}?)"; - - // LinearGradient - private static readonly string LinearGradient = $"(?[Ll][Gg]{Spacer}{StartPoint}{EndPoint}" + - $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{PreColorSpace}?{PostColorSpace}?" + - $"{GradientStops}+{Spacer})"; - - // RadialGradient - private static readonly string RadialGradient = $"(?[Rr][Gg]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + - $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{OriginOffset}?{PreColorSpace}?{PostColorSpace}?" + - $"{GradientStops}+{Spacer})"; - - // LinearGradientHdr - private static readonly string LinearGradientHdr = $"(?[Ll][Hh]{Spacer}{StartPoint}{EndPoint}" + - $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{PreColorSpace}?{PostColorSpace}?" + - $"{GradientStopHdrs}+{Spacer})"; - - // RadialGradientHdr - private static readonly string RadialGradientHdr = $"(?[Rr][Hh]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + - $"{Opacity}?{AlphaMode}?{BufferPrecision}?{EdgeBehavior}?{OriginOffset}?{PreColorSpace}?{PostColorSpace}?" + - $"{GradientStopHdrs}+{Spacer})"; - - // Regex for the CanvasBrush - private static readonly string CanvasBrushRegexString = $"(?{SolidColorBrush}|{LinearGradient}|{RadialGradient}|{LinearGradientHdr}|{RadialGradientHdr})"; - - // Start Point - private static readonly string StartPointAttr = $"(?:[Mm]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; - - // End Point - private static readonly string EndPointAttr = $"(?:[Zz]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; - - // Opacity - private static readonly string OpacityAttr = $"(?:[Oo]{Spacer}(?{Float01}){Spacer})"; - - // Alpha Mode - private static readonly string AlphaModeAttr = $"(?:[Aa]{Spacer}(?[012]){Spacer})"; - - // Buffer Precision - private static readonly string BufferPrecisionAttr = $"(?:[Bb]{Spacer}(?[01234]){Spacer})"; - - // Edge Behavior - private static readonly string EdgeBehaviorAttr = $"(?:[Ee]{Spacer}(?[012]){Spacer})"; - - // PreInterpolation Color Space - private static readonly string PreColorSpaceAttr = $"(?:[Pp]{Spacer}(?[012]){Spacer})"; - - // PostInterpolation Color Space - private static readonly string PostColorSpaceAttr = $"(?:[Rr]{Spacer}(?[012]){Spacer})"; - - // Origin Offset - private static readonly string OriginOffsetAttr = $"(?[Ff]{Spacer}(?{Float}){Sep}(?{Float}){Spacer})"; - - // GradientStop Attributes - private static readonly string GradientStopAttributes = $"(?{Float01}){ColorSep}{RgbColorAttributes}"; - private static readonly string GradientStopMainAttributes = $"(?
[Ss]{Spacer}{GradientStopAttributes})"; - private static readonly string GradientStopRegexString = $"(?{GradientStopMainAttributes}" + $"(?{Sep}{Float01}{ColorSep}{HexColor})*{Spacer})"; - - // GradientStopHdr Attributes - private static readonly string GradientStopHdrAttributes = $"(?{Float01}){Sep}{HdrColorAttributes}"; - private static readonly string GradientStopHdrMainAttributes = $"(?
(?[Ss]){Spacer}{GradientStopHdrAttributes})"; - private static readonly string GradientStopHdrRegexString = $"(?{GradientStopHdrMainAttributes}" + $"(?{Sep}{Float01}{Sep}{HdrColor})*{Spacer})"; - - // Regex for SolidColorBrush Attributes - private static readonly string SolidColorBrushRegexString = $"(?:[Ss][Cc]{Spacer}(?:{RgbColorAttributes}|{HdrColorAttributes}){Spacer}{OpacityAttr}?)"; - - // Regex for LinearGradient Attributes - private static readonly string LinearGradientRegexString = $"[Ll][Gg]{Spacer}{StartPointAttr}{EndPointAttr}" + - $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?" + - $"{EdgeBehaviorAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + - $"{GradientStops}+{Spacer}"; - - // Regex for RadialGradient Attributes - private static readonly string RadialGradientRegexString = $"[Rr][Gg]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + - $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?{EdgeBehaviorAttr}?" + - $"{OriginOffsetAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + - $"{GradientStops}+{Spacer}"; - - // Regex for LinearGradientHdr Attributes - private static readonly string LinearGradientHdrRegexString = $"[Ll][Hh]{Spacer}{StartPointAttr}{EndPointAttr}" + - $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?" + - $"{EdgeBehaviorAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + - $"{GradientStopHdrs}+{Spacer}"; - - // Regex for RadialGradientHdr Attributes - private static readonly string RadialGradientHdrRegexString = $"[Rr][Hh]{Spacer}{RadiusX}{Sep}{RadiusY}{Sep}{CenterX}{Sep}{CenterY}{Spacer}" + - $"{OpacityAttr}?{AlphaModeAttr}?{BufferPrecisionAttr}?{EdgeBehaviorAttr}?" + - $"{OriginOffsetAttr}?{PreColorSpaceAttr}?{PostColorSpaceAttr}?" + - $"{GradientStopHdrs}+{Spacer}"; - - // CanvasStrokeStyle attributes - private static readonly string DashStyle = $"(?:[Dd][Ss]{Spacer}(?[01234]){Spacer})"; - private static readonly string LineJoin = $"(?:[Ll][Jj]{Spacer}(?[0123]){Spacer})"; - private static readonly string MiterLimit = $"(?:[Mm][Ll]{Spacer}(?{Float}){Spacer})"; - private static readonly string DashOffset = $"(?:[Dd][Oo]{Spacer}(?{Float}){Spacer})"; - private static readonly string StartCap = $"(?:[Ss][Cc]{Spacer}(?[0123]){Spacer})"; - private static readonly string EndCap = $"(?:[Ee][Cc]{Spacer}(?[0123]){Spacer})"; - private static readonly string DashCap = $"(?:[Dd][Cc]{Spacer}(?[0123]){Spacer})"; - private static readonly string TransformBehavior = $"(?:[Tt][Bb]{Spacer}(?[012]){Spacer})"; - private static readonly string CustomDashAttribute = $"(?{Float}){Sep}(?{Float})"; - private static readonly string CustomDashStyle = $"(?[Cc][Dd][Ss]{Spacer}(?
{CustomDashAttribute})" + $"(?{Sep}{Float}{Sep}{Float})*{Spacer})"; - - // CanvasStrokeStyle Regex - private static readonly string CanvasStrokeStyleRegexString = $"(?[Cc][Ss][Ss]{Spacer}{DashStyle}?{LineJoin}?{MiterLimit}?{DashOffset}?" + - $"{StartCap}?{EndCap}?{DashCap}?{TransformBehavior}?{CustomDashStyle}?)"; - - // CanvasStroke Regex - private static readonly string CanvasStrokeRegexString = $"(?[Ss][Tt]{Spacer}" + - $"(?{Float}){Spacer}" + - $"{CanvasBrushRegexString}{Spacer}" + - $"{CanvasStrokeStyleRegexString}?)"; - - private static readonly Dictionary PathFigureRegexes; - private static readonly Dictionary PathFigureAttributeRegexes; - private static readonly Dictionary PathElementRegexes; - private static readonly Dictionary PathElementAttributeRegexes; - private static readonly Dictionary BrushRegexes; - private static readonly Dictionary GradientStopAttributeRegexes; - - /// - /// Gets the Regex to perform validation of Path data. - /// - public static Regex ValidationRegex { get; } - - /// - /// Gets the Regex for parsing the CanvasGeometry string. - /// - public static Regex CanvasGeometryRegex { get; } - - /// - /// Gets the Regex for parsing Hexadecimal Color string. - /// - public static Regex ColorRegex { get; } - - /// - /// Gets the Regex for parsing the ICanvasBrush string. - /// - public static Regex CanvasBrushRegex { get; } - - /// - /// Gets the Regex for parsing the GradientStop string. - /// - public static Regex GradientStopRegex { get; } - - /// - /// Gets the Regex for parsing the GradientStopHdr string. - /// - public static Regex GradientStopHdrRegex { get; } - - /// - /// Gets the Regex for parsing the CanvasStrokeStyle string. - /// - public static Regex CanvasStrokeStyleRegex { get; } - - /// - /// Gets the Regex for parsing the CanvasStroke string. - /// - public static Regex CanvasStrokeRegex { get; } - - /// - /// Gets the Regex for parsing the CustomDashStyle attributes. - /// - public static Regex CustomDashAttributeRegex { get; } - - /// - /// Initializes static members of the class. - /// - static RegexFactory() - { - PathFigureRegexes = new Dictionary - { - [PathFigureType.FillRule] = new Regex(FillRuleRegexString, RegexOptions.Compiled), - [PathFigureType.PathFigure] = new Regex(PathFigureRegexString, RegexOptions.Compiled), - [PathFigureType.EllipseFigure] = new Regex(EllipseFigureRegexString, RegexOptions.Compiled), - [PathFigureType.PolygonFigure] = new Regex(PolygonFigureRegexString, RegexOptions.Compiled), - [PathFigureType.RectangleFigure] = new Regex(RectangleFigureRegexString, RegexOptions.Compiled), - [PathFigureType.RoundedRectangleFigure] = new Regex(RoundedRectangleFigureRegexString, RegexOptions.Compiled) - }; - - PathFigureAttributeRegexes = new Dictionary - { - // Not Applicable for FillRuleElement - [PathFigureType.FillRule] = null, - - // Not Applicable for CanvasPathFigure - [PathFigureType.PathFigure] = null, - [PathFigureType.EllipseFigure] = new Regex($"{Sep}{EllipseFigureAttributes}", RegexOptions.Compiled), - [PathFigureType.PolygonFigure] = new Regex($"{Sep}{PolygonFigureAttributes}", RegexOptions.Compiled), - [PathFigureType.RectangleFigure] = new Regex($"{Sep}{RectangleFigureAttributes}", RegexOptions.Compiled), - [PathFigureType.RoundedRectangleFigure] = new Regex($"{Sep}{RoundedRectangleFigureAttributes}", RegexOptions.Compiled) - }; - - PathElementRegexes = new Dictionary - { - [PathElementType.MoveTo] = new Regex(MoveToRegexString, RegexOptions.Compiled), - [PathElementType.Line] = new Regex(LineRegexString, RegexOptions.Compiled), - [PathElementType.HorizontalLine] = new Regex(HorizontalLineRegexString, RegexOptions.Compiled), - [PathElementType.VerticalLine] = new Regex(VerticalLineRegexString, RegexOptions.Compiled), - [PathElementType.QuadraticBezier] = new Regex(QuadraticBezierRegexString, RegexOptions.Compiled), - [PathElementType.SmoothQuadraticBezier] = new Regex(SmoothQuadraticBezierRegexString, RegexOptions.Compiled), - [PathElementType.CubicBezier] = new Regex(CubicBezierRegexString, RegexOptions.Compiled), - [PathElementType.SmoothCubicBezier] = new Regex(SmoothCubicBezierRegexString, RegexOptions.Compiled), - [PathElementType.Arc] = new Regex(ArcRegexString, RegexOptions.Compiled), - [PathElementType.ClosePath] = new Regex(ClosePathRegexString, RegexOptions.Compiled) - }; - - PathElementAttributeRegexes = new Dictionary - { - [PathElementType.MoveTo] = new Regex($"{Sep}{MoveToAttributes}", RegexOptions.Compiled), - [PathElementType.Line] = new Regex($"{Sep}{LineAttributes}", RegexOptions.Compiled), - [PathElementType.HorizontalLine] = new Regex($"{Sep}{HorizontalLineAttributes}", RegexOptions.Compiled), - [PathElementType.VerticalLine] = new Regex($"{Sep}{VerticalLineAttributes}", RegexOptions.Compiled), - [PathElementType.QuadraticBezier] = new Regex($"{Sep}{QuadraticBezierAttributes}", RegexOptions.Compiled), - [PathElementType.SmoothQuadraticBezier] = new Regex($"{Sep}{SmoothQuadraticBezierAttributes}", RegexOptions.Compiled), - [PathElementType.CubicBezier] = new Regex($"{Sep}{CubicBezierAttributes}", RegexOptions.Compiled), - [PathElementType.SmoothCubicBezier] = new Regex($"{Sep}{SmoothCubicBezierAttributes}", RegexOptions.Compiled), - [PathElementType.Arc] = new Regex($"{Sep}{ArcAttributes}", RegexOptions.Compiled), - - // Not Applicable for ClosePathElement as it has no attributes - [PathElementType.ClosePath] = null - }; - - BrushRegexes = new Dictionary - { - [BrushType.SolidColor] = new Regex(SolidColorBrushRegexString, RegexOptions.Compiled), - [BrushType.LinearGradient] = new Regex(LinearGradientRegexString, RegexOptions.Compiled), - [BrushType.LinearGradientHdr] = new Regex(LinearGradientHdrRegexString, RegexOptions.Compiled), - [BrushType.RadialGradient] = new Regex(RadialGradientRegexString, RegexOptions.Compiled), - [BrushType.RadialGradientHdr] = new Regex(RadialGradientHdrRegexString, RegexOptions.Compiled) - }; - - GradientStopAttributeRegexes = new Dictionary - { - [GradientStopAttributeType.Main] = new Regex(GradientStopMainAttributes, RegexOptions.Compiled), - [GradientStopAttributeType.Additional] = new Regex($"{Sep}{GradientStopAttributes}", RegexOptions.Compiled), - [GradientStopAttributeType.MainHdr] = new Regex(GradientStopHdrMainAttributes, RegexOptions.Compiled), - [GradientStopAttributeType.AdditionalHdr] = new Regex($"{Sep}{GradientStopHdrAttributes}", RegexOptions.Compiled) - }; - - ValidationRegex = new Regex(@"\s+"); - CanvasGeometryRegex = new Regex(CanvasGeometryRegexString, RegexOptions.Compiled); - ColorRegex = new Regex(ColorRegexString, RegexOptions.Compiled); - CanvasBrushRegex = new Regex(CanvasBrushRegexString, RegexOptions.Compiled); - GradientStopRegex = new Regex(GradientStopRegexString, RegexOptions.Compiled); - GradientStopHdrRegex = new Regex(GradientStopHdrRegexString, RegexOptions.Compiled); - CanvasStrokeStyleRegex = new Regex(CanvasStrokeStyleRegexString, RegexOptions.Compiled); - CanvasStrokeRegex = new Regex(CanvasStrokeRegexString, RegexOptions.Compiled); - CustomDashAttributeRegex = new Regex($"{Sep}{CustomDashAttribute}", RegexOptions.Compiled); - } - - /// - /// Get the Regex for the given PathFigureType - /// - /// PathFigureType - /// Regex - internal static Regex? GetRegex(PathFigureType figureType) - { - return PathFigureRegexes[figureType]; - } - - /// - /// Get the Regex for the given PathElementType - /// - /// PathElementType - /// Regex - internal static Regex? GetRegex(PathElementType elementType) - { - return PathElementRegexes[elementType]; - } - - /// - /// Get the Regex for extracting attributes of the given PathFigureType - /// - /// PathFigureType - /// Regex - internal static Regex? GetAttributesRegex(PathFigureType figureType) - { - return PathFigureAttributeRegexes[figureType]; - } - - /// - /// Get the Regex for extracting attributes of the given PathElementType - /// - /// PathElementType - /// Regex - internal static Regex? GetAttributesRegex(PathElementType elementType) - { - return PathElementAttributeRegexes[elementType]; - } - - /// - /// Gets the Regex for extracting the attributes of the given BrushType - /// - /// BrushType - /// Regex - internal static Regex? GetAttributesRegex(BrushType brushType) - { - return BrushRegexes[brushType]; - } - - /// - /// Gets the Regex for extracting the attributes of the given - /// GradientStopAttributeType - /// - /// GradientStopAttributeType - /// Regex - internal static Regex? GetAttributesRegex(GradientStopAttributeType gsAttrType) - { - return GradientStopAttributeRegexes[gsAttrType]; - } -} diff --git a/components/Media/src/Geometry/CultureShield.cs b/components/Media/src/Geometry/CultureShield.cs deleted file mode 100644 index b8348cf0..00000000 --- a/components/Media/src/Geometry/CultureShield.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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.Globalization; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Class which can be used to encapsulate code statement(s) so that they are executed in a specific culture. -/// -/// Usage example: -/// -/// The following code block will be executed using the French culture. -/// -/// using (new CultureShield("fr-FR")) -/// -/// { -/// -/// ... -/// -/// } -/// -internal readonly ref struct CultureShield -{ - private readonly CultureInfo _prevCulture; - - /// - /// Initializes a new instance of the struct so that the encapsulated code statement(s) can be executed using the specified culture. - /// - /// Usage example: - /// - /// The following code block will be executed using the French culture. - /// - /// using (new CultureShield("fr-FR")) - /// - /// { - /// - /// ... - /// - /// } - /// - /// The culture in which the encapsulated code statement(s) are to be executed. - internal CultureShield(string culture) - { - _prevCulture = CultureInfo.CurrentCulture; - CultureInfo.CurrentCulture = new CultureInfo(culture); - } - - /// - /// Disposes the CultureShield object. - /// - public void Dispose() - { - CultureInfo.CurrentCulture = _prevCulture; - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs deleted file mode 100644 index 4e3ca6ce..00000000 --- a/components/Media/src/Geometry/Elements/Brush/AbstractCanvasBrushElement.cs +++ /dev/null @@ -1,70 +0,0 @@ -// 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; -using Microsoft.Graphics.Canvas.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Abstract base class for all Brush Elements -/// -internal abstract class AbstractCanvasBrushElement : ICanvasBrushElement -{ -#pragma warning disable SA1401 // Fields should be private - protected float _opacity; -#pragma warning restore SA1401 // Fields should be private - - /// - /// Gets or sets the Brush data defining the Brush Element - /// - public string? Data { get; protected set; } - - /// - /// Gets or sets the number of non-whitespace characters in - /// the Brush Data - /// - public int ValidationCount { get; protected set; } - - /// - /// Initializes the Brush Element with the given Capture - /// - /// Capture object - public virtual void Initialize(Capture capture) - { - Data = capture.Value; - - var regex = GetAttributesRegex(); - var match = regex?.Match(Data); - if (match is null || !match.Success) - { - return; - } - - GetAttributes(match); - - // Get the number of non-whitespace characters in the data - ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; - } - - /// - /// Creates the ICanvasBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// ICanvasBrush - public abstract ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator); - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected abstract Regex? GetAttributesRegex(); - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected abstract void GetAttributes(Match match); -} diff --git a/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs deleted file mode 100644 index 7a29d1a2..00000000 --- a/components/Media/src/Geometry/Elements/Brush/ICanvasBrushElement.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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; -using Microsoft.Graphics.Canvas.Brushes; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Interface for a Brush Element -/// -internal interface ICanvasBrushElement -{ - /// - /// Gets the Brush data defining the Brush Element - /// - string? Data { get; } - - /// - /// Gets the number of non-whitespace characters in - /// the Brush Data - /// - int ValidationCount { get; } - - /// - /// Initializes the Brush Element with the given Capture - /// - /// Capture object - void Initialize(Capture capture); - - /// - /// Creates the ICanvasBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// ICanvasBrush - ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator); -} diff --git a/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs deleted file mode 100644 index 471c0425..00000000 --- a/components/Media/src/Geometry/Elements/Brush/LinearGradientBrushElement.cs +++ /dev/null @@ -1,210 +0,0 @@ -// 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; -using System.Numerics; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Parsers; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Represents a CanvasLinearGradientBrush with GradientStops -/// -internal sealed class LinearGradientBrushElement : AbstractCanvasBrushElement -{ - private Vector2 _startPoint; - private Vector2 _endPoint; - private CanvasAlphaMode _alphaMode; - private CanvasBufferPrecision _bufferPrecision; - private CanvasEdgeBehavior _edgeBehavior; - private CanvasColorSpace _preInterpolationColorSpace; - private CanvasColorSpace _postInterpolationColorSpace; - private List _gradientStops; - - /// - /// Initializes a new instance of the class. - /// - /// Capture object - public LinearGradientBrushElement(Capture capture) - { - // Set the default values - _startPoint = Vector2.Zero; - _endPoint = Vector2.Zero; - _opacity = 1f; - _alphaMode = (CanvasAlphaMode)0; - _bufferPrecision = (CanvasBufferPrecision)0; - _edgeBehavior = (CanvasEdgeBehavior)0; - - // Default ColorSpace is sRGB - _preInterpolationColorSpace = CanvasColorSpace.Srgb; - _postInterpolationColorSpace = CanvasColorSpace.Srgb; - _gradientStops = new List(); - - // Initialize - Initialize(capture); - } - - /// - /// Creates the CanvasLinearGradientBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// An instance of - public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) - { - var brush = new CanvasLinearGradientBrush( - resourceCreator, - _gradientStops.ToArray(), - _edgeBehavior, - _alphaMode, - _preInterpolationColorSpace, - _postInterpolationColorSpace, - _bufferPrecision) - { - StartPoint = _startPoint, - EndPoint = _endPoint, - Opacity = _opacity - }; - - return brush; - } - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(BrushType.LinearGradient); - } - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Start Point - float.TryParse(match.Groups["StartX"].Value, out var startX); - float.TryParse(match.Groups["StartY"].Value, out var startY); - _startPoint = new Vector2(startX, startY); - - // End Point - float.TryParse(match.Groups["EndX"].Value, out var endX); - float.TryParse(match.Groups["EndY"].Value, out var endY); - _endPoint = new Vector2(endX, endY); - - // Opacity (optional) - var group = match.Groups["Opacity"]; - if (group.Success) - { - float.TryParse(group.Value, out _opacity); - } - - // Alpha Mode (optional) - group = match.Groups["AlphaMode"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _alphaMode); - } - - // Buffer Precision (optional) - group = match.Groups["BufferPrecision"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _bufferPrecision); - } - - // Edge Behavior (optional) - group = match.Groups["EdgeBehavior"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _edgeBehavior); - } - - // Pre Interpolation ColorSpace (optional) - group = match.Groups["PreColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _preInterpolationColorSpace); - } - - // Post Interpolation ColorSpace (optional) - group = match.Groups["PostColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _postInterpolationColorSpace); - } - - // GradientStops - group = match.Groups["GradientStops"]; - if (group.Success) - { - _gradientStops.Clear(); - foreach (Capture capture in group.Captures) - { - var gradientMatch = RegexFactory.GradientStopRegex.Match(capture.Value); - if (!gradientMatch.Success) - { - continue; - } - - float position; - Color color; - - // Main Attributes - var main = gradientMatch.Groups["Main"]; - if (main.Success) - { - var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Main); - default(ArgumentNullException).ThrowIfNull(regex); - - var mainMatch = regex.Match(main.Value); - default(ArgumentNullException).ThrowIfNull(regex); - float.TryParse(mainMatch.Groups["Position"].Value, out position); - color = ColorParser.Parse(mainMatch); - - _gradientStops.Add(new CanvasGradientStop() - { - Color = color, - Position = position - }); - } - - // Additional Attributes - var additional = gradientMatch.Groups["Additional"]; - if (!additional.Success) - { - continue; - } - - foreach (Capture addCapture in additional.Captures) - { - var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Additional); - default(ArgumentNullException).ThrowIfNull(regex); - - var addMatch = regex.Match(addCapture.Value); - float.TryParse(addMatch.Groups["Position"].Value, out position); - - color = ColorParser.Parse(addMatch); - - _gradientStops.Add(new CanvasGradientStop() - { - Color = color, - Position = position - }); - } - } - - // Sort the stops based on their position - if (_gradientStops.Any()) - { - _gradientStops = _gradientStops.OrderBy(g => g.Position).ToList(); - } - } - } -} diff --git a/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs deleted file mode 100644 index e1680afe..00000000 --- a/components/Media/src/Geometry/Elements/Brush/LinearGradientHdrBrushElement.cs +++ /dev/null @@ -1,207 +0,0 @@ -// 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.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Represents a CanvasLinearGradientBrush with GradientStopHdrs -/// -internal sealed class LinearGradientHdrBrushElement : AbstractCanvasBrushElement -{ - private Vector2 _startPoint; - private Vector2 _endPoint; - private CanvasAlphaMode _alphaMode; - private CanvasBufferPrecision _bufferPrecision; - private CanvasEdgeBehavior _edgeBehavior; - private CanvasColorSpace _preInterpolationColorSpace; - private CanvasColorSpace _postInterpolationColorSpace; - private List _gradientStopHdrs; - - /// - /// Initializes a new instance of the class. - /// - /// Capture object - public LinearGradientHdrBrushElement(Capture capture) - { - // Set the default values - _startPoint = Vector2.Zero; - _endPoint = Vector2.Zero; - _opacity = 1f; - _alphaMode = (CanvasAlphaMode)0; - _bufferPrecision = (CanvasBufferPrecision)0; - _edgeBehavior = (CanvasEdgeBehavior)0; - - // Default ColorSpace is sRGB - _preInterpolationColorSpace = CanvasColorSpace.Srgb; - _postInterpolationColorSpace = CanvasColorSpace.Srgb; - _gradientStopHdrs = new List(); - - // Initialize - Initialize(capture); - } - - /// - /// Creates the CanvasLinearGradientBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// Instance of - public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) - { - var brush = CanvasLinearGradientBrush.CreateHdr( - resourceCreator, - _gradientStopHdrs.ToArray(), - _edgeBehavior, - _alphaMode, - _preInterpolationColorSpace, - _postInterpolationColorSpace, - _bufferPrecision); - - brush.StartPoint = _startPoint; - brush.EndPoint = _endPoint; - brush.Opacity = _opacity; - - return brush; - } - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(BrushType.LinearGradientHdr); - } - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Start Point - float.TryParse(match.Groups["StartX"].Value, out var startX); - float.TryParse(match.Groups["StartY"].Value, out var startY); - _startPoint = new Vector2(startX, startY); - - // End Point - float.TryParse(match.Groups["EndX"].Value, out var endX); - float.TryParse(match.Groups["EndY"].Value, out var endY); - _endPoint = new Vector2(endX, endY); - - // Opacity (optional) - var group = match.Groups["Opacity"]; - if (group.Success) - { - float.TryParse(group.Value, out _opacity); - } - - // Alpha Mode (optional) - group = match.Groups["AlphaMode"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _alphaMode); - } - - // Buffer Precision (optional) - group = match.Groups["BufferPrecision"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _bufferPrecision); - } - - // Edge Behavior (optional) - group = match.Groups["EdgeBehavior"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _edgeBehavior); - } - - // Pre Interpolation ColorSpace (optional) - group = match.Groups["PreColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _preInterpolationColorSpace); - } - - // Post Interpolation ColorSpace (optional) - group = match.Groups["PostColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _postInterpolationColorSpace); - } - - // GradientStopHdrs - group = match.Groups["GradientStops"]; - if (group.Success) - { - _gradientStopHdrs.Clear(); - foreach (Capture capture in group.Captures) - { - var gradientMatch = RegexFactory.GradientStopHdrRegex.Match(capture.Value); - if (!gradientMatch.Success) - { - continue; - } - - // Main Attributes - var main = gradientMatch.Groups["Main"]; - if (main.Success) - { - var mainMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.MainHdr)? - .Match(main.Value); - float.TryParse(mainMatch?.Groups["Position"].Value, out float position); - float.TryParse(mainMatch?.Groups["X"].Value, out float x); - float.TryParse(mainMatch?.Groups["Y"].Value, out float y); - float.TryParse(mainMatch?.Groups["Z"].Value, out float z); - float.TryParse(mainMatch?.Groups["W"].Value, out float w); - - var color = new Vector4(x, y, z, w); - - _gradientStopHdrs.Add(new CanvasGradientStopHdr() - { - Color = color, - Position = position - }); - } - - // Additional Attributes - var additional = gradientMatch.Groups["Additional"]; - if (!additional.Success) - { - continue; - } - - foreach (Capture addCapture in additional.Captures) - { - var addMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.AdditionalHdr)? - .Match(addCapture.Value); - float.TryParse(addMatch?.Groups["Position"].Value, out float position); - float.TryParse(addMatch?.Groups["X"].Value, out float x); - float.TryParse(addMatch?.Groups["Y"].Value, out float y); - float.TryParse(addMatch?.Groups["Z"].Value, out float z); - float.TryParse(addMatch?.Groups["W"].Value, out float w); - - var color = new Vector4(x, y, z, w); - - _gradientStopHdrs.Add(new CanvasGradientStopHdr() - { - Color = color, - Position = position - }); - } - } - - // Sort the stops based on their position - if (_gradientStopHdrs.Any()) - { - _gradientStopHdrs = _gradientStopHdrs.OrderBy(g => g.Position).ToList(); - } - } - } -} diff --git a/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs deleted file mode 100644 index dbc438ec..00000000 --- a/components/Media/src/Geometry/Elements/Brush/RadialGradientBrushElement.cs +++ /dev/null @@ -1,233 +0,0 @@ -// 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.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Parsers; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Represents a CanvasRadialGradientBrush with GradientStops -/// -internal sealed class RadialGradientBrushElement : AbstractCanvasBrushElement -{ - private float _radiusX; - private float _radiusY; - private Vector2 _center; - private Vector2 _originOffset; - private CanvasAlphaMode _alphaMode; - private CanvasBufferPrecision _bufferPrecision; - private CanvasEdgeBehavior _edgeBehavior; - private CanvasColorSpace _preInterpolationColorSpace; - private CanvasColorSpace _postInterpolationColorSpace; - private List _gradientStops; - - /// - /// Initializes a new instance of the class. - /// - /// Capture object - public RadialGradientBrushElement(Capture capture) - { - // Set the default values - _radiusX = 0f; - _radiusY = 0f; - _center = Vector2.Zero; - _originOffset = Vector2.Zero; - _opacity = 1f; - _alphaMode = (CanvasAlphaMode)0; - _bufferPrecision = (CanvasBufferPrecision)0; - _edgeBehavior = (CanvasEdgeBehavior)0; - - // Default ColorSpace is sRGB - _preInterpolationColorSpace = CanvasColorSpace.Srgb; - _postInterpolationColorSpace = CanvasColorSpace.Srgb; - _gradientStops = new List(); - - // Initialize - Initialize(capture); - } - - /// - /// Creates the CanvasLinearGradientBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// Instance of - public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) - { - var brush = new CanvasRadialGradientBrush( - resourceCreator, - _gradientStops.ToArray(), - _edgeBehavior, - _alphaMode, - _preInterpolationColorSpace, - _postInterpolationColorSpace, - _bufferPrecision) - { - RadiusX = _radiusX, - RadiusY = _radiusY, - Center = _center, - OriginOffset = _originOffset, - Opacity = _opacity - }; - - return brush; - } - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(BrushType.RadialGradient); - } - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // RadiusX - float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); - - // Sanitize by taking the absolute value - _radiusX = Math.Abs(_radiusX); - - // RadiusY - float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); - - // Sanitize by taking the absolute value - _radiusY = Math.Abs(_radiusY); - - // CenterX - float.TryParse(match.Groups["CenterX"].Value, out var centerX); - - // CenterY - float.TryParse(match.Groups["CenterY"].Value, out var centerY); - _center = new Vector2(centerX, centerY); - - // Opacity (optional) - var group = match.Groups["Opacity"]; - if (group.Success) - { - float.TryParse(group.Value, out _opacity); - } - - // Origin Offset (optional) - group = match.Groups["OriginOffset"]; - if (group.Success) - { - float.TryParse(match.Groups["OffsetX"].Value, out var offsetX); - float.TryParse(match.Groups["OffsetY"].Value, out var offsetY); - _originOffset = new Vector2(offsetX, offsetY); - } - - // Alpha Mode (optional) - group = match.Groups["AlphaMode"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _alphaMode); - } - - // Buffer Precision (optional) - group = match.Groups["BufferPrecision"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _bufferPrecision); - } - - // Edge Behavior (optional) - group = match.Groups["EdgeBehavior"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _edgeBehavior); - } - - // Pre Interpolation ColorSpace (optional) - group = match.Groups["PreColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _preInterpolationColorSpace); - } - - // Post Interpolation ColorSpace (optional) - group = match.Groups["PostColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _postInterpolationColorSpace); - } - - // Gradient Stops - group = match.Groups["GradientStops"]; - if (group.Success) - { - _gradientStops.Clear(); - foreach (Capture capture in group.Captures) - { - var gradientMatch = RegexFactory.GradientStopRegex.Match(capture.Value); - if (!gradientMatch.Success) - { - continue; - } - - float position; - Color color; - - // Main Attributes - var main = gradientMatch.Groups["Main"]; - if (main.Success) - { - var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Main); - default(ArgumentNullException).ThrowIfNull(regex); - - var mainMatch = regex.Match(main.Value); - float.TryParse(mainMatch.Groups["Position"].Value, out position); - - color = ColorParser.Parse(mainMatch); - - _gradientStops.Add(new CanvasGradientStop() - { - Color = color, - Position = position - }); - } - - // Additional Attributes - var additional = gradientMatch.Groups["Additional"]; - if (!additional.Success) - { - continue; - } - - foreach (Capture addCapture in additional.Captures) - { - var regex = RegexFactory.GetAttributesRegex(GradientStopAttributeType.Additional); - default(ArgumentNullException).ThrowIfNull(regex); - - var addMatch = regex.Match(addCapture.Value); - float.TryParse(addMatch.Groups["Position"].Value, out position); - - color = ColorParser.Parse(addMatch); - - _gradientStops.Add(new CanvasGradientStop() - { - Color = color, - Position = position - }); - } - } - - // Sort the stops based on their position - if (_gradientStops.Any()) - { - _gradientStops = _gradientStops.OrderBy(g => g.Position).ToList(); - } - } - } -} diff --git a/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs deleted file mode 100644 index 8e2bd14c..00000000 --- a/components/Media/src/Geometry/Elements/Brush/RadialGradientHdrBrushElement.cs +++ /dev/null @@ -1,233 +0,0 @@ -// 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.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Represents a CanvasRadialGradientBrush with GradientStopHdrs -/// -internal sealed class RadialGradientHdrBrushElement : AbstractCanvasBrushElement -{ - private float _radiusX; - private float _radiusY; - private Vector2 _center; - private Vector2 _originOffset; - private CanvasAlphaMode _alphaMode; - private CanvasBufferPrecision _bufferPrecision; - private CanvasEdgeBehavior _edgeBehavior; - private CanvasColorSpace _preInterpolationColorSpace; - private CanvasColorSpace _postInterpolationColorSpace; - private List _gradientStopHdrs; - - /// - /// Initializes a new instance of the class. - /// - /// Capture object - public RadialGradientHdrBrushElement(Capture capture) - { - // Set the default values - _radiusX = 0f; - _radiusY = 0f; - _center = Vector2.Zero; - _originOffset = Vector2.Zero; - _opacity = 1f; - _alphaMode = (CanvasAlphaMode)0; - _bufferPrecision = (CanvasBufferPrecision)0; - _edgeBehavior = (CanvasEdgeBehavior)0; - - // Default ColorSpace is sRGB - _preInterpolationColorSpace = CanvasColorSpace.Srgb; - _postInterpolationColorSpace = CanvasColorSpace.Srgb; - _gradientStopHdrs = new List(); - - // Initialize - Initialize(capture); - } - - /// - /// Creates the CanvasLinearGradientBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// Instance of - public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) - { - var brush = CanvasRadialGradientBrush.CreateHdr( - resourceCreator, - _gradientStopHdrs.ToArray(), - _edgeBehavior, - _alphaMode, - _preInterpolationColorSpace, - _postInterpolationColorSpace, - _bufferPrecision); - - brush.RadiusX = _radiusX; - brush.RadiusY = _radiusY; - brush.Center = _center; - brush.OriginOffset = _originOffset; - brush.Opacity = _opacity; - - return brush; - } - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(BrushType.RadialGradientHdr); - } - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // RadiusX - float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); - - // Sanitize by taking the absolute value - _radiusX = Math.Abs(_radiusX); - - // RadiusY - float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); - - // Sanitize by taking the absolute value - _radiusY = Math.Abs(_radiusY); - - // CenterX - float.TryParse(match.Groups["CenterX"].Value, out var centerX); - - // CenterY - float.TryParse(match.Groups["CenterY"].Value, out var centerY); - _center = new Vector2(centerX, centerY); - - // Opacity (optional) - var group = match.Groups["Opacity"]; - if (group.Success) - { - float.TryParse(group.Value, out _opacity); - } - - // Origin Offset (optional) - group = match.Groups["OriginOffset"]; - if (group.Success) - { - float.TryParse(match.Groups["OffsetX"].Value, out var offsetX); - float.TryParse(match.Groups["OffsetY"].Value, out var offsetY); - _originOffset = new Vector2(offsetX, offsetY); - } - - // Alpha Mode (optional) - group = match.Groups["AlphaMode"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _alphaMode); - } - - // Buffer Precision (optional) - group = match.Groups["BufferPrecision"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _bufferPrecision); - } - - // Edge Behavior (optional) - group = match.Groups["EdgeBehavior"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _edgeBehavior); - } - - // Pre Interpolation ColorSpace (optional) - group = match.Groups["PreColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _preInterpolationColorSpace); - } - - // Post Interpolation ColorSpace (optional) - group = match.Groups["PostColorSpace"]; - if (group.Success) - { - Enum.TryParse(group.Value, out _postInterpolationColorSpace); - } - - // GradientStopHdrs - group = match.Groups["GradientStops"]; - if (group.Success) - { - _gradientStopHdrs.Clear(); - foreach (Capture capture in group.Captures) - { - var gradientMatch = RegexFactory.GradientStopHdrRegex.Match(capture.Value); - if (!gradientMatch.Success) - { - continue; - } - - float position; - float x = 0, y = 0, z = 0, w = 0; - var main = gradientMatch.Groups["Main"]; - if (main.Success) - { - var mainMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.MainHdr)? - .Match(main.Value); - - float.TryParse(mainMatch?.Groups["Position"].Value, out position); - float.TryParse(mainMatch?.Groups["X"].Value, out x); - float.TryParse(mainMatch?.Groups["Y"].Value, out y); - float.TryParse(mainMatch?.Groups["Z"].Value, out z); - float.TryParse(mainMatch?.Groups["W"].Value, out w); - - var color = new Vector4(x, y, z, w); - - _gradientStopHdrs.Add(new CanvasGradientStopHdr() - { - Color = color, - Position = position - }); - } - - var additional = gradientMatch.Groups["Additional"]; - if (!additional.Success) - { - continue; - } - - foreach (Capture addCapture in additional.Captures) - { - var addMatch = RegexFactory.GetAttributesRegex(GradientStopAttributeType.AdditionalHdr)? - .Match(addCapture.Value); - - float.TryParse(addMatch?.Groups["Position"].Value, out position); - float.TryParse(addMatch?.Groups["X"].Value, out x); - float.TryParse(addMatch?.Groups["Y"].Value, out y); - float.TryParse(addMatch?.Groups["Z"].Value, out z); - float.TryParse(addMatch?.Groups["W"].Value, out w); - - var color = new Vector4(x, y, z, w); - - _gradientStopHdrs.Add(new CanvasGradientStopHdr() - { - Color = color, - Position = position - }); - } - } - - // Sort the stops based on their position - if (_gradientStopHdrs.Any()) - { - _gradientStopHdrs = _gradientStopHdrs.OrderBy(g => g.Position).ToList(); - } - } - } -} diff --git a/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs b/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs deleted file mode 100644 index a1dcd9d9..00000000 --- a/components/Media/src/Geometry/Elements/Brush/SolidColorBrushElement.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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; -using Microsoft.Graphics.Canvas.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Parsers; -using Windows.UI; -using Microsoft.UI; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -/// -/// Represents a CanvasSolidColorBrush -/// -internal sealed class SolidColorBrushElement : AbstractCanvasBrushElement -{ - private Color _color; - - /// - /// Initializes a new instance of the class. - /// - /// Capture object - public SolidColorBrushElement(Capture capture) - { - // Set the default values - _color = Colors.Transparent; - _opacity = 1f; - - Initialize(capture); - } - - /// - /// Creates the ICanvasBrush from the parsed data - /// - /// ICanvasResourceCreator object - /// Instance of - public override ICanvasBrush CreateBrush(ICanvasResourceCreator resourceCreator) - { - return new CanvasSolidColorBrush(resourceCreator, _color) - { - Opacity = _opacity - }; - } - - /// - /// Gets the Regex for extracting Brush Element Attributes - /// - /// Regex - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(BrushType.SolidColor); - } - - /// - /// Gets the Brush Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Parse the Color - _color = ColorParser.Parse(match); - - // Opacity (optional) - var group = match.Groups["Opacity"]; - if (group.Success) - { - float.TryParse(group.Value, out _opacity); - } - } -} diff --git a/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs b/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs deleted file mode 100644 index ffe0bc13..00000000 --- a/components/Media/src/Geometry/Elements/Path/AbstractPathElement.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Abstract base class for all Path Elements -/// -internal abstract class AbstractPathElement : ICanvasPathElement -{ - /// - /// Gets or sets index of the Path Element in the Path Data - /// - public int Index { get; protected set; } = -1; - - /// - /// Gets or sets path data defining the Path Element - /// - public string Data { get; protected set; } = string.Empty; - - /// - /// Gets or sets number of non-whitespace characters in - /// the Path Element Data - /// - public int ValidationCount { get; protected set; } = 0; - - /// - /// Gets or sets a value indicating whether the path element contains - /// absolute or relative coordinates. - /// - public bool IsRelative { get; protected set; } = false; - - /// - /// Initializes the Path Element with the given Match - /// - /// Match object - /// Index within the match - public virtual void Initialize(Match match, int index) - { - var main = match.Groups["Main"]; - Index = index; - Data = main.Value; - var command = match.Groups["Command"].Value[0]; - IsRelative = char.IsLower(command); - - // Get the Path Element attributes - GetAttributes(match); - - // Get the number of non-whitespace characters in the data - ValidationCount = RegexFactory.ValidationRegex.Replace(main.Value, string.Empty).Length; - } - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - public virtual void InitializeAdditional(Capture capture, int index, bool isRelative) - { - Index = index; - Data = capture.Value; - IsRelative = isRelative; - - var match = GetAttributesRegex()!.Match(Data); - if (match.Captures.Count != 1) - { - return; - } - - // Get the Path Element attributes - GetAttributes(match); - - // Get the number of non-whitespace characters in the data - ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public abstract Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement); - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected abstract Regex? GetAttributesRegex(); - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected abstract void GetAttributes(Match match); -} diff --git a/components/Media/src/Geometry/Elements/Path/ArcElement.cs b/components/Media/src/Geometry/Elements/Path/ArcElement.cs deleted file mode 100644 index ecadb1bf..00000000 --- a/components/Media/src/Geometry/Elements/Path/ArcElement.cs +++ /dev/null @@ -1,98 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Arc Element in a Path Geometry -/// -internal class ArcElement : AbstractPathElement -{ - private float _radiusX; - private float _radiusY; - private float _angle; - private CanvasArcSize _arcSize; - private CanvasSweepDirection _sweepDirection; - private float _x; - private float _y; - - /// - /// Initializes a new instance of the class. - /// - public ArcElement() - { - _radiusX = _radiusY = _angle = 0; - _arcSize = CanvasArcSize.Small; - _sweepDirection = CanvasSweepDirection.Clockwise; - _sweepDirection = 0; - _x = _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var point = new Vector2(_x, _y); - if (IsRelative) - { - point += currentPoint; - } - - // Execute command - pathBuilder.AddArc(point, _radiusX, _radiusY, _angle, _sweepDirection, _arcSize); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.Arc); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); - - // Sanitize by taking only the absolute value - _radiusX = Math.Abs(_radiusX); - float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); - - // Sanitize by taking only the absolute value - _radiusY = Math.Abs(_radiusY); - - // Angle is provided in degrees - float.TryParse(match.Groups["Angle"].Value, out _angle); - - // Convert angle to radians as CanvasPathBuilder.AddArc() method - // requires the angle to be in radians - _angle *= Scalar.DegreesToRadians; - Enum.TryParse(match.Groups["IsLargeArc"].Value, out _arcSize); - Enum.TryParse(match.Groups["SweepDirection"].Value, out _sweepDirection); - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs deleted file mode 100644 index 19a66905..00000000 --- a/components/Media/src/Geometry/Elements/Path/CanvasEllipseFigure.cs +++ /dev/null @@ -1,80 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Ellipse Figure in a Path Geometry -/// -internal class CanvasEllipseFigure : AbstractPathElement -{ - private float _radiusX; - private float _radiusY; - private float _x; - private float _y; - - /// - /// Initializes a new instance of the class. - /// - public CanvasEllipseFigure() - { - _radiusX = _radiusY = _x = _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the EllipseFigure - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the EllipseFigure - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var center = new Vector2(_x, _y); - if (IsRelative) - { - center += currentPoint; - } - - // Execute command - pathBuilder.AddEllipseFigure(center.X, center.Y, _radiusX, _radiusY); - - // No need to update the lastElement or currentPoint here as we are creating - // a separate closed figure here. So current point will not change. - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathFigureType.EllipseFigure); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); - - // Sanitize by taking the absolute value - _radiusX = Math.Abs(_radiusX); - float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); - - // Sanitize by taking the absolute value - _radiusY = Math.Abs(_radiusY); - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs deleted file mode 100644 index 75276a1f..00000000 --- a/components/Media/src/Geometry/Elements/Path/CanvasPathFigure.cs +++ /dev/null @@ -1,132 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using System.Text.RegularExpressions; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class which contains a collection of ICanvasPathElements -/// which can be used to create CanvasGeometry. -/// -internal class CanvasPathFigure : AbstractPathElement -{ - // Collection of Path Elements - private List _elements; - - public CanvasPathFigure() - { - _elements = new List(); - ValidationCount = 0; - } - - /// - /// Initializes the Path Element with the given Match - /// - /// Match object - /// Index of the path element in the Path data. - public override void Initialize(Match match, int index) - { - Index = index; - var main = match.Groups["Main"]; - Data = main.Value; - - var elements = new List(); - foreach (PathElementType type in Enum.GetValues(typeof(PathElementType))) - { - foreach (Capture elementCapture in match.Groups[type.ToString()].Captures) - { - var elementRootIndex = elementCapture.Index; - var regex = RegexFactory.GetRegex(type); - var elementMatch = regex?.Match(elementCapture.Value); - var isRelative = false; - - // Process the 'Main' Group which contains the Path Command and - // corresponding attributes - if (elementMatch?.Groups["Main"].Captures.Count == 1) - { - var figure = PathElementFactory.CreatePathElement(type, elementMatch, elementRootIndex); - elements.Add(figure); - isRelative = figure.IsRelative; - } - - // Process the 'Additional' Group which contains just the attributes - elements.AddRange(from Capture capture in (elementMatch?.Groups["Additional"].Captures ?? Enumerable.Empty()) - select PathElementFactory.CreateAdditionalPathElement(type, capture, elementRootIndex + capture.Index, isRelative)); - } - } - - // Sort the path elements based on their index value - _elements.AddRange(elements.OrderBy(e => e.Index)); - if (_elements.Count <= 0) - { - return; - } - - // Check if the last path element in the figure is an ClosePathElement - // which would indicate that the path needs to be closed. Otherwise, - // add a default ClosePathElement at the end to indicate that the path - // is not closed. - var lastElement = _elements.ElementAt(_elements.Count - 1); - if ((lastElement as ClosePathElement) == null) - { - _elements.Add(PathElementFactory.CreateDefaultPathElement(PathElementType.ClosePath)); - } - - // Validation Count will be the cumulative sum of the validation count - // of child elements of the PathFigure - ValidationCount = _elements.Sum(x => x.ValidationCount); - } - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - public override void InitializeAdditional(Capture capture, int index, bool isRelative) - { - // Do nothing as this scenario is not valid for CanvasPathFigure - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - foreach (var pathElement in _elements) - { - currentPoint = pathElement.CreatePath(pathBuilder, currentPoint, ref lastElement); - } - - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return null; - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Do nothing - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs deleted file mode 100644 index dbd4a1a5..00000000 --- a/components/Media/src/Geometry/Elements/Path/CanvasPolygonFigure.cs +++ /dev/null @@ -1,82 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Polygon Figure in a Path Geometry -/// -internal class CanvasPolygonFigure : AbstractPathElement -{ - private int _numSides; - private float _radius; - private float _x; - private float _y; - - public CanvasPolygonFigure() - { - _numSides = 0; - _radius = _x = _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the PolygonFigure - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the PolygonFigure - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var center = new Vector2(_x, _y); - if (IsRelative) - { - center += currentPoint; - } - - // Execute command - pathBuilder.AddPolygonFigure(_numSides, center.X, center.Y, _radius); - - // No need to update the lastElement or currentPoint here as we are creating - // a separate closed figure here. So current point will not change. - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathFigureType.PolygonFigure); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Sides - int.TryParse(match.Groups["Sides"].Value, out _numSides); - - // Sanitize by taking the absolute value - _numSides = Math.Abs(_numSides); - - // Radius - float.TryParse(match.Groups["Radius"].Value, out _radius); - - // Sanitize by taking the absolute value - _radius = Math.Abs(_radius); - - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs deleted file mode 100644 index 18f36cf3..00000000 --- a/components/Media/src/Geometry/Elements/Path/CanvasRectangleFigure.cs +++ /dev/null @@ -1,87 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Rectangle Figure in a Path Geometry -/// -internal sealed class CanvasRectangleFigure : AbstractPathElement -{ - private float _x; - private float _y; - private float _width; - private float _height; - - /// - /// Initializes a new instance of the class. - /// - public CanvasRectangleFigure() - { - _x = _y = _width = _height = 0f; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the PolygonFigure - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the PolygonFigure - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var topLeft = new Vector2(_x, _y); - if (IsRelative) - { - topLeft += currentPoint; - } - - // Execute command - pathBuilder.AddRectangleFigure(topLeft.X, topLeft.Y, _width, _height); - - // No need to update the lastElement or currentPoint here as we are creating - // a separate closed figure here.So current point will not change. - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathFigureType.RectangleFigure); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // X - float.TryParse(match.Groups["X"].Value, out _x); - - // Y - float.TryParse(match.Groups["Y"].Value, out _y); - - // Width - float.TryParse(match.Groups["Width"].Value, out _width); - - // Sanitize by taking the absolute value - _width = Math.Abs(_width); - - // Height - float.TryParse(match.Groups["Height"].Value, out _height); - - // Sanitize by taking the absolute value - _height = Math.Abs(_height); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs b/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs deleted file mode 100644 index b6a9f5ff..00000000 --- a/components/Media/src/Geometry/Elements/Path/CanvasRoundRectangleFigure.cs +++ /dev/null @@ -1,101 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the RoundRectangle Figure in a Path Geometry -/// -internal sealed class CanvasRoundRectangleFigure : AbstractPathElement -{ - private float _x; - private float _y; - private float _width; - private float _height; - private float _radiusX; - private float _radiusY; - - /// - /// Initializes a new instance of the class. - /// - public CanvasRoundRectangleFigure() - { - _x = _y = _width = _height = _radiusX = _radiusY = 0f; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the PolygonFigure - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the PolygonFigure - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var topLeft = new Vector2(_x, _y); - if (IsRelative) - { - topLeft += currentPoint; - } - - // Execute command - pathBuilder.AddRoundedRectangleFigure(topLeft.X, topLeft.Y, _width, _height, _radiusX, _radiusY); - - // No need to update the lastElement or currentPoint here as we are creating - // a separate closed figure here.So current point will not change. - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathFigureType.RoundedRectangleFigure); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // X - float.TryParse(match.Groups["X"].Value, out _x); - - // Y - float.TryParse(match.Groups["Y"].Value, out _y); - - // Width - float.TryParse(match.Groups["Width"].Value, out _width); - - // Sanitize by taking the absolute value - _width = Math.Abs(_width); - - // Height - float.TryParse(match.Groups["Height"].Value, out _height); - - // Sanitize by taking the absolute value - _height = Math.Abs(_height); - - // RadiusX - float.TryParse(match.Groups["RadiusX"].Value, out _radiusX); - - // Sanitize by taking the absolute value - _radiusX = Math.Abs(_radiusX); - - // RadiusY - float.TryParse(match.Groups["RadiusY"].Value, out _radiusY); - - // Sanitize by taking the absolute value - _radiusY = Math.Abs(_radiusY); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs b/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs deleted file mode 100644 index a67d7766..00000000 --- a/components/Media/src/Geometry/Elements/Path/ClosePathElement.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the ClosePath command in a Path Geometry -/// -internal class ClosePathElement : AbstractPathElement -{ - private bool _isFigureClosed; - - /// - /// Initializes a new instance of the class. - /// - public ClosePathElement() - { - _isFigureClosed = false; - } - - /// - /// Initializes the Path Element with the given Match - /// - /// Match object - /// Index of the Path Element in the Path data. - public override void Initialize(Match match, int index) - { - var main = match.Groups["Main"]; - Index = index; - Data = main.Value; - var command = match.Groups["Command"]; - - // If the Command is captured, it means that 'Z' is provided and hence - // the figure must be closed - if (command.Captures.Count == 1) - { - _isFigureClosed = true; - } - - // Get the number of non-whitespace characters in the data - ValidationCount = RegexFactory.ValidationRegex.Replace(main.Value, string.Empty).Length; - } - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - public override void InitializeAdditional(Capture capture, int index, bool isRelative) - { - // Do nothing as this scenario is not valid for this Path Element - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Execute command - pathBuilder.EndFigure(_isFigureClosed ? CanvasFigureLoop.Closed : CanvasFigureLoop.Open); - - // Set Last Element - lastElement = this; - - // Return current point - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - // Attributes are not present - return null; - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Do Nothing - } -} diff --git a/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs b/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs deleted file mode 100644 index 514c8c30..00000000 --- a/components/Media/src/Geometry/Elements/Path/CubicBezierElement.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Cubic Bezier Element in a Path Geometry -/// -internal class CubicBezierElement : AbstractPathElement -{ - private float _x1; - private float _y1; - private float _x2; - private float _y2; - private float _x; - private float _y; - - private Vector2 _absoluteControlPoint2; - - /// - /// Initializes a new instance of the class. - /// - public CubicBezierElement() - { - _x1 = _y1 = 0; - _x2 = _y2 = 0; - _x = _y = 0; - _absoluteControlPoint2 = Vector2.Zero; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var controlPoint1 = new Vector2(_x1, _y1); - var controlPoint2 = new Vector2(_x2, _y2); - var point = new Vector2(_x, _y); - - if (IsRelative) - { - controlPoint1 += currentPoint; - controlPoint2 += currentPoint; - point += currentPoint; - } - - // Save the second absolute control point so that it can be used by the following - // SmoothCubicBezierElement (if any) - _absoluteControlPoint2 = controlPoint2; - - // Execute command - pathBuilder.AddCubicBezier(controlPoint1, controlPoint2, point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Gets the Second Control Point of this Cubic Bezier - /// - /// Vector2 - public Vector2 GetControlPoint() - { - return _absoluteControlPoint2; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.CubicBezier); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X1"].Value, out _x1); - float.TryParse(match.Groups["Y1"].Value, out _y1); - float.TryParse(match.Groups["X2"].Value, out _x2); - float.TryParse(match.Groups["Y2"].Value, out _y2); - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/FillRuleElement.cs b/components/Media/src/Geometry/Elements/Path/FillRuleElement.cs deleted file mode 100644 index f4b8110e..00000000 --- a/components/Media/src/Geometry/Elements/Path/FillRuleElement.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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.Geometry; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Fill Rule Element in a Path Geometry -/// -internal class FillRuleElement : AbstractPathElement -{ - private CanvasFilledRegionDetermination _fillValue; - - /// - /// Initializes a new instance of the class. - /// - public FillRuleElement() - { - _fillValue = CanvasFilledRegionDetermination.Alternate; - } - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - public override void InitializeAdditional(Capture capture, int index, bool isRelative) - { - // Do nothing as this scenario is not valid for this Path Element - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Execute command - pathBuilder.SetFilledRegionDetermination(_fillValue); - - // Set Last Element - lastElement = this; - - // Return current point - return currentPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - // Not applicable for this Path Element - return null; - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - Enum.TryParse(match.Groups["FillValue"].Value, out _fillValue); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs b/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs deleted file mode 100644 index 8d761666..00000000 --- a/components/Media/src/Geometry/Elements/Path/HorizontalLineElement.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Horizontal Line Element in a Path Geometry -/// -internal class HorizontalLineElement : AbstractPathElement -{ - private float _x; - - /// - /// Initializes a new instance of the class. - /// - public HorizontalLineElement() - { - _x = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var point = IsRelative ? - new Vector2(currentPoint.X + _x, currentPoint.Y) : new Vector2(_x, currentPoint.Y); - - // Execute command - pathBuilder.AddLine(point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.HorizontalLine); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X"].Value, out _x); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs b/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs deleted file mode 100644 index 7ba47b8d..00000000 --- a/components/Media/src/Geometry/Elements/Path/ICanvasPathElement.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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.Geometry; -using System.Numerics; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Interface for a Path Element which serves -/// as a building block for CanvasPathGeometry -/// -internal interface ICanvasPathElement -{ - /// - /// Gets index of the Path Element in the Path Data - /// - int Index { get; } - - /// - /// Gets path data defining the Path Element - /// - string Data { get; } - - /// - /// Gets number of non-whitespace characters in - /// the Path Element Data - /// - int ValidationCount { get; } - - /// - /// Gets a value indicating whether the path element contains - /// absolute or relative coordinates. - /// - bool IsRelative { get; } - - /// - /// Initializes the Path Element with the given Match - /// - /// Match object - /// Index of the Path Element in the Path data. - void Initialize(Match match, int index); - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - void InitializeAdditional(Capture capture, int index, bool isRelative); - - /// - /// Adds the Path Element to the PathBuilder. - /// - /// CanvasPathBuilder object - /// The current point on the path before the path element is added - /// The previous PathElement in the Path. - /// The current point on the path after the path element is added - Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement); - -} diff --git a/components/Media/src/Geometry/Elements/Path/LineElement.cs b/components/Media/src/Geometry/Elements/Path/LineElement.cs deleted file mode 100644 index 80460307..00000000 --- a/components/Media/src/Geometry/Elements/Path/LineElement.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Line Element in a Path Geometry -/// -internal class LineElement : AbstractPathElement -{ - private float _x; - private float _y; - - /// - /// Initializes a new instance of the class. - /// - public LineElement() - { - _x = _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var point = new Vector2(_x, _y); - if (IsRelative) - { - point += currentPoint; - } - - // Execute command - pathBuilder.AddLine(point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.Line); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/MoveToElement.cs b/components/Media/src/Geometry/Elements/Path/MoveToElement.cs deleted file mode 100644 index ff66b41c..00000000 --- a/components/Media/src/Geometry/Elements/Path/MoveToElement.cs +++ /dev/null @@ -1,85 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the MoveTo Command in a Path Geometry -/// -internal class MoveToElement : AbstractPathElement -{ - private float _x; - private float _y; - - /// - /// Initializes a new instance of the class. - /// - public MoveToElement() - { - _x = _y = 0; - } - - /// - /// Initializes the Path Element with the given Capture - /// - /// Capture object - /// Index of the Path Element in the Path data. - /// Indicates whether the Path Element coordinates are - /// absolute or relative - public override void InitializeAdditional(Capture capture, int index, bool isRelative) - { - // Do nothing as this scenario is not valid for MoveTo Command - // Additional coordinates specified with MoveTo will be converted to Line Commands - } - - /// - /// Adds the Path Element to the PathBuilder. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var point = new Vector2(_x, _y); - if (IsRelative) - { - point += currentPoint; - } - - // Execute command - pathBuilder.BeginFigure(point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.MoveTo); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs b/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs deleted file mode 100644 index ba27e244..00000000 --- a/components/Media/src/Geometry/Elements/Path/QuadraticBezierElement.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Quadratic Bezier Element in a Path Geometry -/// -internal class QuadraticBezierElement : AbstractPathElement -{ - private float _x1; - private float _y1; - private float _x; - private float _y; - private Vector2 _absoluteControlPoint; - - /// - /// Initializes a new instance of the class. - /// - public QuadraticBezierElement() - { - _x1 = _y1 = 0; - _x = _y = 0; - _absoluteControlPoint = Vector2.Zero; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var controlPoint = new Vector2(_x1, _y1); - var point = new Vector2(_x, _y); - - if (IsRelative) - { - controlPoint += currentPoint; - point += currentPoint; - } - - // Save the absolute control point so that it can be used by the following - // SmoothQuadraticBezierElement (if any) - _absoluteControlPoint = controlPoint; - - // Execute command - pathBuilder.AddQuadraticBezier(controlPoint, point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Gets the Control Point of this Quadratic Bezier - /// - /// Vector2 - public Vector2 GetControlPoint() - { - return _absoluteControlPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.QuadraticBezier); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X1"].Value, out _x1); - float.TryParse(match.Groups["Y1"].Value, out _y1); - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs b/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs deleted file mode 100644 index 508f0d42..00000000 --- a/components/Media/src/Geometry/Elements/Path/SmoothCubicBezierElement.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Smooth Cubic Bezier Element in a Path Geometry -/// -internal class SmoothCubicBezierElement : AbstractPathElement -{ - private float _x2; - private float _y2; - private float _x; - private float _y; - private Vector2 _absoluteControlPoint2; - - /// - /// Initializes a new instance of the class. - /// - public SmoothCubicBezierElement() - { - _x2 = _y2 = 0; - _x = _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - // Check if the last element was a Cubic Bezier - Vector2 controlPoint1; - if (lastElement is CubicBezierElement cubicBezier) - { - // Reflect the second control point of the cubic bezier over the current point. The - // resulting point will be the first control point of this Bezier. - controlPoint1 = Utils.Reflect(cubicBezier.GetControlPoint(), currentPoint); - } - - // Or if the last element was s Smooth Cubic Bezier - else - { - // If the last element was a Smooth Cubic Bezier then reflect its second control point - // over the current point. The resulting point will be the first control point of this - // Bezier. Otherwise, if the last element was not a Smooth Cubic Bezier then the - // currentPoint will be the first control point of this Bezier - controlPoint1 = lastElement is SmoothCubicBezierElement smoothCubicBezier - ? Utils.Reflect(smoothCubicBezier.GetControlPoint(), currentPoint) - : currentPoint; - } - - var controlPoint2 = new Vector2(_x2, _y2); - var point = new Vector2(_x, _y); - - if (IsRelative) - { - controlPoint2 += currentPoint; - point += currentPoint; - } - - // Save the second absolute control point so that it can be used by the following - // SmoothCubicBezierElement (if any) - _absoluteControlPoint2 = controlPoint2; - - // Execute command - pathBuilder.AddCubicBezier(controlPoint1, controlPoint2, point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Gets the Second Control Point of this Cubic Bezier - /// - /// Vector2 - public Vector2 GetControlPoint() - { - return _absoluteControlPoint2; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.SmoothCubicBezier); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X2"].Value, out _x2); - float.TryParse(match.Groups["Y2"].Value, out _y2); - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs b/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs deleted file mode 100644 index 6746e128..00000000 --- a/components/Media/src/Geometry/Elements/Path/SmoothQuadraticBezierElement.cs +++ /dev/null @@ -1,104 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Smooth Quadratic Bezier Element in a Path Geometry -/// -internal class SmoothQuadraticBezierElement : AbstractPathElement -{ - private float _x; - private float _y; - private Vector2 _absoluteControlPoint; - - /// - /// Initializes a new instance of the class. - /// - public SmoothQuadraticBezierElement() - { - _x = _y = 0; - _absoluteControlPoint = Vector2.Zero; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - // Check if the last element was a Quadratic Bezier - if (lastElement is QuadraticBezierElement quadBezier) - { - // Reflect the control point of the Quadratic Bezier over the current point. The - // resulting point will be the control point of this Bezier. - _absoluteControlPoint = Utils.Reflect(quadBezier.GetControlPoint(), currentPoint); - } - - // Or if the last element was s Smooth Quadratic Bezier - else - { - // If the last element was a Smooth Quadratic Bezier then reflect its control point - // over the current point. The resulting point will be the control point of this - // Bezier. Otherwise, if the last element was not a Smooth Quadratic Bezier, - // then the currentPoint will be the control point of this Bezier. - _absoluteControlPoint = lastElement is SmoothQuadraticBezierElement smoothQuadBezier - ? Utils.Reflect(smoothQuadBezier.GetControlPoint(), currentPoint) - : currentPoint; - } - - var point = new Vector2(_x, _y); - - if (IsRelative) - { - point += currentPoint; - } - - // Execute command - pathBuilder.AddQuadraticBezier(_absoluteControlPoint, point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Gets the Control Point of this Quadratic Bezier - /// - /// Vector2 - public Vector2 GetControlPoint() - { - return _absoluteControlPoint; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.SmoothQuadraticBezier); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["X"].Value, out _x); - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs b/components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs deleted file mode 100644 index c3bacc4a..00000000 --- a/components/Media/src/Geometry/Elements/Path/VerticalLineElement.cs +++ /dev/null @@ -1,67 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -/// -/// Class representing the Vertical Line Element in a Path Geometry -/// -internal class VerticalLineElement : AbstractPathElement -{ - private float _y; - - /// - /// Initializes a new instance of the class. - /// - public VerticalLineElement() - { - _y = 0; - } - - /// - /// Adds the Path Element to the Path. - /// - /// CanvasPathBuilder object - /// The last active location in the Path before adding - /// the Path Element - /// The previous PathElement in the Path. - /// The latest location in the Path after adding the Path Element - public override Vector2 CreatePath(CanvasPathBuilder pathBuilder, Vector2 currentPoint, ref ICanvasPathElement? lastElement) - { - // Calculate coordinates - var point = IsRelative ? - new Vector2(currentPoint.X, currentPoint.Y + _y) : new Vector2(currentPoint.X, _y); - - // Execute command - pathBuilder.AddLine(point); - - // Set Last Element - lastElement = this; - - // Return current point - return point; - } - - /// - /// Get the Regex for extracting Path Element Attributes - /// - /// Instance of - protected override Regex? GetAttributesRegex() - { - return RegexFactory.GetAttributesRegex(PathElementType.VerticalLine); - } - - /// - /// Gets the Path Element Attributes from the Match - /// - /// Match object - protected override void GetAttributes(Match match) - { - float.TryParse(match.Groups["Y"].Value, out _y); - } -} diff --git a/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs deleted file mode 100644 index db82f92b..00000000 --- a/components/Media/src/Geometry/Elements/Stroke/AbstractCanvasStrokeElement.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -/// -/// Abstract base class for Stroke Element. -/// -internal abstract class AbstractCanvasStrokeElement : ICanvasStrokeElement -{ - /// - /// Gets or sets the Stroke data defining the Brush Element - /// - public string? Data { get; protected set; } - - /// - /// Gets or sets the number of non-whitespace characters in the Stroke Data. - /// - public int ValidationCount { get; protected set; } - - /// - /// Initializes the Stroke Element with the given Capture. - /// - /// Match object - public virtual void Initialize(Match match) - { - Data = match.Value; - - if (!match.Success) - { - return; - } - - GetAttributes(match); - - // Update the validation count - Validate(); - } - - /// - /// Gets the number of non-whitespace characters in the data. - /// - protected virtual void Validate() - { - default(ArgumentNullException).ThrowIfNull(Data); - ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; - } - - /// - /// Creates the ICanvasStroke from the parsed data. - /// - /// ICanvasStroke - public abstract ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator); - - /// - /// Gets the Stroke Element Attributes from the Match. - /// - /// Match object - protected abstract void GetAttributes(Match match); -} diff --git a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs deleted file mode 100644 index 9bddb36c..00000000 --- a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeElement.cs +++ /dev/null @@ -1,105 +0,0 @@ -// 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; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; -using CommunityToolkit.WinUI.Media.Geometry.Parsers; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -/// -/// Represents a Stroke Element. -/// -internal sealed class CanvasStrokeElement : AbstractCanvasStrokeElement -{ - private float _width; - private ICanvasBrushElement? _brush; - private ICanvasStrokeStyleElement? _style; - private int _widthValidationCount; - - /// - /// Initializes a new instance of the class. - /// - /// Match object - public CanvasStrokeElement(Match match) - { - _width = 1f; - _brush = null; - _style = null; - _widthValidationCount = 0; - - Initialize(match); - } - - /// - /// Creates the ICanvasStroke from the parsed data. - /// - /// ICanvasStroke - public override ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator) - { - default(ArgumentNullException).ThrowIfNull(_brush); - default(ArgumentNullException).ThrowIfNull(_style); - - return new CanvasStroke(_brush.CreateBrush(resourceCreator), _width, _style.Style); - } - - /// - /// Gets the Stroke Element Attributes from the Match. - /// - /// Match object - protected override void GetAttributes(Match match) - { - // Stroke Width - var group = match.Groups["StrokeWidth"]; - float.TryParse(group.Value, out _width); - - // Sanitize by taking the absolute value - _width = Math.Abs(_width); - - _widthValidationCount = RegexFactory.ValidationRegex.Replace(group.Value, string.Empty).Length; - - // Stroke Brush - group = match.Groups["CanvasBrush"]; - if (group.Success) - { - _brush = CanvasBrushParser.Parse(group.Value); - } - - // If the ICanvasBrushElement was not created, then the ICanvasStroke cannot be created - if (_brush == null) - { - static void Throw(string value) => throw new ArgumentException($"Unable to create a valid ICanvasBrush for the ICanvasStroke with the following Brush data - '{value}'."); - - Throw(group.Value); - } - - // Stroke Style - _style = CanvasStrokeStyleParser.Parse(match); - } - - /// - /// Gets the number of non-whitespace characters in the data. - /// - protected override void Validate() - { - // Add 2 to the Validation Count to include the stroke command 'ST' - ValidationCount += 2; - - // StrokeWidth Validation Count - ValidationCount += _widthValidationCount; - - // Stroke Brush Validation Count - if (_brush != null) - { - ValidationCount += _brush.ValidationCount; - } - - // Stroke Style Validation Count - if (_style != null) - { - ValidationCount += _style.ValidationCount; - } - } -} diff --git a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs b/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs deleted file mode 100644 index 02f20e06..00000000 --- a/components/Media/src/Geometry/Elements/Stroke/CanvasStrokeStyleElement.cs +++ /dev/null @@ -1,177 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -/// -/// Represents a CanvasStrokeStyle Element. -/// -internal sealed class CanvasStrokeStyleElement : ICanvasStrokeStyleElement -{ - /// - /// Gets the Stroke data defining the Brush Element - /// - public string Data { get; private set; } - - /// - /// Gets the number of non-whitespace characters in - /// the Stroke Data - /// - public int ValidationCount { get; private set; } - - /// - /// Gets the CanvasStrokeStyle obtained by parsing - /// the style data. - /// - public CanvasStrokeStyle Style { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The matching data - public CanvasStrokeStyleElement(Match match) - { - Data = match.Groups["CanvasStrokeStyle"].Value; - - Style = new CanvasStrokeStyle(); - - Initialize(match); - - // Get the number of non-whitespace characters in the data - ValidationCount = RegexFactory.ValidationRegex.Replace(Data, string.Empty).Length; - } - - /// - /// Initializes the Stroke Element with the given Capture. - /// - /// Match object - public void Initialize(Match match) - { - var group = match.Groups["CanvasStrokeStyle"]; - if (group.Success) - { - // DashStyle - group = match.Groups["DashStyle"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasDashStyle dashStyle); - Style.DashStyle = dashStyle; - } - - // LineJoin - group = match.Groups["LineJoin"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasLineJoin lineJoin); - Style.LineJoin = lineJoin; - } - - // MiterLimit - group = match.Groups["MiterLimit"]; - if (group.Success) - { - float.TryParse(group.Value, out var miterLimit); - - // Sanitize by taking the absolute value - Style.MiterLimit = Math.Abs(miterLimit); - } - - // DashOffset - group = match.Groups["DashOffset"]; - if (group.Success) - { - float.TryParse(group.Value, out var dashOffset); - Style.DashOffset = dashOffset; - } - - // StartCap - group = match.Groups["StartCap"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasCapStyle capStyle); - Style.StartCap = capStyle; - } - - // EndCap - group = match.Groups["EndCap"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasCapStyle capStyle); - Style.EndCap = capStyle; - } - - // DashCap - group = match.Groups["DashCap"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasCapStyle capStyle); - Style.DashCap = capStyle; - } - - // TransformBehavior - group = match.Groups["TransformBehavior"]; - if (group.Success) - { - Enum.TryParse(group.Value, out CanvasStrokeTransformBehavior transformBehavior); - Style.TransformBehavior = transformBehavior; - } - - // CustomDashStyle - group = match.Groups["CustomDashStyle"]; - if (group.Success) - { - List dashes = new List(); - group = match.Groups["Main"]; - if (group.Success) - { - if (float.TryParse(match.Groups["DashSize"].Value, out var dashSize)) - { - // Sanitize by taking the absolute value - dashes.Add(Math.Abs(dashSize)); - } - - if (float.TryParse(match.Groups["SpaceSize"].Value, out var spaceSize)) - { - // Sanitize by taking the absolute value - dashes.Add(Math.Abs(spaceSize)); - } - } - - group = match.Groups["Additional"]; - if (group.Success) - { - foreach (Capture capture in group.Captures) - { - var dashMatch = RegexFactory.CustomDashAttributeRegex.Match(capture.Value); - if (!dashMatch.Success) - { - continue; - } - - if (float.TryParse(dashMatch.Groups["DashSize"].Value, out var dashSize)) - { - // Sanitize by taking the absolute value - dashes.Add(Math.Abs(dashSize)); - } - - if (float.TryParse(dashMatch.Groups["SpaceSize"].Value, out var spaceSize)) - { - // Sanitize by taking the absolute value - dashes.Add(Math.Abs(spaceSize)); - } - } - } - - // Any valid dashes? - if (dashes.Any()) - { - Style.CustomDashStyle = dashes.ToArray(); - } - } - } - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs deleted file mode 100644 index ec5d23ad..00000000 --- a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeElement.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -/// -/// Interface for Stroke Element -/// -internal interface ICanvasStrokeElement -{ - /// - /// Gets the Stroke data defining the Brush Element. - /// - string? Data { get; } - - /// - /// Gets the number of non-whitespace characters in the Stroke Data. - /// - int ValidationCount { get; } - - /// - /// Initializes the Stroke Element with the given Capture. - /// - /// Match object - void Initialize(Match match); - - /// - /// Creates the ICanvasStroke from the parsed data. - /// - /// ICanvasStroke - ICanvasStroke CreateStroke(ICanvasResourceCreator resourceCreator); -} diff --git a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs b/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs deleted file mode 100644 index a4e4c657..00000000 --- a/components/Media/src/Geometry/Elements/Stroke/ICanvasStrokeStyleElement.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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.Geometry; - -namespace CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -/// -/// Interface for the CanvasStrokeStyle Element -/// -internal interface ICanvasStrokeStyleElement -{ - /// - /// Gets the Stroke data defining the Brush Element. - /// - string Data { get; } - - /// - /// Gets the number of non-whitespace characters in the Stroke Data. - /// - int ValidationCount { get; } - - /// - /// Gets the CanvasStrokeStyle obtained by parsing the style data. - /// - CanvasStrokeStyle Style { get; } - - /// - /// Initializes the Stroke Element with the given Capture. - /// - /// Match object - void Initialize(Match match); -} \ No newline at end of file diff --git a/components/Media/src/Geometry/ICanvasStroke.cs b/components/Media/src/Geometry/ICanvasStroke.cs deleted file mode 100644 index 935e1f7c..00000000 --- a/components/Media/src/Geometry/ICanvasStroke.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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.Brushes; -using Microsoft.Graphics.Canvas.Geometry; - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Interface to represent the Stroke which can be used to render an outline on a . -/// -public interface ICanvasStroke -{ - /// - /// Gets or sets the brush with which the will be rendered. - /// - ICanvasBrush Brush { get; set; } - - /// - /// Gets or sets the width of the . - /// - float Width { get; set; } - - /// - /// Gets or sets the Style of the . - /// - CanvasStrokeStyle Style { get; set; } - - /// - /// Gets or sets transform matrix of the brush. - /// - Matrix3x2 Transform { get; set; } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs b/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs deleted file mode 100644 index cbe49706..00000000 --- a/components/Media/src/Geometry/Parsers/CanvasBrushParser.cs +++ /dev/null @@ -1,122 +0,0 @@ -// 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.Brushes; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Elements.Brush; - -namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; - -/// -/// Parser for ICanvasBrush. -/// -internal static class CanvasBrushParser -{ - /// - /// Parses the Brush data string and converts it into ICanvasBrushElement. - /// - /// Brush data - /// ICanvasBrushElement - internal static ICanvasBrushElement? Parse(string brushData) - { - var matches = RegexFactory.CanvasBrushRegex.Matches(brushData); - - // If no match is found or no captures in the match, then it means that the brush data is invalid. - if (matches.Count == 0) - { - ThrowForZeroCount(); - } - - // If the match contains more than one captures, it means that there are multiple brushes present in the brush data. - // There should be only one brush defined in the brush data. - if (matches.Count > 1) - { - ThrowForNotOneCount(); - } - - // There should be only one match - var match = matches[0]; - AbstractCanvasBrushElement? brushElement = null; - if (match.Groups["SolidColorBrush"].Success) - { - brushElement = new SolidColorBrushElement(match.Groups["SolidColorBrush"].Captures[0]); - } - else if (match.Groups["LinearGradient"].Success) - { - brushElement = new LinearGradientBrushElement(match.Groups["LinearGradient"].Captures[0]); - } - else if (match.Groups["LinearGradientHdr"].Success) - { - brushElement = new LinearGradientHdrBrushElement(match.Groups["LinearGradientHdr"].Captures[0]); - } - else if (match.Groups["RadialGradient"].Success) - { - brushElement = new RadialGradientBrushElement(match.Groups["RadialGradient"].Captures[0]); - } - else if (match.Groups["RadialGradientHdr"].Success) - { - brushElement = new RadialGradientHdrBrushElement(match.Groups["RadialGradientHdr"].Captures[0]); - } - - if (brushElement == null) - { - return null; - } - - // Perform validation to check if there are any invalid characters in the brush data that were not captured - var preValidationCount = RegexFactory.ValidationRegex.Replace(brushData, string.Empty).Length; - var postValidationCount = brushElement.ValidationCount; - - // If there are invalid characters, extract them and add them to the ArgumentException message - if (preValidationCount != postValidationCount) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowForInvalidCharacters(AbstractCanvasBrushElement brushElement, string brushData) - { - var parseIndex = 0; - if (!string.IsNullOrWhiteSpace(brushElement.Data)) - { - parseIndex = brushData.IndexOf(brushElement.Data, parseIndex, StringComparison.Ordinal); - } - - var errorString = brushData.Substring(parseIndex); - if (errorString.Length > 30) - { - errorString = $"{errorString.Substring(0, 30)}..."; - } - - throw new ArgumentException($"BRUSH_ERR003:Brush data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); - } - - ThrowForInvalidCharacters(brushElement, brushData); - } - - return brushElement; - - static void ThrowForZeroCount() => throw new ArgumentException("BRUSH_ERR001:Invalid Brush data! No matching brush data found!"); - static void ThrowForNotOneCount() => throw new ArgumentException("BRUSH_ERR002:Multiple Brushes defined in Brush Data! " + - "There should be only one Brush definition within the Brush Data. " + - "You can either remove Brush definitions or split the Brush Data " + - "into multiple Brush Data and call the CanvasPathGeometry.CreateBrush() method on each of them."); - } - - /// - /// Parses the Brush data string and converts it into ICanvasBrush. - /// - /// ICanvasResourceCreator - /// Brush data string - /// ICanvasBrush - internal static ICanvasBrush? Parse(ICanvasResourceCreator resourceCreator, string brushData) - { - // Parse the brush data to get the ICanvasBrushElement - var brushElement = Parse(brushData); - if (brushElement is null) - return null; - - // Create ICanvasBrush from the brushElement - return brushElement.CreateBrush(resourceCreator); - } -} diff --git a/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs b/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs deleted file mode 100644 index 8903c13d..00000000 --- a/components/Media/src/Geometry/Parsers/CanvasGeometryParser.cs +++ /dev/null @@ -1,134 +0,0 @@ -// 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 System.Runtime.CompilerServices; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Elements.Path; - -namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; - -/// -/// Parser for CanvasGeometry. -/// -internal static class CanvasGeometryParser -{ - /// - /// Parses the Path data in string format and converts it to . - /// - /// ICanvasResourceCreator - /// Path data - /// - internal static CanvasGeometry? Parse(ICanvasResourceCreator? resourceCreator, string pathData) - { - var pathFigures = new List(); - - var matches = RegexFactory.CanvasGeometryRegex.Matches(pathData); - - // If no match is found or no captures in the match, then it means // that the path data is invalid. - if (matches.Count == 0) - { - ThrowForZeroCount(); - } - - // If the match contains more than one captures, it means that there are multiple FillRuleElements present in the path data. - // There can be only one FillRuleElement in the path data (at the beginning). - if (matches.Count > 1) - { - ThrowForNotOneCount(); - } - - var figures = new List(); - - foreach (PathFigureType type in Enum.GetValues(typeof(PathFigureType))) - { - foreach (Capture figureCapture in matches[0].Groups[type.ToString()].Captures) - { - var figureRootIndex = figureCapture.Index; - var regex = RegexFactory.GetRegex(type); - default(ArgumentNullException).ThrowIfNull(regex); - - var figureMatch = regex.Match(figureCapture.Value); - if (!figureMatch.Success) - { - continue; - } - - // Process the 'Main' Group which contains the Path Command and - // corresponding attributes - var figure = PathElementFactory.CreatePathFigure(type, figureMatch, figureRootIndex); - figures.Add(figure); - - // Process the 'Additional' Group which contains just the attributes - figures.AddRange(from Capture capture in figureMatch.Groups["Additional"].Captures - select PathElementFactory.CreateAdditionalPathFigure(type, capture, figureRootIndex + capture.Index, figure.IsRelative)); - } - } - - // Sort the figures by their indices - pathFigures.AddRange(figures.OrderBy(f => f.Index)); - if (pathFigures.Count > 0) - { - // Check if the first element in the _figures list is a FillRuleElement - // which would indicate the fill rule to be followed while creating the - // path. If it is not present, then insert a default FillRuleElement at - // the beginning. - if ((pathFigures.ElementAt(0) as FillRuleElement) == null) - { - pathFigures.Insert(0, PathElementFactory.CreateDefaultPathElement(PathFigureType.FillRule)); - } - } - else - { - return null; - } - - // Perform validation to check if there are any invalid characters in the path data that were not captured - var preValidationCount = RegexFactory.ValidationRegex.Replace(pathData, string.Empty).Length; - var postValidationCount = pathFigures.Sum(x => x.ValidationCount); - - // If there are invalid characters, extract them and add them to the ArgumentException message - if (preValidationCount != postValidationCount) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowForInvalidCharacters(List pathFigures, string pathData) - { - var parseIndex = 0; - foreach (var figure in pathFigures) - { - parseIndex = pathData.IndexOf(figure.Data, parseIndex, StringComparison.Ordinal) + figure.Data.Length; - } - - var errorString = pathData.Substring(parseIndex); - if (errorString.Length > 30) - { - errorString = $"{errorString.Substring(0, 30)}..."; - } - - throw new ArgumentException($"PATH_ERR003:Path data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); - } - - ThrowForInvalidCharacters(pathFigures, pathData); - } - - ICanvasPathElement? lastElement = null; - var currentPoint = Vector2.Zero; - - using var pathBuilder = new CanvasPathBuilder(resourceCreator); - foreach (var pathFigure in pathFigures) - { - currentPoint = pathFigure.CreatePath(pathBuilder, currentPoint, ref lastElement); - } - - return CanvasGeometry.CreatePath(pathBuilder); - - static void ThrowForZeroCount() => throw new ArgumentException("PATH_ERR000:Invalid Path data! No matching path data found!"); - static void ThrowForNotOneCount() => throw new ArgumentException("PATH_ERR001:Multiple FillRule elements present in Path Data!\n" + - "There should be only one FillRule within the Path Data. " + - "You can either remove additional FillRule elements or split the Path Data " + - "into multiple Path Data and call the CanvasPathGeometry.CreateGeometry() method on each of them."); - } -} diff --git a/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs b/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs deleted file mode 100644 index 182d4ef2..00000000 --- a/components/Media/src/Geometry/Parsers/CanvasStrokeParser.cs +++ /dev/null @@ -1,96 +0,0 @@ -// 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 CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; - -/// -/// Parser for CanvasStroke -/// -internal static class CanvasStrokeParser -{ - /// - /// Parses the Stroke Data string and converts it into ICanvasStrokeElement. - /// - /// Stroke Data string - /// ICanvasStrokeElement - internal static ICanvasStrokeElement Parse(string strokeData) - { - var matches = RegexFactory.CanvasStrokeRegex.Matches(strokeData); - - // If no match is found or no captures in the match, then it means - // that the stroke data is invalid. - if (matches.Count == 0) - { - ThrowForZeroCount(); - } - - // If the match contains more than one captures, it means that there - // are multiple CanvasStrokes present in the stroke data. There should - // be only one CanvasStroke defined in the stroke data. - if (matches.Count > 1) - { - ThrowForNotOneCount(); - } - - // There should be only one match - var match = matches[0]; - var strokeElement = new CanvasStrokeElement(match); - - // Perform validation to check if there are any invalid characters in the stroke data that were not captured - var preValidationCount = RegexFactory.ValidationRegex.Replace(strokeData, string.Empty).Length; - var postValidationCount = strokeElement.ValidationCount; - - // If there are invalid characters, extract them and add them to the ArgumentException message - if (preValidationCount != postValidationCount) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowForInvalidCharacters(CanvasStrokeElement strokeElement, string strokeData) - { - var parseIndex = 0; - if (!string.IsNullOrWhiteSpace(strokeElement.Data)) - { - parseIndex = strokeData.IndexOf(strokeElement.Data, parseIndex, StringComparison.Ordinal); - } - - var errorString = strokeData.Substring(parseIndex); - if (errorString.Length > 30) - { - errorString = $"{errorString.Substring(0, 30)}..."; - } - - throw new ArgumentException($"STROKE_ERR003:Stroke data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); - } - - ThrowForInvalidCharacters(strokeElement, strokeData); - } - - return strokeElement; - - static void ThrowForZeroCount() => throw new ArgumentException("STROKE_ERR001:Invalid Stroke data! No matching CanvasStroke found!"); - static void ThrowForNotOneCount() => throw new ArgumentException("STROKE_ERR002:Multiple CanvasStrokes defined in Stroke Data! " + - "There should be only one CanvasStroke definition within the Stroke Data. " + - "You can either remove CanvasStroke definitions or split the Stroke Data " + - "into multiple Stroke Data and call the CanvasPathGeometry.CreateStroke() method on each of them."); - } - - /// - /// Parses the Stroke Data string and converts it into CanvasStroke. - /// - /// ICanvasResourceCreator - /// Stroke Data string - /// ICanvasStroke - internal static ICanvasStroke Parse(ICanvasResourceCreator resourceCreator, string strokeData) - { - // Parse the stroke data to create the ICanvasStrokeElement - var strokeElement = Parse(strokeData); - - // Create the CanvasStroke from the strokeElement - return strokeElement.CreateStroke(resourceCreator); - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs b/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs deleted file mode 100644 index b8f436a2..00000000 --- a/components/Media/src/Geometry/Parsers/CanvasStrokeStyleParser.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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.Geometry; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using CommunityToolkit.WinUI.Media.Geometry.Elements.Stroke; - -namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; - -/// -/// Parser for the CanvasStrokeStyle -/// -internal static class CanvasStrokeStyleParser -{ - /// - /// Parses the given style data and converts it to CanvasStrokeStyle. - /// - /// Style data - /// CanvasStrokeStyle - internal static CanvasStrokeStyle Parse(string styleData) - { - var matches = RegexFactory.CanvasStrokeStyleRegex.Matches(styleData); - - // If no match is found or no captures in the match, then it means that the style data is invalid. - if (matches.Count == 0) - { - ThrowForZeroCount(); - } - - // If the match contains more than one captures, it means that there - // are multiple CanvasStrokeStyles present in the CanvasStrokeStyle data. There should - // be only one CanvasStrokeStyle defined in the CanvasStrokeStyle data. - if (matches.Count > 1) - { - ThrowForNotOneCount(); - } - - // There should be only one match - var match = matches[0]; - var styleElement = new CanvasStrokeStyleElement(match); - - // Perform validation to check if there are any invalid characters in the brush data that were not captured - var preValidationCount = RegexFactory.ValidationRegex.Replace(styleData, string.Empty).Length; - var postValidationCount = styleElement.ValidationCount; - - // If there are invalid characters, extract them and add them to the ArgumentException message - if (preValidationCount != postValidationCount) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowForInvalidCharacters(CanvasStrokeStyleElement styleElement, string styleData) - { - var parseIndex = 0; - if (!string.IsNullOrWhiteSpace(styleElement.Data)) - { - parseIndex = styleData.IndexOf(styleElement.Data, parseIndex, StringComparison.Ordinal); - } - - var errorString = styleData.Substring(parseIndex); - if (errorString.Length > 30) - { - errorString = $"{errorString.Substring(0, 30)}..."; - } - - throw new ArgumentException($"STYLE_ERR003:Style data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); - } - - ThrowForInvalidCharacters(styleElement, styleData); - } - - return styleElement.Style; - - static void ThrowForZeroCount() => throw new ArgumentException("STYLE_ERR001:Invalid CanvasStrokeStyle data! No matching CanvasStrokeStyle found!"); - static void ThrowForNotOneCount() => throw new ArgumentException("STYLE_ERR002:Multiple CanvasStrokeStyles defined in CanvasStrokeStyle Data! " + - "There should be only one CanvasStrokeStyle definition within the CanvasStrokeStyle Data. " + - "You can either remove CanvasStrokeStyle definitions or split the CanvasStrokeStyle Data " + - "into multiple CanvasStrokeStyle Data and call the CanvasPathGeometry.CreateStrokeStyle() method on each of them."); - } - - /// - /// Parses and constructs a ICanvasStrokeStyleElement from the specified Match object. - /// - /// Match object - /// ICanvasStrokeStyleElement - internal static ICanvasStrokeStyleElement Parse(Match match) - { - return new CanvasStrokeStyleElement(match); - } -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Parsers/ColorParser.cs b/components/Media/src/Geometry/Parsers/ColorParser.cs deleted file mode 100644 index 2fe96561..00000000 --- a/components/Media/src/Geometry/Parsers/ColorParser.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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 System.Runtime.CompilerServices; -using CommunityToolkit.WinUI.Media.Geometry.Core; -using Windows.UI; - -#if WINUI3 -using Colors = Microsoft.UI.Colors; -#endif - -namespace CommunityToolkit.WinUI.Media.Geometry.Parsers; - -/// -/// Parser for Color -/// -internal static class ColorParser -{ - /// - /// Converts the color string in Hexadecimal or HDR color format to the corresponding Color object. - /// The hexadecimal color string should be in #RRGGBB or #AARRGGBB format. - /// The '#' character is optional. - /// The HDR color string should be in R G B A format. - /// (R, G, B & A should have value in the range between 0 and 1, inclusive) - /// - /// Color string in Hexadecimal or HDR format - /// Color - internal static Color Parse(string colorString) - { - var match = RegexFactory.ColorRegex.Match(colorString); - - if (!match.Success) - { - ThrowArgumentException(); - } - - // Perform validation to check if there are any invalid characters in the colorString that were not captured - var preValidationCount = RegexFactory.ValidationRegex.Replace(colorString, string.Empty).Length; - var postValidationCount = RegexFactory.ValidationRegex.Replace(match.Value, string.Empty).Length; - - // If there are invalid characters, extract them and add them to the ArgumentException message - if (preValidationCount != postValidationCount) - { - [MethodImpl(MethodImplOptions.NoInlining)] - static void ThrowForInvalidCharacters(Match match, string colorString) - { - var parseIndex = 0; - if (!string.IsNullOrWhiteSpace(match.Value)) - { - parseIndex = colorString.IndexOf(match.Value, parseIndex, StringComparison.Ordinal); - } - - var errorString = colorString.Substring(parseIndex); - if (errorString.Length > 30) - { - errorString = $"{errorString.Substring(0, 30)}..."; - } - - throw new ArgumentException($"COLOR_ERR003:Color data contains invalid characters!\nIndex: {parseIndex}\n{errorString}"); - } - - ThrowForInvalidCharacters(match, colorString); - } - - return Parse(match); - - static void ThrowArgumentException() => throw new ArgumentException("COLOR_ERR001:Invalid value provided in Color Data! No matching color found in the Color Data."); - } - - /// - /// Converts a Vector4 High Dynamic Range Color to Color. Negative components of the Vector4 will be sanitized by taking the absolute value of the component. - /// The HDR Color components should have value in the range between 0 and 1, inclusive. If they are more than 1, they will be clamped at 1. - /// - /// High Dynamic Range Color - /// Color - internal static Color Parse(Vector4 hdrColor) - { - // Vector4's X, Y, Z, W components match to - // Color's R, G, B, A components respectively - return Parse(hdrColor.X, hdrColor.Y, hdrColor.Z, hdrColor.W); - } - - /// - /// Converts the given HDR color values to Color. - /// - /// Red Component - /// Green Component - /// Blue Component - /// Alpha Component - /// Instance of Color. - internal static Color Parse(float x, float y, float z, float w) - { - Vector4 v4 = new Vector4(x, y, z, w); - v4 = Vector4.Min(Vector4.Abs(v4) * 255f, new Vector4(255f)); - - var r = (byte)v4.X; - var g = (byte)v4.Y; - var b = (byte)v4.Z; - var a = (byte)v4.W; - - return Color.FromArgb(a, r, g, b); - } - - /// - /// Parses and constructs a Color object from the specified Match object. - /// - /// Match object - /// Color - internal static Color Parse(Match match) - { - if (match.Groups["RgbColor"].Success) - { - // Alpha component - byte alpha = 255; - var alphaStr = match.Groups["Alpha"].Value; - if (!string.IsNullOrWhiteSpace(alphaStr)) - { - alpha = (byte)Convert.ToInt32(alphaStr, 16); - } - - // Red component - var red = (byte)Convert.ToInt32(match.Groups["Red"].Value, 16); - - // Green component - var green = (byte)Convert.ToInt32(match.Groups["Green"].Value, 16); - - // Blue component - var blue = (byte)Convert.ToInt32(match.Groups["Blue"].Value, 16); - - return Color.FromArgb(alpha, red, green, blue); - } - - if (match.Groups["HdrColor"].Success) - { - float.TryParse(match.Groups["X"].Value, out var x); - float.TryParse(match.Groups["Y"].Value, out var y); - float.TryParse(match.Groups["Z"].Value, out var z); - float.TryParse(match.Groups["W"].Value, out var w); - - return Parse(x, y, z, w); - } - - return Colors.Transparent; - } -} diff --git a/components/Media/src/Geometry/Scalar.cs b/components/Media/src/Geometry/Scalar.cs deleted file mode 100644 index 6317a279..00000000 --- a/components/Media/src/Geometry/Scalar.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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.Geometry; - -/// -/// Class containing some constants -/// represented as Floating point numbers. -/// -internal static class Scalar -{ - // Pi related floating point constants - - /// - /// (float)Math.PI radians ( or 180 degrees). - /// - internal const float Pi = (float)Math.PI; - - /// - /// Two times (float)Math.PI radians ( or 360 degrees). - /// - internal const float TwoPi = 2f * Pi; - - /// - /// Half of (float)Math.PI radians ( or 90 degrees). - /// - internal const float PiByTwo = Pi / 2f; - - /// - /// One third of (float)Math.PI radians ( or 60 degrees). - /// - internal const float PiByThree = Pi / 3f; - - /// - /// One fourth of (float)Math.PI radians ( or 45 degrees). - /// - internal const float PiByFour = Pi / 4f; - - /// - /// One sixth of (float)Math.PI radians ( or 30 degrees). - /// - internal const float PiBySix = Pi / 6f; - - /// - /// Three times half of (float)Math.PI radians ( or 270 degrees). - /// - internal const float ThreePiByTwo = 3f * Pi / 2f; - - // Conversion constants - - /// - /// 1 degree in radians. - /// - internal const float DegreesToRadians = Pi / 180f; - - /// - /// 1 radian in degrees. - /// - internal const float RadiansToDegrees = 180f / Pi; -} \ No newline at end of file diff --git a/components/Media/src/Geometry/Utils.cs b/components/Media/src/Geometry/Utils.cs deleted file mode 100644 index 884b0ff6..00000000 --- a/components/Media/src/Geometry/Utils.cs +++ /dev/null @@ -1,815 +0,0 @@ -// 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 Colors = Microsoft.UI.Colors; -#endif - -namespace CommunityToolkit.WinUI.Media.Geometry; - -/// -/// Class containing collection of useful methods for various types -/// -public static class Utils -{ - // Constant values - - // Smallest double value such that 1.0 + DoubleEpsilon != 1.0 - internal const double DoubleEpsilon = 2.2250738585072014E-308; - - // Smallest float value such that 1.0f + FloatMin != 1.0f - internal const float FloatMin = 1.175494351E-38F; - - /// - /// Returns whether or not two doubles are "close". - /// - /// The first double to compare. - /// The second double to compare. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsCloseTo(this double value1, double value2) - { - // In case they are Infinities or NaN (then epsilon check does not work) - if ((double.IsInfinity(value1) && - double.IsInfinity(value2)) || - (double.IsNaN(value1) && double.IsNaN(value2))) - { - return true; - } - - // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < DoubleEpsilon - var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0) * DoubleEpsilon; - var delta = value1 - value2; - return (-eps < delta) && (eps > delta); - } - - /// - /// Returns whether or not the first double is less than the second double. - /// - /// The first double to compare. - /// The second double to compare. - /// - /// bool - the result of the LessThan comparison. - /// - public static bool IsLessThan(this double value1, double value2) - { - return (value1 < value2) && !value1.IsCloseTo(value2); - } - - /// - /// Returns whether or not the first double is greater than the second double. - /// - /// The first double to compare. - /// The second double to compare. - /// - /// bool - the result of the GreaterThan comparison. - /// - public static bool IsGreaterThan(this double value1, double value2) - { - return (value1 > value2) && !value1.IsCloseTo(value2); - } - - /// - /// Returns whether or not the double is "close" to 1. Same as AreClose(double, 1), - /// but this is faster. - /// - /// The double to compare to 1. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsOne(this double value) - { - return Math.Abs(value - 1.0d) < 10.0d * DoubleEpsilon; - } - - /// - /// IsZero - Returns whether or not the double is "close" to 0. Same as AreClose(double, 0), - /// but this is faster. - /// - /// The double to compare to 0. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsZero(this double value) - { - return Math.Abs(value) < 10.0d * DoubleEpsilon; - } - - /// - /// Returns whether or not two floats are "close". - /// - /// The first float to compare. - /// The second float to compare. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsCloseTo(this float value1, float value2) - { - // In case they are Infinities or NaN (then epsilon check does not work) - if ((float.IsInfinity(value1) && - float.IsInfinity(value2)) || - (float.IsNaN(value1) && float.IsNaN(value2))) - { - return true; - } - - // This computes (|value1-value2| / (|value1| + |value2| + 10.0)) < FloatMin - var eps = (Math.Abs(value1) + Math.Abs(value2) + 10.0f) * FloatMin; - var delta = value1 - value2; - return (-eps < delta) && (eps > delta); - } - - /// - /// Returns whether or not the first float is less than the second float. - /// - /// The first float to compare. - /// The second float to compare. - /// - /// bool - the result of the LessThan comparison. - /// - public static bool IsLessThan(this float value1, float value2) - { - return (value1 < value2) && !value1.IsCloseTo(value2); - } - - /// - /// Returns whether or not the first float is greater than the second float. - /// - /// The first float to compare. - /// The second float to compare. - /// - /// bool - the result of the GreaterThan comparison. - /// - public static bool IsGreaterThan(this float value1, float value2) - { - return (value1 > value2) && !value1.IsCloseTo(value2); - } - - /// - /// Returns whether or not the float is "close" to 1. Same as AreClose(float, 1), - /// but this is faster. - /// - /// The float to compare to 1. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsOne(this float value) - { - return Math.Abs(value - 1.0f) < 10.0f * FloatMin; - } - - /// - /// IsZero - Returns whether or not the float is "close" to 0. Same as AreClose(float, 0), - /// but this is faster. - /// - /// The float to compare to 0. - /// - /// bool - the result of the AreClose comparison. - /// - public static bool IsZero(this float value) - { - return Math.Abs(value) < 10.0f * FloatMin; - } - - /// - /// Compares two points for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first point to compare - /// The second point to compare - /// Whether or not the two points are equal - public static bool IsCloseTo(this Point point1, Point point2) - { - return point1.X.IsCloseTo(point2.X) && point1.Y.IsCloseTo(point2.Y); - } - - /// - /// Compares two Size instances for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first size to compare - /// The second size to compare - /// Whether or not the two Size instances are equal - public static bool IsCloseTo(this Size size1, Size size2) - { - return size1.Width.IsCloseTo(size2.Width) && size1.Height.IsCloseTo(size2.Height); - } - - /// - /// Compares two rectangles for fuzzy equality. This function - /// helps compensate for the fact that double values can - /// acquire error when operated upon - /// - /// The first rectangle to compare - /// The second rectangle to compare - /// Whether or not the two rectangles are equal - public static bool IsCloseTo(this Rect rect1, Rect rect2) - { - // If they're both empty, don't bother with the double logic. - if (rect1.IsEmpty) - { - return rect2.IsEmpty; - } - - // At this point, rect1 isn't empty, so the first thing we can test is rect2.IsEmpty, followed by property-wise compares. - return (!rect2.IsEmpty) - && rect1.X.IsCloseTo(rect2.X) && rect1.Y.IsCloseTo(rect2.Y) - && rect1.Height.IsCloseTo(rect2.Height) && rect1.Width.IsCloseTo(rect2.Width); - } - - /// - /// Rounds the given value based on the DPI scale - /// - /// Value to round - /// DPI Scale - /// The rounded value - public static double RoundLayoutValue(double value, double dpiScale) - { - double newValue; - - // If DPI == 1, don't use DPI-aware rounding. - if (!dpiScale.IsCloseTo(1.0)) - { - newValue = Math.Round(value * dpiScale) / dpiScale; - - // If rounding produces a value unacceptable to layout (NaN, Infinity or MaxValue), use the original value. - if (double.IsNaN(newValue) || - double.IsInfinity(newValue) || - newValue.IsCloseTo(double.MaxValue)) - { - newValue = value; - } - } - else - { - newValue = Math.Round(value); - } - - return newValue; - } - - /// - /// Calculates the linear interpolated value based on the given values. - /// - /// Starting value. - /// Ending value. - /// Weight-age given to the ending value. - /// Linear interpolated value. - public static float Lerp(this float start, float end, float amount) - { - return start + ((end - start) * amount); - } - - /// - /// Verifies if this Thickness contains only valid values. The set of validity checks is passed as parameters. - /// - /// Thickness value - /// allows negative values - /// allows double.NaN - /// allows double.PositiveInfinity - /// allows double.NegativeInfinity - /// Whether or not the thickness complies to the range specified - public static bool IsValid(this Thickness thick, bool allowNegative, bool allowNaN, bool allowPositiveInfinity, bool allowNegativeInfinity) - { - if (!allowNegative) - { - if (thick.Left < 0d || thick.Right < 0d || thick.Top < 0d || thick.Bottom < 0d) - { - return false; - } - } - - if (!allowNaN) - { - if (double.IsNaN(thick.Left) || double.IsNaN(thick.Right) - || double.IsNaN(thick.Top) || double.IsNaN(thick.Bottom)) - { - return false; - } - } - - if (!allowPositiveInfinity) - { - if (double.IsPositiveInfinity(thick.Left) || double.IsPositiveInfinity(thick.Right) - || double.IsPositiveInfinity(thick.Top) || double.IsPositiveInfinity(thick.Bottom)) - { - return false; - } - } - - if (!allowNegativeInfinity) - { - if (double.IsNegativeInfinity(thick.Left) || double.IsNegativeInfinity(thick.Right) - || double.IsNegativeInfinity(thick.Top) || double.IsNegativeInfinity(thick.Bottom)) - { - return false; - } - } - - return true; - } - - /// - /// Method to add up the left and right size as width, as well as the top and bottom size as height. - /// - /// Thickness - /// Size - public static Size CollapseThickness(this Thickness thick) - { - return new Size(thick.Left + thick.Right, thick.Top + thick.Bottom); - } - - /// - /// Verifies if the Thickness contains only zero values. - /// - /// Thickness - /// Size - public static bool IsZero(this Thickness thick) - { - return thick.Left.IsZero() - && thick.Top.IsZero() - && thick.Right.IsZero() - && thick.Bottom.IsZero(); - } - - /// - /// Verifies if all the values in Thickness are same. - /// - /// Thickness - /// true if yes, otherwise false - public static bool IsUniform(this Thickness thick) - { - return thick.Left.IsCloseTo(thick.Top) - && thick.Left.IsCloseTo(thick.Right) - && thick.Left.IsCloseTo(thick.Bottom); - } - - /// - /// Converts the Thickness object to Vector4. If the Thickness object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. - /// - /// Thickness object - /// Vector4 - public static Vector4 ToVector4(this Thickness thickness) - { - if (thickness.IsValid(true, false, false, false)) - { - // Sanitize the component by taking only - return new Vector4( - (float)thickness.Left, - (float)thickness.Top, - (float)thickness.Right, - (float)thickness.Bottom); - } - - return Vector4.Zero; - } - - /// - /// Converts the Thickness object to Vector4. If the Thickness object contains negative components they will be converted to positive values. If the Thickness object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. - /// - /// Thickness object - /// Vector2 - public static Vector4 ToAbsVector4(this Thickness thickness) - { - if (thickness.IsValid(true, false, false, false)) - { - // Sanitize the component by taking only - return new Vector4( - Math.Abs((float)thickness.Left), - Math.Abs((float)thickness.Top), - Math.Abs((float)thickness.Right), - Math.Abs((float)thickness.Bottom)); - } - - return Vector4.Zero; - } - - /// - /// Gets the top left corner of the thickness structure. - /// - /// Thickness object - /// Vector2 - public static Vector2 GetOffset(this Thickness thickness) - { - return new Vector2((float)thickness.Left, (float)thickness.Top); - } - - /// - /// Verifies if the CornerRadius contains only zero values. - /// - /// CornerRadius - /// true if yes, otherwise false - public static bool IsZero(this CornerRadius corner) - { - return corner.TopLeft.IsZero() - && corner.TopRight.IsZero() - && corner.BottomRight.IsZero() - && corner.BottomLeft.IsZero(); - } - - /// - /// Verifies if the CornerRadius contains same values. - /// - /// CornerRadius - /// true if yes, otherwise false - public static bool IsUniform(this CornerRadius corner) - { - var topLeft = corner.TopLeft; - return topLeft.IsCloseTo(corner.TopRight) && - topLeft.IsCloseTo(corner.BottomLeft) && - topLeft.IsCloseTo(corner.BottomRight); - } - - /// - /// Converts the given corner value to a valid positive value. Returns zero if the corner value is Infinity or NaN or 0. - /// - /// Corner value - /// Valid Corner value - public static double ConvertToValidCornerValue(double corner) - { - if (double.IsNaN(corner) || - double.IsInfinity(corner) || - (corner < 0d)) - { - return 0d; - } - - return corner; - } - - /// - /// Converts the CornerRadius object to Vector4. If the CornerRadius object's component have values NaN, PositiveInfinity or NegativeInfinity, then Vector4.Zero will be returned. - /// - /// CornerRadius object - /// Vector4 - public static Vector4 ToVector4(this CornerRadius corner) - { - return new Vector4( - (float)corner.TopLeft, - (float)corner.TopRight, - (float)corner.BottomRight, - (float)corner.BottomLeft); - } - - /// - /// Deflates rectangle by given thickness. - /// - /// Rectangle - /// Thickness - /// Deflated Rectangle - public static Rect Deflate(this Rect rect, Thickness thick) - { - return new Rect( - rect.Left + thick.Left, - rect.Top + thick.Top, - Math.Max(0.0, rect.Width - thick.Left - thick.Right), - Math.Max(0.0, rect.Height - thick.Top - thick.Bottom)); - } - - /// - /// Inflates rectangle by given thickness. - /// - /// Rectangle - /// Thickness - /// Inflated Rectangle - public static Rect Inflate(this Rect rect, Thickness thick) - { - return new Rect( - rect.Left - thick.Left, - rect.Top - thick.Top, - Math.Max(0.0, rect.Width + thick.Left + thick.Right), - Math.Max(0.0, rect.Height + thick.Top + thick.Bottom)); - } - - /// - /// Verifies if the given brush is a SolidColorBrush and its color does not include transparency. - /// - /// Brush - /// true if yes, otherwise false - public static bool IsOpaqueSolidColorBrush(this Brush brush) - { - return (brush as SolidColorBrush)?.Color.A == 0xff; - } - - /// - /// Verifies if the given brush is the same as the otherBrush. - /// - /// Given - /// The to match it with - /// true if yes, otherwise false - public static bool IsEqualTo(this Brush brush, Brush otherBrush) - { - if (brush.GetType() != otherBrush.GetType()) - { - return false; - } - - if (ReferenceEquals(brush, otherBrush)) - { - return true; - } - - // Are both instances of SolidColorBrush - if ((brush is SolidColorBrush solidBrushA) && (otherBrush is SolidColorBrush solidBrushB)) - { - return (solidBrushA.Color == solidBrushB.Color) - && solidBrushA.Opacity.IsCloseTo(solidBrushB.Opacity); - } - - // Are both instances of LinearGradientBrush - if ((brush is LinearGradientBrush linGradBrushA) && (otherBrush is LinearGradientBrush linGradBrushB)) - { - var result = (linGradBrushA.ColorInterpolationMode == linGradBrushB.ColorInterpolationMode) - && (linGradBrushA.EndPoint == linGradBrushB.EndPoint) - && (linGradBrushA.MappingMode == linGradBrushB.MappingMode) - && linGradBrushA.Opacity.IsCloseTo(linGradBrushB.Opacity) - && (linGradBrushA.StartPoint == linGradBrushB.StartPoint) - && (linGradBrushA.SpreadMethod == linGradBrushB.SpreadMethod) - && (linGradBrushA.GradientStops.Count == linGradBrushB.GradientStops.Count); - if (!result) - { - return false; - } - - for (var i = 0; i < linGradBrushA.GradientStops.Count; i++) - { - result = (linGradBrushA.GradientStops[i].Color == linGradBrushB.GradientStops[i].Color) - && linGradBrushA.GradientStops[i].Offset.IsCloseTo(linGradBrushB.GradientStops[i].Offset); - - if (!result) - { - break; - } - } - - return result; - } - - // Are both instances of ImageBrush - if ((brush is ImageBrush imgBrushA) && (otherBrush is ImageBrush imgBrushB)) - { - var result = (imgBrushA.AlignmentX == imgBrushB.AlignmentX) - && (imgBrushA.AlignmentY == imgBrushB.AlignmentY) - && imgBrushA.Opacity.IsCloseTo(imgBrushB.Opacity) - && (imgBrushA.Stretch == imgBrushB.Stretch) - && (imgBrushA.ImageSource == imgBrushB.ImageSource); - - return result; - } - - return false; - } - - /// - /// Compares one URI with another URI. - /// - /// URI to compare with - /// URI to compare - /// true if yes, otherwise false - public static bool IsEqualTo(this Uri uri, Uri otherUri) - { - return - Uri.Compare(uri, otherUri, UriComponents.AbsoluteUri, UriFormat.SafeUnescaped, StringComparison.OrdinalIgnoreCase) == 0; - } - - /// - /// Reflects point 'a' over point 'b'. - /// - /// Point to be reflected - /// Point of reflection - /// Reflected point - public static Vector2 Reflect(Vector2 a, Vector2 b) - { - // Let 'c' be the reflected point. Then point 'b' - // becomes the middle point between 'a' and 'c'. - // As per MidPoint formula, - // b.X = (a.X + c.X) / 2 and - // b.Y = (a.Y + c.Y) / 2 - // Therefore, c.X = 2 * b.X - a.X - // c.y = 2 * b.Y - a.Y - return new Vector2((2f * b.X) - a.X, (2f * b.Y) - a.Y); - } - - /// - /// Converts a Vector2 structure (x,y) to Vector3 structure (x, y, 0). - /// - /// Input Vector2 - /// Vector3 - public static Vector3 ToVector3(this Vector2 v) - { - return new Vector3(v, 0); - } - - /// - /// Verifies if the Vector4 contains only zero values. - /// - /// Vector4 - /// true if yes, otherwise false - public static bool IsZero(this Vector4 vector) - { - return vector.X.IsZero() - && vector.Y.IsZero() - && vector.Z.IsZero() - && vector.W.IsZero(); - } - - /// - /// Useful in converting the four components of Thickness or Padding to two components by taking a sum of alternate components (X & Z and Y & W). - /// - /// Vector4 - /// Vector3 - public static Vector2 Collapse(this Vector4 vector) - { - return new Vector2(vector.X + vector.Z, vector.Y + vector.W); - } - - /// - /// Useful in converting the four components of Thickness or Padding to two components by adding alternate components - (X & Z and Y & W). - /// - /// Vector4 - /// Size - public static Size ToSize(this Vector4 vector) - { - return new Size(vector.X + vector.Z, vector.Y + vector.W); - } - - /// - /// Converts the Vector4 to Thickness - Left(X), Top(Y), Right(Z), Bottom(W). - /// - /// Vector4 - /// Thickness - public static Thickness ToThickness(this Vector4 vector) - { - return new Thickness(vector.X, vector.Y, vector.Z, vector.W); - } - - /// - /// Converts the Vector4 to CornerRadius - TopLeft(X), TopRight(Y), BottomRight(Z), BottomLeft(W). - /// - /// Vector4 - /// CornerRadius - public static CornerRadius ToCornerRadius(this Vector4 vector) - { - return new CornerRadius(vector.X, vector.Y, vector.Z, vector.W); - } - - /// - /// Calculates the linear interpolated Color based on the given Color values. - /// - /// Source Color. - /// Target Color. - /// Weightage given to the target color. - /// Linear Interpolated Color. - public static Color Lerp(this Color colorFrom, Color colorTo, float amount) - { - // Convert colorFrom components to lerp-able floats - float sa = colorFrom.A, - sr = colorFrom.R, - sg = colorFrom.G, - sb = colorFrom.B; - - // Convert colorTo components to lerp-able floats - float ea = colorTo.A, - er = colorTo.R, - eg = colorTo.G, - eb = colorTo.B; - - // lerp the colors to get the difference - byte a = (byte)Math.Max(0, Math.Min(255, sa.Lerp(ea, amount))), - r = (byte)Math.Max(0, Math.Min(255, sr.Lerp(er, amount))), - g = (byte)Math.Max(0, Math.Min(255, sg.Lerp(eg, amount))), - b = (byte)Math.Max(0, Math.Min(255, sb.Lerp(eb, amount))); - - // return the new color - return Color.FromArgb(a, r, g, b); - } - - /// - /// Darkens the color by the given percentage. - /// - /// Source color. - /// Percentage to darken. Value should be between 0 and 1. - /// Color - public static Color DarkerBy(this Color color, float amount) - { - return color.Lerp(Colors.Black, amount); - } - - /// - /// Lightens the color by the given percentage. - /// - /// Source color. - /// Percentage to lighten. Value should be between 0 and 1. - /// Color - public static Color LighterBy(this Color color, float amount) - { - return color.Lerp(Colors.White, amount); - } - - /// - /// Converts the Point structure P (X,Y) to Vector3 structure - /// V (P.X, P.Y, 0); - /// - /// Point structure - /// Vector3 - public static Vector3 ToVector3(this Point p) - { - return new Vector3((float)p.X, (float)p.Y, 0f); - } - - /// - /// Calculates the best size that can fit in the destination area based on the given stretch and alignment options. - /// - /// Width of the source. - /// Height of the source. - /// Width of the destination area. - /// Height of the destination area. - /// Defines how the source should stretch to fit the destination. - /// Horizontal Alignment - /// Vertical Alignment - /// The best fitting Rectangle in the destination area. - public static Rect GetOptimumSize(double srcWidth, double srcHeight, double destWidth, double destHeight, Stretch stretch, AlignmentX horizontalAlignment, AlignmentY verticalAlignment) - { - var ratio = srcWidth / srcHeight; - var targetWidth = 0d; - var targetHeight = 0d; - - // Stretch Mode - switch (stretch) - { - case Stretch.None: - targetWidth = srcWidth; - targetHeight = srcHeight; - break; - case Stretch.Fill: - targetWidth = destWidth; - targetHeight = destHeight; - break; - case Stretch.Uniform: - // If width is greater than height - if (ratio > 1.0) - { - targetHeight = Math.Min(destWidth / ratio, destHeight); - targetWidth = targetHeight * ratio; - } - else - { - targetWidth = Math.Min(destHeight * ratio, destWidth); - targetHeight = targetWidth / ratio; - } - - break; - case Stretch.UniformToFill: - // If width is greater than height - if (ratio > 1.0) - { - targetHeight = Math.Max(destWidth / ratio, destHeight); - targetWidth = targetHeight * ratio; - } - else - { - targetWidth = Math.Max(destHeight * ratio, destWidth); - targetHeight = targetWidth / ratio; - } - - break; - } - - var left = 0d; - switch (horizontalAlignment) - { - case AlignmentX.Left: - left = 0; - break; - case AlignmentX.Center: - left = (destWidth - targetWidth) / 2.0; - break; - case AlignmentX.Right: - left = destWidth - targetWidth; - break; - } - - var top = 0d; - switch (verticalAlignment) - { - case AlignmentY.Top: - top = 0; - break; - case AlignmentY.Center: - top = (destHeight - targetHeight) / 2.0; - break; - case AlignmentY.Bottom: - top = destHeight - targetHeight; - break; - } - - return new Rect(left, top, targetWidth, targetHeight); - } -} From 47c8133dc71298ec743b60c2a6545034dc68aa81 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 6 Jul 2023 14:00:00 -0500 Subject: [PATCH 03/13] Add missing file header --- components/Animations/src/ArgumentNullExceptionExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/Animations/src/ArgumentNullExceptionExtensions.cs b/components/Animations/src/ArgumentNullExceptionExtensions.cs index 640cc2ec..545654bf 100644 --- a/components/Animations/src/ArgumentNullExceptionExtensions.cs +++ b/components/Animations/src/ArgumentNullExceptionExtensions.cs @@ -1,3 +1,7 @@ +// 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; From b3fc0d2a1d00dae74baf16f0f6bca83448a6bc99 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 6 Jul 2023 14:31:47 -0500 Subject: [PATCH 04/13] Updated tooling submodule, suppress CS0067 --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index b32c3896..53f49b7c 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit b32c38965894ba107b93b7a0ae20107ba6adb040 +Subproject commit 53f49b7c1a6662668f828ee1980dc5c618a1db10 From e44bb87494f875503226e076884137fd9bafb517 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 6 Jul 2023 14:32:40 -0500 Subject: [PATCH 05/13] Removed RadialGradientBrush --- .../Brushes/RadialGradientBrush.Properties.cs | 131 ------------------ .../Media/src/Brushes/RadialGradientBrush.cs | 103 -------------- .../src/Brushes/RadialGradientBrushInterop.cs | 95 ------------- 3 files changed, 329 deletions(-) delete mode 100644 components/Media/src/Brushes/RadialGradientBrush.Properties.cs delete mode 100644 components/Media/src/Brushes/RadialGradientBrush.cs delete mode 100644 components/Media/src/Brushes/RadialGradientBrushInterop.cs diff --git a/components/Media/src/Brushes/RadialGradientBrush.Properties.cs b/components/Media/src/Brushes/RadialGradientBrush.Properties.cs deleted file mode 100644 index 69a61c74..00000000 --- a/components/Media/src/Brushes/RadialGradientBrush.Properties.cs +++ /dev/null @@ -1,131 +0,0 @@ -// 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; - -/// -/// Properties for the -/// -public partial class RadialGradientBrush -{ - /// - /// Gets or sets a enumeration that specifies the way in which an alpha channel affects color channels. The default is for compatibility with WPF. - /// - public AlphaMode AlphaMode - { - get => (AlphaMode)GetValue(AlphaModeProperty); - set => SetValue(AlphaModeProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty AlphaModeProperty = - DependencyProperty.Register(nameof(AlphaMode), typeof(AlphaMode), typeof(RadialGradientBrush), new PropertyMetadata(AlphaMode.Straight, new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets a enumeration that specifies how the gradient's colors are interpolated. The default is . - /// - public ColorInterpolationMode ColorInterpolationMode - { - get => (ColorInterpolationMode)GetValue(ColorInterpolationModeProperty); - set => SetValue(ColorInterpolationModeProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ColorInterpolationModeProperty = - DependencyProperty.Register(nameof(ColorInterpolationMode), typeof(ColorInterpolationMode), typeof(RadialGradientBrush), new PropertyMetadata(ColorInterpolationMode.SRgbLinearInterpolation, new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the brush's gradient stops. - /// - public GradientStopCollection GradientStops - { - get => (GradientStopCollection)GetValue(GradientStopsProperty); - set => SetValue(GradientStopsProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty GradientStopsProperty = - DependencyProperty.Register(nameof(GradientStops), typeof(GradientStopCollection), typeof(RadialGradientBrush), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the center of the outermost circle of the radial gradient. The default is 0.5,0.5. - /// - public Point Center - { - get => (Point)GetValue(CenterProperty); - set => SetValue(CenterProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty CenterProperty = - DependencyProperty.Register(nameof(Center), typeof(Point), typeof(RadialGradientBrush), new PropertyMetadata(new Point(0.5, 0.5), new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the location of the two-dimensional focal point that defines the beginning of the gradient. The default is 0.5,0.5. - /// - public Point GradientOrigin - { - get => (Point)GetValue(GradientOriginProperty); - set => SetValue(GradientOriginProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty GradientOriginProperty = - DependencyProperty.Register(nameof(GradientOrigin), typeof(Point), typeof(RadialGradientBrush), new PropertyMetadata(new Point(0.5, 0.5), new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the horizontal radius of the outermost circle of the radial gradient. The default is 0.5. - /// - public double RadiusX - { - get => (double)GetValue(RadiusXProperty); - set => SetValue(RadiusXProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty RadiusXProperty = - DependencyProperty.Register(nameof(RadiusX), typeof(double), typeof(RadialGradientBrush), new PropertyMetadata(0.5, new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the vertical radius of the outermost circle of the radial gradient. The default is 0.5. - /// - public double RadiusY - { - get => (double)GetValue(RadiusYProperty); - set => SetValue(RadiusYProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty RadiusYProperty = - DependencyProperty.Register(nameof(RadiusY), typeof(double), typeof(RadialGradientBrush), new PropertyMetadata(0.5, new PropertyChangedCallback(OnPropertyChanged))); - - /// - /// Gets or sets the type of spread method that specifies how to draw a gradient that starts or ends inside the bounds of the object to be painted. - /// - public GradientSpreadMethod SpreadMethod - { - get => (GradientSpreadMethod)GetValue(SpreadMethodProperty); - set => SetValue(SpreadMethodProperty, value); - } - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty SpreadMethodProperty = - DependencyProperty.Register(nameof(SpreadMethod), typeof(GradientSpreadMethod), typeof(RadialGradientBrush), new PropertyMetadata(GradientSpreadMethod.Pad, new PropertyChangedCallback(OnPropertyChanged))); -} \ No newline at end of file diff --git a/components/Media/src/Brushes/RadialGradientBrush.cs b/components/Media/src/Brushes/RadialGradientBrush.cs deleted file mode 100644 index 83f3cd26..00000000 --- a/components/Media/src/Brushes/RadialGradientBrush.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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. - -//// UWP Replacement for WPF RadialGradientBrush: https://msdn.microsoft.com/en-us/library/system.windows.media.radialgradientbrush(v=vs.110).aspx. - -using System.Numerics; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Brushes; -using Windows.UI; - -namespace CommunityToolkit.WinUI.Media; - -/// -/// RadialGradientBrush - This GradientBrush defines its Gradient as an interpolation -/// within an Ellipse. -/// -[Obsolete("Please migrate to the RadialGradientBrush control from WinUI, this control will be removed in a future release. https://aka.ms/winui")] -[ContentProperty(Name = nameof(GradientStops))] -public partial class RadialGradientBrush : CanvasBrushBase -{ - private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - var brush = (RadialGradientBrush)d; - - // We need to recreate the brush on any property change. - brush.OnDisconnected(); - brush.OnConnected(); - } - - /// - /// Initializes a new instance of the class. - /// - public RadialGradientBrush() - { - // Rendering surface size, if this is too small the gradient will be pixelated. - // Larger targets aren't effected as one would expect unless the gradient is very complex. - // This seems like a good compromise. - SurfaceWidth = 512; - SurfaceHeight = 512; - - GradientStops = new GradientStopCollection(); - } - - /// - /// Initializes a new instance of the class - /// with two colors specified for GradientStops at - /// offsets 0.0 and 1.0. - /// - /// The Color at offset 0.0. - /// The Color at offset 1.0. - public RadialGradientBrush(Color startColor, Color endColor) - : this() - { - GradientStops.Add(new GradientStop() { Color = startColor, Offset = 0.0 }); - GradientStops.Add(new GradientStop() { Color = endColor, Offset = 1.0 }); - } - - /// - /// Initializes a new instance of the class with GradientStops set to the passed-in collection. - /// - /// GradientStopCollection to set on this brush. - public RadialGradientBrush(GradientStopCollection gradientStopCollection) - : this() - { - GradientStops = gradientStopCollection; - } - - /// - protected override bool OnDraw(CanvasDevice device, CanvasDrawingSession session, Vector2 size) - { - // Create our Brush - if (GradientStops != null && GradientStops.Count > 0) - { - var gradientBrush = new CanvasRadialGradientBrush( - device, - GradientStops.ToWin2DGradientStops(), - SpreadMethod.ToEdgeBehavior(), - (CanvasAlphaMode)(int)AlphaMode, - ColorInterpolationMode.ToCanvasColorSpace(), - CanvasColorSpace.Srgb, - CanvasBufferPrecision.Precision8UIntNormalized) - { - // Calculate Surface coordinates from 0.0-1.0 range given in WPF brush - RadiusX = size.X * (float)RadiusX, - RadiusY = size.Y * (float)RadiusY, - Center = size * Center.ToVector2(), - - // Calculate Win2D Offset from origin/center used in WPF brush - OriginOffset = size * (GradientOrigin.ToVector2() - Center.ToVector2()), - }; - - // Use brush to draw on our canvas - session.FillRectangle(size.ToRect(), gradientBrush); - - gradientBrush.Dispose(); - - return true; - } - - return false; - } -} diff --git a/components/Media/src/Brushes/RadialGradientBrushInterop.cs b/components/Media/src/Brushes/RadialGradientBrushInterop.cs deleted file mode 100644 index 21bbba57..00000000 --- a/components/Media/src/Brushes/RadialGradientBrushInterop.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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.Brushes; - -namespace CommunityToolkit.WinUI.Media; - -/// -/// Interop Helpers between WPF and Win2D used by the -/// -internal static class RadialGradientBrushInterop -{ - /// - /// Converts a WPF to a Win2D . - /// https://msdn.microsoft.com/en-us/library/system.windows.media.colorinterpolationmode(v=vs.110).aspx - /// http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasColorSpace.htm - /// - /// mode. - /// space. - public static CanvasColorSpace ToCanvasColorSpace(this ColorInterpolationMode colorspace) - { - switch (colorspace) - { - case ColorInterpolationMode.ScRgbLinearInterpolation: - return CanvasColorSpace.ScRgb; - case ColorInterpolationMode.SRgbLinearInterpolation: - return CanvasColorSpace.Srgb; - } - - return CanvasColorSpace.Custom; - } - - /// - /// Converts a WPF to a Win2D . - /// https://msdn.microsoft.com/en-us/library/system.windows.media.gradientspreadmethod(v=vs.110).aspx - /// http://microsoft.github.io/Win2D/html/T_Microsoft_Graphics_Canvas_CanvasEdgeBehavior.htm - /// - /// method. - /// behavior. - public static CanvasEdgeBehavior ToEdgeBehavior(this GradientSpreadMethod method) - { - switch (method) - { - case GradientSpreadMethod.Pad: - return CanvasEdgeBehavior.Clamp; - case GradientSpreadMethod.Reflect: - return CanvasEdgeBehavior.Mirror; - case GradientSpreadMethod.Repeat: - return CanvasEdgeBehavior.Wrap; - } - - return CanvasEdgeBehavior.Clamp; - } - - /// - /// Returns a new representing the size of the . - /// - /// vector representing object size for Rectangle. - /// value. - public static Rect ToRect(this Vector2 vector) - { - return new Rect(0, 0, vector.X, vector.Y); - } - - /// - /// Returns a new representing the . - /// - /// value. - /// value. - public static Vector2 ToVector2(this Size size) - { - return new Vector2((float)size.Width, (float)size.Height); - } - - /// - /// Converts a to an array of . - /// - /// collection of gradient stops. - /// New array of stops. - public static CanvasGradientStop[] ToWin2DGradientStops(this GradientStopCollection stops) - { - var canvasStops = new CanvasGradientStop[stops.Count]; - - int x = 0; - foreach (var stop in stops) - { - canvasStops[x++] = new CanvasGradientStop { Color = stop.Color, Position = (float)stop.Offset }; - } - - return canvasStops; - } -} \ No newline at end of file From a58f504ea68cb4b11c852ff50cc62717fbfb1627 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 6 Jul 2023 15:13:02 -0500 Subject: [PATCH 06/13] Update submodule, testing hotfixes --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 53f49b7c..e7cf9bb4 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 53f49b7c1a6662668f828ee1980dc5c618a1db10 +Subproject commit e7cf9bb491bd1223869612bd462d73cbd9460a36 From a1951df3ac3e8ecd17caf760275b1c5862d45ed5 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Fri, 7 Jul 2023 16:31:37 -0500 Subject: [PATCH 07/13] Update tooling to latest main --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index e7cf9bb4..38728ba6 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit e7cf9bb491bd1223869612bd462d73cbd9460a36 +Subproject commit 38728ba616661853b3bbcec9269ab1e362daa72a From d6c2e59a75eb3469a92abf3564a9213fa5e546a7 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Fri, 7 Jul 2023 17:08:59 -0500 Subject: [PATCH 08/13] Fixed null handling --- components/Extensions/src/Markup/FontIconExtension.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/components/Extensions/src/Markup/FontIconExtension.cs b/components/Extensions/src/Markup/FontIconExtension.cs index 4afac400..fdcb0332 100644 --- a/components/Extensions/src/Markup/FontIconExtension.cs +++ b/components/Extensions/src/Markup/FontIconExtension.cs @@ -21,8 +21,11 @@ public class FontIconExtension : TextIconExtension public FontFamily? FontFamily { get; set; } /// - protected override object ProvideValue() + protected override object? ProvideValue() { + if (Glyph is null || string.IsNullOrWhiteSpace(Glyph)) + return null; + var fontIcon = new FontIcon { Glyph = Glyph, From 2f0e26919f178d8dd0f78114c870b925615ca395 Mon Sep 17 00:00:00 2001 From: Arlo Date: Mon, 10 Jul 2023 19:47:20 -0500 Subject: [PATCH 09/13] Fixed version number --- components/Media/src/CommunityToolkit.WinUI.Media.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Media/src/CommunityToolkit.WinUI.Media.csproj b/components/Media/src/CommunityToolkit.WinUI.Media.csproj index 014f6cb4..dc295949 100644 --- a/components/Media/src/CommunityToolkit.WinUI.Media.csproj +++ b/components/Media/src/CommunityToolkit.WinUI.Media.csproj @@ -2,7 +2,7 @@ Media This package contains Media. - 0.0.1 + 8.0.0-beta.1 CommunityToolkit.WinUI.MediaRns From a57afe4f0bde455af5f1026e786b3419c0d29e1d Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Wed, 12 Jul 2023 17:19:25 -0500 Subject: [PATCH 10/13] Use parameter validation for FontIconExtension --- components/Extensions/src/Markup/FontIconExtension.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/Extensions/src/Markup/FontIconExtension.cs b/components/Extensions/src/Markup/FontIconExtension.cs index fdcb0332..4a090833 100644 --- a/components/Extensions/src/Markup/FontIconExtension.cs +++ b/components/Extensions/src/Markup/FontIconExtension.cs @@ -21,10 +21,9 @@ public class FontIconExtension : TextIconExtension public FontFamily? FontFamily { get; set; } /// - protected override object? ProvideValue() + protected override object ProvideValue() { - if (Glyph is null || string.IsNullOrWhiteSpace(Glyph)) - return null; + default(ArgumentNullException).ThrowIfNull(Glyph); var fontIcon = new FontIcon { From c5674aca84174262050328d0ed4580d5319a9d6d Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Wed, 12 Jul 2023 17:26:17 -0500 Subject: [PATCH 11/13] Add ThrowIfNull polyfill to extensions --- .../src/ArgumentNullExceptionExtensions.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 components/Extensions/src/ArgumentNullExceptionExtensions.cs 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; + +/// +/// Throw helper extensions for . +/// +internal static class ArgumentNullExceptionExtensions +{ + /// + /// Throws an for a given parameter name. + /// + /// Dummy value to invoke the extension upon (always pass . + /// The name of the parameter to report in the exception. + /// Thrown with . + [DoesNotReturn] + public static void Throw(this ArgumentNullException? _, string? parameterName) + { + throw new ArgumentNullException(parameterName); + } + + /// + /// Throws an if is . + /// + /// Dummy value to invoke the extension upon (always pass . + /// The reference type argument to validate as non-. + /// The name of the parameter with which corresponds. + /// Thrown if is . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ThrowIfNull(this ArgumentNullException? _, [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? parameterName = null) + { + if (argument is null) + { + Throw(parameterName); + } + } + + /// + /// Throws an if is . + /// + /// Dummy value to invoke the extension upon (always pass . + /// The pointer argument to validate as non-. + /// The name of the parameter with which corresponds. + /// Thrown if is . + [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); + } + } + + /// + [DoesNotReturn] + private static void Throw(string? parameterName) + { + throw new ArgumentNullException(parameterName); + } +} From 3f6e0039ebb2b5966b97d3c037545c75133025de Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 13 Jul 2023 12:31:09 -0500 Subject: [PATCH 12/13] Move internal ArgumentNullExceptionExtensions from Animations to Extensions --- .../src/ArgumentNullExceptionExtensions.cs | 65 ------------------- .../CommunityToolkit.WinUI.Extensions.csproj | 9 +++ 2 files changed, 9 insertions(+), 65 deletions(-) delete mode 100644 components/Animations/src/ArgumentNullExceptionExtensions.cs diff --git a/components/Animations/src/ArgumentNullExceptionExtensions.cs b/components/Animations/src/ArgumentNullExceptionExtensions.cs deleted file mode 100644 index 545654bf..00000000 --- a/components/Animations/src/ArgumentNullExceptionExtensions.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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; - -/// -/// Throw helper extensions for . -/// -internal static class ArgumentNullExceptionExtensions -{ - /// - /// Throws an for a given parameter name. - /// - /// Dummy value to invoke the extension upon (always pass . - /// The name of the parameter to report in the exception. - /// Thrown with . - [DoesNotReturn] - public static void Throw(this ArgumentNullException? _, string? parameterName) - { - throw new ArgumentNullException(parameterName); - } - - /// - /// Throws an if is . - /// - /// Dummy value to invoke the extension upon (always pass . - /// The reference type argument to validate as non-. - /// The name of the parameter with which corresponds. - /// Thrown if is . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ThrowIfNull(this ArgumentNullException? _, [NotNull] object? argument, [CallerArgumentExpression(nameof(argument))] string? parameterName = null) - { - if (argument is null) - { - Throw(parameterName); - } - } - - /// - /// Throws an if is . - /// - /// Dummy value to invoke the extension upon (always pass . - /// The pointer argument to validate as non-. - /// The name of the parameter with which corresponds. - /// Thrown if is . - [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); - } - } - - /// - [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 b4bffbc3..3d2160bc 100644 --- a/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj +++ b/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj @@ -6,11 +6,20 @@ CommunityToolkit.WinUI.ExtensionsRns + true + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + From f19bda7dccc291d8ff2614c19dd190f4bf2c764b Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Thu, 13 Jul 2023 13:32:21 -0500 Subject: [PATCH 13/13] Expose internals to CommunityToolkit.WinUI.Media --- .../Extensions/src/CommunityToolkit.WinUI.Extensions.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj b/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj index 7580f306..e4df1fff 100644 --- a/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj +++ b/components/Extensions/src/CommunityToolkit.WinUI.Extensions.csproj @@ -21,5 +21,6 @@ +