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();