Browse Source

Merge remote-tracking branch 'origin/master' into feature/decode-bitmap-at-sepcified-size

pull/3890/head
Dan Walmsley 6 years ago
parent
commit
46a847a8a4
  1. 53
      samples/RenderDemo/Controls/LineBoundsDemoControl.cs
  2. 3
      samples/RenderDemo/MainWindow.xaml
  3. 9
      samples/RenderDemo/Pages/LineBoundsPage.xaml
  4. 19
      samples/RenderDemo/Pages/LineBoundsPage.xaml.cs
  5. 3
      samples/RenderDemo/RenderDemo.csproj
  6. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  7. 15
      src/Avalonia.Visuals/Media/PixelRect.cs
  8. 11
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  9. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  10. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  11. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  12. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  13. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
  14. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  15. 68
      src/Avalonia.Visuals/Rendering/SceneGraph/LineBoundsHelper.cs
  16. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  17. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  18. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  19. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  20. 2
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  21. 46
      tests/Avalonia.Visuals.UnitTests/Media/PixelRectTests.cs
  22. 17
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

53
samples/RenderDemo/Controls/LineBoundsDemoControl.cs

@ -0,0 +1,53 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
namespace RenderDemo.Controls
{
public class LineBoundsDemoControl : Control
{
static LineBoundsDemoControl()
{
AffectsRender<LineBoundsDemoControl>(AngleProperty);
}
public LineBoundsDemoControl()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1 / 60);
timer.Tick += (sender, e) => Angle += Math.PI / 360;
timer.Start();
}
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<LineBoundsDemoControl, double>(nameof(Angle));
public double Angle
{
get => GetValue(AngleProperty);
set => SetValue(AngleProperty, value);
}
public override void Render(DrawingContext drawingContext)
{
var lineLength = Math.Sqrt((100 * 100) + (100 * 100));
var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength);
var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength);
var p1 = new Point(200, 200);
var p2 = new Point(p1.X + diffX, p1.Y + diffY);
var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square);
var boundPen = new Pen(Brushes.Black);
drawingContext.DrawLine(pen, p1, p2);
drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen));
}
}
}

3
samples/RenderDemo/MainWindow.xaml

@ -44,6 +44,9 @@
<TabItem Header="GlyphRun">
<pages:GlyphRunPage/>
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
</TabControl>
</DockPanel>
</Window>

9
samples/RenderDemo/Pages/LineBoundsPage.xaml

@ -0,0 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:controls="clr-namespace:RenderDemo.Controls"
x:Class="RenderDemo.Pages.LineBoundsPage">
<controls:LineBoundsDemoControl />
</UserControl>

19
samples/RenderDemo/Pages/LineBoundsPage.xaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace RenderDemo.Pages
{
public class LineBoundsPage : UserControl
{
public LineBoundsPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

3
samples/RenderDemo/RenderDemo.csproj

@ -3,6 +3,9 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Collections;
@ -1100,6 +1101,7 @@ namespace Avalonia.Controls
{
_textBoxSubscriptions =
_textBox.GetObservable(TextBox.TextProperty)
.Skip(1)
.Subscribe(_ => OnTextBoxTextChanged());
if (Text != null)

15
src/Avalonia.Visuals/Media/PixelRect.cs

@ -377,7 +377,7 @@ namespace Avalonia
/// <returns>The device-independent rect.</returns>
public static PixelRect FromRect(Rect rect, double scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
FromPointCeiling(rect.BottomRight, new Vector(scale, scale)));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
@ -387,7 +387,7 @@ namespace Avalonia
/// <returns>The device-independent point.</returns>
public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
FromPointCeiling(rect.BottomRight, scale));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
@ -397,7 +397,7 @@ namespace Avalonia
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
FromPointCeiling(rect.BottomRight, new Vector(dpi / 96, dpi / 96)));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
@ -407,7 +407,7 @@ namespace Avalonia
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
FromPointCeiling(rect.BottomRight, dpi / 96));
/// <summary>
/// Returns the string representation of the rectangle.
@ -441,5 +441,12 @@ namespace Avalonia
);
}
}
private static PixelPoint FromPointCeiling(Point point, Vector scale)
{
return new PixelPoint(
(int)Math.Ceiling(point.X * scale.X),
(int)Math.Ceiling(point.Y * scale.Y));
}
}
}

11
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -443,11 +443,12 @@ namespace Avalonia.Rendering
private static Rect SnapToDevicePixels(Rect rect, double scale)
{
return new Rect(
Math.Floor(rect.X * scale) / scale,
Math.Floor(rect.Y * scale) / scale,
Math.Ceiling(rect.Width * scale) / scale,
Math.Ceiling(rect.Height * scale) / scale);
new Point(
Math.Floor(rect.X * scale) / scale,
Math.Floor(rect.Y * scale) / scale),
new Point(
Math.Ceiling(rect.Right * scale) / scale,
Math.Ceiling(rect.Bottom * scale) / scale));
}
private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent)

4
src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs

@ -9,8 +9,8 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
internal abstract class BrushDrawOperation : DrawOperation
{
public BrushDrawOperation(Rect bounds, Matrix transform, IPen pen)
: base(bounds, transform, pen)
public BrushDrawOperation(Rect bounds, Matrix transform)
: base(bounds, transform)
{
}

2
src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs

@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
public Matrix Transform { get; }
public ICustomDrawOperation Custom { get; }
public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform)
: base(custom.Bounds, transform, null)
: base(custom.Bounds, transform)
{
Transform = transform;
Custom = custom;

4
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@ -9,9 +9,9 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary>
internal abstract class DrawOperation : IDrawOperation
{
public DrawOperation(Rect bounds, Matrix transform, IPen pen)
public DrawOperation(Rect bounds, Matrix transform)
{
bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform);
bounds = bounds.TransformToAABB(transform);
Bounds = new Rect(
new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)),
new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom)));

2
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -24,7 +24,7 @@ namespace Avalonia.Rendering.SceneGraph
IPen pen,
IGeometryImpl geometry,
IDictionary<IVisual, Scene> childScenes = null)
: base(geometry.GetRenderBounds(pen), transform, null)
: base(geometry.GetRenderBounds(pen), transform)
{
Transform = transform;
Brush = brush?.ToImmutable();

2
src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs

@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
GlyphRun glyphRun,
Point baselineOrigin,
IDictionary<IVisual, Scene> childScenes = null)
: base(glyphRun.Bounds, transform, null)
: base(glyphRun.Bounds, transform)
{
Transform = transform;
Foreground = foreground?.ToImmutable();

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

@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="destRect">The destination rect.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public ImageNode(Matrix transform, IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
: base(destRect, transform, null)
: base(destRect, transform)
{
Transform = transform;
Source = source.Clone();

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

@ -0,0 +1,68 @@
using System;
using Avalonia.Media;
namespace Avalonia.Rendering.SceneGraph
{
internal static class LineBoundsHelper
{
private static double CalculateAngle(Point p1, Point p2)
{
var xDiff = p2.X - p1.X;
var yDiff = p2.Y - p1.Y;
return Math.Atan2(yDiff, xDiff);
}
internal static double CalculateOppSide(double angle, double hyp)
{
return Math.Sin(angle) * hyp;
}
internal static double CalculateAdjSide(double angle, double hyp)
{
return Math.Cos(angle) * hyp;
}
private static (Point p1, Point p2) TranslatePointsAlongTangent(Point p1, Point p2, double angle, double distance)
{
var xDiff = CalculateOppSide(angle, distance);
var yDiff = CalculateAdjSide(angle, distance);
var c1 = new Point(p1.X + xDiff, p1.Y - yDiff);
var c2 = new Point(p1.X - xDiff, p1.Y + yDiff);
var c3 = new Point(p2.X + xDiff, p2.Y - yDiff);
var c4 = new Point(p2.X - xDiff, p2.Y + yDiff);
var minX = Math.Min(c1.X, Math.Min(c2.X, Math.Min(c3.X, c4.X)));
var minY = Math.Min(c1.Y, Math.Min(c2.Y, Math.Min(c3.Y, c4.Y)));
var maxX = Math.Max(c1.X, Math.Max(c2.X, Math.Max(c3.X, c4.X)));
var maxY = Math.Max(c1.Y, Math.Max(c2.Y, Math.Max(c3.Y, c4.Y)));
return (new Point(minX, minY), new Point(maxX, maxY));
}
private static Rect CalculateBounds(Point p1, Point p2, double thickness, double angleToCorner)
{
var pts = TranslatePointsAlongTangent(p1, p2, angleToCorner, thickness / 2);
return new Rect(pts.p1, pts.p2);
}
public static Rect CalculateBounds(Point p1, Point p2, IPen p)
{
var radians = CalculateAngle(p1, p2);
if (p.LineCap != PenLineCap.Flat)
{
var pts = TranslatePointsAlongTangent(p1, p2, radians - Math.PI / 2, p.Thickness / 2);
return CalculateBounds(pts.p1, pts.p2, p.Thickness, radians);
}
else
{
return CalculateBounds(p1, p2, p.Thickness, radians);
}
}
}
}

2
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
Point p1,
Point p2,
IDictionary<IVisual, Scene> childScenes = null)
: base(new Rect(p1, p2), transform, pen)
: base(LineBoundsHelper.CalculateBounds(p1, p2, pen), transform)
{
Transform = transform;
Pen = pen?.ToImmutable();

4
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs

@ -18,7 +18,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="bounds">The bounds of the mask.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null)
: base(Rect.Empty, Matrix.Identity, null)
: base(Rect.Empty, Matrix.Identity)
{
Mask = mask?.ToImmutable();
MaskBounds = bounds;
@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph
/// opacity mask pop.
/// </summary>
public OpacityMaskNode()
: base(Rect.Empty, Matrix.Identity, null)
: base(Rect.Empty, Matrix.Identity)
{
}

2
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -28,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph
RoundedRect rect,
BoxShadows boxShadows,
IDictionary<IVisual, Scene> childScenes = null)
: base(boxShadows.TransformBounds(rect.Rect), transform, pen)
: base(boxShadows.TransformBounds(rect.Rect).Inflate((pen?.Thickness ?? 0) / 2), transform)
{
Transform = transform;
Brush = brush?.ToImmutable();

2
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@ -24,7 +24,7 @@ namespace Avalonia.Rendering.SceneGraph
Point origin,
IFormattedTextImpl text,
IDictionary<IVisual, Scene> childScenes = null)
: base(text.Bounds.Translate(origin), transform, null)
: base(text.Bounds.Translate(origin), transform)
{
Transform = transform;
Foreground = foreground?.ToImmutable();

2
src/Skia/Avalonia.Skia/GeometryImpl.cs

@ -95,7 +95,7 @@ namespace Avalonia.Skia
UpdatePathCache(strokeWidth);
}
return _pathCache.CachedGeometryRenderBounds.Inflate(strokeWidth / 2.0);
return _pathCache.CachedGeometryRenderBounds;
}
/// <inheritdoc />

46
tests/Avalonia.Visuals.UnitTests/Media/PixelRectTests.cs

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Text;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class PixelRectTests
{
[Fact]
public void FromRect_Snaps_To_Device_Pixels()
{
var rect = new Rect(189, 189, 26, 164);
var result = PixelRect.FromRect(rect, 1.5);
Assert.Equal(new PixelRect(283, 283, 40, 247), result);
}
[Fact]
public void FromRect_Vector_Snaps_To_Device_Pixels()
{
var rect = new Rect(189, 189, 26, 164);
var result = PixelRect.FromRect(rect, new Vector(1.5, 1.5));
Assert.Equal(new PixelRect(283, 283, 40, 247), result);
}
[Fact]
public void FromRectWithDpi_Snaps_To_Device_Pixels()
{
var rect = new Rect(189, 189, 26, 164);
var result = PixelRect.FromRectWithDpi(rect, 144);
Assert.Equal(new PixelRect(283, 283, 40, 247), result);
}
[Fact]
public void FromRectWithDpi_Vector_Snaps_To_Device_Pixels()
{
var rect = new Rect(189, 189, 26, 164);
var result = PixelRect.FromRectWithDpi(rect, new Vector(144, 144));
Assert.Equal(new PixelRect(283, 283, 40, 247), result);
}
}
}

17
tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs

@ -35,7 +35,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
double expectedWidth,
double expectedHeight)
{
var target = new TestDrawOperation(
var target = new TestRectangleDrawOperation(
new Rect(x, y, width, height),
Matrix.CreateScale(scaleX, scaleY),
penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null);
@ -74,10 +74,23 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
geometryNode.HitTest(new Point());
}
private class TestRectangleDrawOperation : RectangleNode
{
public TestRectangleDrawOperation(Rect bounds, Matrix transform, Pen pen)
: base(transform, pen.Brush, pen, bounds, new BoxShadows())
{
}
public override bool HitTest(Point p) => false;
public override void Render(IDrawingContextImpl context) { }
}
private class TestDrawOperation : DrawOperation
{
public TestDrawOperation(Rect bounds, Matrix transform, Pen pen)
:base(bounds, transform, pen)
:base(bounds, transform)
{
}

Loading…
Cancel
Save