Browse Source

Make cairo render tests pass.

scenegraph-after-breakage
Steven Kirk 9 years ago
parent
commit
2a79137e56
  1. 2
      src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj
  2. 24
      src/Gtk/Avalonia.Cairo/CairoPlatform.cs
  3. 61
      src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
  4. 55
      src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs
  5. 2
      src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs
  6. 7
      src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs
  7. 2
      src/Gtk/Avalonia.Cairo/Media/SolidColorBrushImpl.cs
  8. 2
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  9. 55
      src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs
  10. 14
      src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs
  11. 4
      src/Gtk/Avalonia.Cairo/RenderTarget.cs
  12. 71
      tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject
  13. 6
      tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
  14. 5
      tests/Avalonia.RenderTests/Shapes/EllipseTests.cs
  15. BIN
      tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png
  16. BIN
      tests/TestFiles/Cairo/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png

2
src/Gtk/Avalonia.Cairo/Avalonia.Cairo.csproj

@ -61,7 +61,6 @@
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\Imaging\RenderTargetBitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\TileBrushes.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RenderTarget.cs" />
<Compile Include="CairoExtensions.cs" />
@ -71,7 +70,6 @@
<Compile Include="Media\SolidColorBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\ImageBrushImpl.cs" />
<Compile Include="Media\VisualBrushImpl.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj">

24
src/Gtk/Avalonia.Cairo/CairoPlatform.cs

@ -26,14 +26,22 @@ namespace Avalonia.Cairo
{
using System.IO;
using global::Cairo;
using Rendering;
public class CairoPlatform : IPlatformRenderInterface
public class CairoPlatform : IPlatformRenderInterface, IRendererFactory
{
private static readonly CairoPlatform s_instance = new CairoPlatform();
private static readonly Pango.Context s_pangoContext = CreatePangoContext();
public static void Initialize() => AvaloniaLocator.CurrentMutable.Bind<IPlatformRenderInterface>().ToConstant(s_instance);
public static bool UseImmediateRenderer { get; set; }
public static void Initialize()
{
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(s_instance)
.Bind<IRendererFactory>().ToConstant(s_instance);
}
public IBitmapImpl CreateBitmap(int width, int height)
{
@ -53,6 +61,18 @@ namespace Avalonia.Cairo
return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight, constraint);
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
if (UseImmediateRenderer)
{
return new ImmediateRenderer(root, renderLoop);
}
else
{
return new DeferredRenderer(root, renderLoop);
}
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
var accessor = surfaces?.OfType<Func<Gdk.Drawable>>().FirstOrDefault();

61
src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs

@ -5,14 +5,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.InteropServices;
using Avalonia.Cairo.Media.Imaging;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Cairo.Media
{
using Avalonia.Media.Imaging;
using Platform;
using Cairo = global::Cairo;
/// <summary>
@ -20,32 +19,30 @@ namespace Avalonia.Cairo.Media
/// </summary>
public class DrawingContext : IDrawingContextImpl, IDisposable
{
/// <summary>
/// The cairo context.
/// </summary>
private readonly Cairo.Context _context;
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly Stack<BrushImpl> _maskStack = new Stack<BrushImpl>();
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The target surface.</param>
public DrawingContext(Cairo.Surface surface)
public DrawingContext(Cairo.Surface surface, IVisualBrushRenderer visualBrushRenderer)
{
_context = new Cairo.Context(surface);
_visualBrushRenderer = visualBrushRenderer;
}
/// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary>
/// <param name="surface">The GDK drawable.</param>
public DrawingContext(Gdk.Drawable drawable)
public DrawingContext(Gdk.Drawable drawable, IVisualBrushRenderer visualBrushRenderer)
{
_context = Gdk.CairoHelper.Create(drawable);
_visualBrushRenderer = visualBrushRenderer;
}
private Matrix _transform = Matrix.Identity;
/// <summary>
/// Gets the current transform of the drawing context.
@ -120,7 +117,9 @@ namespace Avalonia.Cairo.Media
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
throw new NotImplementedException();
PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.PixelWidth, source.PixelHeight), destRect);
PopOpacityMask();
}
/// <summary>
@ -299,11 +298,11 @@ namespace Avalonia.Cairo.Media
private BrushImpl CreateBrushImpl(IBrush brush, Size destinationSize)
{
var solid = brush as SolidColorBrush;
var linearGradientBrush = brush as LinearGradientBrush;
var radialGradientBrush = brush as RadialGradientBrush;
var imageBrush = brush as ImageBrush;
var visualBrush = brush as VisualBrush;
var solid = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
var radialGradientBrush = brush as IRadialGradientBrush;
var imageBrush = brush as IImageBrush;
var visualBrush = brush as IVisualBrush;
BrushImpl impl = null;
if (solid != null)
@ -320,7 +319,35 @@ namespace Avalonia.Cairo.Media
}
else if (imageBrush != null)
{
impl = new ImageBrushImpl(imageBrush, destinationSize);
impl = new ImageBrushImpl(imageBrush, (BitmapImpl)imageBrush.Source.PlatformImpl, destinationSize);
}
else if (visualBrush != null)
{
if (_visualBrushRenderer != null)
{
var intermediateSize = _visualBrushRenderer.GetRenderTargetSize(visualBrush);
if (intermediateSize.Width >= 1 && intermediateSize.Height >= 1)
{
using (var intermediate = new Cairo.ImageSurface(Cairo.Format.ARGB32, (int)intermediateSize.Width, (int)intermediateSize.Height))
{
using (var ctx = new RenderTarget(intermediate).CreateDrawingContext(_visualBrushRenderer))
{
ctx.Clear(Colors.Transparent);
_visualBrushRenderer.RenderVisualBrush(ctx, visualBrush);
}
return new ImageBrushImpl(
visualBrush,
new RenderTargetBitmapImpl(intermediate),
destinationSize);
}
}
}
else
{
throw new NotSupportedException("No IVisualBrushRenderer was supplied to DrawingContextImpl.");
}
}
else
{

55
src/Gtk/Avalonia.Cairo/Media/ImageBrushImpl.cs

@ -1,14 +1,59 @@
using System;
using Avalonia.Cairo.Media.Imaging;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.Utilities;
using Gdk;
using global::Cairo;
namespace Avalonia.Cairo.Media
{
public class ImageBrushImpl : BrushImpl
{
public ImageBrushImpl(Avalonia.Media.ImageBrush brush, Size destinationSize)
{
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize);
}
}
public ImageBrushImpl(
ITileBrush brush,
IBitmapImpl bitmap,
Size targetSize)
{
var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize);
using (var intermediate = new ImageSurface(Format.ARGB32, (int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Height))
{
using (var context = new RenderTarget(intermediate).CreateDrawingContext(null))
{
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(bitmap, 1, rect, rect);
context.PopClip();
}
var result = new SurfacePattern(intermediate);
if ((brush.TileMode & TileMode.FlipXY) != 0)
{
// TODO: Currently always FlipXY as that's all cairo supports natively.
// Support separate FlipX and FlipY by drawing flipped images to intermediate
// surface.
result.Extend = Extend.Reflect;
}
else
{
result.Extend = Extend.Repeat;
}
if (brush.TileMode != TileMode.None)
{
var matrix = result.Matrix;
matrix.InitTranslate(-calc.DestinationRect.X, -calc.DestinationRect.Y);
result.Matrix = matrix;
}
PlatformBrush = result;
}
}
}
}

2
src/Gtk/Avalonia.Cairo/Media/LinearGradientBrushImpl.cs

@ -5,7 +5,7 @@ namespace Avalonia.Cairo
{
public class LinearGradientBrushImpl : BrushImpl
{
public LinearGradientBrushImpl(Avalonia.Media.LinearGradientBrush brush, Size destinationSize)
public LinearGradientBrushImpl(Avalonia.Media.ILinearGradientBrush brush, Size destinationSize)
{
var start = brush.StartPoint.ToPixels(destinationSize);
var end = brush.EndPoint.ToPixels(destinationSize);

7
src/Gtk/Avalonia.Cairo/Media/RadialGradientBrushImpl.cs

@ -5,13 +5,14 @@ namespace Avalonia.Cairo
{
public class RadialGradientBrushImpl : BrushImpl
{
public RadialGradientBrushImpl(Avalonia.Media.RadialGradientBrush brush, Size destinationSize)
public RadialGradientBrushImpl(Avalonia.Media.IRadialGradientBrush brush, Size destinationSize)
{
var center = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize);
var radius = brush.Radius;
var radius = brush.Radius * Math.Min(destinationSize.Width, destinationSize.Height);
this.PlatformBrush = new RadialGradient(center.X, center.Y, radius, gradientOrigin.X, gradientOrigin.Y, radius);
this.PlatformBrush = new RadialGradient(center.X, center.Y, 1, gradientOrigin.X, gradientOrigin.Y, radius);
this.PlatformBrush.Matrix = Matrix.Identity.ToCairo();
foreach (var stop in brush.GradientStops)
{

2
src/Gtk/Avalonia.Cairo/Media/SolidColorBrushImpl.cs

@ -5,7 +5,7 @@ namespace Avalonia.Cairo
{
public class SolidColorBrushImpl : BrushImpl
{
public SolidColorBrushImpl(Avalonia.Media.SolidColorBrush brush, double opacityOverride = 1.0f)
public SolidColorBrushImpl(Avalonia.Media.ISolidColorBrush brush, double opacityOverride = 1.0f)
{
var color = brush?.Color.ToCairo() ?? new Color();

2
src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs

@ -52,7 +52,7 @@ namespace Avalonia.Cairo.Media
public Rect GetRenderBounds(double strokeThickness)
{
// TODO: Calculate properly.
return Bounds.Inflate(strokeThickness);
return Bounds.TransformToAABB(Transform).Inflate(strokeThickness);
}
public IStreamGeometryContextImpl Open()

55
src/Gtk/Avalonia.Cairo/Media/TileBrushes.cs

@ -1,55 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Cairo;
using Avalonia.Cairo.Media.Imaging;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.RenderHelpers;
namespace Avalonia.Cairo.Media
{
internal static class TileBrushes
{
public static SurfacePattern CreateTileBrush(TileBrush brush, Size targetSize)
{
throw new NotImplementedException();
//// var helper = new TileBrushImplHelper(brush, targetSize);
//// if (!helper.IsValid)
//// return null;
////using (var intermediate = new ImageSurface(Format.ARGB32, (int)helper.IntermediateSize.Width, (int)helper.IntermediateSize.Height))
//// using (var ctx = new RenderTarget(intermediate).CreateDrawingContext())
//// {
//// helper.DrawIntermediate(new Avalonia.Media.DrawingContext(ctx));
//// var result = new SurfacePattern(intermediate);
//// if ((brush.TileMode & TileMode.FlipXY) != 0)
//// {
//// // TODO: Currently always FlipXY as that's all cairo supports natively.
//// // Support separate FlipX and FlipY by drawing flipped images to intermediate
//// // surface.
//// result.Extend = Extend.Reflect;
//// }
//// else
//// {
//// result.Extend = Extend.Repeat;
//// }
//// if (brush.TileMode != TileMode.None)
//// {
//// var matrix = result.Matrix;
//// matrix.InitTranslate(-helper.DestinationRect.X, -helper.DestinationRect.Y);
//// result.Matrix = matrix;
//// }
//// return result;
//// }
}
}
}

14
src/Gtk/Avalonia.Cairo/Media/VisualBrushImpl.cs

@ -1,14 +0,0 @@
using System;
using global::Cairo;
namespace Avalonia.Cairo.Media
{
public class VisualBrushImpl : BrushImpl
{
public VisualBrushImpl(Avalonia.Media.VisualBrush brush, Size destinationSize)
{
this.PlatformBrush = TileBrushes.CreateTileBrush(brush, destinationSize);
}
}
}

4
src/Gtk/Avalonia.Cairo/RenderTarget.cs

@ -47,9 +47,9 @@ namespace Avalonia.Cairo
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
if (_drawableAccessor != null)
return new Media.DrawingContext(_drawableAccessor());
return new Media.DrawingContext(_drawableAccessor(), visualBrushRenderer);
if (_surface != null)
return new Media.DrawingContext(_surface);
return new Media.DrawingContext(_surface, visualBrushRenderer);
throw new InvalidOperationException("Unspecified render target");
}

71
tests/Avalonia.RenderTests/Avalonia.Cairo.RenderTests.v3.ncrunchproject

@ -3,77 +3,6 @@
<HiddenComponentWarnings>
<Value>AbnormalReferenceResolution</Value>
</HiddenComponentWarnings>
<IgnoredTests>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Controls.BorderTests</FixtureName>
</FixtureTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_Fill_NoTile</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_FlipXY_TopLeftDest</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_BottomRight</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_Center</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterDest</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterSource</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_BottomRightQuarterSource_BottomRightQuarterDest</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_Tile_BottomRightQuarterSource_CenterQuarterDest</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_Uniform_NoTile</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_UniformToFill_NoTile</TestName>
</NamedTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Controls.ImageTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.GeometryClippingTests</FixtureName>
</FixtureTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.LinearGradientBrushTests.LinearGradientBrush_RedBlue_Vertical_Fill</TestName>
</NamedTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Media.VisualBrushTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.OpacityMaskTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Shapes.EllipseTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Shapes.LineTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Shapes.PathTests</FixtureName>
</FixtureTestSelector>
<FixtureTestSelector>
<FixtureName>Avalonia.Cairo.RenderTests.Shapes.RectangleTests</FixtureName>
</FixtureTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.ImageBrushTests.ImageBrush_NoStretch_NoTile_Alignment_TopLeft</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.LinearGradientBrushTests.LinearGradientBrush_RedBlue_Horizontal_Fill</TestName>
</NamedTestSelector>
<NamedTestSelector>
<TestName>Avalonia.Cairo.RenderTests.Media.RadialGradientBrushTests.RadialGradientBrush_RedBlue</TestName>
</NamedTestSelector>
</IgnoredTests>
<PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
</Settings>
</ProjectConfiguration>

6
tests/Avalonia.RenderTests/Media/VisualBrushTests.cs

@ -427,7 +427,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImages();
}
#if AVALONIA_CAIRO
[Fact(Skip = "Font scaling currently broken on cairo")]
#elif AVALONIA_SKIA_SKIP_FAIL
[Fact(Skip = "FIXME")]
#else
[Fact]
#endif
public async Task VisualBrush_InTree_Visual()
{
Border source;

5
tests/Avalonia.RenderTests/Shapes/EllipseTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
@ -22,7 +23,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
}
[Fact]
public void Circle_1px_Stroke()
public async Task Circle_1px_Stroke()
{
Decorator target = new Decorator
{
@ -36,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
}
};
RenderToFile(target);
await RenderToFile(target);
CompareImages();
}
}

BIN
tests/TestFiles/Cairo/GeometryClipping/Geometry_Clip_Clips_Path.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

After

Width:  |  Height:  |  Size: 692 B

BIN
tests/TestFiles/Cairo/Media/RadialGradientBrush/RadialGradientBrush_RedBlue.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Loading…
Cancel
Save