@ -1,6 +1,6 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="SkiaSharp" Version="1.57.1" /> |
<PackageReference Include="SkiaSharp" Version="1.60.0" /> |
||||
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" /> |
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -1,142 +0,0 @@ |
|||||
using System; |
|
||||
using System.IO; |
|
||||
using Avalonia.Platform; |
|
||||
using Avalonia.Rendering; |
|
||||
using SkiaSharp; |
|
||||
|
|
||||
namespace Avalonia.Skia |
|
||||
{ |
|
||||
class BitmapImpl : IRenderTargetBitmapImpl, IWriteableBitmapImpl |
|
||||
{ |
|
||||
private Vector _dpi; |
|
||||
|
|
||||
public SKBitmap Bitmap { get; private set; } |
|
||||
|
|
||||
public BitmapImpl(SKBitmap bm) |
|
||||
{ |
|
||||
Bitmap = bm; |
|
||||
PixelHeight = bm.Height; |
|
||||
PixelWidth = bm.Width; |
|
||||
_dpi = new Vector(96, 96); |
|
||||
} |
|
||||
|
|
||||
static void ReleaseProc(IntPtr address, object ctx) |
|
||||
{ |
|
||||
((IUnmanagedBlob) ctx).Dispose(); |
|
||||
} |
|
||||
|
|
||||
private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc; |
|
||||
|
|
||||
public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null) |
|
||||
{ |
|
||||
PixelHeight = height; |
|
||||
PixelWidth = width; |
|
||||
_dpi = dpi; |
|
||||
var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; |
|
||||
var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>(); |
|
||||
var runtime = runtimePlatform?.GetRuntimeInfo(); |
|
||||
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) |
|
||||
colorType = SKColorType.Bgra8888; |
|
||||
|
|
||||
if (runtimePlatform != null) |
|
||||
{ |
|
||||
Bitmap = new SKBitmap(); |
|
||||
var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); |
|
||||
var plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>(); |
|
||||
var blob = plat.AllocBlob(nfo.BytesSize); |
|
||||
Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); |
|
||||
|
|
||||
} |
|
||||
else |
|
||||
Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); |
|
||||
Bitmap.Erase(SKColor.Empty); |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
Bitmap.Dispose(); |
|
||||
} |
|
||||
|
|
||||
public int PixelWidth { get; private set; } |
|
||||
public int PixelHeight { get; private set; } |
|
||||
|
|
||||
class BitmapDrawingContext : DrawingContextImpl |
|
||||
{ |
|
||||
private readonly SKSurface _surface; |
|
||||
|
|
||||
public BitmapDrawingContext(SKBitmap bitmap, Vector dpi, IVisualBrushRenderer visualBrushRenderer) |
|
||||
: this(CreateSurface(bitmap), dpi, visualBrushRenderer) |
|
||||
{ |
|
||||
CanUseLcdRendering = false; |
|
||||
} |
|
||||
|
|
||||
private static SKSurface CreateSurface(SKBitmap bitmap) |
|
||||
{ |
|
||||
IntPtr length; |
|
||||
var rv = SKSurface.Create(bitmap.Info, bitmap.GetPixels(out length), bitmap.RowBytes); |
|
||||
if (rv == null) |
|
||||
throw new Exception("Unable to create Skia surface"); |
|
||||
return rv; |
|
||||
} |
|
||||
|
|
||||
public BitmapDrawingContext(SKSurface surface, Vector dpi, IVisualBrushRenderer visualBrushRenderer) |
|
||||
: base(surface.Canvas, dpi, visualBrushRenderer) |
|
||||
{ |
|
||||
_surface = surface; |
|
||||
} |
|
||||
|
|
||||
public override void Dispose() |
|
||||
{ |
|
||||
base.Dispose(); |
|
||||
_surface.Dispose(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
|
||||
{ |
|
||||
return new BitmapDrawingContext(Bitmap, _dpi, visualBrushRenderer); |
|
||||
} |
|
||||
|
|
||||
public void Save(Stream stream) |
|
||||
{ |
|
||||
IntPtr length; |
|
||||
using (var image = SKImage.FromPixels(Bitmap.Info, Bitmap.GetPixels(out length), Bitmap.RowBytes)) |
|
||||
using (var data = image.Encode()) |
|
||||
{ |
|
||||
data.SaveTo(stream); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void Save(string fileName) |
|
||||
{ |
|
||||
using (var stream = File.Create(fileName)) |
|
||||
Save(stream); |
|
||||
} |
|
||||
|
|
||||
class BitmapFramebuffer : ILockedFramebuffer |
|
||||
{ |
|
||||
private SKBitmap _bmp; |
|
||||
|
|
||||
public BitmapFramebuffer(SKBitmap bmp) |
|
||||
{ |
|
||||
_bmp = bmp; |
|
||||
_bmp.LockPixels(); |
|
||||
} |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
_bmp.UnlockPixels(); |
|
||||
_bmp = null; |
|
||||
} |
|
||||
|
|
||||
public IntPtr Address => _bmp.GetPixels(); |
|
||||
public int Width => _bmp.Width; |
|
||||
public int Height => _bmp.Height; |
|
||||
public int RowBytes => _bmp.RowBytes; |
|
||||
public Vector Dpi { get; } = new Vector(96, 96); |
|
||||
public PixelFormat Format => _bmp.ColorType.ToPixelFormat(); |
|
||||
} |
|
||||
|
|
||||
public ILockedFramebuffer Lock() => new BitmapFramebuffer(Bitmap); |
|
||||
} |
|
||||
} |
|
||||
@ -1,82 +1,199 @@ |
|||||
using System; |
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
using System.Collections.Generic; |
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
using System.Text; |
|
||||
|
using System; |
||||
|
using System.Reactive.Disposables; |
||||
using Avalonia.Controls.Platform.Surfaces; |
using Avalonia.Controls.Platform.Surfaces; |
||||
using Avalonia.Media; |
|
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
using Avalonia.Rendering; |
using Avalonia.Rendering; |
||||
|
using Avalonia.Skia.Helpers; |
||||
using SkiaSharp; |
using SkiaSharp; |
||||
|
|
||||
namespace Avalonia.Skia |
namespace Avalonia.Skia |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Skia render target that renders to a framebuffer surface. No gpu acceleration available.
|
||||
|
/// </summary>
|
||||
public class FramebufferRenderTarget : IRenderTarget |
public class FramebufferRenderTarget : IRenderTarget |
||||
{ |
{ |
||||
private readonly IFramebufferPlatformSurface _surface; |
private readonly IFramebufferPlatformSurface _platformSurface; |
||||
|
private SKImageInfo _currentImageInfo; |
||||
|
private IntPtr _currentFramebufferAddress; |
||||
|
private SKSurface _framebufferSurface; |
||||
|
private PixelFormatConversionShim _conversionShim; |
||||
|
private IDisposable _preFramebufferCopyHandler; |
||||
|
|
||||
public FramebufferRenderTarget(IFramebufferPlatformSurface surface) |
/// <summary>
|
||||
|
/// Create new framebuffer render target using a target surface.
|
||||
|
/// </summary>
|
||||
|
/// <param name="platformSurface">Target surface.</param>
|
||||
|
public FramebufferRenderTarget(IFramebufferPlatformSurface platformSurface) |
||||
{ |
{ |
||||
_surface = surface; |
_platformSurface = platformSurface; |
||||
} |
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
public void Dispose() |
public void Dispose() |
||||
{ |
{ |
||||
//Nothing to do here, since we don't own framebuffer
|
FreeSurface(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
||||
|
{ |
||||
|
var framebuffer = _platformSurface.Lock(); |
||||
|
var framebufferImageInfo = new SKImageInfo(framebuffer.Width, framebuffer.Height, |
||||
|
framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); |
||||
|
|
||||
|
CreateSurface(framebufferImageInfo, framebuffer); |
||||
|
|
||||
|
var canvas = _framebufferSurface.Canvas; |
||||
|
|
||||
|
canvas.RestoreToCount(-1); |
||||
|
canvas.Save(); |
||||
|
canvas.ResetMatrix(); |
||||
|
|
||||
|
var createInfo = new DrawingContextImpl.CreateInfo |
||||
|
{ |
||||
|
Canvas = canvas, |
||||
|
Dpi = framebuffer.Dpi, |
||||
|
VisualBrushRenderer = visualBrushRenderer, |
||||
|
DisableTextLcdRendering = true |
||||
|
}; |
||||
|
|
||||
|
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, framebuffer); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Check if two images info are compatible.
|
||||
|
/// </summary>
|
||||
|
/// <param name="currentImageInfo">Current.</param>
|
||||
|
/// <param name="desiredImageInfo">Desired.</param>
|
||||
|
/// <returns>True, if images are compatible.</returns>
|
||||
|
private static bool AreImageInfosCompatible(SKImageInfo currentImageInfo, SKImageInfo desiredImageInfo) |
||||
|
{ |
||||
|
return currentImageInfo.Width == desiredImageInfo.Width && |
||||
|
currentImageInfo.Height == desiredImageInfo.Height && |
||||
|
currentImageInfo.ColorType == desiredImageInfo.ColorType; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create Skia surface backed by given framebuffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="desiredImageInfo">Desired image info.</param>
|
||||
|
/// <param name="framebuffer">Backing framebuffer.</param>
|
||||
|
private void CreateSurface(SKImageInfo desiredImageInfo, ILockedFramebuffer framebuffer) |
||||
|
{ |
||||
|
if (_framebufferSurface != null && AreImageInfosCompatible(_currentImageInfo, desiredImageInfo) && _currentFramebufferAddress == framebuffer.Address) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
FreeSurface(); |
||||
|
|
||||
|
_currentFramebufferAddress = framebuffer.Address; |
||||
|
|
||||
|
var surface = SKSurface.Create(desiredImageInfo, _currentFramebufferAddress, framebuffer.RowBytes); |
||||
|
|
||||
|
// If surface cannot be created - try to create a compatibilty shim first
|
||||
|
if (surface == null) |
||||
|
{ |
||||
|
_conversionShim = new PixelFormatConversionShim(desiredImageInfo, framebuffer.Address); |
||||
|
_preFramebufferCopyHandler = _conversionShim.SurfaceCopyHandler; |
||||
|
|
||||
|
surface = _conversionShim.Surface; |
||||
|
} |
||||
|
|
||||
|
_framebufferSurface = surface ?? throw new Exception("Unable to create a surface for pixel format " + |
||||
|
framebuffer.Format + |
||||
|
" or pixel format translator"); |
||||
|
_currentImageInfo = desiredImageInfo; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Free Skia surface.
|
||||
|
/// </summary>
|
||||
|
private void FreeSurface() |
||||
|
{ |
||||
|
_conversionShim?.Dispose(); |
||||
|
_conversionShim = null; |
||||
|
_preFramebufferCopyHandler = null; |
||||
|
|
||||
|
if (_conversionShim != null) |
||||
|
{ |
||||
|
_framebufferSurface?.Dispose(); |
||||
|
} |
||||
|
|
||||
|
_framebufferSurface = null; |
||||
|
_currentFramebufferAddress = IntPtr.Zero; |
||||
} |
} |
||||
|
|
||||
class PixelFormatShim : IDisposable |
/// <summary>
|
||||
|
/// Converts non-compatible pixel formats using bitmap copies.
|
||||
|
/// </summary>
|
||||
|
private class PixelFormatConversionShim : IDisposable |
||||
{ |
{ |
||||
private readonly SKImageInfo _nfo; |
private readonly SKBitmap _bitmap; |
||||
private readonly IntPtr _fb; |
private readonly SKImageInfo _destinationInfo; |
||||
private readonly int _rowBytes; |
private readonly IntPtr _framebufferAddress; |
||||
private SKBitmap _bitmap; |
|
||||
|
|
||||
public PixelFormatShim(SKImageInfo nfo, IntPtr fb, int rowBytes) |
public PixelFormatConversionShim(SKImageInfo destinationInfo, IntPtr framebufferAddress) |
||||
{ |
{ |
||||
_nfo = nfo; |
_destinationInfo = destinationInfo; |
||||
_fb = fb; |
_framebufferAddress = framebufferAddress; |
||||
_rowBytes = rowBytes; |
|
||||
|
// Create bitmap using default platform settings
|
||||
|
_bitmap = new SKBitmap(destinationInfo.Width, destinationInfo.Height); |
||||
|
|
||||
|
if (!_bitmap.CanCopyTo(destinationInfo.ColorType)) |
||||
|
{ |
||||
|
_bitmap.Dispose(); |
||||
|
|
||||
|
throw new Exception( |
||||
|
$"Unable to create pixel format shim for conversion from {_bitmap.ColorType} to {destinationInfo.ColorType}"); |
||||
|
} |
||||
|
|
||||
|
Surface = SKSurface.Create(_bitmap.Info, _bitmap.GetPixels(), _bitmap.RowBytes); |
||||
|
|
||||
|
if (Surface == null) |
||||
_bitmap = new SKBitmap(nfo.Width, nfo.Height); |
|
||||
if (!_bitmap.CanCopyTo(nfo.ColorType)) |
|
||||
{ |
{ |
||||
_bitmap.Dispose(); |
_bitmap.Dispose(); |
||||
|
|
||||
throw new Exception( |
throw new Exception( |
||||
$"Unable to create pixel format shim for conversion from {_bitmap.ColorType} to {nfo.ColorType}"); |
$"Unable to create pixel format shim surface for conversion from {_bitmap.ColorType} to {destinationInfo.ColorType}"); |
||||
} |
} |
||||
|
|
||||
|
SurfaceCopyHandler = Disposable.Create(CopySurface); |
||||
} |
} |
||||
|
|
||||
public SKSurface CreateSurface() => SKSurface.Create(_bitmap.Info, _bitmap.GetPixels(), _bitmap.RowBytes); |
/// <summary>
|
||||
|
/// Skia surface.
|
||||
|
/// </summary>
|
||||
|
public SKSurface Surface { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Handler to start conversion via surface copy.
|
||||
|
/// </summary>
|
||||
|
public IDisposable SurfaceCopyHandler { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
public void Dispose() |
public void Dispose() |
||||
{ |
{ |
||||
using (var tmp = _bitmap.Copy(_nfo.ColorType)) |
Surface.Dispose(); |
||||
tmp.CopyPixelsTo(_fb, _nfo.BytesPerPixel * _nfo.Height * _rowBytes, _rowBytes); |
|
||||
_bitmap.Dispose(); |
_bitmap.Dispose(); |
||||
} |
} |
||||
|
|
||||
} |
|
||||
|
|
||||
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
|
||||
{ |
|
||||
var fb = _surface.Lock(); |
|
||||
PixelFormatShim shim = null; |
|
||||
SKImageInfo framebuffer = new SKImageInfo(fb.Width, fb.Height, fb.Format.ToSkColorType(), |
|
||||
SKAlphaType.Premul); |
|
||||
var surface = SKSurface.Create(framebuffer, fb.Address, fb.RowBytes) ?? |
|
||||
(shim = new PixelFormatShim(framebuffer, fb.Address, fb.RowBytes)) |
|
||||
.CreateSurface(); |
|
||||
if (surface == null) |
|
||||
throw new Exception("Unable to create a surface for pixel format " + fb.Format + |
|
||||
" or pixel format translator"); |
|
||||
var canvas = surface.Canvas; |
|
||||
|
|
||||
|
|
||||
|
/// <summary>
|
||||
canvas.RestoreToCount(0); |
/// Convert and copy surface to a framebuffer.
|
||||
canvas.Save(); |
/// </summary>
|
||||
canvas.ResetMatrix(); |
private void CopySurface() |
||||
return new DrawingContextImpl(canvas, fb.Dpi, visualBrushRenderer, canvas, surface, shim, fb); |
{ |
||||
|
using (var snapshot = Surface.Snapshot()) |
||||
|
{ |
||||
|
snapshot.ReadPixels(_destinationInfo, _framebufferAddress, _destinationInfo.RowBytes, 0, 0, |
||||
|
SKImageCachingHint.Disallow); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// 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 System.IO; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia.Helpers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Helps with saving images to stream/file.
|
||||
|
/// </summary>
|
||||
|
public static class ImageSavingHelper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Save Skia image to a file.
|
||||
|
/// </summary>
|
||||
|
/// <param name="image">Image to save</param>
|
||||
|
/// <param name="fileName">Target file.</param>
|
||||
|
public static void SaveImage(SKImage image, string fileName) |
||||
|
{ |
||||
|
if (image == null) throw new ArgumentNullException(nameof(image)); |
||||
|
if (fileName == null) throw new ArgumentNullException(nameof(fileName)); |
||||
|
|
||||
|
using (var stream = File.Create(fileName)) |
||||
|
{ |
||||
|
SaveImage(image, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Save Skia image to a stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="image">Image to save</param>
|
||||
|
/// <param name="stream">Target stream.</param>
|
||||
|
public static void SaveImage(SKImage image, Stream stream) |
||||
|
{ |
||||
|
if (image == null) throw new ArgumentNullException(nameof(image)); |
||||
|
if (stream == null) throw new ArgumentNullException(nameof(stream)); |
||||
|
|
||||
|
using (var data = image.Encode()) |
||||
|
{ |
||||
|
data.SaveTo(stream); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
// 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 Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia.Helpers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Helps with resolving pixel formats to Skia color types.
|
||||
|
/// </summary>
|
||||
|
public static class PixelFormatHelper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Resolve given format to Skia color type.
|
||||
|
/// </summary>
|
||||
|
/// <param name="format">Format to resolve.</param>
|
||||
|
/// <returns>Resolved color type.</returns>
|
||||
|
public static SKColorType ResolveColorType(PixelFormat? format) |
||||
|
{ |
||||
|
var colorType = format?.ToSkColorType() ?? SKImageInfo.PlatformColorType; |
||||
|
|
||||
|
// TODO: This looks like some leftover hack
|
||||
|
var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>(); |
||||
|
var runtime = runtimePlatform?.GetRuntimeInfo(); |
||||
|
|
||||
|
if (runtime?.IsDesktop == true && runtime.Value.OperatingSystem == OperatingSystemType.Linux) |
||||
|
{ |
||||
|
colorType = SKColorType.Bgra8888; |
||||
|
} |
||||
|
|
||||
|
return colorType; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
// 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 Avalonia.Platform; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extended bitmap implementation that allows for drawing it's contents.
|
||||
|
/// </summary>
|
||||
|
internal interface IDrawableBitmapImpl : IBitmapImpl |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Draw bitmap to a drawing context.
|
||||
|
/// </summary>
|
||||
|
/// <param name="context">Drawing context.</param>
|
||||
|
/// <param name="sourceRect">Source rect.</param>
|
||||
|
/// <param name="destRect">Destination rect.</param>
|
||||
|
/// <param name="paint">Paint to use.</param>
|
||||
|
void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,92 @@ |
|||||
|
// 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 System.IO; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Skia.Helpers; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Immutable Skia bitmap.
|
||||
|
/// </summary>
|
||||
|
public class ImmutableBitmap : IDrawableBitmapImpl |
||||
|
{ |
||||
|
private readonly SKImage _image; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create immutable bitmap from given stream.
|
||||
|
/// </summary>
|
||||
|
/// <param name="stream">Stream containing encoded data.</param>
|
||||
|
public ImmutableBitmap(Stream stream) |
||||
|
{ |
||||
|
using (var skiaStream = new SKManagedStream(stream)) |
||||
|
{ |
||||
|
_image = SKImage.FromEncodedData(SKData.Create(skiaStream)); |
||||
|
|
||||
|
if (_image == null) |
||||
|
{ |
||||
|
throw new ArgumentException("Unable to load bitmap from provided data"); |
||||
|
} |
||||
|
|
||||
|
PixelWidth = _image.Width; |
||||
|
PixelHeight = _image.Height; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create immutable bitmap from given pixel data copy.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">Width of data pixels.</param>
|
||||
|
/// <param name="height">Height of data pixels.</param>
|
||||
|
/// <param name="stride">Stride of data pixels.</param>
|
||||
|
/// <param name="format">Format of data pixels.</param>
|
||||
|
/// <param name="data">Data pixels.</param>
|
||||
|
public ImmutableBitmap(int width, int height, int stride, PixelFormat format, IntPtr data) |
||||
|
{ |
||||
|
var imageInfo = new SKImageInfo(width, height, format.ToSkColorType(), SKAlphaType.Premul); |
||||
|
|
||||
|
_image = SKImage.FromPixelCopy(imageInfo, data, stride); |
||||
|
|
||||
|
if (_image == null) |
||||
|
{ |
||||
|
throw new ArgumentException("Unable to create bitmap from provided data"); |
||||
|
} |
||||
|
|
||||
|
PixelWidth = width; |
||||
|
PixelHeight = height; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelWidth { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_image.Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(string fileName) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(_image, fileName); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(Stream stream) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(_image, stream); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) |
||||
|
{ |
||||
|
context.Canvas.DrawImage(_image, sourceRect, destRect, paint); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
// 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 Avalonia.Controls; |
||||
|
using Avalonia.Skia; |
||||
|
|
||||
|
// ReSharper disable once CheckNamespace
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Skia appication extensions.
|
||||
|
/// </summary>
|
||||
|
public static class SkiaApplicationExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Enable Skia renderer.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">Builder type.</typeparam>
|
||||
|
/// <param name="builder">Builder.</param>
|
||||
|
/// <param name="preferredBackendType">Preferred backend type.</param>
|
||||
|
/// <returns>Configure builder.</returns>
|
||||
|
public static T UseSkia<T>(this T builder) where T : AppBuilderBase<T>, new() |
||||
|
{ |
||||
|
builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize(), "Skia"); |
||||
|
return builder; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,47 +1,31 @@ |
|||||
|
// 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 System; |
||||
using System.Collections.Generic; |
using Avalonia.Logging; |
||||
using System.Text; |
|
||||
using Avalonia.Controls; |
|
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
using Avalonia.Rendering; |
|
||||
|
|
||||
namespace Avalonia |
|
||||
{ |
|
||||
public static class SkiaApplicationExtensions |
|
||||
{ |
|
||||
public static T UseSkia<T>(this T builder) where T : AppBuilderBase<T>, new() |
|
||||
{ |
|
||||
builder.UseRenderingSubsystem(Skia.SkiaPlatform.Initialize, "Skia"); |
|
||||
return builder; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
namespace Avalonia.Skia |
namespace Avalonia.Skia |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Skia platform initializer.
|
||||
|
/// </summary>
|
||||
public static class SkiaPlatform |
public static class SkiaPlatform |
||||
{ |
{ |
||||
private static bool s_forceSoftwareRendering; |
/// <summary>
|
||||
|
/// Initialize Skia platform.
|
||||
|
/// </summary>
|
||||
public static void Initialize() |
public static void Initialize() |
||||
{ |
{ |
||||
var renderInterface = new PlatformRenderInterface(); |
var renderInterface = new PlatformRenderInterface(); |
||||
|
|
||||
AvaloniaLocator.CurrentMutable |
AvaloniaLocator.CurrentMutable |
||||
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface); |
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface); |
||||
} |
} |
||||
|
|
||||
public static bool ForceSoftwareRendering |
/// <summary>
|
||||
{ |
/// Default DPI.
|
||||
get { return s_forceSoftwareRendering; } |
/// </summary>
|
||||
set |
public static Vector DefaultDpi => new Vector(96.0f, 96.0f); |
||||
{ |
|
||||
s_forceSoftwareRendering = value; |
|
||||
|
|
||||
// TODO: I left this property here as place holder. Do we still need the ability to Force software rendering?
|
|
||||
// Is it even possible with SkiaSharp? Perhaps kekekes can answer as part of the HW accel work.
|
|
||||
//
|
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,169 @@ |
|||||
|
// 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 System.IO; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Skia.Helpers; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Skia render target that writes to a surface.
|
||||
|
/// </summary>
|
||||
|
public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl |
||||
|
{ |
||||
|
private readonly Vector _dpi; |
||||
|
private readonly SKSurface _surface; |
||||
|
private readonly SKCanvas _canvas; |
||||
|
private readonly bool _disableLcdRendering; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create new surface render target.
|
||||
|
/// </summary>
|
||||
|
/// <param name="createInfo">Create info.</param>
|
||||
|
public SurfaceRenderTarget(CreateInfo createInfo) |
||||
|
{ |
||||
|
PixelWidth = createInfo.Width; |
||||
|
PixelHeight = createInfo.Height; |
||||
|
_dpi = createInfo.Dpi; |
||||
|
_disableLcdRendering = createInfo.DisableTextLcdRendering; |
||||
|
|
||||
|
_surface = CreateSurface(PixelWidth, PixelHeight, createInfo.Format); |
||||
|
|
||||
|
_canvas = _surface?.Canvas; |
||||
|
|
||||
|
if (_surface == null || _canvas == null) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Failed to create Skia render target surface"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create backing Skia surface.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">Width.</param>
|
||||
|
/// <param name="height">Height.</param>
|
||||
|
/// <param name="format">Format.</param>
|
||||
|
/// <returns></returns>
|
||||
|
private static SKSurface CreateSurface(int width, int height, PixelFormat? format) |
||||
|
{ |
||||
|
var imageInfo = MakeImageInfo(width, height, format); |
||||
|
|
||||
|
return SKSurface.Create(imageInfo); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_canvas.Dispose(); |
||||
|
_surface.Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
||||
|
{ |
||||
|
_canvas.RestoreToCount(-1); |
||||
|
_canvas.ResetMatrix(); |
||||
|
|
||||
|
var createInfo = new DrawingContextImpl.CreateInfo |
||||
|
{ |
||||
|
Canvas = _canvas, |
||||
|
Dpi = _dpi, |
||||
|
VisualBrushRenderer = visualBrushRenderer, |
||||
|
DisableTextLcdRendering = _disableLcdRendering |
||||
|
}; |
||||
|
|
||||
|
return new DrawingContextImpl(createInfo); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelWidth { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(string fileName) |
||||
|
{ |
||||
|
using (var image = SnapshotImage()) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(image, fileName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(Stream stream) |
||||
|
{ |
||||
|
using (var image = SnapshotImage()) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(image, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) |
||||
|
{ |
||||
|
using (var image = SnapshotImage()) |
||||
|
{ |
||||
|
context.Canvas.DrawImage(image, sourceRect, destRect, paint); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create Skia image snapshot from a surface.
|
||||
|
/// </summary>
|
||||
|
/// <returns>Image snapshot.</returns>
|
||||
|
public SKImage SnapshotImage() |
||||
|
{ |
||||
|
return _surface.Snapshot(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create image info for given parameters.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">Width.</param>
|
||||
|
/// <param name="height">Height.</param>
|
||||
|
/// <param name="format">Format.</param>
|
||||
|
/// <returns></returns>
|
||||
|
private static SKImageInfo MakeImageInfo(int width, int height, PixelFormat? format) |
||||
|
{ |
||||
|
var colorType = PixelFormatHelper.ResolveColorType(format); |
||||
|
|
||||
|
return new SKImageInfo(width, height, colorType, SKAlphaType.Premul); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create info of a surface render target.
|
||||
|
/// </summary>
|
||||
|
public struct CreateInfo |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Width of a render target.
|
||||
|
/// </summary>
|
||||
|
public int Width; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Height of a render target.
|
||||
|
/// </summary>
|
||||
|
public int Height; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Dpi used when rendering to a surface.
|
||||
|
/// </summary>
|
||||
|
public Vector Dpi; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Pixel format of a render target.
|
||||
|
/// </summary>
|
||||
|
public PixelFormat? Format; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Render text without Lcd rendering.
|
||||
|
/// </summary>
|
||||
|
public bool DisableTextLcdRendering; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,59 +1,43 @@ |
|||||
using System; |
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
using Avalonia.Media; |
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
using Avalonia.Platform; |
using Avalonia.Platform; |
||||
using SkiaSharp; |
using SkiaSharp; |
||||
|
|
||||
namespace Avalonia.Skia |
namespace Avalonia.Skia |
||||
{ |
{ |
||||
class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl |
/// <summary>
|
||||
|
/// A Skia implementation of a <see cref="ITransformedGeometryImpl"/>.
|
||||
|
/// </summary>
|
||||
|
public class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl |
||||
{ |
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="TransformedGeometryImpl"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="source">Source geometry.</param>
|
||||
|
/// <param name="transform">Transform of new geometry.</param>
|
||||
public TransformedGeometryImpl(GeometryImpl source, Matrix transform) |
public TransformedGeometryImpl(GeometryImpl source, Matrix transform) |
||||
{ |
{ |
||||
SourceGeometry = source; |
SourceGeometry = source; |
||||
Transform = transform; |
Transform = transform; |
||||
EffectivePath = source.EffectivePath.Clone(); |
|
||||
EffectivePath.Transform(transform.ToSKMatrix()); |
var transformedPath = source.EffectivePath.Clone(); |
||||
|
transformedPath.Transform(transform.ToSKMatrix()); |
||||
|
|
||||
|
EffectivePath = transformedPath; |
||||
|
Bounds = transformedPath.TightBounds.ToAvaloniaRect(); |
||||
} |
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
public override SKPath EffectivePath { get; } |
public override SKPath EffectivePath { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
public IGeometryImpl SourceGeometry { get; } |
public IGeometryImpl SourceGeometry { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
public Matrix Transform { get; } |
public Matrix Transform { get; } |
||||
|
|
||||
public override Rect Bounds => SourceGeometry.Bounds.TransformToAABB(Transform); |
/// <inheritdoc />
|
||||
|
public override Rect Bounds { get; } |
||||
public override bool FillContains(Point point) |
|
||||
{ |
|
||||
// TODO: Not supported by SkiaSharp yet, so use expanded Rect
|
|
||||
return GetRenderBounds(0).Contains(point); |
|
||||
} |
|
||||
|
|
||||
public override Rect GetRenderBounds(Pen pen) |
|
||||
{ |
|
||||
return GetRenderBounds(pen.Thickness); |
|
||||
} |
|
||||
|
|
||||
public override IGeometryImpl Intersect(IGeometryImpl geometry) |
|
||||
{ |
|
||||
throw new NotImplementedException(); |
|
||||
} |
|
||||
|
|
||||
public override bool StrokeContains(Pen pen, Point point) |
|
||||
{ |
|
||||
// TODO: Not supported by SkiaSharp yet, so use expanded Rect
|
|
||||
return GetRenderBounds(0).Contains(point); |
|
||||
} |
|
||||
|
|
||||
public override ITransformedGeometryImpl WithTransform(Matrix transform) |
|
||||
{ |
|
||||
return new TransformedGeometryImpl(this, transform); |
|
||||
} |
|
||||
|
|
||||
public Rect GetRenderBounds(double strokeThickness) |
|
||||
{ |
|
||||
// TODO: Calculate properly.
|
|
||||
return Bounds.Inflate(strokeThickness); |
|
||||
} |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,151 @@ |
|||||
|
// 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 System.IO; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Skia.Helpers; |
||||
|
using SkiaSharp; |
||||
|
|
||||
|
namespace Avalonia.Skia |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Skia based writeable bitmap.
|
||||
|
/// </summary>
|
||||
|
public class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl |
||||
|
{ |
||||
|
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc; |
||||
|
private readonly SKBitmap _bitmap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create new writeable bitmap.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">Width.</param>
|
||||
|
/// <param name="height">Height.</param>
|
||||
|
/// <param name="format">Format.</param>
|
||||
|
public WriteableBitmapImpl(int width, int height, PixelFormat? format = null) |
||||
|
{ |
||||
|
PixelHeight = height; |
||||
|
PixelWidth = width; |
||||
|
|
||||
|
var colorType = PixelFormatHelper.ResolveColorType(format); |
||||
|
|
||||
|
var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>(); |
||||
|
|
||||
|
if (runtimePlatform != null) |
||||
|
{ |
||||
|
_bitmap = new SKBitmap(); |
||||
|
|
||||
|
var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); |
||||
|
var blob = runtimePlatform.AllocBlob(nfo.BytesSize); |
||||
|
|
||||
|
_bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); |
||||
|
} |
||||
|
|
||||
|
_bitmap.Erase(SKColor.Empty); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelWidth { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int PixelHeight { get; } |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) |
||||
|
{ |
||||
|
context.Canvas.DrawBitmap(_bitmap, sourceRect, destRect, paint); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_bitmap.Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(Stream stream) |
||||
|
{ |
||||
|
using (var image = GetSnapshot()) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(image, stream); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Save(string fileName) |
||||
|
{ |
||||
|
using (var image = GetSnapshot()) |
||||
|
{ |
||||
|
ImageSavingHelper.SaveImage(image, fileName); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public ILockedFramebuffer Lock() => new BitmapFramebuffer(_bitmap); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Get snapshot as image.
|
||||
|
/// </summary>
|
||||
|
/// <returns>Image snapshot.</returns>
|
||||
|
public SKImage GetSnapshot() |
||||
|
{ |
||||
|
return SKImage.FromPixels(_bitmap.Info, _bitmap.GetPixels(), _bitmap.RowBytes); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Release given unmanaged blob.
|
||||
|
/// </summary>
|
||||
|
/// <param name="address">Blob address.</param>
|
||||
|
/// <param name="ctx">Blob.</param>
|
||||
|
private static void ReleaseProc(IntPtr address, object ctx) |
||||
|
{ |
||||
|
((IUnmanagedBlob)ctx).Dispose(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Framebuffer for bitmap.
|
||||
|
/// </summary>
|
||||
|
private class BitmapFramebuffer : ILockedFramebuffer |
||||
|
{ |
||||
|
private SKBitmap _bitmap; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Create framebuffer from given bitmap.
|
||||
|
/// </summary>
|
||||
|
/// <param name="bitmap">Bitmap.</param>
|
||||
|
public BitmapFramebuffer(SKBitmap bitmap) |
||||
|
{ |
||||
|
_bitmap = bitmap; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_bitmap = null; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public IntPtr Address => _bitmap.GetPixels(); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int Width => _bitmap.Width; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int Height => _bitmap.Height; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public int RowBytes => _bitmap.RowBytes; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public Vector Dpi { get; } = SkiaPlatform.DefaultDpi; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public PixelFormat Format => _bitmap.ColorType.ToPixelFormat(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,42 +1,22 @@ |
|||||
TODO: |
|
||||
|
|
||||
BitmapImpl |
|
||||
- constructor from Width/Height |
|
||||
- Save |
|
||||
|
|
||||
StreamGeometryImpl |
|
||||
- Hit testing in Geometry missing as SkiaSharp does not expose this |
|
||||
|
|
||||
DrawingContextImpl |
DrawingContextImpl |
||||
- Alpha support missing as SkiaSharp does not expose this |
- Alpha support missing as SkiaSharp does not expose this |
||||
- Gradient Shader caching? |
- Gradient Shader caching? |
||||
- TileBrushes |
|
||||
- Pen Dash styles |
- Pen Dash styles |
||||
|
|
||||
Formatted Text Rendering |
Formatted Text Rendering |
||||
- minor polish |
- Minor polish |
||||
|
|
||||
RenderTarget |
Linux |
||||
- Figure out a cleaner implementation across all platforms |
- Need gpu platform implementation |
||||
- HW acceleration |
|
||||
|
|
||||
App Bootstrapping |
macOS |
||||
- Cleanup the testapplications across all platforms |
- Need gpu platform implementation |
||||
- Add a cleaner Fluent API for the subsystems |
|
||||
- ie. app.UseDirect2D() (via platform specific extension methods) |
|
||||
|
|
||||
Android |
Android |
||||
- Not tested at all yet |
- Not tested at all yet |
||||
|
|
||||
iOS |
iOS |
||||
- Get GLView working again. See HW above |
- Not tested at all yet |
||||
|
|
||||
Win32 |
|
||||
- Cleanup the unmanaged methods (BITMAPINFO) if possible |
|
||||
|
|
||||
General |
General |
||||
- Cleanup/eliminate obsolete files |
- Get Skia Unit Tests passing (most of the issues are related to antialiasing) |
||||
- Finish cleanup of the many Test Applications |
|
||||
- Get Skia Unit Tests passing |
|
||||
|
|
||||
|
|
||||
@ -0,0 +1,22 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netcoreapp2.0</TargetFrameworks> |
||||
|
</PropertyGroup> |
||||
|
<Import Project="..\..\build\UnitTests.NetCore.targets" /> |
||||
|
<Import Project="..\..\build\Moq.props" /> |
||||
|
<Import Project="..\..\build\XUnit.props" /> |
||||
|
<Import Project="..\..\build\Rx.props" /> |
||||
|
<Import Project="..\..\build\Microsoft.Reactive.Testing.props" /> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" /> |
||||
|
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,79 @@ |
|||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Skia.UnitTests |
||||
|
{ |
||||
|
public class HitTesting |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Hit_Test_Should_Respect_Fill() |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
SkiaPlatform.Initialize(); |
||||
|
|
||||
|
var root = new TestRoot |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Child = new Ellipse |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Fill = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
root.Renderer = new DeferredRenderer(root, null); |
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
|
||||
|
var outsideResult = root.Renderer.HitTest(new Point(10, 10), root, null); |
||||
|
var insideResult = root.Renderer.HitTest(new Point(50, 50), root, null); |
||||
|
|
||||
|
Assert.Empty(outsideResult); |
||||
|
Assert.Equal(new[] {root.Child}, insideResult); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Hit_Test_Should_Respect_Stroke() |
||||
|
{ |
||||
|
using (AvaloniaLocator.EnterScope()) |
||||
|
{ |
||||
|
SkiaPlatform.Initialize(); |
||||
|
|
||||
|
var root = new TestRoot |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Child = new Ellipse |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Stroke = Brushes.Red, |
||||
|
StrokeThickness = 5, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
root.Renderer = new DeferredRenderer(root, null); |
||||
|
root.Measure(Size.Infinity); |
||||
|
root.Arrange(new Rect(root.DesiredSize)); |
||||
|
|
||||
|
var outsideResult = root.Renderer.HitTest(new Point(50, 50), root, null); |
||||
|
var insideResult = root.Renderer.HitTest(new Point(1, 50), root, null); |
||||
|
|
||||
|
Assert.Empty(outsideResult); |
||||
|
Assert.Equal(new[] { root.Child }, insideResult); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
// 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.Reflection; |
||||
|
using Xunit; |
||||
|
|
||||
|
// Don't run tests in parallel.
|
||||
|
[assembly: CollectionBehavior(DisableTestParallelization = true)] |
||||
|
Before Width: | Height: | Size: 652 B After Width: | Height: | Size: 642 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 1.1 KiB |