Browse Source

Merge branch 'master' into scenegraph

Conflicts:
	src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
	tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
	tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
scenegraph-after-breakage
Steven Kirk 9 years ago
parent
commit
9029513b9d
  1. BIN
      _NCrunch_Avalonia/StoredText/65f325bb59ca4d71bd19843c3ce9b90e
  2. 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  3. 2
      src/Avalonia.Controls/Avalonia.Controls.csproj
  4. 1
      src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs
  5. 15
      src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs
  6. 4
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  7. 14
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  8. 22
      src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs
  9. 2
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  10. 21
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  11. 16
      src/Avalonia.Visuals/Platform/IWritableBitmapImpl.cs
  12. 9
      src/Avalonia.Visuals/Platform/PixelFormat.cs
  13. 10
      src/Gtk/Avalonia.Cairo/CairoPlatform.cs
  14. 1
      src/Gtk/Avalonia.Gtk/FramebufferManager.cs
  15. 1
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  16. 45
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  17. 17
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  18. 15
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  19. 24
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  20. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  21. 11
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  22. 30
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  23. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs
  24. 11
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  25. 1
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  26. 1
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  27. 3
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  28. 3
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  29. 2
      src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
  30. 10
      tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs
  31. 1
      tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
  32. 139
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  33. 17
      tests/Avalonia.RenderTests/TestBase.cs
  34. 10
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  35. 174
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
  36. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png
  37. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png
  38. BIN
      tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  39. BIN
      tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  40. BIN
      tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png
  41. BIN
      tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png
  42. BIN
      tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png

BIN
_NCrunch_Avalonia/StoredText/65f325bb59ca4d71bd19843c3ce9b90e

Binary file not shown.

2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@ -2,7 +2,7 @@ using System;
using System.Runtime.InteropServices;
using Android.Runtime;
using Android.Views;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{

2
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -59,8 +59,6 @@
<Compile Include="IScrollable.cs" />
<Compile Include="Platform\IWindowBaseImpl.cs" />
<Compile Include="Platform\Surfaces\IFramebufferPlatformSurface.cs" />
<Compile Include="Platform\Surfaces\ILockedFramebuffer.cs" />
<Compile Include="Platform\Surfaces\PixelFormat.cs" />
<Compile Include="PointEventArgs.cs" />
<Compile Include="Embedding\EmbeddableControlRoot.cs" />
<Compile Include="Platform\IEmbeddableWindowImpl.cs" />

1
src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform.Surfaces
{

15
src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Controls.Platform.Surfaces
{
public enum PixelFormat
{
Rgb565,
Rgba8888,
Bgra8888
}
}

4
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -81,6 +81,7 @@
<Compile Include="Media\IRadialGradientBrush.cs" />
<Compile Include="Media\ITileBrush.cs" />
<Compile Include="Media\IVisualBrush.cs" />
<Compile Include="Media\Imaging\WritableBitmap.cs" />
<Compile Include="Media\TextWrapping.cs" />
<Compile Include="Media\TransformGroup.cs" />
<Compile Include="Media\DashStyle.cs" />
@ -122,7 +123,10 @@
<Compile Include="Rendering\DirtyVisuals.cs" />
<Compile Include="Rendering\DisplayDirtyRect.cs" />
<Compile Include="Rendering\DisplayDirtyRects.cs" />
<Compile Include="Platform\ILockedFramebuffer.cs" />
<Compile Include="Platform\IModuleEnvironmentChecker.cs" />
<Compile Include="Platform\IWritableBitmapImpl.cs" />
<Compile Include="Platform\PixelFormat.cs" />
<Compile Include="Rendering\IRenderer.cs" />
<Compile Include="Rendering\IRendererFactory.cs" />
<Compile Include="Rendering\IRenderLoop.cs" />

14
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -41,6 +41,20 @@ namespace Avalonia.Media.Imaging
PlatformImpl = impl;
}
/// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary>
/// <param name="format">Pixel format</param>
/// <param name="data">Pointer to source bytes</param>
/// <param name="width">Bitmap width</param>
/// <param name="height">Bitmap height</param>
/// <param name="stride">Bytes per row</param>
public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
.LoadBitmap(format, data, width, height, stride);
}
/// <summary>
/// Gets the width of the bitmap, in pixels.
/// </summary>

22
src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
namespace Avalonia.Media.Imaging
{
/// <summary>
/// Holds a writable bitmap image.
/// </summary>
public class WritableBitmap : Bitmap
{
public WritableBitmap(int width, int height, PixelFormat? format = null)
: base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWritableBitmap(width, height, format))
{
}
public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl).Lock();
}
}

2
src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs → src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs

@ -1,6 +1,6 @@
using System;
namespace Avalonia.Controls.Platform.Surfaces
namespace Avalonia.Platform
{
public interface ILockedFramebuffer : IDisposable
{

21
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.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;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
@ -63,6 +64,15 @@ namespace Avalonia.Platform
double dpiX,
double dpiY);
/// <summary>
/// Creates a writable bitmap implementation.
/// </summary>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="format">Pixel format (optional).</param>
/// <returns>An <see cref="IWritableBitmapImpl"/>.</returns>
IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null);
/// <summary>
/// Loads a bitmap implementation from a file..
/// </summary>
@ -76,5 +86,16 @@ namespace Avalonia.Platform
/// <param name="stream">The stream to read the bitmap from.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(Stream stream);
/// <summary>
/// Loads a bitmap implementation from a pixels in memory..
/// </summary>
/// <param name="format">Pixel format</param>
/// <param name="data">Pointer to source bytes</param>
/// <param name="width">Bitmap width</param>
/// <param name="height">Bitmap height</param>
/// <param name="stride">Bytes per row</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride);
}
}

16
src/Avalonia.Visuals/Platform/IWritableBitmapImpl.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Platform
{
/// <summary>
/// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.WritableBitmap"/>.
/// </summary>
public interface IWritableBitmapImpl : IBitmapImpl
{
ILockedFramebuffer Lock();
}
}

9
src/Avalonia.Visuals/Platform/PixelFormat.cs

@ -0,0 +1,9 @@
namespace Avalonia.Platform
{
public enum PixelFormat
{
Rgb565,
Rgba8888,
Bgra8888
}
}

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

@ -112,5 +112,15 @@ namespace Avalonia.Cairo
Gtk.Application.Init();
return new Gtk.Invisible().CreatePangoContext();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotSupportedException("No proper control over pixel format with Cairo, use Skia backend instead");
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
{
throw new NotSupportedException("No proper support with Cairo, use Skia backend instead");
}
}
}

1
src/Gtk/Avalonia.Gtk/FramebufferManager.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Gtk
{

1
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Gtk3
{

45
src/Skia/Avalonia.Skia/BitmapImpl.cs

@ -10,7 +10,7 @@ using SkiaSharp;
namespace Avalonia.Skia
{
class BitmapImpl : IRenderTargetBitmapImpl
class BitmapImpl : IRenderTargetBitmapImpl, IWritableBitmapImpl
{
public SKBitmap Bitmap { get; private set; }
@ -21,11 +21,11 @@ namespace Avalonia.Skia
PixelWidth = bm.Width;
}
public BitmapImpl(int width, int height)
public BitmapImpl(int width, int height, PixelFormat? fmt = null)
{
PixelHeight = height;
PixelWidth = width;
var colorType = SKImageInfo.PlatformColorType;
var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType;
var runtime = AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo();
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
colorType = SKColorType.Bgra8888;
@ -44,10 +44,21 @@ namespace Avalonia.Skia
public void Save(string fileName)
{
#if DESKTOP
if(Bitmap.ColorType != SKColorType.Bgra8888)
{
using (var tmp = new BitmapImpl(Bitmap.Copy(SKColorType.Bgra8888)))
tmp.Save(fileName);
return;
}
IntPtr length;
using (var sdb = new System.Drawing.Bitmap(PixelWidth, PixelHeight, Bitmap.RowBytes,
System.Drawing.Imaging.PixelFormat.Format32bppArgb, Bitmap.GetPixels(out length)))
System.Drawing.Imaging.PixelFormat.Format32bppArgb,
Bitmap.GetPixels(out length)))
sdb.Save(fileName);
#else
//SkiaSharp doesn't expose image encoders yet
@ -103,5 +114,31 @@ namespace Avalonia.Skia
data.SaveTo(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 Size Dpi { get; } = new Size(96, 96);
public PixelFormat Format => _bmp.ColorType.ToPixelFormat();
}
public ILockedFramebuffer Lock() => new BitmapFramebuffer(Bitmap);
}
}

17
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -23,19 +23,6 @@ namespace Avalonia.Skia
//Nothing to do here, since we don't own framebuffer
}
SKColorType TranslatePixelFormat(PixelFormat fmt)
{
if(fmt == PixelFormat.Rgb565)
return SKColorType.Rgb565;
if(fmt == PixelFormat.Bgra8888)
return SKColorType.Bgra8888;
if (fmt == PixelFormat.Rgba8888)
return SKColorType.Rgba8888;
throw new ArgumentException("Unknown pixel format: " + fmt);
}
class PixelFormatShim : IDisposable
{
private readonly SKImageInfo _nfo;
@ -74,8 +61,8 @@ namespace Avalonia.Skia
{
var fb = _surface.Lock();
PixelFormatShim shim = null;
SKImageInfo framebuffer = new SKImageInfo(fb.Width, fb.Height, TranslatePixelFormat(fb.Format),
SKAlphaType.Opaque);
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();

15
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -52,6 +52,16 @@ namespace Avalonia.Skia
}
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
using (var tmp = new SKBitmap())
{
tmp.InstallPixels(new SKImageInfo(width, height, format.ToSkColorType(), SKAlphaType.Premul)
, data, stride);
return new BitmapImpl(tmp.Copy());
}
}
public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
{
return new DeferredRenderer(root, renderLoop);
@ -78,5 +88,10 @@ namespace Avalonia.Skia
throw new Exception("Skia backend currently only supports framebuffer render target");
return new FramebufferRenderTarget(fb);
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null)
{
return new BitmapImpl(width, height, format);
}
}
}

24
src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs

@ -1,4 +1,6 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
using SkiaSharp;
@ -44,6 +46,28 @@ namespace Avalonia.Skia
return new SKColor(c.R, c.G, c.B, c.A);
}
public static SKColorType ToSkColorType(this PixelFormat fmt)
{
if (fmt == PixelFormat.Rgb565)
return SKColorType.Rgb565;
if (fmt == PixelFormat.Bgra8888)
return SKColorType.Bgra8888;
if (fmt == PixelFormat.Rgba8888)
return SKColorType.Rgba8888;
throw new ArgumentException("Unknown pixel format: " + fmt);
}
public static PixelFormat ToPixelFormat(this SKColorType fmt)
{
if (fmt == SKColorType.Rgb565)
return PixelFormat.Rgb565;
if (fmt == SKColorType.Bgra8888)
return PixelFormat.Bgra8888;
if (fmt == SKColorType.Rgba8888)
return PixelFormat.Rgba8888;
throw new ArgumentException("Unknown pixel format: " + fmt);
}
public static SKShaderTileMode ToSKShaderTileMode(this Media.GradientSpreadMethod m)
{
switch (m)

1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -84,6 +84,7 @@
<Compile Include="Media\Imaging\BitmapImpl.cs" />
<Compile Include="Media\Imaging\D2DBitmapImpl.cs" />
<Compile Include="Media\Imaging\WicBitmapImpl.cs" />
<Compile Include="Media\Imaging\WritableWicBitmapImpl.cs" />
<Compile Include="Media\RadialGradientBrushImpl.cs" />
<Compile Include="Media\LinearGradientBrushImpl.cs" />
<Compile Include="Media\AvaloniaTextRenderer.cs" />

11
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -10,6 +10,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Rendering;
namespace Avalonia
@ -153,6 +154,11 @@ namespace Avalonia.Direct2D1
dpiY);
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = null)
{
return new WritableWicBitmapImpl(s_imagingFactory, width, height, format);
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new StreamGeometryImpl();
@ -167,5 +173,10 @@ namespace Avalonia.Direct2D1
{
return new WicBitmapImpl(s_imagingFactory, stream);
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
return new WicBitmapImpl(s_imagingFactory, format, data, width, height, stride);
}
}
}

30
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -4,6 +4,9 @@
using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC;
namespace Avalonia.Direct2D1.Media
@ -53,17 +56,38 @@ namespace Avalonia.Direct2D1.Media
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height)
/// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
{
if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
width,
height,
PixelFormat.Format32bppPBGRA,
pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad);
}
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
{
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
for (var row = 0; row < height; row++)
{
UnmanagedMethods.CopyMemory(new IntPtr(l.Data.DataPointer.ToInt64() + row * l.Stride),
new IntPtr(data.ToInt64() + row * stride), (uint) l.Data.Pitch);
}
}
}
protected APixelFormat? PixelFormat { get; }
/// <summary>
/// Gets the width of the bitmap, in pixels.
/// </summary>
@ -93,7 +117,7 @@ namespace Avalonia.Direct2D1.Media
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(_factory);
converter.Initialize(WicImpl, PixelFormat.Format32bppPBGRA);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
}

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/WritableWicBitmapImpl.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.WIC;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Direct2D1.Media.Imaging
{
class WritableWicBitmapImpl : WicBitmapImpl, IWritableBitmapImpl
{
public WritableWicBitmapImpl(ImagingFactory factory, int width, int height, PixelFormat? pixelFormat)
: base(factory, width, height, pixelFormat)
{
}
class LockedBitmap : ILockedFramebuffer
{
private readonly BitmapLock _lock;
private readonly PixelFormat _format;
public LockedBitmap(BitmapLock l, PixelFormat format)
{
_lock = l;
_format = format;
}
public void Dispose()
{
_lock.Dispose();
}
public IntPtr Address => _lock.Data.DataPointer;
public int Width => _lock.Size.Width;
public int Height => _lock.Size.Height;
public int RowBytes => _lock.Stride;
public Size Dpi { get; } = new Size(96, 96);
public PixelFormat Format => _format;
}
public ILockedFramebuffer Lock() => new LockedBitmap(WicImpl.Lock(BitmapLockFlags.Write), PixelFormat.Value);
}
}

11
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@ -88,6 +88,17 @@ namespace Avalonia.Direct2D1
return CapStyle.Triangle;
}
public static Guid ToWic(this Platform.PixelFormat format)
{
if (format == Platform.PixelFormat.Rgb565)
return SharpDX.WIC.PixelFormat.Format16bppBGR565;
if (format == Platform.PixelFormat.Bgra8888)
return SharpDX.WIC.PixelFormat.Format32bppPBGRA;
if (format == Platform.PixelFormat.Rgba8888)
return SharpDX.WIC.PixelFormat.Format32bppPRGBA;
throw new ArgumentException("Unknown pixel format");
}
/// <summary>
/// Converts a pen to a Direct2D stroke style.
/// </summary>

1
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -11,6 +11,7 @@ using Avalonia.Win32.Interop;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.DXGI;
using PixelFormat = SharpDX.Direct2D1.PixelFormat;
using AlphaMode = SharpDX.Direct2D1.AlphaMode;
using Device = SharpDX.Direct2D1.Device;
using Factory = SharpDX.Direct2D1.Factory;

1
src/Windows/Avalonia.Win32/FramebufferManager.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
namespace Avalonia.Win32

3
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -909,6 +909,9 @@ namespace Avalonia.Win32.Interop
uint dwMaximumSizeLow,
string lpName);
[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
public enum MONITOR
{
MONITOR_DEFAULTTONULL = 0x00000000,

3
src/Windows/Avalonia.Win32/WindowFramebuffer.cs

@ -1,8 +1,9 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = Avalonia.Controls.Platform.Surfaces.PixelFormat;
using PixelFormat = Avalonia.Platform.PixelFormat;
namespace Avalonia.Win32
{

2
src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
using CoreGraphics;
using UIKit;

10
tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs

@ -365,6 +365,16 @@ namespace Avalonia.Input.UnitTests
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotImplementedException();
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
{
throw new NotImplementedException();
}
class MockStreamGeometry : Avalonia.Platform.IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

1
tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems

@ -11,6 +11,7 @@
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)GeometryClippingTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Media\RadialGradientBrushTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Media\BitmapTests.cs" />
<Compile Include="$(MSBuildThisFileDirectory)OpacityMaskTests.cs" />
<Compile Include="Media\FormattedTextImplTests.cs" />
<Compile Include="Controls\ImageTests.cs" />

139
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -0,0 +1,139 @@
// 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 System.Runtime.InteropServices;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Controls.Shapes;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Xunit;
#if AVALONIA_CAIRO
namespace Avalonia.Cairo.RenderTests.Media
#elif AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
#else
namespace Avalonia.Direct2D1.RenderTests.Media
#endif
{
public class BitmapTests : TestBase
{
public BitmapTests()
: base(@"Media\Bitmap")
{
Directory.CreateDirectory(OutputPath);
}
class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface
{
public Framebuffer(PixelFormat fmt, int width, int height)
{
Format = fmt;
var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4;
Width = width;
Height = height;
RowBytes = bpp * width;
Address = Marshal.AllocHGlobal(Height * RowBytes);
}
public IntPtr Address { get; }
public Size Dpi { get; } = new Size(96, 96);
public PixelFormat Format { get; }
public int Height { get; }
public int RowBytes { get; }
public int Width { get; }
public void Dispose()
{
//no-op
}
public ILockedFramebuffer Lock()
{
return this;
}
public void Deallocate() => Marshal.FreeHGlobal(Address);
}
#if AVALONIA_SKIA
[Theory]
#else
[Theory(Skip = "Framebuffer not supported")]
#endif
[InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)]
public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
{
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, 80, 80);
var r = Avalonia.AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
using (var target = r.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext(null))
{
ctx.PushOpacity(0.8);
ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100));
ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100));
ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100));
}
var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes);
fb.Deallocate();
using (var rtb = new RenderTargetBitmap(100, 100))
{
using (var ctx = rtb.CreateDrawingContext(null))
{
ctx.FillRectangle(Brushes.Blue, new Rect(0, 0, 100, 100));
ctx.FillRectangle(Brushes.Pink, new Rect(0, 20, 100, 10));
var rc = new Rect(0, 0, 60, 60);
ctx.DrawImage(bmp.PlatformImpl, 1, rc, rc);
}
rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png"));
}
CompareImagesNoRenderer(testName);
}
#if AVALONIA_CAIRO
//wontfix
#else
[Theory]
#endif
[InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)]
public void WritableBitmapShouldBeUsable(PixelFormat fmt)
{
var writableBitmap = new WritableBitmap(256, 256, fmt);
var data = new int[256 * 256];
for (int y = 0; y < 256; y++)
for (int x = 0; x < 256; x++)
data[y * 256 + x] =(int)((uint)(x + (y << 8)) | 0xFF000000u);
using (var l = writableBitmap.Lock())
{
for(var r = 0; r<256; r++)
{
Marshal.Copy(data, r * 256, new IntPtr(l.Address.ToInt64() + r * l.RowBytes), 256);
}
}
var name = nameof(WritableBitmapShouldBeUsable) + "_" + fmt;
writableBitmap.Save(System.IO.Path.Combine(OutputPath, name + ".out.png"));
CompareImagesNoRenderer(testName);
}
}
}

17
tests/Avalonia.RenderTests/TestBase.cs

@ -132,6 +132,23 @@ namespace Avalonia.Direct2D1.RenderTests
}
}
protected void CompareImagesNoRenderer([CallerMemberName] string testName = "")
{
var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
var actualPath = Path.Combine(OutputPath, testName + ".out.png");
using (var expected = new MagickImage(expectedPath))
using (var actual = new MagickImage(actualPath))
{
double immediateError = expected.Compare(actual, ErrorMetric.RootMeanSquared);
if (immediateError > 0.022)
{
Assert.True(false, actualPath + ": Error = " + immediateError);
}
}
}
private class TestThreadingInterface : IPlatformThreadingInterface
{
public bool CurrentThreadIsLoopThread => MainThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId;

10
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -43,6 +43,11 @@ namespace Avalonia.UnitTests
return new MockStreamGeometryImpl();
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?))
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
return Mock.Of<IBitmapImpl>();
@ -52,5 +57,10 @@ namespace Avalonia.UnitTests
{
return Mock.Of<IBitmapImpl>();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotImplementedException();
}
}
}

174
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Visuals.UnitTests.VisualTree
{
class MockRenderInterface : IPlatformRenderInterface
{
public IFormattedTextImpl CreateFormattedText(
string text,
string fontFamilyName,
double fontSize,
FontStyle fontStyle,
TextAlignment textAlignment,
FontWeight fontWeight,
TextWrapping wrapping)
{
throw new NotImplementedException();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
throw new NotImplementedException();
}
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height)
{
throw new NotImplementedException();
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new MockStreamGeometry();
}
public IBitmapImpl LoadBitmap(Stream stream)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(string fileName)
{
throw new NotImplementedException();
}
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
{
throw new NotImplementedException();
}
public IWritableBitmapImpl CreateWritableBitmap(int width, int height, PixelFormat? fmt)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
public Rect Bounds
{
get
{
throw new NotImplementedException();
}
}
public Matrix Transform
{
get
{
throw new NotImplementedException();
}
set
{
throw new NotImplementedException();
}
}
public IStreamGeometryImpl Clone()
{
return this;
}
public bool FillContains(Point point)
{
return _impl.FillContains(point);
}
public Rect GetRenderBounds(double strokeThickness)
{
throw new NotImplementedException();
}
public IStreamGeometryContextImpl Open()
{
return _impl;
}
class MockStreamGeometryContext : IStreamGeometryContextImpl
{
private List<Point> points = new List<Point>();
public void ArcTo(Point point, Size size, double rotationAngle, bool isLargeArc, SweepDirection sweepDirection)
{
throw new NotImplementedException();
}
public void BeginFigure(Point startPoint, bool isFilled)
{
points.Add(startPoint);
}
public void CubicBezierTo(Point point1, Point point2, Point point3)
{
throw new NotImplementedException();
}
public void Dispose()
{
}
public void EndFigure(bool isClosed)
{
}
public void LineTo(Point point)
{
points.Add(point);
}
public void QuadraticBezierTo(Point control, Point endPoint)
{
throw new NotImplementedException();
}
public void SetFillRule(FillRule fillRule)
{
}
public bool FillContains(Point point)
{
// Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
var a = points[i];
var b = points[(i + 1) % points.Count];
var c = points[(i + 2) % points.Count];
Vector v0 = c - a;
Vector v1 = b - a;
Vector v2 = point - a;
var dot00 = v0 * v0;
var dot01 = v0 * v1;
var dot02 = v0 * v2;
var dot11 = v1 * v1;
var dot12 = v1 * v2;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
if ((u >= 0) && (v >= 0) && (u + v < 1)) return true;
}
return false;
}
}
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

BIN
tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

BIN
tests/TestFiles/Skia/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

BIN
tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Bgra8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

BIN
tests/TestFiles/Skia/Media/Bitmap/WritableBitmapShouldBeUsable_Rgba8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Loading…
Cancel
Save