diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml index 93fbe5e412..aa165d13f7 100644 --- a/samples/RenderDemo/MainWindow.xaml +++ b/samples/RenderDemo/MainWindow.xaml @@ -57,6 +57,9 @@ + + + diff --git a/samples/RenderDemo/Pages/PathMeasurementPage.cs b/samples/RenderDemo/Pages/PathMeasurementPage.cs new file mode 100644 index 0000000000..212377deae --- /dev/null +++ b/samples/RenderDemo/Pages/PathMeasurementPage.cs @@ -0,0 +1,89 @@ +using System; +using System.Diagnostics; +using System.Drawing.Drawing2D; +using System.Security.Cryptography; +using Avalonia; +using Avalonia.Controls; +using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Media.Immutable; +using Avalonia.Threading; +using Avalonia.Visuals.Media.Imaging; + +namespace RenderDemo.Pages +{ + public class PathMeasurementPage : Control + { + static PathMeasurementPage() + { + AffectsRender(BoundsProperty); + } + + private RenderTargetBitmap _bitmap; + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + _bitmap = new RenderTargetBitmap(new PixelSize(500, 500), new Vector(96, 96)); + base.OnAttachedToLogicalTree(e); + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + _bitmap.Dispose(); + _bitmap = null; + base.OnDetachedFromLogicalTree(e); + } + + readonly IPen strokePen = new ImmutablePen(Brushes.DarkBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen1 = new ImmutablePen(Brushes.Purple, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen2 = new ImmutablePen(Brushes.Green, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen3 = new ImmutablePen(Brushes.LightBlue, 10d, null, PenLineCap.Round, PenLineJoin.Round); + readonly IPen strokePen4 = new ImmutablePen(Brushes.Red, 1d, null, PenLineCap.Round, PenLineJoin.Round); + + public override void Render(DrawingContext context) + { + using (var ctxi = _bitmap.CreateDrawingContext(null)) + using (var bitmapCtx = new DrawingContext(ctxi, false)) + { + ctxi.Clear(default); + + var basePath = new PathGeometry(); + + using (var basePathCtx = basePath.Open()) + { + basePathCtx.BeginFigure(new Point(20, 20), false); + basePathCtx.LineTo(new Point(400, 50)); + basePathCtx.LineTo(new Point(80, 100)); + basePathCtx.LineTo(new Point(300, 150)); + basePathCtx.EndFigure(false); + } + + bitmapCtx.DrawGeometry(null, strokePen, basePath); + + + var length = basePath.PlatformImpl.ContourLength; + + if (basePath.PlatformImpl.TryGetSegment(length * 0.05, length * 0.2, true, out var dst1)) + bitmapCtx.DrawGeometry(null, strokePen1, dst1); + + if (basePath.PlatformImpl.TryGetSegment(length * 0.2, length * 0.8, true, out var dst2)) + bitmapCtx.DrawGeometry(null, strokePen2, dst2); + + if (basePath.PlatformImpl.TryGetSegment(length * 0.8, length * 0.95, true, out var dst3)) + bitmapCtx.DrawGeometry(null, strokePen3, dst3); + + var pathBounds = basePath.GetRenderBounds(strokePen); + + bitmapCtx.DrawRectangle(null, strokePen4, pathBounds); + } + + + context.DrawImage(_bitmap, + new Rect(0, 0, 500, 500), + new Rect(0, 0, 500, 500)); + + base.Render(context); + } + } +} diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 6a78f4c6e7..62cac378d7 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -104,6 +104,9 @@ namespace Avalonia.Headless } public Rect Bounds { get; set; } + + public double ContourLength { get; } = 0; + public virtual bool FillContains(Point point) => Bounds.Contains(point); public Rect GetRenderBounds(IPen pen) @@ -126,6 +129,25 @@ namespace Avalonia.Headless public ITransformedGeometryImpl WithTransform(Matrix transform) => new HeadlessTransformedGeometryStub(this, transform); + + public bool TryGetPointAtDistance(double distance, out Point point) + { + point = new Point(); + return false; + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + point = new Point(); + tangent = new Point(); + return false; + } + + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + segmentGeometry = null; + return false; + } } class HeadlessTransformedGeometryStub : HeadlessGeometryStub, ITransformedGeometryImpl @@ -359,6 +381,16 @@ namespace Avalonia.Headless } + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + + } + + public void PopBitmapBlendMode() + { + + } + public void Custom(ICustomDrawOperation custom) { diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt new file mode 100644 index 0000000000..805d1955ea --- /dev/null +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -0,0 +1,9 @@ +Compat issues with assembly Avalonia.Visuals: +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAndTangentAtDistance(System.Double, Avalonia.Point, Avalonia.Point)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetPointAtDistance(System.Double, Avalonia.Point)' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IGeometryImpl.TryGetSegment(System.Double, System.Double, System.Boolean, Avalonia.Platform.IGeometryImpl)' is present in the implementation but not in the contract. +Total Issues: 7 diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index ae4c927ae2..4e3dc8699c 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -121,12 +121,23 @@ namespace Avalonia.Media /// The stroke pen. /// The geometry. public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry) + { + DrawGeometry(brush, pen, geometry.PlatformImpl); + } + + /// + /// Draws a geometry. + /// + /// The fill brush. + /// The stroke pen. + /// The geometry. + public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { Contract.Requires(geometry != null); if (brush != null || PenIsVisible(pen)) { - PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl); + PlatformImpl.DrawGeometry(brush, pen, geometry); } } diff --git a/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs new file mode 100644 index 0000000000..473b43dab3 --- /dev/null +++ b/src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs @@ -0,0 +1,57 @@ +namespace Avalonia.Visuals.Media.Imaging +{ + /// + /// Controls the way the bitmaps are drawn together. + /// + public enum BitmapBlendingMode + { + /// + /// Source is placed over the destination. + /// + SourceOver, + /// + /// Only the source will be present. + /// + Source, + /// + /// Only the destination will be present. + /// + Destination, + /// + /// Destination is placed over the source. + /// + DestinationOver, + /// + /// The source that overlaps the destination, replaces the destination. + /// + SourceIn, + /// + /// Destination which overlaps the source, replaces the source. + /// + DestinationIn, + /// + /// Source is placed, where it falls outside of the destination. + /// + SourceOut, + /// + /// Destination is placed, where it falls outside of the source. + /// + DestinationOut, + /// + /// Source which overlaps the destination, replaces the destination. + /// + SourceAtop, + /// + /// Destination which overlaps the source replaces the source. + /// + DestinationAtop, + /// + /// The non-overlapping regions of source and destination are combined. + /// + Xor, + /// + /// Display the sum of the source image and destination image. + /// + Plus, + } +} diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index d6e88a7507..39d4066e55 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -148,6 +148,17 @@ namespace Avalonia.Platform /// Pops the latest pushed geometry clip. /// void PopGeometryClip(); + + /// + /// Pushes a bitmap blending value. + /// + /// The bitmap blending mode. + void PushBitmapBlendMode(BitmapBlendingMode blendingMode); + + /// + /// Pops the latest pushed bitmap blending value. + /// + void PopBitmapBlendMode(); /// /// Adds a custom draw operation diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 7490ad912a..79e125c5cb 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -11,6 +11,12 @@ namespace Avalonia.Platform /// Gets the geometry's bounding rectangle. /// Rect Bounds { get; } + + /// + /// Gets the geometry's total length as if all its contours are placed + /// in a straight line. + /// + double ContourLength { get; } /// /// Gets the geometry's bounding rectangle with the specified pen. @@ -47,5 +53,38 @@ namespace Avalonia.Platform /// The transform. /// The cloned geometry. ITransformedGeometryImpl WithTransform(Matrix transform); + + /// + /// Attempts to get the corresponding point at the + /// specified distance + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// If there's valid point at the specified distance. + bool TryGetPointAtDistance(double distance, out Point point); + + /// + /// Attempts to get the corresponding point and + /// tangent from the specified distance along the + /// contour of the geometry. + /// + /// The contour distance to get from. + /// The point in the specified distance. + /// The tangent in the specified distance. + /// If there's valid point and tangent at the specified distance. + bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent); + + /// + /// Attempts to get the corresponding path segment + /// given by the two distances specified. + /// Imagine it like snipping a part of the current + /// geometry. + /// + /// The contour distance to start snipping from. + /// The contour distance to stop snipping to. + /// If ture, the resulting snipped path will start with a BeginFigure call. + /// The resulting snipped path. + /// If the snipping operation is successful. + bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs new file mode 100644 index 0000000000..0a5c1f8db6 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -0,0 +1,68 @@ +using Avalonia.Platform; +using Avalonia.Visuals.Media.Imaging; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// A node in the scene graph which represents an bitmap blending mode push or pop. + /// + internal class BitmapBlendModeNode : IDrawOperation + { + /// + /// Initializes a new instance of the class that represents an + /// push. + /// + /// The to push. + public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend) + { + BlendingMode = bitmapBlend; + } + + /// + /// Initializes a new instance of the class that represents an + /// pop. + /// + public BitmapBlendModeNode() + { + } + + /// + public Rect Bounds => Rect.Empty; + + /// + /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. + /// + public BitmapBlendingMode? BlendingMode { get; } + + /// + public bool HitTest(Point p) => false; + + /// + /// Determines if this draw operation equals another. + /// + /// The opacity of the other draw operation. + /// True if the draw operations are the same, otherwise false. + /// + /// The properties of the other draw operation are passed in as arguments to prevent + /// allocation of a not-yet-constructed draw operation object. + /// + public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode; + + /// + public void Render(IDrawingContextImpl context) + { + if (BlendingMode.HasValue) + { + context.PushBitmapBlendMode(BlendingMode.Value); + } + else + { + context.PopBitmapBlendMode(); + } + } + + public void Dispose() + { + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 28f426266d..e6092574c5 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PopBitmapBlendMode() + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(null)) + { + Add(new BitmapBlendModeNode()); + } + else + { + ++_drawOperationindex; + } + } + /// public void PopOpacity() { @@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph } } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(blendingMode)) + { + Add(new BitmapBlendModeNode(blendingMode)); + } + else + { + ++_drawOperationindex; + } + } + public readonly struct UpdateState : IDisposable { public UpdateState( diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index c9052c6ef2..d3da19d8c9 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph /// The scaling mode. /// public BitmapInterpolationMode BitmapInterpolationMode { get; } + + /// + /// The bitmap blending mode. + /// + /// + /// The blending mode. + /// + public BitmapBlendingMode BitmapBlendingMode { get; } /// /// Determines if this draw operation equals another. @@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph public bool Equals(Matrix transform, IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { return transform == Transform && - Equals(source.Item, Source.Item) && - source.Item.Version == SourceVersion && - opacity == Opacity && - sourceRect == SourceRect && - destRect == DestRect && - bitmapInterpolationMode == BitmapInterpolationMode; + Equals(source.Item, Source.Item) && + source.Item.Version == SourceVersion && + opacity == Opacity && + sourceRect == SourceRect && + destRect == DestRect && + bitmapInterpolationMode == BitmapInterpolationMode; } /// diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 2a79a4bb50..b7d5d3ec59 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -23,9 +23,11 @@ namespace Avalonia.Skia private readonly Vector _dpi; private readonly Stack _maskStack = new Stack(); private readonly Stack _opacityStack = new Stack(); + private readonly Stack _blendingModeStack = new Stack(); private readonly Matrix? _postTransform; private readonly IVisualBrushRenderer _visualBrushRenderer; private double _currentOpacity = 1.0f; + private BitmapBlendingMode _currentBlendingMode = BitmapBlendingMode.SourceOver; private readonly bool _canTextUseLcdRendering; private Matrix _currentTransform; private bool _disposed; @@ -145,6 +147,7 @@ namespace Avalonia.Skia }) { paint.FilterQuality = bitmapInterpolationMode.ToSKFilterQuality(); + paint.BlendMode = _currentBlendingMode.ToSKBlendMode(); drawableImage.Draw(this, s, d, paint); } @@ -508,6 +511,19 @@ namespace Avalonia.Skia Canvas.Restore(); } + /// + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + _blendingModeStack.Push(_currentBlendingMode); + _currentBlendingMode = blendingMode; + } + + /// + public void PopBitmapBlendMode() + { + _currentBlendingMode = _blendingModeStack.Pop(); + } + public void Custom(ICustomDrawOperation custom) => custom.Render(this); /// diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 879b18742e..19dbcf9713 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -11,9 +11,36 @@ namespace Avalonia.Skia internal abstract class GeometryImpl : IGeometryImpl { private PathCache _pathCache; - + private SKPathMeasure _pathMeasureCache; + + private SKPathMeasure CachedPathMeasure + { + get + { + if (_pathMeasureCache is null) + { + _pathMeasureCache = new SKPathMeasure(EffectivePath); + } + + return _pathMeasureCache; + } + } + /// public abstract Rect Bounds { get; } + + /// + public double ContourLength + { + get + { + if (EffectivePath is null) + return 0; + + return (double)CachedPathMeasure?.Length; + } + } + public abstract SKPath EffectivePath { get; } /// @@ -30,12 +57,12 @@ namespace Avalonia.Skia // Usually this function is being called with same stroke width per path, so this saves a lot of Skia traffic. var strokeWidth = (float)(pen?.Thickness ?? 0); - + if (!_pathCache.HasCacheFor(strokeWidth)) { UpdatePathCache(strokeWidth); } - + return PathContainsCore(_pathCache.CachedStrokePath, point); } @@ -58,7 +85,7 @@ namespace Avalonia.Skia { paint.IsStroke = true; paint.StrokeWidth = strokeWidth; - + paint.GetFillPath(EffectivePath, strokePath); _pathCache.Cache(strokePath, strokeWidth, strokePath.TightBounds.ToAvaloniaRect()); @@ -74,13 +101,13 @@ namespace Avalonia.Skia /// True, if point is contained in a path. private static bool PathContainsCore(SKPath path, Point point) { - return path.Contains((float)point.X, (float)point.Y); + return path.Contains((float)point.X, (float)point.Y); } /// public IGeometryImpl Intersect(IGeometryImpl geometry) { - var result = EffectivePath.Op(((GeometryImpl) geometry).EffectivePath, SKPathOp.Intersect); + var result = EffectivePath.Op(((GeometryImpl)geometry).EffectivePath, SKPathOp.Intersect); return result == null ? null : new StreamGeometryImpl(result); } @@ -89,21 +116,74 @@ namespace Avalonia.Skia public Rect GetRenderBounds(IPen pen) { var strokeWidth = (float)(pen?.Thickness ?? 0); - + if (!_pathCache.HasCacheFor(strokeWidth)) { UpdatePathCache(strokeWidth); } - + return _pathCache.CachedGeometryRenderBounds; } - + /// public ITransformedGeometryImpl WithTransform(Matrix transform) { return new TransformedGeometryImpl(this, transform); } + /// + public bool TryGetPointAtDistance(double distance, out Point point) + { + if (EffectivePath is null) + { + point = new Point(); + return false; + } + + var res = CachedPathMeasure.GetPosition((float)distance, out var skPoint); + point = new Point(skPoint.X, skPoint.Y); + return res; + } + + /// + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + if (EffectivePath is null) + { + point = new Point(); + tangent = new Point(); + return false; + } + + var res = CachedPathMeasure.GetPositionAndTangent((float)distance, out var skPoint, out var skTangent); + point = new Point(skPoint.X, skPoint.Y); + tangent = new Point(skTangent.X, skTangent.Y); + return res; + } + + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, + out IGeometryImpl segmentGeometry) + { + if (EffectivePath is null) + { + segmentGeometry = null; + return false; + } + + segmentGeometry = null; + + var _skPathSegment = new SKPath(); + + var res = CachedPathMeasure.GetSegment((float)startDistance, (float)stopDistance, _skPathSegment, startOnBeginFigure); + + if (res) + { + segmentGeometry = new StreamGeometryImpl(_skPathSegment); + } + + return res; + } + /// /// Invalidate all caches. Call after chaining path contents. /// @@ -115,12 +195,12 @@ namespace Avalonia.Skia private struct PathCache { private float _cachedStrokeWidth; - + /// /// Tolerance for two stroke widths to be deemed equal /// public const float Tolerance = float.Epsilon; - + /// /// Cached contour path. /// diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs index bb3dbbfadc..75b4231640 100644 --- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs @@ -25,6 +25,39 @@ namespace Avalonia.Skia } } + public static SKBlendMode ToSKBlendMode(this BitmapBlendingMode blendingMode) + { + switch (blendingMode) + { + case BitmapBlendingMode.SourceOver: + return SKBlendMode.SrcOver; + case BitmapBlendingMode.Source: + return SKBlendMode.Src; + case BitmapBlendingMode.SourceIn: + return SKBlendMode.SrcIn; + case BitmapBlendingMode.SourceOut: + return SKBlendMode.SrcOut; + case BitmapBlendingMode.SourceAtop: + return SKBlendMode.SrcATop; + case BitmapBlendingMode.Destination: + return SKBlendMode.Dst; + case BitmapBlendingMode.DestinationIn: + return SKBlendMode.DstIn; + case BitmapBlendingMode.DestinationOut: + return SKBlendMode.DstOut; + case BitmapBlendingMode.DestinationOver: + return SKBlendMode.DstOver; + case BitmapBlendingMode.DestinationAtop: + return SKBlendMode.DstATop; + case BitmapBlendingMode.Xor: + return SKBlendMode.Xor; + case BitmapBlendingMode.Plus: + return SKBlendMode.Plus; + default: + throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null); + } + } + public static SKPoint ToSKPoint(this Point p) { return new SKPoint((float)p.X, (float)p.Y); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 47a19aad8c..af35934785 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -5,6 +5,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; +using Avalonia.Visuals.Media.Imaging; using SharpDX; using SharpDX.Direct2D1; using SharpDX.Mathematics.Interop; @@ -121,7 +122,9 @@ namespace Avalonia.Direct2D1.Media using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext)) { var interpolationMode = GetInterpolationMode(bitmapInterpolationMode); - + + // TODO: How to implement CompositeMode here? + _deviceContext.DrawBitmap( d2d.Value, destRect.ToSharpDX(), @@ -149,6 +152,35 @@ namespace Avalonia.Direct2D1.Media } } + public static CompositeMode GetCompositeMode(BitmapBlendingMode blendingMode) + { + switch (blendingMode) + { + case BitmapBlendingMode.SourceIn: + return CompositeMode.SourceIn; + case BitmapBlendingMode.SourceOut: + return CompositeMode.SourceOut; + case BitmapBlendingMode.SourceOver: + return CompositeMode.SourceOver; + case BitmapBlendingMode.SourceAtop: + return CompositeMode.SourceAtop; + case BitmapBlendingMode.DestinationIn: + return CompositeMode.DestinationIn; + case BitmapBlendingMode.DestinationOut: + return CompositeMode.DestinationOut; + case BitmapBlendingMode.DestinationOver: + return CompositeMode.DestinationOver; + case BitmapBlendingMode.DestinationAtop: + return CompositeMode.DestinationAtop; + case BitmapBlendingMode.Xor: + return CompositeMode.Xor; + case BitmapBlendingMode.Plus: + return CompositeMode.Plus; + default: + throw new ArgumentOutOfRangeException(nameof(blendingMode), blendingMode, null); + } + } + /// /// Draws a bitmap image. /// @@ -525,6 +557,16 @@ namespace Avalonia.Direct2D1.Media PopLayer(); } + public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) + { + // TODO: Stubs for now + } + + public void PopBitmapBlendMode() + { + // TODO: Stubs for now + } + public void PushOpacityMask(IBrush mask, Rect bounds) { var parameters = new LayerParameters diff --git a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs index d04e2b3110..ec88347a17 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs @@ -1,3 +1,4 @@ +using Avalonia.Logging; using Avalonia.Platform; using SharpDX.Direct2D1; @@ -8,6 +9,8 @@ namespace Avalonia.Direct2D1.Media /// public abstract class GeometryImpl : IGeometryImpl { + private const float ContourApproximation = 0.0001f; + public GeometryImpl(Geometry geometry) { Geometry = geometry; @@ -16,6 +19,9 @@ namespace Avalonia.Direct2D1.Media /// public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia(); + /// + public double ContourLength => Geometry.ComputeLength(null, ContourApproximation); + public Geometry Geometry { get; } /// @@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media transform.ToDirect2D()), this); } + + /// + public bool TryGetPointAtDistance(double distance, out Point point) + { + Geometry.ComputePointAtLength((float)distance, ContourApproximation, out var tangentVector); + point = new Point(tangentVector.X, tangentVector.Y); + return true; + } + + /// + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + // Direct2D doesnt have this sadly. + Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetPointAndTangentAtDistance is not available in Direct2D."); + point = new Point(); + tangent = new Point(); + return false; + } + + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + // Direct2D doesnt have this too sadly. + Logger.TryGet(LogEventLevel.Warning, LogArea.Visual)?.Log(this, "TryGetSegment is not available in Direct2D."); + + segmentGeometry = null; + return false; + } protected virtual Geometry GetSourceGeometry() => Geometry; } diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 4fa3fbf523..864e2efbaf 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -30,6 +30,8 @@ namespace Avalonia.UnitTests public IGeometryImpl SourceGeometry { get; } public Rect Bounds => _context.CalculateBounds(); + + public double ContourLength { get; } public Matrix Transform { get; } @@ -69,6 +71,25 @@ namespace Avalonia.UnitTests return new MockStreamGeometryImpl(transform, _context); } + public bool TryGetPointAtDistance(double distance, out Point point) + { + point = new Point(); + return false; + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + point = new Point(); + tangent = new Point(); + return false; + } + + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + segmentGeometry = null; + return false; + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List(); diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 02236e4107..6d0683e699 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -112,6 +112,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree } } + public double ContourLength { get; } + public IStreamGeometryImpl Clone() { return this; @@ -151,6 +153,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } + public bool TryGetPointAtDistance(double distance, out Point point) + { + throw new NotImplementedException(); + } + + public bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent) + { + throw new NotImplementedException(); + } + + public bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry) + { + throw new NotImplementedException(); + } + class MockStreamGeometryContext : IStreamGeometryContextImpl { private List points = new List();