Browse Source

Use PixelSize for device-dependent sizes.

pull/1889/head
Steven Kirk 8 years ago
parent
commit
a0e8dca6e9
  1. 5
      src/Avalonia.Controls/Image.cs
  2. 8
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  3. 2
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  4. 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  5. 31
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  6. 21
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  7. 29
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  8. 11
      src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
  9. 174
      src/Avalonia.Visuals/Media/PixelSize.cs
  10. 8
      src/Avalonia.Visuals/Platform/IBitmapImpl.cs
  11. 9
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  12. 32
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  13. 10
      src/Avalonia.Visuals/Platform/LockedFramebuffer.cs
  14. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  15. 10
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  16. 18
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  17. 9
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  18. 12
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  19. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  20. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  21. 27
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  22. 30
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  23. 18
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  24. 28
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  25. 20
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  26. 4
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  27. 8
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  28. 4
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  29. 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  30. 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  31. 3
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  32. 38
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  33. 12
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  34. 7
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  35. 4
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  36. 3
      src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
  37. 4
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  38. 28
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  39. 8
      tests/Avalonia.Controls.UnitTests/ImageTests.cs
  40. 21
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  41. 11
      tests/Avalonia.RenderTests/TestBase.cs
  42. 18
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  43. 6
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

5
src/Avalonia.Controls/Image.cs

@ -57,7 +57,7 @@ namespace Avalonia.Controls
if (source != null) if (source != null)
{ {
Rect viewPort = new Rect(Bounds.Size); Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = new Size(source.PixelWidth, source.PixelHeight); Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize); Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
Size scaledSize = sourceSize * scale; Size scaledSize = sourceSize * scale;
Rect destRect = viewPort Rect destRect = viewPort
@ -83,8 +83,7 @@ namespace Avalonia.Controls
if (source != null) if (source != null)
{ {
Size sourceSize = new Size(source.PixelWidth, source.PixelHeight); Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height)) if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{ {
return sourceSize; return sourceSize;

8
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -59,9 +59,9 @@ namespace Avalonia.Controls.Remote
if (_lastFrame != null) if (_lastFrame != null)
{ {
var fmt = (PixelFormat) _lastFrame.Format; var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width || if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
_bitmap.PixelHeight != _lastFrame.Height) _bitmap.PixelSize.Height != _lastFrame.Height)
_bitmap = new WriteableBitmap(_lastFrame.Width, _lastFrame.Height, fmt); _bitmap = new WriteableBitmap(new PixelSize(_lastFrame.Width, _lastFrame.Height), new Vector(96, 96), fmt);
using (var l = _bitmap.Lock()) using (var l = _bitmap.Lock())
{ {
var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width; var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width;
@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride, Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen); new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
} }
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelWidth, _bitmap.PixelHeight), context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
new Rect(Bounds.Size)); new Rect(Bounds.Size));
} }
base.Render(context); base.Render(context);

2
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -108,7 +108,7 @@ namespace Avalonia.Controls.Remote.Server
var handle = GCHandle.Alloc(data, GCHandleType.Pinned); var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try try
{ {
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), width, height, width * bpp, _dpi, (PixelFormat)fmt, _framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, (PixelFormat)fmt,
null); null);
Paint?.Invoke(new Rect(0, 0, width, height)); Paint?.Invoke(new Rect(0, 0, width, height));
} }

1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

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

@ -56,30 +56,29 @@ namespace Avalonia.Media.Imaging
{ {
PlatformImpl.Dispose(); PlatformImpl.Dispose();
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Bitmap"/> class. /// Initializes a new instance of the <see cref="Bitmap"/> class.
/// </summary> /// </summary>
/// <param name="format">Pixel format</param> /// <param name="format">The pixel format.</param>
/// <param name="data">Pointer to source bytes</param> /// <param name="data">The pointer to the source bytes.</param>
/// <param name="width">Bitmap width</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">Bitmap height</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="stride">Bytes per row</param> /// <param name="stride">The number of bytes per row.</param>
public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>() PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
.LoadBitmap(format, data, width, height, stride)); .LoadBitmap(format, data, size, dpi, stride));
} }
/// <summary> /// <inheritdoc/>
/// Gets the width of the bitmap, in pixels. public Vector Dpi => PlatformImpl.Item.Dpi;
/// </summary>
public int PixelWidth => PlatformImpl.Item.PixelWidth;
/// <summary> /// <inheritdoc/>
/// Gets the height of the bitmap, in pixels. public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi);
/// </summary>
public int PixelHeight => PlatformImpl.Item.PixelHeight; /// <inheritdoc/>
public PixelSize PixelSize => PlatformImpl.Item.PixelSize;
/// <summary> /// <summary>
/// Gets the platform-specific bitmap implementation. /// Gets the platform-specific bitmap implementation.

21
src/Avalonia.Visuals/Media/Imaging/IBitmap.cs

@ -14,20 +14,33 @@ namespace Avalonia.Media.Imaging
public interface IBitmap : IDisposable public interface IBitmap : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the width of the bitmap, in pixels. /// Gets the dots per inch (DPI) of the image.
/// </summary> /// </summary>
int PixelWidth { get; } /// <remarks>
/// Note that Skia does not currently support reading the DPI of an image so this value
/// will always be 96dpi on Skia.
/// </remarks>
Vector Dpi { get; }
/// <summary> /// <summary>
/// Gets the height of the bitmap, in pixels. /// Gets the size of the bitmap, in device pixels.
/// </summary> /// </summary>
int PixelHeight { get; } PixelSize PixelSize { get; }
/// <summary> /// <summary>
/// Gets the platform-specific bitmap implementation. /// Gets the platform-specific bitmap implementation.
/// </summary> /// </summary>
IRef<IBitmapImpl> PlatformImpl { get; } IRef<IBitmapImpl> PlatformImpl { get; }
/// <summary>
/// Gets the size of the image, in device independent pixels.
/// </summary>
/// <remarks>
/// Note that Skia does not currently support reading the DPI of an image so this value
/// will equal <see cref="PixelSize"/> on Skia.
/// </remarks>
Size Size { get; }
/// <summary> /// <summary>
/// Saves the bitmap to a file. /// Saves the bitmap to a file.
/// </summary> /// </summary>

29
src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs

@ -17,12 +17,19 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class. /// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class.
/// </summary> /// </summary>
/// <param name="pixelWidth">The width of the bitmap in pixels.</param> /// <param name="pixelSize">The size of the bitmap.</param>
/// <param name="pixelHeight">The height of the bitmap in pixels.</param> public RenderTargetBitmap(PixelSize pixelSize)
/// <param name="dpiX">The horizontal DPI of the bitmap.</param> : this(pixelSize, new Vector(96, 96))
/// <param name="dpiY">The vertical DPI of the bitmap.</param> {
public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96) }
: this(RefCountable.Create(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY)))
/// <summary>
/// Initializes a new instance of the <see cref="RenderTargetBitmap"/> class.
/// </summary>
/// <param name="pixelSize">The size of the bitmap in device pixels.</param>
/// <param name="dpi">The DPI of the bitmap.</param>
public RenderTargetBitmap(PixelSize pixelSize, Vector dpi)
: this(RefCountable.Create(CreateImpl(pixelSize, dpi)))
{ {
} }
@ -45,15 +52,13 @@ namespace Avalonia.Media.Imaging
/// <summary> /// <summary>
/// Creates a platform-specific implementation for a <see cref="RenderTargetBitmap"/>. /// Creates a platform-specific implementation for a <see cref="RenderTargetBitmap"/>.
/// </summary> /// </summary>
/// <param name="width">The width of the bitmap.</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">The height of the bitmap.</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param>
/// <returns>The platform-specific implementation.</returns> /// <returns>The platform-specific implementation.</returns>
private static IRenderTargetBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY) private static IRenderTargetBitmapImpl CreateImpl(PixelSize size, Vector dpi)
{ {
IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY); return factory.CreateRenderTargetBitmap(size, dpi);
} }
/// <inheritdoc/> /// <inheritdoc/>

11
src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs

@ -7,8 +7,15 @@ namespace Avalonia.Media.Imaging
/// </summary> /// </summary>
public class WriteableBitmap : Bitmap public class WriteableBitmap : Bitmap
{ {
public WriteableBitmap(int width, int height, PixelFormat? format = null) /// <summary>
: base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWriteableBitmap(width, height, format)) /// Initializes a new instance of the <see cref="WriteableBitmap"/> class.
/// </summary>
/// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="format">The pixel format (optional).</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
: base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWriteableBitmap(size, dpi, format))
{ {
} }

174
src/Avalonia.Visuals/Media/PixelSize.cs

@ -0,0 +1,174 @@
// 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.Globalization;
using Avalonia.Utilities;
namespace Avalonia
{
/// <summary>
/// Represents a size in device pixels.
/// </summary>
public readonly struct PixelSize
{
/// <summary>
/// A size representing zero
/// </summary>
public static readonly PixelSize Empty = new PixelSize(0, 0);
/// <summary>
/// Initializes a new instance of the <see cref="PixelSize"/> structure.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public PixelSize(int width, int height)
{
Width = width;
Height = height;
}
/// <summary>
/// Gets the aspect ratio of the size.
/// </summary>
public double AspectRatio => (double)Width / Height;
/// <summary>
/// Gets the width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Checks for equality between two <see cref="PixelSize"/>s.
/// </summary>
/// <param name="left">The first size.</param>
/// <param name="right">The second size.</param>
/// <returns>True if the sizes are equal; otherwise false.</returns>
public static bool operator ==(PixelSize left, PixelSize right)
{
return left.Width == right.Width && left.Height == right.Height;
}
/// <summary>
/// Checks for inequality between two <see cref="Size"/>s.
/// </summary>
/// <param name="left">The first size.</param>
/// <param name="right">The second size.</param>
/// <returns>True if the sizes are unequal; otherwise false.</returns>
public static bool operator !=(PixelSize left, PixelSize right)
{
return !(left == right);
}
/// <summary>
/// Parses a <see cref="PixelSize"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="PixelSize"/>.</returns>
public static PixelSize Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
{
return new PixelSize(
tokenizer.ReadInt32(),
tokenizer.ReadInt32());
}
}
/// <summary>
/// Checks for equality between a size and an object.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// True if <paramref name="obj"/> is a size that equals the current size.
/// </returns>
public override bool Equals(object obj)
{
if (obj is PixelSize other)
{
return this == other;
}
return false;
}
/// <summary>
/// Returns a hash code for a <see cref="PixelSize"/>.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + Width.GetHashCode();
hash = (hash * 23) + Height.GetHashCode();
return hash;
}
}
/// <summary>
/// Returns a new <see cref="PixelSize"/> with the same height and the specified width.
/// </summary>
/// <param name="width">The width.</param>
/// <returns>The new <see cref="PixelSize"/>.</returns>
public PixelSize WithWidth(int width) => new PixelSize(width, Height);
/// <summary>
/// Returns a new <see cref="PixelSize"/> with the same width and the specified height.
/// </summary>
/// <param name="height">The height.</param>
/// <returns>The new <see cref="PixelSize"/>.</returns>
public PixelSize WithHeight(int height) => new PixelSize(Width, height);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96));
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, double dpi) => new PixelSize(
(int)(size.Width * (dpi / 96)),
(int)(size.Height * (dpi / 96)));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize(
(int)(size.Width * (dpi.X / 96)),
(int)(size.Height * (dpi.Y / 96)));
/// <summary>
/// Returns the string representation of the size.
/// </summary>
/// <returns>The string representation of the size.</returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", Width, Height);
}
}
}

8
src/Avalonia.Visuals/Platform/IBitmapImpl.cs

@ -12,14 +12,14 @@ namespace Avalonia.Platform
public interface IBitmapImpl : IDisposable public interface IBitmapImpl : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the width of the bitmap, in pixels. /// Gets the dots per inch (DPI) of the image.
/// </summary> /// </summary>
int PixelWidth { get; } Vector Dpi { get; }
/// <summary> /// <summary>
/// Gets the height of the bitmap, in pixels. /// Gets the size of the bitmap, in device pixels.
/// </summary> /// </summary>
int PixelHeight { get; } PixelSize PixelSize { get; }
/// <summary> /// <summary>
/// Saves the bitmap to a file. /// Saves the bitmap to a file.

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

@ -10,14 +10,9 @@ namespace Avalonia.Platform
IntPtr Address { get; } IntPtr Address { get; }
/// <summary> /// <summary>
/// Framebuffer width /// Gets the framebuffer size in device pixels.
/// </summary> /// </summary>
int Width { get; } PixelSize Size{ get; }
/// <summary>
/// Framebuffer height
/// </summary>
int Height { get; }
/// <summary> /// <summary>
/// Number of bytes per row /// Number of bytes per row

32
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -49,25 +49,19 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Creates a render target bitmap implementation. /// Creates a render target bitmap implementation.
/// </summary> /// </summary>
/// <param name="width">The width of the bitmap.</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">The height of the bitmap.</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="dpiX">The horizontal DPI of the bitmap.</param>
/// <param name="dpiY">The vertical DPI of the bitmap.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/>.</returns> /// <returns>An <see cref="IRenderTargetBitmapImpl"/>.</returns>
IRenderTargetBitmapImpl CreateRenderTargetBitmap( IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi);
int width,
int height,
double dpiX,
double dpiY);
/// <summary> /// <summary>
/// Creates a writeable bitmap implementation. /// Creates a writeable bitmap implementation.
/// </summary> /// </summary>
/// <param name="width">The width of the bitmap.</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">The height of the bitmap.</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="format">Pixel format (optional).</param> /// <param name="format">Pixel format (optional).</param>
/// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns> /// <returns>An <see cref="IWriteableBitmapImpl"/>.</returns>
IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null); IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null);
/// <summary> /// <summary>
/// Loads a bitmap implementation from a file.. /// Loads a bitmap implementation from a file..
@ -84,14 +78,14 @@ namespace Avalonia.Platform
IBitmapImpl LoadBitmap(Stream stream); IBitmapImpl LoadBitmap(Stream stream);
/// <summary> /// <summary>
/// Loads a bitmap implementation from a pixels in memory.. /// Loads a bitmap implementation from a pixels in memory.
/// </summary> /// </summary>
/// <param name="format">Pixel format</param> /// <param name="format">The pixel format.</param>
/// <param name="data">Pointer to source bytes</param> /// <param name="data">The pointer to source bytes.</param>
/// <param name="width">Bitmap width</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">Bitmap height</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="stride">Bytes per row</param> /// <param name="stride">The number of bytes per row.</param>
/// <returns>An <see cref="IBitmapImpl"/>.</returns> /// <returns>An <see cref="IBitmapImpl"/>.</returns>
IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride); IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride);
} }
} }

10
src/Avalonia.Visuals/Platform/LockedFramebuffer.cs

@ -6,21 +6,19 @@ namespace Avalonia.Platform
{ {
private readonly Action _onDispose; private readonly Action _onDispose;
public LockedFramebuffer(IntPtr address, int width, int height, int rowBytes, Vector dpi, PixelFormat format, public LockedFramebuffer(IntPtr address, PixelSize size, int rowBytes, Vector dpi, PixelFormat format,
Action onDispose) Action onDispose)
{ {
_onDispose = onDispose; _onDispose = onDispose;
Address = address; Address = address;
Width = width; Size = Size;
Height = height;
RowBytes = rowBytes; RowBytes = rowBytes;
Dpi = dpi; Dpi = dpi;
Format = format; Format = format;
} }
public IntPtr Address { get; } public IntPtr Address { get; }
public int Width { get; } public PixelSize Size { get; }
public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format { get; } public PixelFormat Format { get; }
@ -30,4 +28,4 @@ namespace Avalonia.Platform
_onDispose?.Invoke(); _onDispose?.Invoke();
} }
} }
} }

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

@ -344,7 +344,7 @@ namespace Avalonia.Rendering
foreach (var layer in scene.Layers) foreach (var layer in scene.Layers)
{ {
var bitmap = Layers[layer.LayerRoot].Bitmap; var bitmap = Layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.Item.PixelWidth, bitmap.Item.PixelHeight); var sourceRect = new Rect(0, 0, bitmap.Item.PixelSize.Width, bitmap.Item.PixelSize.Height);
if (layer.GeometryClip != null) if (layer.GeometryClip != null)
{ {
@ -368,7 +368,7 @@ namespace Avalonia.Rendering
if (_overlay != null) if (_overlay != null)
{ {
var sourceRect = new Rect(0, 0, _overlay.Item.PixelWidth, _overlay.Item.PixelHeight); var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect); context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
} }
@ -453,8 +453,8 @@ namespace Avalonia.Rendering
var pixelSize = size * scaling; var pixelSize = size * scaling;
if (_overlay == null || if (_overlay == null ||
_overlay.Item.PixelWidth != pixelSize.Width || _overlay.Item.PixelSize.Width != pixelSize.Width ||
_overlay.Item.PixelHeight != pixelSize.Height) _overlay.Item.PixelSize.Height != pixelSize.Height)
{ {
_overlay?.Dispose(); _overlay?.Dispose();
_overlay = RefCountable.Create(parentContext.CreateLayer(size)); _overlay = RefCountable.Create(parentContext.CreateLayer(size));

10
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -22,8 +22,7 @@ namespace Avalonia.Gtk3
height *= _factor; height *= _factor;
_surface = new ManagedCairoSurface(width, height); _surface = new ManagedCairoSurface(width, height);
Width = width; Size = new PixelSize(width, height);
Height = height;
Address = _surface.Buffer; Address = _surface.Buffer;
RowBytes = _surface.Stride; RowBytes = _surface.Stride;
Native.CairoSurfaceFlush(_surface.Surface); Native.CairoSurfaceFlush(_surface.Surface);
@ -115,18 +114,17 @@ namespace Avalonia.Gtk3
if (_impl.CurrentCairoContext != IntPtr.Zero) if (_impl.CurrentCairoContext != IntPtr.Zero)
Draw(_impl.CurrentCairoContext, _surface.Surface, _factor); Draw(_impl.CurrentCairoContext, _surface.Surface, _factor);
else else
DrawToWidget(_widget, _surface.Surface, Width, Height, _factor); DrawToWidget(_widget, _surface.Surface, Size.Width, Size.Height, _factor);
_surface.Dispose(); _surface.Dispose();
} }
else else
_impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Width, Height)); _impl.SetNextRenderOperation(new RenderOp(_widget, _surface, _factor, Size.Width, Size.Height));
_surface = null; _surface = null;
} }
} }
public IntPtr Address { get; } public IntPtr Address { get; }
public int Width { get; } public PixelSize Size { get; }
public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }

18
src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs

@ -13,12 +13,11 @@ namespace Avalonia.Gtk3
{ {
_display = display; _display = display;
_xid = xid; _xid = xid;
Width = width*factor; Size = new PixelSize(width * factor, height * factor);
Height = height*factor; RowBytes = Size.Width * 4;
RowBytes = Width * 4;
Dpi = new Vector(96, 96) * factor; Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Bgra8888; Format = PixelFormat.Bgra8888;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * Height); _blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(RowBytes * Size.Height);
Address = _blob.Address; Address = _blob.Address;
} }
@ -26,8 +25,8 @@ namespace Avalonia.Gtk3
{ {
var image = new X11.XImage(); var image = new X11.XImage();
int bitsPerPixel = 32; int bitsPerPixel = 32;
image.width = Width; image.width = Size.Width;
image.height = Height; image.height = Size.Height;
image.format = 2; //ZPixmap; image.format = 2; //ZPixmap;
image.data = Address; image.data = Address;
image.byte_order = 0;// LSBFirst; image.byte_order = 0;// LSBFirst;
@ -35,12 +34,12 @@ namespace Avalonia.Gtk3
image.bitmap_bit_order = 0;// LSBFirst; image.bitmap_bit_order = 0;// LSBFirst;
image.bitmap_pad = bitsPerPixel; image.bitmap_pad = bitsPerPixel;
image.depth = 24; image.depth = 24;
image.bytes_per_line = RowBytes - Width * 4; image.bytes_per_line = RowBytes - Size.Width * 4;
image.bits_per_pixel = bitsPerPixel; image.bits_per_pixel = bitsPerPixel;
X11.XLockDisplay(_display); X11.XLockDisplay(_display);
X11.XInitImage(ref image); X11.XInitImage(ref image);
var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero);
X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint)Size.Width, (uint)Size.Height);
X11.XFreeGC(_display, gc); X11.XFreeGC(_display, gc);
X11.XSync(_display, true); X11.XSync(_display, true);
X11.XUnlockDisplay(_display); X11.XUnlockDisplay(_display);
@ -48,8 +47,7 @@ namespace Avalonia.Gtk3
} }
public IntPtr Address { get; } public IntPtr Address { get; }
public int Width { get; } public PixelSize Size { get; }
public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format { get; } public PixelFormat Format { get; }

9
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -19,7 +19,7 @@ namespace Avalonia.LinuxFramebuffer
_address = address; _address = address;
Dpi = dpi; Dpi = dpi;
//Use double buffering to avoid flicker //Use double buffering to avoid flicker
Address = Marshal.AllocHGlobal(RowBytes * Height); Address = Marshal.AllocHGlobal(RowBytes * Size.Height);
} }
@ -31,17 +31,16 @@ namespace Avalonia.LinuxFramebuffer
public void Dispose() public void Dispose()
{ {
VSync(); VSync();
NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Height)); NativeUnsafeMethods.memcpy(_address, Address, new IntPtr(RowBytes * Size.Height));
Marshal.FreeHGlobal(Address); Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero; Address = IntPtr.Zero;
} }
public IntPtr Address { get; private set; } public IntPtr Address { get; private set; }
public int Width => (int)_varInfo.xres; public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int Height => (int) _varInfo.yres;
public int RowBytes => (int) _fixedInfo.line_length; public int RowBytes => (int) _fixedInfo.line_length;
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888; public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
} }
} }

12
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@ -24,12 +24,11 @@ namespace Avalonia.MonoMac
_isDeferred = !Dispatcher.UIThread.CheckAccess(); _isDeferred = !Dispatcher.UIThread.CheckAccess();
_logicalSize = _view.LogicalSize; _logicalSize = _view.LogicalSize;
var pixelSize = _view.PixelSize; var pixelSize = _view.PixelSize;
Width = (int)pixelSize.Width; Size = new PixelSize((int)pixelSize.Width, (int)pixelSize.Height);
Height = (int)pixelSize.Height; RowBytes = Size.Width * 4;
RowBytes = Width * 4;
Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height); Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
Format = PixelFormat.Rgba8888; Format = PixelFormat.Rgba8888;
var size = Height * RowBytes; var size = Size.Height * RowBytes;
_blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size); _blob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size);
memset(Address, 0, new IntPtr(size)); memset(Address, 0, new IntPtr(size));
} }
@ -43,7 +42,7 @@ namespace Avalonia.MonoMac
try try
{ {
using (var colorSpace = CGColorSpace.CreateDeviceRGB()) using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4, using (var bContext = new CGBitmapContext(Address, Size.Width, Size.Height, 8, Size.Width * 4,
colorSpace, (CGImageAlphaInfo)nfo)) colorSpace, (CGImageAlphaInfo)nfo))
image = bContext.ToImage(); image = bContext.ToImage();
lock (_view.SyncRoot) lock (_view.SyncRoot)
@ -79,8 +78,7 @@ namespace Avalonia.MonoMac
} }
public IntPtr Address => _blob.Address; public IntPtr Address => _blob.Address;
public int Width { get; } public PixelSize Size { get; }
public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format { get; } public PixelFormat Format { get; }

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -135,7 +135,7 @@ namespace Avalonia.Skia
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{ {
PushOpacityMask(opacityMask, opacityMaskRect); PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.Item.PixelWidth, source.Item.PixelHeight), destRect, BitmapInterpolationMode.Default); DrawImage(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
PopOpacityMask(); PopOpacityMask();
} }
@ -384,7 +384,7 @@ namespace Avalonia.Skia
private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage) private void ConfigureTileBrush(ref PaintWrapper paintWrapper, Size targetSize, ITileBrush tileBrush, IDrawableBitmapImpl tileBrushImage)
{ {
var calc = new TileBrushCalculator(tileBrush, var calc = new TileBrushCalculator(tileBrush,
new Size(tileBrushImage.PixelWidth, tileBrushImage.PixelHeight), targetSize); new Size(tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height), targetSize);
var intermediate = CreateRenderTarget( var intermediate = CreateRenderTarget(
(int)calc.IntermediateSize.Width, (int)calc.IntermediateSize.Width,
@ -394,7 +394,7 @@ namespace Avalonia.Skia
using (var context = intermediate.CreateDrawingContext(null)) using (var context = intermediate.CreateDrawingContext(null))
{ {
var rect = new Rect(0, 0, tileBrushImage.PixelWidth, tileBrushImage.PixelHeight); var rect = new Rect(0, 0, tileBrushImage.PixelSize.Width, tileBrushImage.PixelSize.Height);
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);

2
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -41,7 +41,7 @@ namespace Avalonia.Skia
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{ {
var framebuffer = _platformSurface.Lock(); var framebuffer = _platformSurface.Lock();
var framebufferImageInfo = new SKImageInfo(framebuffer.Width, framebuffer.Height, var framebufferImageInfo = new SKImageInfo(framebuffer.Size.Width, framebuffer.Size.Height,
framebuffer.Format.ToSkColorType(), SKAlphaType.Premul); framebuffer.Format.ToSkColorType(), SKAlphaType.Premul);
CreateSurface(framebufferImageInfo, framebuffer); CreateSurface(framebufferImageInfo, framebuffer);

27
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -31,22 +31,24 @@ namespace Avalonia.Skia
throw new ArgumentException("Unable to load bitmap from provided data"); throw new ArgumentException("Unable to load bitmap from provided data");
} }
PixelWidth = _image.Width; PixelSize = new PixelSize(_image.Width, _image.Height);
PixelHeight = _image.Height;
// TODO: Skia doesn't have an API for DPI.
Dpi = new Vector(96, 96);
} }
} }
/// <summary> /// <summary>
/// Create immutable bitmap from given pixel data copy. /// Create immutable bitmap from given pixel data copy.
/// </summary> /// </summary>
/// <param name="width">Width of data pixels.</param> /// <param name="size">Size of the bitmap.</param>
/// <param name="height">Height of data pixels.</param> /// <param name="dpi">DPI of the bitmap.</param>
/// <param name="stride">Stride of data pixels.</param> /// <param name="stride">Stride of data pixels.</param>
/// <param name="format">Format of data pixels.</param> /// <param name="format">Format of data pixels.</param>
/// <param name="data">Data pixels.</param> /// <param name="data">Data pixels.</param>
public ImmutableBitmap(int width, int height, int stride, PixelFormat format, IntPtr data) public ImmutableBitmap(PixelSize size, Vector dpi, int stride, PixelFormat format, IntPtr data)
{ {
var imageInfo = new SKImageInfo(width, height, format.ToSkColorType(), SKAlphaType.Premul); var imageInfo = new SKImageInfo(size.Width, size.Height, format.ToSkColorType(), SKAlphaType.Premul);
_image = SKImage.FromPixelCopy(imageInfo, data, stride); _image = SKImage.FromPixelCopy(imageInfo, data, stride);
@ -55,15 +57,12 @@ namespace Avalonia.Skia
throw new ArgumentException("Unable to create bitmap from provided data"); throw new ArgumentException("Unable to create bitmap from provided data");
} }
PixelWidth = width; PixelSize = size;
PixelHeight = height; Dpi = dpi;
} }
/// <inheritdoc /> public Vector Dpi { get; }
public int PixelWidth { get; } public PixelSize PixelSize { get; }
/// <inheritdoc />
public int PixelHeight { get; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
@ -89,4 +88,4 @@ namespace Avalonia.Skia
context.Canvas.DrawImage(_image, sourceRect, destRect, paint); context.Canvas.DrawImage(_image, sourceRect, destRect, paint);
} }
} }
} }

30
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -49,34 +49,28 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
return new ImmutableBitmap(width, height, stride, format, data); return new ImmutableBitmap(size, dpi, stride, format, data);
} }
/// <inheritdoc /> /// <inheritdoc />
public IRenderTargetBitmapImpl CreateRenderTargetBitmap( public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
int width,
int height,
double dpiX,
double dpiY)
{ {
if (width < 1) if (size.Width < 1)
{ {
throw new ArgumentException("Width can't be less than 1", nameof(width)); throw new ArgumentException("Width can't be less than 1", nameof(size));
} }
if (height < 1) if (size.Height < 1)
{ {
throw new ArgumentException("Height can't be less than 1", nameof(height)); throw new ArgumentException("Height can't be less than 1", nameof(size));
} }
var dpi = new Vector(dpiX, dpiY);
var createInfo = new SurfaceRenderTarget.CreateInfo var createInfo = new SurfaceRenderTarget.CreateInfo
{ {
Width = width, Width = size.Width,
Height = height, Height = size.Height,
Dpi = dpi, Dpi = dpi,
DisableTextLcdRendering = false DisableTextLcdRendering = false
}; };
@ -100,9 +94,9 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
{ {
return new WriteableBitmapImpl(width, height, format); return new WriteableBitmapImpl(size, dpi, format);
} }
} }
} }

18
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -15,7 +15,6 @@ namespace Avalonia.Skia
/// </summary> /// </summary>
public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
{ {
private readonly Vector _dpi;
private readonly SKSurface _surface; private readonly SKSurface _surface;
private readonly SKCanvas _canvas; private readonly SKCanvas _canvas;
private readonly bool _disableLcdRendering; private readonly bool _disableLcdRendering;
@ -26,12 +25,11 @@ namespace Avalonia.Skia
/// <param name="createInfo">Create info.</param> /// <param name="createInfo">Create info.</param>
public SurfaceRenderTarget(CreateInfo createInfo) public SurfaceRenderTarget(CreateInfo createInfo)
{ {
PixelWidth = createInfo.Width; PixelSize = new PixelSize(createInfo.Width, createInfo.Height);
PixelHeight = createInfo.Height; Dpi = createInfo.Dpi;
_dpi = createInfo.Dpi;
_disableLcdRendering = createInfo.DisableTextLcdRendering;
_surface = CreateSurface(PixelWidth, PixelHeight, createInfo.Format); _disableLcdRendering = createInfo.DisableTextLcdRendering;
_surface = CreateSurface(PixelSize.Width, PixelSize.Height, createInfo.Format);
_canvas = _surface?.Canvas; _canvas = _surface?.Canvas;
@ -71,7 +69,7 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo var createInfo = new DrawingContextImpl.CreateInfo
{ {
Canvas = _canvas, Canvas = _canvas,
Dpi = _dpi, Dpi = Dpi,
VisualBrushRenderer = visualBrushRenderer, VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = _disableLcdRendering DisableTextLcdRendering = _disableLcdRendering
}; };
@ -80,10 +78,10 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public int PixelWidth { get; } public Vector Dpi { get; }
/// <inheritdoc /> /// <inheritdoc />
public int PixelHeight { get; } public PixelSize PixelSize { get; }
/// <inheritdoc /> /// <inheritdoc />
public void Save(string fileName) public void Save(string fileName)
@ -166,4 +164,4 @@ namespace Avalonia.Skia
public bool DisableTextLcdRendering; public bool DisableTextLcdRendering;
} }
} }
} }

28
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -20,13 +20,13 @@ namespace Avalonia.Skia
/// <summary> /// <summary>
/// Create new writeable bitmap. /// Create new writeable bitmap.
/// </summary> /// </summary>
/// <param name="width">Width.</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">Height.</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="format">Format.</param> /// <param name="format">The pixel format.</param>
public WriteableBitmapImpl(int width, int height, PixelFormat? format = null) public WriteableBitmapImpl(PixelSize size, Vector dpi, PixelFormat? format = null)
{ {
PixelHeight = height; PixelSize = size;
PixelWidth = width; Dpi = dpi;
var colorType = PixelFormatHelper.ResolveColorType(format); var colorType = PixelFormatHelper.ResolveColorType(format);
@ -36,24 +36,23 @@ namespace Avalonia.Skia
{ {
_bitmap = new SKBitmap(); _bitmap = new SKBitmap();
var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); var nfo = new SKImageInfo(size.Width, size.Height, colorType, SKAlphaType.Premul);
var blob = runtimePlatform.AllocBlob(nfo.BytesSize); var blob = runtimePlatform.AllocBlob(nfo.BytesSize);
_bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob); _bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, s_releaseDelegate, blob);
} }
else else
{ {
_bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); _bitmap = new SKBitmap(size.Width, size.Height, colorType, SKAlphaType.Premul);
} }
_bitmap.Erase(SKColor.Empty); _bitmap.Erase(SKColor.Empty);
} }
/// <inheritdoc /> public Vector Dpi { get; }
public int PixelWidth { get; }
/// <inheritdoc /> /// <inheritdoc />
public int PixelHeight { get; } public PixelSize PixelSize { get; }
/// <inheritdoc /> /// <inheritdoc />
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
@ -133,10 +132,7 @@ namespace Avalonia.Skia
public IntPtr Address => _bitmap.GetPixels(); public IntPtr Address => _bitmap.GetPixels();
/// <inheritdoc /> /// <inheritdoc />
public int Width => _bitmap.Width; public PixelSize Size => new PixelSize(_bitmap.Width, _bitmap.Height);
/// <inheritdoc />
public int Height => _bitmap.Height;
/// <inheritdoc /> /// <inheritdoc />
public int RowBytes => _bitmap.RowBytes; public int RowBytes => _bitmap.RowBytes;
@ -148,4 +144,4 @@ namespace Avalonia.Skia
public PixelFormat Format => _bitmap.ColorType.ToPixelFormat(); public PixelFormat Format => _bitmap.ColorType.ToPixelFormat();
} }
} }
} }

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

@ -110,9 +110,9 @@ namespace Avalonia.Direct2D1
SharpDX.Configuration.EnableReleaseOnFinalizer = true; SharpDX.Configuration.EnableReleaseOnFinalizer = true;
} }
public IBitmapImpl CreateBitmap(int width, int height) public IBitmapImpl CreateBitmap(PixelSize size, Vector dpi)
{ {
return new WicBitmapImpl(width, height); return new WicBitmapImpl(size, dpi);
} }
public IFormattedTextImpl CreateFormattedText( public IFormattedTextImpl CreateFormattedText(
@ -159,18 +159,14 @@ namespace Avalonia.Direct2D1
throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces");
} }
public IRenderTargetBitmapImpl CreateRenderTargetBitmap( public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
int width,
int height,
double dpiX,
double dpiY)
{ {
return new WicRenderTargetBitmapImpl(width, height, dpiX, dpiY); return new WicRenderTargetBitmapImpl(size, dpi);
} }
public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = null) public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
{ {
return new WriteableWicBitmapImpl(width, height, format); return new WriteableWicBitmapImpl(size, dpi, format);
} }
public IStreamGeometryImpl CreateStreamGeometry() public IStreamGeometryImpl CreateStreamGeometry()
@ -188,9 +184,9 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(stream); return new WicBitmapImpl(stream);
} }
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
return new WicBitmapImpl(format, data, width, height, stride); return new WicBitmapImpl(format, data, size, dpi, stride);
} }
} }
} }

4
src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs

@ -40,7 +40,7 @@ namespace Avalonia.Direct2D1
private readonly ILockedFramebuffer _target; private readonly ILockedFramebuffer _target;
public FramebufferShim(ILockedFramebuffer target) : public FramebufferShim(ILockedFramebuffer target) :
base(target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) base(target.Size, target.Dpi, target.Format)
{ {
_target = target; _target = target;
} }
@ -51,7 +51,7 @@ namespace Avalonia.Direct2D1
{ {
using (var l = WicImpl.Lock(BitmapLockFlags.Read)) using (var l = WicImpl.Lock(BitmapLockFlags.Read))
{ {
for (var y = 0; y < _target.Height; y++) for (var y = 0; y < _target.Size.Height; y++)
{ {
UnmanagedMethods.CopyMemory( UnmanagedMethods.CopyMemory(
(_target.Address + _target.RowBytes * y), (_target.Address + _target.RowBytes * y),

8
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -330,12 +330,8 @@ namespace Avalonia.Direct2D1.Media
{ {
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height);
var pixelSize = size * (dpi / 96); var pixelSize = PixelSize.FromSize(size, dpi);
return platform.CreateRenderTargetBitmap( return platform.CreateRenderTargetBitmap(pixelSize, dpi);
(int)pixelSize.Width,
(int)pixelSize.Height,
dpi.X,
dpi.Y);
} }
} }

4
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -20,7 +20,7 @@ namespace Avalonia.Direct2D1.Media
BitmapImpl bitmap, BitmapImpl bitmap,
Size targetSize) Size targetSize)
{ {
var calc = new TileBrushCalculator(brush, new Size(bitmap.PixelWidth, bitmap.PixelHeight), targetSize); var calc = new TileBrushCalculator(brush, bitmap.PixelSize.ToSize(96), targetSize);
if (!calc.NeedsIntermediate) if (!calc.NeedsIntermediate)
{ {
@ -99,7 +99,7 @@ namespace Avalonia.Direct2D1.Media
using (var context = new RenderTarget(result).CreateDrawingContext(null)) using (var context = new RenderTarget(result).CreateDrawingContext(null))
{ {
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight); var rect = new Rect(0, 0, bitmap.PixelSize.Width, bitmap.PixelSize.Height);
context.Clear(Colors.Transparent); context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip); context.PushClip(calc.IntermediateClip);

4
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -7,8 +7,8 @@ namespace Avalonia.Direct2D1.Media
{ {
public abstract class BitmapImpl : IBitmapImpl, IDisposable public abstract class BitmapImpl : IBitmapImpl, IDisposable
{ {
public abstract int PixelWidth { get; } public abstract Vector Dpi { get; }
public abstract int PixelHeight { get; } public abstract PixelSize PixelSize { get; }
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);

4
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -30,8 +30,8 @@ namespace Avalonia.Direct2D1.Media
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap)); _direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
} }
public override int PixelWidth => _direct2DBitmap.PixelSize.Width; public override Vector Dpi => _direct2DBitmap.DotsPerInch.ToAvaloniaVector();
public override int PixelHeight => _direct2DBitmap.PixelSize.Height; public override PixelSize PixelSize => _direct2DBitmap.PixelSize.ToAvalonia();
public override void Dispose() public override void Dispose()
{ {

3
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -19,9 +19,6 @@ namespace Avalonia.Direct2D1.Media.Imaging
_renderTarget = renderTarget; _renderTarget = renderTarget;
} }
public override int PixelWidth => _renderTarget.PixelSize.Width;
public override int PixelHeight => _renderTarget.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible( public static D2DRenderTargetBitmapImpl CreateCompatible(
SharpDX.Direct2D1.RenderTarget renderTarget, SharpDX.Direct2D1.RenderTarget renderTarget,
Size size) Size size)

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

@ -42,10 +42,10 @@ namespace Avalonia.Direct2D1.Media
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class. /// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary> /// </summary>
/// <param name="width">The width of the bitmap.</param> /// <param name="size">The size of the bitmap in device pixels.</param>
/// <param name="height">The height of the bitmap.</param> /// <param name="dpi">The DPI of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param> /// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(int width, int height, APixelFormat? pixelFormat = null) public WicBitmapImpl(PixelSize size, Vector dpi, APixelFormat? pixelFormat = null)
{ {
if (!pixelFormat.HasValue) if (!pixelFormat.HasValue)
{ {
@ -55,19 +55,22 @@ namespace Avalonia.Direct2D1.Media
PixelFormat = pixelFormat; PixelFormat = pixelFormat;
WicImpl = new Bitmap( WicImpl = new Bitmap(
Direct2D1Platform.ImagingFactory, Direct2D1Platform.ImagingFactory,
width, size.Width,
height, size.Height,
pixelFormat.Value.ToWic(), pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad); BitmapCreateCacheOption.CacheOnLoad);
WicImpl.SetResolution(dpi.X, dpi.Y);
} }
public WicBitmapImpl(APixelFormat format, IntPtr data, int width, int height, int stride) public WicBitmapImpl(APixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand); WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, size.Width, size.Height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
WicImpl.SetResolution(dpi.X, dpi.Y);
PixelFormat = format; PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write)) using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{ {
for (var row = 0; row < height; row++) for (var row = 0; row < size.Height; row++)
{ {
UnmanagedMethods.CopyMemory( UnmanagedMethods.CopyMemory(
(l.Data.DataPointer + row * l.Stride), (l.Data.DataPointer + row * l.Stride),
@ -77,17 +80,18 @@ namespace Avalonia.Direct2D1.Media
} }
} }
protected APixelFormat? PixelFormat { get; } public override Vector Dpi
{
get
{
WicImpl.GetResolution(out double x, out double y);
return new Vector(x, y);
}
}
/// <summary> public override PixelSize PixelSize => WicImpl.Size.ToAvalonia();
/// Gets the width of the bitmap, in pixels.
/// </summary>
public override int PixelWidth => WicImpl.Size.Width;
/// <summary> protected APixelFormat? PixelFormat { get; }
/// Gets the height of the bitmap, in pixels.
/// </summary>
public override int PixelHeight => WicImpl.Size.Height;
public override void Dispose() public override void Dispose()
{ {

12
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -13,17 +13,15 @@ namespace Avalonia.Direct2D1.Media
private readonly WicRenderTarget _renderTarget; private readonly WicRenderTarget _renderTarget;
public WicRenderTargetBitmapImpl( public WicRenderTargetBitmapImpl(
int width, PixelSize size,
int height, Vector dpi,
double dpiX,
double dpiY,
Platform.PixelFormat? pixelFormat = null) Platform.PixelFormat? pixelFormat = null)
: base(width, height, pixelFormat) : base(size, dpi, pixelFormat)
{ {
var props = new RenderTargetProperties var props = new RenderTargetProperties
{ {
DpiX = (float)dpiX, DpiX = (float)dpi.X,
DpiY = (float)dpiY, DpiY = (float)dpi.Y,
}; };
_renderTarget = new WicRenderTarget( _renderTarget = new WicRenderTarget(

7
src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs

@ -10,8 +10,8 @@ namespace Avalonia.Direct2D1.Media.Imaging
{ {
class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl class WriteableWicBitmapImpl : WicBitmapImpl, IWriteableBitmapImpl
{ {
public WriteableWicBitmapImpl(int width, int height, PixelFormat? pixelFormat) public WriteableWicBitmapImpl(PixelSize size, Vector dpi, PixelFormat? pixelFormat)
: base(width, height, pixelFormat) : base(size, dpi, pixelFormat)
{ {
} }
@ -33,8 +33,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
} }
public IntPtr Address => _lock.Data.DataPointer; public IntPtr Address => _lock.Data.DataPointer;
public int Width => _lock.Size.Width; public PixelSize Size => _lock.Size.ToAvalonia();
public int Height => _lock.Size.Height;
public int RowBytes => _lock.Stride; public int RowBytes => _lock.Stride;
public Vector Dpi { get; } = new Vector(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format => _format; public PixelFormat Format => _format;

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

@ -41,6 +41,10 @@ namespace Avalonia.Direct2D1
return new Rect(new Point(r.Left, r.Top), new Point(r.Right, r.Bottom)); return new Rect(new Point(r.Left, r.Top), new Point(r.Right, r.Bottom));
} }
public static PixelSize ToAvalonia(this Size2 p) => new PixelSize(p.Width, p.Height);
public static Vector ToAvaloniaVector(this Size2F p) => new Vector(p.Width, p.Height);
public static RawRectangleF ToSharpDX(this Rect r) public static RawRectangleF ToSharpDX(this Rect r)
{ {
return new RawRectangleF((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom); return new RawRectangleF((float)r.X, (float)r.Y, (float)r.Right, (float)r.Bottom);

3
src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs

@ -58,8 +58,7 @@ namespace Avalonia.Win32.Interop.Wpf
} }
public IntPtr Address => _bitmap.BackBuffer; public IntPtr Address => _bitmap.BackBuffer;
public int Width => _bitmap.PixelWidth; public PixelSize Size => new PixelSize(_bitmap.PixelWidth, _bitmap.PixelHeight);
public int Height => _bitmap.PixelHeight;
public int RowBytes => _bitmap.BackBufferStride; public int RowBytes => _bitmap.BackBufferStride;
public Vector Dpi { get; } public Vector Dpi { get; }
public PixelFormat Format => PixelFormat.Bgra8888; public PixelFormat Format => PixelFormat.Bgra8888;

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

@ -21,11 +21,11 @@ namespace Avalonia.Win32
UnmanagedMethods.GetClientRect(_hwnd, out rc); UnmanagedMethods.GetClientRect(_hwnd, out rc);
var width = rc.right - rc.left; var width = rc.right - rc.left;
var height = rc.bottom - rc.top; var height = rc.bottom - rc.top;
if ((_fb == null || _fb.Width != width || _fb.Height != height) && width > 0 && height > 0) if ((_fb == null || _fb.Size.Width != width || _fb.Size.Height != height) && width > 0 && height > 0)
{ {
_fb?.Deallocate(); _fb?.Deallocate();
_fb = null; _fb = null;
_fb = new WindowFramebuffer(_hwnd, width, height); _fb = new WindowFramebuffer(_hwnd, new PixelSize(width, height));
} }
return _fb; return _fb;
} }

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

@ -11,21 +11,21 @@ namespace Avalonia.Win32
private IUnmanagedBlob _bitmapBlob; private IUnmanagedBlob _bitmapBlob;
private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo;
public WindowFramebuffer(IntPtr handle, int width, int height) public WindowFramebuffer(IntPtr handle, PixelSize size)
{ {
if (width <= 0) if (size.Width <= 0)
throw new ArgumentException("width is less than zero"); throw new ArgumentException("Width is less than zero");
if (height <= 0) if (size.Height <= 0)
throw new ArgumentException("height is less than zero"); throw new ArgumentException("Height is less than zero");
_handle = handle; _handle = handle;
_bmpInfo.Init(); _bmpInfo.Init();
_bmpInfo.biPlanes = 1; _bmpInfo.biPlanes = 1;
_bmpInfo.biBitCount = 32; _bmpInfo.biBitCount = 32;
_bmpInfo.Init(); _bmpInfo.Init();
_bmpInfo.biWidth = width; _bmpInfo.biWidth = size.Width;
_bmpInfo.biHeight = -height; _bmpInfo.biHeight = -size.Height;
_bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(width * height * 4); _bitmapBlob = AvaloniaLocator.Current.GetService<IRuntimePlatform>().AllocBlob(size.Width * size.Height * 4);
} }
~WindowFramebuffer() ~WindowFramebuffer()
@ -34,7 +34,7 @@ namespace Avalonia.Win32
} }
public IntPtr Address => _bitmapBlob.Address; public IntPtr Address => _bitmapBlob.Address;
public int RowBytes => Width * 4; public int RowBytes => Size.Width * 4;
public PixelFormat Format => PixelFormat.Bgra8888; public PixelFormat Format => PixelFormat.Bgra8888;
public Vector Dpi public Vector Dpi
@ -61,19 +61,17 @@ namespace Avalonia.Win32
} }
} }
public int Width => _bmpInfo.biWidth; public PixelSize Size => new PixelSize(_bmpInfo.biWidth, _bmpInfo.biHeight);
public int Height => -_bmpInfo.biHeight;
public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,
int height = -1) int height = -1)
{ {
if (width == -1) if (width == -1)
width = Width; width = Size.Width;
if (height == -1) if (height == -1)
height = Height; height = Size.Height;
UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY,
0, (uint)Height, _bitmapBlob.Address, ref _bmpInfo, 0); 0, (uint)Size.Height, _bitmapBlob.Address, ref _bmpInfo, 0);
} }
public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1,

8
tests/Avalonia.Controls.UnitTests/ImageTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Measure_Should_Return_Correct_Size_For_No_Stretch() public void Measure_Should_Return_Correct_Size_For_No_Stretch()
{ {
var bitmap = Mock.Of<IBitmap>(x => x.PixelWidth == 50 && x.PixelHeight == 100); var bitmap = Mock.Of<IBitmap>(x => x.Size == new Size(50, 100));
var target = new Image(); var target = new Image();
target.Stretch = Stretch.None; target.Stretch = Stretch.None;
target.Source = bitmap; target.Source = bitmap;
@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Measure_Should_Return_Correct_Size_For_Fill_Stretch() public void Measure_Should_Return_Correct_Size_For_Fill_Stretch()
{ {
var bitmap = Mock.Of<IBitmap>(x => x.PixelWidth == 50 && x.PixelHeight == 100); var bitmap = Mock.Of<IBitmap>(x => x.Size == new Size(50, 100));
var target = new Image(); var target = new Image();
target.Stretch = Stretch.Fill; target.Stretch = Stretch.Fill;
target.Source = bitmap; target.Source = bitmap;
@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch() public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch()
{ {
var bitmap = Mock.Of<IBitmap>(x => x.PixelWidth == 50 && x.PixelHeight == 100); var bitmap = Mock.Of<IBitmap>(x => x.Size == new Size(50, 100));
var target = new Image(); var target = new Image();
target.Stretch = Stretch.Uniform; target.Stretch = Stretch.Uniform;
target.Source = bitmap; target.Source = bitmap;
@ -52,7 +52,7 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch() public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{ {
var bitmap = Mock.Of<IBitmap>(x => x.PixelWidth == 50 && x.PixelHeight == 100); var bitmap = Mock.Of<IBitmap>(x => x.Size == new Size(50, 100));
var target = new Image(); var target = new Image();
target.Stretch = Stretch.UniformToFill; target.Stretch = Stretch.UniformToFill;
target.Source = bitmap; target.Source = bitmap;

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

@ -29,14 +29,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media
class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface class Framebuffer : ILockedFramebuffer, IFramebufferPlatformSurface
{ {
public Framebuffer(PixelFormat fmt, int width, int height) public Framebuffer(PixelFormat fmt, PixelSize size)
{ {
Format = fmt; Format = fmt;
var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4; var bpp = fmt == PixelFormat.Rgb565 ? 2 : 4;
Width = width; Size = size;
Height = height; RowBytes = bpp * size.Width;
RowBytes = bpp * width; Address = Marshal.AllocHGlobal(size.Height * RowBytes);
Address = Marshal.AllocHGlobal(Height * RowBytes);
} }
public IntPtr Address { get; } public IntPtr Address { get; }
@ -45,12 +44,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public PixelFormat Format { get; } public PixelFormat Format { get; }
public int Height { get; } public PixelSize Size { get; }
public int RowBytes { get; } public int RowBytes { get; }
public int Width { get; }
public void Dispose() public void Dispose()
{ {
//no-op //no-op
@ -74,7 +71,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt)
{ {
var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt;
var fb = new Framebuffer(fmt, 80, 80); var fb = new Framebuffer(fmt, new PixelSize(80, 80));
var r = Avalonia.AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var r = Avalonia.AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
using (var target = r.CreateRenderTarget(new object[] { fb })) using (var target = r.CreateRenderTarget(new object[] { fb }))
using (var ctx = target.CreateDrawingContext(null)) using (var ctx = target.CreateDrawingContext(null))
@ -87,9 +84,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media
ctx.PopOpacity(); ctx.PopOpacity();
} }
var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); var bmp = new Bitmap(fmt, fb.Address, fb.Size, new Vector(96, 96), fb.RowBytes);
fb.Deallocate(); fb.Deallocate();
using (var rtb = new RenderTargetBitmap(100, 100)) using (var rtb = new RenderTargetBitmap(new PixelSize(100, 100), new Vector(96, 96)))
{ {
using (var ctx = rtb.CreateDrawingContext(null)) using (var ctx = rtb.CreateDrawingContext(null))
{ {
@ -108,7 +105,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
[InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)] [InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgba8888)]
public void WriteableBitmapShouldBeUsable(PixelFormat fmt) public void WriteableBitmapShouldBeUsable(PixelFormat fmt)
{ {
var writeableBitmap = new WriteableBitmap(256, 256, fmt); var writeableBitmap = new WriteableBitmap(new PixelSize(256, 256), new Vector(96, 96), fmt);
var data = new int[256 * 256]; var data = new int[256 * 256];
for (int y = 0; y < 256; y++) for (int y = 0; y < 256; y++)

11
tests/Avalonia.RenderTests/TestBase.cs

@ -73,22 +73,21 @@ namespace Avalonia.Direct2D1.RenderTests
var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png");
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var pixelSize = new PixelSize((int)target.Width, (int)target.Height);
var size = new Size(target.Width, target.Height);
var dpi = new Vector(96, 96);
using (RenderTargetBitmap bitmap = new RenderTargetBitmap( using (RenderTargetBitmap bitmap = new RenderTargetBitmap(pixelSize, dpi))
(int)target.Width,
(int)target.Height))
{ {
Size size = new Size(target.Width, target.Height);
target.Measure(size); target.Measure(size);
target.Arrange(new Rect(size)); target.Arrange(new Rect(size));
bitmap.Render(target); bitmap.Render(target);
bitmap.Save(immediatePath); bitmap.Save(immediatePath);
} }
using (var rtb = factory.CreateRenderTargetBitmap((int)target.Width, (int)target.Height, 96, 96)) using (var rtb = factory.CreateRenderTargetBitmap(pixelSize, dpi))
using (var renderer = new DeferredRenderer(target, rtb)) using (var renderer = new DeferredRenderer(target, rtb))
{ {
Size size = new Size(target.Width, target.Height);
target.Measure(size); target.Measure(size);
target.Arrange(new Rect(size)); target.Arrange(new Rect(size));
renderer.UnitTestUpdateScene(); renderer.UnitTestUpdateScene();

18
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -25,11 +25,7 @@ namespace Avalonia.UnitTests
return Mock.Of<IRenderTarget>(); return Mock.Of<IRenderTarget>();
} }
public IRenderTargetBitmapImpl CreateRenderTargetBitmap( public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
int width,
int height,
double dpiX,
double dpiY)
{ {
return Mock.Of<IRenderTargetBitmapImpl>(); return Mock.Of<IRenderTargetBitmapImpl>();
} }
@ -39,7 +35,10 @@ namespace Avalonia.UnitTests
return new MockStreamGeometryImpl(); return new MockStreamGeometryImpl();
} }
public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? format = default(PixelFormat?)) public IWriteableBitmapImpl CreateWriteableBitmap(
PixelSize size,
Vector dpi,
PixelFormat? format = default(PixelFormat?))
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -54,7 +53,12 @@ namespace Avalonia.UnitTests
return Mock.Of<IBitmapImpl>(); return Mock.Of<IBitmapImpl>();
} }
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public IBitmapImpl LoadBitmap(
PixelFormat format,
IntPtr data,
PixelSize size,
Vector dpi,
int stride)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

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

@ -24,7 +24,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height, double dpiX, double dpiY) public IRenderTargetBitmapImpl CreateRenderTargetBitmap(PixelSize size, Vector dpi)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -44,12 +44,12 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, int width, int height, int stride) public IBitmapImpl LoadBitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public IWriteableBitmapImpl CreateWriteableBitmap(int width, int height, PixelFormat? fmt) public IWriteableBitmapImpl CreateWriteableBitmap(PixelSize size, Vector dpi, PixelFormat? fmt)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

Loading…
Cancel
Save