Browse Source

Merge branch 'master' into fix-renderdemo-xaml

pull/2041/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
1b05157754
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 66
      azure-pipelines.yml
  2. 22
      build.cake
  3. 6
      parameters.cake
  4. 8
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  5. 5
      src/Avalonia.Controls/Image.cs
  6. 8
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  7. 2
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  8. 9
      src/Avalonia.Native/DeferredFramebuffer.cs
  9. 6
      src/Avalonia.Native/WindowImplBase.cs
  10. 39
      src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs
  11. 4
      src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs
  12. 31
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  13. 21
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  14. 29
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  15. 11
      src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs
  16. 174
      src/Avalonia.Visuals/Media/PixelSize.cs
  17. 8
      src/Avalonia.Visuals/Platform/IBitmapImpl.cs
  18. 9
      src/Avalonia.Visuals/Platform/ILockedFramebuffer.cs
  19. 32
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  20. 10
      src/Avalonia.Visuals/Platform/LockedFramebuffer.cs
  21. 8
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  22. 10
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  23. 18
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  24. 9
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  25. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  26. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  27. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  28. 27
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  29. 28
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  30. 15
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  31. 26
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  32. 20
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  33. 4
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  34. 8
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  35. 4
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  36. 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  37. 4
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  38. 3
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  39. 38
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  40. 12
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  41. 7
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WriteableWicBitmapImpl.cs
  42. 4
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  43. 3
      src/Windows/Avalonia.Win32.Interop/Wpf/WritableBitmapSurface.cs
  44. 4
      src/Windows/Avalonia.Win32/FramebufferManager.cs
  45. 28
      src/Windows/Avalonia.Win32/WindowFramebuffer.cs
  46. 12
      src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs
  47. 8
      tests/Avalonia.Controls.UnitTests/ImageTests.cs
  48. 21
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  49. 11
      tests/Avalonia.RenderTests/TestBase.cs
  50. 18
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  51. 6
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

66
azure-pipelines.yml

@ -4,20 +4,31 @@ jobs:
vmImage: 'ubuntu-16.04' vmImage: 'ubuntu-16.04'
steps: steps:
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install CastXML'
inputs: inputs:
script: | script: |
sudo apt-get update sudo apt-get update
sudo apt-get install castxml sudo apt-get install castxml
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install -g Cake.Tool --version 0.30.0
- script: | - task: CmdLine@2
export PATH="$PATH:$HOME/.dotnet/tools" displayName: 'Run Cake'
dotnet --info inputs:
printenv script: |
dotnet cake build.cake -target="Azure-Linux" -configuration="Release" export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
dotnet cake build.cake -target="Azure-Linux" -configuration="Release"
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
- job: macOS - job: macOS
pool: pool:
@ -26,6 +37,7 @@ jobs:
- task: DotNetCoreInstaller@0 - task: DotNetCoreInstaller@0
inputs: inputs:
version: '2.1.403' version: '2.1.403'
- task: Xcode@5 - task: Xcode@5
inputs: inputs:
actions: 'build' actions: 'build'
@ -35,29 +47,43 @@ jobs:
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace' xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: 'default' # Options: 8, 9, default, specifyPath xcodeVersion: 'default' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install CastXML'
inputs: inputs:
script: brew install castxml script: brew install castxml
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install -g Cake.Tool --version 0.30.0
- script: |
export COREHOST_TRACE=0 - task: CmdLine@2
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 displayName: 'Run Cake'
export DOTNET_CLI_TELEMETRY_OPTOUT=1 inputs:
which dotnet script: |
dotnet --info export COREHOST_TRACE=0
export PATH="$PATH:$HOME/.dotnet/tools" export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
dotnet --info export DOTNET_CLI_TELEMETRY_OPTOUT=1
printenv which dotnet
dotnet cake build.cake -target="Azure-OSX" -configuration="Release" dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
dotnet cake build.cake -target="Azure-OSX" -configuration="Release"
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/' pathToPublish: '$(Build.SourcesDirectory)/Build/Products/Release/'
artifactName: 'Avalonia.Native.OSX' artifactName: 'Avalonia.Native.OSX'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget' pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
@ -69,19 +95,29 @@ jobs:
vmImage: 'vs2017-win2016' vmImage: 'vs2017-win2016'
steps: steps:
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Cake'
inputs: inputs:
script: | script: |
dotnet tool install -g Cake.Tool --version 0.30.0 dotnet tool install -g Cake.Tool --version 0.30.0
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Cake'
inputs: inputs:
script: | script: |
set PATH=%PATH%;%USERPROFILE%\.dotnet\tools set PATH=%PATH%;%USERPROFILE%\.dotnet\tools
dotnet cake build.cake -target="Azure-Windows" -configuration="Release" dotnet cake build.cake -target="Azure-Windows" -configuration="Release"
- task: PublishTestResults@2
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget' pathtoPublish: '$(Build.SourcesDirectory)/artifacts/nuget'
artifactName: 'NuGet' artifactName: 'NuGet'
condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false)) condition: and(succeeded(), eq(variables['system.pullrequest.isfork'], false))
- task: PublishBuildArtifacts@1 - task: PublishBuildArtifacts@1
inputs: inputs:
pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip' pathToPublish: '$(Build.SourcesDirectory)/artifacts/zip'

22
build.cake

@ -78,6 +78,7 @@ Task("Clean-Impl")
CleanDirectory(data.ArtifactsDir); CleanDirectory(data.ArtifactsDir);
CleanDirectory(data.NugetRoot); CleanDirectory(data.NugetRoot);
CleanDirectory(data.ZipRoot); CleanDirectory(data.ZipRoot);
CleanDirectory(data.TestResultsRoot);
}); });
void DotNetCoreBuild(Parameters parameters) void DotNetCoreBuild(Parameters parameters)
@ -129,13 +130,20 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
continue; continue;
Information("Running for " + fw); Information("Running for " + fw);
DotNetCoreTest(project, var settings = new DotNetCoreTestSettings {
new DotNetCoreTestSettings { Configuration = parameters.Configuration,
Configuration = parameters.Configuration, Framework = fw,
Framework = fw, NoBuild = true,
NoBuild = true, NoRestore = true
NoRestore = true };
});
if (parameters.PublishTestResults)
{
settings.Logger = "trx";
settings.ResultsDirectory = parameters.TestResultsRoot;
}
DotNetCoreTest(project, settings);
} }
} }

6
parameters.cake

@ -22,11 +22,13 @@ public class Parameters
public bool IsReleasable { get; private set; } public bool IsReleasable { get; private set; }
public bool IsMyGetRelease { get; private set; } public bool IsMyGetRelease { get; private set; }
public bool IsNuGetRelease { get; private set; } public bool IsNuGetRelease { get; private set; }
public bool PublishTestResults { get; private set; }
public string Version { get; private set; } public string Version { get; private set; }
public DirectoryPath ArtifactsDir { get; private set; } public DirectoryPath ArtifactsDir { get; private set; }
public DirectoryPath NugetRoot { get; private set; } public DirectoryPath NugetRoot { get; private set; }
public DirectoryPath ZipRoot { get; private set; } public DirectoryPath ZipRoot { get; private set; }
public DirectoryPath BinRoot { get; private set; } public DirectoryPath BinRoot { get; private set; }
public DirectoryPath TestResultsRoot { get; private set; }
public string DirSuffix { get; private set; } public string DirSuffix { get; private set; }
public DirectoryPathCollection BuildDirs { get; private set; } public DirectoryPathCollection BuildDirs { get; private set; }
public string FileZipSuffix { get; private set; } public string FileZipSuffix { get; private set; }
@ -91,7 +93,8 @@ public class Parameters
else if (IsRunningOnAzure) else if (IsRunningOnAzure)
{ {
// Use AssemblyVersion with Build as version // Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta"; Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
PublishTestResults = true;
} }
// DIRECTORIES // DIRECTORIES
@ -99,6 +102,7 @@ public class Parameters
NugetRoot = ArtifactsDir.Combine("nuget"); NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip"); ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin"); BinRoot = ArtifactsDir.Combine("bin");
TestResultsRoot = ArtifactsDir.Combine("test-results");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj"); BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
DirSuffix = Configuration; DirSuffix = Configuration;
FileZipSuffix = Version + ".zip"; FileZipSuffix = Version + ".zip";

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

@ -20,9 +20,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
ANativeWindow_Buffer buffer; ANativeWindow_Buffer buffer;
var rc = new ARect() var rc = new ARect()
{ {
right = Width = ANativeWindow_getWidth(_window), right = ANativeWindow_getWidth(_window),
bottom = Height = ANativeWindow_getHeight(_window) bottom = ANativeWindow_getHeight(_window)
}; };
Size = new PixelSize(rc.right, rc.bottom);
ANativeWindow_lock(_window, out buffer, ref rc); ANativeWindow_lock(_window, out buffer, ref rc);
Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565
@ -41,8 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
} }
public IntPtr Address { get; set; } public IntPtr Address { get; set; }
public int Width { get; } public PixelSize Size { get; }
public int Height { get; }
public int RowBytes { get; } public int RowBytes { get; }
public Vector Dpi { get; } = new Vector(96, 96); public Vector Dpi { get; } = new Vector(96, 96);
public PixelFormat Format { get; } public PixelFormat Format { get; }

5
src/Avalonia.Controls/Image.cs

@ -58,7 +58,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
@ -84,8 +84,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

@ -73,9 +73,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;
@ -83,7 +83,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

@ -267,7 +267,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));
} }

9
src/Avalonia.Native/DeferredFramebuffer.cs

@ -18,15 +18,14 @@ namespace Avalonia.Native
{ {
_lockWindow = lockWindow; _lockWindow = lockWindow;
Address = Marshal.AllocHGlobal(width * height * 4); Address = Marshal.AllocHGlobal(width * height * 4);
Width = width; Size = new PixelSize(width, height);
Height = height;
RowBytes = width * 4; RowBytes = width * 4;
Dpi = dpi; Dpi = dpi;
Format = PixelFormat.Rgba8888; Format = PixelFormat.Rgba8888;
} }
public IntPtr Address { get; set; } public IntPtr Address { get; set; }
public int Width { get; set; } public PixelSize Size { get; set; }
public int Height { get; set; } public int Height { get; set; }
public int RowBytes { get; set; } public int RowBytes { get; set; }
public Vector Dpi { get; set; } public Vector Dpi { get; set; }
@ -66,8 +65,8 @@ namespace Avalonia.Native
X = Dpi.X, X = Dpi.X,
Y = Dpi.Y Y = Dpi.Y
}, },
Width = Width, Width = Size.Width,
Height = Height, Height = Size.Height,
PixelFormat = (AvnPixelFormat)Format, PixelFormat = (AvnPixelFormat)Format,
Stride = RowBytes Stride = RowBytes
}; };

6
src/Avalonia.Native/WindowImplBase.cs

@ -100,15 +100,13 @@ namespace Avalonia.Native
public FramebufferWrapper(AvnFramebuffer fb) public FramebufferWrapper(AvnFramebuffer fb)
{ {
Address = fb.Data; Address = fb.Data;
Width = fb.Width; Size = new PixelSize(fb.Width, fb.Height);
Height = fb.Height;
RowBytes = fb.Stride; RowBytes = fb.Stride;
Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y); Dpi = new Vector(fb.Dpi.X, fb.Dpi.Y);
Format = (PixelFormat)fb.PixelFormat; Format = (PixelFormat)fb.PixelFormat;
} }
public IntPtr Address { get; set; } public IntPtr Address { get; set; }
public int Width { get; set; } public PixelSize Size { get; set; }
public int Height { get; set; }
public int RowBytes {get;set;} public int RowBytes {get;set;}
public Vector Dpi { get; set; } public Vector Dpi { get; set; }
public PixelFormat Format { get; } public PixelFormat Format { get; }

39
src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs

@ -17,7 +17,10 @@ namespace Avalonia.Media.Fonts
/// <param name="source"></param> /// <param name="source"></param>
public FontFamilyKey(Uri source) public FontFamilyKey(Uri source)
{ {
if (source == null) throw new ArgumentNullException(nameof(source)); if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (source.AbsolutePath.Contains(".ttf")) if (source.AbsolutePath.Contains(".ttf"))
{ {
@ -28,7 +31,17 @@ namespace Avalonia.Media.Fonts
} }
else else
{ {
Location = source; if (source.AbsolutePath.Contains(".otf"))
{
var filePathWithoutExtension = source.AbsolutePath.Replace(".otf", string.Empty);
var fileNameWithoutExtension = filePathWithoutExtension.Split('.').Last();
FileName = fileNameWithoutExtension + ".otf";
Location = new Uri(source.OriginalString.Replace("." + FileName, string.Empty), UriKind.RelativeOrAbsolute);
}
else
{
Location = source;
}
} }
} }
@ -77,11 +90,20 @@ namespace Avalonia.Media.Fonts
/// </returns> /// </returns>
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
if (!(obj is FontFamilyKey other)) return false; if (!(obj is FontFamilyKey other))
{
return false;
}
if (Location != other.Location) return false; if (Location != other.Location)
{
return false;
}
if (FileName != other.FileName) return false; if (FileName != other.FileName)
{
return false;
}
return true; return true;
} }
@ -94,7 +116,10 @@ namespace Avalonia.Media.Fonts
/// </returns> /// </returns>
public override string ToString() public override string ToString()
{ {
if (FileName == null) return Location.PathAndQuery; if (FileName == null)
{
return Location.PathAndQuery;
}
var builder = new UriBuilder(Location); var builder = new UriBuilder(Location);
@ -103,4 +128,4 @@ namespace Avalonia.Media.Fonts
return builder.ToString(); return builder.ToString();
} }
} }
} }

4
src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs

@ -34,7 +34,7 @@ namespace Avalonia.Media.Fonts
{ {
var availableAssets = s_assetLoader.GetAssets(location); var availableAssets = s_assetLoader.GetAssets(location);
var matchingAssets = availableAssets.Where(x => x.absolutePath.EndsWith(".ttf")); var matchingAssets = availableAssets.Where(x => x.absolutePath.EndsWith(".ttf") || x.absolutePath.EndsWith(".otf"));
return matchingAssets.Select(x => GetAssetUri(x.absolutePath, x.assembly)); return matchingAssets.Select(x => GetAssetUri(x.absolutePath, x.assembly));
} }
@ -53,7 +53,7 @@ namespace Avalonia.Media.Fonts
var compareTo = location.AbsolutePath + "." + fileName.Split('*').First(); var compareTo = location.AbsolutePath + "." + fileName.Split('*').First();
var matchingResources = var matchingResources =
availableResources.Where(x => x.absolutePath.Contains(compareTo) && x.absolutePath.EndsWith(".ttf")); availableResources.Where(x => x.absolutePath.Contains(compareTo) && (x.absolutePath.EndsWith(".ttf") || x.absolutePath.EndsWith(".otf")));
return matchingResources.Select(x => GetAssetUri(x.absolutePath, x.assembly)); return matchingResources.Select(x => GetAssetUri(x.absolutePath, x.assembly));
} }

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>
/// Version of the pixel data /// Version of the pixel data

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

@ -356,7 +356,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)
{ {
@ -380,7 +380,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);
} }
@ -432,8 +432,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;
} }
} }

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit 8e4700d4b24935ed5400e5e0d6fce96b5b4a317a Subproject commit 8abbe09592668efb573ac4d5548ba2d7e464ba78

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

@ -142,7 +142,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();
} }
@ -391,7 +391,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,
@ -401,7 +401,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; }
public int Version { get; } = 1; public int Version { get; } = 1;
@ -91,4 +90,4 @@ namespace Avalonia.Skia
context.Canvas.DrawImage(_image, sourceRect, destRect, paint); context.Canvas.DrawImage(_image, sourceRect, destRect, paint);
} }
} }
} }

28
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -67,34 +67,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,
GrContext = GrContext GrContext = GrContext
@ -123,9 +117,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);
} }
} }
} }

15
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -16,7 +16,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;
@ -28,12 +27,12 @@ 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; _disableLcdRendering = createInfo.DisableTextLcdRendering;
_grContext = createInfo.GrContext; _grContext = createInfo.GrContext;
_surface = CreateSurface(createInfo.GrContext, PixelWidth, PixelHeight, createInfo.Format); _surface = CreateSurface(createInfo.GrContext, PixelSize.Width, PixelSize.Height, createInfo.Format);
_canvas = _surface?.Canvas; _canvas = _surface?.Canvas;
@ -74,7 +73,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,
GrContext = _grContext GrContext = _grContext
@ -84,10 +83,10 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public int PixelWidth { get; } public Vector Dpi { get; }
/// <inheritdoc /> /// <inheritdoc />
public int PixelHeight { get; } public PixelSize PixelSize { get; }
public int Version { get; private set; } = 1; public int Version { get; private set; } = 1;

26
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -22,13 +22,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);
@ -38,24 +38,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; }
public int Version { get; private set; } = 1; public int Version { get; private set; } = 1;
@ -146,10 +145,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;

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 int Version { get; protected set; } = 1; public int Version { get; protected set; } = 1;
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

@ -44,10 +44,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)
{ {
@ -57,19 +57,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),
@ -79,17 +82,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)
{ {
} }
@ -36,8 +36,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,

12
src/iOS/Avalonia.iOS/EmulatedFramebuffer.cs

@ -23,12 +23,11 @@ namespace Avalonia.iOS
var frame = view.Frame; var frame = view.Frame;
_viewWidth = frame.Width; _viewWidth = frame.Width;
_viewHeight = frame.Height; _viewHeight = frame.Height;
Width = (int) frame.Width * factor; Size = new PixelSize((int)frame.Width * factor, (int)frame.Height * factor);
Height = (int) frame.Height * factor; RowBytes = Size.Width * 4;
RowBytes = Width * 4;
Dpi = new Vector(96, 96) * factor; Dpi = new Vector(96, 96) * factor;
Format = PixelFormat.Rgba8888; Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes); Address = Marshal.AllocHGlobal(Size.Height * RowBytes);
} }
public void Dispose() public void Dispose()
@ -37,7 +36,7 @@ namespace Avalonia.iOS
return; return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast; var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
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))
using (var image = bContext.ToImage()) using (var image = bContext.ToImage())
using (var context = UIGraphics.GetCurrentContext()) using (var context = UIGraphics.GetCurrentContext())
@ -52,8 +51,7 @@ namespace Avalonia.iOS
} }
public IntPtr Address { get; private set; } public IntPtr Address { get; private set; }
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; }

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.PixelSize == new PixelSize(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.PixelSize == new PixelSize(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.PixelSize == new PixelSize(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.PixelSize == new PixelSize(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