diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 41164c7780..c15abad188 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -33,6 +33,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs
new file mode 100644
index 0000000000..2e59d934a1
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs
@@ -0,0 +1,119 @@
+using System;
+using System.Diagnostics;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+using Avalonia.Skia;
+using Avalonia.Threading;
+using SkiaSharp;
+
+namespace RenderDemo.Pages
+{
+ public class CustomSkiaPage : Control
+ {
+ public CustomSkiaPage()
+ {
+ ClipToBounds = true;
+ }
+
+ class CustomDrawOp : ICustomDrawOperation
+ {
+ private readonly FormattedText _noSkia;
+
+ public CustomDrawOp(Rect bounds, FormattedText noSkia)
+ {
+ _noSkia = noSkia;
+ Bounds = bounds;
+ }
+
+ public void Dispose()
+ {
+ // No-op
+ }
+
+ public Rect Bounds { get; }
+ public bool HitTest(Point p) => false;
+ public bool Equals(ICustomDrawOperation other) => false;
+ static Stopwatch St = Stopwatch.StartNew();
+ public void Render(IDrawingContextImpl context)
+ {
+ var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
+ if (canvas == null)
+ context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
+ else
+ {
+ canvas.Save();
+ // create the first shader
+ var colors = new SKColor[] {
+ new SKColor(0, 255, 255),
+ new SKColor(255, 0, 255),
+ new SKColor(255, 255, 0),
+ new SKColor(0, 255, 255)
+ };
+
+ var sx = Animate(100, 2, 10);
+ var sy = Animate(1000, 5, 15);
+ var lightPosition = new SKPoint(
+ (float)(Bounds.Width / 2 + Math.Cos(St.Elapsed.TotalSeconds) * Bounds.Width / 4),
+ (float)(Bounds.Height / 2 + Math.Sin(St.Elapsed.TotalSeconds) * Bounds.Height / 4));
+ using (var sweep =
+ SKShader.CreateSweepGradient(new SKPoint((int)Bounds.Width / 2, (int)Bounds.Height / 2), colors,
+ null))
+ using(var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0))
+ using(var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop))
+ using(var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15)))
+ using (var paint = new SKPaint
+ {
+ Shader = shader,
+ ImageFilter = blur
+ })
+ canvas.DrawPaint(paint);
+
+ using (var pseudoLight = SKShader.CreateRadialGradient(
+ lightPosition,
+ (float) (Bounds.Width/3),
+ new [] {
+ new SKColor(255, 200, 200, 100),
+ SKColors.Transparent,
+ new SKColor(40,40,40, 220),
+ new SKColor(20,20,20, (byte)Animate(100, 200,220)) },
+ new float[] { 0.3f, 0.3f, 0.8f, 1 },
+ SKShaderTileMode.Clamp))
+ using (var paint = new SKPaint
+ {
+ Shader = pseudoLight
+ })
+ canvas.DrawPaint(paint);
+ canvas.Restore();
+ }
+ }
+ static int Animate(int d, int from, int to)
+ {
+ var ms = (int)(St.ElapsedMilliseconds / d);
+ var diff = to - from;
+ var range = diff * 2;
+ var v = ms % range;
+ if (v > diff)
+ v = range - v;
+ var rv = v + from;
+ if (rv < from || rv > to)
+ throw new Exception("WTF");
+ return rv;
+ }
+ }
+
+
+
+ public override void Render(DrawingContext context)
+ {
+ var noSkia = new FormattedText()
+ {
+ Text = "Current rendering API is not Skia"
+ };
+ context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
+ Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index fd593db991..d3af71ffcb 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
@@ -131,6 +132,12 @@ namespace Avalonia.Media
}
}
+ ///
+ /// Draws a custom drawing operation
+ ///
+ /// custom operation
+ public void Custom(ICustomDrawOperation custom) => PlatformImpl.Custom(custom);
+
///
/// Draws text.
///
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index 57b974f900..e5be04ebf9 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Media;
+using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
@@ -139,5 +140,11 @@ namespace Avalonia.Platform
/// Pops the latest pushed geometry clip.
///
void PopGeometryClip();
+
+ ///
+ /// Adds a custom draw operation
+ ///
+ /// Custom draw operation
+ void Custom(ICustomDrawOperation custom);
}
}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
new file mode 100644
index 0000000000..68e2237430
--- /dev/null
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
@@ -0,0 +1,39 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace Avalonia.Rendering.SceneGraph
+{
+ internal sealed class CustomDrawOperation : DrawOperation
+ {
+ public Matrix Transform { get; }
+ public ICustomDrawOperation Custom { get; }
+ public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform)
+ : base(custom.Bounds, transform, null)
+ {
+ Transform = transform;
+ Custom = custom;
+ }
+
+ public override bool HitTest(Point p)
+ {
+ return Custom.HitTest(p * Transform);
+ }
+
+ public override void Render(IDrawingContextImpl context)
+ {
+ context.Transform = Transform;
+ Custom.Render(context);
+ }
+
+ public override void Dispose() => Custom.Dispose();
+
+ public bool Equals(Matrix transform, ICustomDrawOperation custom) =>
+ Transform == transform && Custom?.Equals(custom) == true;
+ }
+
+ public interface ICustomDrawOperation : IDrawOperation, IEquatable
+ {
+
+ }
+}
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index dfed0d911c..0b33851911 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -165,6 +165,15 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
}
+
+ public void Custom(ICustomDrawOperation custom)
+ {
+ var next = NextDrawAs();
+ if (next == null || !next.Item.Equals(Transform, custom))
+ Add(new CustomDrawOperation(custom, Transform));
+ else
+ ++_drawOperationindex;
+ }
///
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 10e746cbd5..af6b0fe22f 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -9,6 +9,7 @@ using System.Threading;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Rendering.SceneGraph;
using Avalonia.Rendering.Utilities;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
@@ -19,7 +20,7 @@ namespace Avalonia.Skia
///
/// Skia based drawing context.
///
- public class DrawingContextImpl : IDrawingContextImpl
+ internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl
{
private IDisposable[] _disposables;
private readonly Vector _dpi;
@@ -99,6 +100,8 @@ namespace Avalonia.Skia
///
public SKCanvas Canvas { get; }
+ SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas;
+
///
public void Clear(Color color)
{
@@ -296,6 +299,8 @@ namespace Avalonia.Skia
Canvas.Restore();
}
+ public void Custom(ICustomDrawOperation custom) => custom.Render(this);
+
///
public void PushOpacityMask(IBrush mask, Rect bounds)
{
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index 7e3f4aa40a..47852a4d9e 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Skia
///
/// Skia formatted text implementation.
///
- public class FormattedTextImpl : IFormattedTextImpl
+ internal class FormattedTextImpl : IFormattedTextImpl
{
public FormattedTextImpl(
string text,
diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
index 0cb4c3db67..1af3d2968c 100644
--- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Skia
///
/// Skia render target that renders to a framebuffer surface. No gpu acceleration available.
///
- public class FramebufferRenderTarget : IRenderTarget
+ internal class FramebufferRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _platformSurface;
private SKImageInfo _currentImageInfo;
diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs
index fbbd6eb58c..5940de418e 100644
--- a/src/Skia/Avalonia.Skia/GeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs
@@ -11,7 +11,7 @@ namespace Avalonia.Skia
///
/// A Skia implementation of .
///
- public abstract class GeometryImpl : IGeometryImpl
+ internal abstract class GeometryImpl : IGeometryImpl
{
private PathCache _pathCache;
diff --git a/src/Skia/Avalonia.Skia/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
index cd8c334b53..7c0c42ca37 100644
--- a/src/Skia/Avalonia.Skia/GlRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/GlRenderTarget.cs
@@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
- public class GlRenderTarget : IRenderTarget
+ internal class GlRenderTarget : IRenderTarget
{
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;
diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
new file mode 100644
index 0000000000..ff82990c47
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
@@ -0,0 +1,10 @@
+using Avalonia.Platform;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ public interface ISkiaDrawingContextImpl : IDrawingContextImpl
+ {
+ SKCanvas SkCanvas { get; }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 49992df395..f283040eac 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Skia
///
/// Immutable Skia bitmap.
///
- public class ImmutableBitmap : IDrawableBitmapImpl
+ internal class ImmutableBitmap : IDrawableBitmapImpl
{
private readonly SKImage _image;
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index c6e68b1c8b..6e13b154b4 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Skia
///
/// Skia platform render interface.
///
- public class PlatformRenderInterface : IPlatformRenderInterface
+ internal class PlatformRenderInterface : IPlatformRenderInterface
{
private GRContext GrContext { get; }
diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
index c19ff79d87..2764c65c6f 100644
--- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
@@ -10,7 +10,7 @@ namespace Avalonia.Skia
///
/// A Skia implementation of a .
///
- public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
+ internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
{
private Rect _bounds;
private readonly SKPath _effectivePath;
diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
index 4b7eae1af4..9340c9add4 100644
--- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
@@ -14,7 +14,7 @@ namespace Avalonia.Skia
///
/// Skia render target that writes to a surface.
///
- public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
+ internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
{
private readonly SKSurface _surface;
private readonly SKCanvas _canvas;
diff --git a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
index e95069eef3..9826bc2ce3 100644
--- a/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
+++ b/src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Skia
///
/// A Skia implementation of a .
///
- public class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
+ internal class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
{
///
/// Initializes a new instance of the class.
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index c9d6fa6c11..fea21cde58 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Skia
///
/// Skia based writeable bitmap.
///
- public class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
+ internal class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
{
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 5e150ff647..8ecada1ced 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
+using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using SharpDX;
using SharpDX.Direct2D1;
@@ -508,5 +509,7 @@ namespace Avalonia.Direct2D1.Media
{
PopLayer();
}
+
+ public void Custom(ICustomDrawOperation custom) => custom.Render(this);
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
index 7164ec7c0d..91adc29214 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
@@ -9,7 +9,7 @@ using DWrite = SharpDX.DirectWrite;
namespace Avalonia.Direct2D1.Media
{
- public class FormattedTextImpl : IFormattedTextImpl
+ internal class FormattedTextImpl : IFormattedTextImpl
{
public FormattedTextImpl(
string text,