Browse Source

Merge branch 'master' into android-keyboard

pull/5735/head
Ilya Chudin 5 years ago
committed by GitHub
parent
commit
c7532b9bef
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      samples/RenderDemo/MainWindow.xaml
  2. 89
      samples/RenderDemo/Pages/PathMeasurementPage.cs
  3. 32
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  4. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  5. 13
      src/Avalonia.Visuals/Media/DrawingContext.cs
  6. 57
      src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs
  7. 11
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  8. 39
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  9. 68
      src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs
  10. 30
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  11. 20
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  12. 9
      src/Avalonia.Visuals/Vector.cs
  13. 16
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  14. 102
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  15. 33
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  16. 44
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  17. 33
      src/Windows/Avalonia.Direct2D1/Media/GeometryImpl.cs
  18. 21
      tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
  19. 10
      tests/Avalonia.Visuals.UnitTests/VectorTests.cs
  20. 17
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

3
samples/RenderDemo/MainWindow.xaml

@ -57,6 +57,9 @@
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
</TabControl>
</DockPanel>
</Window>

89
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<PathMeasurementPage>(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);
}
}
}

32
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)
{

9
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

13
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -121,12 +121,23 @@ namespace Avalonia.Media
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry)
{
DrawGeometry(brush, pen, geometry.PlatformImpl);
}
/// <summary>
/// Draws a geometry.
/// </summary>
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
Contract.Requires<ArgumentNullException>(geometry != null);
if (brush != null || PenIsVisible(pen))
{
PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
PlatformImpl.DrawGeometry(brush, pen, geometry);
}
}

57
src/Avalonia.Visuals/Media/Imaging/BitmapBlendingMode.cs

@ -0,0 +1,57 @@
namespace Avalonia.Visuals.Media.Imaging
{
/// <summary>
/// Controls the way the bitmaps are drawn together.
/// </summary>
public enum BitmapBlendingMode
{
/// <summary>
/// Source is placed over the destination.
/// </summary>
SourceOver,
/// <summary>
/// Only the source will be present.
/// </summary>
Source,
/// <summary>
/// Only the destination will be present.
/// </summary>
Destination,
/// <summary>
/// Destination is placed over the source.
/// </summary>
DestinationOver,
/// <summary>
/// The source that overlaps the destination, replaces the destination.
/// </summary>
SourceIn,
/// <summary>
/// Destination which overlaps the source, replaces the source.
/// </summary>
DestinationIn,
/// <summary>
/// Source is placed, where it falls outside of the destination.
/// </summary>
SourceOut,
/// <summary>
/// Destination is placed, where it falls outside of the source.
/// </summary>
DestinationOut,
/// <summary>
/// Source which overlaps the destination, replaces the destination.
/// </summary>
SourceAtop,
/// <summary>
/// Destination which overlaps the source replaces the source.
/// </summary>
DestinationAtop,
/// <summary>
/// The non-overlapping regions of source and destination are combined.
/// </summary>
Xor,
/// <summary>
/// Display the sum of the source image and destination image.
/// </summary>
Plus,
}
}

11
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -148,6 +148,17 @@ namespace Avalonia.Platform
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip();
/// <summary>
/// Pushes a bitmap blending value.
/// </summary>
/// <param name="blendingMode">The bitmap blending mode.</param>
void PushBitmapBlendMode(BitmapBlendingMode blendingMode);
/// <summary>
/// Pops the latest pushed bitmap blending value.
/// </summary>
void PopBitmapBlendMode();
/// <summary>
/// Adds a custom draw operation

39
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@ -11,6 +11,12 @@ namespace Avalonia.Platform
/// Gets the geometry's bounding rectangle.
/// </summary>
Rect Bounds { get; }
/// <summary>
/// Gets the geometry's total length as if all its contours are placed
/// in a straight line.
/// </summary>
double ContourLength { get; }
/// <summary>
/// Gets the geometry's bounding rectangle with the specified pen.
@ -47,5 +53,38 @@ namespace Avalonia.Platform
/// <param name="transform">The transform.</param>
/// <returns>The cloned geometry.</returns>
ITransformedGeometryImpl WithTransform(Matrix transform);
/// <summary>
/// Attempts to get the corresponding point at the
/// specified distance
/// </summary>
/// <param name="distance">The contour distance to get from.</param>
/// <param name="point">The point in the specified distance.</param>
/// <returns>If there's valid point at the specified distance.</returns>
bool TryGetPointAtDistance(double distance, out Point point);
/// <summary>
/// Attempts to get the corresponding point and
/// tangent from the specified distance along the
/// contour of the geometry.
/// </summary>
/// <param name="distance">The contour distance to get from.</param>
/// <param name="point">The point in the specified distance.</param>
/// <param name="tangent">The tangent in the specified distance.</param>
/// <returns>If there's valid point and tangent at the specified distance.</returns>
bool TryGetPointAndTangentAtDistance(double distance, out Point point, out Point tangent);
/// <summary>
/// Attempts to get the corresponding path segment
/// given by the two distances specified.
/// Imagine it like snipping a part of the current
/// geometry.
/// </summary>
/// <param name="startDistance">The contour distance to start snipping from.</param>
/// <param name="stopDistance">The contour distance to stop snipping to.</param>
/// <param name="startOnBeginFigure">If ture, the resulting snipped path will start with a BeginFigure call.</param>
/// <param name="segmentGeometry">The resulting snipped path.</param>
/// <returns>If the snipping operation is successful.</returns>
bool TryGetSegment(double startDistance, double stopDistance, bool startOnBeginFigure, out IGeometryImpl segmentGeometry);
}
}

68
src/Avalonia.Visuals/Rendering/SceneGraph/BitmapBlendModeNode.cs

@ -0,0 +1,68 @@
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// A node in the scene graph which represents an bitmap blending mode push or pop.
/// </summary>
internal class BitmapBlendModeNode : IDrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendModeNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> push.
/// </summary>
/// <param name="bitmapBlend">The <see cref="BitmapBlendingMode"/> to push.</param>
public BitmapBlendModeNode(BitmapBlendingMode bitmapBlend)
{
BlendingMode = bitmapBlend;
}
/// <summary>
/// Initializes a new instance of the <see cref="BitmapBlendNode"/> class that represents an
/// <see cref="BitmapBlendingMode"/> pop.
/// </summary>
public BitmapBlendModeNode()
{
}
/// <inheritdoc/>
public Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the BitmapBlend to be pushed or null if the operation represents a pop.
/// </summary>
public BitmapBlendingMode? BlendingMode { get; }
/// <inheritdoc/>
public bool HitTest(Point p) => false;
/// <summary>
/// Determines if this draw operation equals another.
/// </summary>
/// <param name="opacity">The opacity of the other draw operation.</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
public bool Equals(BitmapBlendingMode? blendingMode) => BlendingMode == blendingMode;
/// <inheritdoc/>
public void Render(IDrawingContextImpl context)
{
if (BlendingMode.HasValue)
{
context.PushBitmapBlendMode(BlendingMode.Value);
}
else
{
context.PopBitmapBlendMode();
}
}
public void Dispose()
{
}
}
}

30
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -253,6 +253,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <inheritdoc/>
public void PopBitmapBlendMode()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacity()
{
@ -358,6 +373,21 @@ namespace Avalonia.Rendering.SceneGraph
}
}
/// <inheritdoc/>
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationindex;
}
}
public readonly struct UpdateState : IDisposable
{
public UpdateState(

20
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -67,6 +67,14 @@ namespace Avalonia.Rendering.SceneGraph
/// The scaling mode.
/// </value>
public BitmapInterpolationMode BitmapInterpolationMode { get; }
/// <summary>
/// The bitmap blending mode.
/// </summary>
/// <value>
/// The blending mode.
/// </value>
public BitmapBlendingMode BitmapBlendingMode { get; }
/// <summary>
/// Determines if this draw operation equals another.
@ -85,12 +93,12 @@ namespace Avalonia.Rendering.SceneGraph
public bool Equals(Matrix transform, IRef<IBitmapImpl> 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;
}
/// <inheritdoc/>

9
src/Avalonia.Visuals/Vector.cs

@ -82,6 +82,15 @@ namespace Avalonia
public static Vector operator *(Vector vector, double scale)
=> Multiply(vector, scale);
/// <summary>
/// Scales a vector.
/// </summary>
/// <param name="vector">The vector.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The scaled vector.</returns>
public static Vector operator *(double scale, Vector vector)
=> Multiply(vector, scale);
/// <summary>
/// Scales a vector.
/// </summary>

16
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -23,9 +23,11 @@ namespace Avalonia.Skia
private readonly Vector _dpi;
private readonly Stack<PaintWrapper> _maskStack = new Stack<PaintWrapper>();
private readonly Stack<double> _opacityStack = new Stack<double>();
private readonly Stack<BitmapBlendingMode> _blendingModeStack = new Stack<BitmapBlendingMode>();
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();
}
/// <inheritdoc />
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
_blendingModeStack.Push(_currentBlendingMode);
_currentBlendingMode = blendingMode;
}
/// <inheritdoc />
public void PopBitmapBlendMode()
{
_currentBlendingMode = _blendingModeStack.Pop();
}
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
/// <inheritdoc />

102
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;
}
}
/// <inheritdoc />
public abstract Rect Bounds { get; }
/// <inheritdoc />
public double ContourLength
{
get
{
if (EffectivePath is null)
return 0;
return (double)CachedPathMeasure?.Length;
}
}
public abstract SKPath EffectivePath { get; }
/// <inheritdoc />
@ -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
/// <returns>True, if point is contained in a path.</returns>
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);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public ITransformedGeometryImpl WithTransform(Matrix transform)
{
return new TransformedGeometryImpl(this, transform);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}
/// <summary>
/// Invalidate all caches. Call after chaining path contents.
/// </summary>
@ -115,12 +195,12 @@ namespace Avalonia.Skia
private struct PathCache
{
private float _cachedStrokeWidth;
/// <summary>
/// Tolerance for two stroke widths to be deemed equal
/// </summary>
public const float Tolerance = float.Epsilon;
/// <summary>
/// Cached contour path.
/// </summary>

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

44
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);
}
}
/// <summary>
/// Draws a bitmap image.
/// </summary>
@ -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

33
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
/// </summary>
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
/// <inheritdoc/>
public Rect Bounds => Geometry.GetWidenedBounds(0).ToAvalonia();
/// <inheritdoc />
public double ContourLength => Geometry.ComputeLength(null, ContourApproximation);
public Geometry Geometry { get; }
/// <inheritdoc/>
@ -57,6 +63,33 @@ namespace Avalonia.Direct2D1.Media
transform.ToDirect2D()),
this);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
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;
}

21
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<Point> points = new List<Point>();

10
tests/Avalonia.Visuals.UnitTests/VectorTests.cs

@ -105,5 +105,15 @@ namespace Avalonia.Visuals.UnitTests
Assert.Equal(expected, Vector.Multiply(vector, 2));
}
[Fact]
public void Scale_Vector_Should_Be_Commutative()
{
var vector = new Vector(10, 2);
var expected = vector * 2;
Assert.Equal(expected, 2 * vector);
}
}
}

17
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<Point> points = new List<Point>();

Loading…
Cancel
Save